@agent-native/core 0.63.2 → 0.63.3

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.
Files changed (83) hide show
  1. package/dist/styles/blocks.css +25 -3
  2. package/docs/content/a2a-protocol.md +48 -10
  3. package/docs/content/actions.md +35 -16
  4. package/docs/content/agent-mentions.md +25 -32
  5. package/docs/content/agent-surfaces.md +31 -0
  6. package/docs/content/agent-teams.md +17 -14
  7. package/docs/content/agent-web-surfaces.md +24 -15
  8. package/docs/content/authentication.md +21 -0
  9. package/docs/content/automations.md +37 -21
  10. package/docs/content/blueprint-installer.md +7 -0
  11. package/docs/content/cli-adapters.md +7 -0
  12. package/docs/content/client.md +14 -0
  13. package/docs/content/cloneable-saas.md +14 -0
  14. package/docs/content/code-agents-ui.md +27 -0
  15. package/docs/content/components.md +21 -0
  16. package/docs/content/context-awareness.md +33 -48
  17. package/docs/content/creating-templates.md +43 -52
  18. package/docs/content/cross-app-sso.md +41 -0
  19. package/docs/content/database.md +41 -21
  20. package/docs/content/deployment.md +23 -6
  21. package/docs/content/dispatch.md +27 -0
  22. package/docs/content/drop-in-agent.md +26 -27
  23. package/docs/content/durable-resume.md +13 -1
  24. package/docs/content/embedding-sdk.md +14 -0
  25. package/docs/content/evals.md +14 -0
  26. package/docs/content/extensions.md +19 -0
  27. package/docs/content/external-agents.md +38 -1
  28. package/docs/content/faq.md +14 -0
  29. package/docs/content/file-uploads.md +20 -0
  30. package/docs/content/frames.md +14 -0
  31. package/docs/content/getting-started.md +34 -16
  32. package/docs/content/harness-agents.md +14 -0
  33. package/docs/content/human-approval.md +15 -30
  34. package/docs/content/key-concepts.md +37 -0
  35. package/docs/content/local-file-mode.md +26 -19
  36. package/docs/content/mcp-apps.md +36 -1
  37. package/docs/content/mcp-clients.md +49 -0
  38. package/docs/content/mcp-protocol.md +36 -0
  39. package/docs/content/messaging.md +34 -8
  40. package/docs/content/migration-workbench.md +7 -0
  41. package/docs/content/multi-app-workspace.md +29 -16
  42. package/docs/content/multi-tenancy.md +14 -0
  43. package/docs/content/native-chat-ui.md +20 -3
  44. package/docs/content/notifications.md +30 -0
  45. package/docs/content/observability.md +20 -1
  46. package/docs/content/observational-memory.md +14 -0
  47. package/docs/content/onboarding.md +32 -41
  48. package/docs/content/plan-plugin.md +14 -0
  49. package/docs/content/pr-visual-recap.md +14 -0
  50. package/docs/content/processors.md +7 -0
  51. package/docs/content/progress.md +23 -0
  52. package/docs/content/pure-agent-apps.md +7 -0
  53. package/docs/content/real-time-collaboration.md +19 -18
  54. package/docs/content/recurring-jobs.md +22 -19
  55. package/docs/content/routing.md +8 -0
  56. package/docs/content/sandbox-adapters.md +19 -0
  57. package/docs/content/security.md +38 -0
  58. package/docs/content/server.md +47 -25
  59. package/docs/content/sharing.md +50 -0
  60. package/docs/content/skills-guide.md +27 -42
  61. package/docs/content/template-analytics.md +60 -0
  62. package/docs/content/template-assets.md +68 -0
  63. package/docs/content/template-brain.md +83 -0
  64. package/docs/content/template-calendar.md +74 -0
  65. package/docs/content/template-chat.md +19 -0
  66. package/docs/content/template-clips.md +113 -0
  67. package/docs/content/template-content.md +133 -0
  68. package/docs/content/template-design.md +55 -0
  69. package/docs/content/template-dispatch.md +50 -0
  70. package/docs/content/template-forms.md +59 -0
  71. package/docs/content/template-mail.md +62 -0
  72. package/docs/content/template-plan.md +74 -0
  73. package/docs/content/template-slides.md +82 -0
  74. package/docs/content/template-videos.md +62 -0
  75. package/docs/content/tracking.md +21 -0
  76. package/docs/content/using-your-agent.md +14 -0
  77. package/docs/content/voice-input.md +31 -10
  78. package/docs/content/what-is-agent-native.md +25 -13
  79. package/docs/content/workspace-connections.md +49 -0
  80. package/docs/content/workspace-management.md +24 -0
  81. package/docs/content/workspace.md +50 -19
  82. package/docs/content/writing-agent-instructions.md +7 -0
  83. package/package.json +1 -1
@@ -9,6 +9,13 @@ Agent-native apps are designed to be secure by default. The framework provides a
9
9
 
10
10
  ## What you get for free, and what you own {#what-you-own}
11
11
 
12
+ ```an-diagram title="Defense in layers" summary="The framework owns most of the threat surface; you own two things — tagging tables for scoping and validating external input."
13
+ {
14
+ "html": "<div class=\"sec-layers\"><div class=\"diagram-card free\"><span class=\"diagram-pill ok\">Framework owns</span><small class=\"diagram-muted\">SQL isolation &middot; parameterized queries &middot; XSS escaping &middot; auth guard &middot; CSRF cookies &middot; secret encryption</small></div><div class=\"diagram-card you\"><span class=\"diagram-pill warn\">You own</span><small class=\"diagram-muted\">A. tag tables with ownableColumns() &amp; route through access guards<br>B. give every action a Zod schema &amp; send user URLs through the SSRF guard</small></div></div>",
15
+ "css": ".sec-layers{display:flex;flex-direction:column;gap:12px}.sec-layers .diagram-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px}"
16
+ }
17
+ ```
18
+
12
19
  When you build on the standard patterns, the framework already handles most of the threat surface for you:
13
20
 
14
21
  - **Data isolation** — agent SQL is rewritten so it can only see the current user's (and active org's) rows. See [Data Scoping](#data-scoping).
@@ -76,6 +83,13 @@ await db.insert(notes).values({ title, ownerEmail: email });
76
83
  await exec(`INSERT INTO notes (title) VALUES ('${title}')`);
77
84
  ```
78
85
 
86
+ ```an-callout
87
+ {
88
+ "tone": "risk",
89
+ "body": "Never build SQL by string concatenation or template literals. Pass user input as `args` to `exec` / `db-query`, or use Drizzle — both always parameterize. The `pnpm guards` checks catch unscoped and concatenated queries at CI time."
90
+ }
91
+ ```
92
+
79
93
  ## XSS Prevention {#xss}
80
94
 
81
95
  React auto-escapes all JSX expressions. Additional guidelines:
@@ -109,6 +123,13 @@ Scoping flows from the authenticated session down to the SQL the agent runs:
109
123
  session.orgId → AGENT_ORG_ID → SQL row scoping
110
124
  ```
111
125
 
126
+ ```an-diagram title="The scoping pipeline" summary="Agent SQL never touches base tables directly — it reads through a temporary view scoped to the current identity, so a bare table name can only return owned rows."
127
+ {
128
+ "html": "<div class=\"scope-pipe\"><div class=\"diagram-node\">Signed-in session<br><small class=\"diagram-muted\">email &middot; orgId</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\">Request context<br><small class=\"diagram-muted\">AGENT_ORG_ID</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\">Temporary VIEW<br><small class=\"diagram-muted\">WHERE owner_email = ? AND org_id = ?</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node ok\">Agent SQL<br><small class=\"diagram-muted\">bare table names only</small></div></div>",
129
+ "css": ".scope-pipe{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.scope-pipe .diagram-node{display:flex;flex-direction:column;gap:2px;padding:10px 14px}.scope-pipe .diagram-arrow{font-size:22px;line-height:1}"
130
+ }
131
+ ```
132
+
112
133
  The signed-in session carries `email` and (when an org is active) `orgId`. The framework establishes request context from that session, exposes the active org to agent SQL as `AGENT_ORG_ID`, and rewrites every query so it can only see rows the current identity owns. The same path applies whether the query comes from the UI, an action, or the agent — the agent cannot read data for an org the user isn't a member of.
113
134
 
114
135
  ### Per-User Scoping (`owner_email`)
@@ -168,6 +189,23 @@ export const projects = table("projects", {
168
189
  });
169
190
  ```
170
191
 
192
+ ```an-schema title="What ownableColumns() adds" summary="The three columns that make a table tenant-aware and shareable."
193
+ {
194
+ "entities": [
195
+ {
196
+ "id": "ownable",
197
+ "name": "ownable resource",
198
+ "note": "Any table that spreads ...ownableColumns()",
199
+ "fields": [
200
+ { "name": "owner_email", "type": "text", "nullable": false, "note": "Creator. Auto-filled by write actions; auto-injected on INSERT." },
201
+ { "name": "org_id", "type": "text", "nullable": true, "note": "Owner's active org at creation. Drives org-visibility checks." },
202
+ { "name": "visibility", "type": "enum", "nullable": false, "note": "private | org | public — coarse default, defaults to private." }
203
+ ]
204
+ }
205
+ ]
206
+ }
207
+ ```
208
+
171
209
  ### Access guards in actions {#access-guards}
172
210
 
173
211
  Raw agent SQL is scoped by the temporary views above. Action code that queries with Drizzle directly should go through the framework's access helpers so reads and writes stay scoped to the current identity:
@@ -7,6 +7,13 @@ description: "Nitro server routes, plugins, framework-mounted routes, request co
7
7
 
8
8
  Agent-native apps use [Nitro](https://nitro.build) for server routes and plugins. Most product behavior should live in [Actions](/docs/actions); custom routes are for protocol surfaces that actions do not fit: uploads, streaming, public pages, webhooks, OAuth callbacks, and provider-specific APIs.
9
9
 
10
+ ```an-diagram title="What runs on the server" summary="Actions are the default. Custom file routes and framework-mounted routes share the same Nitro app and the same SQL database."
11
+ {
12
+ "html": "<div class=\"diagram-server\"><div class=\"diagram-col entry\"><div class=\"diagram-node\">Browser / UI</div><div class=\"diagram-node\">Agent loop</div><div class=\"diagram-node\">External clients<br><small class=\"diagram-muted\">HTTP · MCP · A2A</small></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel\" data-rough><strong>Nitro server</strong><div class=\"diagram-row\"><span class=\"diagram-pill accent\">Actions</span><small class=\"diagram-muted\">default surface</small></div><div class=\"diagram-row\"><span class=\"diagram-pill\">/_agent-native/*</span><small class=\"diagram-muted\">framework routes</small></div><div class=\"diagram-row\"><span class=\"diagram-pill\">/api/*</span><small class=\"diagram-muted\">custom file routes</small></div><div class=\"diagram-row\"><span class=\"diagram-pill\">plugins</span><small class=\"diagram-muted\">startup: migrations, jobs</small></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>SQL database<br><small class=\"diagram-muted\">Drizzle · the coordination point</small></div></div>",
13
+ "css": ".diagram-server{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-server .diagram-col{display:flex;flex-direction:column;gap:10px}.diagram-server .diagram-panel{display:flex;flex-direction:column;gap:8px;padding:14px 16px}.diagram-server .diagram-row{display:flex;align-items:center;gap:8px}.diagram-server .diagram-arrow{font-size:22px;line-height:1}"
14
+ }
15
+ ```
16
+
10
17
  ## File-Based Routes {#file-based-routes}
11
18
 
12
19
  Routes live in `server/routes/` and Nitro maps filenames to methods and paths:
@@ -96,30 +103,17 @@ in an action so the UI and agent share the same capability.
96
103
 
97
104
  Actions mounted by the framework automatically run with request context. Custom routes do not. If a custom route reads or writes ownable resources, load the session and wrap the work:
98
105
 
99
- ```ts
100
- import { defineEventHandler, createError } from "h3";
101
- import { getSession, runWithRequestContext } from "@agent-native/core/server";
102
- import { getDb } from "../../db/index.js";
103
- import { accessFilter } from "@agent-native/core/sharing";
104
- import * as schema from "../../db/schema";
105
-
106
- export default defineEventHandler(async (event) => {
107
- const session = await getSession(event);
108
- if (!session?.email) {
109
- throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
110
- }
111
-
112
- return runWithRequestContext(
113
- { userEmail: session.email, orgId: session.orgId },
114
- async () => {
115
- const db = getDb();
116
- return db
117
- .select()
118
- .from(schema.projects)
119
- .where(accessFilter(schema.projects, schema.projectShares));
120
- },
121
- );
122
- });
106
+ ```an-annotated-code title="Scoping a custom route to the request user"
107
+ {
108
+ "filename": "server/routes/api/projects.get.ts",
109
+ "language": "ts",
110
+ "code": "import { defineEventHandler, createError } from \"h3\";\nimport { getSession, runWithRequestContext } from \"@agent-native/core/server\";\nimport { getDb } from \"../../db/index.js\";\nimport { accessFilter } from \"@agent-native/core/sharing\";\nimport * as schema from \"../../db/schema\";\n\nexport default defineEventHandler(async (event) => {\n const session = await getSession(event);\n if (!session?.email) {\n throw createError({ statusCode: 401, statusMessage: \"Unauthorized\" });\n }\n\n return runWithRequestContext(\n { userEmail: session.email, orgId: session.orgId },\n async () => {\n const db = getDb();\n return db\n .select()\n .from(schema.projects)\n .where(accessFilter(schema.projects, schema.projectShares));\n },\n );\n});",
111
+ "annotations": [
112
+ { "lines": "7-10", "label": "Custom routes have no auto-context", "note": "Unlike actions, a file route must load the session itself and fail closed when there is no authenticated user." },
113
+ { "lines": "12-13", "label": "Establish request context", "note": "`runWithRequestContext` makes the user/org available to scoping helpers for the duration of the work." },
114
+ { "lines": "18-19", "label": "Scope ownable reads", "note": "`accessFilter` constrains the query to rows the caller may see. Never run an unscoped `db.select().from(ownableTable)` here." }
115
+ ]
116
+ }
123
117
  ```
124
118
 
125
119
  `getDb` is created per app via `createGetDb(schema)` in `server/db/index.ts`, so custom routes import it from the template (`../../db/index.js`), not from `@agent-native/core/db`; see [Database — Where the DB Client Lives](/docs/database#db-client). Do not run unscoped `db.select().from(ownableTable)` in custom routes.
@@ -177,6 +171,26 @@ Agent-native does not rely on filesystem watchers or sticky in-memory state. Whe
177
171
 
178
172
  This works across serverless and multi-instance deployments because the database is the coordination point. If you write custom mutations outside actions, use framework helpers or emit the appropriate sync invalidation so open UIs refresh.
179
173
 
174
+ ```an-diagram title="SQL-backed sync loop" summary="No watchers, no sticky state. A write bumps a version in SQL; every client polls the version and refetches."
175
+ {
176
+ "html": "<div class=\"diagram-sync\"><div class=\"diagram-box\" data-rough>Action / helper<br><small class=\"diagram-muted\">mutates data</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel\" data-rough><strong>SQL database</strong><small class=\"diagram-muted\">sync version increments</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&larr;</div><div class=\"diagram-col\"><div class=\"diagram-node\">useDbSync()<br><small class=\"diagram-muted\">polls /_agent-native/poll</small></div><div class=\"diagram-pill ok\">invalidate caches &rarr; UI refreshes</div></div></div>",
177
+ "css": ".diagram-sync{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-sync .diagram-col{display:flex;flex-direction:column;gap:8px;align-items:flex-start}.diagram-sync .diagram-arrow{font-size:22px;line-height:1}"
178
+ }
179
+ ```
180
+
181
+ ```an-api title="The poll endpoint" method="GET" path="/_agent-native/poll"
182
+ {
183
+ "method": "GET",
184
+ "path": "/_agent-native/poll",
185
+ "summary": "Return the current per-source database sync versions so the client can detect changes.",
186
+ "description": "`useDbSync()` calls this on an interval (and falls back to it when SSE is unavailable). When a returned version is higher than the client's last-seen value, the matching React Query caches are invalidated and refetch.",
187
+ "auth": "Session cookie (request-scoped identity)",
188
+ "responses": [
189
+ { "status": "200", "description": "Current sync versions keyed by source." }
190
+ ]
191
+ }
192
+ ```
193
+
180
194
  ## Webhooks {#webhooks}
181
195
 
182
196
  Inbound webhooks should verify, persist, and return quickly. Long-running agent work should use the integration queue pattern:
@@ -187,7 +201,15 @@ Inbound webhooks should verify, persist, and return quickly. Long-running agent
187
201
  4. Return 200 immediately.
188
202
  5. Let the fresh processor execution run the agent loop and post the result.
189
203
 
190
- Do not rely on unawaited promises after returning a response. See [Messaging](/docs/messaging) for the canonical integration queue.
204
+ ```an-diagram title="Integration queue pattern" summary="The webhook handler returns in milliseconds; a separate signed execution runs the slow agent work."
205
+ {
206
+ "html": "<div class=\"diagram-webhook\"><div class=\"diagram-box\" data-rough>Inbound webhook<br><small class=\"diagram-muted\">Slack · Stripe · email</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel\" data-rough><strong>Handler</strong><div class=\"diagram-step\"><span class=\"diagram-pill\">1</span><small class=\"diagram-muted\">verify signature</small></div><div class=\"diagram-step\"><span class=\"diagram-pill\">2</span><small class=\"diagram-muted\">insert work into SQL</small></div><div class=\"diagram-step\"><span class=\"diagram-pill\">3</span><small class=\"diagram-muted\">self-fire processor</small></div><div class=\"diagram-step\"><span class=\"diagram-pill ok\">4</span><small class=\"diagram-muted\">return 200 now</small></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>Signed processor<br><small class=\"diagram-muted\">runs agent loop, posts result</small></div></div>",
207
+ "css": ".diagram-webhook{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-webhook .diagram-panel{display:flex;flex-direction:column;gap:6px;padding:14px 16px}.diagram-webhook .diagram-step{display:flex;align-items:center;gap:8px}.diagram-webhook .diagram-arrow{font-size:22px;line-height:1}"
208
+ }
209
+ ```
210
+
211
+ > [!WARNING]
212
+ > Do not rely on unawaited promises after returning a response — serverless hosts freeze the execution. See [Messaging](/docs/messaging) for the canonical integration queue.
191
213
 
192
214
  ## Advanced: Escape Hatches {#advanced-escape-hatches}
193
215
 
@@ -27,6 +27,13 @@ Coarse visibility lives on the resource itself; fine-grained grants live in a co
27
27
 
28
28
  `public` is a deliberately quiet level: a public resource is reachable by direct link, but it does **not** show up in other users' sidebars, lists, or search. That keeps "public for sharing the URL" separate from "public for cross-user discovery." Galleries and template catalogs that genuinely want cross-user discovery opt in explicitly.
29
29
 
30
+ ```an-diagram title="Visibility, widening outward" summary="Coarse visibility on the resource sets the floor; explicit share grants in the companion table add named people on top."
31
+ {
32
+ "html": "<div class=\"share-tiers\"><div class=\"diagram-card\"><span class=\"diagram-pill\">private</span><small class=\"diagram-muted\">owner + explicit grants only &middot; <strong>default</strong></small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-card\"><span class=\"diagram-pill accent\">org</span><small class=\"diagram-muted\">+ anyone in the same org (read-only)</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-card\"><span class=\"diagram-pill warn\">public</span><small class=\"diagram-muted\">+ anyone with the link (read-only) &middot; hidden from others' lists/search</small></div></div>",
33
+ "css": ".share-tiers{display:flex;flex-direction:column;align-items:stretch;gap:8px}.share-tiers .diagram-card{display:flex;flex-direction:column;gap:4px;padding:12px 16px}.share-tiers .diagram-arrow{text-align:center;font-size:20px;line-height:1}"
34
+ }
35
+ ```
36
+
30
37
  ## Roles on a share grant {#roles}
31
38
 
32
39
  When you share with a specific user or org, you pick a role:
@@ -120,6 +127,42 @@ export const decks = table("decks", {
120
127
  export const deckShares = createSharesTable("deck_shares");
121
128
  ```
122
129
 
130
+ ```an-schema title="Resource + companion shares table" summary="Coarse visibility lives on the resource; each fine-grained grant is a row in the shares table."
131
+ {
132
+ "entities": [
133
+ {
134
+ "id": "deck",
135
+ "name": "decks",
136
+ "note": "...ownableColumns()",
137
+ "fields": [
138
+ { "name": "id", "type": "text", "pk": true },
139
+ { "name": "title", "type": "text", "nullable": false },
140
+ { "name": "owner_email", "type": "text", "nullable": false, "note": "The single source of truth for ownership." },
141
+ { "name": "org_id", "type": "text", "nullable": true },
142
+ { "name": "visibility", "type": "enum", "nullable": false, "note": "private | org | public" }
143
+ ]
144
+ },
145
+ {
146
+ "id": "deckShare",
147
+ "name": "deck_shares",
148
+ "note": "createSharesTable() — one row per grant",
149
+ "fields": [
150
+ { "name": "id", "type": "text", "pk": true },
151
+ { "name": "resource_id", "type": "text", "fk": "decks.id", "nullable": false },
152
+ { "name": "principal_type", "type": "enum", "note": "user | org" },
153
+ { "name": "principal_id", "type": "text", "note": "email (user) or org id (org)" },
154
+ { "name": "role", "type": "enum", "note": "viewer | editor | admin" },
155
+ { "name": "created_by", "type": "text" },
156
+ { "name": "created_at", "type": "text" }
157
+ ]
158
+ }
159
+ ],
160
+ "relations": [
161
+ { "from": "deckShare", "to": "deck", "kind": "n-n", "label": "grants access to" }
162
+ ]
163
+ }
164
+ ```
165
+
123
166
  One registration call in `server/db/index.ts`:
124
167
 
125
168
  ```ts
@@ -155,6 +198,13 @@ registerShareableResource({
155
198
 
156
199
  `allowPublic: false` prevents any caller — agent or UI — from setting the resource's visibility to `public`. `requireOrgMemberForUserShares: true` rejects individual user grants to email addresses outside the resource owner's org. Extensions set both: an extension's HTML runs inside an iframe that calls actions and DB as the _viewer_, so public access would be arbitrary code with the viewer's credentials.
157
200
 
201
+ ```an-callout
202
+ {
203
+ "tone": "risk",
204
+ "body": "For resources that execute code or carry elevated trust (like extensions), set `allowPublic: false` and `requireOrgMemberForUserShares: true`. Otherwise a public share becomes arbitrary code running with the *viewer's* credentials."
205
+ }
206
+ ```
207
+
158
208
  `getResourcePath` gives notification emails a direct fallback link when a share is created by the agent or another non-UI caller. The full pattern (including create-action ownership stamping and the migration recipe for existing tables) lives in the `sharing` agent skill — the agent reads it on demand when building a sharing-aware feature.
159
209
 
160
210
  ## Security guarantees {#security}
@@ -13,6 +13,13 @@ Skills live at `.agents/skills/<name>/SKILL.md` and contain detailed guidance fo
13
13
 
14
14
  Every skill's frontmatter `name` and `description` are always injected into the system prompt's skills block so the agent knows what skills exist. The full skill body is loaded on demand when the agent decides a skill is relevant to the task (it is also surfaced via `docs-search`). This is why keeping descriptions short and trigger-specific matters: the description is the only thing the agent reads before deciding whether to load the rest.
15
15
 
16
+ ```an-diagram title="Progressive disclosure" summary="Only the name + description of every skill is always in context. The full body loads on demand when the task matches."
17
+ {
18
+ "html": "<div class=\"sk-flow\"><div class=\"diagram-card\"><span class=\"diagram-pill accent\">Always in the system prompt</span><div class=\"sk-list\"><span class=\"diagram-pill\">storing-data &mdash; <small class=\"diagram-muted\">add data models&hellip;</small></span><span class=\"diagram-pill\">real-time-sync &mdash; <small class=\"diagram-muted\">wire polling&hellip;</small></span><span class=\"diagram-pill\">create-skill &mdash; <small class=\"diagram-muted\">add a skill&hellip;</small></span></div><small class=\"diagram-muted\">just name + description (cheap)</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\"><small class=\"diagram-muted\">task matches a description</small><span class=\"diagram-pill accent\">load on demand</span></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\">Full <code>SKILL.md</code> body<br><small class=\"diagram-muted\">rules, code, do/don't</small></div></div>",
19
+ "css": ".sk-flow{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.sk-flow .diagram-card{display:flex;flex-direction:column;gap:8px;padding:14px 16px;min-width:240px}.sk-flow .sk-list{display:flex;flex-direction:column;gap:6px}.sk-flow .center{display:flex;flex-direction:column;align-items:center;gap:6px}.sk-flow .diagram-arrow{font-size:22px}"
20
+ }
21
+ ```
22
+
16
23
  ## Framework skills {#framework-skills}
17
24
 
18
25
  These are the skills bundled with the **default template**. The exact set available in any given app depends on the template you scaffolded from — check that template's `.agents/skills/` directory for what it actually ships.
@@ -72,48 +79,18 @@ Don't create a skill when:
72
79
 
73
80
  Each skill is a Markdown file with YAML frontmatter:
74
81
 
75
- ```markdown
76
- ---
77
- name: project-imports
78
- description: >-
79
- How to import projects from the legacy CSV export. Use when the user uploads
80
- a project CSV or asks to migrate projects from the old system.
81
- ---
82
-
83
- # Project Imports
84
-
85
- ## Rule
86
-
87
- Always validate the CSV header row before writing any rows. Reject unknown
88
- columns rather than silently dropping them.
89
-
90
- ## Why
91
-
92
- The legacy export has three known formats. Silently skipping columns causes data
93
- loss that is hard to notice until the migration is audited.
94
-
95
- ## How
96
-
97
- 1. Call `get-import-schema` to fetch the expected columns for the target project type.
98
- 2. Parse the first CSV row and diff against the schema.
99
- 3. If any required columns are missing, return an error listing them — do not proceed.
100
- 4. Stream remaining rows through `create-project-item` in batches of 50.
101
- 5. Return a summary: rows processed, rows skipped, and any errors.
102
-
103
- ## Do
104
-
105
- - Run `view-screen` before importing so you know the user's current project context.
106
- - Use the `sharing` skill after import if the project should be shared with collaborators.
107
-
108
- ## Don't
109
-
110
- - Don't hold all rows in memory — stream them.
111
- - Don't create duplicate projects; check for an existing project with the same name first.
112
-
113
- ## Related Skills
114
-
115
- - **storing-data** — SQL schema and write patterns for new project rows
116
- - **sharing** — Exposing a project to other users after import
82
+ ```an-annotated-code title="Anatomy of a SKILL.md"
83
+ {
84
+ "filename": ".agents/skills/project-imports/SKILL.md",
85
+ "language": "markdown",
86
+ "code": "---\nname: project-imports\ndescription: >-\n How to import projects from the legacy CSV export. Use when the user uploads\n a project CSV or asks to migrate projects from the old system.\n---\n\n# Project Imports\n\n## Rule\n\nAlways validate the CSV header row before writing any rows. Reject unknown\ncolumns rather than silently dropping them.\n\n## How\n\n1. Call `get-import-schema` to fetch the expected columns.\n2. Parse the first CSV row and diff against the schema.\n3. If any required columns are missing, return an error — do not proceed.\n4. Stream remaining rows through `create-project-item` in batches of 50.\n\n## Don't\n\n- Don't hold all rows in memory — stream them.\n- Don't create duplicate projects; check for an existing name first.\n\n## Related Skills\n\n- **storing-data** — SQL schema and write patterns for new rows\n- **sharing** — exposing a project to other users after import",
87
+ "annotations": [
88
+ { "lines": "2", "label": "Discovery key", "note": "The `name` matches the folder; it is how the skill is invoked as `/project-imports`." },
89
+ { "lines": "3-5", "label": "The trigger", "note": "This `description` is the **only** text always in context. Make it state precisely *when* the skill applies." },
90
+ { "lines": "9-14", "label": "Rules first", "note": "Lead with the hard rule and the why; the agent reads the body only once the task matches." },
91
+ { "lines": "27-30", "label": "Cross-link", "note": "Point at related skills so the agent can chain them instead of re-deriving guidance." }
92
+ ]
93
+ }
117
94
  ```
118
95
 
119
96
  The frontmatter `name` and `description` are used by the agent's tool system for skill discovery. The description should state when the skill triggers — be specific about the situations.
@@ -152,6 +129,14 @@ The agent-native runtime reads skills from `.agents/skills/`. Claude Code reads
152
129
 
153
130
  This replaces the old hack of relying on Claude Code only reading `.claude/skills` — `scope: dev` makes the dev-vs-runtime split a first-class, explicit choice.
154
131
 
132
+ ```an-diagram title="Which agent loads which skill" summary="scopedecides whether the in-app runtime agent sees a skill.dev skills are visible only to your coding agent."
133
+ {
134
+ "html": "<div class=\"sc-grid\"><div class=\"diagram-card\"><span class=\"diagram-pill\">.agents/skills/</span><div class=\"sc-row\"><span class=\"diagram-pill ok\">scope: both</span><small class=\"diagram-muted\">default</small></div><div class=\"sc-row\"><span class=\"diagram-pill ok\">scope: runtime</span></div><div class=\"sc-row\"><span class=\"diagram-pill warn\">scope: dev</span></div></div><div class=\"sc-targets\"><div class=\"diagram-box\">Runtime agent<br><small class=\"diagram-muted\">reads <code>both</code> + <code>runtime</code></small></div><div class=\"diagram-box\">Coding agent<br><small class=\"diagram-muted\">Claude Code reads <code>.claude/skills/</code> + <code>dev</code></small></div></div></div>",
135
+ "css": ".sc-grid{display:flex;gap:24px;flex-wrap:wrap;align-items:flex-start}.sc-grid .diagram-card{display:flex;flex-direction:column;gap:8px;padding:14px 16px}.sc-grid .sc-row{display:flex;align-items:center;gap:8px}.sc-grid .sc-targets{display:flex;flex-direction:column;gap:10px}"
136
+ }
137
+
138
+ ```
139
+
155
140
  > **See also:** [Writing Agent Instructions](/docs/writing-agent-instructions) for how to word skill descriptions, apply progressive disclosure, and keep `AGENTS.md` lean.
156
141
 
157
142
  ## Skills vs AGENTS.md {#skills-vs-agents-md}
@@ -19,6 +19,13 @@ Ask analytics questions in plain English, get charts and dashboards back. The ag
19
19
 
20
20
  It's an open-source replacement for Amplitude, Mixpanel, and Looker — for teams that want to own the code, the queries, and the data.
21
21
 
22
+ ```an-diagram title="Question to chart" summary="The agent consults the data dictionary, writes SQL, validates it against the warehouse, then renders a chart or saves a panel."
23
+ {
24
+ "html": "<div class=\"diagram-flow\"><div class=\"diagram-node\">Plain-English<br>question</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\"><span class=\"diagram-pill accent\">Agent</span><small class=\"diagram-muted\">reads data dictionary</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>Writes SQL</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col\"><div class=\"diagram-pill ok\">Dry-run validate</div><small class=\"diagram-muted\">BigQuery / source</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\">Chart, table, or<br>saved panel</div></div>",
25
+ "css": ".diagram-flow{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-flow .diagram-col{display:flex;flex-direction:column;gap:6px;align-items:center}.diagram-flow .center{display:flex;flex-direction:column;align-items:center;gap:4px}.diagram-flow .diagram-arrow{font-size:22px;line-height:1}"
26
+ }
27
+ ```
28
+
22
29
  ## What you can do with it
23
30
 
24
31
  - **Ask data questions in plain English.** "What percent of signups last month converted to paid?" or "Show me weekly active users for the past 6 months." The agent picks the right source, writes the SQL, and renders the chart.
@@ -180,6 +187,59 @@ Note: the BigQuery OAuth credential for Google sign-in is a **separate** credent
180
187
 
181
188
  Core tables (see `templates/analytics/server/db/schema.ts`):
182
189
 
190
+ ```an-schema title="Analytics data model" summary="Dashboards and analyses are the resources; views, shares, and a query cache hang off them. Org tables come from @agent-native/core/org."
191
+ {
192
+ "entities": [
193
+ {
194
+ "id": "dashboards",
195
+ "name": "dashboards",
196
+ "note": "Explorer and SQL dashboards",
197
+ "fields": [
198
+ { "name": "id", "type": "text", "pk": true },
199
+ { "name": "kind", "type": "text", "note": "\"explorer\" or \"sql\"" },
200
+ { "name": "config", "type": "text", "note": "JSON matching SqlDashboardConfig" }
201
+ ]
202
+ },
203
+ {
204
+ "id": "dashboard_views",
205
+ "name": "dashboard_views",
206
+ "note": "Saved filter presets per dashboard",
207
+ "fields": [
208
+ { "name": "id", "type": "text", "pk": true },
209
+ { "name": "dashboard_id", "type": "text", "fk": "dashboards.id" }
210
+ ]
211
+ },
212
+ {
213
+ "id": "analyses",
214
+ "name": "analyses",
215
+ "note": "Re-runnable ad-hoc investigations",
216
+ "fields": [
217
+ { "name": "id", "type": "text", "pk": true },
218
+ { "name": "question", "type": "text" },
219
+ { "name": "instructions", "type": "text", "note": "Re-run steps" },
220
+ { "name": "dataSources", "type": "text", "note": "Sources touched" },
221
+ { "name": "resultMarkdown", "type": "text" },
222
+ { "name": "resultData", "type": "text", "nullable": true }
223
+ ]
224
+ },
225
+ {
226
+ "id": "bigquery_cache",
227
+ "name": "bigquery_cache",
228
+ "note": "Result cache keyed by SQL hash",
229
+ "fields": [
230
+ { "name": "sql_hash", "type": "text", "pk": true },
231
+ { "name": "bytes_processed", "type": "integer" }
232
+ ]
233
+ }
234
+ ],
235
+ "relations": [
236
+ { "from": "dashboards", "to": "dashboard_views", "kind": "1-n", "label": "saved views" }
237
+ ]
238
+ }
239
+ ```
240
+
241
+ Plus per-resource share tables (`dashboard_shares`, `analysis_shares`) and the org tables (`organizations`, `org_members`, `org_invitations`) provided by `@agent-native/core/org`. The data dictionary lives in the framework's `settings` table under scoped keys.
242
+
183
243
  - **`dashboards`** — both Explorer and SQL dashboards. `kind` is `"explorer"` or `"sql"`; `config` is a JSON blob matching `SqlDashboardConfig`.
184
244
  - **`dashboard_shares`** — per-resource share grants (principal, role).
185
245
  - **`dashboard_views`** — saved filter presets per dashboard.
@@ -11,6 +11,13 @@ Assets is an agent-native workspace for creating and managing brand-consistent m
11
11
 
12
12
  When you open the app, you see your libraries and folders on the left, the assets in the selected library in the middle, and a chat composer for generating new media. The agent can browse, search, generate, refine, and export every asset through the same actions the UI uses.
13
13
 
14
+ ```an-diagram title="Generate, review, reuse" summary="References and prompts feed a generate-and-choose session; chosen assets land in a library and flow out to other apps via the picker or A2A."
15
+ {
16
+ "html": "<div class=\"diagram-assets\"><div class=\"diagram-col\"><div class=\"diagram-node\">References<br><small class=\"diagram-muted\">logos, product shots, style</small></div><div class=\"diagram-node\">Prompt<br><small class=\"diagram-muted\">chat or Generate controls</small></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough><span class=\"diagram-pill accent\">Generation session</span><small class=\"diagram-muted\">image &amp; video candidates · audit log</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough><span class=\"diagram-pill ok\">Library</span><small class=\"diagram-muted\">chosen, brand-consistent assets</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col\"><div class=\"diagram-node\">Picker<br><small class=\"diagram-muted\">iframe / MCP App</small></div><div class=\"diagram-node\">A2A<br><small class=\"diagram-muted\">Slides · Design · Content</small></div></div></div>",
17
+ "css": ".diagram-assets{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-assets .diagram-col{display:flex;flex-direction:column;gap:10px}.diagram-assets .diagram-box{display:flex;flex-direction:column;gap:4px}.diagram-assets .diagram-arrow{font-size:20px;line-height:1}"
18
+ }
19
+ ```
20
+
14
21
  ## When to pick it
15
22
 
16
23
  - **Your team needs reusable visual direction**, not one-off generic media prompts — collect approved logos, product shots, and style examples so generations stay on brand.
@@ -118,6 +125,67 @@ Note: the SQL table names keep the legacy `image_*` prefix from when the app was
118
125
  | `image_assets` | The asset record — media type, role, status, title/description/alt text, prompt, model, dimensions, MIME type, object/thumbnail keys, and lineage |
119
126
  | `image_generation_runs` | The generation audit log — prompt, compiled prompt, model, references, status, errors, and the `source` (`chat` / `ui` / `a2a`) that triggered it |
120
127
 
128
+ ```an-schema title="Assets data model" summary="Libraries are the ownable container; collections, folders, and presets organize them. Sessions drive generate-and-choose; assets and runs hold output and the audit log. Table names keep the legacy image_* prefix but cover all media."
129
+ {
130
+ "entities": [
131
+ { "id": "library", "name": "image_libraries", "note": "Top-level ownable container", "fields": [
132
+ { "name": "id", "type": "id", "pk": true },
133
+ { "name": "custom_instructions", "type": "text", "nullable": true },
134
+ { "name": "style_brief", "type": "text", "nullable": true },
135
+ { "name": "logo_asset_id", "type": "id", "fk": "image_assets.id", "nullable": true },
136
+ { "name": "archived", "type": "boolean" }
137
+ ] },
138
+ { "id": "library_shares", "name": "image_library_shares", "note": "Framework shares table", "fields": [
139
+ { "name": "library_id", "type": "id", "fk": "image_libraries.id" },
140
+ { "name": "role", "type": "text", "note": "viewer / editor / admin" }
141
+ ] },
142
+ { "id": "collections", "name": "image_collections", "note": "Style/category groupings", "fields": [
143
+ { "name": "library_id", "type": "id", "fk": "image_libraries.id" },
144
+ { "name": "style_brief", "type": "text", "nullable": true },
145
+ { "name": "prompt_template", "type": "text", "nullable": true }
146
+ ] },
147
+ { "id": "folders", "name": "asset_folders", "note": "Nestable folders", "fields": [
148
+ { "name": "library_id", "type": "id", "fk": "image_libraries.id" },
149
+ { "name": "parent_id", "type": "id", "fk": "asset_folders.id", "nullable": true }
150
+ ] },
151
+ { "id": "presets", "name": "image_generation_presets", "note": "Saved generation recipes", "fields": [
152
+ { "name": "media_type", "type": "text" },
153
+ { "name": "prompt_template", "type": "text" },
154
+ { "name": "model", "type": "text" }
155
+ ] },
156
+ { "id": "sessions", "name": "image_generation_sessions", "note": "Iterative generate-and-choose", "fields": [
157
+ { "name": "id", "type": "id", "pk": true },
158
+ { "name": "status", "type": "text" },
159
+ { "name": "active_asset_id", "type": "id", "fk": "image_assets.id", "nullable": true }
160
+ ] },
161
+ { "id": "session_items", "name": "image_generation_session_items", "note": "Candidate assets in a session", "fields": [
162
+ { "name": "session_id", "type": "id", "fk": "image_generation_sessions.id" },
163
+ { "name": "asset_id", "type": "id", "fk": "image_assets.id" },
164
+ { "name": "role", "type": "text" }
165
+ ] },
166
+ { "id": "assets", "name": "image_assets", "note": "The asset record", "fields": [
167
+ { "name": "id", "type": "id", "pk": true },
168
+ { "name": "media_type", "type": "text", "note": "image / video" },
169
+ { "name": "status", "type": "text" },
170
+ { "name": "prompt", "type": "text", "nullable": true },
171
+ { "name": "object_key", "type": "text", "nullable": true }
172
+ ] },
173
+ { "id": "runs", "name": "image_generation_runs", "note": "Generation audit log", "fields": [
174
+ { "name": "model", "type": "text" },
175
+ { "name": "status", "type": "text" },
176
+ { "name": "source", "type": "text", "note": "chat / ui / a2a" }
177
+ ] }
178
+ ],
179
+ "relations": [
180
+ { "from": "library", "to": "collections", "kind": "1-n" },
181
+ { "from": "library", "to": "folders", "kind": "1-n" },
182
+ { "from": "library", "to": "assets", "kind": "1-n" },
183
+ { "from": "sessions", "to": "session_items", "kind": "1-n" },
184
+ { "from": "library", "to": "library_shares", "kind": "1-n" }
185
+ ]
186
+ }
187
+ ```
188
+
121
189
  ### Customizing it
122
190
 
123
191
  Assets is a complete, cloneable template. Some practical extension ideas:
@@ -19,6 +19,13 @@ The product surface stays simple on purpose: **Ask** is the primary chat
19
19
  experience, while **Sources**, **Review**, and **Knowledge** are admin/support
20
20
  surfaces for connecting data, approving proposals, and inspecting cited memory.
21
21
 
22
+ ```an-diagram title="From source to cited answer" summary="Brain ingests approved sources into raw captures, distills durable memory, gates it through review, and only then answers with citations."
23
+ {
24
+ "html": "<div class=\"diagram-flow\"><div class=\"diagram-card\"><span class=\"diagram-pill\">Sources</span><small class=\"diagram-muted\">Slack · Granola · GitHub · Clips · webhooks</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>Raw captures<br><small class=\"diagram-muted\">deduped, redacted</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>Distill<br><small class=\"diagram-muted\">facts · decisions · processes</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill warn\">Review</span><small class=\"diagram-muted\">sensitive / low-confidence queue</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough><span class=\"diagram-pill ok\">Knowledge</span><small class=\"diagram-muted\">approved, atomic</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\"><span class=\"diagram-pill accent\">Ask</span><small class=\"diagram-muted\">cited answer</small></div></div>",
25
+ "css": ".diagram-flow{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.diagram-flow .diagram-card{display:flex;flex-direction:column;gap:4px;padding:10px 12px}.diagram-flow .diagram-box{display:flex;flex-direction:column;gap:4px}.diagram-flow .diagram-arrow{font-size:20px;line-height:1}.diagram-flow .center{display:flex;flex-direction:column;align-items:center;gap:4px}"
26
+ }
27
+ ```
28
+
22
29
  ![Brain company chat with cited memory sources](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F9c9fe3b5b9494e33803cd3f494cba356?format=webp&width=1200)
23
30
 
24
31
  When you open the app, **Ask** is front and center — a clean chat over reviewed
@@ -140,6 +147,65 @@ Brain's schema lives in `templates/brain/server/db/schema.ts`. Eight tables:
140
147
  | `brain_sync_runs` | Sync audit log — provider, status, stats JSON, error, start/end timestamps |
141
148
  | `brain_ingest_queue` | Background distillation queue — operation, status, priority, retry count, `run_after` |
142
149
 
150
+ ```an-schema title="Brain data model" summary="Connectors produce raw captures; distillation turns captures into reviewable knowledge; proposals gate sensitive entries. Sync runs and the ingest queue track background work."
151
+ {
152
+ "entities": [
153
+ { "id": "sources", "name": "brain_sources", "note": "Connector config", "fields": [
154
+ { "name": "id", "type": "id", "pk": true },
155
+ { "name": "provider", "type": "text", "note": "slack / granola / github / clips / webhook" },
156
+ { "name": "ingest_token_hash", "type": "text" },
157
+ { "name": "status", "type": "text" },
158
+ { "name": "last_synced_at", "type": "timestamp", "nullable": true }
159
+ ] },
160
+ { "id": "source_shares", "name": "brain_source_shares", "note": "viewer / editor / admin", "fields": [
161
+ { "name": "source_id", "type": "id", "fk": "brain_sources.id" }
162
+ ] },
163
+ { "id": "captures", "name": "brain_raw_captures", "note": "Ingested raw payloads", "fields": [
164
+ { "name": "id", "type": "id", "pk": true },
165
+ { "name": "source_id", "type": "id", "fk": "brain_sources.id" },
166
+ { "name": "external_id", "type": "text", "note": "dedupe key" },
167
+ { "name": "content_hash", "type": "text" },
168
+ { "name": "kind", "type": "text" }
169
+ ] },
170
+ { "id": "knowledge", "name": "brain_knowledge", "note": "Distilled atomic entries", "fields": [
171
+ { "name": "id", "type": "id", "pk": true },
172
+ { "name": "kind", "type": "text", "note": "decision / fact / process" },
173
+ { "name": "topic", "type": "text" },
174
+ { "name": "entities", "type": "json" },
175
+ { "name": "confidence", "type": "real" },
176
+ { "name": "publish_tier", "type": "text" }
177
+ ] },
178
+ { "id": "knowledge_shares", "name": "brain_knowledge_shares", "fields": [
179
+ { "name": "knowledge_id", "type": "id", "fk": "brain_knowledge.id" }
180
+ ] },
181
+ { "id": "proposals", "name": "brain_proposals", "note": "Pending review items", "fields": [
182
+ { "name": "id", "type": "id", "pk": true },
183
+ { "name": "op", "type": "text", "note": "create / update / archive" }
184
+ ] },
185
+ { "id": "proposal_shares", "name": "brain_proposal_shares", "fields": [
186
+ { "name": "proposal_id", "type": "id", "fk": "brain_proposals.id" }
187
+ ] },
188
+ { "id": "sync_runs", "name": "brain_sync_runs", "note": "Sync audit log", "fields": [
189
+ { "name": "source_id", "type": "id", "fk": "brain_sources.id" },
190
+ { "name": "status", "type": "text" },
191
+ { "name": "stats", "type": "json" }
192
+ ] },
193
+ { "id": "ingest_queue", "name": "brain_ingest_queue", "note": "Background distillation queue", "fields": [
194
+ { "name": "operation", "type": "text" },
195
+ { "name": "status", "type": "text" },
196
+ { "name": "priority", "type": "int" },
197
+ { "name": "run_after", "type": "timestamp", "nullable": true }
198
+ ] }
199
+ ],
200
+ "relations": [
201
+ { "from": "sources", "to": "captures", "kind": "1-n", "label": "ingested into" },
202
+ { "from": "knowledge", "to": "captures", "kind": "n-n", "label": "evidence" },
203
+ { "from": "knowledge", "to": "proposals", "kind": "1-n", "label": "gated by" },
204
+ { "from": "sources", "to": "sync_runs", "kind": "1-n", "label": "audited by" }
205
+ ]
206
+ }
207
+ ```
208
+
143
209
  ### Key actions
144
210
 
145
211
  Grouped by area (`templates/brain/actions/`):
@@ -188,6 +254,23 @@ a source with a `sourceKey` to receive a bearer token, then send a
188
254
  use the same payload shape for call transcripts, customer research, imported
189
255
  notes, or any other source that can produce a bounded capture.
190
256
 
257
+ ```an-api title="Signed ingest webhook" summary="Clips and generic transcript/capture imports post a RawCapturePayload with a per-source bearer token."
258
+ {
259
+ "method": "POST",
260
+ "path": "/api/_agent-native/brain/ingest",
261
+ "summary": "Import a raw capture from Clips or a generic source",
262
+ "auth": "Bearer <ingestToken> issued per source via its sourceKey",
263
+ "request": {
264
+ "contentType": "application/json",
265
+ "example": "RawCapturePayload — bounded transcript / capture body"
266
+ },
267
+ "responses": [
268
+ { "status": "200", "description": "Capture accepted and queued for distillation" },
269
+ { "status": "401", "description": "Missing or invalid ingest bearer token" }
270
+ ]
271
+ }
272
+ ```
273
+
191
274
  Slack, Granola, and GitHub sources can opt into background `autoSync` with a
192
275
  poll cadence once review quality is proven.
193
276