@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.
- package/dist/styles/blocks.css +25 -3
- package/docs/content/a2a-protocol.md +48 -10
- package/docs/content/actions.md +35 -16
- package/docs/content/agent-mentions.md +25 -32
- package/docs/content/agent-surfaces.md +31 -0
- package/docs/content/agent-teams.md +17 -14
- package/docs/content/agent-web-surfaces.md +24 -15
- package/docs/content/authentication.md +21 -0
- package/docs/content/automations.md +37 -21
- package/docs/content/blueprint-installer.md +7 -0
- package/docs/content/cli-adapters.md +7 -0
- package/docs/content/client.md +14 -0
- package/docs/content/cloneable-saas.md +14 -0
- package/docs/content/code-agents-ui.md +27 -0
- package/docs/content/components.md +21 -0
- package/docs/content/context-awareness.md +33 -48
- package/docs/content/creating-templates.md +43 -52
- package/docs/content/cross-app-sso.md +41 -0
- package/docs/content/database.md +41 -21
- package/docs/content/deployment.md +23 -6
- package/docs/content/dispatch.md +27 -0
- package/docs/content/drop-in-agent.md +26 -27
- package/docs/content/durable-resume.md +13 -1
- package/docs/content/embedding-sdk.md +14 -0
- package/docs/content/evals.md +14 -0
- package/docs/content/extensions.md +19 -0
- package/docs/content/external-agents.md +38 -1
- package/docs/content/faq.md +14 -0
- package/docs/content/file-uploads.md +20 -0
- package/docs/content/frames.md +14 -0
- package/docs/content/getting-started.md +34 -16
- package/docs/content/harness-agents.md +14 -0
- package/docs/content/human-approval.md +15 -30
- package/docs/content/key-concepts.md +37 -0
- package/docs/content/local-file-mode.md +26 -19
- package/docs/content/mcp-apps.md +36 -1
- package/docs/content/mcp-clients.md +49 -0
- package/docs/content/mcp-protocol.md +36 -0
- package/docs/content/messaging.md +34 -8
- package/docs/content/migration-workbench.md +7 -0
- package/docs/content/multi-app-workspace.md +29 -16
- package/docs/content/multi-tenancy.md +14 -0
- package/docs/content/native-chat-ui.md +20 -3
- package/docs/content/notifications.md +30 -0
- package/docs/content/observability.md +20 -1
- package/docs/content/observational-memory.md +14 -0
- package/docs/content/onboarding.md +32 -41
- package/docs/content/plan-plugin.md +14 -0
- package/docs/content/pr-visual-recap.md +14 -0
- package/docs/content/processors.md +7 -0
- package/docs/content/progress.md +23 -0
- package/docs/content/pure-agent-apps.md +7 -0
- package/docs/content/real-time-collaboration.md +19 -18
- package/docs/content/recurring-jobs.md +22 -19
- package/docs/content/routing.md +8 -0
- package/docs/content/sandbox-adapters.md +19 -0
- package/docs/content/security.md +38 -0
- package/docs/content/server.md +47 -25
- package/docs/content/sharing.md +50 -0
- package/docs/content/skills-guide.md +27 -42
- package/docs/content/template-analytics.md +60 -0
- package/docs/content/template-assets.md +68 -0
- package/docs/content/template-brain.md +83 -0
- package/docs/content/template-calendar.md +74 -0
- package/docs/content/template-chat.md +19 -0
- package/docs/content/template-clips.md +113 -0
- package/docs/content/template-content.md +133 -0
- package/docs/content/template-design.md +55 -0
- package/docs/content/template-dispatch.md +50 -0
- package/docs/content/template-forms.md +59 -0
- package/docs/content/template-mail.md +62 -0
- package/docs/content/template-plan.md +74 -0
- package/docs/content/template-slides.md +82 -0
- package/docs/content/template-videos.md +62 -0
- package/docs/content/tracking.md +21 -0
- package/docs/content/using-your-agent.md +14 -0
- package/docs/content/voice-input.md +31 -10
- package/docs/content/what-is-agent-native.md +25 -13
- package/docs/content/workspace-connections.md +49 -0
- package/docs/content/workspace-management.md +24 -0
- package/docs/content/workspace.md +50 -19
- package/docs/content/writing-agent-instructions.md +7 -0
- package/package.json +1 -1
package/docs/content/security.md
CHANGED
|
@@ -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 · parameterized queries · XSS escaping · auth guard · CSRF cookies · 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() & route through access guards<br>B. give every action a Zod schema & 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 · orgId</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">→</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\">→</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\">→</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:
|
package/docs/content/server.md
CHANGED
|
@@ -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\">→</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\">→</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
|
-
```
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
import { accessFilter } from "@agent-native/core/sharing";
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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\">→</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\">←</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 → 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
|
-
|
|
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\">→</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\">→</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
|
|
package/docs/content/sharing.md
CHANGED
|
@@ -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 · <strong>default</strong></small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">↓</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\">↓</div><div class=\"diagram-card\"><span class=\"diagram-pill warn\">public</span><small class=\"diagram-muted\">+ anyone with the link (read-only) · 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 — <small class=\"diagram-muted\">add data models…</small></span><span class=\"diagram-pill\">real-time-sync — <small class=\"diagram-muted\">wire polling…</small></span><span class=\"diagram-pill\">create-skill — <small class=\"diagram-muted\">add a skill…</small></span></div><small class=\"diagram-muted\">just name + description (cheap)</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">→</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\">→</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
|
-
```
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
How to import projects from the legacy CSV export. Use when the user uploads
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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\">→</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\">→</div><div class=\"diagram-box\" data-rough>Writes SQL</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">→</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\">→</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\">→</div><div class=\"diagram-box\" data-rough><span class=\"diagram-pill accent\">Generation session</span><small class=\"diagram-muted\">image & video candidates · audit log</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">→</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\">→</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\">→</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\">→</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\">→</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\">→</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\">→</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
|

|
|
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
|
|