# Claude-CLI Engine — Feature Checklist

> Review each feature. Mark: ACCEPT / REJECT / COMMENT

---

## A. Provider & Session Basics

### A1. Provider type `"claude-cli"`
Add `"claude-cli"` as a new engine type in `providers` config.
```json
{ "type": "claude-cli", "path": "claude", "permission_mode": "acceptEdits" }
```
No `base_url`, no `api_key`. Uses user's own `claude` CLI auth.

- [ ] ACCEPT / REJECT / COMMENT:

### A2. No cross-engine fallback
A session that starts as `claude-cli` can ONLY be resumed as `claude-cli`. No fallback to `openai` mid-session. If the CLI is unavailable, the session fails — the user must fix their CLI or start a new session on a different provider.

- [ ] ACCEPT / REJECT / COMMENT:

### A3. Session ID mapping
Store Claude CLI's `session_id` in a new `external_session_id` column on the `sessions` table. Used for resume, fork, and message history retrieval.

- [ ] ACCEPT / REJECT / COMMENT:

### A4. Session resume
Resume a claude-cli session by passing `resume: external_session_id` to the SDK. VeilCli tracks the mapping.

- [ ] ACCEPT / REJECT / COMMENT:

### A5. Session fork
Fork from a specific message using `resume` + `resumeSessionAt` + `forkSession: true`. Creates a new VeilCli session linked to the new Claude session.

- [ ] ACCEPT / REJECT / COMMENT:

### A6. Session reset
"Reset" = close current session, create a new `query()` with same agent config. Old messages stay in DB for UI history.

- [ ] ACCEPT / REJECT / COMMENT:

---

## B. System Prompt

### B1. Append VeilCli's system prompt to Claude's
Use `systemPrompt: { type: "preset", preset: "claude_code", append: assembledPrompt }`. Claude keeps its full prompt (tools, safety, coding guidelines). VeilCli's 7-layer prompt is appended at the end.

- [ ] ACCEPT / REJECT / COMMENT:

### B2. Use `assembleSystemPrompt()` for the append content
Reuse `core/prompt.js` to build the prompt — agent instructions, memory, environment, task heuristics — then pass the output as the `append` value. No duplication of prompt logic.

- [ ] ACCEPT / REJECT / COMMENT:

### B3. Disable Claude's CLAUDE.md loading
Set `settingSources: []` so Claude doesn't also load its own CLAUDE.md files (which could conflict with VeilCli's AGENT.md). VeilCli already injects its own instructions via the append.

- [ ] ACCEPT / REJECT / COMMENT:

---

## C. Tools

### C1. Use Claude's built-in tools for file/shell/search
Let Claude handle: Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch. These are superior to VeilCli's equivalents and are already part of the Claude Code preset.

- [ ] ACCEPT / REJECT / COMMENT:

### C2. Expose VeilCli orchestration tools via in-process MCP
Use `tool()` + `createSdkMcpServer()` to create an in-process MCP server (no subprocess, runs in VeilCli's Node.js process) exposing:
- `agent_spawn`, `agent_send`, `agent_message`
- `task_create`, `task_spawn`, `task_status`, `task_respond`, `task_subscribe`

These have direct access to DB, event bus, running sessions — everything they need.

- [ ] ACCEPT / REJECT / COMMENT:

### C3. Expose VeilCli memory/todo/log tools via in-process MCP
Same MCP server, additional tools:
- `memory_write`, `memory_read`, `memory_search`
- `todo_write`, `todo_read`
- `log_write`

- [ ] ACCEPT / REJECT / COMMENT:

### C4. Drop `tool_search` and `tool_activate`
MCP servers list all tools upfront. The lazy-loading pattern is not needed.

- [ ] ACCEPT / REJECT / COMMENT:

### C5. Drop `sleep` tool
Not critical for claude-cli sessions. Claude manages its own pacing.

- [ ] ACCEPT / REJECT / COMMENT:

### C6. Bash filtering via PreToolUse hook
Instead of replacing Claude's Bash with VeilCli's, use a `PreToolUse` hook on `"Bash"` to apply VeilCli's custom command filters/restrictions. Claude's Bash runs, but the hook can block or modify dangerous commands.

- [ ] ACCEPT / REJECT / COMMENT:

### C7. Map `allowedTools` / `disallowedTools` from agent config
Translate agent's `modeConfig.tools` and `modeConfig.disallowedTools` to SDK format:
- VeilCli `read_file` → SDK `Read`
- VeilCli `bash` → SDK `Bash`
- VeilCli `memory_write` → SDK `mcp__veil_tools__memory_write`
- etc.

If agent says `disallowedTools: ["bash"]`, pass `disallowedTools: ["Bash"]` to SDK.

- [ ] ACCEPT / REJECT / COMMENT:

---

## D. Event Mapping (Tool Calls → VeilCli Events)

### D1. Map Claude tool names to VeilCli tool names in emitted events
When Claude uses `Read`, emit a VeilCli event with `toolName: "read_file"`. When Claude uses `Bash`, emit `toolName: "bash"`. Mapping table:

| Claude tool | VeilCli event name |
|-------------|---------------------|
| `Read` | `read_file` |
| `Write` | `write_file` |
| `Edit` | `edit_file` |
| `Bash` | `bash` |
| `Grep` | `grep` |
| `Glob` | `glob` |
| `WebSearch` | `web_search` |
| `WebFetch` | `web_fetch` |
| `Agent` | `agent_spawn` |
| `mcp__veil_tools__*` | strip prefix, use original name |

This ensures other agents/scripts that listen for specific tool calls (e.g., "when bash is called with X") still work regardless of engine.

- [ ] ACCEPT / REJECT / COMMENT:

### D2. Reformat tool call events to match VeilCli's format
Claude SDK emits `tool_progress` and `assistant` messages with tool use blocks. Transform these into VeilCli's event format so the UI/WebSocket consumers don't need to know which engine is running.

Event shape stays the same:
```js
{ type: 'tool.start', toolName: 'read_file', toolInput: { file_path: '...' }, ... }
{ type: 'tool.result', toolName: 'read_file', result: '...', ... }
```

- [ ] ACCEPT / REJECT / COMMENT:

### D3. Map tool input/output field names
Claude's tools use different parameter names than VeilCli's. Map them in emitted events:
- Claude `Read` input: `{ file_path }` → VeilCli `read_file` input: `{ file_path }` (same)
- Claude `Bash` input: `{ command }` → VeilCli `bash` input: `{ command }` (same)
- Claude `Edit` input: `{ file_path, old_string, new_string }` → map to VeilCli format

Only map for emitted events — don't interfere with actual tool execution.

- [ ] ACCEPT / REJECT / COMMENT:

### D4. Emit streaming text events in same format as openai engine
Map SDK `stream_event (text_delta)` → VeilCli's existing streaming event format. The UI/WebSocket consumer sees the same events regardless of engine.

- [ ] ACCEPT / REJECT / COMMENT:

---

## E. Message Handling

### E1. Prompt queue for multi-turn conversations
Use `AsyncIterable<SDKUserMessage>` backed by a `PromptQueue` class. Push user messages from the VeilCli API into the queue. Claude picks them up.

- [ ] ACCEPT / REJECT / COMMENT:

### E2. Silent message injection (`shouldQuery: false`)
For inter-agent messages and context injection (equivalent of `drainNonFollowup`), push messages with `shouldQuery: false`. They get appended to Claude's context without triggering an assistant turn.

- [ ] ACCEPT / REJECT / COMMENT:

### E3. `streamInput()` for mid-session message attachment
Use `runtime.streamInput(newStream)` to attach additional message sources mid-session. Useful for agent_message/queue injection.

- [ ] ACCEPT / REJECT / COMMENT:

### E4. Interrupt + inject for urgent mid-turn messages
For urgent messages that can't wait for the current turn to finish: `runtime.interrupt()` → push message → Claude picks up immediately.

- [ ] ACCEPT / REJECT / COMMENT:

---

## F. Hooks & Permissions

### F1. Map VeilCli's PreToolUse/PostToolUse hooks to SDK hooks
Port the existing hook logic from `core/loop.js` into SDK `hooks` option format. Hooks fire the same way — before/after each tool call.

- [ ] ACCEPT / REJECT / COMMENT:

### F2. `canUseTool` callback for permission checks
Implement VeilCli's `checkPermission()` logic inside the `canUseTool` callback. Respects agent-level allow/deny lists and settings-level permission patterns.

- [ ] ACCEPT / REJECT / COMMENT:

### F3. Handle `AskUserQuestion` via `canUseTool`
When `toolName === "AskUserQuestion"`, forward to the UI via WebSocket/SSE and return the user's answer. This is how Claude asks clarifying questions.

- [ ] ACCEPT / REJECT / COMMENT:

---

## G. Persistence & Tracking

### G1. Persist user/assistant messages to VeilCli's SQLite
Write user messages (what we send) and assistant responses (from `assistant` events) to the `messages` table. The UI reads from here.

- [ ] ACCEPT / REJECT / COMMENT:

### G2. Persist tool calls to VeilCli's SQLite
Write tool call events (from `assistant` messages with tool_use blocks) to the `messages` table as `role: 'assistant'` with `tool_calls` JSON. Write tool results as `role: 'tool'` messages.

- [ ] ACCEPT / REJECT / COMMENT:

### G3. Track token usage from `result` events
Extract `usage.input_tokens`, `usage.output_tokens`, `usage.cache_read_input_tokens` from `result` messages. Update session totals in DB.

- [ ] ACCEPT / REJECT / COMMENT:

### G4. Track cost from `result.total_cost_usd`
Use the SDK's cost field directly — no need to calculate from model pricing.

- [ ] ACCEPT / REJECT / COMMENT:

---

## H. Session Lifecycle

### H1. Bypass `core/loop.js` entirely for claude-cli sessions
The engine creates its own event loop: `query()` → iterate `AsyncGenerator` → emit events. No `runLoop()`.

- [ ] ACCEPT / REJECT / COMMENT:

### H2. Branch in `core/router.js` based on provider type
After resolving the provider chain, check `type`. If `claude-cli`, call `runClaudeCliSession()`. If `openai`, call existing `runLoop()`.

- [ ] ACCEPT / REJECT / COMMENT:

### H3. Support all 4 modes: chat, task, daemon, subagent
- **chat**: Prompt queue, multi-turn
- **task**: Single prompt, run to completion, `maxTurns` limit
- **daemon**: Ephemeral session (`persistSession: false`), single prompt
- **subagent**: SDK's own sub-agent system OR VeilCli-spawned session

- [ ] ACCEPT / REJECT / COMMENT:

### H4. Compaction handled by Claude (no VeilCli compaction)
Claude CLI manages its own context window. VeilCli's compaction system is bypassed. `PreCompact` hook can observe but not customize.

- [ ] ACCEPT / REJECT / COMMENT:

### H5. `maxTurns` maps to `maxIterations`
Agent's `modeConfig.maxIterations` → SDK's `maxTurns`.

- [ ] ACCEPT / REJECT / COMMENT:

### H6. `maxBudgetUsd` for cost limits
Agent's cost limit (if any) → SDK's `maxBudgetUsd`.

- [ ] ACCEPT / REJECT / COMMENT:

---

## I. Mid-Session Mutations

### I1. Model switching via `runtime.setModel()`
Allow changing the Claude model mid-session (e.g., switch from Opus to Sonnet for cheaper execution).

- [ ] ACCEPT / REJECT / COMMENT:

### I2. Permission mode switching via `runtime.setPermissionMode()`
Allow changing permission mode mid-session.

- [ ] ACCEPT / REJECT / COMMENT:

### I3. MCP server management via `runtime.setMcpServers()` / `toggleMcpServer()`
Allow adding/removing/toggling MCP servers mid-session.

- [ ] ACCEPT / REJECT / COMMENT:

---

## J. File Structure

### J1. All claude-cli code in `engines/` directory
```
engines/
  claude-cli.js          — runClaudeCliSession() async generator
  claude-cli-tools.js    — in-process MCP server wrapping VeilCli tools
  prompt-queue.js        — PromptQueue class
  sdk-event-mapper.js    — SDKMessage → VeilCli event translation + tool name mapping
```

- [ ] ACCEPT / REJECT / COMMENT:

### J2. Optional dependency on `@anthropic-ai/claude-agent-sdk`
Install as `optionalDependencies`. Runtime check with clear error message if missing.

- [ ] ACCEPT / REJECT / COMMENT:

### J3. Schema update for `providerConfig.type` enum
Add `"claude-cli"` to the allowed types in `schemas/settings.json`. Add optional fields: `path`, `permission_mode`, `env`.

- [ ] ACCEPT / REJECT / COMMENT:

---

## K. What We Explicitly Skip

### K1. No cross-engine fallback (confirmed in A2)
### K2. No custom compaction model/prompt
### K3. No stall recovery injection
### K4. No anti-drift reminders
### K5. No `tool_search`/`tool_activate` (confirmed in C4)
### K6. No dynamic tool activation mid-session
### K7. VeilCli's `list_dir` tool (subsumed by Glob/Bash)

- [ ] ACCEPT / REJECT / COMMENT on the skip list:
