@agent-native/core 0.63.2 → 0.63.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
  2. package/dist/client/blocks/library/AnnotatedCodeBlock.js +23 -19
  3. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  4. package/dist/client/blocks/library/diagram.d.ts.map +1 -1
  5. package/dist/client/blocks/library/diagram.js +10 -11
  6. package/dist/client/blocks/library/diagram.js.map +1 -1
  7. package/dist/client/blocks/library/wireframe.d.ts.map +1 -1
  8. package/dist/client/blocks/library/wireframe.js +2 -1
  9. package/dist/client/blocks/library/wireframe.js.map +1 -1
  10. package/dist/server/auth.d.ts.map +1 -1
  11. package/dist/server/auth.js +5 -1
  12. package/dist/server/auth.js.map +1 -1
  13. package/dist/server/onboarding-html.d.ts.map +1 -1
  14. package/dist/server/onboarding-html.js +50 -5
  15. package/dist/server/onboarding-html.js.map +1 -1
  16. package/dist/styles/blocks.css +25 -3
  17. package/docs/content/a2a-protocol.md +48 -10
  18. package/docs/content/actions.md +35 -16
  19. package/docs/content/agent-mentions.md +25 -32
  20. package/docs/content/agent-surfaces.md +31 -0
  21. package/docs/content/agent-teams.md +17 -14
  22. package/docs/content/agent-web-surfaces.md +24 -15
  23. package/docs/content/authentication.md +21 -0
  24. package/docs/content/automations.md +37 -21
  25. package/docs/content/blueprint-installer.md +7 -0
  26. package/docs/content/cli-adapters.md +7 -0
  27. package/docs/content/client.md +14 -0
  28. package/docs/content/cloneable-saas.md +14 -0
  29. package/docs/content/code-agents-ui.md +27 -0
  30. package/docs/content/components.md +21 -0
  31. package/docs/content/context-awareness.md +33 -48
  32. package/docs/content/creating-templates.md +43 -52
  33. package/docs/content/cross-app-sso.md +41 -0
  34. package/docs/content/database.md +41 -21
  35. package/docs/content/deployment.md +23 -6
  36. package/docs/content/dispatch.md +27 -0
  37. package/docs/content/drop-in-agent.md +26 -27
  38. package/docs/content/durable-resume.md +13 -1
  39. package/docs/content/embedding-sdk.md +14 -0
  40. package/docs/content/evals.md +14 -0
  41. package/docs/content/extensions.md +19 -0
  42. package/docs/content/external-agents.md +38 -1
  43. package/docs/content/faq.md +14 -0
  44. package/docs/content/file-uploads.md +20 -0
  45. package/docs/content/frames.md +14 -0
  46. package/docs/content/getting-started.md +34 -16
  47. package/docs/content/harness-agents.md +14 -0
  48. package/docs/content/human-approval.md +15 -30
  49. package/docs/content/key-concepts.md +37 -0
  50. package/docs/content/local-file-mode.md +26 -19
  51. package/docs/content/mcp-apps.md +36 -1
  52. package/docs/content/mcp-clients.md +49 -0
  53. package/docs/content/mcp-protocol.md +36 -0
  54. package/docs/content/messaging.md +34 -8
  55. package/docs/content/migration-workbench.md +7 -0
  56. package/docs/content/multi-app-workspace.md +29 -16
  57. package/docs/content/multi-tenancy.md +14 -0
  58. package/docs/content/native-chat-ui.md +20 -3
  59. package/docs/content/notifications.md +30 -0
  60. package/docs/content/observability.md +20 -1
  61. package/docs/content/observational-memory.md +14 -0
  62. package/docs/content/onboarding.md +32 -41
  63. package/docs/content/plan-plugin.md +14 -0
  64. package/docs/content/pr-visual-recap.md +14 -0
  65. package/docs/content/processors.md +7 -0
  66. package/docs/content/progress.md +23 -0
  67. package/docs/content/pure-agent-apps.md +7 -0
  68. package/docs/content/real-time-collaboration.md +19 -18
  69. package/docs/content/recurring-jobs.md +22 -19
  70. package/docs/content/routing.md +8 -0
  71. package/docs/content/sandbox-adapters.md +19 -0
  72. package/docs/content/security.md +38 -0
  73. package/docs/content/server.md +47 -25
  74. package/docs/content/sharing.md +50 -0
  75. package/docs/content/skills-guide.md +27 -42
  76. package/docs/content/template-analytics.md +71 -41
  77. package/docs/content/template-assets.md +76 -3
  78. package/docs/content/template-brain.md +89 -1
  79. package/docs/content/template-calendar.md +86 -58
  80. package/docs/content/template-chat.md +25 -9
  81. package/docs/content/template-clips.md +124 -16
  82. package/docs/content/template-content.md +146 -47
  83. package/docs/content/template-design.md +62 -2
  84. package/docs/content/template-dispatch.md +56 -9
  85. package/docs/content/template-forms.md +69 -13
  86. package/docs/content/template-mail.md +73 -26
  87. package/docs/content/template-plan.md +80 -1
  88. package/docs/content/template-slides.md +95 -74
  89. package/docs/content/template-videos.md +73 -52
  90. package/docs/content/tracking.md +21 -0
  91. package/docs/content/using-your-agent.md +14 -0
  92. package/docs/content/voice-input.md +31 -10
  93. package/docs/content/what-is-agent-native.md +25 -13
  94. package/docs/content/workspace-connections.md +49 -0
  95. package/docs/content/workspace-management.md +24 -0
  96. package/docs/content/workspace.md +50 -19
  97. package/docs/content/writing-agent-instructions.md +7 -0
  98. package/package.json +1 -1
@@ -11,6 +11,13 @@ Dispatch is the central app that sits in front of every other app in your worksp
11
11
 
12
12
  Without Dispatch, every app in a multi-app workspace ends up re-implementing the same plumbing: its own Slack bot, its own secret store, its own scheduled jobs, its own copy of the workspace's instructions. Rotating one API key turns into ten redeployments. Adding a new policy turns into ten copy-pastes. Dispatch centralizes all of that in one app so the others stay focused on their domain.
13
13
 
14
+ ```an-diagram title="Dispatch as the workspace control plane" summary="One inbox, one vault, one MCP gateway, and shared resources sit in front of the domain apps, which Dispatch reaches as A2A peers."
15
+ {
16
+ "html": "<div class=\"dsp-hub\"><div class=\"diagram-node\">Users &amp; external agents<br><small class=\"diagram-muted\">Slack · email · Telegram · WhatsApp · MCP</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-panel dsp-control\" data-rough><span class=\"diagram-pill accent\">Dispatch &mdash; control plane</span><div class=\"dsp-caps\"><span class=\"diagram-pill\">Central inbox</span><span class=\"diagram-pill\">Secret vault</span><span class=\"diagram-pill\">Cross-app delegation</span><span class=\"diagram-pill\">MCP gateway</span><span class=\"diagram-pill\">Workspace resources</span></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"dsp-peers\"><div class=\"diagram-box\" data-rough>Mail</div><div class=\"diagram-box\" data-rough>Calendar</div><div class=\"diagram-box\" data-rough>Analytics</div></div><small class=\"diagram-muted\">domain apps &mdash; A2A peers</small></div>",
17
+ "css": ".dsp-hub{display:flex;flex-direction:column;align-items:center;gap:10px}.dsp-hub .dsp-control{display:flex;flex-direction:column;align-items:center;gap:10px;width:100%}.dsp-hub .dsp-caps{display:flex;gap:8px;flex-wrap:wrap;justify-content:center}.dsp-hub .dsp-peers{display:flex;gap:10px;flex-wrap:wrap;justify-content:center}"
18
+ }
19
+ ```
20
+
14
21
  ## When you want Dispatch {#when}
15
22
 
16
23
  Reach for Dispatch when any of these are true:
@@ -55,6 +62,19 @@ Dispatch auto-discovers the other apps in your workspace as A2A peers — no man
55
62
 
56
63
  Dispatch can be the single MCP connector for external agents: add `https://dispatch.agent-native.com/_agent-native/mcp` once in Claude, ChatGPT, Codex, or Cursor, and one authorization reaches every granted workspace app instead of one connector per app. See [External Agents](/docs/external-agents) for the full connect flow, app grants, OAuth, and inline MCP App previews.
57
64
 
65
+ ```an-api
66
+ {
67
+ "method": "POST",
68
+ "path": "/_agent-native/mcp",
69
+ "summary": "Unified MCP gateway endpoint",
70
+ "description": "The single MCP connector URL external agents add (e.g. `https://dispatch.agent-native.com/_agent-native/mcp`). One authorization here reaches every **granted** workspace app instead of wiring one connector per app. App grants, OAuth, and inline MCP App previews are covered in [External Agents](/docs/external-agents).",
71
+ "auth": "Standard remote MCP OAuth, handled by the framework. The granted-app set scopes which workspace apps the connector can reach.",
72
+ "responses": [
73
+ { "status": "200", "description": "MCP JSON-RPC response — tools, resources, and MCP App UI resources aggregated across granted workspace apps." }
74
+ ]
75
+ }
76
+ ```
77
+
58
78
  ### Workspace resources
59
79
 
60
80
  Skills, guardrail instructions, agent profiles, and reference resources can be authored once in Dispatch and inherited by the rest of the workspace. Resources with **All apps** scope are global: Dispatch stores them once at workspace scope, and every app agent reads them at runtime. They are not copied into each app, and there is no manual workspace-resource sync step. App shared resources and personal resources can override or narrow the workspace defaults locally.
@@ -90,6 +110,13 @@ Walk through one example end-to-end. A user DMs the bot: _"summarize last week's
90
110
  4. **Reply posted in thread.** The analytics agent returns a result. Dispatch formats it and posts back into the same Slack thread the user wrote in, using the linked identity if there is one (so the agent acts with the requester's permissions, not the workspace owner's).
91
111
  5. **Recovery if anything dies.** If the processor crashes mid-flight — A2A timeout, downstream agent error, function freeze — a retry job sweeps stuck tasks every 60 seconds and re-fires the processor. Up to three attempts before the task is marked `failed`.
92
112
 
113
+ ```an-diagram title="A Slack message through Dispatch" summary="Slack enqueues into SQL, a fresh execution drains it, the Dispatch agent delegates the domain work over A2A, and the reply lands back in the originating thread. A 60s retry job recovers anything that dies mid-flight."
114
+ {
115
+ "html": "<div class=\"dsp-flow\"><div class=\"dsp-row\"><div class=\"diagram-node\">Slack DM<br><small class=\"diagram-muted\">\"summarize last week's signups\"</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough><strong>/slack/webhook</strong><br><small class=\"diagram-muted\">verify + INSERT pending task</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-pill ok\">200</div></div><div class=\"dsp-row\"><div class=\"diagram-box\" data-rough><strong>fresh processor</strong><br><small class=\"diagram-muted\">claim task · start agent loop</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\" data-rough><span class=\"diagram-pill accent\">Dispatch agent decides</span><small class=\"diagram-muted\">analytics intent &rarr; call-agent</small></div></div><div class=\"dsp-row\"><div class=\"diagram-box\" data-rough>Analytics app<br><small class=\"diagram-muted\">A2A peer · runs the SQL work</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-pill ok\">reply posted in thread</div></div><div class=\"diagram-panel dsp-retry\" data-rough><span class=\"diagram-pill warn\">recovery</span> <span class=\"diagram-muted\">if the processor crashes &mdash; A2A timeout, downstream error, freeze &mdash; the 60s retry job re-fires it (&le;3 attempts) so the Slack reply still arrives</span> <span class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&#8635;</span></div></div>",
116
+ "css": ".dsp-flow{display:flex;flex-direction:column;gap:12px}.dsp-flow .dsp-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.dsp-flow .center{display:flex;flex-direction:column;align-items:center;gap:4px}.dsp-flow .dsp-retry{display:flex;align-items:center;gap:8px;flex-wrap:wrap}"
117
+ }
118
+ ```
119
+
93
120
  The same flow applies for email, Telegram, and WhatsApp — only the adapter changes.
94
121
 
95
122
  ## Reliability story {#reliability}
@@ -27,39 +27,31 @@ You don't need to build agent-native from scratch. The agent chat, workspace tab
27
27
 
28
28
  All of these are exported from `@agent-native/core/client`.
29
29
 
30
+ ```an-diagram title="The mount model" summary="<AgentSidebar> wraps your existing layout. Your routes render in the main area; the agent panel mounts beside them. <AgentPanel> is the same panel without the wrapper."
31
+ {
32
+ "html": "<div class=\"diagram-mount\"><div class=\"diagram-box sidebar\" data-rough><span class=\"diagram-pill accent\">&lt;AgentSidebar&gt;</span><div class=\"inner\"><div class=\"diagram-node main\">Your app<br><small class=\"diagram-muted\">children: header + &lt;Outlet/&gt;</small></div><div class=\"diagram-node panel\">Agent panel<br><small class=\"diagram-muted\">chat &middot; CLI &middot; workspace</small></div></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&harr;</div><div class=\"diagram-card alt\"><span class=\"diagram-pill\">&lt;AgentPanel&gt;</span><small class=\"diagram-muted\">same panel, no wrapper &mdash; you own the layout</small></div></div>",
33
+ "css": ".diagram-mount{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-mount .sidebar{display:flex;flex-direction:column;gap:8px;padding:14px}.diagram-mount .inner{display:flex;gap:10px}.diagram-mount .main{flex:2}.diagram-mount .panel{flex:1}.diagram-mount .alt{display:flex;flex-direction:column;gap:6px;padding:14px}.diagram-mount .diagram-arrow{font-size:22px;line-height:1}"
34
+ }
35
+ ```
36
+
30
37
  ## The 80% case: `<AgentSidebar>` {#sidebar}
31
38
 
32
39
  The most common setup is a sidebar that opens from the right on any screen.
33
40
  Wrap your existing root layout with `<AgentSidebar>`; whatever you pass as
34
41
  children stays in the main app area. The agent chat is the side panel.
35
42
 
36
- ```tsx
37
- // app/root.tsx
38
- import { Outlet } from "react-router";
39
- import { AgentSidebar, AgentToggleButton } from "@agent-native/core/client";
40
-
41
- export default function Root() {
42
- return (
43
- <AgentSidebar
44
- emptyStateText="How can I help?"
45
- suggestions={[
46
- "Summarize my inbox",
47
- "Draft a reply to the latest email",
48
- "Show me yesterday's signup numbers",
49
- ]}
50
- dynamicSuggestions
51
- defaultSidebarWidth={420}
52
- position="right"
53
- >
54
- <header>
55
- <AgentToggleButton />
56
- </header>
57
-
58
- <main>
59
- <Outlet />
60
- </main>
61
- </AgentSidebar>
62
- );
43
+ ```an-annotated-code title="Wrapping the root layout with <AgentSidebar>"
44
+ {
45
+ "filename": "app/root.tsx",
46
+ "language": "tsx",
47
+ "code": "import { Outlet } from \"react-router\";\nimport { AgentSidebar, AgentToggleButton } from \"@agent-native/core/client\";\n\nexport default function Root() {\n return (\n <AgentSidebar\n emptyStateText=\"How can I help?\"\n suggestions={[\n \"Summarize my inbox\",\n \"Draft a reply to the latest email\",\n \"Show me yesterday's signup numbers\",\n ]}\n dynamicSuggestions\n defaultSidebarWidth={420}\n position=\"right\"\n >\n <header>\n <AgentToggleButton />\n </header>\n\n <main>\n <Outlet />\n </main>\n </AgentSidebar>\n );\n}",
48
+ "annotations": [
49
+ { "lines": "6", "label": "Wrapper", "note": "`<AgentSidebar>` wraps your whole layout. It adds the toggleable side panel; everything you pass as children stays in the main app area." },
50
+ { "lines": "8-12", "label": "Starter prompts", "note": "`suggestions` render as clickable chips on the empty chat." },
51
+ { "lines": "13", "label": "Context-aware chips", "note": "`dynamicSuggestions` merges screen-aware prompts (e.g. \"Summarize this selection\") with your static ones. On by default." },
52
+ { "lines": "18-20", "label": "Toggle button", "note": "Put `<AgentToggleButton />` anywhere in your header to open and close the panel." },
53
+ { "lines": "22-24", "label": "Your app", "note": "`<Outlet/>` (your routes) renders in the main area, untouched." }
54
+ ]
63
55
  }
64
56
  ```
65
57
 
@@ -205,6 +197,13 @@ const { mutate, isPending } = useActionMutation("reply-to-email");
205
197
 
206
198
  Type-safe arguments come from the zod schema in your `defineAction()`. See [Actions](/docs/actions) for the full action system.
207
199
 
200
+ ```an-callout
201
+ {
202
+ "tone": "decision",
203
+ "body": "**`useActionMutation` vs `sendToAgentChat`.** Run the operation directly with `useActionMutation` when the user clicked a deterministic button (\"Send reply\"). Hand it to `sendToAgentChat` when the work needs the agent's reasoning, tools, or multi-step planning. Never call an inline `llm()` from UI — that is rung 1 of the [ladder](/docs/what-is-agent-native#the-ladder)."
204
+ }
205
+ ```
206
+
208
207
  ## Selection + cursor awareness {#selection}
209
208
 
210
209
  The agent can see what the user has selected — text, cells, slides, contacts — via the `navigation` and `selection` keys in application state. The empty chat also uses those keys to offer dynamic suggestions such as "Summarize this selection" or "Improve this slide" when the current screen makes them relevant. If you'd like Cmd-I (or similar) to send a selected range into the chat as context, see [Context Awareness](/docs/context-awareness).
@@ -13,6 +13,13 @@ Hosted agent runs get interrupted: a serverless function hits its hard timeout m
13
13
 
14
14
  Durable resume closes that gap. On resume, the framework knows which side-effecting tool calls already completed and refuses to re-run them — at two layers.
15
15
 
16
+ ```an-diagram title="Two layers block duplicate side effects on resume" summary="The journal reads the durable ledger and classifies prior calls; layer 1 tells the model, layer 2 hard-blocks a re-dispatched write that matches a completed entry."
17
+ {
18
+ "html": "<div class=\"diagram-durable\"><div class=\"diagram-box\" data-rough>Run-event ledger<br><small class=\"diagram-muted\">tool_start / tool_done</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\" data-rough><strong>Tool-call journal</strong><small class=\"diagram-ok\">completed = start+done</small><small class=\"diagram-warn\">interrupted = start, no done</small></div><div class=\"diagram-col\"><div class=\"diagram-pill\">Layer 1 · prompt note &rarr; model</div><div class=\"diagram-pill accent\">Layer 2 · hard-block re-dispatched write</div></div></div>",
19
+ "css": ".diagram-durable{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-durable .diagram-col{display:flex;flex-direction:column;gap:8px}.diagram-durable .diagram-arrow{font-size:22px;line-height:1}.diagram-durable .center{display:flex;flex-direction:column;align-items:center;gap:4px}"
20
+ }
21
+ ```
22
+
16
23
  ## The tool-call journal {#journal}
17
24
 
18
25
  The journal is a **pure read over the durable run-event ledger** — there is no new recording hook in the hot path. It classifies the tool calls already recorded for the current turn:
@@ -44,7 +51,12 @@ Key properties:
44
51
  - **Consume-once.** Each completed entry is claimed when matched, so two genuinely-distinct identical fresh calls in the same turn don't both short-circuit on one journaled completion.
45
52
  - **Fresh calls untouched.** A first-turn call sees an empty journal; nothing changes for normal runs.
46
53
 
47
- Together the two layers mean an interrupted run that already had a real side effect resumes without repeating it — no duplicate emails, charges, or tickets — while genuinely new work still runs.
54
+ ```an-callout
55
+ {
56
+ "tone": "success",
57
+ "body": "Together the two layers mean an interrupted run that already had a real side effect resumes **without repeating it** — no duplicate emails, charges, or tickets — while genuinely new work still runs. Read-only actions are never blocked; re-reading is always safe."
58
+ }
59
+ ```
48
60
 
49
61
  ## Related
50
62
 
@@ -11,6 +11,13 @@ already using. If you are still deciding between headless agents, rich chat, an
11
11
  embedded sidecar, or a full app, start with
12
12
  [Agent Surfaces](/docs/agent-surfaces).
13
13
 
14
+ ```an-diagram title="The embedding membrane" summary="The host app supplies server-side auth and live page context; Agent-Native runs the durable sidecar and reaches the open tab through client actions and host commands."
15
+ {
16
+ "html": "<div class=\"diagram-embed\"><div class=\"diagram-box\" data-rough><strong>Host SaaS app</strong><small class=\"diagram-muted\">your UI, your auth</small></div><div class=\"diagram-col\"><div class=\"diagram-pill accent\">getContext &rarr;</div><div class=\"diagram-pill\">&larr; client actions</div><div class=\"diagram-pill\">&larr; host commands</div></div><div class=\"diagram-panel center\" data-rough><strong>Agent-Native sidecar</strong><small class=\"diagram-muted\">durable chat · app state · extensions</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>SQL<br><small class=\"diagram-muted\">framework tables</small></div></div>",
17
+ "css": ".diagram-embed{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-embed .diagram-col{display:flex;flex-direction:column;gap:8px}.diagram-embed .diagram-arrow{font-size:22px;line-height:1}.diagram-embed .center{display:flex;flex-direction:column;align-items:center;gap:4px}"
18
+ }
19
+ ```
20
+
14
21
  ## Start here: the batteries-included plugin {#batteries-included}
15
22
 
16
23
  For most SaaS hosts, **use the full embedded runtime** — the server plugin
@@ -366,6 +373,13 @@ const hostTools = createAgentNativeHostTools({
366
373
 
367
374
  For a CLAW-style coworker, the iframe can also register its live browser tab with the sidecar backend. The agent then gets normal backend tools that enqueue a request, the iframe claims it, the host page executes it, and the backend returns the result to the agent.
368
375
 
376
+ ```an-diagram title="Server-mediated browser-session bridge" summary="A backend tool enqueues work; the registered tab claims it, runs it on the live page, and the result returns to the agent — so a backend/Slack/A2A agent can still touch the open tab."
377
+ {
378
+ "html": "<div class=\"diagram-bridge\"><div class=\"diagram-node\" data-rough>Backend agent<br><small class=\"diagram-muted\">chat · Slack · A2A</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>enqueue request<br><small class=\"diagram-muted\">/_agent-native/browser-sessions</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\" data-rough>Live tab claims it<br><small class=\"diagram-muted\">registered bridge</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-pill ok\">result &rarr; agent</div></div>",
379
+ "css": ".diagram-bridge{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-bridge .diagram-arrow{font-size:22px;line-height:1}"
380
+ }
381
+ ```
382
+
369
383
  In the sidecar app, start the browser-session bridge once when the iframe mounts:
370
384
 
371
385
  ```tsx
@@ -14,6 +14,13 @@ This is complementary to the post-hoc scoring in [Observability](/docs/observabi
14
14
 
15
15
  The runner resolves a provider-agnostic engine/model from the existing registry — no model is hardcoded — so the same suite runs against whatever engine the app is configured for.
16
16
 
17
+ ```an-diagram title="From fixed input to deploy gate" summary="The runner actually runs the agent loop on each case, scores the output, and exits non-zero if any scorer falls below threshold — making it a drop-in CI gate."
18
+ {
19
+ "html": "<div class=\"eval-flow\"><div class=\"diagram-node\">*.eval.ts<br><small class=\"diagram-muted\">prompt + expected behavior</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\">Run the agent loop<br><small class=\"diagram-muted\">real engine/model</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\">Scorers<br><small class=\"diagram-muted\">every one must pass threshold</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col\"><div class=\"diagram-box ok\">exit 0 &rarr; deploy</div><div class=\"diagram-box warn\">exit 1 &rarr; block</div></div></div>",
20
+ "css": ".eval-flow{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.eval-flow .diagram-node{display:flex;flex-direction:column;gap:2px;padding:10px 14px}.eval-flow .diagram-col{display:flex;flex-direction:column;gap:8px}.eval-flow .diagram-arrow{font-size:22px;line-height:1}"
21
+ }
22
+ ```
23
+
17
24
  ## Writing an eval {#writing}
18
25
 
19
26
  Drop a `*.eval.ts` file anywhere in the app (or an `evals/*.ts` file). Each file `export default defineEval(...)` (or exports an array of them):
@@ -73,6 +80,13 @@ Imported from `@agent-native/core/eval`:
73
80
 
74
81
  `createScorer` builds a scorer from a Mastra-style 4-step pipeline. Only `generateScore` is required:
75
82
 
83
+ ```an-diagram title="The 4-step scorer pipeline" summary="preprocess and analyze default to identity; only generateScore is required. analyze can run plain JS or call an LLM judge via ctx."
84
+ {
85
+ "html": "<div class=\"scorer\"><div class=\"diagram-card\"><span class=\"diagram-pill\">preprocess(run)</span><small class=\"diagram-muted\">transform the run/output &middot; optional</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill\">analyze(x, ctx)</span><small class=\"diagram-muted\">plain JS or LLM judge &middot; optional</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill accent\">generateScore(a)</span><small class=\"diagram-muted\">&rarr; 0..1 normalized &middot; <strong>required</strong></small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill\">generateReason</span><small class=\"diagram-muted\">human-readable why &middot; optional</small></div></div>",
86
+ "css": ".scorer{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.scorer .diagram-card{display:flex;flex-direction:column;gap:2px;padding:8px 12px}.scorer .diagram-arrow{font-size:20px;line-height:1}"
87
+ }
88
+ ```
89
+
76
90
  ```text
77
91
  preprocess(run) → x transform the run/output (optional)
78
92
  analyze(x, ctx) → analysis plain JS OR an LLM judge (optional)
@@ -17,6 +17,13 @@ Three things make extensions work:
17
17
  - **Full access to the template's data.** Extensions can call the same actions the agent calls — `list-emails` in Mail, `list-decks` in Slides, `list-recordings` in Clips — so they have everything the host app has.
18
18
  - **Built-in storage.** Each extension has its own per-user / per-org key-value store, so it can save state without you adding a new SQL table.
19
19
 
20
+ ```an-diagram title="The sandbox bridge" summary="Extension HTML runs in an isolated iframe and reaches the host only through a fixed set of bridge helpers — every call is scoped and access-checked."
21
+ {
22
+ "html": "<div class=\"ext-bridge\"><div class=\"diagram-card sandbox\" data-rough><span class=\"diagram-pill warn\">Sandboxed iframe</span><small class=\"diagram-muted\">Alpine.js HTML &middot; no host cookies, session, or DOM</small><div class=\"ext-helpers\"><span class=\"diagram-pill\">appAction</span><span class=\"diagram-pill\">appFetch</span><span class=\"diagram-pill\">dbQuery / dbExec</span><span class=\"diagram-pill\">extensionData</span><span class=\"diagram-pill\">extensionFetch</span></div></div><div class=\"diagram-arrow diagram-accent\" aria-hidden=\"true\">&harr;</div><div class=\"diagram-col\"><div class=\"diagram-box\">Host template<br><small class=\"diagram-muted\">actions, auto-scoped SQL</small></div><div class=\"diagram-box\">Secret proxy<br><small class=\"diagram-muted\"><code>${keys.NAME}</code>, domain-locked</small></div><div class=\"diagram-box\">External APIs<br><small class=\"diagram-muted\">via extensionFetch only</small></div></div></div>",
23
+ "css": ".ext-bridge{display:flex;align-items:center;gap:16px;flex-wrap:wrap}.ext-bridge .sandbox{display:flex;flex-direction:column;gap:8px;padding:16px 18px;flex:1;min-width:240px}.ext-bridge .ext-helpers{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px}.ext-bridge .diagram-col{display:flex;flex-direction:column;gap:8px}.ext-bridge .diagram-arrow{font-size:24px}"
24
+ }
25
+ ```
26
+
20
27
  Extensions can also be **repo-backed in Local File Mode**. In that workflow,
21
28
  `agent-native.json` declares an `extensions` folder, each extension has an
22
29
  `extension.json` manifest plus an HTML entry file, and the app renders those
@@ -208,6 +215,14 @@ A slot is a named widget area a template ships:
208
215
 
209
216
  When an extension is **installed into a slot**, the host pushes the relevant context — the contact's email, the dashboard id, the event id — into the iframe. The extension reads `window.slotContext` to know what the user is looking at.
210
217
 
218
+ ```an-diagram title="Slots push context into the widget" summary="The host template owns named slots; installing an extension into one feeds it window.slotContext for whatever the user is currently viewing."
219
+ {
220
+ "html": "<div class=\"slot\"><div class=\"diagram-card\"><span class=\"diagram-pill\">Mail thread</span><small class=\"diagram-muted\">slot <code>mail.contact-sidebar.bottom</code></small></div><div class=\"diagram-arrow diagram-accent\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box accent\"><code>window.slotContext</code><br><small class=\"diagram-muted\">{ contactEmail }</small></div><div class=\"diagram-arrow diagram-accent\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill\">Contact notes</span><small class=\"diagram-muted\">loads notes for that contact &mdash; same widget, different context</small></div></div>",
221
+ "css": ".slot{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.slot .diagram-card{display:flex;flex-direction:column;gap:4px;padding:14px 16px;min-width:180px}.slot .diagram-arrow{font-size:22px}"
222
+ }
223
+
224
+ ```
225
+
211
226
  ### A concrete example
212
227
 
213
228
  Imagine the contact-notes extension from the gallery. On its own, it's a standalone widget. To make it appear inside the Mail contact sidebar:
@@ -308,6 +323,10 @@ Rule of thumb: **if it's for one user or one team, it's an extension.** If every
308
323
 
309
324
  ## Security {#security}
310
325
 
326
+ ```an-callout
327
+ { "tone": "success", "body": "**The raw secret never reaches the browser.** `extensionFetch` substitutes `${keys.NAME}` server-side and each key is locked to a URL allowlist, so even a leaked extension can't exfiltrate it elsewhere." }
328
+ ```
329
+
311
330
  Extensions run in a sandboxed iframe:
312
331
 
313
332
  - **Isolated** from the parent app's cookies, session, and DOM.
@@ -19,6 +19,15 @@ An agent-native app is reachable by any MCP-compatible host — Claude, Claude D
19
19
 
20
20
  The external-agent bridge closes the loop. First you connect your own agent to a **hosted** app — either by pasting the app's remote MCP URL into a chat host like Claude or ChatGPT, or by running the developer CLI flow for local coding agents. Then the agent does the work over MCP and hands the user either an inline **MCP App** UI in compatible hosts or a single **"Open in &lt;app&gt; →"** link that opens the real app focused on exactly what was produced. It reuses the existing `navigate` / `application_state` contract the UI already drains every 2s (see [Context Awareness](/docs/context-awareness)) — there is no second navigation mechanism.
21
21
 
22
+ ```an-diagram title="The external-agent round-trip" summary="An external host calls a tool over MCP; the app returns an artifact plus an Open link. Clicking it resolves the browser session and focuses the artifact in the running UI — the link carries no privileged state."
23
+ {
24
+ "html": "<div class=\"xa-trip\"><div class=\"diagram-box\" data-rough>External host<br><small class=\"diagram-muted\">Claude &middot; ChatGPT &middot; Codex &middot; Cursor</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\" data-rough><span class=\"diagram-pill accent\">MCP tool call</span><small class=\"diagram-muted\">e.g. <code>manage-draft</code></small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>App produces artifact<br><small class=\"diagram-muted\">+ <code>Open in &lt;app&gt; &rarr;</code> deep link / MCP App</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-box\" data-rough>User clicks link</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\" data-rough><span class=\"diagram-pill ok\"><code>/_agent-native/open</code></span><small class=\"diagram-muted\">resolves the <strong>browser</strong> session</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>Writes <code>navigate</code> app-state<br><small class=\"diagram-muted\">UI focuses the artifact</small></div></div>",
25
+ "css": ".xa-trip{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.xa-trip .center{display:flex;flex-direction:column;align-items:center;gap:4px}.xa-trip .diagram-arrow{font-size:22px;line-height:1}.xa-trip code{font-size:.85em}"
26
+ }
27
+ ```
28
+
29
+ The identity rule is the safety hinge: the link is just `view` + record ids + filters, and the record-focusing `navigate` write is scoped to whoever is logged into the **browser** — never the external agent's MCP token. That is why the link is safe to paste into a terminal or chat transcript.
30
+
22
31
  ## Which agent path do you need? {#which-agent-path}
23
32
 
24
33
  - **External MCP host:** use this page when Claude, ChatGPT, Codex, Cursor, OpenCode, GitHub Copilot / VS Code, or another MCP-compatible host should call your hosted agent-native app.
@@ -238,6 +247,13 @@ This is the canonical explanation of MCP catalog tiers — other pages link here
238
247
 
239
248
  The MCP server serves a **compact catalog by default to every caller** — hosted connectors (ChatGPT, Claude), code clients (Claude Code, Cursor, Codex), and the local CLI/stdio proxy alike. The full action surface is served only on explicit opt-in. The catalog is never inferred from the client name or user-agent.
240
249
 
250
+ ```an-diagram title="Two catalog tiers" summary="Every caller gets the compact tier by default; the full ~105-tool surface is opt-in only. tool-search bridges the gap so nothing is ever truly hidden."
251
+ {
252
+ "html": "<div class=\"xa-tiers\"><div class=\"diagram-card\" data-rough><span class=\"diagram-pill ok\">Compact / connector tier &middot; default</span><strong>~20&ndash;30 tools</strong><small class=\"diagram-muted\">Template-declared app actions + cross-app builtins (<code>list_apps</code>, <code>open_app</code>, <code>ask_app</code>, <code>create_embed_session</code>) + always-present <code>tool-search</code>.</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&harr;</div><div class=\"diagram-card\" data-rough><span class=\"diagram-pill accent\">Full tier &middot; opt-in</span><strong>~105 tools</strong><small class=\"diagram-muted\">Explicit opt-in only: <code>--full-catalog</code> token or <code>AGENT_NATIVE_MCP_FULL_CATALOG=1</code>.</small></div></div><p class=\"diagram-muted note\"><code>tool-search</code> reaches any full-tier tool on demand &mdash; so the compact default keeps context small without hiding capability.</p>",
253
+ "css": ".xa-tiers{display:flex;align-items:stretch;gap:14px;flex-wrap:wrap}.xa-tiers .diagram-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;flex:1;min-width:240px}.xa-tiers .diagram-arrow{align-self:center;font-size:24px;line-height:1}.xa-tiers .note{flex-basis:100%;margin:4px 0 0;font-size:.85em}.xa-tiers code{font-size:.85em}"
254
+ }
255
+ ```
256
+
241
257
  ### Compact / connector tier (default) {#connector-tier}
242
258
 
243
259
  By default every connected agent sees a small, curated catalog (~20–30 tools vs. ~105 in the full surface):
@@ -420,7 +436,28 @@ The `link` builder is **pure and synchronous — no I/O, no awaits**. It runs be
420
436
 
421
437
  ### The `/_agent-native/open` route {#open-route}
422
438
 
423
- When the user clicks the link in any browser or inline webview, `GET /_agent-native/open` (`createOpenRouteHandler`, mounted by the core routes plugin):
439
+ When the user clicks the link in any browser or inline webview, `GET /_agent-native/open` (`createOpenRouteHandler`, mounted by the core routes plugin) runs the steps below.
440
+
441
+ ```an-api
442
+ {
443
+ "method": "GET",
444
+ "path": "/_agent-native/open",
445
+ "summary": "Deep-link open route — focuses the browser UI on a record",
446
+ "description": "Resolves the browser session, writes a one-shot `navigate` application-state command scoped to that session, and 302-redirects to a safe same-origin path. Always build the URL with `buildDeepLink(...)`; never hand-format it. Can be disabled per app with `disableOpenRoute`.",
447
+ "auth": "Browser session via `getSession`. The auth guard bypasses this exact path; if unauthenticated it serves login HTML at the same URL, and the form reload re-enters authenticated (no `?next=` plumbing).",
448
+ "params": [
449
+ { "name": "app", "in": "query", "type": "string", "description": "Target app id (e.g. `mail`)." },
450
+ { "name": "view", "in": "query", "type": "string", "description": "View to focus; also folded into the `navigate` payload." },
451
+ { "name": "to", "in": "query", "type": "string", "description": "Optional explicit same-origin relative redirect target. Falls back to `/<view>`, then a per-template `resolveOpenPath`." },
452
+ { "name": "compose", "in": "query", "type": "string", "description": "base64url-encoded draft, decoded into a `compose-<id>` application-state key." },
453
+ { "name": "f_*", "in": "query", "type": "string", "description": "Filter params forwarded to the redirect so lists/dashboards open pre-filtered." }
454
+ ],
455
+ "responses": [
456
+ { "status": "302", "description": "Redirect to a safe same-origin relative path. Cross-origin, scheme-relative `//host`, and control-char redirects are rejected (open-redirect guard)." },
457
+ { "status": "200", "description": "Login HTML served at the same URL when the browser session is unauthenticated." }
458
+ ]
459
+ }
460
+ ```
424
461
 
425
462
  1. Resolves the **browser** session via `getSession` (the auth guard bypasses the exact path `/_agent-native/open`).
426
463
  2. If unauthenticated, serves the configured login HTML **at the same URL**; the form's success handler reloads `window.location`, re-entering the route authenticated — no `?next=` plumbing.
@@ -28,6 +28,13 @@ Agent-native is for people who want a real app and an AI agent to work from the
28
28
 
29
29
  Most apps bolt AI on as an afterthought that can't actually _do_ things in the app. In an agent-native app the agent is a first-class citizen that shares the same actions, database, and state as the UI, so it can do anything the buttons can — and modify the app's own code. See [What Is Agent-Native?](/docs/what-is-agent-native#the-ladder).
30
30
 
31
+ ```an-diagram title="Bolted-on AI vs. agent-native" summary="A bolted-on chat sidebar lives in its own world. An agent-native agent shares the same actions, database, and state as the UI."
32
+ {
33
+ "html": "<div class=\"diagram-vs\"><div class=\"diagram-col\"><span class=\"diagram-pill warn\">Bolted-on AI</span><div class=\"diagram-node\">Chat sidebar</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-box\" data-rough>separate AI world<br><small class=\"diagram-muted\">can't touch the app</small></div><div class=\"diagram-box diagram-muted\">App UI &amp; data</div></div><div class=\"diagram-divider\" aria-hidden=\"true\"></div><div class=\"diagram-col\"><span class=\"diagram-pill ok\">Agent-native</span><div class=\"diagram-row2\"><div class=\"diagram-node\">UI</div><div class=\"diagram-node\">Agent</div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-box\" data-rough>shared actions, DB &amp; state</div></div></div>",
34
+ "css": ".diagram-vs{display:flex;align-items:stretch;gap:18px;flex-wrap:wrap}.diagram-vs .diagram-col{display:flex;flex-direction:column;gap:8px;align-items:center;flex:1;min-width:200px}.diagram-vs .diagram-row2{display:flex;gap:8px}.diagram-vs .diagram-arrow{font-size:20px;line-height:1}.diagram-vs .diagram-divider{width:1px;align-self:stretch;background:currentColor;opacity:.15}"
35
+ }
36
+ ```
37
+
31
38
  ### Is it open source? {#is-this-open-source}
32
39
 
33
40
  Yes. The framework and all templates are open source. You can run everything locally, self-host, or use Builder.io's cloud for managed hosting, collaboration, and team features.
@@ -119,6 +126,13 @@ Anywhere. The server runs on Nitro, which compiles to any deployment target: Nod
119
126
 
120
127
  SSE gives same-process writes an immediate path to the browser, and a lightweight version-counter poll remains the fallback because it works in every deployment environment — including serverless and edge, where persistent sockets may not be available. See [Key Concepts — Live sync](/docs/key-concepts#polling-sync).
121
128
 
129
+ ```an-diagram title="SSE first, polling fallback" summary="Same-process writes stream instantly; a version-counter poll keeps serverless, edge, and cross-process writes convergent."
130
+ {
131
+ "html": "<div class=\"diagram-transport\"><div class=\"diagram-box\" data-rough>DB write</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col\"><div class=\"diagram-node\">SSE<br><small class=\"diagram-muted\">/_agent-native/events &middot; instant</small></div><div class=\"diagram-node\">Poll<br><small class=\"diagram-muted\">/_agent-native/poll &middot; universal fallback</small></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>Browser refetch</div></div>",
132
+ "css": ".diagram-transport{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-transport .diagram-col{display:flex;flex-direction:column;gap:8px}.diagram-transport .diagram-arrow{font-size:22px;line-height:1}"
133
+ }
134
+ ```
135
+
122
136
  ### Why can't the UI call an LLM directly? {#why-no-inline-llm-calls}
123
137
 
124
138
  AI is non-deterministic, so you need conversation flow to give feedback and iterate — not one-shot buttons — and the agent already has your codebase, instructions, skills, and history that an inline call lacks. Routing everything through the agent is also what lets the app be driven from Slack, Telegram, or another agent. See [Key Concepts — Agent chat bridge](/docs/key-concepts#agent-chat-bridge).
@@ -17,6 +17,26 @@ The provider resolution order is:
17
17
  2. **Builder.io provider** — built-in, activates automatically when Builder.io is connected
18
18
  3. **SQL fallback** — stores files as base64 in the database (fine for dev, not for production)
19
19
 
20
+ ```an-diagram title="Provider resolution order" summary="uploadFile() picks the first configured provider in order. The SQL fallback always exists so uploads work with zero setup."
21
+ {
22
+ "html": "<div class=\"diagram-upload\"><div class=\"diagram-box\" data-rough>uploadFile()<br><small class=\"diagram-muted\">POST /_agent-native/file-upload</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col\"><div class=\"diagram-step\"><span class=\"diagram-pill accent\">1</span><div class=\"diagram-node\">User-registered<br><small class=\"diagram-muted\">registerFileUploadProvider() — S3, R2, GCS…</small></div></div><div class=\"diagram-step\"><span class=\"diagram-pill\">2</span><div class=\"diagram-node\">Builder.io<br><small class=\"diagram-muted\">auto when connected — CDN-served</small></div></div><div class=\"diagram-step\"><span class=\"diagram-pill warn\">3</span><div class=\"diagram-node\">SQL fallback<br><small class=\"diagram-muted\">base64 in DB — dev only</small></div></div></div></div>",
23
+ "css": ".diagram-upload{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-upload .diagram-col{display:flex;flex-direction:column;gap:10px}.diagram-upload .diagram-step{display:flex;align-items:center;gap:8px}.diagram-upload .diagram-arrow{font-size:22px;line-height:1}"
24
+ }
25
+ ```
26
+
27
+ ```an-api title="The upload endpoint" method="POST" path="/_agent-native/file-upload"
28
+ {
29
+ "method": "POST",
30
+ "path": "/_agent-native/file-upload",
31
+ "summary": "Upload a file through the active provider and get back a public URL.",
32
+ "description": "Dispatches to the first configured provider in resolution order. Check the active provider at `GET /_agent-native/file-upload/status`.",
33
+ "request": { "contentType": "multipart/form-data" },
34
+ "responses": [
35
+ { "status": "200", "description": "{ url, id?, provider } — the public URL and which provider handled it." }
36
+ ]
37
+ }
38
+ ```
39
+
20
40
  ## Default: SQL Fallback {#sql-fallback}
21
41
 
22
42
  When no provider is configured, files are stored as base64 data in the SQL database via the resources system. This works out of the box for local development but is not recommended for production — large files bloat the database and there's no CDN.
@@ -20,6 +20,13 @@ chat, run, and (in dev) edit code. There are three frames, sharing one runtime:
20
20
  Your app code is identical regardless of which frame hosts it. The agent talks
21
21
  to your app through the same actions and application state in every case.
22
22
 
23
+ ```an-diagram title="Three frames, one runtime" summary="Your app and the agent panel are the same in every frame; only the wrapper around them changes."
24
+ {
25
+ "html": "<div class=\"diagram-frames\"><div class=\"diagram-card\" data-rough><span class=\"diagram-pill accent\">Embedded panel</span><small class=\"diagram-muted\">ships in every app · dev + prod</small></div><div class=\"diagram-card\" data-rough><span class=\"diagram-pill\">Local dev frame</span><small class=\"diagram-muted\">app in an iframe + panel + CLI terminal</small></div><div class=\"diagram-card\" data-rough><span class=\"diagram-pill\">Builder.io cloud frame</span><small class=\"diagram-muted\">hosted: collaboration · visual edit · parallel runs</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-box\" data-rough>Same runtime<br><small class=\"diagram-muted\">your app · actions · application state</small></div></div>",
26
+ "css": ".diagram-frames{display:flex;flex-direction:column;gap:10px;align-items:stretch}.diagram-frames .diagram-card{display:flex;flex-direction:column;gap:4px;padding:12px 16px}.diagram-frames .diagram-arrow{font-size:22px;line-height:1;align-self:center}"
27
+ }
28
+ ```
29
+
23
30
  ## Embedded agent panel {#embedded-agent}
24
31
 
25
32
  The embedded panel is the agent sidebar your app renders. It ships with
@@ -50,6 +57,13 @@ The panel runs in one of two tool modes:
50
57
  coding-tool list and shared UI contracts, see
51
58
  [Agent-Native Code UI](/docs/code-agents-ui).
52
59
 
60
+ ```an-diagram title="Code-request gating" summary="A code-typed message needs a code-capable frame. With one connected, the request routes there; without one, the panel explains code changes need Desktop or Builder."
61
+ {
62
+ "html": "<div class=\"diagram-gate\"><div class=\"diagram-node\" data-rough>message<br><small class=\"diagram-muted\">type: \\\"code\\\"</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\" data-rough>code-capable frame connected?</div><div class=\"diagram-col\"><div class=\"diagram-pill ok\">yes &rarr; route to frame, show code-agent indicator</div><div class=\"diagram-pill warn\">no &rarr; dialog: needs Desktop or Builder</div></div></div>",
63
+ "css": ".diagram-gate{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-gate .diagram-col{display:flex;flex-direction:column;gap:8px}.diagram-gate .diagram-arrow{font-size:22px;line-height:1}.diagram-gate .center{text-align:center}"
64
+ }
65
+ ```
66
+
53
67
  "Code mode" is the agent-capability toggle — distinct from environment dev mode
54
68
  (`NODE_ENV` / Vite). The client hook is `useCodeMode()`. (See
55
69
  [Compatibility notes](#compatibility) for the back-compat aliases.)
@@ -17,6 +17,13 @@ each a full-featured app you customize.
17
17
  Building from scratch? The only choice up front is whether you want a UI —
18
18
  everything after (defining actions, running the agent) is the same either way.
19
19
 
20
+ ```an-diagram title="The three-step on-ramp" summary="Create an app, add one action, run it. The action is then reachable from every surface."
21
+ {
22
+ "html": "<div class=\"diagram-flow\"><div class=\"diagram-card\"><span class=\"diagram-pill\">1</span><strong>Create</strong><small class=\"diagram-muted\">chat template or headless</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill\">2</span><strong>Add an action</strong><small class=\"diagram-muted\">one defineAction file</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill accent\">3</span><strong>Run it</strong><small class=\"diagram-muted\">CLI, agent, or browser</small></div></div>",
23
+ "css": ".diagram-flow{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-flow .diagram-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;min-width:140px}.diagram-flow .diagram-arrow{font-size:22px;line-height:1}"
24
+ }
25
+ ```
26
+
20
27
  ## 1. Create your app
21
28
 
22
29
  You'll need [Node.js 22+](https://nodejs.org) and [pnpm](https://pnpm.io).
@@ -49,22 +56,19 @@ From here on, the two are identical.
49
56
  An action is one operation your agent — and your UI — can call. Both scaffolds
50
57
  ship with this example:
51
58
 
52
- ```ts
53
- // actions/hello.ts
54
- import { defineAction } from "@agent-native/core/action";
55
- import { z } from "zod";
56
-
57
- export default defineAction({
58
- description: "Say hello from the local agent.",
59
- schema: z.object({
60
- name: z.string().default("world"),
61
- }),
62
- http: { method: "GET" },
63
- readOnly: true,
64
- run: async ({ name }) => {
65
- return { message: `Hello, ${name}!` };
66
- },
67
- });
59
+ ```an-annotated-code title="Your first action"
60
+ {
61
+ "filename": "actions/hello.ts",
62
+ "language": "ts",
63
+ "code": "import { defineAction } from \"@agent-native/core/action\";\nimport { z } from \"zod\";\n\nexport default defineAction({\n description: \"Say hello from the local agent.\",\n schema: z.object({\n name: z.string().default(\"world\"),\n }),\n http: { method: \"GET\" },\n readOnly: true,\n run: async ({ name }) => {\n return { message: `Hello, ${name}!` };\n },\n});",
64
+ "annotations": [
65
+ { "lines": "5", "label": "Tool description", "note": "The agent reads `description` to decide when to call this as a tool." },
66
+ { "lines": "6-8", "label": "Typed contract", "note": "One zod `schema` validates input from every surface — agent, UI, HTTP, MCP, and A2A." },
67
+ { "lines": "9", "label": "HTTP verb", "note": "Opt this action into an auto-mounted HTTP endpoint." },
68
+ { "lines": "10", "label": "Read-only", "note": "`readOnly` marks the action as safe to call without approval and cacheable for queries." },
69
+ { "lines": "11-13", "label": "One implementation", "note": "The `run` body is the single source of truth that every surface executes." }
70
+ ]
71
+ }
68
72
  ```
69
73
 
70
74
  Replace `hello` with the smallest real operation in your domain. You define it
@@ -94,6 +98,13 @@ pnpm dev
94
98
  That one action is now reachable from the chat UI, the CLI, HTTP, MCP, A2A,
95
99
  scheduled jobs, and webhooks. Define once, call from anywhere.
96
100
 
101
+ ```an-diagram title="One action, every surface" summary="A single defineAction file fans out to every consumer with no extra wiring."
102
+ {
103
+ "html": "<div class=\"diagram-fan\"><div class=\"diagram-box\" data-rough>defineAction</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-surfaces\"><span class=\"diagram-pill\">Chat UI</span><span class=\"diagram-pill\">CLI</span><span class=\"diagram-pill\">HTTP</span><span class=\"diagram-pill\">MCP</span><span class=\"diagram-pill\">A2A</span><span class=\"diagram-pill\">Scheduled jobs</span><span class=\"diagram-pill\">Webhooks</span></div></div>",
104
+ "css": ".diagram-fan{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-fan .diagram-surfaces{display:flex;flex-wrap:wrap;gap:8px;max-width:420px}.diagram-fan .diagram-arrow{font-size:22px;line-height:1}"
105
+ }
106
+ ```
107
+
97
108
  ## State is built in
98
109
 
99
110
  Headless doesn't mean stateless. Actions, sessions, application state, threads,
@@ -101,6 +112,13 @@ run history, and credentials all live in SQL. Locally that's SQLite at
101
112
  `data/app.db`; in production you set `DATABASE_URL`. See
102
113
  [Deployment](/docs/deployment).
103
114
 
115
+ ```an-callout
116
+ {
117
+ "tone": "info",
118
+ "body": "**Headless is still a real app.** The app-agent loop persists sessions, threads, runs, settings, and credentials in SQL — it is not a stateless prompt. You can add a UI later without touching your actions or state."
119
+ }
120
+ ```
121
+
104
122
  ## Customize the UI
105
123
 
106
124
  If you started from the Chat template, the UI is yours to edit. The chat itself
@@ -23,6 +23,13 @@ beneath `runAgentLoop`. A harness is not an `AgentEngine` provider — it runs i
23
23
  own loop end to end, so Agent-Native drives it as a session, not as a single
24
24
  model call.
25
25
 
26
+ ```an-diagram title="A harness owns its loop; Agent-Native drives the session" summary="The AgentHarness substrate creates/resumes the native session, streams its events into the normal transcript, and persists resumeState in SQL between turns."
27
+ {
28
+ "html": "<div class=\"diagram-harness\"><div class=\"diagram-box\" data-rough><strong>AgentHarness substrate</strong><small class=\"diagram-muted\">@agent-native/core/agent/harness</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\" data-rough><strong>Native harness loop</strong><small class=\"diagram-muted\">Claude Code · Codex · Pi — own tools, sandbox, compaction</small></div><div class=\"diagram-col\"><div class=\"diagram-pill accent\">events &rarr; transcript</div><div class=\"diagram-pill ok\">resumeState &rarr; SQL session</div></div></div>",
29
+ "css": ".diagram-harness{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-harness .diagram-col{display:flex;flex-direction:column;gap:8px}.diagram-harness .diagram-arrow{font-size:22px;line-height:1}.diagram-harness .center{display:flex;flex-direction:column;align-items:center;gap:4px}"
30
+ }
31
+ ```
32
+
26
33
  ## Which coding doc do I want? {#which-doc}
27
34
 
28
35
  | You want to… | Use |
@@ -147,6 +154,13 @@ so a thread can survive across turns, processes, and deploys. The `resumeState`
147
154
  is **opaque** — Agent-Native stores it and hands it back, but never inspects or
148
155
  interprets it.
149
156
 
157
+ ```an-diagram title="Resume across turns, processes, and deploys" summary="Each turn detaches an opaque resumeState into SQL; the next turn feeds it back into createSession instead of replaying chat history."
158
+ {
159
+ "html": "<div class=\"diagram-resume\"><div class=\"diagram-node\" data-rough>Turn N<br><small class=\"diagram-muted\">streamTurn</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>detach &rarr; resumeState<br><small class=\"diagram-muted\">opaque · SQL harness session</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\" data-rough>Turn N+1<br><small class=\"diagram-muted\">createSession.resumeState</small></div></div>",
160
+ "css": ".diagram-resume{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-resume .diagram-arrow{font-size:22px;line-height:1}"
161
+ }
162
+ ```
163
+
150
164
  ```ts
151
165
  import {
152
166
  getLatestAgentHarnessSessionForThread,