@vellumai/assistant 0.4.29 → 0.4.31

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 (237) hide show
  1. package/ARCHITECTURE.md +39 -37
  2. package/Dockerfile +14 -8
  3. package/README.md +7 -8
  4. package/docs/architecture/memory.md +28 -29
  5. package/docs/runbook-trusted-contacts.md +76 -43
  6. package/package.json +1 -1
  7. package/scripts/ipc/check-swift-decoder-drift.ts +2 -3
  8. package/scripts/test.sh +1 -1
  9. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +4 -37
  10. package/src/__tests__/actor-token-service.test.ts +4 -3
  11. package/src/__tests__/app-executors.test.ts +7 -17
  12. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
  13. package/src/__tests__/browser-skill-endstate.test.ts +10 -1
  14. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -0
  15. package/src/__tests__/channel-approval-routes.test.ts +44 -44
  16. package/src/__tests__/channel-approval.test.ts +8 -0
  17. package/src/__tests__/channel-approvals.test.ts +39 -1
  18. package/src/__tests__/channel-guardian.test.ts +15 -5
  19. package/src/__tests__/channel-reply-delivery.test.ts +31 -0
  20. package/src/__tests__/config-schema.test.ts +0 -9
  21. package/src/__tests__/conflict-policy.test.ts +76 -0
  22. package/src/__tests__/conflict-store.test.ts +14 -20
  23. package/src/__tests__/contacts-tools.test.ts +8 -61
  24. package/src/__tests__/contradiction-checker.test.ts +5 -1
  25. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
  26. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  27. package/src/__tests__/gemini-image-service.test.ts +2 -2
  28. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  29. package/src/__tests__/guardian-grant-minting.test.ts +6 -6
  30. package/src/__tests__/guardian-routing-invariants.test.ts +40 -15
  31. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +4 -6
  32. package/src/__tests__/inbound-invite-redemption.test.ts +1 -1
  33. package/src/__tests__/integrations-cli.test.ts +3 -27
  34. package/src/__tests__/intent-routing.test.ts +3 -0
  35. package/src/__tests__/invite-redemption-service.test.ts +1 -1
  36. package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +40 -320
  37. package/src/__tests__/ipc-snapshot.test.ts +4 -31
  38. package/src/__tests__/memory-lifecycle-e2e.test.ts +11 -10
  39. package/src/__tests__/nl-approval-parser.test.ts +305 -0
  40. package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
  41. package/src/__tests__/provider-error-scenarios.test.ts +68 -0
  42. package/src/__tests__/registry.test.ts +0 -10
  43. package/src/__tests__/relay-server.test.ts +1 -1
  44. package/src/__tests__/retry-after-extraction.test.ts +111 -0
  45. package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
  46. package/src/__tests__/script-proxy-session-runtime.test.ts +6 -1
  47. package/src/__tests__/session-agent-loop.test.ts +0 -2
  48. package/src/__tests__/session-conflict-gate.test.ts +243 -388
  49. package/src/__tests__/session-media-retry.test.ts +147 -0
  50. package/src/__tests__/session-profile-injection.test.ts +0 -2
  51. package/src/__tests__/session-runtime-assembly.test.ts +2 -3
  52. package/src/__tests__/session-skill-tools.test.ts +0 -49
  53. package/src/__tests__/session-workspace-cache-state.test.ts +0 -1
  54. package/src/__tests__/session-workspace-injection.test.ts +0 -1
  55. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
  56. package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
  57. package/src/__tests__/skill-feature-flags.test.ts +18 -12
  58. package/src/__tests__/skill-load-feature-flag.test.ts +4 -3
  59. package/src/__tests__/slack-block-formatting.test.ts +100 -0
  60. package/src/__tests__/slack-inbound-verification.test.ts +346 -0
  61. package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
  62. package/src/__tests__/slack-skill.test.ts +3 -2
  63. package/src/__tests__/starter-task-flow.test.ts +0 -1
  64. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -1
  65. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -1
  66. package/src/__tests__/trusted-contact-verification.test.ts +3 -1
  67. package/src/__tests__/voice-invite-redemption.test.ts +1 -1
  68. package/src/amazon/client.ts +7 -24
  69. package/src/approvals/guardian-decision-primitive.ts +11 -7
  70. package/src/approvals/guardian-request-resolvers.ts +5 -3
  71. package/src/calls/relay-server.ts +44 -11
  72. package/src/channels/config.ts +1 -1
  73. package/src/cli/integrations.ts +10 -66
  74. package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
  75. package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
  76. package/src/config/bundled-skills/browser/TOOLS.json +59 -2
  77. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
  78. package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
  79. package/src/config/bundled-skills/contacts/SKILL.md +49 -53
  80. package/src/config/bundled-skills/contacts/TOOLS.json +26 -22
  81. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +40 -62
  82. package/src/config/bundled-skills/contacts/tools/contact-search.ts +17 -43
  83. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +18 -57
  84. package/src/config/bundled-skills/document/TOOLS.json +8 -0
  85. package/src/config/bundled-skills/email-setup/SKILL.md +10 -7
  86. package/src/config/bundled-skills/followups/TOOLS.json +12 -0
  87. package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
  88. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +54 -21
  89. package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
  90. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +14 -8
  91. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
  92. package/src/config/bundled-skills/media-processing/SKILL.md +1 -1
  93. package/src/config/bundled-skills/media-processing/TOOLS.json +28 -0
  94. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +26 -6
  95. package/src/config/bundled-skills/messaging/TOOLS.json +228 -182
  96. package/src/config/bundled-skills/notifications/SKILL.md +3 -2
  97. package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
  98. package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
  99. package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
  100. package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
  101. package/src/config/bundled-skills/schedule/SKILL.md +33 -15
  102. package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
  103. package/src/config/bundled-skills/slack/SKILL.md +30 -1
  104. package/src/config/bundled-skills/slack/TOOLS.json +89 -2
  105. package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
  106. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
  107. package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
  108. package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
  109. package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
  110. package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
  111. package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
  112. package/src/config/bundled-tool-registry.ts +2 -5
  113. package/src/config/channel-permission-profiles.ts +155 -0
  114. package/src/config/env.ts +4 -1
  115. package/src/config/memory-schema.ts +0 -10
  116. package/src/config/system-prompt.ts +6 -0
  117. package/src/contacts/contact-store.ts +221 -56
  118. package/src/contacts/contacts-write.ts +14 -3
  119. package/src/contacts/types.ts +35 -4
  120. package/src/daemon/assistant-attachments.ts +23 -3
  121. package/src/daemon/guardian-verification-intent.ts +7 -4
  122. package/src/daemon/handlers/apps.ts +1 -2
  123. package/src/daemon/handlers/config-heartbeat.ts +1 -2
  124. package/src/daemon/handlers/config-inbox.ts +16 -134
  125. package/src/daemon/handlers/contacts.ts +2 -2
  126. package/src/daemon/handlers/guardian-actions.ts +21 -88
  127. package/src/daemon/handlers/sessions.ts +2 -2
  128. package/src/daemon/ipc-contract/apps.ts +0 -1
  129. package/src/daemon/ipc-contract/contacts.ts +2 -2
  130. package/src/daemon/ipc-contract/inbox.ts +7 -66
  131. package/src/daemon/ipc-contract/sessions.ts +1 -0
  132. package/src/daemon/ipc-contract/surfaces.ts +0 -1
  133. package/src/daemon/ipc-contract-inventory.json +2 -4
  134. package/src/daemon/lifecycle.ts +14 -2
  135. package/src/daemon/session-agent-loop-handlers.ts +9 -0
  136. package/src/daemon/session-agent-loop.ts +2 -45
  137. package/src/daemon/session-attachments.ts +5 -1
  138. package/src/daemon/session-conflict-gate.ts +21 -82
  139. package/src/daemon/session-error.ts +18 -0
  140. package/src/daemon/session-lifecycle.ts +4 -5
  141. package/src/daemon/session-media-retry.ts +15 -1
  142. package/src/daemon/session-memory.ts +7 -52
  143. package/src/daemon/session-process.ts +3 -1
  144. package/src/daemon/session-runtime-assembly.ts +18 -35
  145. package/src/daemon/session-surfaces.ts +0 -1
  146. package/src/daemon/session-tool-setup.ts +7 -4
  147. package/src/events/domain-events.ts +2 -1
  148. package/src/heartbeat/heartbeat-service.ts +5 -1
  149. package/src/home-base/prebuilt/seed.ts +0 -1
  150. package/src/influencer/client.ts +7 -24
  151. package/src/media/gemini-image-service.ts +48 -3
  152. package/src/memory/app-store.ts +0 -4
  153. package/src/memory/conflict-intent.ts +3 -6
  154. package/src/memory/conflict-policy.ts +34 -0
  155. package/src/memory/conflict-store.ts +10 -18
  156. package/src/memory/contradiction-checker.ts +2 -2
  157. package/src/memory/conversation-attention-store.ts +3 -1
  158. package/src/memory/db-init.ts +8 -0
  159. package/src/memory/job-handlers/conflict.ts +0 -7
  160. package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
  161. package/src/memory/migrations/134-contacts-notes-column.ts +51 -0
  162. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +31 -0
  163. package/src/memory/migrations/index.ts +3 -0
  164. package/src/memory/schema.ts +12 -17
  165. package/src/memory/slack-thread-store.ts +187 -0
  166. package/src/messaging/index.ts +0 -1
  167. package/src/messaging/providers/slack/client.ts +84 -26
  168. package/src/messaging/providers/slack/types.ts +4 -0
  169. package/src/messaging/types.ts +0 -38
  170. package/src/notifications/adapters/slack.ts +90 -0
  171. package/src/notifications/destination-resolver.ts +42 -1
  172. package/src/notifications/emit-signal.ts +17 -1
  173. package/src/oauth/provider-profiles.ts +22 -0
  174. package/src/providers/anthropic/client.ts +3 -0
  175. package/src/providers/openai/client.ts +3 -0
  176. package/src/providers/retry.ts +9 -1
  177. package/src/runtime/actor-trust-resolver.ts +8 -0
  178. package/src/runtime/auth/require-bound-guardian.ts +44 -0
  179. package/src/runtime/auth/route-policy.ts +4 -8
  180. package/src/runtime/channel-approval-types.ts +18 -0
  181. package/src/runtime/channel-approvals.ts +8 -0
  182. package/src/runtime/channel-invite-transport.ts +1 -1
  183. package/src/runtime/channel-reply-delivery.ts +62 -3
  184. package/src/runtime/gateway-client.ts +36 -2
  185. package/src/runtime/gateway-internal-client.ts +86 -0
  186. package/src/runtime/guardian-action-service.ts +128 -0
  187. package/src/runtime/guardian-outbound-actions.ts +3 -3
  188. package/src/runtime/guardian-reply-router.ts +4 -4
  189. package/src/runtime/guardian-verification-templates.ts +16 -1
  190. package/src/runtime/http-server.ts +29 -46
  191. package/src/runtime/invite-redemption-service.ts +1 -1
  192. package/src/runtime/{ingress-service.ts → invite-service.ts} +5 -157
  193. package/src/runtime/nl-approval-parser.ts +138 -0
  194. package/src/runtime/routes/approval-routes.ts +1 -40
  195. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +6 -3
  196. package/src/runtime/routes/channel-route-shared.ts +35 -1
  197. package/src/runtime/routes/contact-routes.ts +494 -47
  198. package/src/runtime/routes/conversation-routes.ts +2 -1
  199. package/src/runtime/routes/global-search-routes.ts +2 -2
  200. package/src/runtime/routes/guardian-action-routes.ts +19 -111
  201. package/src/runtime/routes/guardian-approval-interception.ts +78 -1
  202. package/src/runtime/routes/guardian-bootstrap-routes.ts +6 -1
  203. package/src/runtime/routes/inbound-message-handler.ts +40 -12
  204. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +227 -1
  205. package/src/runtime/routes/inbound-stages/background-dispatch.ts +108 -0
  206. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +2 -1
  207. package/src/runtime/routes/{ingress-routes.ts → invite-routes.ts} +10 -110
  208. package/src/runtime/routes/migration-routes.ts +17 -17
  209. package/src/runtime/slack-block-formatting.ts +176 -0
  210. package/src/schedule/scheduler.ts +11 -2
  211. package/src/tools/apps/executors.ts +16 -15
  212. package/src/tools/calls/call-end.ts +1 -1
  213. package/src/tools/computer-use/definitions.ts +16 -0
  214. package/src/tools/credentials/vault.ts +86 -2
  215. package/src/tools/network/script-proxy/session-manager.ts +28 -3
  216. package/src/tools/permission-checker.ts +18 -0
  217. package/src/tools/terminal/shell.ts +15 -5
  218. package/src/tools/tool-approval-handler.ts +48 -4
  219. package/src/tools/types.ts +38 -1
  220. package/src/util/errors.ts +5 -1
  221. package/src/util/retry.ts +21 -0
  222. package/src/watcher/providers/slack.ts +33 -3
  223. package/src/workspace/git-service.ts +6 -4
  224. package/src/__tests__/get-weather.test.ts +0 -393
  225. package/src/__tests__/weather-skill-regression.test.ts +0 -276
  226. package/src/autonomy/autonomy-resolver.ts +0 -62
  227. package/src/autonomy/autonomy-store.ts +0 -138
  228. package/src/autonomy/disposition-mapper.ts +0 -31
  229. package/src/autonomy/index.ts +0 -11
  230. package/src/autonomy/types.ts +0 -43
  231. package/src/config/bundled-skills/weather/SKILL.md +0 -38
  232. package/src/config/bundled-skills/weather/TOOLS.json +0 -32
  233. package/src/config/bundled-skills/weather/icon.svg +0 -24
  234. package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -12
  235. package/src/messaging/triage-engine.ts +0 -344
  236. package/src/tools/weather/service.ts +0 -712
  237. /package/src/memory/{ingress-invite-store.ts → invite-store.ts} +0 -0
package/ARCHITECTURE.md CHANGED
@@ -433,31 +433,33 @@ External users who are not the guardian can gain access to the assistant through
433
433
 
434
434
  **Notification signals:** The flow emits signals at each lifecycle transition via `emitNotificationSignal()`:
435
435
 
436
- - `ingress.access_request` — non-member denied, guardian notified
436
+ - `ingress.access_request` — unknown contact denied, guardian notified
437
437
  - `ingress.trusted_contact.guardian_decision` — guardian approved or denied
438
438
  - `ingress.trusted_contact.verification_sent` — code created and delivered
439
- - `ingress.trusted_contact.activated` — requester verified, member active
439
+ - `ingress.trusted_contact.activated` — requester verified, contact active
440
440
  - `ingress.trusted_contact.denied` — guardian explicitly denied
441
441
 
442
442
  **HTTP API (for management):**
443
443
 
444
- | Endpoint | Method | Description |
445
- | ------------------------------- | ------ | ------------------------------------------------------------- |
446
- | `/v1/ingress/members` | GET | List trusted contacts (filterable by channel, status, policy) |
447
- | `/v1/ingress/members` | POST | Upsert a member (add/update trusted contact) |
448
- | `/v1/ingress/members/:id` | DELETE | Revoke a trusted contact |
449
- | `/v1/ingress/members/:id/block` | POST | Block a member |
444
+ | Endpoint | Method | Description |
445
+ | --------------------------- | ------ | ---------------------------------------------------------------- |
446
+ | `/v1/contacts` | GET | List contacts (filterable by role, search by query/channel/etc.) |
447
+ | `/v1/contacts` | POST | Create or update a contact |
448
+ | `/v1/contacts/:id` | GET | Get a contact by ID |
449
+ | `/v1/contacts/merge` | POST | Merge two contacts |
450
+ | `/v1/contacts/channels/:id` | PATCH | Update a contact channel's status/policy |
450
451
 
451
452
  **Key source files:**
452
453
 
453
454
  | File | Purpose |
454
455
  | ------------------------------------------------------ | ----------------------------------------------------------------------------- |
455
- | `src/runtime/routes/inbound-message-handler.ts` | Ingress ACL, non-member rejection, verification code interception |
456
+ | `src/runtime/routes/inbound-message-handler.ts` | Ingress ACL, unknown-contact rejection, verification code interception |
456
457
  | `src/runtime/routes/access-request-decision.ts` | Guardian decision → verification session creation |
457
458
  | `src/runtime/routes/guardian-approval-interception.ts` | Routes guardian decisions (button + conversational) to access request handler |
458
459
  | `src/runtime/channel-guardian-service.ts` | Verification challenge lifecycle, identity binding, rate limiting |
459
- | `src/runtime/routes/ingress-routes.ts` | HTTP API handlers for member/invite management |
460
- | `src/runtime/ingress-service.ts` | Business logic for member CRUD |
460
+ | `src/runtime/routes/contact-routes.ts` | HTTP API handlers for contact and channel management |
461
+ | `src/runtime/routes/invite-routes.ts` | HTTP API handlers for invite management |
462
+ | `src/runtime/invite-service.ts` | Business logic for invite operations |
461
463
  | `src/contacts/contact-store.ts` | Contact read queries — lookup, search, list, and channel operations |
462
464
  | `src/memory/channel-guardian-store.ts` | Approval request and verification challenge persistence |
463
465
  | `src/config/bundled-skills/contacts/SKILL.md` | Unified skill for contact management, access control, and invite links |
@@ -482,7 +484,7 @@ A complementary access-granting flow where the guardian proactively creates a sh
482
484
  ├─────────────────────────────────────────────────────────────┤
483
485
  │ Core Redemption Engine (invite-redemption-service.ts) │
484
486
  │ Channel-agnostic token validation, expiry, use-count, │
485
- │ channel-match enforcement, member creation/reactivation
487
+ │ channel-match enforcement, contact activation/reactivation
486
488
  │ Returns: InviteRedemptionOutcome (discriminated union) │
487
489
  │ Reply templates: invite-redemption-templates.ts │
488
490
  └─────────────────────────────────────────────────────────────┘
@@ -496,12 +498,12 @@ A complementary access-granting flow where the guardian proactively creates a sh
496
498
  4. Guardian shares the link with the invitee out-of-band.
497
499
  5. Invitee clicks the link, opening Telegram which sends `/start iv_<token>` to the bot.
498
500
  6. The gateway forwards the message to `/channels/inbound`. The inbound handler calls `getTransport('telegram').extractInboundToken()` to parse the `iv_` token.
499
- 7. The token is redeemed via `invite-redemption-service.ts`, which validates, creates an active member record, and returns a `redeemed` outcome.
501
+ 7. The token is redeemed via `invite-redemption-service.ts`, which validates, activates the contact, and returns a `redeemed` outcome.
500
502
  8. A deterministic welcome message is delivered to the invitee (bypasses the LLM pipeline).
501
503
 
502
504
  **Token prefix convention:** The `iv_` prefix distinguishes invite tokens from `gv_` (guardian verification) tokens. Both use the same Telegram `/start` deep-link mechanism but are routed to different handlers.
503
505
 
504
- **Inbound intercept points:** Invite token extraction runs early in the inbound handler, before ACL denial, so valid invites short-circuit the membership check. Two intercept branches handle: (a) non-members — the invite creates their first member record; (b) inactive members (revoked/pending) — the invite reactivates them.
506
+ **Inbound intercept points:** Invite token extraction runs early in the inbound handler, before ACL denial, so valid invites short-circuit the contact check. Two intercept branches handle: (a) unknown contacts — the invite creates their first contact record; (b) inactive contacts (revoked/pending) — the invite reactivates them.
505
507
 
506
508
  **Channel adapter status:**
507
509
 
@@ -518,8 +520,8 @@ Voice invites use a short numeric code (4-10 digits, default 6) instead of a URL
518
520
 
519
521
  **Creation flow:**
520
522
 
521
- 1. Guardian creates a voice invite via `POST /v1/ingress/invites` with `sourceChannel: "voice"` and `expectedExternalUserId` (E.164 phone).
522
- 2. `ingress-service.ts` generates a cryptographically random numeric code (`generateVoiceCode`), hashes it with SHA-256 (`hashVoiceCode`), and stores only the hash.
523
+ 1. Guardian creates a voice invite via `POST /v1/contacts/invites` with `sourceChannel: "voice"` and `expectedExternalUserId` (E.164 phone).
524
+ 2. `invite-service.ts` generates a cryptographically random numeric code (`generateVoiceCode`), hashes it with SHA-256 (`hashVoiceCode`), and stores only the hash.
523
525
  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.
524
526
  4. Guardian communicates the code to the invitee out-of-band.
525
527
 
@@ -528,7 +530,7 @@ Voice invites use a short numeric code (4-10 digits, default 6) instead of a URL
528
530
  1. Unknown caller dials in. `relay-server.ts` resolves trust via `resolveActorTrust`. Caller is `unknown`, no pending guardian challenge.
529
531
  2. The relay checks `findActiveVoiceInvites` for invites bound to the caller's phone number.
530
532
  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.`
531
- 4. `redeemVoiceInviteCode` validates: identity match, code hash match, expiry, use count. On success, an active member record is upserted and the call transitions to the normal call flow.
533
+ 4. `redeemVoiceInviteCode` validates: identity match, code hash match, expiry, use count. On success, the contact is activated and the call transitions to the normal call flow.
532
534
  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.
533
535
 
534
536
  **Security invariants:**
@@ -536,24 +538,24 @@ Voice invites use a short numeric code (4-10 digits, default 6) instead of a URL
536
538
  - The plaintext voice code is returned exactly once at creation time and never stored.
537
539
  - 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.
538
540
  - Failure responses are intentionally generic (`invalid_or_expired`) to prevent oracle attacks.
539
- - Blocked members cannot bypass the guardian's explicit block via invite redemption.
541
+ - Blocked contacts cannot bypass the guardian's explicit block via invite redemption.
540
542
 
541
543
  **Key source files:**
542
544
 
543
- | File | Purpose |
544
- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
545
- | `src/runtime/invite-redemption-service.ts` | Core redemption engine — token validation, voice code redemption, member creation, discriminated-union outcomes |
546
- | `src/runtime/invite-redemption-templates.ts` | Deterministic reply templates for each redemption outcome |
547
- | `src/runtime/channel-invite-transport.ts` | Transport adapter registry with `buildShareableInvite` / `extractInboundToken` interface |
548
- | `src/runtime/channel-invite-transports/telegram.ts` | Telegram adapter — `t.me/<bot>?start=iv_<token>` deep links, `/start iv_<token>` extraction |
549
- | `src/runtime/channel-invite-transports/voice.ts` | Voice transport adapter — code-based redemption metadata |
550
- | `src/daemon/guardian-invite-intent.ts` | Intent detection — routes create/list/revoke requests into the contacts skill |
551
- | `src/runtime/ingress-service.ts` | Shared business logic for invite/member operations (used by both HTTP routes and IPC) |
552
- | `src/runtime/routes/ingress-routes.ts` | HTTP API handlers for member/invite management including voice invite creation and redemption |
553
- | `src/runtime/routes/inbound-message-handler.ts` | Invite token intercept in the inbound flow (non-member and inactive-member branches) |
554
- | `src/calls/relay-server.ts` | Voice relay state machine — `invite_redemption_pending` subflow (always-on canonical behavior) |
555
- | `src/util/voice-code.ts` | Cryptographic voice code generation and SHA-256 hashing |
556
- | `src/memory/ingress-invite-store.ts` | Invite persistence including `findActiveVoiceInvites` for identity-bound lookup |
545
+ | File | Purpose |
546
+ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
547
+ | `src/runtime/invite-redemption-service.ts` | Core redemption engine — token validation, voice code redemption, contact activation, discriminated-union outcomes |
548
+ | `src/runtime/invite-redemption-templates.ts` | Deterministic reply templates for each redemption outcome |
549
+ | `src/runtime/channel-invite-transport.ts` | Transport adapter registry with `buildShareableInvite` / `extractInboundToken` interface |
550
+ | `src/runtime/channel-invite-transports/telegram.ts` | Telegram adapter — `t.me/<bot>?start=iv_<token>` deep links, `/start iv_<token>` extraction |
551
+ | `src/runtime/channel-invite-transports/voice.ts` | Voice transport adapter — code-based redemption metadata |
552
+ | `src/daemon/guardian-invite-intent.ts` | Intent detection — routes create/list/revoke requests into the contacts skill |
553
+ | `src/runtime/invite-service.ts` | Shared business logic for invite operations (used by both HTTP routes and IPC) |
554
+ | `src/runtime/routes/invite-routes.ts` | HTTP API handlers for invite management including voice invite creation and redemption |
555
+ | `src/runtime/routes/inbound-message-handler.ts` | Invite token intercept in the inbound flow (unknown-contact and inactive-contact branches) |
556
+ | `src/calls/relay-server.ts` | Voice relay state machine — `invite_redemption_pending` subflow (always-on canonical behavior) |
557
+ | `src/util/voice-code.ts` | Cryptographic voice code generation and SHA-256 hashing |
558
+ | `src/memory/invite-store.ts` | Invite persistence including `findActiveVoiceInvites` for identity-bound lookup |
557
559
 
558
560
  ### Voice Inbound Security Model (Canonical)
559
561
 
@@ -595,7 +597,7 @@ When no invite exists and no pending guardian challenge is active, the relay ent
595
597
  2. On name capture, `notifyGuardianOfAccessRequest` creates a canonical guardian request (`kind: 'access_request'`) and notifies the guardian via the notification pipeline.
596
598
  3. The relay transitions to `awaiting_guardian_decision` and plays hold music/messaging while polling the canonical request status.
597
599
  4. The guardian approves or denies via any channel (Telegram, SMS, desktop). All decisions route through `applyCanonicalGuardianDecision`, which dispatches to the `access_request` resolver in `guardian-request-resolvers.ts`.
598
- 5. On approval: the resolver directly activates the caller as a trusted contact (upserts member with `status: 'active'`, `policy: 'allow'`), the poll detects the approved status, the relay transitions to the normal call flow with the caller's guardian context updated.
600
+ 5. On approval: the resolver directly activates the caller as a trusted contact (sets channel `status: 'active'`, `policy: 'allow'`), the poll detects the approved status, the relay transitions to the normal call flow with the caller's guardian context updated.
599
601
  6. On denial or timeout: the caller hears a denial message and the call ends.
600
602
 
601
603
  **Path 3: Inbound guardian verification (pending challenge)**
@@ -888,7 +890,7 @@ graph LR
888
890
  C12["tool_permission_simulate<br/>toolName, input, workingDir?,<br/>isInteractive?, forcePromptSideEffects?,<br/>executionTarget?"]
889
891
  C13["conversation_search<br/>query, limit?,<br/>maxMessagesPerConversation?"]
890
892
  C14["ingress_invite<br/>create / list / revoke / redeem"]
891
- C15["ingress_member<br/>list / upsert / revoke / block"]
893
+ C15["contacts<br/>list / get / update_channel"]
892
894
  end
893
895
 
894
896
  SOCKET["Unix Socket<br/>~/.vellum/vellum.sock<br/>───────────────<br/>Newline-delimited JSON<br/>Max 96MB per message<br/>Ping/pong every 30s<br/>Auto-reconnect<br/>1s → 30s backoff"]
@@ -920,7 +922,7 @@ graph LR
920
922
  S22["tool_permission_simulate_response<br/>decision, riskLevel, reason?,<br/>promptPayload?, matchedRuleId?"]
921
923
  S23["conversation_search_response<br/>query, results[]: conversationId,<br/>title, updatedAt, matchingMessages[]"]
922
924
  S24["ingress_invite_response<br/>invite / invites"]
923
- S25["ingress_member_response<br/>member / members"]
925
+ S25["contacts_response<br/>contact / contacts"]
924
926
  end
925
927
 
926
928
  C0 --> SOCKET
@@ -2204,8 +2206,8 @@ Connected channels are resolved at signal emission time: vellum is always includ
2204
2206
  | Guardian bindings | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; revoked bindings retained |
2205
2207
  | Guardian verification challenges | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; consumed/expired challenges retained |
2206
2208
  | Guardian approval requests | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; decision outcome retained |
2207
- | Ingress invites | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; token hash stored, raw token never persisted |
2208
- | Ingress members | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; revoked/blocked members retained |
2209
+ | Contact invites | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; token hash stored, raw token never persisted |
2210
+ | Contacts & channels | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; revoked/blocked contacts retained |
2209
2211
  | Notification events | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; deduplicated by dedupeKey |
2210
2212
  | Notification decisions | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; FK to notification_events |
2211
2213
  | Notification deliveries | `~/.vellum/workspace/data/db/assistant.db` | SQLite | Drizzle ORM | Permanent; FK to notification_decisions |
package/Dockerfile CHANGED
@@ -16,16 +16,21 @@ RUN apt-get update && apt-get install -y \
16
16
  RUN curl -fsSL https://bun.sh/install | bash
17
17
  ENV PATH="/root/.bun/bin:${PATH}"
18
18
 
19
- # Copy package files
20
- COPY package.json bun.lock ./
19
+ # Install assistant and CLI dependencies first for cache reuse
20
+ COPY assistant/package.json assistant/bun.lock ./assistant/
21
+ RUN cd /app/assistant && bun install --frozen-lockfile
21
22
 
22
- # Install dependencies
23
- RUN bun install --frozen-lockfile
23
+ COPY cli/package.json cli/bun.lock ./cli/
24
+ RUN cd /app/cli && bun install --frozen-lockfile
25
+
26
+ # Copy source
27
+ COPY assistant ./assistant
28
+ COPY cli ./cli
24
29
 
25
30
  # Final stage
26
31
  FROM debian:trixie@sha256:3615a749858a1cba49b408fb49c37093db813321355a9ab7c1f9f4836341e9db AS runner
27
32
 
28
- WORKDIR /app
33
+ WORKDIR /app/assistant
29
34
 
30
35
  # Install runtime dependencies for Playwright and tree-sitter
31
36
  RUN apt-get update && apt-get install -y \
@@ -47,6 +52,10 @@ RUN apt-get update && apt-get install -y \
47
52
  COPY --from=builder /root/.bun/bin/bun /usr/local/bin/bun
48
53
  RUN ln -sf /usr/local/bin/bun /usr/local/bin/bunx
49
54
 
55
+ # Install a vellum CLI launcher backed by the bundled local cli package
56
+ RUN printf '#!/usr/bin/env sh\nexec bun run /app/cli/src/index.ts "$@"\n' > /usr/local/bin/vellum && \
57
+ chmod +x /usr/local/bin/vellum
58
+
50
59
  # Create non-root user that also has sudo access so it can like install stuff
51
60
  RUN groupadd --system --gid 1001 assistant && \
52
61
  useradd --system --uid 1001 --gid assistant --create-home --shell /bin/bash assistant && \
@@ -95,8 +104,5 @@ ENV IS_CONTAINERIZED=true
95
104
  # Copy from builder
96
105
  COPY --from=builder /app /app
97
106
 
98
- # Copy source
99
- COPY . .
100
-
101
107
  # Run the daemon + http server
102
108
  CMD ["bun", "run", "src/daemon/main.ts"]
package/README.md CHANGED
@@ -337,7 +337,7 @@ Guardian verification and ingress contact management are complementary but indep
337
337
  | `src/runtime/trust-context-resolver.ts` | Actor role classification: guardian / non-guardian / unverified_channel |
338
338
  | `src/runtime/routes/inbound-message-handler.ts` | Ingress ACL enforcement, verification-code intercept, escalation creation |
339
339
  | `src/contacts/contact-store.ts` | Contact + channel CRUD: `findContactChannel`, `upsertContact`, `updateChannelStatus`, `searchContacts` |
340
- | `src/memory/ingress-invite-store.ts` | Invite lifecycle: `createInvite`, `redeemInvite` (atomically creates member record) |
340
+ | `src/memory/invite-store.ts` | Invite lifecycle: `createInvite`, `redeemInvite` (atomically creates member record) |
341
341
  | `src/memory/channel-guardian-store.ts` | Persistence for guardian bindings, verification challenges, and approval requests |
342
342
  | `src/runtime/guardian-outbound-actions.ts` | Shared business logic for outbound verification (start/resend/cancel) |
343
343
  | `src/runtime/routes/integration-routes.ts` | HTTP route handlers for outbound guardian verification endpoints |
@@ -432,7 +432,7 @@ Redemption auto-creates a **member** record with an access policy:
432
432
  - **`deny`** — Messages are rejected with a refusal notice.
433
433
  - **`escalate`** — Messages are held for guardian (owner) approval before processing.
434
434
 
435
- Non-members (senders with no invite redemption) are denied by default. Members can be listed, updated, revoked, or blocked via the `ingress_member` IPC contract.
435
+ Non-members (senders with no invite redemption) are denied by default. Contacts can be listed, updated, revoked, or blocked via the HTTP API (`/v1/contacts` and `/v1/contacts/channels`).
436
436
 
437
437
  ### Escalation Flow
438
438
 
@@ -447,15 +447,14 @@ If no guardian binding exists, escalation fails closed — the message is denied
447
447
  | Message Type | Actions | Description |
448
448
  | ---------------- | ---------------------------- | ------------------------------------------------------------------------ |
449
449
  | `ingress_invite` | create, list, revoke, redeem | Manage invite tokens (SHA-256 hashed, raw token returned once on create) |
450
- | `ingress_member` | list, upsert, revoke, block | Manage member records and access policies |
451
450
 
452
451
  ### Key Modules
453
452
 
454
453
  | File | Purpose |
455
454
  | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
456
- | `src/memory/ingress-invite-store.ts` | CRUD for invite tokens with SHA-256 hashing and expiry |
455
+ | `src/memory/invite-store.ts` | CRUD for invite tokens with SHA-256 hashing and expiry |
457
456
  | `src/contacts/contact-store.ts` | Contact + channel CRUD with policy enforcement |
458
- | `src/daemon/handlers/config-inbox.ts` | IPC handlers for ingress invite and member contracts |
457
+ | `src/daemon/handlers/config-inbox.ts` | IPC handlers for invite contract |
459
458
  | `src/daemon/ipc-contract/inbox.ts` | TypeScript type definitions for ingress IPC messages |
460
459
  | `src/runtime/routes/channel-routes.ts` | ACL enforcement point — member lookup, policy check, escalation creation |
461
460
  | `src/runtime/invite-redemption-service.ts` | Core redemption engine — token validation, member creation, discriminated-union outcomes |
@@ -463,7 +462,7 @@ If no guardian binding exists, escalation fails closed — the message is denied
463
462
  | `src/runtime/channel-invite-transport.ts` | Transport adapter registry — `buildShareableInvite` / `extractInboundToken` per channel |
464
463
  | `src/runtime/channel-invite-transports/telegram.ts` | Telegram adapter — builds `t.me/<bot>?start=iv_<token>` deep links, extracts `iv_` tokens from `/start` commands |
465
464
  | `src/daemon/guardian-invite-intent.ts` | Intent detection — routes guardian invite management requests into the `contacts` skill |
466
- | `src/runtime/ingress-service.ts` | Shared business logic for invite/member operations (HTTP + IPC) |
465
+ | `src/runtime/invite-service.ts` | Shared business logic for invite and contact operations (HTTP + IPC) |
467
466
 
468
467
  ## Database
469
468
 
@@ -482,7 +481,7 @@ bun run db:push # Apply migrations
482
481
 
483
482
  ```bash
484
483
  # Build production image
485
- docker build -t vellum-assistant:local assistant
484
+ docker build -f assistant/Dockerfile -t vellum-assistant:local .
486
485
 
487
486
  # Run
488
487
  docker run --rm -p 3001:3001 \
@@ -490,7 +489,7 @@ docker run --rm -p 3001:3001 \
490
489
  vellum-assistant:local
491
490
  ```
492
491
 
493
- The image runs as non-root user `assistant` (uid 1001) and exposes port `3001`.
492
+ The image exposes port `3001` and bundles the `vellum` CLI binary.
494
493
 
495
494
  ## Troubleshooting
496
495
 
@@ -41,7 +41,7 @@ graph TB
41
41
 
42
42
  subgraph "Read Path (Memory Recall)"
43
43
  QUERY["Recall Query Builder<br/>User request + compacted context summary"]
44
- CONFLICT_GATE["Soft Conflict Gate<br/>dismiss non-actionable conflicts (kind + statement policy)<br/>resolve pending conflicts from user turn<br/>relevance + cooldown + configurable ask behavior"]
44
+ CONFLICT_GATE["Soft Conflict Gate<br/>dismiss non-actionable conflicts (kind + statement + provenance policy)<br/>attempt internal resolution from user turn<br/>relevance-based; never produces user-facing prompts"]
45
45
  PROFILE_BUILD["Dynamic Profile Compiler<br/>active trusted profile memories<br/>user_confirmed > user_reported > assistant_inferred"]
46
46
  PROFILE_INJECT["Inject profile context block<br/>into runtime user tail<br/>(strict token cap)"]
47
47
  BUDGET["Dynamic Recall Budget<br/>computeRecallBudget()<br/>from prompt headroom"]
@@ -158,28 +158,26 @@ The key distinction: normal compaction is a cost-optimized background process th
158
158
 
159
159
  ### Memory Retrieval Config Knobs (Defaults)
160
160
 
161
- | Config key | Default | Purpose |
162
- | --------------------------------------------------------- | ----------------------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
163
- | `memory.retrieval.dynamicBudget.enabled` | `true` | Toggle per-turn recall budget calculation from live prompt headroom. |
164
- | `memory.retrieval.dynamicBudget.minInjectTokens` | `1200` | Lower clamp for computed recall injection budget. |
165
- | `memory.retrieval.dynamicBudget.maxInjectTokens` | `10000` | Upper clamp for computed recall injection budget. |
166
- | `memory.retrieval.dynamicBudget.targetHeadroomTokens` | `10000` | Reserved headroom to keep free for response generation/tool traces. |
167
- | `memory.entity.extractRelations.enabled` | `true` | Enable relation edge extraction and persistence in `memory_entity_relations`. |
168
- | `memory.entity.extractRelations.backfillBatchSize` | `200` | Batch size for checkpointed `backfill_entity_relations` jobs. |
169
- | `memory.entity.relationRetrieval.enabled` | `true` | Enable one-hop relation expansion from matched seed entities at recall time. |
170
- | `memory.entity.relationRetrieval.maxSeedEntities` | `8` | Maximum matched seed entities from the query. |
171
- | `memory.entity.relationRetrieval.maxNeighborEntities` | `20` | Maximum unique neighbor entities expanded from relation edges. |
172
- | `memory.entity.relationRetrieval.maxEdges` | `40` | Maximum relation edges traversed during expansion. |
173
- | `memory.entity.relationRetrieval.neighborScoreMultiplier` | `0.7` | Downweight multiplier for relation-expanded candidates vs direct entity hits. |
174
- | `memory.conflicts.enabled` | `true` | Enable soft conflict gate for unresolved `memory_item_conflicts`. |
175
- | `memory.conflicts.reaskCooldownTurns` | `3` | Minimum turn distance before re-asking the same conflict clarification. |
176
- | `memory.conflicts.resolverLlmTimeoutMs` | `12000` | Timeout bound for clarification resolver LLM fallback. |
177
- | `memory.conflicts.relevanceThreshold` | `0.3` | Similarity threshold for deciding whether a pending conflict is relevant to the current request. |
178
- | `memory.conflicts.gateMode` | `'soft'` | Conflict gate strategy. Currently only `'soft'` is supported (asks the user inline). |
179
- | `memory.conflicts.askOnIrrelevantTurns` | `false` | When `true`, soft-inject irrelevant conflict clarifications into every turn. When `false` (default), only ask when the conflict is topically relevant. |
180
- | `memory.conflicts.conflictableKinds` | `['preference', 'profile', 'constraint', 'instruction', 'style']` | Memory item kinds eligible for conflict detection. Items with kinds outside this list are auto-dismissed. |
181
- | `memory.profile.enabled` | `true` | Enable dynamic profile compilation from active trusted profile/preference/constraint/instruction memories. |
182
- | `memory.profile.maxInjectTokens` | `800` | Hard token cap enforced by `ProfileCompiler` when generating the runtime profile block. |
161
+ | Config key | Default | Purpose |
162
+ | --------------------------------------------------------- | ----------------------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------ |
163
+ | `memory.retrieval.dynamicBudget.enabled` | `true` | Toggle per-turn recall budget calculation from live prompt headroom. |
164
+ | `memory.retrieval.dynamicBudget.minInjectTokens` | `1200` | Lower clamp for computed recall injection budget. |
165
+ | `memory.retrieval.dynamicBudget.maxInjectTokens` | `10000` | Upper clamp for computed recall injection budget. |
166
+ | `memory.retrieval.dynamicBudget.targetHeadroomTokens` | `10000` | Reserved headroom to keep free for response generation/tool traces. |
167
+ | `memory.entity.extractRelations.enabled` | `true` | Enable relation edge extraction and persistence in `memory_entity_relations`. |
168
+ | `memory.entity.extractRelations.backfillBatchSize` | `200` | Batch size for checkpointed `backfill_entity_relations` jobs. |
169
+ | `memory.entity.relationRetrieval.enabled` | `true` | Enable one-hop relation expansion from matched seed entities at recall time. |
170
+ | `memory.entity.relationRetrieval.maxSeedEntities` | `8` | Maximum matched seed entities from the query. |
171
+ | `memory.entity.relationRetrieval.maxNeighborEntities` | `20` | Maximum unique neighbor entities expanded from relation edges. |
172
+ | `memory.entity.relationRetrieval.maxEdges` | `40` | Maximum relation edges traversed during expansion. |
173
+ | `memory.entity.relationRetrieval.neighborScoreMultiplier` | `0.7` | Downweight multiplier for relation-expanded candidates vs direct entity hits. |
174
+ | `memory.conflicts.enabled` | `true` | Enable soft conflict gate for unresolved `memory_item_conflicts`. |
175
+ | `memory.conflicts.resolverLlmTimeoutMs` | `12000` | Timeout bound for clarification resolver LLM fallback. |
176
+ | `memory.conflicts.relevanceThreshold` | `0.3` | Similarity threshold for deciding whether a pending conflict is relevant to the current request. |
177
+ | `memory.conflicts.gateMode` | `'soft'` | Conflict gate strategy. Currently only `'soft'` is supported (resolves conflicts internally without user prompts). |
178
+ | `memory.conflicts.conflictableKinds` | `['preference', 'profile', 'constraint', 'instruction', 'style']` | Memory item kinds eligible for conflict detection. Items with kinds outside this list are auto-dismissed. |
179
+ | `memory.profile.enabled` | `true` | Enable dynamic profile compilation from active trusted profile/preference/constraint/instruction memories. |
180
+ | `memory.profile.maxInjectTokens` | `800` | Hard token cap enforced by `ProfileCompiler` when generating the runtime profile block. |
183
181
 
184
182
  ### Memory Recall Debugging Playbook
185
183
 
@@ -211,7 +209,7 @@ The key distinction: normal compaction is a cost-optimized background process th
211
209
  stateDiagram-v2
212
210
  [*] --> ActiveItems : extract_items/check_contradictions
213
211
  ActiveItems --> PendingConflict : ambiguous_contradiction\n(candidate -> pending_clarification)
214
- PendingConflict --> PendingConflict : soft gate ask\n(reask cooldown + relevance + askOnIrrelevantTurns)
212
+ PendingConflict --> PendingConflict : internal evaluation\n(relevance check, no user prompt)
215
213
  PendingConflict --> Dismissed : non-actionable\n(kind policy + transient statement filter)
216
214
  PendingConflict --> ResolvedKeepExisting : clarification resolver\n+ applyConflictResolution
217
215
  PendingConflict --> ResolvedKeepCandidate : clarification resolver\n+ applyConflictResolution
@@ -224,6 +222,10 @@ stateDiagram-v2
224
222
  SupersededItems --> CleanupItems : cleanup_stale_superseded_items
225
223
  ```
226
224
 
225
+ ### Internal-Only Conflict Handling
226
+
227
+ Memory conflict resolution is entirely internal and non-interruptive. The conflict gate evaluates pending conflicts on each turn, dismisses non-actionable ones (based on kind policy, statement eligibility, coherence, and provenance), and attempts resolution when user input looks like a natural clarification. At no point does the conflict system produce user-facing clarification prompts, inject conflict instructions into the assistant's response, or block the user's request. The user is never aware that a conflict exists; the runtime response path always continues answering the user's actual request. This invariant is enforced across the conflict gate (`session-conflict-gate.ts`), session memory (`session-memory.ts`), session agent loop (`session-agent-loop.ts`), and runtime assembly (`session-runtime-assembly.ts`).
228
+
227
229
  Runtime profile flow (per turn):
228
230
 
229
231
  1. `ProfileCompiler` builds a trusted profile block from active `profile` / `preference` / `constraint` / `instruction` items under strict token cap.
@@ -238,7 +240,7 @@ Two trust gates enforce trust-class-based access control over the memory pipelin
238
240
 
239
241
  - **Write gate** (`indexer.ts`): The `extract_items` and `resolve_conflicts` jobs only run for messages from trusted actors (guardian or undefined provenance). Messages from untrusted actors (`trusted_contact`, `unknown`) are still segmented and embedded — so they appear in conversation context — but no profile extraction or conflict resolution is triggered. This prevents untrusted channels from injecting or mutating long-term memory items.
240
242
 
241
- - **Read gate** (`session-memory.ts`): When the current session's actor is untrusted, the memory recall pipeline returns a no-op context — no recall injection, no dynamic profile, no conflict clarification prompts. This ensures untrusted actors cannot surface or exploit previously extracted memory.
243
+ - **Read gate** (`session-memory.ts`): When the current session's actor is untrusted, the memory recall pipeline returns a no-op context — no recall injection, no dynamic profile, no conflict resolution. This ensures untrusted actors cannot surface or exploit previously extracted memory.
242
244
 
243
245
  Trust policy is **cross-channel and trust-class-based**: decisions use `trustContext.trustClass`, not the channel string. Desktop/IPC sessions default to `trustClass: 'guardian'`. External channels (Telegram, SMS, WhatsApp, voice) provide explicit trust context via the resolver. Messages without provenance metadata are treated as trusted (guardian); all new messages carry provenance.
244
246
 
@@ -381,7 +383,7 @@ graph TB
381
383
  - **Prepend, not append**: The workspace block is prepended to the user message content so that Anthropic cache breakpoints continue to land on the trailing user text block, preserving prompt cache efficiency.
382
384
  - **Runtime-only**: The injected `<workspace_top_level>` block is stripped from `this.messages` after the agent loop completes. It never persists in conversation history or the database.
383
385
  - **Dirty-refresh**: The scanner runs once on the first turn, then only re-runs after a successful mutation tool (`file_edit`, `file_write`, `bash`). Failed tool results do not trigger a refresh.
384
- - **Injection ordering**: Workspace context is injected after other runtime injections (soft conflict instruction, active surface) via `applyRuntimeInjections`, but because it is **prepended** to content blocks, it appears first in the final message.
386
+ - **Injection ordering**: Workspace context is injected after other runtime injections (active surface, etc.) via `applyRuntimeInjections`, but because it is **prepended** to content blocks, it appears first in the final message.
385
387
 
386
388
  ### Cache compatibility
387
389
 
@@ -506,7 +508,6 @@ graph TB
506
508
  10. **Provider-aware commit message generation (optional)**: When `workspaceGit.commitMessageLLM.enabled` is `true`, turn-boundary commits attempt to generate a descriptive commit message using the configured LLM provider before falling back to deterministic messages. The feature ships disabled by default and is designed to never degrade turn completion guarantees.
507
509
 
508
510
  **Commit message LLM fallback chain**: The generator runs a sequence of pre-flight checks before calling the LLM. Each check that fails produces a machine-readable `llmFallbackReason` in the structured log output and immediately returns a deterministic message. The checks, in order:
509
-
510
511
  1. `disabled` — `commitMessageLLM.enabled` is `false` or `useConfiguredProvider` is `false`
511
512
  2. `missing_provider_api_key` — the configured provider's API key is not set in `config.apiKeys` (skipped for keyless providers like Ollama that run without an API key)
512
513
  3. `breaker_open` — the generator's internal circuit breaker is open after consecutive LLM failures (exponential backoff)
@@ -516,11 +517,9 @@ graph TB
516
517
  7. `timeout` — the LLM call exceeded `timeoutMs` (AbortController fires)
517
518
  8. `provider_error` — the provider threw an exception during the LLM call
518
519
  9. `invalid_output` — the LLM returned empty text, the literal string "FALLBACK", or total output > 500 chars
519
-
520
520
  - **Subject line capping**: If the LLM subject line exceeds 72 chars it is deterministically truncated to 72 chars. This is NOT treated as a failure (no breaker penalty, no deterministic fallback).
521
521
 
522
522
  **Fast model resolution**: The LLM call uses a small/fast model to minimize latency and cost. The model is resolved **before** any provider call as a pre-flight check:
523
-
524
523
  - If `commitMessageLLM.providerFastModelOverrides[provider]` is set, that model is used.
525
524
  - Otherwise, a built-in default is used: `anthropic` -> `claude-haiku-4-5-20251001`, `openai` -> `gpt-4o-mini`, `gemini` -> `gemini-2.0-flash`.
526
525
  - If the configured provider has no override and no built-in default (e.g., `ollama`, `fireworks`, `openrouter`), the generator returns a deterministic fallback with reason `missing_fast_model` and the provider is never called. To enable LLM commit messages for such providers, set `providerFastModelOverrides[provider]` to the desired model.