@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
@@ -17,6 +17,13 @@ approval/denial controls, or links into app views. Use [MCP Apps](/docs/mcp-apps
17
17
  when an external host such as Claude, ChatGPT, Copilot, or Cursor should render
18
18
  an inline route from your app.
19
19
 
20
+ ```an-diagram title="The native render path" summary="An action returns JSON; the runtime matches an explicit widget discriminant or chatUI.renderer; AssistantChat mounts a real React component. No iframe, no HTML execution."
21
+ {
22
+ "html": "<div class=\"diagram-render\"><div class=\"diagram-node\">Action runs<br><small class=\"diagram-muted\">returns structured JSON</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\">Match</span><small class=\"diagram-muted\">explicit widget &middot; chatUI.renderer</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\">&lt;AssistantChat&gt;<br><small class=\"diagram-muted\">mounts a React widget</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card col\"><div class=\"diagram-pill ok\">DataTable</div><div class=\"diagram-pill ok\">DataChart</div><div class=\"diagram-pill ok\">DataInsights</div></div></div>",
23
+ "css": ".diagram-render{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-render .center{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px}.diagram-render .col{display:flex;flex-direction:column;gap:6px;padding:12px}.diagram-render .diagram-arrow{font-size:22px;line-height:1}"
24
+ }
25
+ ```
26
+
20
27
  ## Action-declared widgets {#action-declared-widgets}
21
28
 
22
29
  The native path has two explicit parts:
@@ -90,9 +97,12 @@ export default defineAction({
90
97
  });
91
98
  ```
92
99
 
93
- The renderer only takes over when the action declares `chatUI` or the result has
94
- an explicit known `widget` discriminant. It never shape-infers arbitrary objects
95
- and it never executes HTML or JavaScript from tool results.
100
+ ```an-callout
101
+ {
102
+ "tone": "success",
103
+ "body": "The renderer only takes over when the action declares `chatUI` **or** the result carries an explicit known `widget` discriminant. It never shape-infers arbitrary objects and never executes HTML or JavaScript from tool results — so a native widget can't become an injection vector."
104
+ }
105
+ ```
96
106
 
97
107
  When a user asks for a chart, graph, table, trend, or compact report, app agents
98
108
  should prefer an action that declares one of these native renderers. The final
@@ -199,6 +209,13 @@ tutorial points here for the runtime story, and [Component API](/docs/components
199
209
  lists each connector and adapter with its import path; the contract itself is
200
210
  described below.
201
211
 
212
+ ```an-diagram title="BYO runtime keeps the Agent-Native chat shell" summary="Your external agent streams normalized events through a connector; Agent-Native keeps the composer, transcript, tool cards, approvals, and native widgets."
213
+ {
214
+ "html": "<div class=\"diagram-byo\"><div class=\"diagram-box\" data-rough>Your agent<br><small class=\"diagram-muted\">OpenAI &middot; Claude &middot; Vercel AI &middot; AG-UI &middot; HTTP</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\"><span class=\"diagram-pill accent\">connector</span><small class=\"diagram-muted\">normalized message-* / tool-* events</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card col\"><div class=\"diagram-pill\">&lt;AssistantChat runtime=&hellip; /&gt;</div><small class=\"diagram-muted\">composer &middot; transcript &middot; tool cards</small><small class=\"diagram-muted\">approvals &middot; native widgets</small></div></div>",
215
+ "css": ".diagram-byo{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-byo .center{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px}.diagram-byo .col{display:flex;flex-direction:column;gap:6px;padding:14px}.diagram-byo .diagram-arrow{font-size:22px;line-height:1}"
216
+ }
217
+ ```
218
+
202
219
  All connectors are exported from `@agent-native/core/client/chat` (and the root
203
220
  `@agent-native/core/client` entry). Use the generic HTTP runtime when your agent
204
221
  can expose a POST endpoint that returns SSE or NDJSON runtime events:
@@ -18,6 +18,13 @@ await notify(
18
18
  );
19
19
  ```
20
20
 
21
+ ```an-diagram title="One call, many destinations" summary="notify() always writes the owner-scoped inbox row, fans out to every registered channel in parallel (best-effort), then emits notification.sent on the event bus."
22
+ {
23
+ "html": "<div class=\"diagram-notify\"><div class=\"diagram-node\">notify(input, { owner })<br><small class=\"diagram-muted\">any server code &middot; action, automation, plugin</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel fan\" data-rough><div class=\"fan-row\"><span class=\"diagram-pill accent\">inbox</span><div class=\"diagram-box\" data-rough>notifications table &rarr; bell UI<br><small class=\"diagram-muted\">always on &middot; owner-scoped</small></div></div><div class=\"fan-row\"><span class=\"diagram-pill\">webhook</span><div class=\"diagram-box\" data-rough>POST JSON to NOTIFICATIONS_WEBHOOK_URL<br><small class=\"diagram-muted\">best-effort</small></div></div><div class=\"fan-row\"><span class=\"diagram-pill\">custom</span><div class=\"diagram-box\" data-rough>registerNotificationChannel(...)<br><small class=\"diagram-muted\">best-effort &middot; runs in parallel</small></div></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill ok\">notification.sent</span><small class=\"diagram-muted\">event bus &middot; automations can chain</small></div></div>",
24
+ "css": ".diagram-notify{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-notify .fan{display:flex;flex-direction:column;gap:10px;padding:14px}.diagram-notify .fan-row{display:flex;align-items:center;gap:10px}.diagram-notify .diagram-card{display:flex;flex-direction:column;gap:6px;padding:12px 14px}.diagram-notify .diagram-arrow{font-size:22px;line-height:1}"
25
+ }
26
+ ```
27
+
21
28
  ## Severities {#severities}
22
29
 
23
30
  | Severity | Use for |
@@ -37,6 +44,13 @@ Severity drives the badge styling in the dropdown and is passed through to chann
37
44
 
38
45
  The webhook channel resolves `${keys.NAME}` references in both the URL and `NOTIFICATIONS_WEBHOOK_AUTH` against the owner's ad-hoc [secrets](/docs/security), so the raw value never enters the agent's context. Per-key URL allowlists are enforced — same rule the automations `web-request` tool uses.
39
46
 
47
+ ```an-diagram title="Channels and severity" summary="inbox is always on; webhook needs an env var; custom channels register at startup. Severity drives badge styling and is passed through to every channel."
48
+ {
49
+ "html": "<div class=\"diagram-channels\"><div class=\"diagram-panel col\" data-rough><strong>Channels</strong><div class=\"diagram-box\" data-rough>inbox<br><small class=\"diagram-muted\">always on &mdash; part of the primitive</small></div><div class=\"diagram-box\" data-rough>webhook<br><small class=\"diagram-muted\">needs NOTIFICATIONS_WEBHOOK_URL</small></div><div class=\"diagram-box\" data-rough>custom<br><small class=\"diagram-muted\">registerNotificationChannel()</small></div></div><div class=\"diagram-panel col\" data-rough><strong>Severity drives the badge</strong><div class=\"sev-row\"><span class=\"diagram-pill\">info</span><span class=\"diagram-muted\">confirmations, FYI</span></div><div class=\"sev-row\"><span class=\"diagram-pill warn\">warning</span><span class=\"diagram-muted\">look at soon</span></div><div class=\"sev-row\"><span class=\"diagram-pill accent\">critical</span><span class=\"diagram-muted\">needs immediate attention</span></div></div></div>",
50
+ "css": ".diagram-channels{display:flex;gap:16px;flex-wrap:wrap;align-items:flex-start}.diagram-channels .col{display:flex;flex-direction:column;gap:10px;padding:14px;min-width:240px}.diagram-channels .sev-row{display:flex;align-items:center;gap:10px}"
51
+ }
52
+ ```
53
+
40
54
  ## API {#api}
41
55
 
42
56
  ### `notify(input, meta)` {#notify}
@@ -141,6 +155,22 @@ Mounted at `/_agent-native/notifications/*` by the core-routes plugin. All route
141
155
  | `POST` | `/_agent-native/notifications/read-all` |
142
156
  | `DELETE` | `/_agent-native/notifications/:id` |
143
157
 
158
+ ```an-api title="List notifications" summary="The route behind listNotifications() — scoped to the authenticated session's email."
159
+ {
160
+ "method": "GET",
161
+ "path": "/_agent-native/notifications?unread=true&limit=50",
162
+ "summary": "List recent notifications for the current user",
163
+ "auth": "Authenticated session; results are scoped to the session's email.",
164
+ "params": [
165
+ { "name": "unread", "in": "query", "type": "boolean", "required": false, "description": "When true, returns only unread notifications." },
166
+ { "name": "limit", "in": "query", "type": "number", "required": false, "description": "Max rows to return." }
167
+ ],
168
+ "responses": [
169
+ { "status": "200", "description": "Owner-scoped notification rows, newest first." }
170
+ ]
171
+ }
172
+ ```
173
+
144
174
  ## UI component {#ui}
145
175
 
146
176
  ```tsx
@@ -33,6 +33,13 @@ When a user sends a message, the framework automatically records:
33
33
 
34
34
  No code changes needed. The instrumentation hooks into `production-agent.ts` transparently.
35
35
 
36
+ ```an-diagram title="Every run feeds the loop" summary="One agent run produces a trace, automated scores, and a feedback hook — all stored in the app's own SQL and surfaced on the dashboard. Experiments split traffic across config variants."
37
+ {
38
+ "html": "<div class=\"obs-loop\"><div class=\"diagram-node\">Agent run<br><small class=\"diagram-muted\">production-agent.ts</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\"><span class=\"diagram-pill accent\">Captured automatically</span><small class=\"diagram-muted\">tokens &middot; cost &middot; latency &middot; tool calls</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col\"><div class=\"diagram-box\">Traces &amp; spans</div><div class=\"diagram-box\">Evals (5 scorers + LLM judge)</div><div class=\"diagram-box\">Feedback &amp; frustration index</div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node ok\">Dashboard<br><small class=\"diagram-muted\">scoped to the signed-in user</small></div></div>",
39
+ "css": ".obs-loop{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.obs-loop .diagram-col{display:flex;flex-direction:column;gap:8px}.obs-loop .diagram-arrow{font-size:22px;line-height:1}.obs-loop .center{display:flex;flex-direction:column;align-items:center;gap:4px}"
40
+ }
41
+ ```
42
+
36
43
  ## The dashboard {#dashboard}
37
44
 
38
45
  Add the dashboard to any template with a single route:
@@ -140,6 +147,13 @@ PUT /_agent-native/observability/experiments/:id
140
147
 
141
148
  Use the real model identifiers your engine accepts in place of `<your-model-id>` / `<other-model-id>` (model names change often — check your provider/engine for the current ids). The agent loop automatically resolves the user's variant and applies the config override. Assignment uses consistent hashing — same user always gets the same variant.
142
149
 
150
+ ```an-diagram title="Consistent-hash variant assignment" summary="Each user hashes to a stable variant, the loop applies that variant's config override, and results roll up per variant with confidence intervals."
151
+ {
152
+ "html": "<div class=\"exp\"><div class=\"diagram-node\">User id<br><small class=\"diagram-muted\">consistent hash</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col\"><div class=\"diagram-card\"><span class=\"diagram-pill\">control &middot; 50%</span><small class=\"diagram-muted\">config override A</small></div><div class=\"diagram-card\"><span class=\"diagram-pill accent\">treatment &middot; 50%</span><small class=\"diagram-muted\">config override B</small></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\">Results per variant<br><small class=\"diagram-muted\">cost &middot; latency &middot; satisfaction</small></div></div>",
153
+ "css": ".exp{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.exp .diagram-col{display:flex;flex-direction:column;gap:8px}.exp .diagram-card{display:flex;flex-direction:column;gap:2px;padding:8px 12px}.exp .diagram-arrow{font-size:22px;line-height:1}"
154
+ }
155
+ ```
156
+
143
157
  ## Configuration {#config}
144
158
 
145
159
  All settings are stored in the `observability-config` key:
@@ -155,7 +169,12 @@ All settings are stored in the `observability-config` key:
155
169
  }
156
170
  ```
157
171
 
158
- Content is **redacted by default** — only token counts, costs, and timing are stored. Opt in to content capture when needed for debugging.
172
+ ```an-callout
173
+ {
174
+ "tone": "info",
175
+ "body": "Content is **redacted by default** — only token counts, costs, and timing are stored. `capturePrompts`, `captureToolArgs`, and `captureToolResults` are opt-in; turn them on only when you need prompt/argument content for debugging."
176
+ }
177
+ ```
159
178
 
160
179
  ## API endpoints {#api}
161
180
 
@@ -19,6 +19,13 @@ OM represents a long thread as three layers, from most-distilled to most-recent:
19
19
  | **Observations** | Dense, dated entries that fold a stretch of raw messages into a compact record of what happened. |
20
20
  | **Recent raw messages** | The last N turns, kept **verbatim** — never folded — so the agent always sees the latest context. |
21
21
 
22
+ ```an-diagram title="Three tiers, distilled to recent" summary="The older prefix folds into dated observations and a long-arc reflection; only the most recent turns stay verbatim."
23
+ {
24
+ "html": "<div class=\"om\"><div class=\"diagram-card\"><span class=\"diagram-pill\">Reflections</span><small class=\"diagram-muted\">long-arc summary, condensed from the observation log</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&uarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill accent\">Observations</span><small class=\"diagram-muted\">dense, dated entries folding stretches of raw messages</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&uarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill ok\">Recent raw messages</span><small class=\"diagram-muted\">last N turns, kept <strong>verbatim</strong> — never folded</small></div></div>",
25
+ "css": ".om{display:flex;flex-direction:column-reverse;align-items:stretch;gap:8px}.om .diagram-card{display:flex;flex-direction:column;gap:4px;padding:12px 16px}.om .diagram-arrow{text-align:center;font-size:20px;line-height:1}"
26
+ }
27
+ ```
28
+
22
29
  On each turn, the read side assembles these into a single self-labeled `[Observational Memory]` block that replaces the raw older prefix, keeps the recent-raw window intact, and tells the model to treat the compacted record as authoritative (don't redo completed work, trust the recorded decisions, names, dates, and status).
23
30
 
24
31
  ## How compaction runs {#compaction}
@@ -28,6 +35,13 @@ Two passes run as a **fire-and-forget, best-effort** step _after_ a clean turn,
28
35
  1. **Observer** — once a thread's _unobserved_ messages exceed the observation token threshold, folds them into a single dense observation entry.
29
36
  2. **Reflector** — once the persisted observation log itself exceeds the reflection token threshold, condenses the observations into a higher-level reflection.
30
37
 
38
+ ```an-diagram title="Two best-effort passes after a clean turn" summary="Each pass no-ops below its threshold, so running the compactor every turn is cheap. Failures are swallowed and never add latency."
39
+ {
40
+ "html": "<div class=\"om-pass\"><div class=\"diagram-node\">Clean turn ends<br><small class=\"diagram-muted\">fire-and-forget</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill accent\">Observer</span><small class=\"diagram-muted\">unobserved tokens &gt; 30k? &rarr; fold into one observation</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill accent\">Reflector</span><small class=\"diagram-muted\">observation log &gt; 40k? &rarr; condense into a reflection</small></div></div>",
41
+ "css": ".om-pass{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.om-pass .diagram-node,.om-pass .diagram-card{display:flex;flex-direction:column;gap:2px;padding:10px 14px}.om-pass .diagram-arrow{font-size:22px;line-height:1}"
42
+ }
43
+ ```
44
+
31
45
  Both passes no-op below their thresholds, so calling the compactor after every turn is cheap. Because OM replaces the volatile raw prefix with stable compacted text, it also keeps the prompt **cache-stable** across turns of a long thread.
32
46
 
33
47
  OM data lives in the app's own SQL database, scoped to the owner (and org when present) — the same scoping model as the rest of the framework. It is never shared across users.
@@ -10,6 +10,13 @@ When you first open an app built on the agent-native framework, you'll see a
10
10
  to the agent chat: connect an AI engine, optionally point the app at shared
11
11
  infrastructure, and add providers only when you need them.
12
12
 
13
+ ```an-diagram title="The setup checklist" summary="Only Connect an AI engine is required. The panel tracks completion and auto-hides once everything required is done."
14
+ {
15
+ "html": "<div class=\"ob\"><div class=\"diagram-card\"><span class=\"diagram-pill warn\">required</span><strong>Connect an AI engine</strong><small class=\"diagram-muted\">Connect Builder (one click) or paste an LLM key</small></div><div class=\"diagram-card\"><span class=\"diagram-pill\">optional</span><strong>Database</strong><small class=\"diagram-muted\">set <code>DATABASE_URL</code></small></div><div class=\"diagram-card\"><span class=\"diagram-pill\">optional</span><strong>Authentication</strong><small class=\"diagram-muted\">OAuth / access token</small></div><div class=\"diagram-card\"><span class=\"diagram-pill\">optional</span><strong>Email delivery</strong><small class=\"diagram-muted\">Resend / SendGrid</small></div><div class=\"diagram-arrow diagram-accent\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box ok\">all required done &rarr; panel auto-hides</div></div>",
16
+ "css": ".ob{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.ob .diagram-card{display:flex;flex-direction:column;gap:3px;padding:12px 14px}.ob .diagram-arrow{font-size:22px}"
17
+ }
18
+ ```
19
+
13
20
  ## For end users
14
21
 
15
22
  ### What you'll see
@@ -93,49 +100,33 @@ All routes live under `/_agent-native/onboarding/`:
93
100
  | `POST /_agent-native/onboarding/reopen` | Clear dismissal (re-show panel) |
94
101
  | `GET /_agent-native/onboarding/dismissed` | Read dismissal + allComplete flag |
95
102
 
103
+ ```an-api title="List onboarding steps"
104
+ {
105
+ "method": "GET",
106
+ "path": "/_agent-native/onboarding/steps",
107
+ "summary": "List all registered steps with their completion status",
108
+ "description": "Drives the sidebar checklist — returns each step's id, title, methods, required flag, and whether `isComplete` currently passes.",
109
+ "responses": [
110
+ { "status": "200", "description": "Array of steps with completion status for the current user/app." }
111
+ ]
112
+ }
113
+ ```
114
+
96
115
  ### Adding a step from a template
97
116
 
98
- ```ts
99
- // server/plugins/my-onboarding.ts
100
- import { defineNitroPlugin } from "@agent-native/core/server";
101
- import { registerOnboardingStep } from "@agent-native/core/onboarding";
102
- import { listOAuthAccounts } from "@agent-native/core/oauth-tokens";
103
-
104
- export default defineNitroPlugin(() => {
105
- registerOnboardingStep({
106
- id: "gmail",
107
- order: 100,
108
- title: "Connect Gmail",
109
- description: "Grant read/send access so the agent can work with email.",
110
- methods: [
111
- {
112
- id: "oauth",
113
- kind: "link",
114
- primary: true,
115
- label: "Sign in with Google",
116
- payload: {
117
- url: "/_agent-native/google/auth-url?scope=mail",
118
- external: false,
119
- },
120
- },
121
- {
122
- id: "delegate",
123
- kind: "agent-task",
124
- label: "Let the agent set it up",
125
- badge: "beta",
126
- payload: {
127
- prompt: "Walk me through connecting Gmail. Set env vars as needed.",
128
- },
129
- },
130
- ],
131
- // OAuth tokens live in the oauth_tokens store (saveOAuthTokens), not env vars.
132
- // Check the store — not process.env.GMAIL_REFRESH_TOKEN.
133
- isComplete: async () => {
134
- const accounts = await listOAuthAccounts("google");
135
- return accounts.length > 0;
136
- },
137
- });
138
- });
117
+ ```an-annotated-code title="Registering a custom onboarding step"
118
+ {
119
+ "filename": "server/plugins/my-onboarding.ts",
120
+ "language": "ts",
121
+ "code": "import { defineNitroPlugin } from \"@agent-native/core/server\";\nimport { registerOnboardingStep } from \"@agent-native/core/onboarding\";\nimport { listOAuthAccounts } from \"@agent-native/core/oauth-tokens\";\n\nexport default defineNitroPlugin(() => {\n registerOnboardingStep({\n id: \"gmail\",\n order: 100,\n title: \"Connect Gmail\",\n description: \"Grant read/send access so the agent can work with email.\",\n methods: [\n {\n id: \"oauth\",\n kind: \"link\",\n primary: true,\n label: \"Sign in with Google\",\n payload: { url: \"/_agent-native/google/auth-url?scope=mail\", external: false },\n },\n {\n id: \"delegate\",\n kind: \"agent-task\",\n label: \"Let the agent set it up\",\n badge: \"beta\",\n payload: { prompt: \"Walk me through connecting Gmail. Set env vars as needed.\" },\n },\n ],\n isComplete: async () => {\n const accounts = await listOAuthAccounts(\"google\");\n return accounts.length > 0;\n },\n });\n});",
122
+ "annotations": [
123
+ { "lines": "5", "label": "Auto-mounted", "note": "Register from a Nitro plugin — the framework handles rendering, completion tracking, and dismissal." },
124
+ { "lines": "7", "label": "Stable id", "note": "Re-registering with the same `id` after defaults load overrides a built-in step." },
125
+ { "lines": "12-19", "label": "Primary method", "note": "`primary: true` marks the big CTA. `kind: \"link\"` sends the user into the OAuth flow." },
126
+ { "lines": "20-26", "label": "Delegate path", "note": "`kind: \"agent-task\"` hands the setup to the agent chat with a prompt." },
127
+ { "lines": "28-31", "label": "Completion check", "note": "`isComplete` runs server-side. OAuth tokens live in the `oauth_tokens` store — check it, not `process.env.GMAIL_REFRESH_TOKEN`." }
128
+ ]
129
+ }
139
130
  ```
140
131
 
141
132
  ### Checking Workspace Connections in Onboarding
@@ -14,6 +14,13 @@ One install gives you:
14
14
  - **Two skills** — `/visual-plan` (the canonical entry point) and `/visual-recap`.
15
15
  - **The Plan MCP connector** — registered against the hosted app at `https://plan.agent-native.com` (MCP endpoint `https://plan.agent-native.com/_agent-native/mcp`, server name `plan`).
16
16
 
17
+ ```an-diagram title="Three routes, one bundle" summary="The universal CLI, Claude Code plugin, and Codex plugin all install the same two skills plus the hosted Plan connector."
18
+ {
19
+ "html": "<div class=\"diagram-routes\"><div class=\"diagram-col\"><div class=\"diagram-node\">Universal CLI<br><small class=\"diagram-muted\">skills add visual-plan</small></div><div class=\"diagram-node\">Claude Code plugin<br><small class=\"diagram-muted\">/plugin install</small></div><div class=\"diagram-node\">Codex plugin<br><small class=\"diagram-muted\">codex plugin add</small></div></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\"><span class=\"diagram-pill accent\">/visual-plan</span><span class=\"diagram-pill accent\">/visual-recap</span><small class=\"diagram-muted\">two skills</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\">Plan MCP connector<br><small class=\"diagram-muted\">plan.agent-native.com/_agent-native/mcp</small></div></div>",
20
+ "css": ".diagram-routes{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-routes .diagram-col{display:flex;flex-direction:column;gap:10px}.diagram-routes .diagram-arrow{font-size:22px;line-height:1}.diagram-routes .center{display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center}.diagram-routes .center .diagram-pill{margin:2px}"
21
+ }
22
+ ```
23
+
17
24
  By default, both skills publish to the hosted Plan app — they create a plan via
18
25
  the MCP connector and hand you a link or inline plan to review. They never dump
19
26
  an inline Markdown/ASCII plan into chat as the deliverable. If a Plan tool
@@ -75,6 +82,13 @@ This keeps plan content out of the Agent-Native Plan database. Hosted sharing,
75
82
  comments, screenshots, and plan history are unavailable until you explicitly
76
83
  publish later.
77
84
 
85
+ ```an-diagram title="Hosted vs. local-files mode" summary="By default skills publish through the connector; local-files mode writes MDX to disk and previews via a localhost bridge instead."
86
+ {
87
+ "html": "<div class=\"diagram-modes\"><div class=\"diagram-card\"><span class=\"diagram-pill accent\">Default · hosted</span><strong>Publish to the Plan app</strong><small class=\"diagram-muted\">MCP connector &rarr; hosted DB &rarr; share links, comments, history, screenshots</small></div><div class=\"diagram-card\"><span class=\"diagram-pill warn\">Local-files privacy</span><strong>Write MDX to disk</strong><small class=\"diagram-muted\">plan.mdx + canvas.mdx + prototype.mdx &rarr; localhost bridge &rarr; hosted Plan UI reads local source. No DB writes until <code>publish-visual-plan</code>.</small></div></div>",
88
+ "css": ".diagram-modes{display:flex;gap:14px;flex-wrap:wrap}.diagram-modes .diagram-card{flex:1 1 260px;display:flex;flex-direction:column;gap:6px;padding:16px 18px}"
89
+ }
90
+ ```
91
+
78
92
  Agent Native Desktop has a separate local-file sync path for hosted plans: the
79
93
  Desktop app can mirror a hosted plan to local MDX files and import edits back
80
94
  without cloning the Plan app or running a CLI. That workflow keeps the hosted
@@ -24,6 +24,13 @@ On each PR push, the workflow:
24
24
  7. Upserts a single sticky PR comment that embeds the screenshots **inline** with a `<picture>` element (served through GitHub's camo image proxy) next to the link to the interactive recap.
25
25
  8. Completes the `Visual Recap` check as success, skipped, or neutral.
26
26
 
27
+ ```an-diagram title="What happens on each PR push" summary="A bounded diff feeds a real coding agent, which authors a recap; the workflow screenshots it and upserts one sticky comment."
28
+ {
29
+ "html": "<div class=\"diagram-recap\"><div class=\"diagram-node\">PR push<br><small class=\"diagram-muted\">bounded base&hellip;head diff</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\">Coding agent<br><small class=\"diagram-muted\">Claude Code / Codex reads diff</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\"><span class=\"diagram-pill accent\">create-visual-recap</span><small class=\"diagram-muted\">publishes recap plan</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\">Headless Chrome<br><small class=\"diagram-muted\">light + dark screenshots</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\">One sticky PR comment<br><small class=\"diagram-muted\">inline screenshot + plan link</small></div></div><div class=\"diagram-foot diagram-muted\">Plus an informational <span class=\"diagram-pill\">Visual Recap</span> check &mdash; non-blocking, never required.</div>",
30
+ "css": ".diagram-recap{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.diagram-recap .diagram-arrow{font-size:20px;line-height:1}.diagram-recap .center{display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center}.diagram-recap .diagram-foot{flex-basis:100%;margin-top:10px;font-size:13px}"
31
+ }
32
+ ```
33
+
27
34
  A re-push updates the same plan and the same sticky comment in place — no orphaned plans, no comment spam.
28
35
 
29
36
  ## Installing it
@@ -150,6 +157,13 @@ If you want to generate recaps for fork PRs, a second workflow file is available
150
157
 
151
158
  To install it, copy the file from [BuilderIO/agent-native](https://github.com/BuilderIO/agent-native/blob/main/.github/workflows/pr-visual-recap-fork.yml) into your repo's `.github/workflows/` directory alongside the existing `pr-visual-recap.yml`. The same secrets (`PLAN_RECAP_TOKEN`, `ANTHROPIC_API_KEY`) apply.
152
159
 
160
+ ```an-diagram title="Fork PR consent gate" summary="Fork PRs get no secrets by default; the opt-in workflow runs only after a maintainer reviews the diff and applies the recap label."
161
+ {
162
+ "html": "<div class=\"diagram-fork\"><div class=\"diagram-node\">Fork PR opened<br><small class=\"diagram-muted\">no recap runs automatically</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-card\"><span class=\"diagram-pill warn\">Maintainer review</span><small class=\"diagram-muted\">skim diff for prompt-injection, then apply the <code>recap</code> label</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel center\">Gate checks<br><small class=\"diagram-muted\">is fork PR? &amp; label present?</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box ok\">Recap runs<br><small class=\"diagram-muted\">base-repo code only · fork diff is text input</small></div></div>",
163
+ "css": ".diagram-fork{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.diagram-fork .diagram-arrow{font-size:20px;line-height:1}.diagram-fork .center{display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center}.diagram-fork .diagram-card{display:flex;flex-direction:column;gap:6px;padding:12px 14px}"
164
+ }
165
+ ```
166
+
153
167
  ### How the label gate works
154
168
 
155
169
  1. A fork contributor opens a PR. No recap runs automatically.
@@ -7,6 +7,13 @@ description: "Loop-internal observer/guardrail hooks that watch the model's stre
7
7
 
8
8
  A `Processor` is a loop-internal **observer/guardrail** for the agent run. It watches the model's streamed output and the tool calls it requests _as the run progresses_, keeps its own scratch state, and can **abort** the run before a "done" is claimed. This is the structural prerequisite for real-time guardrails (block disallowed output mid-stream) and a proof-of-done / coverage gate (inspect what the model is about to do and halt it).
9
9
 
10
+ ```an-diagram title="Where the three hooks fire in the run" summary="processOutputStream watches every chunk, processOutputStep gates tool calls per response, processOutputResult records a verdict at the end. Any hook can abort with a TripWire."
11
+ {
12
+ "html": "<div class=\"diagram-proc\"><div class=\"diagram-node\" data-rough>stream chunks<br><small class=\"diagram-muted\">processOutputStream</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\" data-rough>per model response<br><small class=\"diagram-muted\">processOutputStep — gate tool calls</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-node\" data-rough>run end<br><small class=\"diagram-muted\">processOutputResult — verdict</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-pill warn\">abort() &rarr; TripWire &rarr; tripwire event</div></div>",
13
+ "css": ".diagram-proc{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.diagram-proc .diagram-arrow{font-size:22px;line-height:1}.diagram-proc .diagram-pill{flex-basis:100%}"
14
+ }
15
+ ```
16
+
10
17
  > [!WARNING]
11
18
  > A processor is **configuration**, not a tool, not an action, and not an authoring DSL. Processors only observe, mutate their own stream-scoped state, and `abort()`. They never define app behavior, replace actions, or appear to the model. App operations belong in [actions](/docs/actions).
12
19
 
@@ -41,6 +41,13 @@ Separate concern from [notifications](/docs/notifications): notifications fire o
41
41
  | `failed` | Error terminal |
42
42
  | `cancelled` | User interrupted |
43
43
 
44
+ ```an-diagram title="Run lifecycle" summary="startRun opens a running row; updateRunProgress patches it; completeRun moves it to one terminal status and stamps completed_at."
45
+ {
46
+ "html": "<div class=\"diagram-run\"><div class=\"diagram-box\" data-rough>startRun()</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-panel running\" data-rough><span class=\"diagram-pill accent\">running</span><small class=\"diagram-muted\">updateRunProgress() &#8635; percent + step</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-col terminal\"><span class=\"diagram-pill ok\">succeeded</span><span class=\"diagram-pill warn\">failed</span><span class=\"diagram-pill\">cancelled</span><small class=\"diagram-muted\">completeRun() &rarr; sets completed_at</small></div></div>",
47
+ "css": ".diagram-run{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-run .diagram-panel{display:flex;flex-direction:column;gap:6px;padding:12px 16px}.diagram-run .terminal{display:flex;flex-direction:column;gap:6px;align-items:flex-start}.diagram-run .diagram-arrow{font-size:22px;line-height:1}"
48
+ }
49
+ ```
50
+
44
51
  Terminal statuses set `completed_at`. The UI tray shows only `running` rows; completed rows stay in the database for `action=list` queries.
45
52
 
46
53
  ## API {#api}
@@ -106,6 +113,22 @@ Mounted at `/_agent-native/runs/*` by the core-routes plugin. **Read-only over H
106
113
  | `GET` | `/_agent-native/runs/:id` |
107
114
  | `DELETE` | `/_agent-native/runs/:id` |
108
115
 
116
+ ```an-api title="List active runs" method="GET" path="/_agent-native/runs"
117
+ {
118
+ "method": "GET",
119
+ "path": "/_agent-native/runs",
120
+ "summary": "List the caller's runs. The RunsTray polls this with active=true.",
121
+ "description": "Read-only and owner-scoped — every row has an `owner` column and every query filters on it, so callers only ever see their own runs. Writes (start/update/complete) go through the agent's `manage-progress` tool, not HTTP.",
122
+ "auth": "Session cookie (owner-scoped)",
123
+ "params": [
124
+ { "name": "active", "in": "query", "type": "boolean", "required": false, "description": "When true, returns only `running` rows." }
125
+ ],
126
+ "responses": [
127
+ { "status": "200", "description": "Array of AgentRun rows owned by the caller." }
128
+ ]
129
+ }
130
+ ```
131
+
109
132
  ## UI component {#ui}
110
133
 
111
134
  ```tsx
@@ -12,6 +12,13 @@ daily metrics to Slack" — and the agent acts and returns the result wherever i
12
12
  belongs. It is still a real app: actions, sessions, app state, history,
13
13
  settings, credentials, and share records all live in SQL.
14
14
 
15
+ ```an-diagram title="The app-agent loop is the front door" summary="Many entry points reach one agent loop over SQL-backed actions and state; results return to wherever the request came from. UI is added only when humans need to supervise."
16
+ {
17
+ "html": "<div class=\"diagram-pure\"><div class=\"diagram-col\"><div class=\"diagram-pill\">Terminal</div><div class=\"diagram-pill\">Slack · email</div><div class=\"diagram-pill\">Scheduled job</div><div class=\"diagram-pill\">Another agent (A2A)</div><div class=\"diagram-pill\">Chat</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\">App-agent loop</span><small class=\"diagram-muted\">actions · sessions · app state in SQL</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</div><div class=\"diagram-box\" data-rough>Result returns<br><small class=\"diagram-muted\">to where it belongs</small></div></div>",
18
+ "css": ".diagram-pure{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-pure .diagram-col{display:flex;flex-direction:column;gap:8px}.diagram-pure .diagram-arrow{font-size:22px;line-height:1}.diagram-pure .center{display:flex;flex-direction:column;align-items:center;gap:4px}"
19
+ }
20
+ ```
21
+
15
22
  Reach for this shape when the work runs in the background, the output leaves the
16
23
  app, the domain is one-shot, or you're prototyping. The agent still needs a UI —
17
24
  not a dashboard, but a place for humans to supervise, configure, and steer it —
@@ -36,6 +36,13 @@ This is built on three battle-tested technologies: **Yjs** (CRDT for conflict-fr
36
36
 
37
37
  The collaboration system has five interlocking layers.
38
38
 
39
+ ```an-diagram title="Five interlocking layers" summary="From the in-memory CRDT down to the transport that carries updates between peers — each layer has one job."
40
+ {
41
+ "html": "<div class=\"diagram-stack\"><div class=\"diagram-card layer\"><span class=\"diagram-pill accent\">1 &middot; Yjs Y.Doc</span><small class=\"diagram-muted\">CRDT &mdash; conflict-free merge, no coordinator</small></div><div class=\"diagram-card layer\"><span class=\"diagram-pill\">2 &middot; SQL canonical content</span><small class=\"diagram-muted\">_collab_docs &mdash; durable source of truth, versioned</small></div><div class=\"diagram-card layer\"><span class=\"diagram-pill\">3 &middot; updatedAt-gated reconcile</span><small class=\"diagram-muted\">agent edits propagate via the SQL bump</small></div><div class=\"diagram-card layer\"><span class=\"diagram-pill\">4 &middot; Lead-client election</span><small class=\"diagram-muted\">exactly one tab applies the snapshot</small></div><div class=\"diagram-card layer\"><span class=\"diagram-pill ok\">5 &middot; SSE fast-path + polling</span><small class=\"diagram-muted\">~tens of ms, degrades to 2s poll anywhere</small></div></div>",
42
+ "css": ".diagram-stack{display:flex;flex-direction:column;gap:8px}.diagram-stack .layer{display:flex;flex-direction:column;gap:4px;padding:12px 14px}"
43
+ }
44
+ ```
45
+
39
46
  ### 1. Yjs Y.Doc (CRDT layer)
40
47
 
41
48
  Each collaborative document is a `Y.Doc` containing shared types — usually a
@@ -93,24 +100,11 @@ cycle.
93
100
 
94
101
  Network errors use exponential backoff with jitter, capped at ~15 s.
95
102
 
96
- ```
97
- Browser Server SQL
98
- ────── ────── ───
99
- [Human edits]
100
- → Y.Doc update
101
- → debounce ~80ms
102
- → POST /collab/:docId/update ──────→ apply + persist
103
- emitCollabUpdate
104
- ← SSE push ──── poll-events stream
105
- ← Y.applyUpdate
106
-
107
- [Agent action]
108
- action writes SQL content + bumps updatedAt
109
- ← change-sync detects updatedAt bump
110
- ← lead client calls setContent
111
- → Y.Doc update
112
- → POST /collab/:docId/update ──────→ apply + persist
113
- ← SSE push to all other clients
103
+ ```an-diagram title="Two edit paths, one merge" summary="Human keystrokes flow Y.Doc → server → SSE. Agent edits go through SQL: the action bumps updatedAt, the lead client reconciles, then the change re-enters Yjs."
104
+ {
105
+ "html": "<div class=\"diagram-collab\"><div class=\"lane\"><span class=\"diagram-pill\">Human edit</span><div class=\"diagram-node\">Y.Doc update<br><small class=\"diagram-muted\">debounce ~80ms</small></div><span class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</span><div class=\"diagram-box\" data-rough>POST /update<br><small class=\"diagram-muted\">apply + persist</small></div><span class=\"diagram-arrow diagram-accent\" aria-hidden=\"true\">&rarr;</span><div class=\"diagram-box diagram-accent\">SSE push<br><small class=\"diagram-muted\">to all peers</small></div></div><div class=\"lane\"><span class=\"diagram-pill warn\">Agent edit</span><div class=\"diagram-node\">Action writes SQL<br><small class=\"diagram-muted\">bumps updatedAt</small></div><span class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&rarr;</span><div class=\"diagram-box\" data-rough>Lead client<br><small class=\"diagram-muted\">setContent into Y.Doc</small></div><span class=\"diagram-arrow diagram-accent\" aria-hidden=\"true\">&rarr;</span><div class=\"diagram-box diagram-accent\">POST /update<br><small class=\"diagram-muted\">re-enters Yjs &middot; SSE push</small></div></div></div>",
106
+ "css": ".diagram-collab{display:flex;flex-direction:column;gap:14px}.diagram-collab .lane{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.diagram-collab .diagram-arrow{font-size:22px;line-height:1}"
107
+ }
114
108
  ```
115
109
 
116
110
  ## Quickstart {#quickstart}
@@ -624,6 +618,13 @@ Key properties:
624
618
 
625
619
  ## Known limitations {#limitations}
626
620
 
621
+ ```an-callout
622
+ {
623
+ "tone": "risk",
624
+ "body": "**Same-region simultaneous rewrite is last-write-wins.** If the agent rewrites a passage while a human has unsaved edits in the *exact same region*, the lead-client snapshot can clobber the in-flight human edit. Edits in different regions always merge cleanly via the CRDT. For structured documents, use granular server-side merge to sidestep this entirely."
625
+ }
626
+ ```
627
+
627
628
  - **Same-region simultaneous rewrite is LWW** — If the agent rewrites a
628
629
  passage and a human has unsaved edits in the exact same region, the
629
630
  lead-client snapshot can overwrite the human's in-flight changes. Edits in
@@ -13,18 +13,18 @@ Jobs live in the [workspace](/docs/workspace) at `jobs/<name>.md` — just a Mar
13
13
 
14
14
  ## A job file {#job-file}
15
15
 
16
- ```markdown
17
- ---
18
- schedule: "0 7 * * *"
19
- enabled: true
20
- runAs: creator
21
- ---
22
-
23
- # Morning digest
24
-
25
- Summarize the emails received overnight. Group by sender domain.
26
- Pin the top 3 threads that look like they need a reply today to the
27
- "Needs reply" label. Draft replies for any that are obvious.
16
+ ```an-annotated-code title="jobs/morning-digest.md"
17
+ {
18
+ "filename": "jobs/morning-digest.md",
19
+ "language": "markdown",
20
+ "code": "---\nschedule: \"0 7 * * *\"\nenabled: true\nrunAs: creator\n---\n\n# Morning digest\n\nSummarize the emails received overnight. Group by sender domain.\nPin the top 3 threads that look like they need a reply today to the\n\"Needs reply\" label. Draft replies for any that are obvious.",
21
+ "annotations": [
22
+ { "lines": "2", "label": "When", "note": "Standard 5-field cron — `0 7 * * *` is every day at 07:00." },
23
+ { "lines": "3", "label": "Pause switch", "note": "Flip to `false` to stop the job without deleting it." },
24
+ { "lines": "4", "label": "Identity", "note": "`creator` runs with the owner's identity and `ANTHROPIC_API_KEY`; `shared` uses the org's key." },
25
+ { "lines": "7-12", "label": "The prompt", "note": "The body is just a prompt — the agent runs it at each firing with all its normal tools and workspace context." }
26
+ ]
27
+ }
28
28
  ```
29
29
 
30
30
  That's it. The body is a prompt the agent runs at each scheduled firing. The agent has access to all the same tools and workspace context it has in an interactive chat — actions, skills, memory, connected MCP servers, sub-agents.
@@ -93,15 +93,18 @@ Summarize overnight emails.`,
93
93
 
94
94
  ## How the scheduler runs {#how-scheduler-runs}
95
95
 
96
- The scheduler is a framework plugin (the internal `processRecurringJobs()` routine). On each tick it:
96
+ The scheduler is a framework plugin (the internal `processRecurringJobs()` routine) that runs in-process: a `setInterval` fires every 60 seconds (with a 10-second startup delay) inside the agent chat plugin, wherever the server is running.
97
97
 
98
- 1. Lists every enabled `jobs/*.md` resource across all owners.
99
- 2. Compares `nextRun` to the current time.
100
- 3. For each due job, spins up a fresh agent thread with the job body as the user message.
101
- 4. The agent runs its loop — calling actions, writing to SQL, sending A2A messages, emailing, whatever the prompt asks for.
102
- 5. On completion, writes `lastRun`, `lastStatus`, `lastError`, and recomputes `nextRun` from the cron.
98
+ ```an-diagram title="One scheduler tick" summary="Every 60s the scheduler finds due jobs, runs each as a fresh agent thread, and writes the outcome back to the job file."
99
+ {
100
+ "html": "<div class=\"sched\"><div class=\"diagram-box accent\"><code>setInterval</code> &bull; 60s &#8635;</div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-card\"><span class=\"diagram-pill\">1 &middot; scan</span><small class=\"diagram-muted\">list every enabled <code>jobs/*.md</code> across all owners</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-card\"><span class=\"diagram-pill\">2 &middot; due?</span><small class=\"diagram-muted\">compare <code>nextRun</code> to now</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-card accent\"><span class=\"diagram-pill accent\">3 &middot; run</span><small class=\"diagram-muted\">fresh agent thread, job body as the user message &mdash; actions, SQL, A2A, email</small></div><div class=\"diagram-arrow diagram-muted\" aria-hidden=\"true\">&darr;</div><div class=\"diagram-card ok\"><span class=\"diagram-pill ok\">4 &middot; record</span><small class=\"diagram-muted\">write <code>lastRun</code> / <code>lastStatus</code> / <code>lastError</code>, recompute <code>nextRun</code></small></div></div>",
101
+ "css": ".sched{display:flex;flex-direction:column;gap:6px;max-width:520px}.sched .diagram-card{display:flex;flex-direction:column;gap:2px;padding:10px 14px}.sched .diagram-box{align-self:flex-start}.sched .diagram-arrow{font-size:18px;align-self:center}"
102
+ }
103
+ ```
103
104
 
104
- The scheduler runs in-process: a `setInterval` fires every 60 seconds (with a 10-second startup delay) inside the agent chat plugin, wherever the server is running. On scale-to-zero serverless hosts, jobs only fire while an instance is warm — if reliable scheduling matters, keep an instance warm with keep-alive pings or use an always-on host (Fly, Render, a VPS).
105
+ ```an-callout
106
+ { "tone": "risk", "body": "**Scale-to-zero caveat.** The scheduler is in-process, so on serverless hosts jobs only fire while an instance is warm. If reliable scheduling matters, keep an instance warm with keep-alive pings or use an always-on host (Fly, Render, a VPS)." }
107
+ ```
105
108
 
106
109
  ## Debugging a job {#debugging}
107
110
 
@@ -21,6 +21,14 @@ Agent-native apps use **React Router v7** with file-based routing via `flatRoute
21
21
 
22
22
  Prefix a segment with `$` for a dynamic param. Prefix with `_` to make it a pathless layout route (no URL segment). Templates use `flatRoutes()` — the dot-notation file above is primary; the nested-folder form `inbox/route.tsx` also works.
23
23
 
24
+ ```an-diagram title="Pathless layout wraps the pages" summary="A _app.tsx layout (no URL segment) renders the shared shell once; matched pages render inside its <Outlet/>, so the agent sidebar never remounts on navigation."
25
+ {
26
+ "html": "<div class=\"diagram-layout\" data-rough><div class=\"diagram-shell\"><span class=\"diagram-pill accent\">_app.tsx</span><small class=\"diagram-muted\">pathless layout · persistent shell + agent sidebar</small><div class=\"diagram-outlet\" data-rough><small class=\"diagram-muted\">&lt;Outlet/&gt; — the matched page</small><div class=\"diagram-row\"><span class=\"diagram-pill\">_index.tsx &rarr; /</span><span class=\"diagram-pill\">settings.tsx &rarr; /settings</span><span class=\"diagram-pill\">inbox.$threadId.tsx &rarr; /inbox/:threadId</span></div></div></div></div>",
27
+ "css": ".diagram-layout .diagram-shell{display:flex;flex-direction:column;gap:8px;padding:16px}.diagram-layout .diagram-outlet{display:flex;flex-direction:column;gap:8px;padding:14px;margin-top:6px}.diagram-layout .diagram-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:4px}"
28
+ }
29
+
30
+ ```
31
+
24
32
  ## Adding a new page {#adding-a-page}
25
33
 
26
34
  Create the file and export a default component:
@@ -40,10 +40,29 @@ The default backend spawns a locked-down local Node child process. That's bounde
40
40
 
41
41
  Keeping the contract narrow means a remote adapter inherits the same security posture. The parent process keeps ownership of everything secret-bearing: it builds the sandbox module, runs the localhost bridge (which holds the request context and applies host allowlists + SSRF guards), scrubs the env, and formats output. An adapter only receives an already-prepared, **non-secret** module source plus resource limits — it is responsible solely for _running_ it and capturing stdout/stderr/exit status.
42
42
 
43
+ ```an-diagram title="The parent keeps the secrets; the adapter only runs code" summary="run-code builds the module and runs the loopback bridge; the adapter receives a non-secret module + limits and returns stdout/stderr/exit."
44
+ {
45
+ "html": "<div class=\"diagram-sandbox\"><div class=\"diagram-box\" data-rough><strong>Parent process</strong><small class=\"diagram-muted\">builds module · loopback bridge · env scrub · output format</small></div><div class=\"diagram-col\"><div class=\"diagram-pill accent\">non-secret module + limits &rarr;</div><div class=\"diagram-pill ok\">&larr; stdout / stderr / exitCode</div><div class=\"diagram-pill\">&harr; bridge calls (127.0.0.1)</div></div><div class=\"diagram-panel center\" data-rough><strong>SandboxAdapter.run</strong><small class=\"diagram-muted\">local child · Docker · remote · durable</small></div></div>",
46
+ "css": ".diagram-sandbox{display:flex;align-items:center;gap:14px;flex-wrap:wrap}.diagram-sandbox .diagram-col{display:flex;flex-direction:column;gap:8px}.diagram-sandbox .center{display:flex;flex-direction:column;align-items:center;gap:4px}"
47
+ }
48
+ ```
49
+
43
50
  ## The interface {#interface}
44
51
 
45
52
  The seam lives in core at `packages/core/src/coding-tools/sandbox/` — `adapter.ts` (the contract), `index.ts` (selection: `getSandboxAdapter()` / `registerSandboxAdapter()`), and `local-child-process-adapter.ts` (the default). It is wired in-package by `run-code.ts`; a host plugs in a different backend through the `index.ts` registration helper (or, for a Docker backend, via the [blueprint](/docs/blueprint-installer) that edits these files directly).
46
53
 
54
+ ```an-file-tree title="The sandbox seam in core"
55
+ {
56
+ "title": "packages/core/src/coding-tools/sandbox/",
57
+ "entries": [
58
+ { "path": "adapter.ts", "note": "the SandboxAdapter contract (SandboxRunRequest / SandboxRunResult)" },
59
+ { "path": "index.ts", "note": "selection: getSandboxAdapter() / registerSandboxAdapter()" },
60
+ { "path": "local-child-process-adapter.ts", "note": "the default backend — locked-down Node child process" },
61
+ { "path": "../run-code.ts", "note": "wires the seam; never changes when you swap backends" }
62
+ ]
63
+ }
64
+ ```
65
+
47
66
  Every backend implements `SandboxAdapter`:
48
67
 
49
68
  ```ts