@vellumai/assistant 0.4.11 → 0.4.13

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 (111) hide show
  1. package/ARCHITECTURE.md +401 -385
  2. package/package.json +1 -1
  3. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +75 -61
  4. package/src/__tests__/registry.test.ts +235 -187
  5. package/src/__tests__/secure-keys.test.ts +27 -0
  6. package/src/__tests__/session-agent-loop.test.ts +521 -256
  7. package/src/__tests__/session-surfaces-task-progress.test.ts +1 -0
  8. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  9. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  10. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  11. package/src/__tests__/skills.test.ts +334 -276
  12. package/src/__tests__/slack-skill.test.ts +124 -0
  13. package/src/__tests__/starter-task-flow.test.ts +7 -17
  14. package/src/agent/loop.ts +10 -3
  15. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +449 -0
  16. package/src/config/bundled-skills/doordash/SKILL.md +171 -0
  17. package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +203 -0
  18. package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +164 -0
  19. package/src/config/bundled-skills/doordash/doordash-cli.ts +1193 -0
  20. package/src/config/bundled-skills/doordash/doordash-entry.ts +22 -0
  21. package/src/config/bundled-skills/doordash/lib/cart-queries.ts +787 -0
  22. package/src/config/bundled-skills/doordash/lib/client.ts +1071 -0
  23. package/src/config/bundled-skills/doordash/lib/order-queries.ts +85 -0
  24. package/src/config/bundled-skills/doordash/lib/queries.ts +28 -0
  25. package/src/config/bundled-skills/doordash/lib/query-extractor.ts +94 -0
  26. package/src/config/bundled-skills/doordash/lib/search-queries.ts +203 -0
  27. package/src/config/bundled-skills/doordash/lib/session.ts +93 -0
  28. package/src/config/bundled-skills/doordash/lib/shared/errors.ts +61 -0
  29. package/src/config/bundled-skills/doordash/lib/shared/ipc.ts +32 -0
  30. package/src/config/bundled-skills/doordash/lib/shared/network-recorder.ts +380 -0
  31. package/src/config/bundled-skills/doordash/lib/shared/platform.ts +35 -0
  32. package/src/config/bundled-skills/doordash/lib/shared/recording-store.ts +43 -0
  33. package/src/config/bundled-skills/doordash/lib/shared/recording-types.ts +49 -0
  34. package/src/config/bundled-skills/doordash/lib/shared/truncate.ts +6 -0
  35. package/src/config/bundled-skills/doordash/lib/store-queries.ts +246 -0
  36. package/src/config/bundled-skills/doordash/lib/types.ts +367 -0
  37. package/src/config/bundled-skills/google-calendar/SKILL.md +4 -5
  38. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +41 -41
  39. package/src/config/bundled-skills/messaging/SKILL.md +59 -42
  40. package/src/config/bundled-skills/messaging/TOOLS.json +14 -92
  41. package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +5 -1
  42. package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +11 -2
  43. package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +8 -1
  44. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +12 -4
  45. package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +5 -1
  46. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +5 -1
  47. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +5 -2
  48. package/src/config/bundled-skills/notion/SKILL.md +240 -0
  49. package/src/config/bundled-skills/notion-oauth-setup/SKILL.md +127 -0
  50. package/src/config/bundled-skills/oauth-setup/SKILL.md +144 -0
  51. package/src/config/bundled-skills/phone-calls/SKILL.md +76 -45
  52. package/src/config/bundled-skills/skills-catalog/SKILL.md +32 -29
  53. package/src/config/bundled-skills/slack/SKILL.md +49 -0
  54. package/src/config/bundled-skills/slack/TOOLS.json +167 -0
  55. package/src/config/bundled-skills/slack/tools/shared.ts +23 -0
  56. package/src/config/bundled-skills/{messaging → slack}/tools/slack-add-reaction.ts +2 -5
  57. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +33 -0
  58. package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +75 -0
  59. package/src/config/bundled-skills/{messaging → slack}/tools/slack-delete-message.ts +2 -5
  60. package/src/config/bundled-skills/{messaging → slack}/tools/slack-leave-channel.ts +2 -5
  61. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +193 -0
  62. package/src/config/{vellum-skills → bundled-skills}/sms-setup/SKILL.md +29 -22
  63. package/src/config/{vellum-skills → bundled-skills}/telegram-setup/SKILL.md +17 -14
  64. package/src/config/{vellum-skills → bundled-skills}/twilio-setup/SKILL.md +20 -5
  65. package/src/config/bundled-tool-registry.ts +292 -267
  66. package/src/config/schema.ts +1 -1
  67. package/src/daemon/handlers/skills.ts +334 -234
  68. package/src/daemon/ipc-contract/messages.ts +2 -0
  69. package/src/daemon/ipc-contract/surfaces.ts +2 -0
  70. package/src/daemon/lifecycle.ts +358 -221
  71. package/src/daemon/response-tier.ts +2 -0
  72. package/src/daemon/server.ts +453 -193
  73. package/src/daemon/session-agent-loop-handlers.ts +43 -2
  74. package/src/daemon/session-agent-loop.ts +3 -0
  75. package/src/daemon/session-lifecycle.ts +3 -0
  76. package/src/daemon/session-process.ts +1 -0
  77. package/src/daemon/session-surfaces.ts +22 -20
  78. package/src/daemon/session-tool-setup.ts +1 -0
  79. package/src/daemon/session.ts +5 -2
  80. package/src/messaging/outreach-classifier.ts +12 -5
  81. package/src/messaging/provider-types.ts +5 -0
  82. package/src/messaging/provider.ts +1 -1
  83. package/src/messaging/providers/gmail/adapter.ts +11 -5
  84. package/src/messaging/providers/gmail/client.ts +2 -0
  85. package/src/messaging/providers/slack/adapter.ts +1 -0
  86. package/src/messaging/providers/slack/client.ts +8 -0
  87. package/src/messaging/providers/slack/types.ts +5 -0
  88. package/src/runtime/http-errors.ts +33 -20
  89. package/src/runtime/http-server.ts +706 -291
  90. package/src/runtime/http-types.ts +26 -16
  91. package/src/runtime/routes/secret-routes.ts +57 -2
  92. package/src/runtime/routes/surface-action-routes.ts +66 -0
  93. package/src/runtime/routes/trust-rules-routes.ts +140 -0
  94. package/src/security/keychain-to-encrypted-migration.ts +59 -0
  95. package/src/security/secure-keys.ts +17 -0
  96. package/src/skills/frontmatter.ts +9 -7
  97. package/src/tools/apps/executors.ts +2 -1
  98. package/src/tools/tool-manifest.ts +44 -42
  99. package/src/tools/types.ts +9 -0
  100. package/src/__tests__/skill-mirror-parity.test.ts +0 -176
  101. package/src/config/vellum-skills/catalog.json +0 -63
  102. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +0 -295
  103. package/src/skills/vellum-catalog-remote.ts +0 -166
  104. package/src/tools/skills/vellum-catalog.ts +0 -168
  105. /package/src/config/{vellum-skills → bundled-skills}/chatgpt-import/SKILL.md +0 -0
  106. /package/src/config/{vellum-skills → bundled-skills}/chatgpt-import/TOOLS.json +0 -0
  107. /package/src/config/{vellum-skills → bundled-skills}/deploy-fullstack-vercel/SKILL.md +0 -0
  108. /package/src/config/{vellum-skills → bundled-skills}/document-writer/SKILL.md +0 -0
  109. /package/src/config/{vellum-skills → bundled-skills}/guardian-verify-setup/SKILL.md +0 -0
  110. /package/src/config/{vellum-skills → bundled-skills}/slack-oauth-setup/SKILL.md +0 -0
  111. /package/src/config/{vellum-skills → bundled-skills}/trusted-contacts/SKILL.md +0 -0
package/ARCHITECTURE.md CHANGED
@@ -62,19 +62,19 @@ Refresh tokens provide a rotating credential renewal mechanism that avoids re-bo
62
62
 
63
63
  **Key source files:**
64
64
 
65
- | File | Purpose |
66
- |------|---------|
67
- | `src/runtime/actor-token-service.ts` | HMAC-SHA256 mint/verify, signing key management, `hashToken` |
68
- | `src/runtime/actor-token-store.ts` | Hash-only persistence: create, find by hash/device binding, revoke |
69
- | `src/runtime/actor-refresh-token-service.ts` | Refresh token rotation, replay detection, family revocation |
70
- | `src/runtime/actor-refresh-token-store.ts` | Refresh token hash-only persistence: create, find, rotate, revoke by family |
71
- | `src/runtime/middleware/actor-token.ts` | HTTP middleware: `verifyHttpActorToken`, `verifyHttpActorTokenWithLocalFallback`, `isActorBoundGuardian` |
72
- | `src/runtime/local-actor-identity.ts` | `resolveLocalIpcGuardianContext` — deterministic IPC identity |
73
- | `src/runtime/guardian-vellum-migration.ts` | `ensureVellumGuardianBinding` — startup binding backfill |
74
- | `src/runtime/routes/guardian-bootstrap-routes.ts` | `POST /v1/integrations/guardian/vellum/bootstrap` handler (initial issuance only) |
75
- | `src/runtime/routes/guardian-refresh-routes.ts` | `POST /v1/integrations/guardian/vellum/refresh` handler (token rotation) |
76
- | `src/runtime/routes/pairing-routes.ts` | `mintPairingActorToken` — actor token + refresh token in pairing response |
77
- | `src/memory/guardian-bindings.ts` | Guardian binding persistence (shared across all channels) |
65
+ | File | Purpose |
66
+ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
67
+ | `src/runtime/actor-token-service.ts` | HMAC-SHA256 mint/verify, signing key management, `hashToken` |
68
+ | `src/runtime/actor-token-store.ts` | Hash-only persistence: create, find by hash/device binding, revoke |
69
+ | `src/runtime/actor-refresh-token-service.ts` | Refresh token rotation, replay detection, family revocation |
70
+ | `src/runtime/actor-refresh-token-store.ts` | Refresh token hash-only persistence: create, find, rotate, revoke by family |
71
+ | `src/runtime/middleware/actor-token.ts` | HTTP middleware: `verifyHttpActorToken`, `verifyHttpActorTokenWithLocalFallback`, `isActorBoundGuardian` |
72
+ | `src/runtime/local-actor-identity.ts` | `resolveLocalIpcGuardianContext` — deterministic IPC identity |
73
+ | `src/runtime/guardian-vellum-migration.ts` | `ensureVellumGuardianBinding` — startup binding backfill |
74
+ | `src/runtime/routes/guardian-bootstrap-routes.ts` | `POST /v1/integrations/guardian/vellum/bootstrap` handler (initial issuance only) |
75
+ | `src/runtime/routes/guardian-refresh-routes.ts` | `POST /v1/integrations/guardian/vellum/refresh` handler (token rotation) |
76
+ | `src/runtime/routes/pairing-routes.ts` | `mintPairingActorToken` — actor token + refresh token in pairing response |
77
+ | `src/memory/guardian-bindings.ts` | Guardian binding persistence (shared across all channels) |
78
78
 
79
79
  ### Channel-Agnostic Scoped Approval Grants
80
80
 
@@ -86,10 +86,10 @@ All guardian approval decisions — regardless of how they arrive — route thro
86
86
 
87
87
  **Core API:**
88
88
 
89
- | Function | Purpose |
90
- |----------|---------|
91
- | `applyGuardianDecision(params)` | Apply a guardian decision atomically: downgrade `approve_always` for guardian-on-behalf requests, capture approval info, resolve the pending interaction, update the approval record, and mint a scoped grant on approve. Returns `{ applied, reason?, requestId? }`. |
92
- | `listGuardianDecisionPrompts({ conversationId })` | List pending prompts for a conversation, aggregating channel guardian approval requests and pending confirmation interactions into a uniform `GuardianDecisionPrompt` shape. |
89
+ | Function | Purpose |
90
+ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
91
+ | `applyGuardianDecision(params)` | Apply a guardian decision atomically: downgrade `approve_always` for guardian-on-behalf requests, capture approval info, resolve the pending interaction, update the approval record, and mint a scoped grant on approve. Returns `{ applied, reason?, requestId? }`. |
92
+ | `listGuardianDecisionPrompts({ conversationId })` | List pending prompts for a conversation, aggregating channel guardian approval requests and pending confirmation interactions into a uniform `GuardianDecisionPrompt` shape. |
93
93
 
94
94
  **Security invariants enforced by the primitive:**
95
95
 
@@ -107,14 +107,14 @@ All guardian approval decisions — regardless of how they arrive — route thro
107
107
 
108
108
  **Key source files:**
109
109
 
110
- | File | Purpose |
111
- |------|---------|
112
- | `src/approvals/guardian-decision-primitive.ts` | Unified decision application: downgrade, approval info capture, `handleChannelDecision`, record update, grant minting |
113
- | `src/runtime/guardian-decision-types.ts` | Shared types: `GuardianDecisionPrompt`, `GuardianDecisionAction`, `buildDecisionActions`, `buildPlainTextFallback`, `ApplyGuardianDecisionResult` |
114
- | `src/runtime/routes/guardian-action-routes.ts` | HTTP route handlers for `GET /v1/guardian-actions/pending` and `POST /v1/guardian-actions/decision` |
115
- | `src/daemon/handlers/guardian-actions.ts` | IPC handlers wrapping the same logic for desktop socket clients |
116
- | `src/daemon/ipc-contract/guardian-actions.ts` | IPC message type definitions for guardian action requests/responses |
117
- | `src/runtime/channel-approval-types.ts` | Channel-facing approval action types and `toApprovalActionOptions` bridge |
110
+ | File | Purpose |
111
+ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
112
+ | `src/approvals/guardian-decision-primitive.ts` | Unified decision application: downgrade, approval info capture, `handleChannelDecision`, record update, grant minting |
113
+ | `src/runtime/guardian-decision-types.ts` | Shared types: `GuardianDecisionPrompt`, `GuardianDecisionAction`, `buildDecisionActions`, `buildPlainTextFallback`, `ApplyGuardianDecisionResult` |
114
+ | `src/runtime/routes/guardian-action-routes.ts` | HTTP route handlers for `GET /v1/guardian-actions/pending` and `POST /v1/guardian-actions/decision` |
115
+ | `src/daemon/handlers/guardian-actions.ts` | IPC handlers wrapping the same logic for desktop socket clients |
116
+ | `src/daemon/ipc-contract/guardian-actions.ts` | IPC message type definitions for guardian action requests/responses |
117
+ | `src/runtime/channel-approval-types.ts` | Channel-facing approval action types and `toApprovalActionOptions` bridge |
118
118
 
119
119
  ### Canonical Guardian Request System
120
120
 
@@ -135,21 +135,22 @@ The canonical guardian request system provides a channel-agnostic, unified domai
135
135
  **Resolver registry:** Kind-specific resolvers (`src/approvals/guardian-request-resolvers.ts`) handle side effects after CAS resolution. Built-in resolvers: `tool_approval` (channel/desktop approval path) and `pending_question` (voice call question path). New request kinds register resolvers without touching the core primitive.
136
136
 
137
137
  **Expiry sweeps:** Three complementary sweeps run on 60-second intervals to clean up stale requests:
138
+
138
139
  - `src/calls/guardian-action-sweep.ts` — voice call guardian action requests
139
140
  - `src/runtime/routes/guardian-expiry-sweep.ts` — channel guardian approval requests
140
141
  - `src/runtime/routes/canonical-guardian-expiry-sweep.ts` — canonical guardian requests (CAS-safe)
141
142
 
142
143
  **Key source files:**
143
144
 
144
- | File | Purpose |
145
- |------|---------|
146
- | `src/memory/canonical-guardian-store.ts` | Canonical request and delivery persistence (CRUD, CAS resolve, list with filters) |
147
- | `src/approvals/guardian-decision-primitive.ts` | Unified decision primitive: `applyCanonicalGuardianDecision` (canonical) and `applyGuardianDecision` (legacy) |
148
- | `src/approvals/guardian-request-resolvers.ts` | Resolver registry: kind-specific side-effect dispatch after CAS resolution |
149
- | `src/runtime/guardian-reply-router.ts` | Shared inbound router: callback -> code -> NL classification pipeline |
150
- | `src/runtime/routes/guardian-action-routes.ts` | HTTP endpoints for prompt listing and decision submission |
151
- | `src/daemon/handlers/guardian-actions.ts` | IPC handlers for desktop socket clients |
152
- | `src/runtime/routes/canonical-guardian-expiry-sweep.ts` | Canonical request expiry sweep |
145
+ | File | Purpose |
146
+ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
147
+ | `src/memory/canonical-guardian-store.ts` | Canonical request and delivery persistence (CRUD, CAS resolve, list with filters) |
148
+ | `src/approvals/guardian-decision-primitive.ts` | Unified decision primitive: `applyCanonicalGuardianDecision` (canonical) and `applyGuardianDecision` (legacy) |
149
+ | `src/approvals/guardian-request-resolvers.ts` | Resolver registry: kind-specific side-effect dispatch after CAS resolution |
150
+ | `src/runtime/guardian-reply-router.ts` | Shared inbound router: callback -> code -> NL classification pipeline |
151
+ | `src/runtime/routes/guardian-action-routes.ts` | HTTP endpoints for prompt listing and decision submission |
152
+ | `src/daemon/handlers/guardian-actions.ts` | IPC handlers for desktop socket clients |
153
+ | `src/runtime/routes/canonical-guardian-expiry-sweep.ts` | Canonical request expiry sweep |
153
154
 
154
155
  ### Outbound Guardian Verification (HTTP Endpoints)
155
156
 
@@ -157,11 +158,11 @@ Guardian verification can be initiated through gateway HTTP endpoints (which for
157
158
 
158
159
  **HTTP Endpoints:**
159
160
 
160
- | Endpoint | Method | Description |
161
- |----------|--------|-------------|
162
- | `/v1/integrations/guardian/outbound/start` | POST | Start a new outbound verification session. Body: `{ channel, destination?, assistantId?, rebind? }` |
163
- | `/v1/integrations/guardian/outbound/resend` | POST | Resend the verification code for an active session. Body: `{ channel, assistantId? }` |
164
- | `/v1/integrations/guardian/outbound/cancel` | POST | Cancel an active outbound verification session. Body: `{ channel, assistantId? }` |
161
+ | Endpoint | Method | Description |
162
+ | ------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------- |
163
+ | `/v1/integrations/guardian/outbound/start` | POST | Start a new outbound verification session. Body: `{ channel, destination?, assistantId?, rebind? }` |
164
+ | `/v1/integrations/guardian/outbound/resend` | POST | Resend the verification code for an active session. Body: `{ channel, assistantId? }` |
165
+ | `/v1/integrations/guardian/outbound/cancel` | POST | Cancel an active outbound verification session. Body: `{ channel, assistantId? }` |
165
166
 
166
167
  All endpoints are bearer-authenticated via the gateway token (`~/.vellum/http-token` in local setups). Skills and user-facing tooling should target the gateway URL (default `http://localhost:7830`), not the runtime port.
167
168
 
@@ -179,12 +180,12 @@ The HTTP route handlers (`integration-routes.ts`) and the legacy IPC handlers (`
179
180
 
180
181
  **Key Source Files:**
181
182
 
182
- | File | Purpose |
183
- |------|---------|
184
- | `src/runtime/guardian-outbound-actions.ts` | Shared business logic for start/resend/cancel outbound verification |
185
- | `src/runtime/routes/integration-routes.ts` | HTTP route handlers for `/v1/integrations/guardian/outbound/*` |
186
- | `src/daemon/handlers/config-channels.ts` | IPC handler that delegates to the same shared actions |
187
- | `src/config/vellum-skills/guardian-verify-setup/SKILL.md` | Skill that teaches the assistant how to orchestrate guardian verification via chat |
183
+ | File | Purpose |
184
+ | ---------------------------------------------------------- | ---------------------------------------------------------------------------------- |
185
+ | `src/runtime/guardian-outbound-actions.ts` | Shared business logic for start/resend/cancel outbound verification |
186
+ | `src/runtime/routes/integration-routes.ts` | HTTP route handlers for `/v1/integrations/guardian/outbound/*` |
187
+ | `src/daemon/handlers/config-channels.ts` | IPC handler that delegates to the same shared actions |
188
+ | `src/config/bundled-skills/guardian-verify-setup/SKILL.md` | Skill that teaches the assistant how to orchestrate guardian verification via chat |
188
189
 
189
190
  **Guardian-Only Tool Invocation Gate:**
190
191
 
@@ -232,24 +233,25 @@ When a voice call's ASK_GUARDIAN consultation times out before the guardian resp
232
233
 
233
234
  **Key source files:**
234
235
 
235
- | File | Purpose |
236
- |------|---------|
237
- | `src/memory/guardian-action-store.ts` | Follow-up state machine with atomic transitions (`startFollowupFromExpiredRequest`, `progressFollowupState`, `finalizeFollowup`) and query helpers for pending/expired/follow-up deliveries |
238
- | `src/runtime/guardian-action-message-composer.ts` | 2-tier text generation: daemon-injected LLM generator with deterministic fallback templates. Covers all scenarios from timeout acknowledgment through follow-up completion |
239
- | `src/runtime/guardian-action-conversation-turn.ts` | Follow-up decision engine: classifies guardian replies into `call_back`, `message_back`, `decline`, or `keep_pending` dispositions using LLM tool calling |
240
- | `src/runtime/guardian-action-followup-executor.ts` | Action dispatch: resolves counterparty from call session, executes `message_back` (SMS via gateway) or `call_back` (outbound call via `startCall`), finalizes follow-up state |
241
- | `src/daemon/guardian-action-generators.ts` | Daemon-injected generator factories: `createGuardianActionCopyGenerator` (latency-optimized text rewriting) and `createGuardianFollowUpConversationGenerator` (tool-calling intent classification) |
242
- | `src/calls/call-controller.ts` | Voice timeout handling: marks requests as timed out, sends expiry notices, injects `[GUARDIAN_TIMEOUT]` instruction for generated voice response |
243
- | `src/runtime/routes/inbound-message-handler.ts` | Late reply interception for Telegram/SMS channels: matches late answers to expired requests, routes follow-up conversation turns, dispatches actions |
244
- | `src/daemon/session-process.ts` | Late reply interception for mac/IPC channel: same logic as inbound-message-handler but using conversation-ID-based delivery lookup |
245
- | `src/calls/guardian-action-sweep.ts` | Periodic sweep for stale pending requests; sends expiry notices to guardian destinations |
246
- | `src/memory/migrations/030-guardian-action-followup.ts` | Schema migration adding follow-up columns (`followup_state`, `late_answer_text`, `late_answered_at`, `followup_action`, `followup_completed_at`) |
236
+ | File | Purpose |
237
+ | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
238
+ | `src/memory/guardian-action-store.ts` | Follow-up state machine with atomic transitions (`startFollowupFromExpiredRequest`, `progressFollowupState`, `finalizeFollowup`) and query helpers for pending/expired/follow-up deliveries |
239
+ | `src/runtime/guardian-action-message-composer.ts` | 2-tier text generation: daemon-injected LLM generator with deterministic fallback templates. Covers all scenarios from timeout acknowledgment through follow-up completion |
240
+ | `src/runtime/guardian-action-conversation-turn.ts` | Follow-up decision engine: classifies guardian replies into `call_back`, `message_back`, `decline`, or `keep_pending` dispositions using LLM tool calling |
241
+ | `src/runtime/guardian-action-followup-executor.ts` | Action dispatch: resolves counterparty from call session, executes `message_back` (SMS via gateway) or `call_back` (outbound call via `startCall`), finalizes follow-up state |
242
+ | `src/daemon/guardian-action-generators.ts` | Daemon-injected generator factories: `createGuardianActionCopyGenerator` (latency-optimized text rewriting) and `createGuardianFollowUpConversationGenerator` (tool-calling intent classification) |
243
+ | `src/calls/call-controller.ts` | Voice timeout handling: marks requests as timed out, sends expiry notices, injects `[GUARDIAN_TIMEOUT]` instruction for generated voice response |
244
+ | `src/runtime/routes/inbound-message-handler.ts` | Late reply interception for Telegram/SMS channels: matches late answers to expired requests, routes follow-up conversation turns, dispatches actions |
245
+ | `src/daemon/session-process.ts` | Late reply interception for mac/IPC channel: same logic as inbound-message-handler but using conversation-ID-based delivery lookup |
246
+ | `src/calls/guardian-action-sweep.ts` | Periodic sweep for stale pending requests; sends expiry notices to guardian destinations |
247
+ | `src/memory/migrations/030-guardian-action-followup.ts` | Schema migration adding follow-up columns (`followup_state`, `late_answer_text`, `late_answered_at`, `followup_action`, `followup_completed_at`) |
247
248
 
248
249
  ### SMS Channel (Twilio)
249
250
 
250
251
  The SMS channel provides text-only messaging via Twilio, sharing the same telephony provider as voice calls. It follows the same ingress/egress pattern as Telegram but uses Twilio's HMAC-SHA1 signature validation instead of a secret header.
251
252
 
252
253
  **Ingress** (`POST /webhooks/twilio/sms`):
254
+
253
255
  1. Twilio delivers an inbound SMS as a form-encoded POST to the gateway.
254
256
  2. The gateway validates the `X-Twilio-Signature` header using HMAC-SHA1 with the Twilio Auth Token against the canonical request URL (reconstructed from `INGRESS_PUBLIC_BASE_URL` when behind a tunnel).
255
257
  3. `MessageSid` deduplication prevents reprocessing retried webhooks.
@@ -260,6 +262,7 @@ The SMS channel provides text-only messaging via Twilio, sharing the same teleph
260
262
  8. The event is forwarded to the runtime via `POST /channels/inbound`, including SMS-specific transport hints (`chat-first-medium`, `sms-character-limits`, etc.) and a `replyCallbackUrl` pointing to `/deliver/sms`.
261
263
 
262
264
  **Egress** (`POST /deliver/sms`):
265
+
263
266
  1. The runtime calls the gateway's `/deliver/sms` endpoint with `{ to, text }` or `{ chatId, text }`. The `chatId` field is an alias for `to`, allowing the runtime channel callback (which sends `{ chatId, text }`) to work without translation. When both `to` and `chatId` are provided, `to` takes precedence.
264
267
  2. The gateway authenticates the request via bearer token (same fail-closed model as `/deliver/telegram`).
265
268
  3. The gateway sends the SMS via the Twilio Messages API using the configured `TWILIO_PHONE_NUMBER` as the `From` number.
@@ -271,6 +274,7 @@ The SMS channel provides text-only messaging via Twilio, sharing the same teleph
271
274
  **Credential Clearing Semantics**: `clear_credentials` removes only the authentication credentials (Account SID and Auth Token) from secure storage. The phone number is preserved in both the config file (`sms.phoneNumber`) and the secure key (`credential:twilio:phone_number`) so that re-entering credentials resumes working without needing to reassign the number.
272
275
 
273
276
  **Webhook Lifecycle**: Twilio webhook URLs are managed through a shared `syncTwilioWebhooks` helper in `config.ts` that computes voice, status-callback, and SMS URLs from the ingress config and pushes them to Twilio. Webhooks are synchronized at three points:
277
+
274
278
  1. **Number provisioning** (`provision_number`) — immediately after purchasing a number.
275
279
  2. **Number assignment** (`assign_number`) — when an existing number is assigned to the assistant.
276
280
  3. **Ingress URL change** (`ingress_config` set) — when the public ingress URL is updated or enabled, the daemon automatically re-synchronizes Twilio webhooks (fire-and-forget) if credentials and an assigned number are present. This ensures tunnel URL changes (e.g., ngrok restart) propagate without manual re-assignment.
@@ -295,12 +299,14 @@ The WhatsApp channel enables inbound and outbound messaging via the Meta WhatsAp
295
299
  8. The event is forwarded to the runtime via `POST /channels/inbound` with WhatsApp-specific transport hints and a `replyCallbackUrl` pointing to `/deliver/whatsapp`.
296
300
 
297
301
  **Egress** (`POST /deliver/whatsapp`):
302
+
298
303
  1. The runtime calls the gateway's `/deliver/whatsapp` endpoint with `{ to, text }` or `{ chatId, text }` (alias).
299
304
  2. The gateway authenticates the request via bearer token (same fail-closed model as other deliver endpoints).
300
305
  3. The gateway sends the message via the WhatsApp Cloud API `/{phoneNumberId}/messages` endpoint using the configured access token.
301
306
  4. Text is split at 4096 characters if needed.
302
307
 
303
308
  **Required credentials**:
309
+
304
310
  - `WHATSAPP_PHONE_NUMBER_ID` — the numeric WhatsApp Business phone number ID from Meta
305
311
  - `WHATSAPP_ACCESS_TOKEN` — System User or temporary access token
306
312
  - `WHATSAPP_APP_SECRET` — App secret for webhook signature verification
@@ -320,11 +326,11 @@ The Slack channel provides text-based messaging via Slack's Socket Mode API. Unl
320
326
 
321
327
  **Control-plane endpoints** (`/v1/integrations/slack/channel/config`):
322
328
 
323
- | Endpoint | Method | Description |
324
- |----------|--------|-------------|
325
- | `/v1/integrations/slack/channel/config` | GET | Returns current config status: `hasBotToken`, `hasAppToken`, `connected`, plus workspace metadata (`teamId`, `teamName`, `botUserId`, `botUsername`) |
326
- | `/v1/integrations/slack/channel/config` | POST | Validates and stores credentials. Body: `{ botToken?: string, appToken?: string }` |
327
- | `/v1/integrations/slack/channel/config` | DELETE | Clears all Slack channel credentials from secure storage and credential metadata |
329
+ | Endpoint | Method | Description |
330
+ | --------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
331
+ | `/v1/integrations/slack/channel/config` | GET | Returns current config status: `hasBotToken`, `hasAppToken`, `connected`, plus workspace metadata (`teamId`, `teamName`, `botUserId`, `botUsername`) |
332
+ | `/v1/integrations/slack/channel/config` | POST | Validates and stores credentials. Body: `{ botToken?: string, appToken?: string }` |
333
+ | `/v1/integrations/slack/channel/config` | DELETE | Clears all Slack channel credentials from secure storage and credential metadata |
328
334
 
329
335
  All endpoints are bearer-authenticated via the runtime HTTP token (`~/.vellum/http-token`).
330
336
 
@@ -332,9 +338,9 @@ All endpoints are bearer-authenticated via the runtime HTTP token (`~/.vellum/ht
332
338
 
333
339
  Both tokens are stored in the secure key store (macOS Keychain with encrypted file fallback):
334
340
 
335
- | Secure key | Content |
336
- |-----------|---------|
337
- | `credential:slack_channel:bot_token` | Slack bot token (used for `chat.postMessage` and `auth.test`) |
341
+ | Secure key | Content |
342
+ | ------------------------------------ | -------------------------------------------------------------------------- |
343
+ | `credential:slack_channel:bot_token` | Slack bot token (used for `chat.postMessage` and `auth.test`) |
338
344
  | `credential:slack_channel:app_token` | Slack app token (`xapp-...`, used for Socket Mode `apps.connections.open`) |
339
345
 
340
346
  Workspace metadata (team ID, team name, bot user ID, bot username) is stored as JSON in the credential metadata store under `('slack_channel', 'bot_token')`.
@@ -351,10 +357,10 @@ Both `GET` and `POST` endpoints report `connected: true` only when both `hasBotT
351
357
 
352
358
  **Key source files:**
353
359
 
354
- | File | Purpose |
355
- |------|---------|
356
- | `src/daemon/handlers/config-slack-channel.ts` | Business logic for get/set/clear Slack channel config |
357
- | `src/runtime/routes/integration-routes.ts` | HTTP route handlers for `/v1/integrations/slack/channel/config` |
360
+ | File | Purpose |
361
+ | --------------------------------------------- | --------------------------------------------------------------- |
362
+ | `src/daemon/handlers/config-slack-channel.ts` | Business logic for get/set/clear Slack channel config |
363
+ | `src/runtime/routes/integration-routes.ts` | HTTP route handlers for `/v1/integrations/slack/channel/config` |
358
364
 
359
365
  ### Trusted Contact Access (Channel-Agnostic)
360
366
 
@@ -363,6 +369,7 @@ External users who are not the guardian can gain access to the assistant through
363
369
  **Full design doc:** [`docs/trusted-contact-access.md`](docs/trusted-contact-access.md)
364
370
 
365
371
  **Flow summary:**
372
+
366
373
  1. Unknown user messages the assistant on any channel.
367
374
  2. Ingress ACL (`inbound-message-handler.ts`) rejects the message and emits an `ingress.access_request` notification signal to the guardian.
368
375
  3. Guardian approves or denies via callback button or conversational intent (routed through `guardian-approval-interception.ts`).
@@ -376,6 +383,7 @@ External users who are not the guardian can gain access to the assistant through
376
383
  **Lifecycle states:** `requested → pending_guardian → verification_pending → active | denied | expired`
377
384
 
378
385
  **Notification signals:** The flow emits signals at each lifecycle transition via `emitNotificationSignal()`:
386
+
379
387
  - `ingress.access_request` — non-member denied, guardian notified
380
388
  - `ingress.trusted_contact.guardian_decision` — guardian approved or denied
381
389
  - `ingress.trusted_contact.verification_sent` — code created and delivered
@@ -384,26 +392,26 @@ External users who are not the guardian can gain access to the assistant through
384
392
 
385
393
  **HTTP API (for management):**
386
394
 
387
- | Endpoint | Method | Description |
388
- |----------|--------|-------------|
389
- | `/v1/ingress/members` | GET | List trusted contacts (filterable by channel, status, policy) |
390
- | `/v1/ingress/members` | POST | Upsert a member (add/update trusted contact) |
391
- | `/v1/ingress/members/:id` | DELETE | Revoke a trusted contact |
392
- | `/v1/ingress/members/:id/block` | POST | Block a member |
395
+ | Endpoint | Method | Description |
396
+ | ------------------------------- | ------ | ------------------------------------------------------------- |
397
+ | `/v1/ingress/members` | GET | List trusted contacts (filterable by channel, status, policy) |
398
+ | `/v1/ingress/members` | POST | Upsert a member (add/update trusted contact) |
399
+ | `/v1/ingress/members/:id` | DELETE | Revoke a trusted contact |
400
+ | `/v1/ingress/members/:id/block` | POST | Block a member |
393
401
 
394
402
  **Key source files:**
395
403
 
396
- | File | Purpose |
397
- |------|---------|
398
- | `src/runtime/routes/inbound-message-handler.ts` | Ingress ACL, non-member rejection, verification code interception |
399
- | `src/runtime/routes/access-request-decision.ts` | Guardian decision → verification session creation |
404
+ | File | Purpose |
405
+ | ------------------------------------------------------ | ----------------------------------------------------------------------------- |
406
+ | `src/runtime/routes/inbound-message-handler.ts` | Ingress ACL, non-member rejection, verification code interception |
407
+ | `src/runtime/routes/access-request-decision.ts` | Guardian decision → verification session creation |
400
408
  | `src/runtime/routes/guardian-approval-interception.ts` | Routes guardian decisions (button + conversational) to access request handler |
401
- | `src/runtime/channel-guardian-service.ts` | Verification challenge lifecycle, identity binding, rate limiting |
402
- | `src/runtime/routes/ingress-routes.ts` | HTTP API handlers for member/invite management |
403
- | `src/runtime/ingress-service.ts` | Business logic for member CRUD |
404
- | `src/memory/ingress-member-store.ts` | Member record persistence |
405
- | `src/memory/channel-guardian-store.ts` | Approval request and verification challenge persistence |
406
- | `src/config/vellum-skills/trusted-contacts/SKILL.md` | Skill teaching the assistant to manage contacts via HTTP API |
409
+ | `src/runtime/channel-guardian-service.ts` | Verification challenge lifecycle, identity binding, rate limiting |
410
+ | `src/runtime/routes/ingress-routes.ts` | HTTP API handlers for member/invite management |
411
+ | `src/runtime/ingress-service.ts` | Business logic for member CRUD |
412
+ | `src/memory/ingress-member-store.ts` | Member record persistence |
413
+ | `src/memory/channel-guardian-store.ts` | Approval request and verification challenge persistence |
414
+ | `src/config/bundled-skills/trusted-contacts/SKILL.md` | Skill teaching the assistant to manage contacts via HTTP API |
407
415
 
408
416
  ### Guardian-Initiated Invite Links
409
417
 
@@ -448,24 +456,26 @@ A complementary access-granting flow where the guardian proactively creates a sh
448
456
 
449
457
  **Channel adapter status:**
450
458
 
451
- | Channel | Status | Prerequisites |
452
- |---------|--------|--------------|
453
- | Telegram | Shipped | Bot username resolved from credential metadata or `TELEGRAM_BOT_USERNAME` env |
454
- | Voice | Shipped | Identity-bound voice code redemption via DTMF/speech in the relay state machine. Always-on canonical behavior with personalized friend/guardian name prompts. |
455
- | SMS | Deferred | Needs a deep-link strategy compatible with SMS (short URL or web redemption page) |
456
- | Slack | Deferred | Needs DM-safe ingress — Socket Mode handles channel messages but DM-initiated invite flows need routing |
459
+ | Channel | Status | Prerequisites |
460
+ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
461
+ | Telegram | Shipped | Bot username resolved from credential metadata or `TELEGRAM_BOT_USERNAME` env |
462
+ | Voice | Shipped | Identity-bound voice code redemption via DTMF/speech in the relay state machine. Always-on canonical behavior with personalized friend/guardian name prompts. |
463
+ | SMS | Deferred | Needs a deep-link strategy compatible with SMS (short URL or web redemption page) |
464
+ | Slack | Deferred | Needs DM-safe ingress — Socket Mode handles channel messages but DM-initiated invite flows need routing |
457
465
 
458
466
  ### Voice Invite Flow (invite_redemption_pending)
459
467
 
460
468
  Voice invites use a short numeric code (4-10 digits, default 6) instead of a URL token. The guardian creates an invite bound to the invitee's E.164 phone number; the invitee redeems it by entering the code during an inbound voice call.
461
469
 
462
470
  **Creation flow:**
471
+
463
472
  1. Guardian creates a voice invite via `POST /v1/ingress/invites` with `sourceChannel: "voice"` and `expectedExternalUserId` (E.164 phone).
464
473
  2. `ingress-service.ts` generates a cryptographically random numeric code (`generateVoiceCode`), hashes it with SHA-256 (`hashVoiceCode`), and stores only the hash.
465
474
  3. The one-time plaintext `voiceCode` is returned in the creation response. The raw token is NOT returned for voice invites — redemption uses the identity-bound code flow exclusively.
466
475
  4. Guardian communicates the code to the invitee out-of-band.
467
476
 
468
477
  **Call-time redemption subflow (`invite_redemption_pending`):**
478
+
469
479
  1. Unknown caller dials in. `relay-server.ts` resolves trust via `resolveActorTrust`. Caller is `unknown`, no pending guardian challenge.
470
480
  2. The relay checks `findActiveVoiceInvites` for invites bound to the caller's phone number.
471
481
  3. If active, non-expired invites exist, the relay enters the `invite_redemption_pending` state (reuses the `verification_pending` connection state) and prompts the caller with personalized copy: `Welcome <friend-name>. Please enter the 6-digit code that <guardian-name> provided you to verify your identity.`
@@ -473,6 +483,7 @@ Voice invites use a short numeric code (4-10 digits, default 6) instead of a URL
473
483
  5. On invalid/expired code, the caller hears deterministic failure copy: `Sorry, the code you provided is incorrect or has since expired. Please ask <guardian-name> for a new code. Goodbye.` and the call ends immediately.
474
484
 
475
485
  **Security invariants:**
486
+
476
487
  - The plaintext voice code is returned exactly once at creation time and never stored.
477
488
  - Voice invites are identity-bound: `expectedExternalUserId` must match the caller's E.164 number. An attacker with the code but the wrong phone number cannot redeem.
478
489
  - Failure responses are intentionally generic (`invalid_or_expired`) to prevent oracle attacks.
@@ -480,20 +491,20 @@ Voice invites use a short numeric code (4-10 digits, default 6) instead of a URL
480
491
 
481
492
  **Key source files:**
482
493
 
483
- | File | Purpose |
484
- |------|---------|
485
- | `src/runtime/invite-redemption-service.ts` | Core redemption engine — token validation, voice code redemption, member creation, discriminated-union outcomes |
486
- | `src/runtime/invite-redemption-templates.ts` | Deterministic reply templates for each redemption outcome |
487
- | `src/runtime/channel-invite-transport.ts` | Transport adapter registry with `buildShareableInvite` / `extractInboundToken` interface |
488
- | `src/runtime/channel-invite-transports/telegram.ts` | Telegram adapter — `t.me/<bot>?start=iv_<token>` deep links, `/start iv_<token>` extraction |
489
- | `src/runtime/channel-invite-transports/voice.ts` | Voice transport adapter — code-based redemption metadata |
490
- | `src/daemon/guardian-invite-intent.ts` | Intent detection — routes create/list/revoke requests into the trusted-contacts skill |
491
- | `src/runtime/ingress-service.ts` | Shared business logic for invite/member operations (used by both HTTP routes and IPC) |
492
- | `src/runtime/routes/ingress-routes.ts` | HTTP API handlers for member/invite management including voice invite creation and redemption |
493
- | `src/runtime/routes/inbound-message-handler.ts` | Invite token intercept in the inbound flow (non-member and inactive-member branches) |
494
- | `src/calls/relay-server.ts` | Voice relay state machine — `invite_redemption_pending` subflow (always-on canonical behavior) |
495
- | `src/util/voice-code.ts` | Cryptographic voice code generation and SHA-256 hashing |
496
- | `src/memory/ingress-invite-store.ts` | Invite persistence including `findActiveVoiceInvites` for identity-bound lookup |
494
+ | File | Purpose |
495
+ | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
496
+ | `src/runtime/invite-redemption-service.ts` | Core redemption engine — token validation, voice code redemption, member creation, discriminated-union outcomes |
497
+ | `src/runtime/invite-redemption-templates.ts` | Deterministic reply templates for each redemption outcome |
498
+ | `src/runtime/channel-invite-transport.ts` | Transport adapter registry with `buildShareableInvite` / `extractInboundToken` interface |
499
+ | `src/runtime/channel-invite-transports/telegram.ts` | Telegram adapter — `t.me/<bot>?start=iv_<token>` deep links, `/start iv_<token>` extraction |
500
+ | `src/runtime/channel-invite-transports/voice.ts` | Voice transport adapter — code-based redemption metadata |
501
+ | `src/daemon/guardian-invite-intent.ts` | Intent detection — routes create/list/revoke requests into the trusted-contacts skill |
502
+ | `src/runtime/ingress-service.ts` | Shared business logic for invite/member operations (used by both HTTP routes and IPC) |
503
+ | `src/runtime/routes/ingress-routes.ts` | HTTP API handlers for member/invite management including voice invite creation and redemption |
504
+ | `src/runtime/routes/inbound-message-handler.ts` | Invite token intercept in the inbound flow (non-member and inactive-member branches) |
505
+ | `src/calls/relay-server.ts` | Voice relay state machine — `invite_redemption_pending` subflow (always-on canonical behavior) |
506
+ | `src/util/voice-code.ts` | Cryptographic voice code generation and SHA-256 hashing |
507
+ | `src/memory/ingress-invite-store.ts` | Invite persistence including `findActiveVoiceInvites` for identity-bound lookup |
497
508
 
498
509
  ### Voice Inbound Security Model (Canonical)
499
510
 
@@ -545,6 +556,7 @@ When a pending voice guardian challenge exists (`getPendingChallenge`), the call
545
556
  **Canonical decision routing:**
546
557
 
547
558
  All guardian decisions for voice access requests flow through:
559
+
548
560
  - `applyCanonicalGuardianDecision` (canonical guardian request system)
549
561
  - `accessRequestResolver` in `guardian-request-resolvers.ts` (kind-specific resolver)
550
562
  - For voice approvals: direct trusted-contact activation (no verification session needed since the caller is already on the line)
@@ -552,20 +564,21 @@ All guardian decisions for voice access requests flow through:
552
564
 
553
565
  **Key source files:**
554
566
 
555
- | File | Purpose |
556
- |------|---------|
557
- | `src/calls/relay-server.ts` | Inbound call decision tree, name capture, guardian approval wait polling |
558
- | `src/runtime/access-request-helper.ts` | Creates canonical access request and notifies guardian |
559
- | `src/approvals/guardian-decision-primitive.ts` | `applyCanonicalGuardianDecision` — unified decision primitive |
560
- | `src/approvals/guardian-request-resolvers.ts` | `access_request` resolver — voice direct activation, text-channel verification session |
561
- | `src/runtime/actor-trust-resolver.ts` | `resolveActorTrust` — caller trust classification |
562
- | `src/memory/canonical-guardian-store.ts` | Canonical request persistence and CAS resolution |
567
+ | File | Purpose |
568
+ | ---------------------------------------------- | -------------------------------------------------------------------------------------- |
569
+ | `src/calls/relay-server.ts` | Inbound call decision tree, name capture, guardian approval wait polling |
570
+ | `src/runtime/access-request-helper.ts` | Creates canonical access request and notifies guardian |
571
+ | `src/approvals/guardian-decision-primitive.ts` | `applyCanonicalGuardianDecision` — unified decision primitive |
572
+ | `src/approvals/guardian-request-resolvers.ts` | `access_request` resolver — voice direct activation, text-channel verification session |
573
+ | `src/runtime/actor-trust-resolver.ts` | `resolveActorTrust` — caller trust classification |
574
+ | `src/memory/canonical-guardian-store.ts` | Canonical request persistence and CAS resolution |
563
575
 
564
576
  ### Update Bulletin System
565
577
 
566
578
  Release-driven update notification system that surfaces release notes to the assistant via the system prompt.
567
579
 
568
580
  **Data flow:**
581
+
569
582
  1. **Bundled template** (`src/config/templates/UPDATES.md`) — source of release notes, maintained per-release in the repo.
570
583
  2. **Startup sync** (`syncUpdateBulletinOnStartup()` in `src/config/update-bulletin.ts`) — materializes the bundled template into the workspace `UPDATES.md` on daemon boot. Uses atomic write (temp + rename) for crash safety.
571
584
  3. **System prompt injection** — `buildSystemPrompt()` reads workspace `UPDATES.md` and injects it as a `## Recent Updates` section with judgment-based handling instructions.
@@ -573,20 +586,21 @@ Release-driven update notification system that surfaces release notes to the ass
573
586
  5. **Cross-release merge** — if pending updates from a prior release exist when a new release lands, both release blocks coexist in the same file.
574
587
 
575
588
  **Checkpoint keys** (in `memory_checkpoints` table):
589
+
576
590
  - `updates:active_releases` — JSON array of version strings currently active.
577
591
  - `updates:completed_releases` — JSON array of version strings already completed.
578
592
 
579
593
  **Key source files:**
580
594
 
581
- | File | Purpose |
582
- |------|---------|
583
- | `src/config/templates/UPDATES.md` | Bundled release-note template |
584
- | `src/config/update-bulletin.ts` | Startup sync logic (materialize, delete-complete, merge) |
585
- | `src/config/update-bulletin-format.ts` | Release block formatter/parser helpers |
586
- | `src/config/update-bulletin-state.ts` | Checkpoint state helpers for active/completed releases |
587
- | `src/config/system-prompt.ts` | Prompt injection of updates section |
588
- | `src/daemon/config-watcher.ts` | File watcher — evicts sessions on UPDATES.md changes |
589
- | `src/permissions/defaults.ts` | Auto-allow rules for file_read/write/edit + rm UPDATES.md |
595
+ | File | Purpose |
596
+ | -------------------------------------- | --------------------------------------------------------- |
597
+ | `src/config/templates/UPDATES.md` | Bundled release-note template |
598
+ | `src/config/update-bulletin.ts` | Startup sync logic (materialize, delete-complete, merge) |
599
+ | `src/config/update-bulletin-format.ts` | Release block formatter/parser helpers |
600
+ | `src/config/update-bulletin-state.ts` | Checkpoint state helpers for active/completed releases |
601
+ | `src/config/system-prompt.ts` | Prompt injection of updates section |
602
+ | `src/daemon/config-watcher.ts` | File watcher — evicts sessions on UPDATES.md changes |
603
+ | `src/permissions/defaults.ts` | Auto-allow rules for file_read/write/edit + rm UPDATES.md |
590
604
 
591
605
  ---
592
606
 
@@ -597,6 +611,7 @@ The assistant feature-flag resolver (`src/config/assistant-feature-flags.ts`) is
597
611
  **Canonical key format:** `feature_flags.<flag_id>.enabled` (e.g., `feature_flags.hatch-new-assistant.enabled`).
598
612
 
599
613
  **Resolution priority** (highest wins):
614
+
600
615
  1. `config.assistantFeatureFlagValues[key]` — canonical config section, written by the gateway's PATCH endpoint
601
616
  2. Defaults registry `defaultEnabled` — from the unified registry (`meta/feature-flags/feature-flag-registry.json`, filtered to `scope: "assistant"`)
602
617
  3. `true` — unknown/undeclared flags with no persisted override default to enabled
@@ -604,19 +619,20 @@ The assistant feature-flag resolver (`src/config/assistant-feature-flags.ts`) is
604
619
  **Storage:** Flags are persisted in `~/.vellum/workspace/config.json`. New writes go to the `assistantFeatureFlagValues` section (managed by the gateway's `/v1/feature-flags` API — see [`gateway/ARCHITECTURE.md`](../gateway/ARCHITECTURE.md)). The legacy `featureFlags` section is still read for backward compatibility. The daemon's config watcher hot-reloads this file, so flag changes take effect on the next tool resolution or session.
605
620
 
606
621
  **Public API:**
622
+
607
623
  - `isAssistantFeatureFlagEnabled(key, config)` — full resolver with the canonical key
608
624
  - `isAssistantSkillEnabled(skillId, config)` — convenience wrapper that constructs `feature_flags.<skillId>.enabled` and delegates
609
625
  - `isSkillFeatureEnabled(skillId, config)` — deprecated legacy wrapper in `config/skill-state.ts`
610
626
 
611
627
  **Skill-gating guarantee:** For skills that are explicitly mapped to declared assistant flags, when the flag is OFF the skill is unavailable everywhere — it cannot appear in client UIs, model context, or runtime tool execution. This is enforced at five independent points:
612
628
 
613
- | Enforcement Point | Module | Effect |
614
- |-------------------|--------|--------|
615
- | **1. Client skill list** | `resolveSkillStates()` in `config/skill-state.ts` | Skills with flag OFF are excluded from the resolved list returned to IPC clients (macOS skill list, settings UI). The skill never appears in the client. |
616
- | **2. System prompt skill catalog** | `appendSkillsCatalog()` in `config/system-prompt.ts` | The model-visible `## Skills Catalog` section in the system prompt filters out flagged-off skills. The model cannot see or reference them. |
617
- | **3. `skill_load` tool** | `executeSkillLoad()` in `tools/skills/load.ts` | If the model attempts to load a flagged-off skill by name, the tool returns an error: `"skill is currently unavailable (disabled by feature flag)"`. |
618
- | **4. Runtime tool projection** | `projectSkillTools()` in `daemon/session-skill-tools.ts` | Even if a skill was previously active in a session (has `<loaded_skill>` markers in history), the per-turn projection drops it when the flag is OFF. Already-registered tools are unregistered. |
619
- | **5. Included child skills** | `executeSkillLoad()` in `tools/skills/load.ts` | When a parent skill includes children via the `includes` directive, each child is independently checked against its feature flag. Flagged-off children are silently excluded from the loaded skill content. |
629
+ | Enforcement Point | Module | Effect |
630
+ | ---------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
631
+ | **1. Client skill list** | `resolveSkillStates()` in `config/skill-state.ts` | Skills with flag OFF are excluded from the resolved list returned to IPC clients (macOS skill list, settings UI). The skill never appears in the client. |
632
+ | **2. System prompt skill catalog** | `appendSkillsCatalog()` in `config/system-prompt.ts` | The model-visible `## Skills Catalog` section in the system prompt filters out flagged-off skills. The model cannot see or reference them. |
633
+ | **3. `skill_load` tool** | `executeSkillLoad()` in `tools/skills/load.ts` | If the model attempts to load a flagged-off skill by name, the tool returns an error: `"skill is currently unavailable (disabled by feature flag)"`. |
634
+ | **4. Runtime tool projection** | `projectSkillTools()` in `daemon/session-skill-tools.ts` | Even if a skill was previously active in a session (has `<loaded_skill>` markers in history), the per-turn projection drops it when the flag is OFF. Already-registered tools are unregistered. |
635
+ | **5. Included child skills** | `executeSkillLoad()` in `tools/skills/load.ts` | When a parent skill includes children via the `includes` directive, each child is independently checked against its feature flag. Flagged-off children are silently excluded from the loaded skill content. |
620
636
 
621
637
  All five enforcement points use `isAssistantSkillEnabled()` from `config/assistant-feature-flags.ts` for consistency.
622
638
 
@@ -624,18 +640,18 @@ All five enforcement points use `isAssistantSkillEnabled()` from `config/assista
624
640
 
625
641
  **Key source files:**
626
642
 
627
- | File | Purpose |
628
- |------|---------|
629
- | `src/config/assistant-feature-flags.ts` | Canonical resolver: `isAssistantFeatureFlagEnabled()`, `isAssistantSkillEnabled()`, `getAssistantFeatureFlagDefaults()`, registry loader |
630
- | `src/config/skill-state.ts` | `isSkillFeatureEnabled()` (deprecated wrapper) — delegates to canonical resolver; `resolveSkillStates()` — enforcement point 1 |
631
- | `src/config/system-prompt.ts` | `appendSkillsCatalog()` — enforcement point 2 |
632
- | `src/tools/skills/load.ts` | `executeSkillLoad()` — enforcement points 3 and 5 |
633
- | `src/daemon/session-skill-tools.ts` | `projectSkillTools()` — enforcement point 4 |
634
- | `src/config/schema.ts` | `featureFlags` and `assistantFeatureFlagValues` field definitions in `AssistantConfig` (Zod schema) |
635
- | `src/config/types.ts` | Type definitions for `FeatureFlags` (legacy) and `AssistantFeatureFlagValues` (canonical) |
636
- | `src/daemon/handlers/skills.ts` | `handleSkillsList()` — uses `resolveSkillStates()` for IPC client responses |
637
- | `meta/feature-flags/feature-flag-registry.json` | Unified feature flag registry (repo root) — all declared flags with scope, label, default values, and descriptions |
638
- | `src/config/feature-flag-registry.json` | Bundled copy of the unified registry for compiled binary resolution |
643
+ | File | Purpose |
644
+ | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
645
+ | `src/config/assistant-feature-flags.ts` | Canonical resolver: `isAssistantFeatureFlagEnabled()`, `isAssistantSkillEnabled()`, `getAssistantFeatureFlagDefaults()`, registry loader |
646
+ | `src/config/skill-state.ts` | `isSkillFeatureEnabled()` (deprecated wrapper) — delegates to canonical resolver; `resolveSkillStates()` — enforcement point 1 |
647
+ | `src/config/system-prompt.ts` | `appendSkillsCatalog()` — enforcement point 2 |
648
+ | `src/tools/skills/load.ts` | `executeSkillLoad()` — enforcement points 3 and 5 |
649
+ | `src/daemon/session-skill-tools.ts` | `projectSkillTools()` — enforcement point 4 |
650
+ | `src/config/schema.ts` | `featureFlags` and `assistantFeatureFlagValues` field definitions in `AssistantConfig` (Zod schema) |
651
+ | `src/config/types.ts` | Type definitions for `FeatureFlags` (legacy) and `AssistantFeatureFlagValues` (canonical) |
652
+ | `src/daemon/handlers/skills.ts` | `handleSkillsList()` — uses `resolveSkillStates()` for IPC client responses |
653
+ | `meta/feature-flags/feature-flag-registry.json` | Unified feature flag registry (repo root) — all declared flags with scope, label, default values, and descriptions |
654
+ | `src/config/feature-flag-registry.json` | Bundled copy of the unified registry for compiled binary resolution |
639
655
 
640
656
  ---
641
657
 
@@ -711,7 +727,6 @@ graph LR
711
727
 
712
728
  ---
713
729
 
714
-
715
730
  ---
716
731
 
717
732
  ## Web Server — Connection Modes
@@ -979,30 +994,31 @@ When the daemon receives a CU observation with blob refs, it attempts blob-first
979
994
 
980
995
  The daemon emits two distinct error message types over IPC:
981
996
 
982
- | Message type | Scope | Purpose | Payload |
983
- |---|---|---|---|
997
+ | Message type | Scope | Purpose | Payload |
998
+ | --------------- | -------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
984
999
  | `session_error` | Session-scoped | Typed, actionable failures during chat/session runtime (e.g., provider network error, rate limit, API failure) | `sessionId`, `code` (typed enum), `userMessage`, `retryable`, `debugDetails?` |
985
- | `error` | Global | Generic, non-session failures (e.g., daemon startup errors, unknown message types) | `message` (string) |
1000
+ | `error` | Global | Generic, non-session failures (e.g., daemon startup errors, unknown message types) | `message` (string) |
986
1001
 
987
1002
  **Design rationale:** `session_error` carries structured metadata (error code, retryable flag, debug details) so the client can present actionable UI — a toast with retry/dismiss buttons — rather than a generic error banner. The older `error` type is retained for backward compatibility with non-session contexts.
988
1003
 
989
1004
  ### Session Error Codes
990
1005
 
991
- | Code | Meaning | Retryable |
992
- |---|---|---|
993
- | `PROVIDER_NETWORK` | Unable to reach the LLM provider (connection refused, timeout, DNS) | Yes |
994
- | `PROVIDER_RATE_LIMIT` | LLM provider rate-limited the request (HTTP 429) | Yes |
995
- | `PROVIDER_API` | Provider returned a server error (5xx) | Yes |
996
- | `QUEUE_FULL` | The message queue is full | Yes |
997
- | `SESSION_ABORTED` | Non-user abort interrupted the request | Yes |
998
- | `SESSION_PROCESSING_FAILED` | Catch-all for unexpected processing failures | No |
999
- | `REGENERATE_FAILED` | Failed to regenerate a previous response | Yes |
1006
+ | Code | Meaning | Retryable |
1007
+ | --------------------------- | ------------------------------------------------------------------- | --------- |
1008
+ | `PROVIDER_NETWORK` | Unable to reach the LLM provider (connection refused, timeout, DNS) | Yes |
1009
+ | `PROVIDER_RATE_LIMIT` | LLM provider rate-limited the request (HTTP 429) | Yes |
1010
+ | `PROVIDER_API` | Provider returned a server error (5xx) | Yes |
1011
+ | `QUEUE_FULL` | The message queue is full | Yes |
1012
+ | `SESSION_ABORTED` | Non-user abort interrupted the request | Yes |
1013
+ | `SESSION_PROCESSING_FAILED` | Catch-all for unexpected processing failures | No |
1014
+ | `REGENERATE_FAILED` | Failed to regenerate a previous response | Yes |
1000
1015
 
1001
1016
  ### Error Classification
1002
1017
 
1003
1018
  The daemon classifies errors via `classifySessionError()` in `session-error.ts`. Before classification, `isUserCancellation()` checks whether the error is a user-initiated abort (active abort signal or `AbortError`); if so, the daemon emits `generation_cancelled` instead of `session_error` — cancel never surfaces a session-error toast.
1004
1019
 
1005
1020
  Classification uses a two-tier strategy:
1021
+
1006
1022
  1. **Structured provider errors**: If the error is a `ProviderError` with a `statusCode`, the status code determines the category deterministically — `429` maps to `PROVIDER_RATE_LIMIT` (retryable), `5xx` to `PROVIDER_API` (retryable), other `4xx` to `PROVIDER_API` (not retryable).
1007
1023
  2. **Regex fallback**: For non-provider errors or `ProviderError` without a status code, regex pattern matching against the error message detects network failures, rate limits, and API errors. Phase-specific overrides handle queue and regeneration contexts.
1008
1024
 
@@ -1038,7 +1054,7 @@ sequenceDiagram
1038
1054
  end
1039
1055
  ```
1040
1056
 
1041
- 1. **Daemon** encounters a session-scoped failure, classifies it via `classifySessionError()`, and sends a `session_error` IPC message with the session ID, typed error code, user-facing message, retryable flag, and optional debug details. Session-scoped failures emit *only* `session_error` (never the generic `error` type) to prevent cross-session bleed.
1057
+ 1. **Daemon** encounters a session-scoped failure, classifies it via `classifySessionError()`, and sends a `session_error` IPC message with the session ID, typed error code, user-facing message, retryable flag, and optional debug details. Session-scoped failures emit _only_ `session_error` (never the generic `error` type) to prevent cross-session bleed.
1042
1058
  2. **ChatViewModel** receives the error via DaemonClient's `subscribe()` stream (each view model gets an independent stream), sets the `sessionError` property, and transitions out of the streaming/loading state so the UI is interactive. If the error arrives during an active cancel (`wasCancelling == true`), it is suppressed — cancel only shows `generation_cancelled` behavior.
1043
1059
  3. **ChatView** observes the published `sessionError` and displays an actionable toast with a category-specific icon and accent color:
1044
1060
  - **Retry** (shown when `retryable` is true): calls `retryAfterSessionError()`, which clears the error and sends a `regenerate` message to the daemon.
@@ -1088,14 +1104,14 @@ graph TB
1088
1104
 
1089
1105
  The text_qa system prompt includes an action execution hierarchy that guides tool selection toward the least invasive method:
1090
1106
 
1091
- | Priority | Method | Tool | When to use |
1092
- |----------|--------|------|-------------|
1093
- | **BEST** | Sandboxed filesystem/shell | `file_*`, `bash` | Work that can stay isolated in sandbox filesystem |
1094
- | **BETTER** | Explicit host filesystem/shell | `host_file_*`, `host_bash` | Host reads/writes/commands that must touch the real machine |
1095
- | **GOOD** | Headless browser | `browser_*` (bundled `browser` skill) | Web automation, form filling, scraping (background) |
1096
- | **LAST RESORT** | Foreground computer use | `computer_use_request_control` | Only on explicit user request ("go ahead", "take over") |
1107
+ | Priority | Method | Tool | When to use |
1108
+ | --------------- | ------------------------------ | ------------------------------------- | ----------------------------------------------------------- |
1109
+ | **BEST** | Sandboxed filesystem/shell | `file_*`, `bash` | Work that can stay isolated in sandbox filesystem |
1110
+ | **BETTER** | Explicit host filesystem/shell | `host_file_*`, `host_bash` | Host reads/writes/commands that must touch the real machine |
1111
+ | **GOOD** | Headless browser | `browser_*` (bundled `browser` skill) | Web automation, form filling, scraping (background) |
1112
+ | **LAST RESORT** | Foreground computer use | `computer_use_request_control` | Only on explicit user request ("go ahead", "take over") |
1097
1113
 
1098
- The `computer_use_request_control` tool is a core proxy tool available only to text_qa sessions. When invoked, the session's `surfaceProxyResolver` creates a CU session and sends a `task_routed` message to the client, effectively escalating from text_qa to foreground computer use. The CU session constructor sets `preactivatedSkillIds: ['computer-use']`, and its `getProjectedCuToolDefinitions()` calls `projectSkillTools()` to load the 12 `computer_use_*` action tools from the bundled `computer-use` skill (via TOOLS.json). These tools are not core-registered at daemon startup; they exist only within CU sessions through skill projection.
1114
+ The `computer_use_request_control` tool is a core proxy tool available only to text*qa sessions. When invoked, the session's `surfaceProxyResolver` creates a CU session and sends a `task_routed` message to the client, effectively escalating from text_qa to foreground computer use. The CU session constructor sets `preactivatedSkillIds: ['computer-use']`, and its `getProjectedCuToolDefinitions()` calls `projectSkillTools()` to load the 12 `computer_use*\*`action tools from the bundled`computer-use` skill (via TOOLS.json). These tools are not core-registered at daemon startup; they exist only within CU sessions through skill projection.
1099
1115
 
1100
1116
  ### Sandbox Filesystem and Host Access
1101
1117
 
@@ -1159,6 +1175,7 @@ graph TB
1159
1175
  ```
1160
1176
 
1161
1177
  Key behaviors:
1178
+
1162
1179
  - **Known**: Content is rewritten via `rewriteKnownSlashCommandPrompt` to instruct the model to invoke the skill. Trailing arguments are preserved.
1163
1180
  - **Unknown**: A deterministic `assistant_text_delta` + `message_complete` is emitted listing available slash commands. No message persistence or model call occurs.
1164
1181
  - **Queue**: Queued messages receive the same slash resolution. Unknown slash commands in the queue emit their response and continue draining without stalling.
@@ -1227,6 +1244,7 @@ graph TB
1227
1244
  ```
1228
1245
 
1229
1246
  **Key design decisions:**
1247
+
1230
1248
  - `evaluate_typescript_code` always forces `sandbox.enabled = true` regardless of global config.
1231
1249
  - Snippet contract: must export `default` or `run` with signature `(input: unknown) => unknown | Promise<unknown>`.
1232
1250
  - Managed-store writes are atomic (tmp file + rename) to prevent partial `SKILL.md` or `SKILLS.md` files.
@@ -1256,18 +1274,19 @@ graph LR
1256
1274
  ```
1257
1275
 
1258
1276
  **Validation rules:**
1277
+
1259
1278
  - **Missing children**: If any skill in the recursive graph references an `includes` ID not found in the catalog, validation fails with the full path from root to the missing reference.
1260
1279
  - **Cycles**: Three-state DFS (unseen → visiting → done) detects direct and indirect cycles. The error includes the cycle path.
1261
1280
  - **Fail-closed**: On any validation error, `skill_load` returns `isError: true` with no `<loaded_skill>` marker, preventing the agent from using a skill with broken dependencies.
1262
1281
 
1263
1282
  **Key constraint**: Include metadata is metadata-only. Child skills are **not** auto-activated — the agent must explicitly call `skill_load` for each child. The `projectSkillTools()` function only projects tools for skills with explicit `<loaded_skill>` markers in conversation history.
1264
1283
 
1265
- | Source File | Purpose |
1266
- |---|---|
1284
+ | Source File | Purpose |
1285
+ | --------------------------------------- | ------------------------------------------------------------------------------------------ |
1267
1286
  | `assistant/src/skills/include-graph.ts` | `indexCatalogById()`, `getImmediateChildren()`, `validateIncludes()`, `traverseIncludes()` |
1268
- | `assistant/src/tools/skills/load.ts` | Include validation integration in `skill_load` execute path |
1269
- | `assistant/src/config/skills.ts` | `includes` field parsing from SKILL.md frontmatter |
1270
- | `assistant/src/skills/managed-store.ts` | `includes` emission in `buildSkillMarkdown()` |
1287
+ | `assistant/src/tools/skills/load.ts` | Include validation integration in `skill_load` execute path |
1288
+ | `assistant/src/config/skills.ts` | `includes` field parsing from SKILL.md frontmatter |
1289
+ | `assistant/src/skills/managed-store.ts` | `includes` emission in `buildSkillMarkdown()` |
1271
1290
 
1272
1291
  ---
1273
1292
 
@@ -1291,16 +1310,16 @@ skills/<skill-id>/
1291
1310
 
1292
1311
  The following capabilities ship as bundled skills in `assistant/src/config/bundled-skills/`:
1293
1312
 
1294
- | Skill ID | Tools | Purpose |
1295
- |----------|-------|---------|
1296
- | `browser` | `browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_close`, `browser_click`, `browser_type`, `browser_press_key`, `browser_wait_for`, `browser_extract`, `browser_fill_credential` | Headless browser automation — web scraping, form filling, interaction (previously core-registered as `headless-browser`; now skill-provided with default allow rules) |
1297
- | `gmail` | Gmail search, archive, send, etc. | Email management via OAuth2 integration |
1298
- | `claude-code` | Claude Code tool | Delegate coding tasks to Claude Code subprocess |
1299
- | `computer-use` | `computer_use_click`, `computer_use_double_click`, `computer_use_right_click`, `computer_use_type_text`, `computer_use_key`, `computer_use_scroll`, `computer_use_drag`, `computer_use_open_app`, `computer_use_run_applescript`, `computer_use_wait`, `computer_use_done`, `computer_use_respond` | Computer-use action tools — internally preactivated by `ComputerUseSession` via `preactivatedSkillIds`; not user-invocable or model-discoverable in text sessions. Each wrapper script forwards to `forwardComputerUseProxyTool()` which uses the session's proxy resolver to send actions to the macOS client. |
1300
- | `weather` | `get-weather` | Fetch current weather data |
1301
- | `app-builder` | `app_create`, `app_list`, `app_query`, `app_update`, `app_delete`, `app_file_list`, `app_file_read`, `app_file_edit`, `app_file_write` | Dynamic app authoring — CRUD and file-level editing for persistent apps (activated via `skill_load app-builder`; `app_open` remains a core proxy tool) |
1302
- | `self-upgrade` | (instruction-only) | Self-improvement workflow |
1303
- | `start-the-day` | (instruction-only) | Morning briefing routine |
1313
+ | Skill ID | Tools | Purpose |
1314
+ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1315
+ | `browser` | `browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_close`, `browser_click`, `browser_type`, `browser_press_key`, `browser_wait_for`, `browser_extract`, `browser_fill_credential` | Headless browser automation — web scraping, form filling, interaction (previously core-registered as `headless-browser`; now skill-provided with default allow rules) |
1316
+ | `gmail` | Gmail search, archive, send, etc. | Email management via OAuth2 integration |
1317
+ | `claude-code` | Claude Code tool | Delegate coding tasks to Claude Code subprocess |
1318
+ | `computer-use` | `computer_use_click`, `computer_use_double_click`, `computer_use_right_click`, `computer_use_type_text`, `computer_use_key`, `computer_use_scroll`, `computer_use_drag`, `computer_use_open_app`, `computer_use_run_applescript`, `computer_use_wait`, `computer_use_done`, `computer_use_respond` | Computer-use action tools — internally preactivated by `ComputerUseSession` via `preactivatedSkillIds`; not user-invocable or model-discoverable in text sessions. Each wrapper script forwards to `forwardComputerUseProxyTool()` which uses the session's proxy resolver to send actions to the macOS client. |
1319
+ | `weather` | `get-weather` | Fetch current weather data |
1320
+ | `app-builder` | `app_create`, `app_list`, `app_query`, `app_update`, `app_delete`, `app_file_list`, `app_file_read`, `app_file_edit`, `app_file_write` | Dynamic app authoring — CRUD and file-level editing for persistent apps (activated via `skill_load app-builder`; `app_open` remains a core proxy tool) |
1321
+ | `self-upgrade` | (instruction-only) | Self-improvement workflow |
1322
+ | `start-the-day` | (instruction-only) | Morning briefing routine |
1304
1323
 
1305
1324
  ### Activation and Projection Flow
1306
1325
 
@@ -1380,19 +1399,19 @@ graph TB
1380
1399
 
1381
1400
  ### Key Source Files
1382
1401
 
1383
- | File | Role |
1384
- |------|------|
1385
- | `assistant/src/config/skills.ts` | Skill catalog loading: bundled, managed, workspace, extra directories |
1386
- | `assistant/src/config/bundled-skills/` | Bundled skill directories (browser, gmail, claude-code, computer-use, weather, etc.) |
1387
- | `assistant/src/skills/tool-manifest.ts` | `TOOLS.json` parser and validator |
1388
- | `assistant/src/skills/active-skill-tools.ts` | `deriveActiveSkillIds()` — scans history for `<loaded_skill>` markers |
1389
- | `assistant/src/skills/include-graph.ts` | Include graph builder: `indexCatalogById()`, `validateIncludes()`, cycle/missing detection |
1390
- | `assistant/src/daemon/session-skill-tools.ts` | `projectSkillTools()` — per-turn projection, register/unregister lifecycle |
1391
- | `assistant/src/tools/skills/skill-tool-factory.ts` | `createSkillToolsFromManifest()` — manifest entries to Tool objects |
1392
- | `assistant/src/tools/skills/skill-script-runner.ts` | Host runner: dynamic import + `run()` call |
1393
- | `assistant/src/tools/skills/sandbox-runner.ts` | Sandbox runner: isolated subprocess execution |
1394
- | `assistant/src/tools/registry.ts` | `registerSkillTools()` / `unregisterSkillTools()` — global tool registry |
1395
- | `assistant/src/permissions/checker.ts` | Skill-origin default-ask permission policy |
1402
+ | File | Role |
1403
+ | --------------------------------------------------- | ------------------------------------------------------------------------------------------ |
1404
+ | `assistant/src/config/skills.ts` | Skill catalog loading: bundled, managed, workspace, extra directories |
1405
+ | `assistant/src/config/bundled-skills/` | Bundled skill directories (browser, gmail, claude-code, computer-use, weather, etc.) |
1406
+ | `assistant/src/skills/tool-manifest.ts` | `TOOLS.json` parser and validator |
1407
+ | `assistant/src/skills/active-skill-tools.ts` | `deriveActiveSkillIds()` — scans history for `<loaded_skill>` markers |
1408
+ | `assistant/src/skills/include-graph.ts` | Include graph builder: `indexCatalogById()`, `validateIncludes()`, cycle/missing detection |
1409
+ | `assistant/src/daemon/session-skill-tools.ts` | `projectSkillTools()` — per-turn projection, register/unregister lifecycle |
1410
+ | `assistant/src/tools/skills/skill-tool-factory.ts` | `createSkillToolsFromManifest()` — manifest entries to Tool objects |
1411
+ | `assistant/src/tools/skills/skill-script-runner.ts` | Host runner: dynamic import + `run()` call |
1412
+ | `assistant/src/tools/skills/sandbox-runner.ts` | Sandbox runner: isolated subprocess execution |
1413
+ | `assistant/src/tools/registry.ts` | `registerSkillTools()` / `unregisterSkillTools()` — global tool registry |
1414
+ | `assistant/src/permissions/checker.ts` | Skill-origin default-ask permission policy |
1396
1415
 
1397
1416
  ---
1398
1417
 
@@ -1436,19 +1455,19 @@ graph TB
1436
1455
 
1437
1456
  The `permissions.mode` config option (`workspace`, `strict`, or `legacy`) controls the default behavior when no trust rule matches a tool invocation. The default is `workspace`.
1438
1457
 
1439
- | Behavior | Workspace mode (default) | Strict mode | Legacy mode (deprecated) |
1440
- |---|---|---|---|
1441
- | Workspace-scoped ops with no matching rule | Auto-allowed | Prompted | Auto-allowed (low risk) |
1442
- | Non-workspace low-risk tools with no matching rule | Auto-allowed | Prompted | Auto-allowed |
1443
- | Medium-risk tools with no matching rule | Prompted | Prompted | Prompted |
1444
- | High-risk tools with no matching rule | Prompted | Prompted | Prompted |
1445
- | `skill_load` with no matching rule | Prompted | Prompted | Auto-allowed (low risk) |
1446
- | `skill_load` with system default rule | Auto-allowed (`skill_load:*` at priority 100) | Auto-allowed (`skill_load:*` at priority 100) | Auto-allowed (`skill_load:*` at priority 100) |
1447
- | `browser_*` skill tools with system default rules | Auto-allowed (priority 100 allow rules) | Auto-allowed (priority 100 allow rules) | Auto-allowed (priority 100 allow rules) |
1448
- | Skill-origin tools with no matching rule | Prompted | Prompted | Prompted |
1449
- | Allow rules for non-high-risk tools | Auto-allowed | Auto-allowed | Auto-allowed |
1450
- | Allow rules with `allowHighRisk: true` | Auto-allowed (even high risk) | Auto-allowed (even high risk) | Auto-allowed (even high risk) |
1451
- | Deny rules | Blocked | Blocked | Blocked |
1458
+ | Behavior | Workspace mode (default) | Strict mode | Legacy mode (deprecated) |
1459
+ | -------------------------------------------------- | --------------------------------------------- | --------------------------------------------- | --------------------------------------------- |
1460
+ | Workspace-scoped ops with no matching rule | Auto-allowed | Prompted | Auto-allowed (low risk) |
1461
+ | Non-workspace low-risk tools with no matching rule | Auto-allowed | Prompted | Auto-allowed |
1462
+ | Medium-risk tools with no matching rule | Prompted | Prompted | Prompted |
1463
+ | High-risk tools with no matching rule | Prompted | Prompted | Prompted |
1464
+ | `skill_load` with no matching rule | Prompted | Prompted | Auto-allowed (low risk) |
1465
+ | `skill_load` with system default rule | Auto-allowed (`skill_load:*` at priority 100) | Auto-allowed (`skill_load:*` at priority 100) | Auto-allowed (`skill_load:*` at priority 100) |
1466
+ | `browser_*` skill tools with system default rules | Auto-allowed (priority 100 allow rules) | Auto-allowed (priority 100 allow rules) | Auto-allowed (priority 100 allow rules) |
1467
+ | Skill-origin tools with no matching rule | Prompted | Prompted | Prompted |
1468
+ | Allow rules for non-high-risk tools | Auto-allowed | Auto-allowed | Auto-allowed |
1469
+ | Allow rules with `allowHighRisk: true` | Auto-allowed (even high risk) | Auto-allowed (even high risk) | Auto-allowed (even high risk) |
1470
+ | Deny rules | Blocked | Blocked | Blocked |
1452
1471
 
1453
1472
  **Workspace mode** (default) auto-allows operations scoped to the workspace (file reads/writes/edits within the workspace directory, sandboxed bash) without prompting. Host operations, network requests, and operations outside the workspace still follow the normal approval flow. Explicit deny and ask rules override auto-allow.
1454
1473
 
@@ -1460,16 +1479,16 @@ The `permissions.mode` config option (`workspace`, `strict`, or `legacy`) contro
1460
1479
 
1461
1480
  Rules are stored in `~/.vellum/protected/trust.json` with version `3`. Each rule can include the following fields:
1462
1481
 
1463
- | Field | Type | Purpose |
1464
- |---|---|---|
1465
- | `id` | `string` | Unique identifier (UUID for user rules, `default:*` for system defaults) |
1466
- | `tool` | `string` | Tool name to match (e.g., `bash`, `file_write`, `skill_load`) |
1467
- | `pattern` | `string` | Minimatch glob pattern for the command/target string |
1468
- | `scope` | `string` | Path prefix or `everywhere` — restricts where the rule applies |
1469
- | `decision` | `allow \| deny \| ask` | What to do when the rule matches |
1470
- | `priority` | `number` | Higher priority wins; deny wins ties at equal priority |
1471
- | `executionTarget` | `string?` | `sandbox` or `host` — restricts by execution context |
1472
- | `allowHighRisk` | `boolean?` | When true, auto-allows even high-risk invocations |
1482
+ | Field | Type | Purpose |
1483
+ | ----------------- | ---------------------- | ------------------------------------------------------------------------ |
1484
+ | `id` | `string` | Unique identifier (UUID for user rules, `default:*` for system defaults) |
1485
+ | `tool` | `string` | Tool name to match (e.g., `bash`, `file_write`, `skill_load`) |
1486
+ | `pattern` | `string` | Minimatch glob pattern for the command/target string |
1487
+ | `scope` | `string` | Path prefix or `everywhere` — restricts where the rule applies |
1488
+ | `decision` | `allow \| deny \| ask` | What to do when the rule matches |
1489
+ | `priority` | `number` | Higher priority wins; deny wins ties at equal priority |
1490
+ | `executionTarget` | `string?` | `sandbox` or `host` — restricts by execution context |
1491
+ | `allowHighRisk` | `boolean?` | When true, auto-allows even high-risk invocations |
1473
1492
 
1474
1493
  Missing optional fields act as wildcards. A rule with no `executionTarget` matches any target.
1475
1494
 
@@ -1477,16 +1496,16 @@ Missing optional fields act as wildcards. A rule with no `executionTarget` match
1477
1496
 
1478
1497
  The `classifyRisk()` function determines the risk level for each tool invocation:
1479
1498
 
1480
- | Tool | Risk level | Notes |
1481
- |---|---|---|
1482
- | `file_read`, `web_search`, `skill_load` | Low | Read-only or informational |
1483
- | `file_write`, `file_edit` | Medium (default) | Filesystem mutations |
1484
- | `file_write`, `file_edit` targeting skill source paths | **High** | `isSkillSourcePath()` detects managed/bundled/workspace/extra skill roots |
1485
- | `host_file_write`, `host_file_edit` targeting skill source paths | **High** | Same path classification, host variant |
1486
- | `bash`, `host_bash` | Varies | Parsed via tree-sitter: low-risk programs = Low, high-risk programs = High, unknown = Medium |
1487
- | `scaffold_managed_skill`, `delete_managed_skill` | High | Skill lifecycle mutations always high-risk |
1488
- | `evaluate_typescript_code` | High | Arbitrary code execution |
1489
- | Skill-origin tools with no matching rule | Prompted regardless of risk | Even Low-risk skill tools default to `ask` |
1499
+ | Tool | Risk level | Notes |
1500
+ | ---------------------------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------- |
1501
+ | `file_read`, `web_search`, `skill_load` | Low | Read-only or informational |
1502
+ | `file_write`, `file_edit` | Medium (default) | Filesystem mutations |
1503
+ | `file_write`, `file_edit` targeting skill source paths | **High** | `isSkillSourcePath()` detects managed/bundled/workspace/extra skill roots |
1504
+ | `host_file_write`, `host_file_edit` targeting skill source paths | **High** | Same path classification, host variant |
1505
+ | `bash`, `host_bash` | Varies | Parsed via tree-sitter: low-risk programs = Low, high-risk programs = High, unknown = Medium |
1506
+ | `scaffold_managed_skill`, `delete_managed_skill` | High | Skill lifecycle mutations always high-risk |
1507
+ | `evaluate_typescript_code` | High | Arbitrary code execution |
1508
+ | Skill-origin tools with no matching rule | Prompted regardless of risk | Even Low-risk skill tools default to `ask` |
1490
1509
 
1491
1510
  The escalation of skill source file mutations to High risk is a privilege-escalation defense: modifying skill source code could grant the agent new capabilities, so such operations always require explicit approval.
1492
1511
 
@@ -1504,14 +1523,14 @@ In strict mode, `skill_load` without a matching rule is always prompted. In lega
1504
1523
 
1505
1524
  The starter bundle is an opt-in set of low-risk allow rules that reduces prompt noise, particularly in strict mode. It covers read-only tools that never mutate the filesystem or execute arbitrary code:
1506
1525
 
1507
- | Rule | Tool | Pattern |
1508
- |---|---|---|
1509
- | `file_read` | `file_read` | `file_read:**` |
1510
- | `glob` | `glob` | `glob:**` |
1511
- | `grep` | `grep` | `grep:**` |
1526
+ | Rule | Tool | Pattern |
1527
+ | ---------------- | ---------------- | ------------------- |
1528
+ | `file_read` | `file_read` | `file_read:**` |
1529
+ | `glob` | `glob` | `glob:**` |
1530
+ | `grep` | `grep` | `grep:**` |
1512
1531
  | `list_directory` | `list_directory` | `list_directory:**` |
1513
- | `web_search` | `web_search` | `web_search:**` |
1514
- | `web_fetch` | `web_fetch` | `web_fetch:**` |
1532
+ | `web_search` | `web_search` | `web_search:**` |
1533
+ | `web_fetch` | `web_fetch` | `web_fetch:**` |
1515
1534
 
1516
1535
  Acceptance is idempotent and persisted as `starterBundleAccepted: true` in `trust.json`. Rules are seeded at priority 90 (below user rules at 100, above system defaults at 50).
1517
1536
 
@@ -1519,19 +1538,19 @@ Acceptance is idempotent and persisted as `starterBundleAccepted: true` in `trus
1519
1538
 
1520
1539
  In addition to the opt-in starter bundle, the permission system seeds unconditional default allow rules at priority 100 for two categories:
1521
1540
 
1522
- | Rule ID | Tool | Pattern | Rationale |
1523
- |---|---|---|---|
1524
- | `default:allow-skill_load-global` | `skill_load` | `skill_load:*` | Loading any skill is globally allowed — no prompt for activating bundled, managed, or workspace skills |
1525
- | `default:allow-browser_navigate-global` | `browser_navigate` | `browser_navigate:*` | Browser tools migrated from core to the bundled `browser` skill; default allow preserves frictionless UX |
1526
- | `default:allow-browser_snapshot-global` | `browser_snapshot` | `browser_snapshot:*` | (same) |
1527
- | `default:allow-browser_screenshot-global` | `browser_screenshot` | `browser_screenshot:*` | (same) |
1528
- | `default:allow-browser_close-global` | `browser_close` | `browser_close:*` | (same) |
1529
- | `default:allow-browser_click-global` | `browser_click` | `browser_click:*` | (same) |
1530
- | `default:allow-browser_type-global` | `browser_type` | `browser_type:*` | (same) |
1531
- | `default:allow-browser_press_key-global` | `browser_press_key` | `browser_press_key:*` | (same) |
1532
- | `default:allow-browser_wait_for-global` | `browser_wait_for` | `browser_wait_for:*` | (same) |
1533
- | `default:allow-browser_extract-global` | `browser_extract` | `browser_extract:*` | (same) |
1534
- | `default:allow-browser_fill_credential-global` | `browser_fill_credential` | `browser_fill_credential:*` | (same) |
1541
+ | Rule ID | Tool | Pattern | Rationale |
1542
+ | ---------------------------------------------- | ------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------- |
1543
+ | `default:allow-skill_load-global` | `skill_load` | `skill_load:*` | Loading any skill is globally allowed — no prompt for activating bundled, managed, or workspace skills |
1544
+ | `default:allow-browser_navigate-global` | `browser_navigate` | `browser_navigate:*` | Browser tools migrated from core to the bundled `browser` skill; default allow preserves frictionless UX |
1545
+ | `default:allow-browser_snapshot-global` | `browser_snapshot` | `browser_snapshot:*` | (same) |
1546
+ | `default:allow-browser_screenshot-global` | `browser_screenshot` | `browser_screenshot:*` | (same) |
1547
+ | `default:allow-browser_close-global` | `browser_close` | `browser_close:*` | (same) |
1548
+ | `default:allow-browser_click-global` | `browser_click` | `browser_click:*` | (same) |
1549
+ | `default:allow-browser_type-global` | `browser_type` | `browser_type:*` | (same) |
1550
+ | `default:allow-browser_press_key-global` | `browser_press_key` | `browser_press_key:*` | (same) |
1551
+ | `default:allow-browser_wait_for-global` | `browser_wait_for` | `browser_wait_for:*` | (same) |
1552
+ | `default:allow-browser_extract-global` | `browser_extract` | `browser_extract:*` | (same) |
1553
+ | `default:allow-browser_fill_credential-global` | `browser_fill_credential` | `browser_fill_credential:*` | (same) |
1535
1554
 
1536
1555
  These rules are emitted by `getDefaultRuleTemplates()` in `assistant/src/permissions/defaults.ts`. Because they use priority 100 (equal to user rules), they take effect in both strict and legacy modes. The `skill_load` rule means skill activation never prompts; the `browser_*` rules mean the browser skill's tools behave identically to the old core `headless-browser` tool from a permission standpoint.
1537
1556
 
@@ -1554,14 +1573,14 @@ For `bash` and `host_bash` tool invocations, the permission system uses parser-d
1554
1573
 
1555
1574
  When a permission prompt is sent to the client (via `confirmation_request` IPC message), it includes:
1556
1575
 
1557
- | Field | Content |
1558
- |---|---|
1559
- | `toolName` | The tool being invoked |
1560
- | `input` | Redacted tool input (sensitive fields removed) |
1561
- | `riskLevel` | `low`, `medium`, or `high` |
1562
- | `executionTarget` | `sandbox` or `host` — where the action will execute |
1563
- | `allowlistOptions` | Suggested patterns for "always allow" rules |
1564
- | `scopeOptions` | Suggested scopes for rule persistence |
1576
+ | Field | Content |
1577
+ | ------------------ | --------------------------------------------------- |
1578
+ | `toolName` | The tool being invoked |
1579
+ | `input` | Redacted tool input (sensitive fields removed) |
1580
+ | `riskLevel` | `low`, `medium`, or `high` |
1581
+ | `executionTarget` | `sandbox` or `host` — where the action will execute |
1582
+ | `allowlistOptions` | Suggested patterns for "always allow" rules |
1583
+ | `scopeOptions` | Suggested scopes for rule persistence |
1565
1584
 
1566
1585
  The user can respond with: `allow` (one-time), `always_allow` (create allow rule), `always_allow_high_risk` (create allow rule with `allowHighRisk: true`), `deny` (one-time), or `always_deny` (create deny rule).
1567
1586
 
@@ -1571,19 +1590,19 @@ File tool candidates include canonical (symlink-resolved) absolute paths via `no
1571
1590
 
1572
1591
  ### Key Source Files
1573
1592
 
1574
- | File | Role |
1575
- |---|---|
1576
- | `assistant/src/permissions/types.ts` | `TrustRule`, `PolicyContext`, `RiskLevel`, `UserDecision` types |
1577
- | `assistant/src/permissions/checker.ts` | `classifyRisk()`, `check()`, `buildCommandCandidates()`, allowlist/scope generation |
1593
+ | File | Role |
1594
+ | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1595
+ | `assistant/src/permissions/types.ts` | `TrustRule`, `PolicyContext`, `RiskLevel`, `UserDecision` types |
1596
+ | `assistant/src/permissions/checker.ts` | `classifyRisk()`, `check()`, `buildCommandCandidates()`, allowlist/scope generation |
1578
1597
  | `assistant/src/permissions/shell-identity.ts` | `analyzeShellCommand()`, `deriveShellActionKeys()`, `buildShellCommandCandidates()`, `buildShellAllowlistOptions()` — parser-based shell command identity and action key derivation |
1579
- | `assistant/src/permissions/trust-store.ts` | Rule persistence, `findHighestPriorityRule()`, execution-target matching, starter bundle |
1580
- | `assistant/src/permissions/prompter.ts` | IPC prompt flow: `confirmation_request` → `confirmation_response` |
1581
- | `assistant/src/permissions/defaults.ts` | Default rule templates (system ask rules for host tools, CU, etc.) |
1582
- | `assistant/src/skills/version-hash.ts` | `computeSkillVersionHash()` — deterministic SHA-256 of skill source files |
1583
- | `assistant/src/skills/path-classifier.ts` | `isSkillSourcePath()`, `normalizeFilePath()`, skill root detection |
1584
- | `assistant/src/config/schema.ts` | `PermissionsConfigSchema` — `permissions.mode` (`workspace` / `strict` / `legacy`) |
1585
- | `assistant/src/tools/executor.ts` | `ToolExecutor` — orchestrates risk classification, permission check, and execution |
1586
- | `assistant/src/daemon/handlers/config.ts` | `handleToolPermissionSimulate()` — dry-run simulation handler |
1598
+ | `assistant/src/permissions/trust-store.ts` | Rule persistence, `findHighestPriorityRule()`, execution-target matching, starter bundle |
1599
+ | `assistant/src/permissions/prompter.ts` | IPC prompt flow: `confirmation_request` → `confirmation_response` |
1600
+ | `assistant/src/permissions/defaults.ts` | Default rule templates (system ask rules for host tools, CU, etc.) |
1601
+ | `assistant/src/skills/version-hash.ts` | `computeSkillVersionHash()` — deterministic SHA-256 of skill source files |
1602
+ | `assistant/src/skills/path-classifier.ts` | `isSkillSourcePath()`, `normalizeFilePath()`, skill root detection |
1603
+ | `assistant/src/config/schema.ts` | `PermissionsConfigSchema` — `permissions.mode` (`workspace` / `strict` / `legacy`) |
1604
+ | `assistant/src/tools/executor.ts` | `ToolExecutor` — orchestrates risk classification, permission check, and execution |
1605
+ | `assistant/src/daemon/handlers/config.ts` | `handleToolPermissionSimulate()` — dry-run simulation handler |
1587
1606
 
1588
1607
  ### Permission Simulation (Tool Permission Tester)
1589
1608
 
@@ -1656,15 +1675,15 @@ sequenceDiagram
1656
1675
 
1657
1676
  ### Config knobs
1658
1677
 
1659
- | Config key | Default | Purpose |
1660
- |---|---:|---|
1661
- | `swarm.enabled` | `true` | Master switch for swarm orchestration |
1662
- | `swarm.maxWorkers` | `3` | Max concurrent worker processes (hard ceiling: 6) |
1663
- | `swarm.maxTasks` | `8` | Max tasks per plan (hard ceiling: 20) |
1664
- | `swarm.maxRetriesPerTask` | `1` | Per-task retry limit (hard ceiling: 3) |
1665
- | `swarm.workerTimeoutSec` | `900` | Worker timeout in seconds |
1666
- | `swarm.plannerModel` | (varies) | Model used for plan generation |
1667
- | `swarm.synthesizerModel` | (varies) | Model used for result synthesis |
1678
+ | Config key | Default | Purpose |
1679
+ | ------------------------- | -------: | ------------------------------------------------- |
1680
+ | `swarm.enabled` | `true` | Master switch for swarm orchestration |
1681
+ | `swarm.maxWorkers` | `3` | Max concurrent worker processes (hard ceiling: 6) |
1682
+ | `swarm.maxTasks` | `8` | Max tasks per plan (hard ceiling: 20) |
1683
+ | `swarm.maxRetriesPerTask` | `1` | Per-task retry limit (hard ceiling: 3) |
1684
+ | `swarm.workerTimeoutSec` | `900` | Worker timeout in seconds |
1685
+ | `swarm.plannerModel` | (varies) | Model used for plan generation |
1686
+ | `swarm.synthesizerModel` | (varies) | Model used for result synthesis |
1668
1687
 
1669
1688
  ---
1670
1689
 
@@ -1767,24 +1786,24 @@ sequenceDiagram
1767
1786
 
1768
1787
  Events emitted during a session lifecycle:
1769
1788
 
1770
- | Kind | Emitted by | When |
1771
- |------|-----------|------|
1772
- | `request_received` | Handlers / Session | User message or surface action arrives |
1773
- | `request_queued` | Handlers / Session | Message queued while session is busy |
1774
- | `request_dequeued` | Session | Queued message begins processing |
1775
- | `llm_call_started` | Session | LLM API call initiated |
1776
- | `llm_call_finished` | Session | LLM API call completed (carries `inputTokens`, `outputTokens`, `latencyMs`) |
1777
- | `assistant_message` | Session | Assistant response assembled (carries `toolUseCount`) |
1778
- | `tool_started` | ToolTraceListener | Tool execution begins |
1779
- | `tool_permission_requested` | ToolTraceListener | Permission check needed (carries `riskLevel`) |
1780
- | `tool_permission_decided` | ToolTraceListener | Permission granted or denied (carries `decision`) |
1781
- | `tool_finished` | ToolTraceListener | Tool execution completed (carries `durationMs`) |
1782
- | `tool_failed` | ToolTraceListener | Tool execution failed (carries `durationMs`) |
1783
- | `secret_detected` | ToolTraceListener | Secret found in tool output |
1784
- | `generation_handoff` | Session | Yielding to next queued message |
1785
- | `message_complete` | Session | Full request processing finished |
1786
- | `generation_cancelled` | Session | User cancelled the generation |
1787
- | `request_error` | Handlers / Session | Unrecoverable error during processing (includes queue-full rejection and persist-failure paths) |
1789
+ | Kind | Emitted by | When |
1790
+ | --------------------------- | ------------------ | ----------------------------------------------------------------------------------------------- |
1791
+ | `request_received` | Handlers / Session | User message or surface action arrives |
1792
+ | `request_queued` | Handlers / Session | Message queued while session is busy |
1793
+ | `request_dequeued` | Session | Queued message begins processing |
1794
+ | `llm_call_started` | Session | LLM API call initiated |
1795
+ | `llm_call_finished` | Session | LLM API call completed (carries `inputTokens`, `outputTokens`, `latencyMs`) |
1796
+ | `assistant_message` | Session | Assistant response assembled (carries `toolUseCount`) |
1797
+ | `tool_started` | ToolTraceListener | Tool execution begins |
1798
+ | `tool_permission_requested` | ToolTraceListener | Permission check needed (carries `riskLevel`) |
1799
+ | `tool_permission_decided` | ToolTraceListener | Permission granted or denied (carries `decision`) |
1800
+ | `tool_finished` | ToolTraceListener | Tool execution completed (carries `durationMs`) |
1801
+ | `tool_failed` | ToolTraceListener | Tool execution failed (carries `durationMs`) |
1802
+ | `secret_detected` | ToolTraceListener | Secret found in tool output |
1803
+ | `generation_handoff` | Session | Yielding to next queued message |
1804
+ | `message_complete` | Session | Full request processing finished |
1805
+ | `generation_cancelled` | Session | User cancelled the generation |
1806
+ | `request_error` | Handlers / Session | Unrecoverable error during processing (includes queue-full rejection and persist-failure paths) |
1788
1807
 
1789
1808
  ### Architecture
1790
1809
 
@@ -1796,7 +1815,6 @@ Events emitted during a session lifecycle:
1796
1815
 
1797
1816
  ---
1798
1817
 
1799
-
1800
1818
  ---
1801
1819
 
1802
1820
  ## Assistant Events — SSE Transport Layer
@@ -1843,13 +1861,13 @@ graph TB
1843
1861
 
1844
1862
  Every event published through the hub is wrapped in an `AssistantEvent` (defined in `runtime/assistant-event.ts`):
1845
1863
 
1846
- | Field | Type | Description |
1847
- |---|---|---|
1848
- | `id` | `string` (UUID) | Globally unique event identifier |
1849
- | `assistantId` | `string` | Logical assistant identifier (`"self"` for HTTP runs) |
1850
- | `sessionId` | `string?` | Resolved conversation ID when available |
1851
- | `emittedAt` | `string` (ISO-8601) | Server-side timestamp |
1852
- | `message` | `ServerMessage` | Unchanged IPC outbound message — no schema fork |
1864
+ | Field | Type | Description |
1865
+ | ------------- | ------------------- | ----------------------------------------------------- |
1866
+ | `id` | `string` (UUID) | Globally unique event identifier |
1867
+ | `assistantId` | `string` | Logical assistant identifier (`"self"` for HTTP runs) |
1868
+ | `sessionId` | `string?` | Resolved conversation ID when available |
1869
+ | `emittedAt` | `string` (ISO-8601) | Server-side timestamp |
1870
+ | `message` | `ServerMessage` | Unchanged IPC outbound message — no schema fork |
1853
1871
 
1854
1872
  ### SSE Frame Format
1855
1873
 
@@ -1869,22 +1887,22 @@ Keep-alive heartbeats (every 30 s by default):
1869
1887
 
1870
1888
  ### Subscription Lifecycle
1871
1889
 
1872
- | Event | Action |
1873
- |---|---|
1874
- | `GET /v1/events` received | Hub subscribes eagerly before `ReadableStream` is created |
1875
- | Client disconnects / aborts | `req.signal` abort listener disposes subscription and closes stream |
1876
- | Client cancels reader | `ReadableStream.cancel()` disposes subscription and closes stream |
1877
- | New connection pushes over cap (100) | Oldest subscriber evicted (FIFO); its `onEvict` callback closes its stream |
1878
- | Client buffer full (16 queued frames) | `desiredSize <= 0` guard sheds the subscriber immediately |
1890
+ | Event | Action |
1891
+ | ------------------------------------- | -------------------------------------------------------------------------- |
1892
+ | `GET /v1/events` received | Hub subscribes eagerly before `ReadableStream` is created |
1893
+ | Client disconnects / aborts | `req.signal` abort listener disposes subscription and closes stream |
1894
+ | Client cancels reader | `ReadableStream.cancel()` disposes subscription and closes stream |
1895
+ | New connection pushes over cap (100) | Oldest subscriber evicted (FIFO); its `onEvict` callback closes its stream |
1896
+ | Client buffer full (16 queued frames) | `desiredSize <= 0` guard sheds the subscriber immediately |
1879
1897
 
1880
1898
  ### Key Source Files
1881
1899
 
1882
- | File | Role |
1883
- |---|---|
1884
- | `assistant/src/runtime/assistant-event.ts` | `AssistantEvent` type, `buildAssistantEvent()` factory, SSE framing helpers |
1885
- | `assistant/src/runtime/assistant-event-hub.ts` | `AssistantEventHub` class and process-level singleton |
1886
- | `assistant/src/runtime/routes/events-routes.ts` | `handleSubscribeAssistantEvents()` — SSE route handler |
1887
- | `assistant/src/daemon/server.ts` | IPC send/broadcast paths that publish to the hub (`send` → `publishAssistantEvent`) |
1900
+ | File | Role |
1901
+ | ----------------------------------------------- | ----------------------------------------------------------------------------------- |
1902
+ | `assistant/src/runtime/assistant-event.ts` | `AssistantEvent` type, `buildAssistantEvent()` factory, SSE framing helpers |
1903
+ | `assistant/src/runtime/assistant-event-hub.ts` | `AssistantEventHub` class and process-level singleton |
1904
+ | `assistant/src/runtime/routes/events-routes.ts` | `handleSubscribeAssistantEvents()` — SSE route handler |
1905
+ | `assistant/src/daemon/server.ts` | IPC send/broadcast paths that publish to the hub (`send` → `publishAssistantEvent`) |
1888
1906
 
1889
1907
  ---
1890
1908
 
@@ -1985,24 +2003,24 @@ Connected channels are resolved at signal emission time: vellum is always includ
1985
2003
 
1986
2004
  **Key modules:**
1987
2005
 
1988
- | Module | Purpose |
1989
- |--------|---------|
1990
- | `assistant/src/channels/config.ts` | Channel policy registry — single source of truth for per-channel notification behavior |
1991
- | `assistant/src/notifications/emit-signal.ts` | Single entry point for all producers; orchestrates the full pipeline |
1992
- | `assistant/src/notifications/decision-engine.ts` | LLM-based routing decisions with deterministic fallback |
1993
- | `assistant/src/notifications/deterministic-checks.ts` | Hard invariant checks (dedupe, source-active suppression, channel availability) |
1994
- | `assistant/src/notifications/broadcaster.ts` | Dispatches decisions to channel adapters; emits `notification_thread_created` IPC (creation-only) |
1995
- | `assistant/src/notifications/conversation-pairing.ts` | Materializes conversation + message per delivery; executes thread reuse decisions |
1996
- | `assistant/src/notifications/thread-candidates.ts` | Builds per-channel candidate set of recent conversations for the decision engine |
1997
- | `assistant/src/notifications/adapters/macos.ts` | Vellum adapter — broadcasts `notification_intent` via IPC with deep-link metadata |
1998
- | `assistant/src/notifications/adapters/telegram.ts` | Telegram adapter — POSTs to gateway `/deliver/telegram` |
1999
- | `assistant/src/notifications/adapters/sms.ts` | SMS adapter — POSTs to gateway `/deliver/sms` via Twilio Messages API |
2000
- | `assistant/src/notifications/destination-resolver.ts` | Resolves per-channel endpoints (vellum IPC, Telegram chat ID from guardian binding) |
2001
- | `assistant/src/notifications/copy-composer.ts` | Template-based fallback copy when LLM copy is unavailable |
2002
- | `assistant/src/notifications/preference-extractor.ts` | Detects preference statements in conversation messages |
2003
- | `assistant/src/notifications/preferences-store.ts` | CRUD for user notification preferences |
2004
- | `assistant/src/config/bundled-skills/messaging/tools/send-notification.ts` | Explicit producer tool for user-requested notifications; emits signals into the same routing pipeline |
2005
- | `assistant/src/calls/guardian-dispatch.ts` | Guardian question dispatch that reuses canonical notification pairing and records guardian delivery bookkeeping from pipeline results |
2006
+ | Module | Purpose |
2007
+ | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
2008
+ | `assistant/src/channels/config.ts` | Channel policy registry — single source of truth for per-channel notification behavior |
2009
+ | `assistant/src/notifications/emit-signal.ts` | Single entry point for all producers; orchestrates the full pipeline |
2010
+ | `assistant/src/notifications/decision-engine.ts` | LLM-based routing decisions with deterministic fallback |
2011
+ | `assistant/src/notifications/deterministic-checks.ts` | Hard invariant checks (dedupe, source-active suppression, channel availability) |
2012
+ | `assistant/src/notifications/broadcaster.ts` | Dispatches decisions to channel adapters; emits `notification_thread_created` IPC (creation-only) |
2013
+ | `assistant/src/notifications/conversation-pairing.ts` | Materializes conversation + message per delivery; executes thread reuse decisions |
2014
+ | `assistant/src/notifications/thread-candidates.ts` | Builds per-channel candidate set of recent conversations for the decision engine |
2015
+ | `assistant/src/notifications/adapters/macos.ts` | Vellum adapter — broadcasts `notification_intent` via IPC with deep-link metadata |
2016
+ | `assistant/src/notifications/adapters/telegram.ts` | Telegram adapter — POSTs to gateway `/deliver/telegram` |
2017
+ | `assistant/src/notifications/adapters/sms.ts` | SMS adapter — POSTs to gateway `/deliver/sms` via Twilio Messages API |
2018
+ | `assistant/src/notifications/destination-resolver.ts` | Resolves per-channel endpoints (vellum IPC, Telegram chat ID from guardian binding) |
2019
+ | `assistant/src/notifications/copy-composer.ts` | Template-based fallback copy when LLM copy is unavailable |
2020
+ | `assistant/src/notifications/preference-extractor.ts` | Detects preference statements in conversation messages |
2021
+ | `assistant/src/notifications/preferences-store.ts` | CRUD for user notification preferences |
2022
+ | `assistant/src/config/bundled-skills/messaging/tools/send-notification.ts` | Explicit producer tool for user-requested notifications; emits signals into the same routing pipeline |
2023
+ | `assistant/src/calls/guardian-dispatch.ts` | Guardian question dispatch that reuses canonical notification pairing and records guardian delivery bookkeeping from pipeline results |
2006
2024
 
2007
2025
  **Audit trail (SQLite):** `notification_events` → `notification_decisions` (with `threadActions` in validation results) → `notification_deliveries` (with `conversation_id`, `message_id`, `conversation_strategy`, `thread_action`, `thread_target_conversation_id`, `thread_decision_fallback_used`)
2008
2026
 
@@ -2012,50 +2030,48 @@ Connected channels are resolved at signal emission time: vellum is always includ
2012
2030
 
2013
2031
  ## Storage Summary
2014
2032
 
2015
- | What | Where | Format | ORM/Driver | Retention |
2016
- |------|-------|--------|-----------|-----------|
2017
- | API key | macOS Keychain | Encrypted binary | `/usr/bin/security` CLI | Permanent |
2018
- | Credential secrets | macOS Keychain (or encrypted file fallback) | Encrypted binary | `secure-keys.ts` wrapper | Permanent (until deleted via tool) |
2019
- | Credential metadata | `~/.vellum/workspace/data/credentials/metadata.json` | JSON | Atomic file write | Permanent (until deleted via tool) |
2020
- | Integration OAuth tokens | macOS Keychain (or encrypted file fallback, via `secure-keys.ts`) | Encrypted binary | `TokenManager` auto-refresh | Until disconnected or revoked |
2021
- | User preferences | UserDefaults | plist | Foundation | Permanent |
2022
- | Session logs | `~/Library/.../logs/session-*.json` | JSON per session | Swift Codable | Unbounded |
2023
- | Conversations & messages | `~/.vellum/workspace/data/db/assistant.db` | SQLite + WAL | Drizzle ORM (Bun) | Permanent |
2024
- | Memory segments & FTS | `~/.vellum/workspace/data/db/assistant.db` | SQLite FTS5 | Drizzle ORM | Permanent |
2025
- | Extracted facts | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, deduped |
2026
- | Conflict lifecycle rows | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Pending until clarified, then retained as resolved history |
2027
- | Entity graph (entities/relations/item links) | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, deduped by unique relation edge |
2028
- | Embeddings | `~/.vellum/workspace/data/db/assistant.db` | JSON float arrays | Drizzle ORM | Permanent |
2029
- | Async job queue | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Completed jobs persist |
2030
- | Attachments | `~/.vellum/workspace/data/db/assistant.db` | Base64 in SQLite | Drizzle ORM | Permanent |
2031
- | Sandbox filesystem | `~/.vellum/workspace` | Real filesystem tree | Node FS APIs | Persistent across sessions |
2032
- | Tool permission rules | `~/.vellum/protected/trust.json` | JSON | File I/O | Permanent |
2033
- | Web users & assistants | PostgreSQL | Relational | Drizzle ORM (pg) | Permanent |
2034
- | Trace events | In-memory (TraceStore) | Structured events | Swift ObservableObject | Max 5,000 per session, ephemeral |
2035
- | Media embed settings | `~/.vellum/workspace/config.json` (`ui.mediaEmbeds`) | JSON | `WorkspaceConfigIO` (atomic merge) | Permanent |
2036
- | Media embed MIME cache | In-memory (`ImageMIMEProbe`) | `NSCache` (500 entries) | HTTP HEAD | Ephemeral; cleared on app restart |
2037
- | IPC blob payloads | `~/.vellum/workspace/data/ipc-blobs/` | Binary files (UUID names) | File I/O (atomic write) | Ephemeral; consumed on hydration, stale sweep every 5min |
2038
- | Tasks & task runs | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent |
2039
- | Work items (Task Queue) | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; archived items retained |
2040
- | Recurrence schedules & runs | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; supports cron and RRULE syntax |
2041
- | Watchers & events | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, cascade on watcher delete |
2042
- | Proxy CA cert + key | `{dataDir}/proxy-ca/` | PEM files (ca.pem, ca-key.pem) | openssl CLI | Permanent (10-year validity) |
2043
- | Proxy leaf certs | `{dataDir}/proxy-ca/issued/` | PEM files per hostname | openssl CLI, cached | 1-year validity, re-issued on CA change |
2044
- | Proxy sessions | In-memory (SessionManager) | Map<ProxySessionId, ManagedSession> | Manual lifecycle | Ephemeral; 5min idle timeout, cleared on shutdown |
2045
- | Call sessions, events, pending questions | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, cascade on session delete |
2046
- | Active call controllers | In-memory (CallState) | Map<callSessionId, CallController> | Manual lifecycle | Ephemeral; cleared on call end or destroy |
2047
- | Guardian bindings | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; revoked bindings retained |
2048
- | Guardian verification challenges | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; consumed/expired challenges retained |
2049
- | Guardian approval requests | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; decision outcome retained |
2050
- | Ingress invites | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; token hash stored, raw token never persisted |
2051
- | Ingress members | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; revoked/blocked members retained |
2052
- | Notification events | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; deduplicated by dedupeKey |
2053
- | Notification decisions | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; FK to notification_events |
2054
- | Notification deliveries | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; FK to notification_decisions |
2055
- | Notification preferences | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; per-assistant conversational preferences |
2056
- | IPC transport | `~/.vellum/vellum.sock` | Unix domain socket | NWConnection (Swift) / Bun net | Ephemeral |
2057
-
2058
-
2033
+ | What | Where | Format | ORM/Driver | Retention |
2034
+ | -------------------------------------------- | ----------------------------------------------------------------- | ----------------------------------- | ---------------------------------- | ---------------------------------------------------------- |
2035
+ | API key | macOS Keychain | Encrypted binary | `/usr/bin/security` CLI | Permanent |
2036
+ | Credential secrets | macOS Keychain (or encrypted file fallback) | Encrypted binary | `secure-keys.ts` wrapper | Permanent (until deleted via tool) |
2037
+ | Credential metadata | `~/.vellum/workspace/data/credentials/metadata.json` | JSON | Atomic file write | Permanent (until deleted via tool) |
2038
+ | Integration OAuth tokens | macOS Keychain (or encrypted file fallback, via `secure-keys.ts`) | Encrypted binary | `TokenManager` auto-refresh | Until disconnected or revoked |
2039
+ | User preferences | UserDefaults | plist | Foundation | Permanent |
2040
+ | Session logs | `~/Library/.../logs/session-*.json` | JSON per session | Swift Codable | Unbounded |
2041
+ | Conversations & messages | `~/.vellum/workspace/data/db/assistant.db` | SQLite + WAL | Drizzle ORM (Bun) | Permanent |
2042
+ | Memory segments & FTS | `~/.vellum/workspace/data/db/assistant.db` | SQLite FTS5 | Drizzle ORM | Permanent |
2043
+ | Extracted facts | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, deduped |
2044
+ | Conflict lifecycle rows | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Pending until clarified, then retained as resolved history |
2045
+ | Entity graph (entities/relations/item links) | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, deduped by unique relation edge |
2046
+ | Embeddings | `~/.vellum/workspace/data/db/assistant.db` | JSON float arrays | Drizzle ORM | Permanent |
2047
+ | Async job queue | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Completed jobs persist |
2048
+ | Attachments | `~/.vellum/workspace/data/db/assistant.db` | Base64 in SQLite | Drizzle ORM | Permanent |
2049
+ | Sandbox filesystem | `~/.vellum/workspace` | Real filesystem tree | Node FS APIs | Persistent across sessions |
2050
+ | Tool permission rules | `~/.vellum/protected/trust.json` | JSON | File I/O | Permanent |
2051
+ | Web users & assistants | PostgreSQL | Relational | Drizzle ORM (pg) | Permanent |
2052
+ | Trace events | In-memory (TraceStore) | Structured events | Swift ObservableObject | Max 5,000 per session, ephemeral |
2053
+ | Media embed settings | `~/.vellum/workspace/config.json` (`ui.mediaEmbeds`) | JSON | `WorkspaceConfigIO` (atomic merge) | Permanent |
2054
+ | Media embed MIME cache | In-memory (`ImageMIMEProbe`) | `NSCache` (500 entries) | HTTP HEAD | Ephemeral; cleared on app restart |
2055
+ | IPC blob payloads | `~/.vellum/workspace/data/ipc-blobs/` | Binary files (UUID names) | File I/O (atomic write) | Ephemeral; consumed on hydration, stale sweep every 5min |
2056
+ | Tasks & task runs | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent |
2057
+ | Work items (Task Queue) | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; archived items retained |
2058
+ | Recurrence schedules & runs | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; supports cron and RRULE syntax |
2059
+ | Watchers & events | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, cascade on watcher delete |
2060
+ | Proxy CA cert + key | `{dataDir}/proxy-ca/` | PEM files (ca.pem, ca-key.pem) | openssl CLI | Permanent (10-year validity) |
2061
+ | Proxy leaf certs | `{dataDir}/proxy-ca/issued/` | PEM files per hostname | openssl CLI, cached | 1-year validity, re-issued on CA change |
2062
+ | Proxy sessions | In-memory (SessionManager) | Map<ProxySessionId, ManagedSession> | Manual lifecycle | Ephemeral; 5min idle timeout, cleared on shutdown |
2063
+ | Call sessions, events, pending questions | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent, cascade on session delete |
2064
+ | Active call controllers | In-memory (CallState) | Map<callSessionId, CallController> | Manual lifecycle | Ephemeral; cleared on call end or destroy |
2065
+ | Guardian bindings | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; revoked bindings retained |
2066
+ | Guardian verification challenges | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; consumed/expired challenges retained |
2067
+ | Guardian approval requests | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; decision outcome retained |
2068
+ | Ingress invites | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; token hash stored, raw token never persisted |
2069
+ | Ingress members | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; revoked/blocked members retained |
2070
+ | Notification events | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; deduplicated by dedupeKey |
2071
+ | Notification decisions | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; FK to notification_events |
2072
+ | Notification deliveries | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; FK to notification_decisions |
2073
+ | Notification preferences | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; per-assistant conversational preferences |
2074
+ | IPC transport | `~/.vellum/vellum.sock` | Unix domain socket | NWConnection (Swift) / Bun net | Ephemeral |
2059
2075
 
2060
2076
  ### Sensitive Tool Output Placeholder Substitution
2061
2077
 
@@ -2067,7 +2083,7 @@ Some tool outputs contain values that must reach the user's final reply but shou
2067
2083
 
2068
2084
  3. **Post-generation substitution** (`src/agent/loop.ts`): Before emitting streamed `text_delta` events and before building the final `assistantMessage`, all placeholders are deterministically replaced with their real values. The substitution is chunk-safe for streaming (buffering partial placeholder prefixes across deltas).
2069
2085
 
2070
- Key files: `src/tools/sensitive-output-placeholders.ts`, `src/tools/executor.ts` (extraction hook), `src/agent/loop.ts` (substitution), `src/config/vellum-skills/trusted-contacts/SKILL.md` (invite flow adoption).
2086
+ Key files: `src/tools/sensitive-output-placeholders.ts`, `src/tools/executor.ts` (extraction hook), `src/agent/loop.ts` (substitution), `src/config/bundled-skills/trusted-contacts/SKILL.md` (invite flow adoption).
2071
2087
 
2072
2088
  ### Notifications
2073
2089
 
@@ -2081,10 +2097,10 @@ The daemon uses a single fixed internal scope constant — `DAEMON_INTERNAL_ASSI
2081
2097
 
2082
2098
  **Key files:**
2083
2099
 
2084
- | File | Purpose |
2085
- |------|---------|
2086
- | `src/runtime/assistant-scope.ts` | Exports `DAEMON_INTERNAL_ASSISTANT_ID` constant |
2087
- | `src/__tests__/assistant-id-boundary-guard.test.ts` | Guard tests enforcing the identity boundary |
2100
+ | File | Purpose |
2101
+ | --------------------------------------------------- | ----------------------------------------------- |
2102
+ | `src/runtime/assistant-scope.ts` | Exports `DAEMON_INTERNAL_ASSISTANT_ID` constant |
2103
+ | `src/__tests__/assistant-id-boundary-guard.test.ts` | Guard tests enforcing the identity boundary |
2088
2104
 
2089
2105
  ### Canonical Trust-Context Model
2090
2106
 
@@ -2102,10 +2118,10 @@ The guardian trust system uses a three-valued `TrustClass` — `'guardian'`, `'t
2102
2118
 
2103
2119
  **Key files:**
2104
2120
 
2105
- | File | Purpose |
2106
- |------|---------|
2107
- | `src/daemon/session-runtime-assembly.ts` | `GuardianRuntimeContext` type definition |
2108
- | `src/tools/types.ts` | `ToolContext.guardianTrustClass` (required trust gate) |
2109
- | `src/runtime/channel-retry-sweep.ts` | Strict `trustClass` parser for retry sweep |
2110
- | `src/memory/guardian-bindings.ts` | `GuardianBinding` with required `guardianPrincipalId` |
2111
- | `src/__tests__/trust-context-guards.test.ts` | Guard tests enforcing trust-context type invariants |
2121
+ | File | Purpose |
2122
+ | -------------------------------------------- | ------------------------------------------------------ |
2123
+ | `src/daemon/session-runtime-assembly.ts` | `GuardianRuntimeContext` type definition |
2124
+ | `src/tools/types.ts` | `ToolContext.guardianTrustClass` (required trust gate) |
2125
+ | `src/runtime/channel-retry-sweep.ts` | Strict `trustClass` parser for retry sweep |
2126
+ | `src/memory/guardian-bindings.ts` | `GuardianBinding` with required `guardianPrincipalId` |
2127
+ | `src/__tests__/trust-context-guards.test.ts` | Guard tests enforcing trust-context type invariants |