main
md 8.29 KB

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_command gains an optional boolean argument run_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 as mcp.rs), and the tool returns immediately with the task id. A reader thread drains stdout/stderr into a capped buffer (COMMAND_OUTPUT_LIMIT per 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, TerminateProcess semantics via Child::kill everywhere) 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_output is read-only, kill_command is 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_command stops 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. No task (no recursion), no mutating tools, so no permission prompts fire mid-subagent.
  • Architecture: tools.rs is 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>>>>): for OpenAiBackend the factory builds a fresh backend with the same base_url / key / model and a short subagent system prompt; for LocalBackend the factory returns None for 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_results and execute_tool, with its own round cap (8).
  • The task tool is only offered when the factory reports availability, the same conditional-spec pattern the skill tool uses.

Acceptance

  • On an OpenAI-compatible backend, task completes 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.rs fake-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. InferenceBackend gains history_snapshot() -> Vec<serde_json::Value> and restore_history(Vec<serde_json::Value>). OpenAiBackend serializes its message vec as-is. LocalBackend flattens through onde's history() / push_history (tool entries flatten to text in the MVP; acceptable loss). A new session_store module writes one JSONL file per session under $SIGIT_CONFIG_DIR/sessions/<session-id>.jsonl after every completed turn, and loads it on demand.
  • Resume. ACP session/load restores from the store when a file for that session id exists. The TUI gets /resume to reload the most recent session. /clear deletes 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, raise MAX_TOOL_ROUNDS from 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.