docs: rewrite Rust codebase spec#67
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR refreshes the Rust architecture/spec documentation to match the actual src-rust/ Coven Code workspace, replacing the previous upstream/placeholder Rust rewrite spec and updating the spec index accordingly.
Changes:
- Rewrites
spec/13_rust_codebase.mdto document the realsrc-rustworkspace layout, crates, CLI surface, tools runtime, persistence, and related subsystems. - Updates
spec/INDEX.mdto mark the Rust spec as current (not pending) and to align the Rust crate/source counts and section pointers with the rewritten spec.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| spec/INDEX.md | Updates the spec index entry and quick-lookup pointers for the rewritten Rust workspace spec. |
| spec/13_rust_codebase.md | Replaces stale upstream Rust-rewrite content with a current src-rust workspace reference (crates, CLI, tools/runtime, persistence). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+237
to
+238
| Bash | ||
| Read |
Comment on lines
+102
to
+110
| ## Workspace Dependencies | ||
|
|
||
| ### Tool: `EnterPlanModeTool` (`enter_plan_mode.rs`) | ||
| The workspace root centralizes common dependencies: | ||
|
|
||
| **Name:** `"EnterPlanMode"` | ||
| **Permission level:** `None` | ||
|
|
||
| Returns `ToolResult::success` with metadata `{ type: "enter_plan_mode" }`. Signals the session to switch to Plan permission mode. | ||
|
|
||
| ### Tool: `ExitPlanModeTool` (`exit_plan_mode.rs`) | ||
|
|
||
| **Name:** `"ExitPlanMode"` | ||
| **Permission level:** `None` | ||
|
|
||
| Input schema: `{ summary: optional string }` | ||
|
|
||
| Returns success with metadata `{ type: "exit_plan_mode", summary }`. Signals return from Plan mode. | ||
|
|
||
| ### Tool: `PowerShellTool` (`powershell.rs`) | ||
|
|
||
| **Name:** `"PowerShell"` | ||
| **Permission level:** `Execute` | ||
|
|
||
| Input schema: `{ command: string, timeout: optional u64 }` | ||
|
|
||
| Same execution pattern as `BashTool`. On Windows uses `powershell -NoProfile -NonInteractive -Command`. On other platforms uses `pwsh`. | ||
|
|
||
| ### Tool: `EnterWorktreeTool`, `ExitWorktreeTool` (`worktree.rs`) | ||
|
|
||
| Global: `WORKTREE_SESSION: Lazy<Arc<RwLock<Option<WorktreeSession>>>>` | ||
|
|
||
| **`WorktreeSession`:** `{ branch: String, path: PathBuf, original_dir: PathBuf }` | ||
|
|
||
| **`EnterWorktreeTool`** (`"EnterWorktree"`): | ||
| - Input: `{ branch: string, path: optional string }` | ||
| - Runs `git worktree add -b <branch> <path>` | ||
| - Saves session to `WORKTREE_SESSION` | ||
|
|
||
| **`ExitWorktreeTool`** (`"ExitWorktree"`): | ||
| - Input: `{ action: "keep" | "remove", discard_changes: optional bool }` | ||
| - `keep`: locks worktree, clears session | ||
| - `remove`: checks for uncommitted changes (requires `discard_changes=true` to override), runs `git worktree remove --force <path>`, then `git branch -D <branch>` | ||
|
|
||
| ### Tool: `SendMessageTool` (`send_message.rs`) | ||
|
|
||
| **Name:** `"SendMessage"` | ||
| **Permission level:** `None` | ||
|
|
||
| Global: `INBOX: Lazy<DashMap<String, Vec<AgentMessage>>>` | ||
|
|
||
| Input schema: `{ to: string, message: string, metadata: optional Value }` | ||
|
|
||
| - Delivers message to named recipient in `INBOX` | ||
| - `to = "*"` broadcasts to all existing keys | ||
| - `drain_inbox(recipient: &str) -> Vec<AgentMessage>` — removes and returns all messages | ||
| - `peek_inbox(recipient: &str) -> Vec<AgentMessage>` — returns without removing | ||
|
|
||
| ### Tool: `SkillTool` (`skill_tool.rs`) | ||
|
|
||
| **Name:** `"Skill"` | ||
| **Permission level:** `None` | ||
|
|
||
| Input schema: `{ skill: string, arguments: optional string }` | ||
|
|
||
| **Algorithm:** | ||
| 1. `skill = "list"` → enumerates `.claude/commands/*.md` and `~/.claude/commands/*.md`, extracts description from YAML frontmatter or first heading | ||
| 2. Otherwise: resolves `<skill>.md` file from project then user commands directory | ||
| 3. Strips YAML frontmatter (`---` block) | ||
| 4. Substitutes `$ARGUMENTS` with provided arguments string | ||
| 5. Returns file content as `ToolResult::success` | ||
|
|
||
| ### Tool: `SleepTool` (`sleep.rs`) | ||
|
|
||
| **Name:** `"Sleep"` | ||
| **Permission level:** `None` | ||
|
|
||
| Input schema: `{ duration: f64 (seconds) }` | ||
|
|
||
| Calls `tokio::time::sleep(Duration::from_secs_f64(duration))`. Maximum 300 seconds. | ||
|
|
||
| ### Tool: `ToolSearchTool` (`tool_search.rs`) | ||
|
|
||
| **Name:** `"ToolSearch"` | ||
| **Permission level:** `None` | ||
|
|
||
| Input schema: `{ query: string, max_results: optional u32 (default 5) }` | ||
|
|
||
| Static `TOOL_CATALOG: &[(&str, &str, &[&str])]` — 32 entries of `(name, description, keywords)`. | ||
|
|
||
| **Scoring algorithm:** | ||
| - `select:Name` syntax → score 100 for exact name match | ||
| - Otherwise for each catalog entry: | ||
| - exact name match: +20 | ||
| - name contains query: +10 | ||
| - description contains query: +5 | ||
| - keyword exact match: +8 | ||
| - keyword contains query: +3 | ||
| - Returns top `max_results` entries with non-zero score | ||
|
|
||
| ### Tool: `BriefTool` (`brief.rs`) | ||
|
|
||
| **Name:** `"Brief"` | ||
| **Permission level:** `None` | ||
|
|
||
| Input schema: `{ message: string, status: optional string, attachments: optional Array<string> (file paths) }` | ||
|
|
||
| Resolves attachment metadata (file size, is_image flag from extension). Returns `ToolResult::success("")` with metadata `{ message, status, sentAt, attachments: [{ path, size, isImage }] }`. | ||
|
|
||
| ### Tool: `ConfigTool` (`config_tool.rs`) | ||
|
|
||
| **Name:** `"Config"` | ||
| **Permission level:** `None` | ||
|
|
||
| Input schema: `{ action: "get" | "set", key: string, value: optional Value }` | ||
|
|
||
| Reads/writes `~/.claude/settings.json`. Supported keys: `model`, `max_tokens`, `verbose`, `permission_mode`, `auto_compact`. Returns current value on `get`, writes and confirms on `set`. | ||
|
|
||
| ### Tool: `ListMcpResourcesTool`, `ReadMcpResourceTool` (`mcp_resources.rs`) | ||
|
|
||
| | Tool | Name | Description | | ||
| |---|---|---| | ||
| | `ListMcpResourcesTool` | `"ListMcpResources"` | Calls `ctx.mcp_manager.list_all_resources()`, returns JSON | | ||
| | `ReadMcpResourceTool` | `"ReadMcpResource"` | Input: `{ uri: string }`. Calls `ctx.mcp_manager.read_resource(uri)` | | ||
|
|
||
| Both return error if `ctx.mcp_manager` is `None`. | ||
|
|
||
| ### Relationship to TypeScript | ||
|
|
||
| `cc-tools` corresponds to the TypeScript tool implementations in `src/` (e.g., bash is in the tool system, file operations in ReadTool/EditTool/WriteTool, etc.). Tool names are identical to the TypeScript constants. The `ToolContext` mirrors the TypeScript `ToolUseContext`. | ||
|
|
||
| --- | ||
|
|
||
| ## Crate: `cc-query` | ||
|
|
||
| **Path:** `crates/query/src/` | ||
|
|
||
| The core agentic query loop crate. Contains 4 source files. | ||
|
|
||
| ### Module: `lib.rs` — Main Query Loop | ||
|
|
||
| **`QueryOutcome` enum:** | ||
| - `EndTurn { message: Message, usage: UsageInfo }` — model issued `end_turn` | ||
| - `MaxTokens { partial_message: Message, usage: UsageInfo }` — hit token limit | ||
| - `Cancelled` — cancellation token fired | ||
| - `Error(ClaudeError)` — unrecoverable error | ||
|
|
||
| **`QueryConfig` struct:** | ||
| - `model: String` | ||
| - `max_tokens: u32` | ||
| - `max_turns: u32` (default: `MAX_TURNS_DEFAULT = 10`) | ||
| - `system_prompt: Option<String>` | ||
| - `append_system_prompt: Option<String>` | ||
| - `thinking_budget: Option<u32>` | ||
| - `temperature: Option<f32>` | ||
| - `QueryConfig::default()` uses `DEFAULT_MODEL` + `DEFAULT_MAX_TOKENS` | ||
| - `QueryConfig::from_config(cfg: &Config)` — reads model + max_tokens from Config | ||
|
|
||
| **`QueryEvent` enum:** | ||
| - `Stream(StreamEvent)` — raw API stream event | ||
| - `ToolStart { tool_name, tool_id }` — tool beginning execution | ||
| - `ToolEnd { tool_name, tool_id, result, is_error }` — tool completed | ||
| - `TurnComplete { turn: u32, stop_reason: String }` — model turn finished | ||
| - `Status(String)` — informational message | ||
| - `Error(String)` — error notification | ||
|
|
||
| **`run_query_loop(client, messages, tools, tool_ctx, config, cost_tracker, event_tx, cancel_token) -> QueryOutcome`** (async): | ||
|
|
||
| Main agentic loop: | ||
| 1. Increments turn counter; returns `EndTurn` if `> max_turns` | ||
| 2. Checks `cancel_token.is_cancelled()` → `Cancelled` | ||
| 3. Converts `messages` → `Vec<ApiMessage>`, tools → `Vec<ApiToolDefinition>` | ||
| 4. Calls `build_system_prompt(config)` to construct `SystemPrompt` | ||
| 5. Builds `CreateMessageRequest` (with thinking config if `budget` provided) | ||
| 6. Creates `ChannelStreamHandler` or `NullStreamHandler` | ||
| 7. Calls `client.create_message_stream()`, receives `mpsc::Receiver<StreamEvent>` | ||
| 8. Inner loop: `tokio::select!` on cancellation or stream events; feeds `StreamAccumulator` | ||
| 9. On `MessageStop` or channel close: calls `accumulator.finish()` | ||
| 10. Tracks costs via `cost_tracker.add_usage()` | ||
| 11. Appends assistant message to `messages` | ||
| 12. Calls `auto_compact_if_needed()` if stop reason is `end_turn` or `tool_use` | ||
| 13. Dispatches on `stop_reason`: | ||
| - `"end_turn"` / `"stop_sequence"` / unknown → fires `Stop` hook → returns `EndTurn` | ||
| - `"max_tokens"` → returns `MaxTokens` | ||
| - `"tool_use"` → executes all tool_use blocks (see below), appends results, `continue` | ||
|
|
||
| **Tool execution in `tool_use` turn:** | ||
| 1. For each `ContentBlock::ToolUse { id, name, input }`: | ||
| 2. Emits `QueryEvent::ToolStart` | ||
| 3. Fires `PreToolUse` hooks via `cc_core::hooks::run_hooks()`; if `HookOutcome::Blocked` → `ToolResult::error("Blocked by hook: ...")` | ||
| 4. Otherwise calls `execute_tool(name, input, tools, ctx)` | ||
| 5. Fires `PostToolUse` hooks | ||
| 6. Emits `QueryEvent::ToolEnd` | ||
| 7. Pushes `ContentBlock::ToolResult` to result_blocks | ||
| 8. Appends `Message::user_blocks(result_blocks)` to conversation | ||
|
|
||
| **`execute_tool(name, input, tools, ctx) -> ToolResult`** (async): | ||
| - Finds tool by name in slice, calls `tool.execute(input, ctx)` | ||
| - Unknown tool → `ToolResult::error("Unknown tool: {name}")` | ||
|
|
||
| **`build_system_prompt(config) -> SystemPrompt`:** | ||
| - Joins `system_prompt` and `append_system_prompt` with `\n\n` | ||
| - Empty → default `"You are Claude, an AI assistant by Anthropic."` | ||
|
|
||
| **`run_single_query(client, messages, config) -> Result<Message>`** (async): | ||
| - Single API call, no tool loop, `NullStreamHandler` | ||
| - Returns complete assistant message | ||
|
|
||
| **`ChannelStreamHandler`** — implements `StreamHandler`: | ||
| - `on_event(&self, event)` forwards to `mpsc::UnboundedSender<QueryEvent>` | ||
|
|
||
| ### Module: `compact.rs` — Auto-Compact | ||
|
|
||
| **Constants:** | ||
| - `AUTOCOMPACT_BUFFER_TOKENS = 13_000` | ||
| - `WARNING_THRESHOLD_BUFFER_TOKENS = 20_000` | ||
| - `AUTOCOMPACT_TRIGGER_FRACTION = 0.90` | ||
| - `KEEP_RECENT_MESSAGES = 10` | ||
| - `MAX_CONSECUTIVE_FAILURES = 3` | ||
|
|
||
| **`AutoCompactState` struct:** | ||
| - `compaction_count: u32` | ||
| - `consecutive_failures: u32` | ||
| - `disabled: bool` — circuit breaker; set after 3 consecutive failures | ||
|
|
||
| **`TokenWarningState` enum:** `Ok`, `Warning`, `Critical` | ||
|
|
||
| **`context_window_for_model(model: &str) -> u32`:** | ||
| - `200_000` for models matching "opus-4", "sonnet-4", "haiku-4", "claude-3-5" | ||
| - `100_000` otherwise | ||
|
|
||
| **`calculate_token_warning_state(input_tokens, model) -> TokenWarningState`:** | ||
| - Uses `WARNING_THRESHOLD_BUFFER_TOKENS` to determine Warning vs Critical | ||
|
|
||
| **`should_auto_compact(state, input_tokens, model) -> bool`:** | ||
| - Returns false if `state.disabled` | ||
| - Returns true if `input_tokens / context_window > AUTOCOMPACT_TRIGGER_FRACTION` | ||
|
|
||
| **`summarise_head(client, messages_to_summarize, model) -> Result<String>`** (async): | ||
| - Calls API with prompt asking to summarize the provided conversation | ||
| - Returns summary wrapped in `<compact-summary>...</compact-summary>` XML tags | ||
|
|
||
| **`compact_conversation(client, messages, model) -> Result<Vec<Message>>`** (async): | ||
| - Splits conversation: head = `messages[0..total-KEEP_RECENT_MESSAGES]`, tail = last 10 messages | ||
| - Calls `summarise_head()` on head | ||
| - Returns `[Message::user(summary)] + tail` | ||
|
|
||
| **`auto_compact_if_needed(client, messages, input_tokens, model, state) -> Option<Vec<Message>>`** (async): | ||
| - Checks `should_auto_compact()`, calls `compact_conversation()` | ||
| - On success: resets `consecutive_failures`, increments `compaction_count` | ||
| - On failure: increments `consecutive_failures`; disables if `>= MAX_CONSECUTIVE_FAILURES` | ||
| - Returns `Some(new_messages)` on success, `None` if not needed or failed | ||
|
|
||
| ### Module: `agent_tool.rs` — Sub-Agent Tool | ||
|
|
||
| **`AgentTool`** implements `Tool`: | ||
| - **Name:** `"Task"` (constant `TOOL_NAME_AGENT`) | ||
| - **Permission level:** `Execute` | ||
|
|
||
| Input schema: `{ description: string, prompt: string, tools: optional Array<string>, system_prompt: optional string, max_turns: optional u32, model: optional string }` | ||
|
|
||
| **Algorithm:** | ||
| 1. Creates dedicated `AnthropicClient` from `ANTHROPIC_API_KEY` env var | ||
| 2. Filters tool list: if `tools` field provided, uses that subset; always excludes `TOOL_NAME_AGENT` (prevents recursion) | ||
| 3. Calls `run_query_loop()` with: | ||
| - `event_tx = None` (no TUI forwarding for sub-agent) | ||
| - New `ToolContext` with same working_dir, permission_mode, etc. | ||
| 4. Returns final assistant message text as `ToolResult::success()` | ||
|
|
||
| ### Module: `cron_scheduler.rs` — Background Cron | ||
|
|
||
| **`start_cron_scheduler(tools, tool_ctx, cancel_token) -> JoinHandle<()>`:** | ||
| - Spawns `tokio::spawn(run_scheduler_loop(...))` | ||
|
|
||
| **`run_scheduler_loop(tools, tool_ctx, cancel_token)`** (async loop): | ||
| 1. Computes seconds until next minute boundary: `sleep(60 - now.second() + 1)` | ||
| 2. Calls `cc_tools::cron::pop_due_tasks()` to get matching tasks | ||
| 3. For each due task: spawns `run_query_loop()` with: | ||
| - Single user message from `task.prompt` | ||
| - `event_tx = None` (background, no UI) | ||
| - `cancel_token` clone | ||
| 4. Loop continues until cancellation | ||
|
|
||
| ### Relationship to TypeScript | ||
|
|
||
| `cc-query` corresponds to `src/query.ts`, `src/query/`, `src/services/compact/autoCompact.ts`, `src/coordinator/`, and parts of `src/services/autoDream/`. The `AgentTool` corresponds to the TypeScript `Task` tool. | ||
| | Area | Dependencies | | ||
| |---|---| | ||
| | Async/runtime | `tokio`, `tokio-stream`, `futures`, `async-trait`, `async-stream` | | ||
| | HTTP/streaming | `reqwest`, `tokio-tungstenite`, `tower-http`, `sse-stream` | | ||
| | Serialization/config | `serde`, `serde_json`, `toml`, `schemars` | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
spec/13_rust_codebase.mdupstream/placeholder Rust spec with the current Coven Codesrc-rustworkspace mapcoven-codebinary,claurst-*crate list, provider/tool/runtime surfaces, and~/.coven-codepersistencespec/INDEX.mdso Security: agent tool-filter fails open for unknown access strings #13 is no longer marked pending and Rust crate/source counts match the current workspaceVerification
claude-code-rust,cc-*,~/.claude, orCLAUDE.mdreferences remain inspec/13_rust_codebase.md/spec/INDEX.mdsrc-rust,claurst-*,coven-code,~/.coven-code,AGENTS.md,0.0.24, and 12-crate references are presentFixes #58