@yottagraph-app/aether-instructions 1.1.18 → 1.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottagraph-app/aether-instructions",
3
- "version": "1.1.18",
3
+ "version": "1.1.19",
4
4
  "description": "Cursor rules, commands, and skills for Aether development",
5
5
  "files": [
6
6
  "rules",
package/rules/agents.mdc CHANGED
@@ -88,9 +88,9 @@ dev (`agents/` on sys.path → absolute import) and Agent Engine runtime
88
88
 
89
89
  Key endpoints:
90
90
  - `GET /elemental/metadata/schema` — entity types and properties
91
- - `POST /elemental/find` — search for entities
91
+ - `POST /elemental/find` — search for entities by expression
92
+ - `POST /entities/search` — search for entities by name (batch, scored)
92
93
  - `POST /elemental/entities/properties` — get entity property values
93
- - `GET /entities/lookup?q=<name>` — look up entity by name
94
94
 
95
95
  Requirements for agents using the Elemental API (add to `requirements.txt`):
96
96
  ```
@@ -352,6 +352,31 @@ for await (const { event, data } of readSSE(res)) {
352
352
  The `done` event always includes the final extracted text, so you don't
353
353
  need to track text deltas yourself.
354
354
 
355
+ ## useAgentChat Gotcha — Vue Reactivity
356
+
357
+ When building a chat UI with `useAgentChat`, do NOT hold a local reference
358
+ to a message object after pushing it into the `messages` array. Vue's
359
+ reactivity only tracks mutations made through the reactive Proxy — writes
360
+ to the original plain object are invisible to the template.
361
+
362
+ ```typescript
363
+ // WRONG — local ref bypasses Vue's reactivity, UI won't update:
364
+ const msg: ChatMessage = { id: '...', role: 'agent', text: '', streaming: true };
365
+ messages.value.push(msg);
366
+ msg.text = 'hello'; // data changes, but Vue doesn't know
367
+ msg.streaming = false; // template still shows typing indicator
368
+
369
+ // CORRECT — access through the reactive array:
370
+ messages.value.push({ id: '...', role: 'agent', text: '', streaming: true });
371
+ const idx = messages.value.length - 1;
372
+ messages.value[idx].text = 'hello'; // Vue detects this
373
+ messages.value[idx].streaming = false; // template re-renders
374
+ ```
375
+
376
+ The `useAgentChat` composable uses the correct pattern internally (via an
377
+ `updateAgent()` helper that writes through the array index). If you build a
378
+ custom chat composable or modify `sendMessage`, follow the same approach.
379
+
355
380
  ## Agent Design Guidelines
356
381
 
357
382
  - Keep agents focused: one agent per domain or task type
package/rules/api.mdc CHANGED
@@ -82,15 +82,18 @@ import { useElementalClient } from '@yottagraph-app/elemental-api/client';
82
82
 
83
83
  const client = useElementalClient();
84
84
 
85
- const results = await client.getNEID({ entityName: 'Apple', maxResults: 5 });
86
- const report = await client.getNamedEntityReport(results.neids[0]);
87
85
  const schema = await client.getSchema();
86
+ const report = await client.getNamedEntityReport('00508379502570440213');
87
+ const entities = await client.findEntities({
88
+ expression: JSON.stringify({ type: 'comparison', comparison: { operator: 'string_like', pid: 8, value: 'Apple' } }),
89
+ limit: 5,
90
+ });
88
91
  ```
89
92
 
90
93
  Types are also imported from the client:
91
94
 
92
95
  ```typescript
93
- import type { NamedEntityReport, GetNEIDResponse } from '@yottagraph-app/elemental-api/client';
96
+ import type { NamedEntityReport } from '@yottagraph-app/elemental-api/client';
94
97
  ```
95
98
 
96
99
  ### Client Method Quick Reference
@@ -101,11 +104,15 @@ All methods return data directly and throw on non-2xx responses.
101
104
 
102
105
  | Method | Signature | Purpose |
103
106
  |---|---|---|
104
- | `getNEID` | `(params: { entityName, maxResults?, includeNames? })` | Lookup entity by name |
105
107
  | `findEntities` | `(body: FindEntitiesBody)` | Expression-based search (see `find.md`) |
106
108
  | `getNamedEntityReport` | `(neid: string)` | Entity details (name, aliases, type) |
107
109
  | `getEntityDetails` | `(neid: string)` | Alias for entity reports |
108
110
 
111
+ > **Entity search**: Use `findEntities()` with `string_like` on the name PID
112
+ > for name-based searches, or call `POST /entities/search` directly via
113
+ > `$fetch` for batch name resolution with scored ranking (this endpoint is
114
+ > not wrapped by the generated client).
115
+
109
116
  **Properties and schema:**
110
117
 
111
118
  | Method | Signature | Purpose |
@@ -118,11 +125,7 @@ All methods return data directly and throw on non-2xx responses.
118
125
 
119
126
  | Method | Signature | Purpose |
120
127
  |---|---|---|
121
- | `getLinkedEntities` | `(neid, params?: { entity_type?, link_type? })` | Linked entities (person/org/location only) |
122
- | `getLinks` | `(sourceNeid, targetNeid, params?)` | Links between two specific entities |
123
- | `getLinkCounts` | `(sourceNeid, targetNeid)` | Link counts between entities |
124
- | `getNeighborhood` | `(centerNeid, params?)` | Neighboring entities |
125
- | `getGraphLayout` | `(centerNeid, params?)` | Graph layout for visualization |
128
+ | `findEntities` | `(body: { expression, limit? })` | Find linked entities via `linked` expression (see `find.md`) |
126
129
 
127
130
  **Other:**
128
131
 
@@ -205,6 +208,22 @@ const type = res.report?.type ?? (res as any).type;
205
208
  The `(res as any).name` fallback handles the case where the client is
206
209
  eventually fixed to unwrap the response.
207
210
 
211
+ ### Relationship property values need zero-padding to form valid NEIDs
212
+
213
+ Relationship properties (`data_nindex`) return linked entity IDs as raw
214
+ numbers (e.g. `4926132345040704022`). These must be **zero-padded to 20
215
+ characters** to form valid NEIDs. This is easy to miss and causes silent
216
+ failures — `getNamedEntityReport` returns a 404, `getPropertyValues`
217
+ returns empty results.
218
+
219
+ ```typescript
220
+ // WRONG — raw value is NOT a valid NEID:
221
+ const filingId = res.values[0].value; // "4926132345040704022" (19 chars)
222
+
223
+ // CORRECT — always pad to 20 characters:
224
+ const filingNeid = String(res.values[0].value).padStart(20, '0'); // "04926132345040704022"
225
+ ```
226
+
208
227
  > **WARNING -- `getPropertyValues()` takes JSON-stringified arrays**: The `eids`
209
228
  > and `pids` parameters must be JSON-encoded strings, NOT native arrays. The
210
229
  > TypeScript type is `string`, not `string[]`. Passing a raw array will silently
@@ -217,31 +236,41 @@ const values = await client.getPropertyValues({
217
236
  });
218
237
  ```
219
238
 
220
- ### `getLinkedEntities` only supports graph node types
221
-
222
- `getLinkedEntities(neid, { entity_type: ['document'] })` will fail at
223
- runtime with _"entity_type document not a valid graph node type"_. The
224
- `/entities/{neid}/linked` endpoint only supports three entity types:
225
- **person**, **organization**, **location**. Documents, filings, articles,
226
- financial instruments, events, and all other types are excluded — even
227
- though the schema shows relationships like `filed` connecting organizations
228
- to documents.
229
-
230
- **Why?** The knowledge graph has two layers. The **graph layer** models
231
- first-class entities (people, organizations, locations) as nodes with
232
- edges between them this is what `getLinkedEntities` traverses. The
233
- **property layer** attaches everything else (documents, filings, financial
234
- instruments, events) as property values on graph nodes. Documents aren't
235
- "lesser" entities — they're stored differently because they're associated
236
- with specific graph nodes rather than standing independently in the graph.
237
- Understanding this distinction helps you generalize: if a target entity
238
- type doesn't appear in the `getLinkedEntities` response, it's a property-
239
- layer entity and you need `getPropertyValues` with the relationship PID.
240
-
241
- To traverse relationships to non-graph-node types, use `getPropertyValues`
242
- with the relationship PID instead. Relationship properties (`data_nindex`)
243
- return linked entity IDs as values. Zero-pad the returned IDs to 20
244
- characters to form valid NEIDs.
239
+ ### Traversing relationships: graph-layer vs property-layer entities
240
+
241
+ The knowledge graph has two layers:
242
+
243
+ - **Graph layer** people, organizations, and locations are first-class
244
+ nodes with edges between them. Use `findEntities()` with a `linked`
245
+ expression to traverse these (see `find.md`).
246
+ - **Property layer** documents, filings, articles, financial instruments,
247
+ events, and all other types are attached as property values on graph
248
+ nodes. Use `getPropertyValues()` with the relationship PID to traverse
249
+ these.
250
+
251
+ If you need to find people linked to an organization, use `findEntities`
252
+ with a `linked` expression:
253
+
254
+ ```typescript
255
+ const res = await client.findEntities({
256
+ expression: JSON.stringify({
257
+ type: 'linked',
258
+ linked: {
259
+ to_entity: orgNeid,
260
+ distance: 1,
261
+ pids: [isOfficerPid, isDirectorPid, worksAtPid],
262
+ direction: 'incoming',
263
+ },
264
+ }),
265
+ limit: 50,
266
+ });
267
+ const personNeids = (res as any).eids ?? [];
268
+ ```
269
+
270
+ For non-graph-node types (filings, documents, etc.), use `getPropertyValues`
271
+ with the relationship PID. Relationship properties (`data_nindex`) return
272
+ linked entity IDs as values. Zero-pad the returned IDs to 20 characters
273
+ to form valid NEIDs.
245
274
 
246
275
  ```typescript
247
276
  const pidMap = await getPropertyPidMap(client);
@@ -255,64 +284,43 @@ const docNeids = (res.values ?? []).map((v) => String(v.value).padStart(20, '0')
255
284
 
256
285
  See the **cookbook** rule for a full "Get filings for a company" recipe.
257
286
 
258
- ### `getNEID()` vs `findEntities()` for entity search
259
-
260
- - **`client.getNEID()`** -- simple single-entity lookup by name
261
- (`GET /entities/lookup`). Best for resolving one company/person name.
262
- - **`client.findEntities()`** -- expression-based search
263
- (`POST /elemental/find`). Best for filtered searches (by type, property,
264
- relationship). See `find.md` for the expression language.
287
+ ### Entity Search
265
288
 
266
- ## Common Entity Relationships
289
+ Use `client.findEntities()` (`POST /elemental/find`) for entity search.
290
+ It supports filtering by type, property value, and relationship via the
291
+ expression language (see `find.md`). For name-based lookups, use
292
+ `string_like` on the name property (PID 8).
267
293
 
268
- The knowledge graph connects entities through relationship properties
269
- (`data_nindex` PIDs). These are the most common patterns:
294
+ For batch name resolution with scored ranking, call `POST /entities/search`
295
+ directly via `$fetch` (not on the generated client). See the
296
+ **elemental-api skill** (`entities.md`) for request/response shapes.
270
297
 
271
- | From | PID | To | How to traverse |
272
- |---|---|---|---|
273
- | Organization | `filed` | Document (filings) | `getPropertyValues` with the `filed` PID on the org's NEID |
274
- | Organization | `employs` | Person | `getLinkedEntities` (person is a graph node type) |
275
- | Person | `employed_by` | Organization | `getLinkedEntities` (organization is a graph node type) |
276
- | Organization | `headquartered_in` | Location | `getLinkedEntities` (location is a graph node type) |
277
- | Any entity | `related_to` | Any entity | `getLinkedEntities` for person/org/location; `getPropertyValues` for others |
298
+ ## Traversing Relationships
278
299
 
279
- **Key constraint:** `getLinkedEntities` only works for three target types:
280
- **person**, **organization**, **location**. For documents, filings,
281
- articles, financial instruments, and events, use `getPropertyValues` with
282
- the relationship PID. See the cookbook rule (recipe #7) for a full filings
283
- example.
284
-
285
- **Traversal pattern for non-graph-node types:**
286
-
287
- ```typescript
288
- // 1. Get the PID for the relationship
289
- const schema = await client.getSchema();
290
- const pids = schema.schema?.properties ?? [];
291
- const filedPid = pids.find((p: any) => p.name === 'filed')?.pid;
300
+ Relationships between entities are discoverable via the schema — use
301
+ `getSchema()` to find relationship properties (`data_nindex` type) and
302
+ their PIDs. Do NOT hardcode relationship names or PIDs; they can change
303
+ as the knowledge graph evolves. See the **data-model skill** for
304
+ source-specific schemas.
292
305
 
293
- // 2. Get linked entity IDs via getPropertyValues
294
- const res = await client.getPropertyValues({
295
- eids: JSON.stringify([orgNeid]),
296
- pids: JSON.stringify([filedPid]),
297
- });
306
+ **Two traversal methods:**
298
307
 
299
- // 3. Pad IDs to 20 chars to form valid NEIDs
300
- const docNeids = (res.values ?? []).map((v: any) => String(v.value).padStart(20, '0'));
308
+ - **Graph-layer entities** (person, organization, location): Use
309
+ `findEntities()` with a `linked` expression. See `find.md`.
310
+ - **Property-layer entities** (documents, filings, articles, etc.): Use
311
+ `getPropertyValues()` with the relationship PID. Values are entity IDs
312
+ that must be zero-padded to 20 characters.
301
313
 
302
- // 4. Get details for each linked entity (response is nested under .report)
303
- const reports = await Promise.all(
304
- docNeids.map(async (neid: string) => {
305
- const r = await client.getNamedEntityReport(neid);
306
- return r.report ?? r;
307
- }),
308
- );
309
- ```
314
+ See the **cookbook** rule (recipe #7) for a full example.
310
315
 
311
316
  ## Error Handling
312
317
 
313
318
  ```typescript
314
319
  try {
315
- const data = await client.getNEID({ entityName: '...' });
320
+ const data = await client.findEntities({
321
+ expression: JSON.stringify({ type: 'comparison', comparison: { operator: 'string_like', pid: 8, value: 'Apple' } }),
322
+ limit: 5,
323
+ });
316
324
  } catch (error) {
317
325
  console.error('API Error:', error);
318
326
  showError('Failed to load data. Please try again.');
@@ -53,19 +53,35 @@ Search for entities by name and display results.
53
53
  const error = ref<string | null>(null);
54
54
  const searched = ref(false);
55
55
 
56
+ function getSearchUrl() {
57
+ const config = useRuntimeConfig();
58
+ const gw = (config.public as any).gatewayUrl as string;
59
+ const org = (config.public as any).tenantOrgId as string;
60
+ return `${gw}/api/qs/${org}/entities/search`;
61
+ }
62
+
63
+ function getApiKey() {
64
+ return (useRuntimeConfig().public as any).qsApiKey as string;
65
+ }
66
+
56
67
  async function search() {
57
68
  if (!query.value.trim()) return;
58
69
  loading.value = true;
59
70
  error.value = null;
60
71
  searched.value = true;
61
72
  try {
62
- const res = await client.getNEID({
63
- entityName: query.value.trim(),
64
- maxResults: 10,
65
- includeNames: true,
73
+ const res = await $fetch<any>(getSearchUrl(), {
74
+ method: 'POST',
75
+ headers: { 'Content-Type': 'application/json', 'X-Api-Key': getApiKey() },
76
+ body: {
77
+ queries: [{ queryId: 1, query: query.value.trim() }],
78
+ maxResults: 10,
79
+ includeNames: true,
80
+ },
66
81
  });
67
- results.value = res.neids || [];
68
- names.value = res.names || [];
82
+ const matches = res?.results?.[0]?.matches ?? [];
83
+ results.value = matches.map((m: any) => m.neid);
84
+ names.value = matches.map((m: any) => m.name || m.neid);
69
85
  } catch (e: any) {
70
86
  error.value = e.message || 'Search failed';
71
87
  results.value = [];
@@ -374,9 +390,10 @@ Two-column layout with selectable list and detail panel.
374
390
 
375
391
  Fetch Edgar filings (or any relationship-linked documents) for an organization.
376
392
 
377
- **Important:** `getLinkedEntities` only supports graph node types (person,
378
- organization, location). Documents, filings, articles, and other types are
379
- NOT supported use `getPropertyValues` with the relationship PID instead.
393
+ **Important:** For graph-layer entities (person, organization, location),
394
+ use `findEntities` with a `linked` expression. For property-layer entities
395
+ (documents, filings, articles), use `getPropertyValues` with the
396
+ relationship PID. See the `api` rule for the two-layer architecture.
380
397
 
381
398
  ```vue
382
399
  <template>
@@ -430,22 +447,38 @@ NOT supported — use `getPropertyValues` with the relationship PID instead.
430
447
  return new Map(properties.map((p: any) => [p.name, p.pid]));
431
448
  }
432
449
 
450
+ function getSearchUrl() {
451
+ const config = useRuntimeConfig();
452
+ const gw = (config.public as any).gatewayUrl as string;
453
+ const org = (config.public as any).tenantOrgId as string;
454
+ return `${gw}/api/qs/${org}/entities/search`;
455
+ }
456
+
457
+ function getApiKey() {
458
+ return (useRuntimeConfig().public as any).qsApiKey as string;
459
+ }
460
+
433
461
  async function search() {
434
462
  if (!query.value.trim()) return;
435
463
  loading.value = true;
436
464
  error.value = null;
437
465
  searched.value = true;
438
466
  try {
439
- const lookup = await client.getNEID({
440
- entityName: query.value.trim(),
441
- maxResults: 1,
442
- includeNames: true,
467
+ const res = await $fetch<any>(getSearchUrl(), {
468
+ method: 'POST',
469
+ headers: { 'Content-Type': 'application/json', 'X-Api-Key': getApiKey() },
470
+ body: {
471
+ queries: [{ queryId: 1, query: query.value.trim(), flavors: ['organization'] }],
472
+ maxResults: 1,
473
+ includeNames: true,
474
+ },
443
475
  });
444
- if (!lookup.neids?.length) {
476
+ const matches = res?.results?.[0]?.matches ?? [];
477
+ if (!matches.length) {
445
478
  filings.value = [];
446
479
  return;
447
480
  }
448
- const orgNeid = lookup.neids[0];
481
+ const orgNeid = matches[0].neid;
449
482
 
450
483
  const pidMap = await getPropertyPidMap(client);
451
484
  const filedPid = pidMap.get('filed');
@@ -62,12 +62,14 @@ flavors:
62
62
  description: "A news article or press release being processed"
63
63
  display_name: "Article"
64
64
  mergeability: not_mergeable
65
+ strong_id_properties: ["newsdata_id"]
65
66
  passive: true
66
67
 
67
68
  - name: "publication"
68
69
  description: "A news publication or media outlet identified by its home URL"
69
70
  display_name: "Publication"
70
71
  mergeability: not_mergeable
72
+ strong_id_properties: ["homeUrl"]
71
73
  passive: true
72
74
 
73
75
  # =============================================================================
@@ -389,6 +391,14 @@ properties:
389
391
  domain_flavors: ["article"]
390
392
  passive: true
391
393
 
394
+ - name: "newsdata_id"
395
+ type: string
396
+ description: "Unique article identifier from the NewsData API"
397
+ display_name: "NewsData ID"
398
+ mergeability: not_mergeable
399
+ domain_flavors: ["article"]
400
+ passive: true
401
+
392
402
  - name: "title"
393
403
  type: string
394
404
  description: "Title of the entity"
@@ -11,17 +11,15 @@ This skill provides documentation for the Lovelace Elemental API, the primary in
11
11
 
12
12
  Use this skill when you need to:
13
13
  - Look up entities (companies, people, organizations) by name or ID
14
- - Get sentiment analysis for entities over time
15
- - Find news mentions and articles about entities
16
- - Explore relationships and connections between entities
17
- - Retrieve events involving specific entities
14
+ - Search for entities by type, property values, or relationships using the expression language
15
+ - Get entity metadata (types/flavors, properties)
18
16
  - Build knowledge graphs of entity networks
19
17
 
20
18
  ## Quick Start
21
19
 
22
20
  1. **Find an entity**: Use `entities.md` to look up a company or person by name and get their NEID (Named Entity ID)
23
- 2. **Get information**: Use the NEID to query sentiment, mentions, relationships, or events
24
- 3. **Dive deeper**: Retrieve full article details or explore connected entities
21
+ 2. **Get information**: Use the NEID to query entity properties or explore the graph
22
+ 3. **Search**: Use `find.md` for expression-based entity searches
25
23
 
26
24
  ## Files in This Skill
27
25
 
@@ -29,9 +27,5 @@ See [overview.md](overview.md) for descriptions of each endpoint category:
29
27
  - `entities.md` - Entity search, details, and properties
30
28
  - `find.md` - Expression language for searching entities by type, property values, and relationships
31
29
  - `schema.md` - Data model: entity types (flavors), properties, and schema endpoints
32
- - `sentiment.md` - Sentiment analysis
33
- - `articles.md` - Articles mentioning entities and full article content
34
- - `events.md` - Events involving entities
35
- - `relationships.md` - Entity connections
36
30
  - `graph.md` - Visual graph generation
37
31
  - `server.md` - Server status and health