@yottagraph-app/aether-instructions 1.1.27 → 1.1.29

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.
@@ -102,15 +102,15 @@ Then read these files to understand what's available:
102
102
 
103
103
  1. `DESIGN.md` -- project vision and current status
104
104
  2. `broadchurch.yaml` -- project config (name, gateway URL, etc.)
105
- 3. **The `api` cursor rule** -- this is critical. It describes the Query Server, the platform's primary data source. Build against platform APIs, not external sources.
105
+ 3. **The `data` cursor rule** -- this is critical. It describes the Query Server, the platform's primary data source. Build against platform APIs, not external sources.
106
106
  4. **`.cursor/skills/`** — Each subdirectory is one skill. List them, open each skill’s entry (usually `SKILL.md`) and follow its structure to learn what is documented (APIs, schemas, helpers, etc.).
107
107
  5. `.cursor/rules/` -- scan rule names to know what other patterns are available
108
108
 
109
- **Important: Use the platform's data.** This app runs on the Lovelace platform, which provides a Query Server with entities, news, filings, sentiment, relationships, events, and more. Read the `api` rule and the skills under `.cursor/skills/` to understand what data is available. Use `getSchema()` to discover entity types and properties at runtime.
109
+ **Important: Use the platform's data.** This app runs on the Lovelace platform, which provides a Query Server with entities, news, filings, sentiment, relationships, events, and more. Read the `data` rule and the skills under `.cursor/skills/` to understand what data is available. Use `getSchema()` to discover entity types and properties at runtime.
110
110
 
111
111
  Key capabilities:
112
112
 
113
- - **Query Server / Elemental API** -- the primary data source. Use `useElementalClient()` from `@yottagraph-app/elemental-api/client`. See the `api` rule.
113
+ - **Query Server / Elemental API** -- the primary data source. Use `useElementalClient()` from `@yottagraph-app/elemental-api/client`. See the `data` rule.
114
114
  - **KV storage** -- always available for preferences and lightweight data (see `pref` rule)
115
115
  - **Neon Postgres** -- check if `DATABASE_URL` is in `.env` for database access (see `server` rule)
116
116
  - **AI agent chat** -- use the `useAgentChat` composable to build a chat UI for deployed agents
@@ -100,6 +100,29 @@ cp "$TEMP_DIR/package/commands/"* .cursor/commands/ 2>/dev/null || true
100
100
  cp -r "$TEMP_DIR/package/skills/"* .cursor/skills/ 2>/dev/null || true
101
101
  ```
102
102
 
103
+ ### Data-mode variant overlay
104
+
105
+ If this project uses **mcp-only** (or another non-default mode), re-apply the same overlay `init-project.js` uses. Read the saved mode:
106
+
107
+ ```bash
108
+ MODE=$(tr -d '\n' < .cursor/.aether-data-mode 2>/dev/null || echo "api-mcp")
109
+ PKG="$TEMP_DIR/package"
110
+ if [ "$MODE" != "api-mcp" ] && [ -d "$PKG/variants/$MODE/rules" ]; then
111
+ cp "$PKG/variants/$MODE/rules/"* .cursor/rules/ 2>/dev/null || true
112
+ fi
113
+ if [ "$MODE" != "api-mcp" ] && [ -d "$PKG/variants/$MODE/commands" ]; then
114
+ cp "$PKG/variants/$MODE/commands/"* .cursor/commands/ 2>/dev/null || true
115
+ fi
116
+ if [ "$MODE" != "api-mcp" ] && [ -d "$PKG/variants/$MODE/skills" ]; then
117
+ cp -r "$PKG/variants/$MODE/skills/"* .cursor/skills/ 2>/dev/null || true
118
+ fi
119
+ if [ "$MODE" = "mcp-only" ]; then
120
+ rm -rf .cursor/skills/elemental-api
121
+ fi
122
+ ```
123
+
124
+ If `.cursor/.aether-data-mode` is missing, skip the overlay (defaults to **api-mcp**).
125
+
103
126
  ---
104
127
 
105
128
  ## Step 6: Write Manifest and Version Marker
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@yottagraph-app/aether-instructions",
3
- "version": "1.1.27",
3
+ "version": "1.1.29",
4
4
  "description": "Cursor rules, commands, and skills for Aether development",
5
5
  "files": [
6
6
  "rules",
7
7
  "commands",
8
- "skills"
8
+ "skills",
9
+ "variants"
9
10
  ],
10
11
  "repository": {
11
12
  "type": "git",
package/rules/aether.mdc CHANGED
@@ -8,7 +8,7 @@ alwaysApply: true
8
8
 
9
9
  **Structure:** `pages/` (file-based routing), `components/`, `composables/`, `server/api/`, `agents/` (Python ADK), `mcp-servers/` (Python FastMCP).
10
10
 
11
- **Data:** The Query Server (Elemental API) is the primary data source -- entities, news, filings, sentiment, relationships, events. Use `useElementalClient()` from `@yottagraph-app/elemental-api/client`. Do NOT call external APIs for data the platform provides. Discovery-first: use `getSchema()` to discover entity types and properties at runtime. Skill docs: `skills/elemental-api/` (API endpoints) and `skills/data-model/` (Lovelace entity types, properties, relationships, YAML schemas per fetch source; `SKILL.md` first). Run `npm install` if those directories are empty. Lovelace MCP servers may also be configured (see `api` rule) but are optional — check your tool list before assuming they're available.
11
+ **Data:** This app runs on the Lovelace platform -- entities, news, filings, sentiment, relationships, events. See the `data` rule for access patterns and gotchas. Skill docs: `skills/data-model/` (entity types, properties, relationships; `SKILL.md` first). Do NOT call external APIs for data the platform provides.
12
12
 
13
13
  **Storage:** KV (Upstash Redis) for preferences and lightweight state via `Pref<T>` from `usePrefsStore()`. Neon Postgres for relational data if connected (check `.env` for `DATABASE_URL`).
14
14
 
@@ -18,4 +18,4 @@ alwaysApply: true
18
18
 
19
19
  **First action for a new project:** Run `/build_my_app`.
20
20
 
21
- **Task-specific rules:** `architecture` (project structure, navigation, server routes, agents, MCP), `api` (Elemental API client, schema discovery, gotchas, optional MCP servers), `design` (DESIGN.md workflow, feature docs), `ui` (page templates, layout patterns), `cookbook` (copy-paste UI patterns), `pref` (KV preferences), `branding` (colors, fonts), `server` (Nitro routes, Neon Postgres, server-side Elemental API), `something-broke` (error recovery, build failures).
21
+ **Task-specific rules:** `architecture` (project structure, navigation, server routes, agents, MCP), `data` (Elemental API / data access patterns and gotchas), `cookbook` (copy-paste UI patterns), `cookbook-data` (data-fetching recipes), `design` (DESIGN.md workflow, feature docs), `ui` (page templates, layout patterns), `pref` (KV preferences), `branding` (colors, fonts), `server` (Nitro routes, Neon Postgres), `server-data` (server-side Elemental API from routes), `agents` (ADK agents), `agents-data` (agents calling Elemental API), `something-broke` (error recovery, build failures).
@@ -0,0 +1,72 @@
1
+ ---
2
+ description: "ADK agents calling the Elemental API via broadchurch_auth and local testing env vars."
3
+ alwaysApply: false
4
+ globs: agents/**
5
+ ---
6
+
7
+ # Agents: Elemental API (Query Server)
8
+
9
+ ## Connecting to the Elemental API
10
+
11
+ Use `broadchurch_auth` for all Elemental API calls. It lives at
12
+ `agents/broadchurch_auth.py` and is automatically bundled into each agent
13
+ directory at deploy time. It handles:
14
+
15
+ - Routing through the Broadchurch Portal gateway proxy in production
16
+ (no direct QS credentials needed — the portal handles Auth0 M2M auth)
17
+ - Authenticating to the proxy with a per-tenant API key from
18
+ `broadchurch.yaml` (`gateway.qs_api_key`)
19
+ - Falling back to `ELEMENTAL_API_URL` + `ELEMENTAL_API_TOKEN` env vars
20
+ for local dev
21
+
22
+ ```python
23
+ try:
24
+ from broadchurch_auth import elemental_client
25
+ except ImportError:
26
+ from .broadchurch_auth import elemental_client
27
+
28
+ def get_schema() -> dict:
29
+ """Get the yottagraph schema."""
30
+ resp = elemental_client.get("/elemental/metadata/schema")
31
+ resp.raise_for_status()
32
+ return resp.json()
33
+
34
+ def find_entities(expression: str, limit: int = 10) -> dict:
35
+ """Search for entities."""
36
+ resp = elemental_client.post("/elemental/find", data={"expression": expression, "limit": str(limit)})
37
+ resp.raise_for_status()
38
+ return resp.json()
39
+ ```
40
+
41
+ The try/except is required because the import path differs between local
42
+ dev (`agents/` on sys.path → absolute import) and Agent Engine runtime
43
+ (code packaged inside an ADK module → relative import).
44
+
45
+ **Do NOT** hardcode URLs or manually handle auth tokens. Do NOT read
46
+ `broadchurch.yaml` directly — `broadchurch_auth` handles all of this.
47
+
48
+ Key endpoints:
49
+ - `GET /elemental/metadata/schema` — entity types and properties
50
+ - `POST /elemental/find` — search for entities by expression
51
+ - `POST /entities/search` — search for entities by name (batch, scored)
52
+ - `POST /elemental/entities/properties` — get entity property values
53
+
54
+ Requirements for agents using the Elemental API (add to `requirements.txt`):
55
+ ```
56
+ google-auth>=2.20.0
57
+ pyyaml>=6.0
58
+ ```
59
+
60
+ ## Local testing: Elemental API env vars
61
+
62
+ When testing agents locally with `adk web`, for agents that call the Elemental API:
63
+
64
+ ```bash
65
+ export ELEMENTAL_API_URL=https://stable-query.lovelace.ai
66
+ export ELEMENTAL_API_TOKEN=<your-token>
67
+ ```
68
+
69
+ In production, all Elemental API calls go through the Portal Gateway at
70
+ `{gateway_url}/api/qs/{org_id}/...`. The agent sends `X-Api-Key` (from
71
+ `broadchurch.yaml`) and the portal injects its own Auth0 M2M token
72
+ upstream.
package/rules/agents.mdc CHANGED
@@ -47,56 +47,8 @@ Key rules:
47
47
  - Use `google-adk` for the agent framework, `httpx` for HTTP calls
48
48
  - Pin dependency versions in `requirements.txt` for reproducible deployments
49
49
 
50
- ## Connecting to the Elemental API
51
-
52
- Use `broadchurch_auth` for all Elemental API calls. It lives at
53
- `agents/broadchurch_auth.py` and is automatically bundled into each agent
54
- directory at deploy time. It handles:
55
-
56
- - Routing through the Broadchurch Portal gateway proxy in production
57
- (no direct QS credentials needed — the portal handles Auth0 M2M auth)
58
- - Authenticating to the proxy with a per-tenant API key from
59
- `broadchurch.yaml` (`gateway.qs_api_key`)
60
- - Falling back to `ELEMENTAL_API_URL` + `ELEMENTAL_API_TOKEN` env vars
61
- for local dev
62
-
63
- ```python
64
- try:
65
- from broadchurch_auth import elemental_client
66
- except ImportError:
67
- from .broadchurch_auth import elemental_client
68
-
69
- def get_schema() -> dict:
70
- """Get the yottagraph schema."""
71
- resp = elemental_client.get("/elemental/metadata/schema")
72
- resp.raise_for_status()
73
- return resp.json()
74
-
75
- def find_entities(expression: str, limit: int = 10) -> dict:
76
- """Search for entities."""
77
- resp = elemental_client.post("/elemental/find", data={"expression": expression, "limit": str(limit)})
78
- resp.raise_for_status()
79
- return resp.json()
80
- ```
81
-
82
- The try/except is required because the import path differs between local
83
- dev (`agents/` on sys.path → absolute import) and Agent Engine runtime
84
- (code packaged inside an ADK module → relative import).
85
-
86
- **Do NOT** hardcode URLs or manually handle auth tokens. Do NOT read
87
- `broadchurch.yaml` directly — `broadchurch_auth` handles all of this.
88
-
89
- Key endpoints:
90
- - `GET /elemental/metadata/schema` — entity types and properties
91
- - `POST /elemental/find` — search for entities by expression
92
- - `POST /entities/search` — search for entities by name (batch, scored)
93
- - `POST /elemental/entities/properties` — get entity property values
94
-
95
- Requirements for agents using the Elemental API (add to `requirements.txt`):
96
- ```
97
- google-auth>=2.20.0
98
- pyyaml>=6.0
99
- ```
50
+ For agents that call the **Elemental API** (`broadchurch_auth`, endpoints,
51
+ local `ELEMENTAL_*` env vars), see the `agents-data` rule.
100
52
 
101
53
  ## Local Testing
102
54
 
@@ -113,10 +65,6 @@ export GOOGLE_CLOUD_PROJECT=broadchurch
113
65
  export GOOGLE_CLOUD_LOCATION=us-central1
114
66
  export GOOGLE_GENAI_USE_VERTEXAI=1
115
67
 
116
- # For agents that call the Elemental API:
117
- export ELEMENTAL_API_URL=https://stable-query.lovelace.ai
118
- export ELEMENTAL_API_TOKEN=<your-token>
119
-
120
68
  adk web # Opens browser UI at http://127.0.0.1:8000/dev-ui/
121
69
  ```
122
70
 
@@ -163,19 +111,20 @@ adk deploy agent_engine \
163
111
 
164
112
  ## How Agents Reach the App
165
113
 
166
- Once deployed, the agent is reachable through the Portal Gateway:
114
+ Once deployed, the app talks to Agent Engine directly — the portal is only
115
+ in the auth path:
167
116
 
168
117
  ```
169
- App (useAgentChat composable)
170
- POST NUXT_PUBLIC_GATEWAY_URL/api/agents/{tenantId}/{agentId}/query
171
- Portal Gateway proxies to Vertex AI Agent Engine (streamQuery)
118
+ Browser → Tenant Nitro Server (POST /api/agent/:agentId/stream)
119
+ Portal /authorize (gets short-lived tenant SA token, cached 15 min)
120
+ Agent Engine :streamQuery (direct, single hop)
172
121
  → Agent runs (may invoke tools, make multiple LLM calls)
173
- Gateway collects the ADK event stream, extracts final text
174
- → App displays it
122
+ Nitro re-emits ADK events as clean SSE to the browser
175
123
  ```
176
124
 
177
125
  The gateway URL and tenant ID come from `broadchurch.yaml` (injected as
178
- `NUXT_PUBLIC_GATEWAY_URL` and `NUXT_PUBLIC_TENANT_ORG_ID`).
126
+ `NUXT_PUBLIC_GATEWAY_URL` and `NUXT_PUBLIC_TENANT_ORG_ID`). The token is
127
+ minted for the tenant's GCP service account via SA impersonation.
179
128
 
180
129
  ### Agent Discovery
181
130
 
@@ -207,7 +156,8 @@ Each agent entry has:
207
156
  | `engine_id` | `string` | Vertex AI Agent Engine resource ID — used as `{agentId}` in query/stream URLs |
208
157
 
209
158
  The `engine_id` is the key value — it becomes the `{agentId}` path
210
- parameter in `POST /api/agents/{tenantId}/{agentId}/query`.
159
+ parameter in `/api/agent/{agentId}/stream` (the local Nitro route) and
160
+ the portal's `/authorize` endpoint.
211
161
 
212
162
  **How agents get populated:** The portal discovers agents from two sources:
213
163
  1. **Firestore** — agents registered by the deploy workflow (`deploy-agent.yml`
@@ -232,39 +182,24 @@ const agents = config.value?.agents ?? [];
232
182
  const agentId = agents[0]?.engine_id; // use as {agentId} in query URLs
233
183
  ```
234
184
 
235
- ### Gateway Request
236
-
237
- ```
238
- POST {NUXT_PUBLIC_GATEWAY_URL}/api/agents/{tenantId}/{agentId}/query
239
- Content-Type: application/json
240
-
241
- { "message": "Summarize the latest 8-K filing", "session_id": "optional-session-id" }
242
- ```
185
+ ### Streaming via the Local Nitro Route
243
186
 
244
- Omit `session_id` on the first message the gateway auto-creates one.
187
+ The `useAgentChat` composable calls `POST /api/agent/:agentId/stream` (the
188
+ local Nitro route). This route handles token acquisition, session creation,
189
+ and SSE parsing — the browser just sees clean Server-Sent Events.
245
190
 
246
- ### Gateway Response Format
191
+ For custom server-side agent calls (e.g. background tasks), you can call the
192
+ portal's `/authorize` endpoint directly and then use the token against
193
+ Agent Engine:
247
194
 
248
- ```json
249
- {
250
- "output": "The agent's final text response",
251
- "session_id": "session-abc-123",
252
- "events": [ /* raw ADK event stream */ ]
253
- }
195
+ ```typescript
196
+ const auth = await $fetch(`${gatewayUrl}/api/agents/${orgId}/${agentId}/authorize`, {
197
+ method: 'POST',
198
+ body: { user_id: 'background-task', create_session: false },
199
+ });
200
+ // auth.token, auth.engine_url, auth.expires_in
254
201
  ```
255
202
 
256
- | Field | Type | Description |
257
- |---|---|---|
258
- | `output` | `string \| any[]` | Usually the agent's final text. Falls back to the raw `events` array if the gateway couldn't extract text. |
259
- | `session_id` | `string` | Pass back on subsequent messages to continue the conversation. |
260
- | `events` | `any[]` | Full ADK event stream. Useful for debugging or building UIs that show intermediate agent steps. |
261
-
262
- **Important:** `output` is NOT always a string. When the agent's response
263
- involves complex tool chains, the gateway's server-side extraction may miss
264
- the text and return the raw events array instead. Always use
265
- `extractAgentText()` to parse `output` safely rather than treating it as a
266
- string directly.
267
-
268
203
  ### ADK Event Stream Format
269
204
 
270
205
  The `events` array contains one object per step the agent took. Each event
@@ -292,13 +227,11 @@ than objects — always handle both.
292
227
 
293
228
  ### Parsing Agent Responses (Non-Streaming)
294
229
 
295
- For one-shot calls (server routes, background tasks) where streaming isn't
296
- needed, use the buffered `/query` endpoint with `extractAgentText`:
230
+ For non-streaming use cases (e.g. extracting text from a buffered response),
231
+ use `extractAgentText`:
297
232
 
298
233
  ```typescript
299
234
  import { extractAgentText } from '~/composables/useAgentChat';
300
-
301
- const response = await $fetch(url, { method: 'POST', body: { message } });
302
235
  const text = extractAgentText(response.output);
303
236
  ```
304
237
 
@@ -307,28 +240,20 @@ JSON-string or object elements), single event objects, and several legacy
307
240
  Agent Engine response shapes. It skips `functionCall` / `functionResponse`
308
241
  events and returns the agent's final text.
309
242
 
310
- ### Streaming Responses
243
+ ### Streaming SSE Events
311
244
 
312
- The gateway also exposes a streaming endpoint that returns Server-Sent
313
- Events as the agent executes. **The `useAgentChat` composable uses this by
314
- default** — it tries `/stream` first and falls back to `/query`
245
+ The local Nitro route (`POST /api/agent/:agentId/stream`) returns
246
+ Server-Sent Events. The `useAgentChat` composable handles these
315
247
  automatically.
316
248
 
317
- ```
318
- POST {NUXT_PUBLIC_GATEWAY_URL}/api/agents/{tenantId}/{agentId}/stream
319
- Content-Type: application/json
320
-
321
- { "message": "Summarize the latest 8-K filing", "session_id": "optional" }
322
- ```
323
-
324
- The response is an SSE stream with these event types:
249
+ SSE event types:
325
250
 
326
251
  | Event | Data Shape | Description |
327
252
  |---|---|---|
328
253
  | `text` | `{ "text": "..." }` | Agent text output (replaces previous text) |
329
254
  | `function_call` | `{ "name": "...", "args": {...} }` | Agent is calling a tool |
330
255
  | `function_response` | `{ "name": "...", "response": {...} }` | Tool returned a result |
331
- | `error` | `{ "message": "..." }` | Error during processing |
256
+ | `error` | `{ "message": "...", "code": "..." }` | Error during processing (`code` is `PERMISSION_DENIED` for IAM failures) |
332
257
  | `done` | `{ "session_id": "...", "text": "..." }` | Stream complete with final text |
333
258
 
334
259
  For custom agent UIs that need streaming, import `readSSE`:
@@ -9,7 +9,7 @@ Aether is an app framework built on Nuxt 3 + Vue 3 + Vuetify 3 + TypeScript. It
9
9
 
10
10
  **Tech stack**: Nuxt 3 (SPA), Vue 3 Composition API (`<script setup>`), Vuetify 3, TypeScript (required), Auth0 (automatic).
11
11
 
12
- **Data source**: This app runs on the Lovelace platform. The Query Server (Elemental API) is the primary data source -- use it for entities, news, filings, sentiment, relationships, and events. See the `api` rule. Do not call external APIs for data the platform provides.
12
+ **Data source**: This app runs on the Lovelace platform (entities, news, filings, sentiment, relationships, events). See the `data` rule for how your app accesses that data. Do not call external APIs for data the platform provides.
13
13
 
14
14
  ## Project Structure
15
15
 
@@ -28,11 +28,10 @@ mcp-servers/ # MCP servers (Python, deploy to Cloud Run)
28
28
 
29
29
  | Store | Purpose | When to use |
30
30
  |---|---|---|
31
- | Query Server (Elemental API) | Lovelace Knowledge Graph | Entities, news, relationships, events, sentiment, filings |
31
+ | Platform data (Query Server / Postgres per your architecture) | Lovelace knowledge graph or synced local data | See the `data` rule |
32
32
  | KV (Upstash Redis) | User preferences, lightweight state | Settings, watchlists, UI state that should persist |
33
33
  | Neon Postgres | App-specific relational data | Custom tables, complex queries (if connected — check `DATABASE_URL` in `.env`) |
34
34
 
35
- Use `useElementalClient()` for Query Server data (see `api` rule).
36
35
  Use `Pref<T>` or `usePrefsStore()` for KV (see `pref` rule).
37
36
  Check `.env` for `DATABASE_URL` before using Neon Postgres (see `server` rule).
38
37
 
@@ -113,12 +112,12 @@ using them later when adding features to an established app.
113
112
  ## Server Routes
114
113
 
115
114
  Nitro server routes live in `server/api/` and deploy with the app to Vercel.
116
- Use server routes when you need to proxy external APIs (avoid CORS), call
117
- the Elemental API server-side, or keep secrets off the client. Call them
118
- from client code with `$fetch('/api/my-data/fetch')`.
115
+ Use server routes when you need to proxy external APIs (avoid CORS), access
116
+ data server-side, or keep secrets off the client. Call them from client code
117
+ with `$fetch('/api/my-data/fetch')`.
119
118
 
120
- See the `server` rule for file-routing conventions, Neon Postgres patterns,
121
- and server-side Elemental API access via `useRuntimeConfig()`.
119
+ See the `server` rule for file-routing conventions and Neon Postgres patterns.
120
+ See `server-data` when calling the platform Query Server from server routes.
122
121
 
123
122
  ## Beyond the UI: Agents, MCP Servers, and Server Routes
124
123
 
@@ -126,7 +125,21 @@ Aether apps are more than just a Nuxt SPA. The project contains three additional
126
125
 
127
126
  ### `agents/` -- ADK Agents (Python)
128
127
 
129
- Each subdirectory is a self-contained Python agent that deploys to Vertex AI Agent Engine. See the `agents` cursor rule for development patterns. Agents are deployed via the Broadchurch Portal UI or the `/deploy_agent` Cursor command, both of which trigger `deploy-agent.yml`. Once deployed, agents are reachable through the Portal Gateway (`NUXT_PUBLIC_GATEWAY_URL`). Use the `useAgentChat` composable to build a chat UI that talks to them.
128
+ Each subdirectory is a self-contained Python agent that deploys to Vertex AI Agent Engine. See the `agents` cursor rule for development patterns. Agents are deployed via the Broadchurch Portal UI or the `/deploy_agent` Cursor command, both of which trigger `deploy-agent.yml`. Use the `useAgentChat` composable to build a chat UI that talks to them.
129
+
130
+ **Agent query path:** The app talks to Agent Engine directly — the portal is
131
+ only in the auth path. The flow is:
132
+
133
+ 1. Tenant Nitro route (`server/api/agent/[agentId]/stream.post.ts`) calls the
134
+ Portal Gateway's `/authorize` endpoint to get a short-lived (15 min) GCP
135
+ access token minted for the tenant's service account.
136
+ 2. The Nitro route uses that token to call Agent Engine's `:streamQuery`
137
+ directly — streaming data does NOT flow through the portal.
138
+ 3. Tokens are cached server-side for their TTL.
139
+
140
+ If you see a 403 from Agent Engine, the tenant's service account is missing
141
+ IAM permissions (`roles/aiplatform.user`). Check the Broadchurch portal's
142
+ project detail page for IAM health status.
130
143
 
131
144
  ### `mcp-servers/` -- MCP Servers (Python)
132
145
 
@@ -141,14 +154,15 @@ Tenant-specific configuration generated during provisioning. Contains GCP projec
141
154
  The template ships composables for interacting with the Lovelace platform.
142
155
  Use these to build whatever UI fits the app:
143
156
 
144
- - **`useAgentChat()`** -- Send messages to deployed ADK agents through the
145
- Portal Gateway. Handles streaming, session management, and response
146
- parsing. See the `agents` cursor rule for gateway endpoints and response
147
- formats.
157
+ - **`useAgentChat()`** -- Send messages to deployed ADK agents. Uses the
158
+ local Nitro streaming route (`/api/agent/:agentId/stream`) which calls
159
+ Agent Engine directly with a tenant SA token. Handles streaming, session
160
+ management, and response parsing. Shows clear error messages when IAM
161
+ permissions are missing. See the `agents` cursor rule for details.
148
162
  - **`useTenantConfig()`** -- Fetch the tenant's runtime config (deployed
149
163
  agents, feature flags, MCP servers) from the Portal Gateway.
150
- - **`useElementalClient()`** -- Query Server client for entities, news,
151
- filings, etc. See the `api` rule.
164
+ - **`useElementalClient()`** -- When using the Elemental API from the client,
165
+ see the `data` rule.
152
166
  - **`usePrefsStore()` / `Pref<T>`** -- KV-backed user preferences. See
153
167
  the `pref` rule.
154
168