@newsails/veil-cli 1.0.1

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 (199) hide show
  1. package/.veil/agents/analyst/AGENT.md +21 -0
  2. package/.veil/agents/analyst/agent.json +23 -0
  3. package/.veil/agents/assistant/AGENT.md +15 -0
  4. package/.veil/agents/assistant/agent.json +19 -0
  5. package/.veil/agents/coder/AGENT.md +18 -0
  6. package/.veil/agents/coder/agent.json +19 -0
  7. package/.veil/agents/hello/AGENT.md +5 -0
  8. package/.veil/agents/hello/agent.json +13 -0
  9. package/.veil/agents/writer/AGENT.md +12 -0
  10. package/.veil/agents/writer/agent.json +17 -0
  11. package/.veil/memory/MEMORY.md +343 -0
  12. package/.veil/memory/agents/analyst/MEMORY.md +55 -0
  13. package/.veil/memory/agents/hello/MEMORY.md +12 -0
  14. package/.veil/runtime.pid +1 -0
  15. package/.veil/settings.json +10 -0
  16. package/.veil-studio/studio.db +0 -0
  17. package/.veil-studio/studio.db-shm +0 -0
  18. package/.veil-studio/studio.db-wal +0 -0
  19. package/PLAN/01-vision.md +26 -0
  20. package/PLAN/02-tech-stack.md +94 -0
  21. package/PLAN/03-agents.md +232 -0
  22. package/PLAN/04-runtime.md +171 -0
  23. package/PLAN/05-tools.md +211 -0
  24. package/PLAN/06-communication.md +243 -0
  25. package/PLAN/07-storage.md +218 -0
  26. package/PLAN/08-api-cli.md +153 -0
  27. package/PLAN/09-permissions.md +108 -0
  28. package/PLAN/10-ably.md +105 -0
  29. package/PLAN/11-file-formats.md +442 -0
  30. package/PLAN/12-folder-structure.md +205 -0
  31. package/PLAN/13-operations.md +212 -0
  32. package/PLAN/README.md +23 -0
  33. package/README.md +128 -0
  34. package/REPORT.md +174 -0
  35. package/TODO.md +45 -0
  36. package/ai-tests/FRONTEND_PROMPT.md +220 -0
  37. package/ai-tests/Research & Planning.md +814 -0
  38. package/ai-tests/prompt-001-basic-api.md +230 -0
  39. package/ai-tests/prompt-002-basic-flows.md +230 -0
  40. package/ai-tests/prompt-003-agent-behaviors.md +220 -0
  41. package/api/middleware.js +60 -0
  42. package/api/routes/agents.js +193 -0
  43. package/api/routes/chat.js +93 -0
  44. package/api/routes/completions.js +122 -0
  45. package/api/routes/daemons.js +80 -0
  46. package/api/routes/memory.js +169 -0
  47. package/api/routes/models.js +40 -0
  48. package/api/routes/remote-methods.js +74 -0
  49. package/api/routes/sessions.js +208 -0
  50. package/api/routes/settings.js +108 -0
  51. package/api/routes/system.js +50 -0
  52. package/api/routes/tasks.js +270 -0
  53. package/api/server.js +120 -0
  54. package/cli/formatter.js +70 -0
  55. package/cli/index.js +443 -0
  56. package/cli/parser.js +113 -0
  57. package/config/config.json +10 -0
  58. package/config/models.json +6826 -0
  59. package/core/agent.js +329 -0
  60. package/core/cancel.js +38 -0
  61. package/core/compaction.js +176 -0
  62. package/core/events.js +13 -0
  63. package/core/loop.js +564 -0
  64. package/core/memory.js +51 -0
  65. package/core/prompt.js +185 -0
  66. package/core/queue.js +96 -0
  67. package/core/registry.js +291 -0
  68. package/core/remote-methods.js +124 -0
  69. package/core/router.js +386 -0
  70. package/core/running-sessions.js +18 -0
  71. package/docs/api/01-system.md +84 -0
  72. package/docs/api/02-agents.md +374 -0
  73. package/docs/api/03-chat.md +269 -0
  74. package/docs/api/04-tasks.md +470 -0
  75. package/docs/api/05-sessions.md +444 -0
  76. package/docs/api/06-daemons.md +142 -0
  77. package/docs/api/07-memory.md +186 -0
  78. package/docs/api/08-settings.md +133 -0
  79. package/docs/api/09-models.md +119 -0
  80. package/docs/api/09-websocket.md +350 -0
  81. package/docs/api/10-completions.md +134 -0
  82. package/docs/api/README.md +116 -0
  83. package/docs/guide/01-quickstart.md +220 -0
  84. package/docs/guide/02-folder-structure.md +185 -0
  85. package/docs/guide/03-configuration.md +252 -0
  86. package/docs/guide/04-agents.md +267 -0
  87. package/docs/guide/05-cli.md +290 -0
  88. package/docs/guide/06-tools.md +643 -0
  89. package/docs/guide/07-permissions.md +236 -0
  90. package/docs/guide/08-memory.md +139 -0
  91. package/docs/guide/09-multi-agent.md +271 -0
  92. package/docs/guide/10-daemons.md +226 -0
  93. package/docs/guide/README.md +53 -0
  94. package/docs/index.html +623 -0
  95. package/examples/README.md +151 -0
  96. package/examples/agents/assistant/AGENT.md +31 -0
  97. package/examples/agents/assistant/SOUL.md +9 -0
  98. package/examples/agents/assistant/agent.json +74 -0
  99. package/examples/agents/hello/AGENT.md +15 -0
  100. package/examples/agents/hello/agent.json +14 -0
  101. package/examples/agents/monitor/AGENT.md +51 -0
  102. package/examples/agents/monitor/agent.json +33 -0
  103. package/examples/agents/monitor/heartbeats/monitor.md +24 -0
  104. package/examples/agents/orchestrator/AGENT.md +70 -0
  105. package/examples/agents/orchestrator/agent.json +30 -0
  106. package/examples/agents/researcher/AGENT.md +52 -0
  107. package/examples/agents/researcher/agent.json +49 -0
  108. package/examples/agents/researcher/skills/web-research.md +28 -0
  109. package/examples/skills/code-review.md +72 -0
  110. package/examples/skills/summarise.md +59 -0
  111. package/examples/skills/web-research.md +42 -0
  112. package/examples/tools/word-count/index.js +27 -0
  113. package/examples/tools/word-count/tool.json +18 -0
  114. package/infrastructure/database.js +563 -0
  115. package/infrastructure/scheduler.js +122 -0
  116. package/llm/client.js +206 -0
  117. package/migrations/001-initial.sql +121 -0
  118. package/migrations/002-debuggability.sql +13 -0
  119. package/migrations/003-drop-orphaned-columns.sql +72 -0
  120. package/migrations/004-session-message-token-fields.sql +78 -0
  121. package/migrations/005-session-thinking.sql +5 -0
  122. package/package.json +30 -0
  123. package/schemas/agent.json +143 -0
  124. package/schemas/settings.json +111 -0
  125. package/scripts/fetch-models.js +93 -0
  126. package/session-debug-scenario.md +248 -0
  127. package/settings/fields.js +52 -0
  128. package/system-prompts/base-core.md +7 -0
  129. package/system-prompts/environment.md +13 -0
  130. package/system-prompts/reminders/anti-drift.md +6 -0
  131. package/system-prompts/reminders/stall-recovery.md +10 -0
  132. package/system-prompts/safety-rules.md +25 -0
  133. package/system-prompts/task-heuristics.md +27 -0
  134. package/test/client.js +71 -0
  135. package/test/integration/01-health.test.js +25 -0
  136. package/test/integration/02-agents.test.js +80 -0
  137. package/test/integration/03-chat-hello.test.js +48 -0
  138. package/test/integration/04-chat-multiturn.test.js +61 -0
  139. package/test/integration/05-chat-writer.test.js +48 -0
  140. package/test/integration/06-task-basic.test.js +68 -0
  141. package/test/integration/07-task-tools.test.js +74 -0
  142. package/test/integration/08-task-code-analysis.test.js +69 -0
  143. package/test/integration/09-memory-analyst.test.js +63 -0
  144. package/test/integration/10-task-advanced.test.js +85 -0
  145. package/test/integration/11-sessions-advanced.test.js +84 -0
  146. package/test/integration/12-assistant-chat-tools.test.js +75 -0
  147. package/test/integration/13-edge-cases.test.js +99 -0
  148. package/test/integration/14-cancel.test.js +62 -0
  149. package/test/integration/15-debug.test.js +106 -0
  150. package/test/integration/16-memory-api.test.js +83 -0
  151. package/test/integration/17-settings-api.test.js +41 -0
  152. package/test/integration/18-tool-search-activation.test.js +119 -0
  153. package/test/results/.gitkeep +0 -0
  154. package/test/runner.js +206 -0
  155. package/test/smoke.js +216 -0
  156. package/tools/agent_message.js +85 -0
  157. package/tools/agent_send.js +80 -0
  158. package/tools/agent_spawn.js +44 -0
  159. package/tools/bash.js +49 -0
  160. package/tools/edit_file.js +41 -0
  161. package/tools/glob.js +64 -0
  162. package/tools/grep.js +82 -0
  163. package/tools/list_dir.js +63 -0
  164. package/tools/log_write.js +31 -0
  165. package/tools/memory_read.js +38 -0
  166. package/tools/memory_search.js +65 -0
  167. package/tools/memory_write.js +42 -0
  168. package/tools/read_file.js +48 -0
  169. package/tools/sleep.js +22 -0
  170. package/tools/task_create.js +41 -0
  171. package/tools/task_respond.js +37 -0
  172. package/tools/task_spawn.js +64 -0
  173. package/tools/task_status.js +39 -0
  174. package/tools/task_subscribe.js +37 -0
  175. package/tools/todo_read.js +26 -0
  176. package/tools/todo_write.js +38 -0
  177. package/tools/tool_activate.js +24 -0
  178. package/tools/tool_search.js +24 -0
  179. package/tools/web_fetch.js +50 -0
  180. package/tools/web_search.js +52 -0
  181. package/tools/write_file.js +28 -0
  182. package/ui/api.js +190 -0
  183. package/ui/app.js +281 -0
  184. package/ui/index.html +382 -0
  185. package/ui/views/agents.js +377 -0
  186. package/ui/views/chat.js +610 -0
  187. package/ui/views/connection.js +96 -0
  188. package/ui/views/daemons.js +129 -0
  189. package/ui/views/feed.js +194 -0
  190. package/ui/views/memory.js +263 -0
  191. package/ui/views/models.js +146 -0
  192. package/ui/views/sessions.js +314 -0
  193. package/ui/views/settings.js +142 -0
  194. package/ui/views/tasks.js +415 -0
  195. package/utils/context.js +49 -0
  196. package/utils/id.js +16 -0
  197. package/utils/models.js +88 -0
  198. package/utils/paths.js +213 -0
  199. package/utils/settings.js +172 -0
@@ -0,0 +1,211 @@
1
+ # 05 — Tools
2
+
3
+ ## Built-in Tools (24)
4
+
5
+ | Tool | Category | Description |
6
+ |------|----------|-------------|
7
+ | `bash` | System | Execute shell commands (with permission checks) |
8
+ | `read_file` | Files | Read file contents (with offset/limit for large files) |
9
+ | `write_file` | Files | Write/overwrite entire file |
10
+ | `edit_file` | Files | Search-and-replace patch (str_replace, like Claude Code) |
11
+ | `list_dir` | Files | List directory contents with metadata |
12
+ | `glob` | Files | Find files by glob pattern |
13
+ | `grep` | Files | Search file contents (ripgrep-style) |
14
+ | `web_fetch` | Network | Fetch URL content (HTML → text, JSON, images → base64) |
15
+ | `web_search` | Network | Search the web, return results |
16
+ | `memory_write` | Memory | Write to memory (agent-scoped by default; `scope: "global"` writes to `.veil/memory/MEMORY.md`) |
17
+ | `memory_read` | Memory | Read agent's memory files |
18
+ | `memory_search` | Memory | Search across project-level memory files (`.veil/memory/` + agent-specific). Does NOT search global `~/.veil/memory/`. |
19
+ | `todo_write` | Memory | Create/update structured TODO list (survives compaction) |
20
+ | `todo_read` | Memory | Read current TODO list |
21
+ | `task_create` | Orchestration | Create a new task for any agent |
22
+ | `task_status` | Orchestration | Check task status and output |
23
+ | `task_respond` | Orchestration | Respond to a task waiting for input (task mode only) |
24
+ | `agent_spawn` | Orchestration | Spawn a sub-agent with isolated context (sync or async via `wait` param) |
25
+ | `agent_message` | Orchestration | Send message to another agent, wait for response (sync) |
26
+ | `agent_send` | Orchestration | Send message to another agent, fire-and-forget (async) |
27
+ | `task_subscribe` | Orchestration | Subscribe to a task's completion (for external observers — own spawns/tasks auto-subscribe) |
28
+ | `tool_search` | Meta | Search available tools by name/description, load full schema |
29
+ | `sleep` | Control | Wait for a configurable duration (max 300s). Counts against `maxDurationSeconds` but not `maxIterations`. |
30
+ | `log_write` | Logging | Write entry to task log / push progress event |
31
+
32
+ ### Notes
33
+ - **No `ask_user` tool** — chat mode agents respond naturally, task mode agents use `task_respond` to pause and wait.
34
+ - **`task_respond` only available in task mode** — other modes don't need it.
35
+ - **`task_subscribe`** registers durable interest in a task the agent didn't create. `agent_spawn(wait: false)` and `task_create` auto-subscribe the caller — no need for explicit `task_subscribe` on your own work. Use `task_subscribe` for monitoring agents, daemons, or external observers. Notifications survive session closure (runtime reopens session to deliver).
36
+ - All other tools available in all modes (unless restricted by agent.json `tools`/`disallowedTools`).
37
+
38
+ ### Key Parameters (Orchestration Tools)
39
+
40
+ Full JSON Schemas defined at implementation time. Key parameters for complex tools:
41
+
42
+ | Tool | Key Parameters |
43
+ |------|---------------|
44
+ | `agent_spawn` | `agent` (string, required), `instruction` (string, required), `returnMode` ("summary" / "raw" / "lastMessage", default: "summary"), `wait` (boolean, default: true — false for async parallel fan-out) |
45
+
46
+ **`returnMode` output formats:**
47
+ - `"summary"` — LLM-generated summary of the sub-agent's work (~500 tokens max). Best for orchestrators that need gist only.
48
+ - `"raw"` — Full conversation history (all messages). Can be large. Use for debugging or when caller needs full context.
49
+ - `"lastMessage"` — Sub-agent's final assistant message only. Compact, good for direct answers.
50
+
51
+ | `agent_message` | `agent` (string, required), `message` (string, required), `timeout` (number, default: 60s) |
52
+ | `agent_send` | `agent` (string, required), `message` (string, required), `followup` (boolean, default: false) |
53
+ | `task_create` | `agent` (string, required), `input` (string, required), `priority` ("high" / "normal" / "low"), `tags` (string[]), `maxIterations` (number), `maxDurationSeconds` (number) |
54
+ | `task_subscribe` | `taskId` (string, required) |
55
+ | `task_status` | `taskId` (string, required) |
56
+ | `task_respond` | `taskId` (string, required), `message` (string, required) |
57
+ | `memory_write` | `content` (string, required), `scope` ("agent" / "global", default: "agent") |
58
+ | `memory_read` | `file` (string, optional — defaults to MEMORY.md) |
59
+ | `memory_search` | `query` (string, required), `scope` ("project" / "agent", default: "project" — searches all project memory) |
60
+ | `sleep` | `seconds` (number, required, max: 300) |
61
+ | `edit_file` | `file` (string, required), `old_string` (string, required), `new_string` (string, required) |
62
+ | `web_fetch` | `url` (string, required), `mode` ("text" / "json" / "base64", default: "text") |
63
+
64
+ ## Custom JS Tools
65
+
66
+ User-defined tools at three levels:
67
+ - **Agent-level:** `.veil/agents/<agentName>/tools/<toolName>/` — scoped to that agent only
68
+ - **Project-level:** `.veil/tools/<toolName>/` — available to all agents in the project
69
+ - **Global-level:** `~/.veil/tools/<toolName>/` — available to all agents everywhere
70
+
71
+ ```
72
+ <toolName>/
73
+ ├── tool.json ← Manifest (name, description, input_schema, timeout)
74
+ └── index.js ← Implementation
75
+ ```
76
+
77
+ ### tool.json
78
+ ```json
79
+ {
80
+ "name": "send_slack_message",
81
+ "description": "Sends a message to a Slack channel",
82
+ "input_schema": {
83
+ "type": "object",
84
+ "properties": {
85
+ "channel": { "type": "string", "description": "Slack channel name" },
86
+ "message": { "type": "string", "description": "Message to send" }
87
+ },
88
+ "required": ["channel", "message"]
89
+ },
90
+ "timeout": 30
91
+ }
92
+ ```
93
+
94
+ ### index.js
95
+ ```javascript
96
+ /** @param {Object} input - Validated against input_schema */
97
+ module.exports = async function(input) {
98
+ // Execute tool logic
99
+ return { success: true, messageId: "msg_123" };
100
+ };
101
+ ```
102
+
103
+ ### Execution Model
104
+ - **No sandboxing** — tools run in the runtime process via `require()`. User's responsibility.
105
+ - **Input validation** — `ajv` validates args against `input_schema` before execution.
106
+ - **Timeout** — enforced by runtime. Default 30s. Tool killed on timeout.
107
+ - **Errors** — thrown errors caught by runtime, formatted as tool error message for LLM.
108
+ - **Hot reload** — tool re-loaded from disk on each invocation (no caching).
109
+ - **Dependencies** — custom tools can have their own `node_modules` (user runs `npm install` in tool folder).
110
+
111
+ ## MCP Tools
112
+
113
+ MCP (Model Context Protocol) tools from external MCP servers.
114
+
115
+ - **Client:** Existing MCP client library (not custom-built)
116
+ - **Transports:** `stdio` and `Streamable HTTP`
117
+ - **Configuration:** In `settings.json` under `mcpServers`
118
+ - **Caching:** Tool descriptions cached, refreshed on server reconnect
119
+ - **Permissions:** MCP tools subject to the same deny/ask/allow permission system. Use `mcp:<server>.<tool>(pattern)` format in permissions (see 09-permissions.md).
120
+ - **Not the primary extension mechanism** — built-in + custom JS tools are core
121
+
122
+ ## Tool Resolution Order
123
+
124
+ When the LLM requests a tool, resolved in order:
125
+ ```
126
+ 1. Built-in tools ← always available
127
+ 2. Agent-level custom tools ← ./.veil/agents/<name>/tools/
128
+ 3. Project-level custom tools ← ./.veil/tools/
129
+ 4. Global custom tools ← ~/.veil/tools/
130
+ 5. MCP tools ← from configured MCP servers
131
+ 6. NOT FOUND → error returned to LLM
132
+ ```
133
+
134
+ Name conflicts: first match wins. Agent tools shadow project tools shadow global tools shadow MCP tools.
135
+
136
+ ## Tool Discovery (Hybrid Tiered Loading)
137
+
138
+ Context window is finite. Not all tool descriptions should be loaded at once.
139
+
140
+ - **Tier 1 (always in context):** Built-in tools — name + description + full schema (~24 tools)
141
+ - **Tier 2 (listed, not loaded):** Custom (agent + project + global) + MCP tools — name + one-line description only
142
+ - **Tier 3 (on-demand):** Full schema loaded via `tool_search` when agent needs it
143
+
144
+ The `tool_search` tool lets agents discover and load tools they don't have full schemas for:
145
+ ```
146
+ tool_search("slack") → returns full schema for send_slack_message
147
+ ```
148
+
149
+ ## Skills
150
+
151
+ Skills are **prompt templates** that agents invoke. They are NOT tools — they inject instructions or spawn sub-agent contexts.
152
+
153
+ Skill resolution order:
154
+ ```
155
+ 1. ./.veil/agents/<agentName>/skills/ ← agent-specific
156
+ 2. ./.veil/skills/ ← project-level
157
+ 3. ~/.veil/skills/ ← global
158
+ 4. NOT FOUND → error
159
+ ```
160
+
161
+ ### SKILL.md
162
+ ```markdown
163
+ ---
164
+ name: review-code
165
+ description: Review code for security and performance issues
166
+ argument-hint: [file-path]
167
+ allowed-tools: bash, read_file, edit_file
168
+ context: fork
169
+ agent: anthropic/claude-3-haiku
170
+ disable-model-invocation: false
171
+ ---
172
+
173
+ Review the following file: $1
174
+
175
+ Focus on:
176
+ 1. Security vulnerabilities
177
+ 2. Performance bottlenecks
178
+ 3. Code style issues
179
+ ```
180
+
181
+ ### Execution Modes
182
+ - **`context: main`** — skill content injected into current conversation
183
+ - **`context: fork`** — skill runs in an isolated sub-agent context (via `agent_spawn`)
184
+
185
+ ### Loading
186
+ - **Default:** Names + descriptions listed in system prompt. Full content loaded on-demand when invoked.
187
+ - **Auto-loaded:** Skills listed in agent.json `autoLoadSkills` array have full content always in system prompt.
188
+ - **Disabled:** When agent.json `skillDiscovery: false`, no skills listed in system prompt. Agent can only use skills explicitly in `autoLoadSkills`.
189
+ - **`disable-model-invocation`:** When `true`, only user/API can trigger this skill — the agent cannot invoke it autonomously. Default: `false`. Use for dangerous skills (e.g., `/deploy-prod`) that require human initiation.
190
+
191
+ ### Skill Invocation via Agent Communication
192
+ When using `agent_spawn`, `agent_message`, or `agent_send`, the instruction can start with `/skill-name` to trigger a specific skill on the target agent:
193
+ ```
194
+ agent_spawn(agent: "researcher", instruction: "/deep-analysis What are the security risks?")
195
+ ```
196
+ Runtime detects the `/` prefix, looks up the skill on the target agent, and executes it with the rest of the instruction as `$ARGUMENTS`.
197
+
198
+ ### Template Variables
199
+
200
+ **`$AGENT_FOLDER`** — resolved in **AGENT.md, SKILL.md, and heartbeat files** at load time. Returns the absolute path to the agent's folder. Also available as the `VEIL_AGENT_FOLDER` environment variable in custom tool execution and hooks.
201
+
202
+ **Skill-only variables** — resolved only when a skill is invoked. They do NOT apply to AGENT.md, agent.json, or heartbeat files.
203
+
204
+ | Variable | Scope | Description |
205
+ |----------|-------|-------------|
206
+ | `$AGENT_FOLDER` | AGENT.md, SKILL.md, heartbeat | Absolute path to the agent's folder |
207
+ | `$ARGUMENTS` | SKILL.md only | All arguments passed when skill is invoked |
208
+ | `$1`, `$2`, ... | SKILL.md only | Arguments by position |
209
+ | `${VEIL_SESSION_ID}` | SKILL.md only | Current session ID |
210
+ | `${VEIL_TASK_ID}` | SKILL.md only | Current task ID |
211
+ | `${VEIL_AGENT_NAME}` | SKILL.md only | Current agent name |
@@ -0,0 +1,243 @@
1
+ # 06 — Communication & Orchestration
2
+
3
+ ## Agent-to-Agent Communication
4
+
5
+ Five mechanisms, each for a different use case:
6
+
7
+ ### 1. `agent_message` (Sync Direct Message)
8
+ Agent A sends a message to Agent B and **blocks** until B responds.
9
+
10
+ ```
11
+ Agent A calls agent_message(agent: "researcher", message: "What's the status of X?")
12
+
13
+ If Agent B is idle:
14
+ Runtime creates a temporary chat session for Agent B
15
+ Agent B processes the message (may use tools)
16
+ Agent B's response returned to Agent A as tool output
17
+ If Agent B is busy:
18
+ Message queued. Agent A blocks. Message injected into B at next
19
+ safe injection point (between tool calls). B's response returned.
20
+ ```
21
+
22
+ **Use when:** Quick question/answer. B's response is needed to continue.
23
+ **Timeout:** Configurable. Default 60s.
24
+
25
+ ### 2. `agent_send` (Async Fire-and-Forget)
26
+ Agent A sends a message to Agent B and **continues immediately**. Returns a `message_id`.
27
+
28
+ ```
29
+ Agent A calls agent_send(
30
+ agent: "notifier",
31
+ message: "Deploy completed",
32
+ followup: false // default: false
33
+ )
34
+
35
+ Runtime queues the message for Agent B
36
+
37
+ Agent A gets back { messageId: "msg_xxx" } and continues
38
+
39
+ If Agent B is idle:
40
+ Message delivered immediately as a new chat turn
41
+ If Agent B is busy:
42
+ Message held in queue, injected at next safe point
43
+ (between tool calls — after tool result, before next LLM call)
44
+ If followup: true:
45
+ Message only injected after Agent B fully finishes current work
46
+ ```
47
+
48
+ **Use when:** Notifications, status updates, triggering side effects without waiting.
49
+
50
+ **`followup` behavior per mode:**
51
+ - **Chat mode:** `followup: true` waits until agent's current response is complete (no pending tool calls).
52
+ - **Task mode:** `followup: true` waits until task finishes entirely (status = finished/failed).
53
+ - **Daemon mode:** `followup: true` waits until current tick completes.
54
+ - **Subagent mode:** `followup: true` waits until sub-agent finishes its work.
55
+
56
+ ### 3. `task_create` (Async Task Delegation)
57
+ Agent A creates a **task** for Agent B. The task enters the queue and runs independently.
58
+
59
+ ```
60
+ Agent A calls task_create(agent: "researcher", input: "Research topic X", priority: "high")
61
+
62
+ Runtime creates task in SQLite: status = pending
63
+
64
+ Agent A gets back { taskId: "task_xxx" } and can:
65
+ - Continue working (fire-and-forget)
66
+ - Poll with task_status(taskId)
67
+ - Set own status to "waiting" until task completes
68
+ ```
69
+
70
+ **Use when:** Long-running work. Parallel execution. Work that should be tracked/queryable.
71
+
72
+ ## Sub-Agent Spawning
73
+
74
+ ### `agent_spawn` (Isolated Context — Sync or Async)
75
+
76
+ ```
77
+ Agent A calls agent_spawn(
78
+ agent: "explore",
79
+ instruction: "Find all files related to authentication",
80
+ returnMode: "summary", // "summary" | "raw" | "lastMessage"
81
+ wait: true // true (default) = sync, false = async
82
+ )
83
+
84
+ Runtime creates a new session with clean context:
85
+ - Sub-agent's AGENT.md + SOUL.md loaded
86
+ - Instruction injected as user message
87
+ - Tools inherited from sub-agent's agent.json (`modes.subagent`)
88
+ - If instruction starts with `/skill-name`, runtime triggers that skill
89
+ - Runtime creates a task entry in SQLite (trackable via task_status)
90
+
91
+ If wait: true (default — synchronous):
92
+ Sub-agent runs to completion (or timeout)
93
+ Result returned to Agent A based on returnMode
94
+ If wait: false (asynchronous):
95
+ Agent A gets back { taskId: "task_xxx" } immediately
96
+ Runtime auto-subscribes Agent A to this task (durable notification)
97
+ Sub-agent runs in background using modes.subagent config
98
+ Agent A can:
99
+ - Spawn more sub-agents (parallel fan-out)
100
+ - Poll with task_status(taskId) to get result early
101
+ When sub-agent finishes:
102
+ - Result stored as task output (per returnMode)
103
+ - Notification auto-injected into Agent A's conversation
104
+ ```
105
+
106
+ **Parallel fan-out pattern** (deep research, multi-source investigation):
107
+ ```
108
+ // Orchestrator spawns 5 researchers in parallel:
109
+ spawn1 = agent_spawn(agent: "researcher", instruction: "...", wait: false)
110
+ spawn2 = agent_spawn(agent: "researcher", instruction: "...", wait: false)
111
+ spawn3 = agent_spawn(agent: "researcher", instruction: "...", wait: false)
112
+ // Auto-subscribed to all 3 — no explicit task_subscribe needed.
113
+ // Orchestrator continues planning while subagents work...
114
+ // Notifications arrive as each subagent completes.
115
+ // Orchestrator collects results via task_status and synthesizes.
116
+ ```
117
+
118
+ **Fallback:** If the target agent doesn't have `modes.subagent` defined or enabled, `agent_spawn` falls back to chat-like behavior using `modes.chat` config.
119
+
120
+ **Key properties:**
121
+ - Isolated context window (no parent conversation history)
122
+ - Gets its own task + session in SQLite (queryable, auditable)
123
+ - Token usage aggregated with parent task
124
+ - Max recursion depth: configurable (default 3 — agents can spawn sub-agents that spawn sub-agents)
125
+ - Timeout: inherits parent's remaining time or fixed limit (whichever is shorter)
126
+ - `wait: false` spawns count against `maxConcurrentTasks` limit. When limit is reached, new spawns queue as `pending` until a slot opens (no error, no blocking).
127
+ - **Daemon mode exemption:** Daemon ticks do NOT auto-subscribe. Daemon sessions are ephemeral (per-tick). Use system-wide `triggers` (task.completed, task.failed) instead.
128
+
129
+ ### 4. `task_subscribe` (Durable Callback for External Observers)
130
+ Any agent can subscribe to another task's completion — even tasks it didn't create. When the watched task finishes/fails, the runtime injects a notification into the subscriber's conversation.
131
+
132
+ **Note:** `agent_spawn(wait: false)` **auto-subscribes** the caller. You do NOT need `task_subscribe` for your own async spawns. `task_subscribe` is for **external observers**: monitoring agents, daemons, or agents that want to watch tasks they didn't create.
133
+
134
+ ```
135
+ // Monitoring agent watches a task it didn't create:
136
+ Agent M calls task_subscribe(taskId: "task_xyz")
137
+ → registered in SQLite. Agent M continues other work.
138
+
139
+ ... later, task_xyz finishes ...
140
+
141
+ If Agent M's session is still active:
142
+ Runtime injects system message at next safe point:
143
+ "[System] Task task_xyz completed: { output: ... }"
144
+
145
+ If Agent M's session has ended:
146
+ Runtime reopens the session and injects the notification.
147
+ Agent M wakes up, processes the result, and can continue or finish.
148
+ ```
149
+
150
+ **Use when:** Monitoring agent, daemon, or script wants to observe tasks it didn't create.
151
+ **Auto-subscribe:** `agent_spawn(wait: false)` and `task_create` callers are auto-subscribed — no need to call `task_subscribe` for your own work.
152
+ **Durability:** Subscriptions are stored in SQLite and survive session completion. If the subscriber's session has ended, the runtime reopens it to deliver the notification.
153
+
154
+ **Edge cases:**
155
+ - **Delivery timeout:** If session cannot be reopened within 60s, subscription marked `failed` and logged.
156
+ - **Multiple subscribers:** Each subscriber gets notified independently. No deduplication.
157
+ - **Cleanup:** Delivered subscriptions (`status: delivered`) are purged after 7 days. Failed subscriptions retained for debugging.
158
+
159
+ ### When to Use What
160
+
161
+ | Mechanism | Sync? | Context | Use Case |
162
+ |-----------|-------|---------|----------|
163
+ | `agent_message` | Yes | Temporary session | Quick Q&A between agents |
164
+ | `agent_send` | No | Queued | Notifications, fire-and-forget |
165
+ | `task_create` | No | New task session (modes.task) | Long work, parallel execution, tracked (auto-subscribes caller) |
166
+ | `task_subscribe` | No | Durable callback | External observer watching tasks it didn't create |
167
+ | `agent_spawn` | Configurable | Isolated sub-session (modes.subagent) | `wait:true` = focused sub-task now; `wait:false` = parallel fan-out (auto-subscribes) |
168
+
169
+ **`agent_spawn` vs `task_create`:** Both can run async and both auto-subscribe the caller. The difference is the **mode used**: `agent_spawn` uses `modes.subagent` (restricted tools, isolated context, result condensed). `task_create` uses `modes.task` (full task capabilities, formal task lifecycle). Use `agent_spawn(wait: false)` when you want parallel sub-agents with subagent-mode restrictions. Use `task_create` when you want a formal, independently tracked task.
170
+
171
+ ## Task System
172
+
173
+ ### Task Lifecycle
174
+ ```
175
+ pending → processing → [waiting] → finished
176
+ ↘ ↗
177
+ → failed
178
+ → canceled
179
+
180
+ **Transitions from `waiting`:**
181
+ - `waiting` → `processing` — via `task_respond` (human provides input)
182
+ - `waiting` → `failed` — via API cancel or wait timeout (`maxWaitSeconds`, default 3600)
183
+ - `waiting` → `canceled` — via `DELETE /tasks/:id`
184
+
185
+ **Note:** Tasks in `waiting` status do NOT count against `maxConcurrentTasks` (they're paused, not running).
186
+ ```
187
+
188
+ ### Task Fields
189
+ | Field | Type | Description |
190
+ |-------|------|-------------|
191
+ | `id` | string | `task_<nanoid>` |
192
+ | `agentName` | string | Which agent runs this task |
193
+ | `status` | enum | pending/processing/waiting/finished/failed/canceled |
194
+ | `priority` | enum | high/normal/low |
195
+ | `parentTaskId` | string? | If created by another task |
196
+ | `input` | object | Prompt + files + context |
197
+ | `output` | object? | Final result (when finished) |
198
+ | `error` | string? | Error message (when failed) |
199
+ | `iterations` | number | Loop iterations executed |
200
+ | `maxIterations` | number | Limit |
201
+ | `maxDurationSeconds` | number | Limit |
202
+ | `tokenUsage` | object | input/output/cache tokens + cost |
203
+ | `tags` | string[] | User-defined tags for filtering |
204
+ | `createdAt` | timestamp | |
205
+ | `startedAt` | timestamp? | |
206
+ | `finishedAt` | timestamp? | |
207
+
208
+ ### Task Dependencies
209
+ Managed by the **orchestrating agent**, not by the runtime. The runtime provides `task_create`, `task_status`, and the agent decides ordering, parallelism, and failure handling.
210
+
211
+ ### Parallel Execution
212
+ Multiple tasks can run concurrently. Configurable concurrency limit in settings.json (`maxConcurrentTasks`, default 5).
213
+
214
+ ## Message Queue
215
+
216
+ When a message targets a busy agent (mid-loop), it enters the **message queue**.
217
+
218
+ ### Queue Behavior
219
+ - Messages stored in SQLite `agent_messages` table
220
+ - **Normal messages** (`followup: false`): injected at the next **safe injection point** — between tool calls (after tool result returned, before next LLM call)
221
+ - **Followup messages** (`followup: true`): held until agent fully finishes current work (no more tool calls)
222
+ - Injected as system messages: `[Message from <agentName>]: <content>`
223
+ - Multiple queued messages injected in FIFO order
224
+
225
+ ### Safe Injection Points
226
+ ```
227
+ LLM responds with tool_calls
228
+
229
+ Execute tool call #1
230
+
231
+ ★ SAFE INJECTION POINT ★ ← drain normal message queue here
232
+
233
+ Execute tool call #2
234
+
235
+ Loop back to LLM
236
+
237
+ LLM responds with no tool_calls (done)
238
+
239
+ ★ FOLLOWUP INJECTION POINT ★ ← drain followup queue here
240
+ ```
241
+
242
+ ### For `agent_message` (sync)
243
+ If target agent is busy, the calling agent **blocks and waits**. The message is queued and injected at the next **safe injection point** (between tool calls). Once the target agent processes it and produces a response, that response is returned to the caller. This is consistent with `agent_message`'s blocking nature — the caller waits, but the target agent doesn't have to fully finish its current work first.
@@ -0,0 +1,218 @@
1
+ # 07 — Storage
2
+
3
+ ## Architecture
4
+
5
+ **SQLite for runtime data.** Single file: `~/.veil/data.db`.
6
+ **Files for definitions.** agent.json, settings.json, AGENT.md — human-editable, versionable.
7
+ **Files for mutable state.** Memory (`.veil/memory/`), heartbeats (`.veil/heartbeats/`) — human-editable, agent-writable.
8
+
9
+ ### Naming Convention
10
+ - **JSON configs:** camelCase (`maxDurationSeconds`, `allowedAgents`)
11
+ - **SQLite columns:** snake_case (`max_duration_seconds`, `allowed_agents`)
12
+
13
+ The runtime maps between conventions automatically. This is a standard pattern — JSON for human-facing configs, snake_case for SQL.
14
+
15
+ ## SQLite Configuration
16
+
17
+ ```javascript
18
+ db.pragma('journal_mode = WAL'); // Concurrent readers while writer is active
19
+ db.pragma('synchronous = NORMAL'); // Optimal crash safety for WAL mode
20
+ db.pragma('foreign_keys = ON'); // Enforce relational integrity
21
+ ```
22
+
23
+ ## Tables (Core)
24
+
25
+ ### sessions
26
+ | Column | Type | Description |
27
+ |--------|------|-------------|
28
+ | id | TEXT PK | `sess_<nanoid>` |
29
+ | agent_name | TEXT NOT NULL | Agent that owns this session |
30
+ | mode | TEXT NOT NULL | `chat` / `task` / `daemon` / `subagent` |
31
+ | instance_folder | TEXT | Project folder path (tracks which directory the session was started from, used to resolve project-level agents/settings). See **Project Isolation** below. |
32
+ | status | TEXT | `active` / `closed` |
33
+ | model | TEXT | Model used |
34
+ | title | TEXT | Auto-generated title |
35
+ | message_count | INTEGER | |
36
+ | token_input | INTEGER | Cumulative input tokens |
37
+ | token_output | INTEGER | Cumulative output tokens |
38
+ | token_cache | INTEGER | Cumulative cache tokens |
39
+ | estimated_cost | REAL | Estimated USD cost |
40
+ | compaction_count | INTEGER | Times compacted |
41
+ | created_at | TEXT | ISO 8601 |
42
+ | updated_at | TEXT | ISO 8601 |
43
+
44
+ ### messages
45
+ | Column | Type | Description |
46
+ |--------|------|-------------|
47
+ | id | INTEGER PK AUTOINCREMENT | |
48
+ | session_id | TEXT FK → sessions | |
49
+ | role | TEXT NOT NULL | `system` / `user` / `assistant` / `tool` |
50
+ | content | TEXT | Text content (may be large) |
51
+ | tool_calls | TEXT | JSON array of tool calls |
52
+ | tool_call_id | TEXT | For tool response messages |
53
+ | token_count | INTEGER | Tokens in this message |
54
+ | created_at | TEXT | ISO 8601 |
55
+
56
+ ### tasks
57
+ | Column | Type | Description |
58
+ |--------|------|-------------|
59
+ | id | TEXT PK | `task_<nanoid>` |
60
+ | session_id | TEXT FK → sessions | |
61
+ | agent_name | TEXT NOT NULL | |
62
+ | status | TEXT NOT NULL | pending/processing/waiting/finished/failed/canceled |
63
+ | priority | TEXT | high/normal/low |
64
+ | parent_task_id | TEXT FK → tasks | |
65
+ | input | TEXT | JSON |
66
+ | output | TEXT | JSON |
67
+ | error | TEXT | |
68
+ | tags | TEXT | JSON array |
69
+ | iterations | INTEGER | |
70
+ | max_iterations | INTEGER | |
71
+ | max_duration_seconds | INTEGER | |
72
+ | token_input | INTEGER | |
73
+ | token_output | INTEGER | |
74
+ | token_cache | INTEGER | |
75
+ | estimated_cost | REAL | |
76
+ | created_at | TEXT | |
77
+ | started_at | TEXT | |
78
+ | finished_at | TEXT | |
79
+ | updated_at | TEXT | |
80
+
81
+ ### task_events
82
+ | Column | Type | Description |
83
+ |--------|------|-------------|
84
+ | id | INTEGER PK AUTOINCREMENT | |
85
+ | task_id | TEXT FK → tasks | |
86
+ | type | TEXT NOT NULL | iteration.start, tool.start, tool.end, status.change, custom |
87
+ | data | TEXT | JSON payload (see schemas below) |
88
+ | created_at | TEXT | ISO 8601 |
89
+
90
+ **Event `data` schemas:**
91
+ ```json
92
+ // type: "iteration.start"
93
+ { "iteration": 5, "tokensSoFar": 12500 }
94
+
95
+ // type: "tool.start"
96
+ { "toolName": "bash", "toolInput": { "command": "npm test" } }
97
+
98
+ // type: "tool.end"
99
+ { "toolName": "bash", "durationMs": 1234, "success": true, "outputPreview": "..." }
100
+
101
+ // type: "status.change"
102
+ { "from": "processing", "to": "waiting", "reason": "task_respond called" }
103
+
104
+ // type: "custom" (via log_write tool)
105
+ { "level": "info", "message": "Completed phase 1", "metadata": { ... } }
106
+ ```
107
+
108
+ ### todos
109
+ | Column | Type | Description |
110
+ |--------|------|-------------|
111
+ | session_id | TEXT PK FK → sessions | |
112
+ | items | TEXT NOT NULL | JSON array of {id, content, status, priority} |
113
+ | updated_at | TEXT | |
114
+
115
+ ### agent_messages
116
+ | Column | Type | Description |
117
+ |--------|------|-------------|
118
+ | id | INTEGER PK AUTOINCREMENT | |
119
+ | target_agent | TEXT NOT NULL | Agent this message is queued for |
120
+ | target_session_id | TEXT FK → sessions | Session to inject into (if active) |
121
+ | from_agent | TEXT NOT NULL | Sending agent name |
122
+ | content | TEXT NOT NULL | Message content |
123
+ | followup | INTEGER NOT NULL DEFAULT 0 | 1 = only inject after agent finishes |
124
+ | skill | TEXT | Skill to trigger (if `/skill-name` prefix) |
125
+ | status | TEXT NOT NULL | pending / delivered / expired |
126
+ | correlation_id | TEXT | For sync `agent_message` response matching |
127
+ | response | TEXT | Response content (for sync `agent_message`) |
128
+ | created_at | TEXT | ISO 8601 |
129
+ | delivered_at | TEXT | ISO 8601 |
130
+
131
+ ### task_subscriptions
132
+ | Column | Type | Description |
133
+ |--------|------|-------------|
134
+ | id | INTEGER PK AUTOINCREMENT | |
135
+ | task_id | TEXT FK → tasks NOT NULL | Task being watched |
136
+ | subscriber_session_id | TEXT FK → sessions NOT NULL | Session to notify (reopened if closed) |
137
+ | subscriber_agent | TEXT NOT NULL | Agent that subscribed |
138
+ | status | TEXT NOT NULL | pending / delivered |
139
+ | created_at | TEXT | ISO 8601 |
140
+ | delivered_at | TEXT | ISO 8601 |
141
+
142
+ ### schema_version
143
+ | Column | Type | Description |
144
+ |--------|------|-------------|
145
+ | version | INTEGER PK | Current schema version |
146
+ | applied_at | TEXT | ISO 8601 |
147
+
148
+ ## Indexes
149
+
150
+ ```sql
151
+ CREATE INDEX idx_sessions_agent ON sessions(agent_name);
152
+ CREATE INDEX idx_sessions_status ON sessions(status);
153
+ CREATE INDEX idx_sessions_created ON sessions(created_at);
154
+ CREATE INDEX idx_messages_session ON messages(session_id);
155
+ CREATE INDEX idx_tasks_agent ON tasks(agent_name);
156
+ CREATE INDEX idx_tasks_status ON tasks(status);
157
+ CREATE INDEX idx_tasks_parent ON tasks(parent_task_id);
158
+ CREATE INDEX idx_tasks_created ON tasks(created_at);
159
+ CREATE INDEX idx_task_events_task ON task_events(task_id);
160
+ CREATE INDEX idx_agent_messages_target ON agent_messages(target_agent, status);
161
+ CREATE INDEX idx_task_subscriptions_task ON task_subscriptions(task_id, status);
162
+ ```
163
+
164
+ ## Migrations
165
+
166
+ - Numbered SQL files: `migrations/001-initial.sql`, `002-add-events.sql`, etc.
167
+ - `schema_version` table tracks current version and applied timestamp
168
+ - Additive-only rule: never drop columns, never rename tables
169
+ - Applied in transaction on startup
170
+
171
+ **All migration SQL uses `IF NOT EXISTS` (idempotent).** Migrations are run on every startup, not only when the database is freshly created. This prevents timing bugs where the DB file exists (SQLite created it) but the schema hasn't been applied yet:
172
+ ```sql
173
+ CREATE TABLE IF NOT EXISTS sessions (...);
174
+ CREATE TABLE IF NOT EXISTS messages (...);
175
+ CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions(agent_name);
176
+ ```
177
+ The `schema_version` table check still determines whether to run a given numbered migration (skip if already at that version), but the SQL itself is always safe to re-run. Never use `CREATE TABLE` without `IF NOT EXISTS`.
178
+
179
+ ## Project Isolation
180
+
181
+ All sessions go to a single global DB (`~/.veil/data.db`). Project isolation is handled via `instance_folder`:
182
+
183
+ - **Same agent name, different projects:** Each session records its `instance_folder`. When listing/querying, results are filtered by the current working directory.
184
+ - **Agent resolution:** Runtime uses `instance_folder` to resolve project-level agents first (`./<instance_folder>/.veil/agents/`) before falling back to global (`~/.veil/agents/`).
185
+ - **No cross-project leakage:** An agent in Project A cannot accidentally see sessions from Project B — queries always filter by `instance_folder`.
186
+
187
+ ## Lazy Loading
188
+
189
+ - **Session metadata** always queryable (SELECT from sessions)
190
+ - **Messages** loaded on-demand (only when session is active or API requests them)
191
+ - **LRU cache** — max 50 active sessions in memory. Evict oldest on overflow.
192
+
193
+ ## Retention & Cleanup
194
+
195
+ Configurable in settings.json:
196
+ ```json
197
+ {
198
+ "storage": {
199
+ "retention": {
200
+ "sessions": { "maxAgeDays": 90, "maxCount": 10000 },
201
+ "tasks": { "maxAgeDays": 30, "maxCount": 5000 }
202
+ }
203
+ }
204
+ }
205
+ ```
206
+
207
+ **Cleanup process:**
208
+ 1. Runs on startup and every 24h
209
+ 2. Sessions/tasks older than `maxAgeDays` → archive to gzipped JSONL → delete from SQLite
210
+ 3. If count exceeds `maxCount` → archive oldest → delete
211
+ 4. Archive path: `~/.veil/archive/sessions-2026-03.jsonl.gz`
212
+
213
+ ## Backup
214
+
215
+ - Hot backup via SQLite `VACUUM INTO` (non-blocking)
216
+ - Triggered before cleanup runs
217
+ - Stored in `~/.veil/backups/data-2026-03-01.db`
218
+ - Keep last 3 backups