@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.
- package/.veil/agents/analyst/AGENT.md +21 -0
- package/.veil/agents/analyst/agent.json +23 -0
- package/.veil/agents/assistant/AGENT.md +15 -0
- package/.veil/agents/assistant/agent.json +19 -0
- package/.veil/agents/coder/AGENT.md +18 -0
- package/.veil/agents/coder/agent.json +19 -0
- package/.veil/agents/hello/AGENT.md +5 -0
- package/.veil/agents/hello/agent.json +13 -0
- package/.veil/agents/writer/AGENT.md +12 -0
- package/.veil/agents/writer/agent.json +17 -0
- package/.veil/memory/MEMORY.md +343 -0
- package/.veil/memory/agents/analyst/MEMORY.md +55 -0
- package/.veil/memory/agents/hello/MEMORY.md +12 -0
- package/.veil/runtime.pid +1 -0
- package/.veil/settings.json +10 -0
- package/.veil-studio/studio.db +0 -0
- package/.veil-studio/studio.db-shm +0 -0
- package/.veil-studio/studio.db-wal +0 -0
- package/PLAN/01-vision.md +26 -0
- package/PLAN/02-tech-stack.md +94 -0
- package/PLAN/03-agents.md +232 -0
- package/PLAN/04-runtime.md +171 -0
- package/PLAN/05-tools.md +211 -0
- package/PLAN/06-communication.md +243 -0
- package/PLAN/07-storage.md +218 -0
- package/PLAN/08-api-cli.md +153 -0
- package/PLAN/09-permissions.md +108 -0
- package/PLAN/10-ably.md +105 -0
- package/PLAN/11-file-formats.md +442 -0
- package/PLAN/12-folder-structure.md +205 -0
- package/PLAN/13-operations.md +212 -0
- package/PLAN/README.md +23 -0
- package/README.md +128 -0
- package/REPORT.md +174 -0
- package/TODO.md +45 -0
- package/ai-tests/FRONTEND_PROMPT.md +220 -0
- package/ai-tests/Research & Planning.md +814 -0
- package/ai-tests/prompt-001-basic-api.md +230 -0
- package/ai-tests/prompt-002-basic-flows.md +230 -0
- package/ai-tests/prompt-003-agent-behaviors.md +220 -0
- package/api/middleware.js +60 -0
- package/api/routes/agents.js +193 -0
- package/api/routes/chat.js +93 -0
- package/api/routes/completions.js +122 -0
- package/api/routes/daemons.js +80 -0
- package/api/routes/memory.js +169 -0
- package/api/routes/models.js +40 -0
- package/api/routes/remote-methods.js +74 -0
- package/api/routes/sessions.js +208 -0
- package/api/routes/settings.js +108 -0
- package/api/routes/system.js +50 -0
- package/api/routes/tasks.js +270 -0
- package/api/server.js +120 -0
- package/cli/formatter.js +70 -0
- package/cli/index.js +443 -0
- package/cli/parser.js +113 -0
- package/config/config.json +10 -0
- package/config/models.json +6826 -0
- package/core/agent.js +329 -0
- package/core/cancel.js +38 -0
- package/core/compaction.js +176 -0
- package/core/events.js +13 -0
- package/core/loop.js +564 -0
- package/core/memory.js +51 -0
- package/core/prompt.js +185 -0
- package/core/queue.js +96 -0
- package/core/registry.js +291 -0
- package/core/remote-methods.js +124 -0
- package/core/router.js +386 -0
- package/core/running-sessions.js +18 -0
- package/docs/api/01-system.md +84 -0
- package/docs/api/02-agents.md +374 -0
- package/docs/api/03-chat.md +269 -0
- package/docs/api/04-tasks.md +470 -0
- package/docs/api/05-sessions.md +444 -0
- package/docs/api/06-daemons.md +142 -0
- package/docs/api/07-memory.md +186 -0
- package/docs/api/08-settings.md +133 -0
- package/docs/api/09-models.md +119 -0
- package/docs/api/09-websocket.md +350 -0
- package/docs/api/10-completions.md +134 -0
- package/docs/api/README.md +116 -0
- package/docs/guide/01-quickstart.md +220 -0
- package/docs/guide/02-folder-structure.md +185 -0
- package/docs/guide/03-configuration.md +252 -0
- package/docs/guide/04-agents.md +267 -0
- package/docs/guide/05-cli.md +290 -0
- package/docs/guide/06-tools.md +643 -0
- package/docs/guide/07-permissions.md +236 -0
- package/docs/guide/08-memory.md +139 -0
- package/docs/guide/09-multi-agent.md +271 -0
- package/docs/guide/10-daemons.md +226 -0
- package/docs/guide/README.md +53 -0
- package/docs/index.html +623 -0
- package/examples/README.md +151 -0
- package/examples/agents/assistant/AGENT.md +31 -0
- package/examples/agents/assistant/SOUL.md +9 -0
- package/examples/agents/assistant/agent.json +74 -0
- package/examples/agents/hello/AGENT.md +15 -0
- package/examples/agents/hello/agent.json +14 -0
- package/examples/agents/monitor/AGENT.md +51 -0
- package/examples/agents/monitor/agent.json +33 -0
- package/examples/agents/monitor/heartbeats/monitor.md +24 -0
- package/examples/agents/orchestrator/AGENT.md +70 -0
- package/examples/agents/orchestrator/agent.json +30 -0
- package/examples/agents/researcher/AGENT.md +52 -0
- package/examples/agents/researcher/agent.json +49 -0
- package/examples/agents/researcher/skills/web-research.md +28 -0
- package/examples/skills/code-review.md +72 -0
- package/examples/skills/summarise.md +59 -0
- package/examples/skills/web-research.md +42 -0
- package/examples/tools/word-count/index.js +27 -0
- package/examples/tools/word-count/tool.json +18 -0
- package/infrastructure/database.js +563 -0
- package/infrastructure/scheduler.js +122 -0
- package/llm/client.js +206 -0
- package/migrations/001-initial.sql +121 -0
- package/migrations/002-debuggability.sql +13 -0
- package/migrations/003-drop-orphaned-columns.sql +72 -0
- package/migrations/004-session-message-token-fields.sql +78 -0
- package/migrations/005-session-thinking.sql +5 -0
- package/package.json +30 -0
- package/schemas/agent.json +143 -0
- package/schemas/settings.json +111 -0
- package/scripts/fetch-models.js +93 -0
- package/session-debug-scenario.md +248 -0
- package/settings/fields.js +52 -0
- package/system-prompts/base-core.md +7 -0
- package/system-prompts/environment.md +13 -0
- package/system-prompts/reminders/anti-drift.md +6 -0
- package/system-prompts/reminders/stall-recovery.md +10 -0
- package/system-prompts/safety-rules.md +25 -0
- package/system-prompts/task-heuristics.md +27 -0
- package/test/client.js +71 -0
- package/test/integration/01-health.test.js +25 -0
- package/test/integration/02-agents.test.js +80 -0
- package/test/integration/03-chat-hello.test.js +48 -0
- package/test/integration/04-chat-multiturn.test.js +61 -0
- package/test/integration/05-chat-writer.test.js +48 -0
- package/test/integration/06-task-basic.test.js +68 -0
- package/test/integration/07-task-tools.test.js +74 -0
- package/test/integration/08-task-code-analysis.test.js +69 -0
- package/test/integration/09-memory-analyst.test.js +63 -0
- package/test/integration/10-task-advanced.test.js +85 -0
- package/test/integration/11-sessions-advanced.test.js +84 -0
- package/test/integration/12-assistant-chat-tools.test.js +75 -0
- package/test/integration/13-edge-cases.test.js +99 -0
- package/test/integration/14-cancel.test.js +62 -0
- package/test/integration/15-debug.test.js +106 -0
- package/test/integration/16-memory-api.test.js +83 -0
- package/test/integration/17-settings-api.test.js +41 -0
- package/test/integration/18-tool-search-activation.test.js +119 -0
- package/test/results/.gitkeep +0 -0
- package/test/runner.js +206 -0
- package/test/smoke.js +216 -0
- package/tools/agent_message.js +85 -0
- package/tools/agent_send.js +80 -0
- package/tools/agent_spawn.js +44 -0
- package/tools/bash.js +49 -0
- package/tools/edit_file.js +41 -0
- package/tools/glob.js +64 -0
- package/tools/grep.js +82 -0
- package/tools/list_dir.js +63 -0
- package/tools/log_write.js +31 -0
- package/tools/memory_read.js +38 -0
- package/tools/memory_search.js +65 -0
- package/tools/memory_write.js +42 -0
- package/tools/read_file.js +48 -0
- package/tools/sleep.js +22 -0
- package/tools/task_create.js +41 -0
- package/tools/task_respond.js +37 -0
- package/tools/task_spawn.js +64 -0
- package/tools/task_status.js +39 -0
- package/tools/task_subscribe.js +37 -0
- package/tools/todo_read.js +26 -0
- package/tools/todo_write.js +38 -0
- package/tools/tool_activate.js +24 -0
- package/tools/tool_search.js +24 -0
- package/tools/web_fetch.js +50 -0
- package/tools/web_search.js +52 -0
- package/tools/write_file.js +28 -0
- package/ui/api.js +190 -0
- package/ui/app.js +281 -0
- package/ui/index.html +382 -0
- package/ui/views/agents.js +377 -0
- package/ui/views/chat.js +610 -0
- package/ui/views/connection.js +96 -0
- package/ui/views/daemons.js +129 -0
- package/ui/views/feed.js +194 -0
- package/ui/views/memory.js +263 -0
- package/ui/views/models.js +146 -0
- package/ui/views/sessions.js +314 -0
- package/ui/views/settings.js +142 -0
- package/ui/views/tasks.js +415 -0
- package/utils/context.js +49 -0
- package/utils/id.js +16 -0
- package/utils/models.js +88 -0
- package/utils/paths.js +213 -0
- package/utils/settings.js +172 -0
package/PLAN/05-tools.md
ADDED
|
@@ -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
|