@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
@@ -14,23 +14,16 @@ Most actions should just run. A few — sending an email, charging a card, delet
14
14
 
15
15
  Set `needsApproval` on a `defineAction`. It accepts a boolean or a predicate:
16
16
 
17
- ```ts
18
- // actions/send-email.ts
19
- export default defineAction({
20
- description: "Send an email via Gmail.",
21
- schema: z.object({
22
- to: z.string(),
23
- subject: z.string(),
24
- body: z.string(),
25
- }),
26
- // Sending is outward-facing and hard to undo, so the agent can never send
27
- // without a human approving the specific call. Drafting/queueing is
28
- // unaffected — only the real send is gated.
29
- needsApproval: true,
30
- run: async (args) => {
31
- /* ...actually send... */
32
- },
33
- });
17
+ ```an-annotated-code title="Gating the one consequential action"
18
+ {
19
+ "filename": "actions/send-email.ts",
20
+ "language": "ts",
21
+ "code": "export default defineAction({\n description: \"Send an email via Gmail.\",\n schema: z.object({\n to: z.string(),\n subject: z.string(),\n body: z.string(),\n }),\n // Sending is outward-facing and hard to undo, so the agent can never send\n // without a human approving the specific call. Drafting/queueing is\n // unaffected — only the real send is gated.\n needsApproval: true,\n run: async (args) => {\n /* ...actually send... */\n },\n});",
22
+ "annotations": [
23
+ { "lines": "10", "label": "The whole gate", "note": "One flag. With it truthy and the call unapproved, the loop stops before `run` — the model never reaches the side effect on its own." },
24
+ { "lines": "11-13", "label": "run() is untouched", "note": "The handler stays the same. Approval is enforced by the loop around it, not by anything inside `run`." }
25
+ ]
26
+ }
34
27
  ```
35
28
 
36
29
  - **`needsApproval: true`** — always require approval.
@@ -77,19 +70,11 @@ On `approval_required`, the chat UI renders an **Approve / Deny** affordance on
77
70
 
78
71
  ## End-to-end {#flow}
79
72
 
80
- ```text
81
- agent calls send-email
82
-
83
-
84
- needsApproval truthy, call not yet approved
85
- │ loop emits tool_start + approval_required { tool, input, approvalKey }
86
-
87
- turn pauses — run() did NOT execute
88
-
89
- human clicks Approve in the chat UI
90
- │ client re-issues the turn with approvedToolCalls: [approvalKey]
91
-
92
- gate sees the key → run() executes → email sends
73
+ ```an-diagram title="The approval interrupt" summary="A gated call pauses the turn before run() fires. Approval re-issues the turn carrying the call's key; only then does the side effect happen."
74
+ {
75
+ "html": "<div class=\"diagram-approve\"><div class=\"diagram-box\" data-rough>Agent calls send-email</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-panel warn\" data-rough><strong>Gate truthy, call not yet approved</strong><small class=\"diagram-muted\">loop emits tool_start + approval_required { tool, input, approvalKey }</small><span class=\"diagram-pill warn\">turn pauses &mdash; run() did NOT execute</span></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-box\" data-rough>Human clicks Approve in chat<br><small class=\"diagram-muted\">client re-issues the turn with approvedToolCalls: [approvalKey]</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-panel ok\" data-rough><span class=\"diagram-pill ok\">Gate sees the key &rarr; run() executes &rarr; email sends</span></div></div>",
76
+ "css": ".diagram-approve{display:flex;flex-direction:column;align-items:center;gap:8px}.diagram-approve .diagram-panel{display:flex;flex-direction:column;gap:6px;align-items:center;padding:12px 16px;text-align:center}.diagram-approve .diagram-arrow{font-size:22px;line-height:1}"
77
+ }
93
78
  ```
94
79
 
95
80
  The canonical (and intentionally rare) use of this gate in the framework is the Mail template's `send-email` action, which sets `needsApproval: true` so the agent can draft and queue freely but can never actually send a message without a human approving the specific send.
@@ -17,6 +17,13 @@ Every agent-native app is three things working together:
17
17
  >
18
18
  > **Computer** — Database, browser, code execution. Agents work directly with SQL and built-in tools; MCP servers are optional add-ons, not the foundation.
19
19
 
20
+ ```an-diagram title="Agent, application, and computer" summary="Three layers working together over one shared SQL store. The agent and the application both read and write the same data."
21
+ {
22
+ "html": "<div class=\"diagram-arch\"><div class=\"diagram-row\"><div class=\"diagram-card\"><span class=\"diagram-pill accent\">Agent</span><small class=\"diagram-muted\">reads + writes data, runs actions, modifies code</small></div><div class=\"diagram-card\"><span class=\"diagram-pill\">Application</span><small class=\"diagram-muted\">action-only, chat, control plane, or full React UI</small></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;&nbsp;&uarr;</div><div class=\"diagram-box\" data-rough>Computer<br><small class=\"diagram-muted\">SQL database · browser · code execution</small></div></div>",
23
+ "css": ".diagram-arch{display:flex;flex-direction:column;align-items:center;gap:10px}.diagram-arch .diagram-row{display:flex;gap:12px;flex-wrap:wrap;justify-content:center}.diagram-arch .diagram-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;min-width:220px}.diagram-arch .diagram-arrow{font-size:20px;line-height:1}.diagram-arch .diagram-box{text-align:center;padding:12px 18px}"
24
+ }
25
+ ```
26
+
20
27
  Headless apps can run the same production app-agent loop from the folder with `pnpm agent`, while UI apps mount the embedded agent panel and run locally with `pnpm dev`. In the cloud, Builder.io provides a managed frame — the environment that hosts the agent next to your app — with collaboration, visual editing, and managed infrastructure for teams.
21
28
 
22
29
  Six rules govern the architecture:
@@ -52,6 +59,29 @@ Core SQL stores are auto-created and available in every template:
52
59
  - `oauth_tokens` — OAuth credentials
53
60
  - `sessions` — auth sessions
54
61
 
62
+ ```an-schema title="Core SQL stores" summary="Auto-created in every template — the agent and UI both read and write these."
63
+ {
64
+ "entities": [
65
+ { "id": "application_state", "name": "application_state", "note": "Ephemeral UI state the agent reads for context", "fields": [
66
+ { "name": "key", "type": "text", "pk": true, "note": "e.g. 'navigation'" },
67
+ { "name": "value", "type": "json", "note": "view, selection, drafts" }
68
+ ] },
69
+ { "id": "settings", "name": "settings", "note": "Persistent key-value config", "fields": [
70
+ { "name": "key", "type": "text", "pk": true },
71
+ { "name": "value", "type": "json" }
72
+ ] },
73
+ { "id": "oauth_tokens", "name": "oauth_tokens", "note": "OAuth credentials", "fields": [
74
+ { "name": "provider", "type": "text", "pk": true },
75
+ { "name": "token", "type": "text" }
76
+ ] },
77
+ { "id": "sessions", "name": "sessions", "note": "Auth sessions", "fields": [
78
+ { "name": "id", "type": "text", "pk": true },
79
+ { "name": "userId", "type": "text" }
80
+ ] }
81
+ ]
82
+ }
83
+ ```
84
+
55
85
  ```ts
56
86
  // Drizzle schema for domain data
57
87
  import { table, text, integer } from "@agent-native/core/db/schema";
@@ -147,6 +177,13 @@ The flow is:
147
177
  4. `useActionQuery` hooks and source-versioned `useQuery` hooks refetch
148
178
  5. Components render the new data without a page reload
149
179
 
180
+ ```an-diagram title="Live sync flow" summary="An agent write becomes a UI render with no manual refresh — SSE first, polling as the universal fallback."
181
+ {
182
+ "html": "<div class=\"diagram-sync\"><div class=\"diagram-node\">Agent action<br><small class=\"diagram-muted\">writes to DB</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\">Change event<br><small class=\"diagram-muted\">source: action / settings</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\"><span class=\"diagram-pill accent\">useDbSync</span><small class=\"diagram-muted\">SSE &middot; poll fallback</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\">Query refetch<br><small class=\"diagram-muted\">render, no reload</small></div></div>",
183
+ "css": ".diagram-sync{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.diagram-sync .diagram-arrow{font-size:22px;line-height:1}.diagram-sync .center{display:flex;flex-direction:column;align-items:center;gap:4px;padding:10px 14px}"
184
+ }
185
+ ```
186
+
150
187
  This works in all deployment environments — including serverless and edge — because it uses the database, not in-memory state or file system watchers.
151
188
 
152
189
  ## Frames {#frames}
@@ -46,29 +46,36 @@ The UI and agent actions should stay the same shape in both modes. A Content
46
46
  editor still edits documents; the difference is whether those documents resolve
47
47
  to SQL rows or local files.
48
48
 
49
+ ```an-diagram title="Same actions, two sources of truth" summary="The UI and agent call identical actions in both modes. The action layer decides whether each call resolves to SQL rows or repo files."
50
+ {
51
+ "html": "<div class=\"diagram-mode\"><div class=\"diagram-col entry\"><div class=\"diagram-node\">Content UI</div><div class=\"diagram-node\">Agent + actions<br><small class=\"diagram-muted\">list/get/update-document</small></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-row resolve\"><div class=\"diagram-panel\" data-rough><span class=\"diagram-pill accent\">Database mode</span><small class=\"diagram-muted\">SQL rows via Drizzle</small><small class=\"diagram-muted\">hosted · sharing · comments · history</small></div><div class=\"diagram-panel\" data-rough><span class=\"diagram-pill ok\">Local File Mode</span><small class=\"diagram-muted\">repo files via agent-native.json</small><small class=\"diagram-muted\">Git review · coding-agent edits</small></div></div></div>",
52
+ "css": ".diagram-mode{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-mode .diagram-col{display:flex;flex-direction:column;gap:10px}.diagram-mode .diagram-arrow{font-size:22px;line-height:1}.diagram-mode .resolve{display:flex;gap:12px;flex-wrap:wrap}.diagram-mode .diagram-panel{display:flex;flex-direction:column;gap:4px;padding:12px 14px}"
53
+ }
54
+ ```
55
+
49
56
  ## Example Repo
50
57
 
51
58
  A Content workspace can be as small as this:
52
59
 
53
- ```text
54
- my-content-repo/
55
- agent-native.json
56
- docs/
57
- getting-started.mdx
58
- guides/
59
- custom-components.mdx
60
- blog/
61
- launch-post.mdx
62
- resources/
63
- messaging/
64
- positioning.md
65
- components/
66
- FrameworkTabs.tsx
67
- Callout.tsx
68
- extensions/
69
- doc-status/
70
- extension.json
71
- index.html
60
+ ```an-file-tree title="A Content workspace repo"
61
+ {
62
+ "entries": [
63
+ { "path": "agent-native.json", "note": "declares which folders are content roots and their kinds" },
64
+ { "path": "docs/", "note": "content root — shows in the sidebar as pages" },
65
+ { "path": "docs/getting-started.mdx" },
66
+ { "path": "docs/guides/custom-components.mdx" },
67
+ { "path": "blog/", "note": "content root" },
68
+ { "path": "blog/launch-post.mdx" },
69
+ { "path": "resources/", "note": "content root" },
70
+ { "path": "resources/messaging/positioning.md" },
71
+ { "path": "components/", "note": "NOT a content root — preview component library MDX can import" },
72
+ { "path": "components/FrameworkTabs.tsx" },
73
+ { "path": "components/Callout.tsx" },
74
+ { "path": "extensions/", "note": "NOT a content root — local extension library (sandboxed widgets)" },
75
+ { "path": "extensions/doc-status/extension.json" },
76
+ { "path": "extensions/doc-status/index.html" }
77
+ ]
78
+ }
72
79
  ```
73
80
 
74
81
  In Local File Mode, the Content sidebar shows the `docs/`, `blog/`, and
@@ -30,6 +30,14 @@ On rare occasions the right target is a focused app route that renders one share
30
30
 
31
31
  Do not hand-write one-off plain HTML MCP Apps for product UI; if the action needs a custom surface, add or reuse a real app route/component first and embed that route.
32
32
 
33
+ ```an-diagram title="MCP App embed round-trip" summary="The action's link target is also the embed target. Capable hosts load the same signed app route inline; everyone else falls back to the deep link."
34
+ {
35
+ "html": "<div class=\"diagram-embed\"><div class=\"diagram-card\" data-rough><strong>Action</strong><small class=\"diagram-muted\">`link` target = MCP App embed target</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\" data-rough><strong>embedApp()</strong><span class=\"diagram-pill accent\">create_embed_session</span><small class=\"diagram-muted\">mints short-lived embed session</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\" data-rough><strong>/_agent-native/embed/start</strong><small class=\"diagram-muted\">exchanges one-time SQL ticket</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\" data-rough><strong>Signed app route</strong><span class=\"diagram-pill ok\">real React route</span><small class=\"diagram-muted\">short-lived browser session</small></div><div class=\"diagram-fallback\"><span class=\"diagram-pill warn\">no MCP Apps support</span><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-box\" data-rough>&quot;Open in … &rarr;&quot; deep link</div></div></div>",
36
+ "css": ".diagram-embed{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-embed .diagram-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;min-width:140px}.diagram-embed .diagram-arrow{font-size:22px;line-height:1}.diagram-embed .diagram-fallback{display:flex;flex-direction:column;align-items:center;gap:6px;margin-inline-start:8px}"
37
+ }
38
+
39
+ ```
40
+
33
41
  ```ts
34
42
  import { embedApp } from "@agent-native/core";
35
43
 
@@ -46,6 +54,19 @@ export default defineAction({
46
54
  });
47
55
  ```
48
56
 
57
+ ```an-annotated-code title="The mcpApp resource config"
58
+ {
59
+ "filename": "actions/review-draft.ts",
60
+ "language": "ts",
61
+ "code": "import { embedApp } from \"@agent-native/core\";\n\nexport default defineAction({\n // ...description, schema, run, link...\n mcpApp: {\n resource: embedApp({\n title: \"Review draft\",\n description: \"Open the generated draft in the real Mail compose UI.\",\n iframeTitle: \"Agent-Native Mail\",\n openLabel: \"Open in Mail\",\n }),\n },\n});",
62
+ "annotations": [
63
+ { "lines": "6", "label": "Progressive enhancement", "note": "`mcpApp.resource` advertises an inline UI for hosts that support the MCP Apps extension. Keep the action's `link` builder too — CLI-only and older hosts ignore the UI metadata and still need the deep link." },
64
+ { "lines": "7", "label": "Embed = the link target", "note": "`embedApp()` uses the action's `link` as its launch target: it calls `create_embed_session`, exchanges a one-time SQL ticket at `/_agent-native/embed/start`, and navigates the MCP App frame to the same signed app route." },
65
+ { "lines": "11", "label": "Universal fallback label", "note": "`openLabel` is the visible `\"Open in … →\"` text used as the deep-link escape hatch when a host does not render the inline iframe." }
66
+ ]
67
+ }
68
+ ```
69
+
49
70
  The MCP server advertises extension `io.modelcontextprotocol/ui`, adds `_meta.ui.resourceUri` plus `_meta["ui/resourceUri"]` to `tools/list`, and also emits ChatGPT Apps SDK compatibility metadata (`openai/outputTemplate`, widget CSP/description/accessibility). It exposes the HTML through `resources/list`, `resources/templates/list`, and `resources/read` using MIME `text/html;profile=mcp-app`. The stdio proxy forwards those resource handlers from the live app, so desktop and CLI clients see the same resources as HTTP clients.
50
71
 
51
72
  Keep the existing `link` builder even when adding `mcpApp`. CLI-only clients, older hosts, and any host that does not render MCP Apps will ignore the UI metadata and still need the `"Open in … →"` link. `embedApp()` uses that link as its launch target, calls the app-only `create_embed_session` helper, exchanges a one-time SQL ticket at `/_agent-native/embed/start`, and navigates the MCP App frame to the target route with a short-lived browser session plus a bearer fallback for same-origin fetches. `open_app({ app, path, embed: true })` is the generic escape hatch for routes such as full dashboards, filtered inboxes, calendar draft views, analyses, and extension pages, and should be used liberally when the full app is the clearest review/edit surface.
@@ -56,7 +77,17 @@ Inside those `embedApp()` routes, `sendToAgentChat()` is embed-aware. Auto-submi
56
77
 
57
78
  ## First-class MCP App bridge {#mcp-app-bridge}
58
79
 
59
- MCP App embeds are route embeds, not separate mini-products. `embedApp()` starts from the action's `link` target, creates a short-lived embed session, and launches that signed app route. Standard MCP Apps hosts can navigate the MCP App frame itself when the host can hydrate the route directly. Claude web uses a single-frame transplant path: the resource document fetches the signed app HTML and hydrates it inside Claude's MCP App iframe because Claude does not reliably allow app-owned child iframes or external frame navigation. ChatGPT web gets a controlled route iframe because its Apps bridge gives us stable `window.openai` host APIs and bounded height control. All paths point at the same signed app route and render the normal route and React components. Design embedded routes so a reload with the same signed URL reconstructs the same view.
80
+ MCP App embeds are route embeds, not separate mini-products. `embedApp()` starts from the action's `link` target, creates a short-lived embed session, and launches that signed app route. Standard MCP Apps hosts can navigate the MCP App frame itself when the host can hydrate the route directly.
81
+
82
+ ```an-diagram title="Two host bridge paths, one signed route" summary="Claude transplants the hydrated route and uses the direct ui/_bridge; ChatGPT gets a controlled iframe via window.openai and relays host actions over postMessage. Both point at the same signed app route."
83
+ {
84
+ "html": "<div class=\"diagram-bridge\"><div class=\"diagram-col\"><div class=\"diagram-card\" data-rough><strong>Claude web</strong><span class=\"diagram-pill accent\">single-frame transplant</span><small class=\"diagram-muted\">hydrates signed app HTML in Claude's iframe, then direct`ui/_` host bridge</small></div><div class=\"diagram-card\" data-rough><strong>ChatGPT web</strong><span class=\"diagram-pill accent\">controlled route iframe</span><small class=\"diagram-muted\">`window.openai`host APIs ·`agentNative.mcpHost.*` postMessage relay</small></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>Same signed app route<br><small class=\"diagram-muted\">normal route + React components</small></div></div>",
85
+ "css": ".diagram-bridge{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-bridge .diagram-col{display:flex;flex-direction:column;gap:12px}.diagram-bridge .diagram-card{display:flex;flex-direction:column;gap:6px;padding:14px 16px;max-width:300px}.diagram-bridge .diagram-arrow{font-size:22px;line-height:1}.diagram-bridge .diagram-box{padding:16px 18px;text-align:center}"
86
+ }
87
+
88
+ ```
89
+
90
+ Claude web uses a single-frame transplant path: the resource document fetches the signed app HTML and hydrates it inside Claude's MCP App iframe because Claude does not reliably allow app-owned child iframes or external frame navigation. ChatGPT web gets a controlled route iframe because its Apps bridge gives us stable `window.openai` host APIs and bounded height control. All paths point at the same signed app route and render the normal route and React components. Design embedded routes so a reload with the same signed URL reconstructs the same view.
60
91
 
61
92
  For same-app `open_app({ embed: true })`, the framework mints the embed-start ticket during the original tool call and stores the signed start URL in hidden tool metadata. Custom actions can return `embedStartUrl` for the same fast path; the MCP layer strips that ticket-bearing URL from model-visible `structuredContent` and normal open-link metadata. When no embed start URL is present, the resource falls back to the app-only `create_embed_session` helper. This keeps production hosts that restrict iframe-initiated tool calls on the direct route without leaking one-time app session URLs into the transcript. If a user reopens an old chat after a one-time start ticket has expired, the start route returns a small refresh page and posts `agentNative.embedSessionExpired` to the wrapper; `embedApp()` clears the stale start URL and mints a fresh ticket through `create_embed_session` when it still has the original app route.
62
93
 
@@ -110,3 +141,7 @@ Test MCP Apps with the lightweight fixtures around `embedApp()` and `McpAppRende
110
141
  - [External Agents](/docs/external-agents) — connecting Claude, ChatGPT, Codex, and Cursor to hosted apps; MCP Apps compatibility matrix; catalog tiers; deep links.
111
142
  - [MCP Protocol](/docs/mcp-protocol) — the auto-mounted MCP server, auth, tools, and `ask-agent`.
112
143
  - [Actions](/docs/actions) — `defineAction`, the `link` builder, `publicAgent`.
144
+
145
+ ```
146
+
147
+ ```
@@ -18,6 +18,17 @@ With one config file, every agent-native app in your workspace gains access to t
18
18
 
19
19
  You can also [connect remote (HTTP) MCP servers at runtime](#remote-via-ui) — individual users or whole organizations — without editing a config file.
20
20
 
21
+ Every source resolves into one runtime **MCP manager**, and every tool it learns lands in the agent's tool registry under a collision-proof `mcp__<server-id>__<tool>` prefix — searchable by intent through `tool-search`.
22
+
23
+ ```an-diagram title="Client direction: many sources, one tool registry" summary="Config files, env, and runtime UI all merge into the MCP manager; its tools appear prefixed and tool-searchable alongside your app's actions. This is the mirror of the server direction."
24
+ {
25
+ "html": "<div class=\"mcp-merge\"><div class=\"diagram-col sources\"><div class=\"diagram-box\" data-rough>Workspace <code>mcp.config.json</code><br><small class=\"diagram-muted\">shared across apps</small></div><div class=\"diagram-box\" data-rough>App-root <code>mcp.config.json</code><br><small class=\"diagram-muted\">per-app override</small></div><div class=\"diagram-box\" data-rough><code>MCP_SERVERS</code> env<br><small class=\"diagram-muted\">CI / production</small></div><div class=\"diagram-box\" data-rough>Remote via settings UI<br><small class=\"diagram-muted\">personal &amp; org scope</small></div></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 manager</span><small class=\"diagram-muted\">merge &middot; hot-reload</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col out\"><div class=\"diagram-node\">Agent tool registry<br><small class=\"diagram-muted\"><code>mcp__&lt;server-id&gt;__&lt;tool&gt;</code></small></div><div class=\"diagram-node\"><code>tool-search</code><br><small class=\"diagram-muted\">discover by intent</small></div></div></div>",
26
+ "css": ".mcp-merge{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.mcp-merge .diagram-col{display:flex;flex-direction:column;gap:8px}.mcp-merge .center{display:flex;flex-direction:column;align-items:center;gap:4px}.mcp-merge .diagram-arrow{font-size:22px;line-height:1}.mcp-merge code{font-size:.85em}"
27
+ }
28
+ ```
29
+
30
+ > The opposite direction — making _your_ app an MCP server that other hosts consume — lives in [MCP Protocol](/docs/mcp-protocol) and [External Agents](/docs/external-agents).
31
+
21
32
  ## Built-in browser and computer-use capabilities {#built-in-capabilities}
22
33
 
23
34
  Agent-native includes local-development toggles for common stdio MCP servers.
@@ -89,6 +100,21 @@ Create `mcp.config.json` at your workspace root (or at an individual app root
89
100
  }
90
101
  ```
91
102
 
103
+ The shape is small: a `servers` map keyed by server id, where each entry is either a stdio launcher (`command` + `args` + optional `env`) or a remote `{ "type": "http", "url", "headers" }` entry.
104
+
105
+ ```an-annotated-code title="mcp.config.json, annotated"
106
+ {
107
+ "filename": "mcp.config.json",
108
+ "language": "jsonc",
109
+ "code": "{\n \"$schema\": \"https://agent-native.com/schema/mcp.config.json\",\n \"servers\": {\n \"claude-in-chrome\": {\n \"command\": \"claude-in-chrome-mcp\",\n \"args\": [],\n \"env\": { \"LOG_LEVEL\": \"info\" }\n },\n \"filesystem\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@modelcontextprotocol/server-filesystem@latest\", \"/Users/me/projects\"]\n }\n }\n}",
110
+ "annotations": [
111
+ { "lines": "3", "label": "Server id", "note": "The key becomes the tool prefix: this server's tools surface as `mcp__claude-in-chrome__*` in the agent's registry, so they can't collide with your template's actions." },
112
+ { "lines": "4-6", "label": "stdio launcher", "note": "`command` + `args` spawn a local binary. Stdio servers are intended for **local development** — they are a no-op in edge runtimes." },
113
+ { "lines": "6", "label": "Process env", "note": "Optional `env` is passed to the spawned process. Keep secrets out of committed config; prefer `MCP_SERVERS` or the settings UI for tokens." }
114
+ ]
115
+ }
116
+ ```
117
+
92
118
  On next app start you'll see:
93
119
 
94
120
  ```
@@ -176,6 +202,13 @@ If your workspace runs multiple agent-native apps (e.g. dispatch + mail + clips)
176
202
 
177
203
  Dispatch is the conventional hub — it already coordinates across apps.
178
204
 
205
+ ```an-diagram title="Hub model: one app serves org-scope MCP servers" summary="Dispatch holds the org-scope MCP servers; consumer apps pull and merge them as mcp__hub_<orgId>_<name>__*. Only org-scope rows are shared — personal credentials stay put."
206
+ {
207
+ "html": "<div class=\"mcp-hub\"><div class=\"diagram-panel center\" data-rough><span class=\"diagram-pill accent\">Dispatch hub</span><small class=\"diagram-muted\">org-scope MCP servers</small><small class=\"diagram-muted\"><code>GET /mcp/hub/servers</code></small></div><div class=\"diagram-col arrows\"><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div></div><div class=\"diagram-col consumers\"><div class=\"diagram-box\" data-rough>Mail<br><small class=\"diagram-muted\"><code>mcp__hub_&lt;orgId&gt;_&lt;name&gt;__*</code></small></div><div class=\"diagram-box\" data-rough>Clips<br><small class=\"diagram-muted\">pull + merge each ~60s</small></div></div></div><p class=\"diagram-muted note\">Bearer-gated by <code>AGENT_NATIVE_MCP_HUB_TOKEN</code>. Personal (user-scope) servers are never re-exposed.</p>",
208
+ "css": ".mcp-hub{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.mcp-hub .center{display:flex;flex-direction:column;align-items:center;gap:4px}.mcp-hub .diagram-col{display:flex;flex-direction:column;gap:10px}.mcp-hub .arrows .diagram-arrow{font-size:22px;line-height:1}.mcp-hub .note{margin:8px 0 0;font-size:.85em}.mcp-hub code{font-size:.85em}"
209
+ }
210
+ ```
211
+
179
212
  For new workspace setups, prefer **Dispatch workspace MCP resources** when you
180
213
  want the same All-app vs selected-app grant model used by workspace skills,
181
214
  instructions, and reference resources. Add a workspace resource with:
@@ -248,6 +281,22 @@ Local UI adds in each app hot-reload via `McpClientManager.reconfigure()` — no
248
281
 
249
282
  Every app exposes `GET /_agent-native/mcp/status` for tooling and onboarding:
250
283
 
284
+ ```an-api
285
+ {
286
+ "method": "GET",
287
+ "path": "/_agent-native/mcp/status",
288
+ "summary": "MCP client status for tooling and onboarding",
289
+ "description": "Reports which configured servers connected, the total live tool count, the merged prefixed tool list, and any per-server connection errors. Use it to build \"detected — your agent can now drive X\" hints or to debug connection problems.",
290
+ "responses": [
291
+ {
292
+ "status": "200",
293
+ "description": "Configured vs connected servers, tool inventory, and per-server errors.",
294
+ "example": "{\n \"configuredServers\": [\"claude-in-chrome\", \"playwright\"],\n \"connectedServers\": [\"claude-in-chrome\", \"playwright\"],\n \"totalTools\": 21,\n \"tools\": [\n {\n \"source\": \"claude-in-chrome\",\n \"name\": \"mcp__claude-in-chrome__navigate\",\n \"description\": \"Navigate the browser to a URL\"\n }\n ],\n \"errors\": {}\n}"
295
+ }
296
+ ]
297
+ }
298
+ ```
299
+
251
300
  ```json
252
301
  {
253
302
  "configuredServers": ["claude-in-chrome", "playwright"],
@@ -30,6 +30,13 @@ Key concepts:
30
30
  - **Standard remote MCP OAuth** — OAuth 2.1 discovery, dynamic client registration, authorization-code + PKCE, refresh-token rotation
31
31
  - **Bearer auth fallback** — uses `ACCESS_TOKEN`, `ACCESS_TOKENS`, or connect-minted JWTs for clients that cannot run OAuth
32
32
 
33
+ ```an-diagram title="Your app as an MCP server" summary="External hosts connect over Streamable HTTP. Each action is one tool; ask-agent delegates to the full agent loop."
34
+ {
35
+ "html": "<div class=\"diagram-mcp\"><div class=\"diagram-col\"><div class=\"diagram-node\">Claude</div><div class=\"diagram-node\">ChatGPT</div><div class=\"diagram-node\">Cursor · Codex</div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel\"><span class=\"diagram-pill accent\">POST /_agent-native/mcp</span><small class=\"diagram-muted\">Streamable HTTP</small><small class=\"diagram-muted\">initialize &rarr; tools/list &rarr; tools/call</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col\"><div class=\"diagram-box\" data-rough>each action<br><small class=\"diagram-muted\">= one tool</small></div><div class=\"diagram-box\" data-rough>ask-agent<br><small class=\"diagram-muted\">&rarr; full agent loop</small></div></div></div>",
36
+ "css": ".diagram-mcp{display:flex;align-items:center;gap:16px;flex-wrap:wrap}.diagram-mcp .diagram-col{display:flex;flex-direction:column;gap:8px}.diagram-mcp .diagram-panel{display:flex;flex-direction:column;align-items:center;gap:4px}.diagram-mcp .diagram-arrow{font-size:20px;line-height:1}"
37
+ }
38
+ ```
39
+
33
40
  ## MCP vs A2A {#mcp-vs-a2a}
34
41
 
35
42
  Both protocols are auto-mounted. Use whichever fits your use case:
@@ -85,6 +92,28 @@ POST https://your-app.example.com/_agent-native/mcp
85
92
 
86
93
  The server supports the standard MCP handshake: `initialize` → `initialized` → `tools/list` → `tools/call`.
87
94
 
95
+ ```an-api title="MCP endpoint" summary="The auto-mounted Streamable HTTP endpoint every agent-native app exposes."
96
+ {
97
+ "method": "POST",
98
+ "path": "/_agent-native/mcp",
99
+ "summary": "MCP Streamable HTTP endpoint",
100
+ "description": "Auto-mounted on every app. Speaks the standard MCP handshake (`initialize` → `initialized` → `tools/list` → `tools/call`) plus `resources/list`, `resources/templates/list`, and `resources/read` when an action declares `mcpApp`. Each action maps to one tool; `ask-agent` delegates to the full agent loop.",
101
+ "auth": "Standard remote MCP OAuth (Bearer access token), connect-minted JWT, or static ACCESS_TOKEN/ACCESS_TOKENS",
102
+ "params": [
103
+ { "name": "Authorization", "in": "header", "type": "string", "required": false, "description": "Bearer access token. Required except for loopback local-dev probes." },
104
+ { "name": "method", "in": "body", "type": "string", "required": true, "description": "MCP method, e.g. initialize, tools/list, tools/call." }
105
+ ],
106
+ "request": {
107
+ "contentType": "application/json",
108
+ "example": "{\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"ask-agent\",\n \"arguments\": { \"message\": \"Summarize Q3 signups by source\" }\n }\n}"
109
+ },
110
+ "responses": [
111
+ { "status": "200", "description": "MCP result (POST + SSE)." },
112
+ { "status": "401", "description": "Unauthenticated — responds with a WWW-Authenticate header pointing at OAuth discovery." }
113
+ ]
114
+ }
115
+ ```
116
+
88
117
  If an action declares `mcpApp`, the server also advertises the official MCP Apps extension (`io.modelcontextprotocol/ui`) and supports `resources/list`, `resources/templates/list`, and `resources/read` for the app resource. Hosts that render MCP Apps can show the UI inline; hosts that do not can still call the tool and use the deep-link fallback. Product UIs should use `embedApp()` so the inline surface is the real React app route, or a focused route that renders a shared React component such as an Analytics chart, not a separate plain HTML implementation. The server emits both standard MCP Apps metadata and ChatGPT Apps SDK compatibility metadata so app-capable hosts can find the same `ui://` resource. The current official extension matrix includes Claude, Claude Desktop, VS Code GitHub Copilot, Goose, Postman, MCPJam, ChatGPT, and Cursor; host support varies by version and plan, so use the [External Agents MCP Apps notes](/docs/external-agents#mcp-apps-compatibility) for the user-facing guidance.
89
118
 
90
119
  ### MCP App embed bridge {#mcp-app-embed-bridge}
@@ -165,6 +194,13 @@ Discovery endpoints:
165
194
  | `/_agent-native/mcp/oauth/authorize` | Browser authorization + consent |
166
195
  | `/_agent-native/mcp/oauth/token` | Authorization-code and refresh-token grants |
167
196
 
197
+ ```an-diagram title="OAuth discovery flow" summary="A 401 kicks off discovery, registration, and a PKCE authorize → token exchange. The Bearer token is audience-bound and scoped."
198
+ {
199
+ "html": "<div class=\"diagram-oauth\"><div class=\"diagram-box\" data-rough>first request<br><small class=\"diagram-muted\">no token</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-pill warn\">401 · WWW-Authenticate</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel\"><span class=\"diagram-pill\">/.well-known/oauth-protected-resource</span><span class=\"diagram-pill\">/.well-known/oauth-authorization-server</span><small class=\"diagram-muted\">discover</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col\"><div class=\"diagram-pill\">register</div><div class=\"diagram-pill\">authorize (PKCE)</div><div class=\"diagram-pill\">token</div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>Bearer access token<br><small class=\"diagram-muted\">audience-bound · mcp:read / write / apps</small></div></div>",
200
+ "css": ".diagram-oauth{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-oauth .diagram-panel{display:flex;flex-direction:column;align-items:center;gap:4px}.diagram-oauth .diagram-col{display:flex;flex-direction:column;gap:6px}.diagram-oauth .diagram-arrow{font-size:20px;line-height:1}"
201
+ }
202
+ ```
203
+
168
204
  Access tokens are signed JWTs whose audience is the exact MCP resource URL. The server accepts only tokens issued for itself and applies scopes before listing/calling tools:
169
205
 
170
206
  | Scope | Allows |
@@ -18,6 +18,13 @@ Connect your agent to Slack, email, Telegram, or WhatsApp so you can chat with i
18
18
  - **Same agent, same memory.** Whatever you tell it on Slack is remembered when you email it later. The web chat and external messages share one thread history.
19
19
  - For one-way in-app alerts (bell icon, webhooks) see [Notifications](/docs/notifications).
20
20
 
21
+ ```an-diagram title="Many channels, one agent" summary="Every platform fans into the same agent loop and the same SQL thread history — so a Slack DM and an email continue the same conversation."
22
+ {
23
+ "html": "<div class=\"msg-fanin\"><div class=\"diagram-col\"><div class=\"diagram-node\">Slack</div><div class=\"diagram-node\">Email</div><div class=\"diagram-node\">Telegram</div><div class=\"diagram-node\">WhatsApp</div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\" data-rough><span class=\"diagram-pill accent\">One agent loop</span><small class=\"diagram-muted\">same memory · same tools</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>One SQL thread history<br><small class=\"diagram-muted\">web chat + external messages share it</small></div></div>",
24
+ "css": ".msg-fanin{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.msg-fanin .diagram-col{display:flex;flex-direction:column;gap:8px}.msg-fanin .center{display:flex;flex-direction:column;align-items:center;gap:4px}"
25
+ }
26
+ ```
27
+
21
28
  ## Set up Slack {#slack}
22
29
 
23
30
  ### What you'll need
@@ -199,18 +206,37 @@ Inbound platform webhooks use a cross-platform SQL-queue pattern so they work on
199
206
  3. The processor endpoint runs in a **fresh function execution** with its own full timeout budget. It atomically claims the task (`pending` → `processing` via `claimPendingTask`), runs the agent loop, posts the reply through the adapter, and marks the task `completed`.
200
207
  4. A recurring retry job (`startPendingTasksRetryJob`, every 60s) sweeps tasks stuck in `pending` >90s or `processing` >5min and re-fires the processor. Capped at 3 attempts, then marked `failed`.
201
208
 
202
- ```text
203
- Platform → /webhook → verify + parse → INSERT pending task ──► return 200
204
-
205
- └─ fetch /process-task (fire-and-forget)
206
-
207
- fresh exec ──► claim → agent loop → adapter.sendResponse → completed
208
-
209
- (every 60s) retry job: sweep stuck tasks → re-fire /process-task (≤3 attempts)
209
+ ```an-diagram title="Inbound webhook lifecycle" summary="The webhook only verifies, enqueues, and returns 200. A fresh function execution drains the queue and runs the agent loop, with a 60s retry job as the safety net."
210
+ {
211
+ "html": "<div class=\"msg-flow\"><div class=\"msg-row\"><div class=\"diagram-node\">Platform<br><small class=\"diagram-muted\">Slack · email · etc.</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough><strong>/webhook</strong><br><small class=\"diagram-muted\">verify signature + parse</small><br><span class=\"diagram-pill\">INSERT pending task</span></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-pill ok\">return 200</div></div><div class=\"msg-fire\"><span class=\"diagram-muted\">fire-and-forget</span> <span class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</span></div><div class=\"msg-row\"><div class=\"diagram-box\" data-rough><strong>/process-task</strong><br><small class=\"diagram-muted\">fresh execution · own timeout</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-pill accent\">claim</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-pill accent\">agent loop</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-pill accent\">adapter.sendResponse</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-pill ok\">completed</div></div><div class=\"diagram-panel msg-retry\" data-rough><span class=\"diagram-pill warn\">every 60s</span> <span class=\"diagram-muted\">retry job sweeps stuck tasks (pending &gt;90s · processing &gt;5min) and re-fires /process-task &mdash; capped at 3 attempts, then <strong>failed</strong></span></div></div>",
212
+ "css": ".msg-flow{display:flex;flex-direction:column;gap:12px}.msg-flow .msg-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.msg-flow .msg-fire{display:flex;align-items:center;gap:8px;padding-inline-start:12px}.msg-flow .msg-retry{display:flex;align-items:center;gap:8px;flex-wrap:wrap}"
213
+ }
210
214
  ```
211
215
 
212
216
  Inbound and outbound conversations live in the same SQL thread, so you can continue a Slack DM from the web UI or vice versa.
213
217
 
218
+ ```an-api
219
+ {
220
+ "method": "POST",
221
+ "path": "/_agent-native/integrations/slack/webhook",
222
+ "summary": "Slack Events API inbound webhook",
223
+ "description": "Receives Slack events (DMs and channel `app_mention`s). Verifies the request signature, parses the payload into an `IncomingMessage`, inserts a `pending` row into `integration_pending_tasks`, fires the fresh-execution processor, and returns **200 immediately** — well inside Slack's 3-second SLA. The same route shape exists per platform under `/_agent-native/integrations/<platform>/webhook`.",
224
+ "auth": "HMAC-SHA256 of the raw body using `SLACK_SIGNING_SECRET`, checked against the `X-Slack-Signature` header. In production also gated by `SLACK_ALLOWED_TEAM_IDS` / `SLACK_ALLOWED_API_APP_IDS`.",
225
+ "params": [
226
+ { "name": "X-Slack-Signature", "in": "header", "type": "string", "required": true, "description": "Slack request signature, verified before any processing." },
227
+ { "name": "X-Slack-Request-Timestamp", "in": "header", "type": "string", "required": true, "description": "Timestamp used in the signature base string." }
228
+ ],
229
+ "request": {
230
+ "contentType": "application/json",
231
+ "example": "{\n \"type\": \"event_callback\",\n \"team_id\": \"T0123\",\n \"api_app_id\": \"A0123\",\n \"event\": {\n \"type\": \"message\",\n \"channel_type\": \"im\",\n \"user\": \"U0123\",\n \"text\": \"summarize last week's signups\"\n }\n}"
232
+ },
233
+ "responses": [
234
+ { "status": "200", "description": "Acknowledged immediately. The agent loop runs in the separate /process-task execution. The first time a Request URL is saved, Slack POSTs a `url_verification` challenge and the adapter replies with the `challenge` value automatically.", "example": "{ \"ok\": true }" },
235
+ { "status": "401", "description": "Signature verification failed, or the team/app id is not in the production allowlist." }
236
+ ]
237
+ }
238
+ ```
239
+
214
240
  #### Why this pattern (and not the platform-native shortcuts) {#why-this-pattern}
215
241
 
216
242
  Serverless functions freeze the moment the response is sent. Anything still running — including a fire-and-forget Promise, a deferred LLM call, or an in-flight tool — gets killed mid-execution. The only way to keep an agent loop alive is to start a **new** function execution for it, which is what the self-fired `/process-task` POST does.
@@ -9,6 +9,13 @@ Migration is **not a separate product or template** — it is the built-in
9
9
  `/migrate` goal inside the [Agent-Native Code](/docs/code-agents-ui) workspace.
10
10
  It runs as a normal Code session you can resume, attach to, inspect, and stop.
11
11
 
12
+ ```an-diagram title="/migrate is a Code session, not a separate app" summary="A path, URL, or description goes in; the run shares the same store, transcript, and controls as every other Code session, and can emit a portable dossier."
13
+ {
14
+ "html": "<div class=\"diagram-migrate\"><div class=\"diagram-col\"><div class=\"diagram-pill\">./local-app</div><div class=\"diagram-pill\">https://example.com</div><div class=\"diagram-pill\">--describe \\\"...\\\"</div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\" data-rough><span class=\"diagram-pill accent\">/migrate goal</span><small class=\"diagram-muted\">same store · transcript · run controls</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col\"><div class=\"diagram-box\" data-rough>Migrated app</div><div class=\"diagram-pill ok\">--emit dossier</div></div></div>",
15
+ "css": ".diagram-migrate{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-migrate .diagram-col{display:flex;flex-direction:column;gap:8px}.diagram-migrate .diagram-arrow{font-size:22px;line-height:1}.diagram-migrate .center{display:flex;flex-direction:column;align-items:center;gap:4px}"
16
+ }
17
+ ```
18
+
12
19
  ```bash
13
20
  npx @agent-native/core@latest code /migrate ./my-next-app --out ../migrated-app
14
21
  npx @agent-native/core@latest code /migrate https://example.com --describe "marketing site plus dashboard"
@@ -45,22 +45,21 @@ The CLI shows a multi-select picker of every first-party template. Pick as many
45
45
 
46
46
  You get a pnpm monorepo with the private shared package, a root `package.json` that wires up workspace discovery, a shared `.env`, and one sub-directory per app you picked:
47
47
 
48
- ```text
49
- my-company-platform/
50
- ├── package.json # declares agent-native.workspaceCore
51
- ├── pnpm-workspace.yaml # packages: ["packages/*", "apps/*"]
52
- ├── .env.example # shared ANTHROPIC_API_KEY, BUILDER_PRIVATE_KEY,
53
- │ # A2A_SECRET, DATABASE_URL, ...
54
- ├── packages/
55
- │ └── shared/ # @my-company-platform/shared
56
- │ ├── src/
57
- │ │ ├── server/ # plugin overrides only when needed
58
- │ │ └── client/ # shared React code only when needed
59
- │ └── AGENTS.md # workspace-wide instructions
60
- └── apps/
61
- ├── mail/
62
- ├── calendar/
63
- └── forms/
48
+ ```an-file-tree title="A scaffolded workspace"
49
+ {
50
+ "entries": [
51
+ { "path": "package.json", "note": "declares agent-native.workspaceCore" },
52
+ { "path": "pnpm-workspace.yaml", "note": "packages: [\"packages/*\", \"apps/*\"]" },
53
+ { "path": ".env.example", "note": "shared ANTHROPIC_API_KEY, A2A_SECRET, DATABASE_URL, ..." },
54
+ { "path": "packages/shared/", "note": "@my-company-platform/shared" },
55
+ { "path": "packages/shared/src/server/", "note": "plugin overrides only when needed" },
56
+ { "path": "packages/shared/src/client/", "note": "shared React code only when needed" },
57
+ { "path": "packages/shared/AGENTS.md", "note": "workspace-wide instructions" },
58
+ { "path": "apps/mail/" },
59
+ { "path": "apps/calendar/" },
60
+ { "path": "apps/forms/" }
61
+ ]
62
+ }
64
63
  ```
65
64
 
66
65
  Then boot it:
@@ -116,6 +115,13 @@ Agent-native apps inside a workspace resolve cross-cutting behavior from three p
116
115
 
117
116
  The merge happens by file name. If an app provides a local file that also exists upstream, the local one wins. If it doesn't, the workspace shared version applies. If shared doesn't provide one either, the framework default kicks in. This applies to plugins, skills, actions, and `AGENTS.md`.
118
117
 
118
+ ```an-diagram title="Three layers, merged by file name" summary="Each app resolves plugins, skills, actions, and AGENTS.md from app-local first, then the shared package, then the framework default."
119
+ {
120
+ "html": "<div class=\"layer\"><div class=\"diagram-card accent\"><span class=\"diagram-pill accent\">1 &middot; App local</span><small class=\"diagram-muted\"><code>apps/&lt;name&gt;/</code> &mdash; highest priority</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-card\"><span class=\"diagram-pill\">2 &middot; Workspace shared</span><small class=\"diagram-muted\"><code>packages/shared/</code> &mdash; the mid-layer</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-card\"><span class=\"diagram-pill\">3 &middot; Framework default</span><small class=\"diagram-muted\"><code>@agent-native/core</code> &mdash; lowest</small></div><div class=\"diagram-arrow diagram-accent\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box ok\">first match wins</div></div>",
121
+ "css": ".layer{display:flex;flex-direction:column;align-items:flex-start;gap:8px}.layer .diagram-card{display:flex;flex-direction:column;gap:3px;padding:12px 16px;min-width:300px}.layer .diagram-arrow{font-size:20px;align-self:center}.layer .diagram-box{align-self:center;margin-top:4px}"
122
+ }
123
+ ```
124
+
119
125
  When one app needs something different, drop a local file:
120
126
 
121
127
  | Thing to override | File to create inside the app |
@@ -219,6 +225,13 @@ npx @agent-native/core@latest deploy
219
225
 
220
226
  Each app is built with `APP_BASE_PATH=/<name>` and `VITE_APP_BASE_PATH=/<name>` and emitted through the selected Nitro preset. Cloudflare Pages is the default preset and uses a dispatcher worker at `dist/_worker.js` plus `_routes.json`. Netlify is supported with `npx @agent-native/core@latest deploy --preset netlify`; it emits app functions under `.netlify/functions-internal/<app>-server` and generated redirects that leave static assets unforced so the CDN serves files first. Vercel is supported with `npx @agent-native/core@latest deploy --preset vercel`; it writes a root `.vercel/output` bundle using Vercel's Build Output API.
221
227
 
228
+ ```an-diagram title="Unified deploy: one origin, one path per app" summary="Every app ships behind a single origin, so login sessions and cross-app A2A are free."
229
+ {
230
+ "html": "<div class=\"deploy\"><div class=\"diagram-box accent\">your-agents.com<br><small class=\"diagram-muted\">one DNS record &middot; one cert &middot; one CDN</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"deploy-apps\"><div class=\"diagram-box\">/mail/*</div><div class=\"diagram-box\">/calendar/*</div><div class=\"diagram-box\">/forms/*</div></div><div class=\"diagram-pill ok\">shared login cookie on the apex &bull; same-origin A2A, no CORS</div></div>",
231
+ "css": ".deploy{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.deploy .deploy-apps{display:flex;flex-direction:column;gap:8px}.deploy .diagram-arrow{font-size:24px}.deploy .diagram-pill{flex-basis:100%}"
232
+ }
233
+ ```
234
+
222
235
  Being on the **same origin** is where the real payoff lives:
223
236
 
224
237
  - **Shared login session.** Better Auth sets its cookie on the apex domain, so logging into any app logs you into every app. No cross-domain SSO dance.
@@ -19,6 +19,13 @@ A fresh `npx @agent-native/core@latest create` scaffold already ships with:
19
19
 
20
20
  If you're evaluating agent-native for a CRM, project tracker, support inbox, or any team tool, the multi-tenant foundation is already there. All first-party templates are multi-tenant — see [Cloneable SaaS templates](/docs/cloneable-saas) for the list.
21
21
 
22
+ ```an-diagram title="Org membership and isolation" summary="Users join organizations as owner/admin/member. Every ownable row carries the org_id of the tenant that owns it, and no row leaks across the boundary."
23
+ {
24
+ "html": "<div class=\"mt-grid\"><div class=\"diagram-card\"><span class=\"diagram-pill accent\">Org A</span><small class=\"diagram-muted\">members: alice (owner), bob (member)</small><div class=\"diagram-box\">rows where org_id = A</div></div><div class=\"diagram-card\"><span class=\"diagram-pill accent\">Org B</span><small class=\"diagram-muted\">members: carol (owner)</small><div class=\"diagram-box\">rows where org_id = B</div></div></div><div class=\"mt-wall\" aria-hidden=\"true\"><span class=\"diagram-pill warn\">no cross-org reads</span></div>",
25
+ "css": ".mt-grid{display:flex;gap:16px;flex-wrap:wrap}.mt-grid .diagram-card{display:flex;flex-direction:column;gap:8px;padding:14px 16px;flex:1;min-width:200px}.mt-wall{display:flex;justify-content:center;margin-top:12px}"
26
+ }
27
+ ```
28
+
22
29
  ## The org switcher UI {#org-switcher}
23
30
 
24
31
  The org-switcher and members UI render in every template with no extra code. They drive the core org REST routes under `/_agent-native/org/*` (create org, switch org, list/invite/remove members, change roles, set allowed email domain). Users pick the active org from the switcher; the members panel handles invitations and role changes.
@@ -29,6 +36,13 @@ This is the framework's own `org/` module, not Better Auth's organization plugin
29
36
 
30
37
  Tenant data is isolated by an `org_id` column (added by `ownableColumns()`), and the framework scopes every query to the active org automatically: `session.orgId → AGENT_ORG_ID → SQL`. When a user switches organizations, the UI, actions, and agent all see only that org's data — the agent cannot reach data for an org the user isn't a member of.
31
38
 
39
+ ```an-diagram title="From session to scoped SQL" summary="The active org on the session becomes AGENT_ORG_ID, which the framework folds into the WHERE clause of every query."
40
+ {
41
+ "html": "<div class=\"mt-pipe\"><div class=\"diagram-node\">session.orgId<br><small class=\"diagram-muted\">active org on session</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\">AGENT_ORG_ID<br><small class=\"diagram-muted\">request context</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\">SQL row scoping<br><small class=\"diagram-muted\">WHERE owner_email = ? AND org_id = ?</small></div></div>",
42
+ "css": ".mt-pipe{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.mt-pipe .diagram-node{display:flex;flex-direction:column;gap:2px;padding:10px 14px}.mt-pipe .diagram-arrow{font-size:22px;line-height:1}"
43
+ }
44
+ ```
45
+
32
46
  This is the same pipeline used for per-user scoping. For the SQL-level mechanics, the `ownableColumns()` contract, and the `accessFilter` / `resolveAccess` / `assertAccess` guards, see [Security → Data Scoping](/docs/security#data-scoping) — the single source of truth for the scoping pipeline.
33
47
 
34
48
  ## Related docs {#related}