@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
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Permissions
|
|
2
|
+
|
|
3
|
+
VeilCLI uses a layered allow/deny permission system to control which tools an agent can use in each mode.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
When an agent tries to call a tool, the runtime checks a permission chain from most-specific to least-specific:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
agent.modes.<mode>.permissions (most specific — wins)
|
|
13
|
+
↓
|
|
14
|
+
agent.modes.<mode>.tools / disallowedTools
|
|
15
|
+
↓
|
|
16
|
+
settings.json permissions (project-wide default)
|
|
17
|
+
↓
|
|
18
|
+
built-in defaults (deny everything not listed)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The **first matching rule wins**.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Permission Fields
|
|
26
|
+
|
|
27
|
+
### `allow`
|
|
28
|
+
|
|
29
|
+
An array of tool names that are explicitly permitted. Use `["*"]` as a wildcard to allow all tools.
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
"permissions": {
|
|
33
|
+
"allow": ["read_file", "list_dir", "bash"]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `deny`
|
|
38
|
+
|
|
39
|
+
An array of tool names that are explicitly blocked. **`deny` always wins over `allow`** — if a tool appears in both, it is denied.
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
"permissions": {
|
|
43
|
+
"allow": ["*"],
|
|
44
|
+
"deny": ["bash", "write_file"]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `ask`
|
|
49
|
+
|
|
50
|
+
*(Settings-level only — not available in agent.json)*
|
|
51
|
+
|
|
52
|
+
Tools that require interactive human approval before executing. When a tool in the `ask` list is called, the task pauses (`status: "waiting"`) and the caller must use `POST /tasks/:id/respond` to approve and continue.
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
"permissions": {
|
|
56
|
+
"allow": ["*"],
|
|
57
|
+
"ask": ["write_file", "edit_file", "bash"]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Where to Set Permissions
|
|
64
|
+
|
|
65
|
+
### 1. Global default — `settings.json`
|
|
66
|
+
|
|
67
|
+
Applies to all agents and all modes unless overridden:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"permissions": {
|
|
72
|
+
"allow": ["read_file", "list_dir", "grep", "glob", "web_search"],
|
|
73
|
+
"deny": [],
|
|
74
|
+
"ask": ["bash", "write_file"]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 2. Per-mode in `agent.json`
|
|
80
|
+
|
|
81
|
+
Overrides the global default for a specific mode of a specific agent:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"modes": {
|
|
86
|
+
"task": {
|
|
87
|
+
"enabled": true,
|
|
88
|
+
"permissions": {
|
|
89
|
+
"allow": ["read_file", "list_dir", "bash", "write_file"],
|
|
90
|
+
"deny": []
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 3. Tool whitelists and blacklists in `agent.json`
|
|
98
|
+
|
|
99
|
+
`tools` and `disallowedTools` provide an alternative syntax:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"modes": {
|
|
104
|
+
"task": {
|
|
105
|
+
"enabled": true,
|
|
106
|
+
"tools": ["read_file", "list_dir"],
|
|
107
|
+
"disallowedTools": ["bash"]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`tools` = controls which tools are shown to the LLM (tool list in the API call)
|
|
114
|
+
`disallowedTools` = hides tools from the LLM
|
|
115
|
+
`permissions.allow`/`deny` = controls which tools are allowed to *execute* at runtime (checked at call time, supports glob patterns like `"bash(rm *)"`)
|
|
116
|
+
|
|
117
|
+
You can use `tools`/`disallowedTools`, `permissions`, or both together.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Common Patterns
|
|
122
|
+
|
|
123
|
+
### Allow everything (development / trusted environment)
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"permissions": {
|
|
128
|
+
"allow": ["*"],
|
|
129
|
+
"deny": [],
|
|
130
|
+
"ask": []
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Read-only agent (safe for untrusted tasks)
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"modes": {
|
|
140
|
+
"task": {
|
|
141
|
+
"enabled": true,
|
|
142
|
+
"permissions": {
|
|
143
|
+
"allow": ["read_file", "list_dir", "grep", "glob", "web_search", "web_fetch"],
|
|
144
|
+
"deny": []
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Full access except destructive tools
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"permissions": {
|
|
156
|
+
"allow": ["*"],
|
|
157
|
+
"deny": ["bash", "write_file", "edit_file"]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Require approval for write operations
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"permissions": {
|
|
167
|
+
"allow": ["*"],
|
|
168
|
+
"deny": [],
|
|
169
|
+
"ask": ["write_file", "edit_file", "bash"]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Orchestrator agent (multi-agent tools only)
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"modes": {
|
|
179
|
+
"task": {
|
|
180
|
+
"enabled": true,
|
|
181
|
+
"permissions": {
|
|
182
|
+
"allow": ["agent_spawn", "task_create", "task_status", "task_subscribe", "log_write"],
|
|
183
|
+
"deny": []
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Permission Denied Behaviour
|
|
193
|
+
|
|
194
|
+
When a tool is denied, the agent receives a tool result of:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
Permission denied for tool "bash"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The agent loop continues — the LLM can choose to try another approach.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Agent Restrictions
|
|
205
|
+
|
|
206
|
+
In addition to tool permissions, you can restrict which sub-agents an agent is allowed to spawn:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"modes": {
|
|
211
|
+
"task": {
|
|
212
|
+
"allowedAgents": ["coder", "writer"],
|
|
213
|
+
"disallowedAgents": ["admin"]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`allowedAgents` — whitelist: only these agents can be spawned
|
|
220
|
+
`disallowedAgents` — blacklist: these agents are blocked
|
|
221
|
+
|
|
222
|
+
If both are empty, all agents are allowed (subject to the target agent's own `subagent` mode config).
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Quick Reference
|
|
227
|
+
|
|
228
|
+
| Field | Location | Description |
|
|
229
|
+
|-------|----------|-------------|
|
|
230
|
+
| `permissions.allow` | settings.json or agent.json modes | Tools allowed to execute at runtime (or `["*"]`). Supports glob patterns. |
|
|
231
|
+
| `permissions.deny` | settings.json or agent.json modes | Tools blocked from executing at runtime. Wins over allow. Supports glob patterns. |
|
|
232
|
+
| `permissions.ask` | **settings.json only** | Tools requiring human approval before executing |
|
|
233
|
+
| `tools` | agent.json modes | Tools shown to the LLM (whitelist — LLM cannot call tools not in this list) |
|
|
234
|
+
| `disallowedTools` | agent.json modes | Tools hidden from the LLM (blacklist) |
|
|
235
|
+
| `allowedAgents` | agent.json modes | Sub-agents this agent may spawn |
|
|
236
|
+
| `disallowedAgents` | agent.json modes | Sub-agents blocked from spawning |
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Memory & Context Compaction
|
|
2
|
+
|
|
3
|
+
VeilCLI provides two complementary systems for managing long-running agent knowledge: **persistent memory files** for cross-session recall, and **context compaction** for keeping LLM context windows healthy during long tasks.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Persistent Memory
|
|
8
|
+
|
|
9
|
+
Memory files are plain Markdown files that persist across sessions and tasks. They are injected into the agent's system prompt at the start of each conversation.
|
|
10
|
+
|
|
11
|
+
### Memory scopes
|
|
12
|
+
|
|
13
|
+
| Scope | File path | Who writes it | Who reads it |
|
|
14
|
+
|-------|-----------|--------------|--------------|
|
|
15
|
+
| `agent` | `.veil/memory/agents/<name>/MEMORY.md` | The agent itself | Only that agent |
|
|
16
|
+
| `global` | `.veil/memory/MEMORY.md` | Any agent | All agents with memory enabled |
|
|
17
|
+
|
|
18
|
+
### Writing memory
|
|
19
|
+
|
|
20
|
+
Agents use the `memory_write` tool:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
"Remember that the project uses port 5051"
|
|
24
|
+
→ memory_write({ content: "Project uses port 5051.", scope: "agent" })
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Each entry is appended with a date comment:
|
|
28
|
+
```markdown
|
|
29
|
+
<!-- 2025-03-02 -->
|
|
30
|
+
Project uses port 5051.
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Reading memory
|
|
34
|
+
|
|
35
|
+
- **Automatic injection**: If `memory.enabled` is `true` in the agent config (or settings), the memory file is read and included in the system prompt at session start.
|
|
36
|
+
- **Manual read**: Use `memory_read` to explicitly read memory mid-task.
|
|
37
|
+
- **Search**: Use `memory_search` to find relevant entries by keyword.
|
|
38
|
+
|
|
39
|
+
### Configuring memory
|
|
40
|
+
|
|
41
|
+
In `agent.json`:
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"memory": {
|
|
45
|
+
"enabled": true,
|
|
46
|
+
"maxLines": 300
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
In `settings.json` (applies globally to all agents):
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"memory": {
|
|
55
|
+
"enabled": true,
|
|
56
|
+
"maxLines": 500
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`maxLines` limits how much of the memory file is injected. Oldest entries are truncated first. The full file is preserved on disk; only the injection is capped.
|
|
62
|
+
|
|
63
|
+
### Memory best practices
|
|
64
|
+
|
|
65
|
+
- Write focused, factual entries — avoid vague notes like "talked about stuff"
|
|
66
|
+
- Use global memory for facts shared across agents (e.g. project conventions, API endpoints)
|
|
67
|
+
- Use agent memory for agent-specific state (e.g. "already processed file X")
|
|
68
|
+
- Periodically use `memory_search` before writing to avoid duplicates
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Context Compaction
|
|
73
|
+
|
|
74
|
+
LLM context windows are finite. During long tasks with many tool calls, the conversation history can exceed the model's context limit. VeilCLI manages this automatically through **context compaction**.
|
|
75
|
+
|
|
76
|
+
### How it works
|
|
77
|
+
|
|
78
|
+
1. **Observation masking**: Tool result messages older than `observationMaskingTurns` turns are replaced with `[observation masked]`. The tool call itself remains, but the full output is hidden. This is the first and cheapest form of compression.
|
|
79
|
+
|
|
80
|
+
2. **Full compaction**: If the estimated token count exceeds `threshold × model_context_limit`, the runtime:
|
|
81
|
+
- Sends the current conversation to an LLM (using the `compact` model role if configured, otherwise `main`) with a special prompt asking for a structured summary
|
|
82
|
+
- Replaces the full message history with the summary plus the most recent turns
|
|
83
|
+
- Resumes the task with the compressed context
|
|
84
|
+
|
|
85
|
+
### Configuration
|
|
86
|
+
|
|
87
|
+
In `settings.json`:
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"compaction": {
|
|
91
|
+
"threshold": 0.8,
|
|
92
|
+
"observationMaskingTurns": 10
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
| Field | Default | Description |
|
|
98
|
+
|-------|---------|-------------|
|
|
99
|
+
| `threshold` | `0.8` | Fraction of context window that triggers full compaction (0.1–1.0) |
|
|
100
|
+
| `observationMaskingTurns` | `10` | Keep tool results visible for N most recent turns; mask older ones |
|
|
101
|
+
|
|
102
|
+
### Using a dedicated compact model
|
|
103
|
+
|
|
104
|
+
For cost efficiency, use a cheaper/faster model for compaction:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"models": {
|
|
109
|
+
"main": {
|
|
110
|
+
"base_url": "https://openrouter.ai/api/v1",
|
|
111
|
+
"api_key": "sk-or-v1-...",
|
|
112
|
+
"model": "anthropic/claude-3.5-sonnet"
|
|
113
|
+
},
|
|
114
|
+
"compact": {
|
|
115
|
+
"base_url": "https://openrouter.ai/api/v1",
|
|
116
|
+
"api_key": "sk-or-v1-...",
|
|
117
|
+
"model": "google/gemini-flash-1.5-8b"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Memory extraction during compaction
|
|
124
|
+
|
|
125
|
+
Before compacting, VeilCLI extracts important facts from the conversation and writes them to the agent's memory file (if memory is enabled). This means the agent "remembers" key decisions even after the context is compressed.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Memory vs Compaction
|
|
130
|
+
|
|
131
|
+
| Feature | Memory | Compaction |
|
|
132
|
+
|---------|--------|------------|
|
|
133
|
+
| **Scope** | Cross-session | Within a single session/task |
|
|
134
|
+
| **Trigger** | Agent explicitly calls `memory_write` | Automatic when context is full |
|
|
135
|
+
| **Storage** | Markdown files on disk | Replaced messages in the DB |
|
|
136
|
+
| **Purpose** | Long-term recall across runs | Keep long tasks running smoothly |
|
|
137
|
+
| **Controllable by agent** | Yes | No (automatic) |
|
|
138
|
+
|
|
139
|
+
Use **memory** to remember things between runs. Rely on **compaction** to handle long-running tasks gracefully without manual intervention.
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Multi-Agent Orchestration
|
|
2
|
+
|
|
3
|
+
VeilCLI supports multiple agents working together. An **orchestrator** agent delegates work to **worker** agents using spawn, messaging, and subscription tools.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Core Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Mode | Description |
|
|
10
|
+
|------|------|-------------|
|
|
11
|
+
| `agent_spawn` | Sync | Start a **chat session** with an agent — returns `{ sessionId, response }` |
|
|
12
|
+
| `task_spawn` | Sync or async | Spawn a **task** for an agent with an isolated context |
|
|
13
|
+
| `task_create` | Always async | Create a task for any agent (uses task mode) |
|
|
14
|
+
| `task_status` | — | Poll a task's current status and output |
|
|
15
|
+
| `task_subscribe` | — | Durable subscription to task completion |
|
|
16
|
+
| `agent_message` | Sync | Send a message to a **specific session**, wait for reply |
|
|
17
|
+
| `agent_send` | Fire-and-forget | Send a message to a **specific session** without waiting |
|
|
18
|
+
|
|
19
|
+
> **Key distinction:** `task_spawn` isolates the sub-agent in its own context (task mode). `agent_spawn` opens a conversational session you can continue with `agent_message`. Use `task_spawn` for heavy delegated work; use `agent_spawn` + `agent_message` for interactive back-and-forth.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Pattern 1: Simple Delegation (Sync)
|
|
24
|
+
|
|
25
|
+
The simplest pattern — orchestrator spawns a task worker and waits for the result:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Orchestrator
|
|
29
|
+
└► task_spawn(agent="coder", instruction="Analyse src/index.js", wait=true)
|
|
30
|
+
└► Coder runs in task mode, returns summary
|
|
31
|
+
◄─── Result returned inline
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Agent config** (`orchestrator/agent.json`):
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"name": "orchestrator",
|
|
38
|
+
"modes": {
|
|
39
|
+
"task": {
|
|
40
|
+
"enabled": true,
|
|
41
|
+
"permissions": {
|
|
42
|
+
"allow": ["task_spawn", "task_status", "log_write"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**How it works**: `task_spawn` with `wait: true` (default) blocks until the sub-agent finishes. The sub-agent runs in `subagent` mode — isolated from the orchestrator's context.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Pattern 2: Parallel Fan-Out (Async)
|
|
54
|
+
|
|
55
|
+
Spawn multiple task workers simultaneously, then collect results:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
Orchestrator
|
|
59
|
+
├► task_spawn(agent="researcher", instruction="Find X", wait=false) → taskId_1
|
|
60
|
+
├► task_spawn(agent="researcher", instruction="Find Y", wait=false) → taskId_2
|
|
61
|
+
└► task_spawn(agent="researcher", instruction="Find Z", wait=false) → taskId_3
|
|
62
|
+
│
|
|
63
|
+
├─ poll task_status(taskId_1) until finished
|
|
64
|
+
├─ poll task_status(taskId_2) until finished
|
|
65
|
+
└─ poll task_status(taskId_3) until finished
|
|
66
|
+
│
|
|
67
|
+
└► Synthesise all results
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Orchestrator AGENT.md instruction**:
|
|
71
|
+
```markdown
|
|
72
|
+
When given a research topic with multiple subtopics:
|
|
73
|
+
1. Use task_spawn with wait=false to spawn one researcher per subtopic
|
|
74
|
+
2. Save all taskIds
|
|
75
|
+
3. Poll each taskId with task_status until all are finished
|
|
76
|
+
4. Synthesise all outputs into a final report
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Pattern 3: Durable Subscriptions
|
|
82
|
+
|
|
83
|
+
For long-running async tasks, use `task_subscribe` to get notified on completion even if the orchestrator's own session is interrupted:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Orchestrator
|
|
87
|
+
└► task_create(agent="analyst", input="Long analysis job") → taskId
|
|
88
|
+
└► task_subscribe(taskId) ← stored in DB, survives restart
|
|
89
|
+
│
|
|
90
|
+
[... time passes, orchestrator may pause ...]
|
|
91
|
+
│
|
|
92
|
+
◄─── Notification injected when analyst finishes
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`task_subscribe` writes to the `task_subscriptions` SQLite table. When the target task reaches a terminal state (`finished`/`failed`), the runtime injects a notification into the subscriber's message queue — even if the subscriber session had been paused.
|
|
96
|
+
|
|
97
|
+
**When to use**: Prefer `task_subscribe` over polling loops for very long tasks or when you need to save iterations.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Pattern 4: Deep Research Pipeline
|
|
102
|
+
|
|
103
|
+
The full deep research pattern combining fan-out + subscriptions + synthesis:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
Research Orchestrator
|
|
107
|
+
├─ Decompose topic into N sub-questions
|
|
108
|
+
├─ Spawn N researchers async (wait=false) via task_spawn
|
|
109
|
+
├─ Subscribe to all N tasks
|
|
110
|
+
├─ Sleep or continue other work
|
|
111
|
+
├─ Collect results as notifications arrive
|
|
112
|
+
└─ Call synthesis agent with all results
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Orchestrator task flow** (pseudocode for AGENT.md):
|
|
116
|
+
```markdown
|
|
117
|
+
1. Decompose the research question into 3-5 subtopics
|
|
118
|
+
2. For each subtopic:
|
|
119
|
+
a. Use task_spawn(wait=false) to spawn a "researcher" agent
|
|
120
|
+
b. Note the returned taskId
|
|
121
|
+
c. Use task_subscribe(taskId) for durable tracking
|
|
122
|
+
3. Poll task_status for each taskId until all are finished or failed
|
|
123
|
+
4. Collect all outputs
|
|
124
|
+
5. Use task_spawn to call a "writer" agent with all collected research
|
|
125
|
+
6. Return the final synthesised report
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Pattern 5: Hierarchical Delegation
|
|
131
|
+
|
|
132
|
+
Multi-level hierarchies — orchestrators can spawn sub-orchestrators:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
Top-level Orchestrator
|
|
136
|
+
├─► Project Manager (sub-orchestrator)
|
|
137
|
+
│ ├─► Coder agent
|
|
138
|
+
│ └─► Reviewer agent
|
|
139
|
+
└─► Quality Checker
|
|
140
|
+
└─► Tester agent
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The `maxSubAgentDepth` setting (default: 5) limits how deep nesting can go, preventing infinite recursion.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Pattern 6: Session-Based Messaging
|
|
148
|
+
|
|
149
|
+
For back-and-forth exchanges with an agent in the same conversation:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
Orchestrator
|
|
153
|
+
└► agent_spawn(agent="advisor", message="What should I prioritise?") → { sessionId, response }
|
|
154
|
+
└► agent_message(agent="advisor", sessionId=sessionId, message="What about task B?")
|
|
155
|
+
◄─ "Task B should come second because..."
|
|
156
|
+
└► agent_send(agent="advisor", sessionId=sessionId, message="Thanks, moving on.")
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**`agent_spawn`** opens the session and gets the first response. Always required to obtain a `sessionId`.
|
|
160
|
+
|
|
161
|
+
**`agent_message`** is synchronous — it routes the message to the exact session and waits for a reply.
|
|
162
|
+
- If the session is idle → triggers a new turn directly (same as a user message)
|
|
163
|
+
- If the session is mid-processing → injects at the next tool-call checkpoint
|
|
164
|
+
- Automatically falls back if the message is missed at the last iteration
|
|
165
|
+
|
|
166
|
+
**`agent_send`** is fire-and-forget — useful for notifications or status updates without blocking the orchestrator.
|
|
167
|
+
|
|
168
|
+
Both tools require a `sessionId` (from `agent_spawn`) and validate that the `agent` name matches the session. Unknown agents or mismatched sessions return an immediate error instead of silently timing out.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Sub-agent Configuration
|
|
173
|
+
|
|
174
|
+
Target agents need `subagent` mode enabled to be spawnable via `task_spawn`. If `subagent` mode is not configured, the runtime falls back to `task` mode.
|
|
175
|
+
|
|
176
|
+
For `agent_spawn` (chat session), agents need `chat` mode enabled.
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"name": "researcher",
|
|
181
|
+
"modes": {
|
|
182
|
+
"chat": {
|
|
183
|
+
"enabled": true,
|
|
184
|
+
"maxIterations": 20
|
|
185
|
+
},
|
|
186
|
+
"subagent": {
|
|
187
|
+
"enabled": true,
|
|
188
|
+
"maxIterations": 15,
|
|
189
|
+
"maxDurationSeconds": 90,
|
|
190
|
+
"permissions": {
|
|
191
|
+
"allow": ["web_search", "web_fetch", "read_file", "memory_write"]
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Agent Restrictions
|
|
201
|
+
|
|
202
|
+
Orchestrators can whitelist which sub-agents they're allowed to spawn:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"modes": {
|
|
207
|
+
"task": {
|
|
208
|
+
"allowedAgents": ["researcher", "writer", "coder"],
|
|
209
|
+
"disallowedAgents": ["admin"]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Depth Limit
|
|
218
|
+
|
|
219
|
+
Configure the maximum nesting depth in `settings.json`:
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"maxSubAgentDepth": 3
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
When an agent tries to spawn beyond this depth, the spawn is rejected with an error.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Practical Example: Code Review Pipeline
|
|
232
|
+
|
|
233
|
+
**Agents**: `coordinator`, `coder`, `reviewer`
|
|
234
|
+
|
|
235
|
+
**Coordinator AGENT.md**:
|
|
236
|
+
```markdown
|
|
237
|
+
You are a code review coordinator.
|
|
238
|
+
|
|
239
|
+
When given a directory to review:
|
|
240
|
+
1. Use list_dir to find all .js files
|
|
241
|
+
2. For each file, use task_spawn with wait=false to spawn a "coder" agent
|
|
242
|
+
3. Subscribe to all tasks with task_subscribe
|
|
243
|
+
4. Once all analyses are done, use task_spawn to call a "reviewer" agent with the combined analysis
|
|
244
|
+
5. Return the final review report
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Coder `agent.json`**:
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"name": "coder",
|
|
251
|
+
"modes": {
|
|
252
|
+
"subagent": {
|
|
253
|
+
"enabled": true,
|
|
254
|
+
"permissions": { "allow": ["read_file", "grep", "glob"] }
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Reviewer `agent.json`**:
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"name": "reviewer",
|
|
264
|
+
"modes": {
|
|
265
|
+
"subagent": {
|
|
266
|
+
"enabled": true,
|
|
267
|
+
"permissions": { "allow": ["read_file", "memory_write"] }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|