siGit Code parity roadmap: the three biggest wins
Status: draft v1 (2026-07-04). Owner: product/eng. Audience: internal (private repo).
Scope: siGit Code, the Rust CLI / ACP agent (see product-overview.md).
This document records where siGit Code stands against Claude Code and specifies the
three features that close the most important gaps. Each spec below is written to be
implementable on its own branch off development in the sigit repo; the three are
independent of each other.
1. Where we stand
siGit Code already covers the core agent loop: file tools, shell, glob and search, web fetch, todos, memory, Agent Skills, an MCP client (Streamable HTTP), project instruction files (AGENTS.md / CLAUDE.md), a tool permission system with plan mode (PR #20), commit co-author attribution (PR #21), ACP editor integration, a Unix TUI, and local / cloud / BYO inference. Local inference is our differentiator; Claude Code has no equivalent.
What Claude Code has that we lack is not one feature. It is the machinery that lets an agent work for a long time:
| Capability | Claude Code | siGit Code today |
|---|---|---|
| Durable sessions (resume after restart) | yes | no, history lives in RAM |
| Context compaction | auto + manual | no, hard cap of 10 tool rounds |
| Subagents (delegate work to a fresh context) | yes | no |
| Background / long-running commands | yes | no, commands are killed at 120s |
| Web search | yes | fetch only |
| Custom slash commands | yes | built-ins only |
Permission rule patterns (Bash(git:*)) |
yes | mode + per-tool only |
| stdio MCP transport | yes | HTTP only |
| Headless mode for CI | yes | no |
The three specs below are the biggest wins, ordered by implementation cost from smallest to largest. Everything else on the list is a fast follow.
2. Spec: background and long-running commands
Branch: feature/background-commands · Touches: src/tools.rs (self-contained)
Problem
run_command kills every child at the 120 second COMMAND_TIMEOUT. A cold
cargo build, a full test suite, a dev server, or a watch task cannot run at all.
This is the most visceral failure in daily use: the agent cannot even build the
projects it is asked to work on.
Design
run_commandgains an optional boolean argumentrun_in_background. When true, the child is spawned, registered in a process-global task table (Mutex<HashMap<u64, BackgroundTask>>, monotonically increasing ids, same process-global pattern asmcp.rs), and the tool returns immediately with the task id. A reader thread drains stdout/stderr into a capped buffer (COMMAND_OUTPUT_LIMITper task, oldest output dropped first).- New tool
command_output{task_id}: returns output accumulated since the last poll plus the task status (running / exited with code). - New tool
kill_command{task_id}: terminates the child (SIGKILL on Unix,TerminateProcesssemantics viaChild::killeverywhere) and reports the tail of its output. - Foreground behavior is unchanged, including the 120s timeout. Background tasks have no timeout; all children die with the sigit process (document this).
- Tool descriptions must steer the model: run servers, builds, and anything that may exceed two minutes in the background, then poll.
- When the permission system (PR #20) merges:
command_outputis read-only,kill_commandis mutating but only affects children the agent itself started.
Acceptance
- A background task outliving the 120s foreground timeout keeps running and its
output is retrievable via
command_output. kill_commandstops a running task; polling a finished task reports its exit code.- Unit tests cover: spawn + poll + finish, kill, unknown task id, output capping.
3. Spec: subagent tool
Branch: feature/subagent-tool · Touches: src/tools.rs, src/backend.rs, both surfaces' startup
Problem
One conversation does everything, so every file read pollutes the main context forever. With on-device models this is fatal: a 3B model's context fills after a few searches. Delegating research to a throwaway context keeps the main thread small.
Design
- New tool
task{description, prompt}: runs a nested agent loop in a fresh conversation and returns only its final text answer (capped, e.g. 8k chars). - The subagent's toolset is read-only:
read_file,list_directory,search_files,glob,read_website. Notask(no recursion), no mutating tools, so no permission prompts fire mid-subagent. - Architecture:
tools.rsis backend-agnostic, so it cannot construct a backend. At startup each surface registers a subagent factory in a process-global (OnceLock<Box<dyn Fn() -> Option<Arc<dyn InferenceBackend>>>>): forOpenAiBackendthe factory builds a fresh backend with the same base_url / key / model and a short subagent system prompt; forLocalBackendthe factory returnsNonefor now (onde has a single shared history; a second concurrent context needs onde support first) and the tool returns a clear "not available on-device yet" result the model can react to. - The nested loop reuses
send_message_with_tools/send_tool_resultsandexecute_tool, with its own round cap (8). - The
tasktool is only offered when the factory reports availability, the same conditional-spec pattern theskilltool uses.
Acceptance
- On an OpenAI-compatible backend,
taskcompletes a research prompt using only read-only tools and returns a text answer; the main conversation history gains one tool result, not the subagent transcript. - On-device, the tool either is not offered or returns the documented fallback.
- Unit tests cover the loop against a scripted backend (the
tests/acp_permissions.rsfake-endpoint harness pattern) and the toolset restriction.
4. Spec: durable sessions and context compaction
Branch: feature/session-persistence-compaction · Touches: src/backend.rs, src/main.rs, src/chat.rs, new src/session_store.rs
Problem
History lives in RAM (OpenAiBackend's message vec, onde's engine state). A
restart loses everything, ACP session/load cannot actually restore, and the hard
MAX_TOOL_ROUNDS = 10 cap is the only defense against blowing the context window.
This is the single hardest ceiling on task size, and it hurts most on-device where
context windows are smallest.
Design
- Persistence.
InferenceBackendgainshistory_snapshot() -> Vec<serde_json::Value>andrestore_history(Vec<serde_json::Value>).OpenAiBackendserializes its message vec as-is.LocalBackendflattens through onde'shistory()/push_history(tool entries flatten to text in the MVP; acceptable loss). A newsession_storemodule writes one JSONL file per session under$SIGIT_CONFIG_DIR/sessions/<session-id>.jsonlafter every completed turn, and loads it on demand. - Resume. ACP
session/loadrestores from the store when a file for that session id exists. The TUI gets/resumeto reload the most recent session./cleardeletes the file along with the in-memory history. - Compaction. A
compact_history()path: estimate tokens (chars / 4), and when the estimate crosses a per-model budget (default 24k tokens), ask the backend to summarize the conversation, then rebuild history as system prompt + summary + the last few turns. Exposed as/compact, and run automatically between tool rounds when over budget. With compaction in place, raiseMAX_TOOL_ROUNDSfrom 10 to 24.
Acceptance
- Kill sigit mid-conversation, restart,
session/load(or/resume): the model answers a follow-up that requires earlier context. - A conversation pushed past the token budget compacts instead of erroring, and the model still answers questions whose answers survived in the summary.
- Unit tests cover snapshot/restore round-trips for both backends, store read/write, threshold triggering, and post-compaction history shape.
5. Sequencing
All three branch off development independently. Suggested land order: background
commands (smallest, immediately felt), subagent tool, then sessions and compaction
(largest). Fast follows after these: permission rule patterns on top of PR #20,
stdio MCP transport, web search, and a headless sigit -p mode for CI, which is
also what the Cloud Agent sandbox runner needs.