@yottagraph-app/aether-instructions 1.1.3 → 1.1.4

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.3",
3
+ "version": "1.1.4",
4
4
  "description": "Cursor rules, commands, and skills for Aether development",
5
5
  "files": [
6
6
  "rules",
package/rules/agents.mdc CHANGED
@@ -168,8 +168,9 @@ Once deployed, the agent is reachable through the Portal Gateway:
168
168
  ```
169
169
  Chat UI (pages/chat.vue)
170
170
  → POST NUXT_PUBLIC_GATEWAY_URL/api/agents/{tenantId}/{agentId}/query
171
- → Portal Gateway proxies to Vertex AI Agent Engine
172
- → Agent runs, returns response
171
+ → Portal Gateway proxies to Vertex AI Agent Engine (streamQuery)
172
+ → Agent runs (may invoke tools, make multiple LLM calls)
173
+ → Gateway collects the ADK event stream, extracts final text
173
174
  → Chat UI displays it
174
175
  ```
175
176
 
@@ -177,6 +178,126 @@ The gateway URL and tenant ID come from `broadchurch.yaml` (injected as
177
178
  `NUXT_PUBLIC_GATEWAY_URL` and `NUXT_PUBLIC_TENANT_ORG_ID`). The chat page
178
179
  discovers available agents from the Portal config endpoint.
179
180
 
181
+ ### Gateway Request
182
+
183
+ ```
184
+ POST {NUXT_PUBLIC_GATEWAY_URL}/api/agents/{tenantId}/{agentId}/query
185
+ Content-Type: application/json
186
+
187
+ { "message": "Summarize the latest 8-K filing", "session_id": "optional-session-id" }
188
+ ```
189
+
190
+ Omit `session_id` on the first message — the gateway auto-creates one.
191
+
192
+ ### Gateway Response Format
193
+
194
+ ```json
195
+ {
196
+ "output": "The agent's final text response",
197
+ "session_id": "session-abc-123",
198
+ "events": [ /* raw ADK event stream */ ]
199
+ }
200
+ ```
201
+
202
+ | Field | Type | Description |
203
+ |---|---|---|
204
+ | `output` | `string \| any[]` | Usually the agent's final text. Falls back to the raw `events` array if the gateway couldn't extract text. |
205
+ | `session_id` | `string` | Pass back on subsequent messages to continue the conversation. |
206
+ | `events` | `any[]` | Full ADK event stream. Useful for debugging or building UIs that show intermediate agent steps. |
207
+
208
+ **Important:** `output` is NOT always a string. When the agent's response
209
+ involves complex tool chains, the gateway's server-side extraction may miss
210
+ the text and return the raw events array instead. Always use
211
+ `extractAgentText()` to parse `output` safely rather than treating it as a
212
+ string directly.
213
+
214
+ ### ADK Event Stream Format
215
+
216
+ The `events` array contains one object per step the agent took. Each event
217
+ has `content.parts[]` where each part is one of:
218
+
219
+ ```json
220
+ { "text": "The agent's text response..." }
221
+ { "functionCall": { "name": "search", "args": { "q": "AAPL" } } }
222
+ { "functionResponse": { "name": "search", "response": { "results": [...] } } }
223
+ ```
224
+
225
+ A typical stream for an agent that uses a tool:
226
+
227
+ ```json
228
+ [
229
+ { "content": { "parts": [{ "functionCall": { "name": "search", "args": {"q": "AAPL 8-K"} } }], "role": "model" } },
230
+ { "content": { "parts": [{ "functionResponse": { "name": "search", "response": {"results": ["..."]} } }], "role": "tool" } },
231
+ { "content": { "parts": [{ "text": "Here is the summary of the 8-K filing..." }], "role": "model" } }
232
+ ]
233
+ ```
234
+
235
+ The final text is the last `text` part that isn't in a `functionCall` or
236
+ `functionResponse` event. Events may also arrive as JSON strings rather
237
+ than objects — always handle both.
238
+
239
+ ### Parsing Agent Responses (Non-Streaming)
240
+
241
+ For one-shot calls (server routes, background tasks) where streaming isn't
242
+ needed, use the buffered `/query` endpoint with `extractAgentText`:
243
+
244
+ ```typescript
245
+ import { extractAgentText } from '~/composables/useAgentChat';
246
+
247
+ const response = await $fetch(url, { method: 'POST', body: { message } });
248
+ const text = extractAgentText(response.output);
249
+ ```
250
+
251
+ `extractAgentText` handles: plain strings, ADK event stream arrays (with
252
+ JSON-string or object elements), single event objects, and several legacy
253
+ Agent Engine response shapes. It skips `functionCall` / `functionResponse`
254
+ events and returns the agent's final text.
255
+
256
+ ### Streaming Responses
257
+
258
+ The gateway also exposes a streaming endpoint that returns Server-Sent
259
+ Events as the agent executes. **The `useAgentChat` composable uses this by
260
+ default** — it tries `/stream` first and falls back to `/query`
261
+ automatically.
262
+
263
+ ```
264
+ POST {NUXT_PUBLIC_GATEWAY_URL}/api/agents/{tenantId}/{agentId}/stream
265
+ Content-Type: application/json
266
+
267
+ { "message": "Summarize the latest 8-K filing", "session_id": "optional" }
268
+ ```
269
+
270
+ The response is an SSE stream with these event types:
271
+
272
+ | Event | Data Shape | Description |
273
+ |---|---|---|
274
+ | `text` | `{ "text": "..." }` | Agent text output (replaces previous text) |
275
+ | `function_call` | `{ "name": "...", "args": {...} }` | Agent is calling a tool |
276
+ | `function_response` | `{ "name": "...", "response": {...} }` | Tool returned a result |
277
+ | `error` | `{ "message": "..." }` | Error during processing |
278
+ | `done` | `{ "session_id": "...", "text": "..." }` | Stream complete with final text |
279
+
280
+ For custom agent UIs that need streaming, import `readSSE`:
281
+
282
+ ```typescript
283
+ import { readSSE } from '~/composables/useAgentChat';
284
+
285
+ const res = await fetch(streamUrl, {
286
+ method: 'POST',
287
+ headers: { 'Content-Type': 'application/json' },
288
+ body: JSON.stringify({ message: 'Hello' }),
289
+ });
290
+
291
+ for await (const { event, data } of readSSE(res)) {
292
+ if (event === 'text') console.log('Agent says:', data.text);
293
+ if (event === 'function_call') console.log('Calling:', data.name);
294
+ if (event === 'done') console.log('Session:', data.session_id);
295
+ }
296
+ ```
297
+
298
+ The `done` event always includes the final extracted text, so you don't
299
+ need to track text deltas yourself.
300
+
180
301
  ## Agent Design Guidelines
181
302
 
182
303
  - Keep agents focused: one agent per domain or task type
package/rules/api.mdc CHANGED
@@ -51,6 +51,57 @@ Types are also imported from the client:
51
51
  import type { NamedEntityReport, GetNEIDResponse } from '@yottagraph-app/elemental-api/client';
52
52
  ```
53
53
 
54
+ ### Client Method Quick Reference
55
+
56
+ All methods return data directly and throw on non-2xx responses.
57
+
58
+ **Entity search and lookup:**
59
+
60
+ | Method | Signature | Purpose |
61
+ |---|---|---|
62
+ | `getNEID` | `(params: { entityName, maxResults?, includeNames? })` | Lookup entity by name |
63
+ | `findEntities` | `(body: FindEntitiesBody)` | Expression-based search (see `find.md`) |
64
+ | `getNamedEntityReport` | `(neid: string)` | Entity details (name, aliases, type) |
65
+ | `getEntityDetails` | `(neid: string)` | Alias for entity reports |
66
+
67
+ **Properties and schema:**
68
+
69
+ | Method | Signature | Purpose |
70
+ |---|---|---|
71
+ | `getSchema` | `()` | All entity types (flavors) and properties (PIDs) |
72
+ | `getPropertyValues` | `(body: { eids: string, pids: string })` | Property values (eids/pids are JSON-stringified arrays!) |
73
+ | `summarizeProperty` | `(pid: number)` | Summary stats for a property |
74
+
75
+ **Relationships and graph:**
76
+
77
+ | Method | Signature | Purpose |
78
+ |---|---|---|
79
+ | `getLinkedEntities` | `(neid, params?: { entity_type?, link_type? })` | Linked entities (person/org/location only) |
80
+ | `getLinks` | `(sourceNeid, targetNeid, params?)` | Links between two specific entities |
81
+ | `getLinkCounts` | `(sourceNeid, targetNeid)` | Link counts between entities |
82
+ | `getNeighborhood` | `(centerNeid, params?)` | Neighboring entities |
83
+ | `getGraphLayout` | `(centerNeid, params?)` | Graph layout for visualization |
84
+
85
+ **News, events, and sentiment:**
86
+
87
+ | Method | Signature | Purpose |
88
+ |---|---|---|
89
+ | `getArticle` | `(artid: string)` | Article by ID |
90
+ | `getArticleText` | `(artid: string)` | Article full text |
91
+ | `getEvent` | `(eveid: string)` | Event by ID |
92
+ | `getEventsForEntity` | `(params: { neid, startTime?, endTime? })` | Events involving an entity |
93
+ | `getMentions` | `(params: { neid, startTime?, endTime? })` | Mention codes for entities |
94
+ | `getMentionCounts` | `(params: { neid, ... })` | Bucketed mention counts |
95
+ | `getNamedEntitySentiment` | `(neid: string)` | Sentiment for an entity |
96
+
97
+ **Other:**
98
+
99
+ | Method | Signature | Purpose |
100
+ |---|---|---|
101
+ | `getHealth` | `()` | Health check |
102
+ | `getStatus` | `()` | Server status and capabilities |
103
+ | `adaMessage` | `(body: AdaMessageBody)` | Ada AI chat |
104
+
54
105
  ## Discovery-First Pattern
55
106
 
56
107
  The knowledge graph contains many entity types and properties, and new datasets
@@ -81,17 +132,27 @@ knowledge of what's in the graph.
81
132
 
82
133
  ## API Gotchas
83
134
 
84
- > **WARNING -- `getSchema()` response nesting**: The generated TypeScript types
85
- > put `flavors` and `properties` at the top level, but the actual API response
86
- > nests them under a `schema` key. Using `response.properties` directly will
87
- > crash with `Cannot read properties of undefined`. Always use:
135
+ ### `getSchema()` response is nested WILL crash if you don't handle it
136
+
137
+ The generated TypeScript types suggest `response.properties` and
138
+ `response.flavors` exist at the top level. **They don't.** The API nests
139
+ them under `response.schema`. This mismatch between types and reality
140
+ causes `Cannot read properties of undefined` every time.
88
141
 
89
142
  ```typescript
143
+ // WRONG — will crash at runtime despite TypeScript compiling fine:
144
+ const res = await client.getSchema();
145
+ const props = res.properties; // undefined!
146
+
147
+ // CORRECT — always access through .schema:
90
148
  const res = await client.getSchema();
91
149
  const properties = res.schema?.properties ?? (res as any).properties ?? [];
92
150
  const flavors = res.schema?.flavors ?? (res as any).flavors ?? [];
93
151
  ```
94
152
 
153
+ The `(res as any).properties` fallback is there in case the API is ever
154
+ fixed to match the types. Use this pattern every time.
155
+
95
156
  > **WARNING -- `getPropertyValues()` takes JSON-stringified arrays**: The `eids`
96
157
  > and `pids` parameters must be JSON-encoded strings, NOT native arrays. The
97
158
  > TypeScript type is `string`, not `string[]`. Passing a raw array will silently
@@ -139,6 +200,48 @@ See the **cookbook** rule for a full "Get filings for a company" recipe.
139
200
  (`POST /elemental/find`). Best for filtered searches (by type, property,
140
201
  relationship). See `find.md` for the expression language.
141
202
 
203
+ ## Common Entity Relationships
204
+
205
+ The knowledge graph connects entities through relationship properties
206
+ (`data_nindex` PIDs). These are the most common patterns:
207
+
208
+ | From | PID | To | How to traverse |
209
+ |---|---|---|---|
210
+ | Organization | `filed` | Document (filings) | `getPropertyValues` with the `filed` PID on the org's NEID |
211
+ | Organization | `employs` | Person | `getLinkedEntities` (person is a graph node type) |
212
+ | Person | `employed_by` | Organization | `getLinkedEntities` (organization is a graph node type) |
213
+ | Organization | `headquartered_in` | Location | `getLinkedEntities` (location is a graph node type) |
214
+ | Any entity | `related_to` | Any entity | `getLinkedEntities` for person/org/location; `getPropertyValues` for others |
215
+
216
+ **Key constraint:** `getLinkedEntities` only works for three target types:
217
+ **person**, **organization**, **location**. For documents, filings,
218
+ articles, financial instruments, and events, use `getPropertyValues` with
219
+ the relationship PID. See the cookbook rule (recipe #7) for a full filings
220
+ example.
221
+
222
+ **Traversal pattern for non-graph-node types:**
223
+
224
+ ```typescript
225
+ // 1. Get the PID for the relationship
226
+ const schema = await client.getSchema();
227
+ const pids = schema.schema?.properties ?? [];
228
+ const filedPid = pids.find((p: any) => p.name === 'filed')?.pid;
229
+
230
+ // 2. Get linked entity IDs via getPropertyValues
231
+ const res = await client.getPropertyValues({
232
+ eids: JSON.stringify([orgNeid]),
233
+ pids: JSON.stringify([filedPid]),
234
+ });
235
+
236
+ // 3. Pad IDs to 20 chars to form valid NEIDs
237
+ const docNeids = res.values.map((v: any) => String(v.value).padStart(20, '0'));
238
+
239
+ // 4. Get details for each linked entity
240
+ const reports = await Promise.all(
241
+ docNeids.map((neid: string) => client.getNamedEntityReport(neid)),
242
+ );
243
+ ```
244
+
142
245
  ## Error Handling
143
246
 
144
247
  ```typescript
@@ -133,9 +133,17 @@ Tenant-specific configuration generated during provisioning. Contains GCP projec
133
133
 
134
134
  ## Built-in Pages
135
135
 
136
- These pages ship with the template and can be kept, modified, or removed based on the app's needs:
137
-
138
- - `pages/chat.vue` -- Agent chat UI. Talks to deployed ADK agents through the Portal Gateway. Keep if the app uses AI agents.
139
- - `pages/mcp.vue` -- MCP Explorer. Browse and test MCP server tools. Keep if the app uses MCP servers.
140
- - `pages/entity-lookup.vue` -- Entity search tool. Useful for looking up NEIDs. Keep or remove based on the app.
136
+ These pages ship with the template and can be kept, modified, or removed
137
+ based on the app's needs:
138
+
139
+ - `pages/chat.vue` -- Agent chat UI. Talks to deployed ADK agents through
140
+ the Portal Gateway with streaming support. Keep if the app uses AI agents.
141
+ - `pages/mcp.vue` -- MCP Explorer. Browse and test MCP server tools. Keep
142
+ if the app uses MCP servers.
143
+ - `pages/entity-lookup.vue` -- Entity search tool. Useful for looking up
144
+ NEIDs. Keep or remove based on the app.
145
+
146
+ If any of these pages are missing from your project, your project may have
147
+ been created from an older template version. Copy them from the latest
148
+ aether-dev source or run `/update_instructions` to check for updates.
141
149