diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..6327d399 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,106 @@ +name: ci + +on: + push: + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install toolchain from rust-toolchain.toml + run: rustup show active-toolchain || rustup toolchain install + - uses: Swatinem/rust-cache@v2 + - run: cargo fmt --all -- --check + - run: cargo clippy --all-targets --locked -- -D warnings + - run: cargo test --workspace --locked + + macos: + runs-on: macos-latest + needs: check + steps: + - uses: actions/checkout@v5 + - name: Install toolchain from rust-toolchain.toml + run: rustup show active-toolchain || rustup toolchain install + - uses: Swatinem/rust-cache@v2 + - run: cargo clippy --all-targets --locked -- -D warnings + - run: cargo test --locked -p gitagent-sandbox + - name: The launcher refuses writes outside the allowed folders + run: | + cargo build --locked -p gitagent-cli + mkdir -p allowed + target/debug/gitagent __sandbox --write "$PWD/allowed" --write /dev -- \ + sh -c 'echo ok > allowed/in.txt && ! (echo x > outside.txt) 2>/dev/null' 2> launcher.err + cat launcher.err + test -f allowed/in.txt + test ! -e outside.txt + ! grep -q 'sandbox limited' launcher.err + + musl: + runs-on: ubuntu-latest + needs: check + steps: + - uses: actions/checkout@v5 + - name: Install toolchain from rust-toolchain.toml + run: rustup show active-toolchain || rustup toolchain install + - uses: Swatinem/rust-cache@v2 + - uses: mlugg/setup-zig@v2 + - run: cargo install --locked cargo-zigbuild + - run: scripts/build-release.sh + - name: Check the binaries are static + run: | + for t in x86_64-unknown-linux-musl aarch64-unknown-linux-musl; do + file target/$t/release/gitagent | tee /dev/stderr | grep -q 'statically linked' + done + - uses: actions/upload-artifact@v4 + with: + name: gitagent-linux + path: target/*-unknown-linux-musl/release/gitagent + + clients: + runs-on: ubuntu-latest + needs: check + steps: + - uses: actions/checkout@v5 + - name: Install toolchain from rust-toolchain.toml + run: rustup show active-toolchain || rustup toolchain install + - uses: Swatinem/rust-cache@v2 + - run: cargo build --locked -p gitagent-cli + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + - name: Python client + run: GITAGENT_BIN=target/debug/gitagent python3 -m unittest discover -s clients/python/tests -v + - uses: actions/setup-node@v5 + with: + node-version: "22" + - name: TypeScript client + working-directory: clients/typescript + run: npm install --no-audit --no-fund --omit=optional && npm run build + - name: The npm launcher runs the binary + working-directory: clients/typescript + run: | + GITAGENT_BIN=../../target/debug/gitagent node bin/gitagent.js --version + node --input-type=module -e "const m = await import('./dist/index.js'); if (typeof m.query !== 'function' || typeof m.loadAgent !== 'function') process.exit(1);" + - name: The sandbox, with a stand-in for gitmachine + working-directory: clients/typescript + run: GITAGENT_BIN="$GITHUB_WORKSPACE/target/debug/gitagent" node --test test/sandbox.test.mjs + + npm-install: + runs-on: ubuntu-latest + needs: check + steps: + - uses: actions/checkout@v5 + - name: Install toolchain from rust-toolchain.toml + run: rustup show active-toolchain || rustup toolchain install + - uses: Swatinem/rust-cache@v2 + - run: cargo build --locked -p gitagent-cli + - uses: actions/setup-node@v5 + with: + node-version: "22" + - name: Install from a private registry, upgrade from 2.x, use the SDK + run: scripts/test-npm-install.sh target/debug/gitagent diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index f235d09e..00000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Publish to npm - -on: - push: - tags: - - "v*" - -permissions: - contents: read - id-token: write - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 20 - registry-url: https://registry.npmjs.org - - - run: npm ci - - - run: npm run build - - - run: npm publish --provenance --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..c9681b5d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,135 @@ +name: release + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +jobs: + version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: The tag matches every package's version + run: | + tag="${GITHUB_REF_NAME#v}" + cargo=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1) + npm=$(node -p "require('./clients/typescript/package.json').version") + python=$(sed -n 's/^version = "\(.*\)"/\1/p' clients/python/pyproject.toml | head -1) + echo "tag $tag; Cargo.toml $cargo; npm $npm; Python $python" + [ "$cargo" = "$tag" ] && [ "$npm" = "$tag" ] && [ "$python" = "$tag" ] + + linux: + needs: version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install toolchain from rust-toolchain.toml + run: rustup show active-toolchain || rustup toolchain install + - uses: mlugg/setup-zig@v2 + - run: cargo install --locked cargo-zigbuild + - run: scripts/package-release.sh x86_64-unknown-linux-musl aarch64-unknown-linux-musl + - name: The binaries are static, and the x86_64 one starts with the tag's version + run: | + for t in x86_64-unknown-linux-musl aarch64-unknown-linux-musl; do + tar -xzf "dist/gitagent-$t.tar.gz" -O gitagent > "$RUNNER_TEMP/gitagent-$t" + file "$RUNNER_TEMP/gitagent-$t" | tee /dev/stderr | grep -q 'statically linked' + done + chmod +x "$RUNNER_TEMP/gitagent-x86_64-unknown-linux-musl" + "$RUNNER_TEMP/gitagent-x86_64-unknown-linux-musl" --version | grep -F "${GITHUB_REF_NAME#v}" + - uses: actions/upload-artifact@v4 + with: + name: linux + path: dist/*.tar.gz + + macos: + needs: version + runs-on: macos-latest + steps: + - uses: actions/checkout@v5 + - name: Install toolchain from rust-toolchain.toml + run: rustup show active-toolchain || rustup toolchain install + - run: scripts/package-release.sh aarch64-apple-darwin x86_64-apple-darwin + - name: The arm64 binary starts with the tag's version + run: | + tar -xzf dist/gitagent-aarch64-apple-darwin.tar.gz -O gitagent > "$RUNNER_TEMP/gitagent" + chmod +x "$RUNNER_TEMP/gitagent" + "$RUNNER_TEMP/gitagent" --version | grep -F "${GITHUB_REF_NAME#v}" + - uses: actions/upload-artifact@v4 + with: + name: macos + path: dist/*.tar.gz + + publish: + needs: [linux, macos] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + - name: Checksums + working-directory: dist + run: sha256sum gitagent-*.tar.gz > SHA256SUMS + - name: Create the release + env: + GH_TOKEN: ${{ github.token }} + run: | + version="${GITHUB_REF_NAME#v}" + # docs/RELEASE-NOTES-.md (without a pre-release part) is the + # release text, with its relative links pointed at this tag. + notes="docs/RELEASE-NOTES-${version%%-*}.md" + blob="https://github.com/$GITHUB_REPOSITORY/blob/$GITHUB_REF_NAME" + args=(--title "gitagent $version") + if [ -f "$notes" ]; then + sed -e "s#](\./#]($blob/docs/#g" -e "s#](\.\./#]($blob/#g" "$notes" > "$RUNNER_TEMP/notes.md" + args+=(--notes-file "$RUNNER_TEMP/notes.md") + else + args+=(--generate-notes) + fi + case "$GITHUB_REF_NAME" in *-*) args+=(--prerelease) ;; esac + gh release create "$GITHUB_REF_NAME" dist/* --repo "$GITHUB_REPOSITORY" "${args[@]}" + + npm: + needs: publish + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v5 + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + - uses: actions/setup-node@v5 + with: + node-version: "22" + registry-url: "https://registry.npmjs.org" + - name: Platform packages + run: scripts/npm-packages.sh "$GITHUB_REF_NAME" + - name: Main package + working-directory: clients/typescript + run: npm install --no-audit --no-fund --omit=optional && npm run build + - name: Publish to npm + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + # A version with a pre-release part (3.1.0-rc.1) goes to the "next" tag, not "latest". + tag=latest + case "$GITHUB_REF_NAME" in *-*) tag=next ;; esac + publish() { + name=$(node -p "require('./$1/package.json').name") + version=$(node -p "require('./$1/package.json').version") + if npm view "$name@$version" version >/dev/null 2>&1; then + echo "$name@$version is already published" + else + npm publish "./$1" --access public --provenance --tag "$tag" + fi + } + # The platform packages first, so the main package's optional dependencies resolve. + for dir in npm/*/; do publish "${dir%/}"; done + publish clients/typescript diff --git a/.gitignore b/.gitignore index 7595ffa4..80e5877d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,21 @@ -node_modules/ -dist/ -pi-mono/ -.claude/ -.gitagent/ -.gitagent/whatsapp-auth/ -*.tgz -snake-game.html -examples/debug-events.ts +/target/ +bench/jobs/ .env -cmd -workspace/ -memory/.latest-frame.jpg -memory/.latest-screen.jpg +*.env .DS_Store -.basecode/ -.cursor/ -.vscode/ -.mcp.json +.env.* +__pycache__/ +*.pyc +dist/ +/npm/ +node_modules/ + +# The full run log, the dev/held-out split, the round history, the published +# scores and the lesson store stay on the benchmark machine; the public proof +# is the job logs on the Harbor hub. +bench/RESULTS.md +bench/split.json +bench/rounds.json +bench/leaderboard.json +bench/lessons/ +bench/reruns/ diff --git a/BLUEPRINT.md b/BLUEPRINT.md new file mode 100644 index 00000000..9bdebd68 --- /dev/null +++ b/BLUEPRINT.md @@ -0,0 +1,250 @@ +# gitagent 3 — Blueprint + +This document says **what** gitagent is built to do and **why** each part exists. The README says how to use it; [docs/HOW-IT-WORKS.md](./docs/HOW-IT-WORKS.md) gives the exact mechanics, with numbers and formats. + +--- + +## 0. Goal in one paragraph + +gitagent 3 is a single static Rust binary that runs **agents defined as git repositories**. An agent is a folder of plain files (identity, rules, memory, skills, tools, hooks, schedules, sub-agents), so its behaviour is versioned, reviewed and diffed like code. One loop runs every agent, the same way in a terminal, behind an SDK, in a fresh clone of a repository, on a timetable, or inside a benchmark container. That loop is built to finish real work: it shapes what the model reads, keeps the provider's cache warm, guards against loops and running out of time, and refuses "done" until the work has been verified. The proof is public: 93.03% ±0.8 on Terminal-Bench 2.1 with Claude Opus 5.5, above every published harness on that benchmark. + +--- + +## 1. Requirements (every design choice traces to one) + +**The agent** + +| ID | Requirement | +|---|---| +| A1 | An agent is a folder of version-controlled files; every behaviour traces to a commit | +| A2 | Agent folders written for the TypeScript gitagent (1.x and 2.x) and in the OpenGAP format run unchanged | +| A3 | Usable from a terminal, from Python, TypeScript or any other language, from a repository clone, and on a schedule | +| A4 | Safe to run on a real machine: a sandboxed shell, hooks that can block any action, approvals by a person, an audit log | +| A5 | Large jobs can be split into parallel pieces and reconciled into one answer | +| A6 | Works with Anthropic, OpenAI, Google and any OpenAI-compatible endpoint, with fallbacks | + +**The loop** + +| ID | Requirement | +|---|---| +| L1 | A higher Terminal-Bench 2.1 pass rate than any published harness on the same model | +| L2 | Median time per task at or below other harnesses; timeouts rare | +| L3 | Most input tokens served from the provider's cache on long tasks | +| L4 | Handles REPLs, servers, long builds and interrupts | +| L5 | Never accepts "done" without a verification after the last change | +| L6 | Runs in any Linux container with no dependencies | +| L7 | Every tunable behaviour is a switch in a YAML profile, measured on its own | +| L8 | Every feature emits counters; every run writes a complete trajectory, every turn | +| L9 | Benchmark integrity: no reading or writing of hidden tests, no routing on task names, no network but the model API, no learning during a scored run | + +--- + +## 2. Why Harbor + +gitagent is measured on Terminal-Bench 2.1, and Terminal-Bench runs through **Harbor**, an open framework for running agents against containerised tasks. Harbor matters for four reasons. + +1. **It is the same yardstick as the published results.** The leaderboards for Terminal-Bench 2.1 were produced with Harbor: the same task images, the same time limits, the same hidden tests, the same scoring. Running gitagent through it is what makes 93.03% ±0.8 comparable with the published 87.6% for Terminus 2 on the same model, rather than a number from a home-made runner. +2. **Grading is independent of the agent.** Harbor starts each task's container, hands the agent the instruction, stops it at the task's time limit, and when the agent has finished it runs the task's hidden tests itself and records the reward. gitagent never sees, reads or writes the tests. The result is a pass or a fail decided outside the agent, which is the only kind worth publishing. +3. **It fits a static binary.** Harbor's "installed agent" model uploads the agent into the task container and runs it there. gitagent is one binary with no runtime, so installing it is a single file copy, and nothing in the container has to change. The shim that does this, `gitagent_harbor/agent.py`, is about two hundred lines. +4. **It produces evidence others can check.** Harbor runs many trials in parallel, resumes trials that failed for reasons outside the agent (a revoked key, a full Docker network), keeps every trajectory and verifier log, and uploads a job publicly. The headline run's nine jobs, one per time limit, are public, so anyone can read every trial behind the number. + +Harbor also hosts other benchmarks with the same task shape, SWE-bench Verified and SWE-bench Pro among them, so the same shim can be pointed at them without changes to the agent. + +**How gitagent runs under Harbor.** The shim uploads the binary for the container's CPU to `/opt/gitagent/`, then runs `gitagent --bench --instruction-file … --model … --profile … --linger`. `--bench` makes the run depend only on files committed in the repository: no project or home configuration, no memory, no learning, no telemetry, repo mode refused, writes under `/tests` refused. `--linger` keeps the agent's shells open after it finishes, so a server it started is still up when the tests connect. Harbor enforces each task's time limit, but the shim is not given it, so `scripts/run-dev-pass.sh` reads the limit from each task's `task.toml`, starts one job per limit, and passes `--timeout-s` 60 seconds below it; the time budget then counts down against the real limit, and the run ends cleanly before Harbor stops the container. The trajectory and counters are written to Harbor's log folder every turn, so even a timed-out trial keeps its record. A test, `bench_prompt_and_tools_match_the_published_run`, fails if the prompt or tool list sent to the model in `--bench` ever changes, so features built for agent folders can never leak into a scored run. + +--- + +## 3. Architecture + +``` + console · -p · --repo Python · TypeScript · any language Harbor + gitagent schedule start (gitagent --ipc, JSON lines) (gitagent --bench) + │ │ │ + └──────────────────┬───────────────┴───────────────┬───────────────┘ + ▼ ▼ + ┌──────────────────────────────────────────────────────────┐ + │ loader: agent folder + config layers ──▶ one AgentSpec │ + │ agent.yaml · SOUL/RULES/DUTIES · skills · tools · │ + │ hooks · memory · knowledge · workflows · schedules · │ + │ agents · plugins · config/ · compliance │ + ├──────────────────────────────────────────────────────────┤ + │ core: THE LOOP │ Model APIs + │ provider ── native tool calls, cache markers ─────────┼────▶ Anthropic, OpenAI, + │ tools ── shell, files, edits, memory, skill, │ Google, compatible + │ subagent, plan, workflow, YAML, MCP │ + │ terminal ── persistent PTY shells │ + │ shaper ── output to a budget, spill files │ + │ guards ── stop check, time budget, loop detector │ + │ context ── masking, compaction │ + │ hooks · approvals · audit │ + ├──────────────────────────────────────────────────────────┤ + │ record: trajectory + counters every turn, events │ + │ sandbox: Landlock + seccomp; macOS: sandbox-exec │ + └──────────────────────────────────────────────────────────┘ +``` + +### One run, start to finish + +1. **Start.** An entry point starts the loop with an instruction: the console, `-p`, repo mode, an IPC client, the scheduler, or Harbor. +2. **Load.** The loader reads the agent folder and the configuration layers into one spec and assembles the system prompt in a fixed order: identity, working method, lessons, skills, sub-agents, workflows, knowledge, examples, compliance, memory, bootstrap. In `--bench` only the built-ins apply, and the prompt is byte-identical across tasks so the cache holds. +3. **Snapshot.** One probe, at most 15 seconds, collects the working directory, the system, the tools present and the directory listing, and puts it in the first user message, never the system prompt. +4. **Model call.** The provider adapter sends the request with native tool calls and cache markers; malformed calls get one repair request. +5. **Dispatch.** Each tool call passes the hooks and, when the agent's rules require it, a person's approval, then runs. Shell commands run in a persistent shell with an end marker; sub-agent and plan calls run concurrently. +6. **Shape.** Every result is cut to a token budget, with similar lines collapsed and the full output saved to a spill file. +7. **Guard.** The time left is shown on every result, a reminder fires at 70% of the budget and a final call at 90%; a call repeated with the same result three times gets a change-approach warning. +8. **Context.** Old results are cleared in batches, through the provider's context editing where it exists; near the window's end the history becomes a structured summary with the task repeated verbatim. +9. **Stop check.** `task_complete` is accepted only when a verification ran after the last change. +10. **Record.** The trajectory step and counters are written after every turn. + +Steps 4 to 9 repeat until the stop check accepts, the agent replies in conversation mode, or a limit is hit. + +--- + +## 4. Design decisions + +| # | Decision | Why | +|---|---|---| +| D1 | One static Rust binary | No runtime to install anywhere; fast start; direct PTY access; memory safety; a sandbox from inside the process | +| D2 | The agent is a folder of files | Behaviour is versioned and reviewable; forking, branching and diffing an agent are git operations (A1) | +| D3 | Accept the TypeScript gitagent's and OpenGAP's formats, warn about old spellings | Existing agents run unchanged; `validate` points to the newer form instead of failing (A2) | +| D4 | One loop for every entry point | A fix or a measurement applies everywhere; the benchmark result describes the product | +| D5 | A single linear, append-only history | Keeps the provider's cache valid; the gains are in the loop, not in agent graphs | +| D6 | Native tool calls with a text fallback and one repair | Reliable calls on every provider, open-weight models included | +| D7 | Persistent PTY shells with end markers and background sessions | No guessed waits; REPLs and servers work; Ctrl-C reaches only the running job | +| D8 | A small tool set, edit tool chosen per model family | A short cached prefix and fewer wrong choices; `apply_patch` for GPT, `str_replace` for Claude | +| D9 | The environment snapshot in the first message | Saves the first turns of looking around without touching the cached system prompt | +| D10 | An output shaper with spill files | The largest token cost, cut without losing anything | +| D11 | Batched masking, server-side where possible, and structured compaction near the limit | About half the cost at the same solve rate, with the cache and thinking blocks intact | +| D12 | An evidence-based stop check that never rewrites the answer | Premature "done" is the most common failure; critics that rewrite answers make results worse | +| D13 | A time budget and a loop detector | Timeouts are failures; loops waste the budget | +| D14 | Per-model-family profiles in YAML, every switch measured on its own | Settings do not transfer across providers; a switch that does not help is turned off | +| D15 | Thin provider adapters of our own | Byte-level control of requests, which cache hits depend on | +| D16 | Counters and a trajectory written every turn | A feature that never fires shows up; a timed-out run keeps its record | +| D17 | Hooks fail open, compliance hooks fail closed | Existing hook scripts behave as before; rules that must hold always do | +| D18 | Clients talk to the binary over JSON lines on stdin and stdout, one process per session | Any language can drive an agent; the agent logic exists once; a crash in a client cannot corrupt a run | +| D19 | Parallel work as a `plan` tool over sub-agents, with a reconciling step, opt-in per agent | Large jobs finish faster and more consistently; bounded phases and tasks keep the cost predictable | +| D20 | Schedules run in-process while `gitagent schedule start` runs | The same behaviour as before; anything that must not be missed calls `gitagent schedule run` from an external scheduler | +| D21 | The terminal is the interface | One console with tool lines, approvals, a task board and command suggestions; no web server to secure | +| D22 | Landlock and seccomp (on macOS, a `sandbox-exec` profile) around the agent's shell, not around gitagent | The agent cannot write outside its folders, while gitagent can still record | +| D23 | `--bench` reads only committed files, and a test pins its prompt and tools | Product features cannot change a scored run by accident | +| D24 | Everyday defaults keep the benchmark's guards, except the snapshot; the console has no time limit unless one is set | The loop detector, stop check and context clearing help in any repository; a person at the console stops a turn with Ctrl-C, while runs nobody watches keep a 30-minute ceiling; the snapshot costs up to 15 seconds per start in a project the user already knows | +| D25 | Sub-agents are warned as their turns run low, may be given their own turn limit (up to 100), and come back marked partial when they run out | A review cut off mid-step wastes its whole cost; a warned one hands back what it found, and the lead re-runs only what is marked partial | +| D26 | The `/tests` write guard applies to benchmark runs only | It protects graders' tests; in a user's project `/tests` is an ordinary path and a pattern such as `grep -v /tests/` must not be refused | +| D27 | One throttle per session: `max_parallel` is a ceiling, a rate-limit refusal halves the window, ten clean calls in a row give a slot back | The right number of parallel calls depends on the account's rate limit, which gitagent cannot know; backing off beats retrying blindly, and the ceiling stays the user's choice | +| D28 | Where a 1.x agent relies on a default, 1.x's default stays: `.sh` scripts under `sh`, 10-second hooks, every sampling setting a provider takes, plugin settings for plugin scripts, unknown `gitagent.yaml` keys as warnings; a setting that cannot be honoured is a warning, never silent | Existing agents must run unchanged, and a difference nobody is told about costs more than an old default | + +**Left out on purpose:** critics that rewrite answers; best-of-N on benchmarks; memory or learning during a scored run; automated rewriting of the harness; masking every turn; third-party multi-provider frameworks; a web interface; voice; remote sandboxes. + +--- + +## 5. Product surface + +| Capability | How | Notes | +|---|---|---| +| Console | `gitagent -d ` | Welcome box, `$` tool lines, a thinking indicator, approvals inline, `/` command suggestions, a task board for plans, `/help` guide | +| One task | `gitagent -p "…" -d ` | Prints the agent's summary; exit 3 on a provider failure | +| Repo mode | `gitagent --repo -p "…" [--push]` | Clones (or reuses a clone), works on a new branch, commits when done; never pushes the base branch or force-pushes | +| Agent folders | `agent.yaml`, `SOUL.md`, `RULES.md`, `DUTIES.md`, `memory/`, `skills/`, `tools/`, `hooks/`, `agents/`, `workflows/`, `schedules/`, `knowledge/`, `examples/`, `plugins/`, `config/` | `gitagent init` creates one; `gitagent validate` checks one; `extends` and `dependencies` compose them (`gitagent install`) | +| Memory | `memory/` with layers and line limits | Each save is a git commit | +| Hooks | `hooks/hooks.yaml`, eight events | Scripts answer allow, block or modify | +| Compliance | `compliance:` in `agent.yaml` | Human in the loop, escalation triggers, kill switch, segregation of duties, spending limits, audit log | +| Sub-agents and parallel work | `agents/`, the `subagent` tool, `runtime.parallel_tasks` | Phases of tasks run at the same time, then reconciled | +| Workflows | `workflows/*.yaml`, `@` | Ordered steps with conditions, retries and approvals | +| Schedules | `schedules/*.yaml`, `gitagent schedule …` | Cron or a one-time moment; each job logged and recorded in its file | +| MCP | `mcp_servers:` | stdio, streamable HTTP and SSE servers; their tools join the agent's | +| Plugins | `plugins/`, `gitagent plugin …` | Tools, skills, hooks and prompt text from a folder or a git URL | +| SDKs | Python, TypeScript, any language over `gitagent --ipc`; a Rust crate in-process | The TypeScript gitagent's `query()` API | +| Benchmark | `gitagent --bench` under Harbor | §2 | +| Learning | `gitagent learn`, `learning: online` | Lessons from finished runs, audited, shown only when two tasks agree; never during a scored run | +| Telemetry | `OTEL_EXPORTER_OTLP_ENDPOINT` | Traces and metrics over OTLP; off by default | + +--- + +## 6. Code layout + +A Cargo workspace; `unsafe` is forbidden everywhere. + +``` +gitagent/ +├─ crates/ +│ ├─ cli/ the binary: flags, console, line editor, IPC server, subcommands +│ ├─ core/ the loop, sub-agents, plan, workflows, approvals, audit +│ ├─ loader/ agent folders and config layers → AgentSpec; validate +│ ├─ provider/ Anthropic, OpenAI, Google, compatible, scripted (tests) +│ ├─ tools/ built-in tools, YAML tools, edit tools +│ ├─ terminal/ persistent PTY shells, end markers, background sessions +│ ├─ shaper/ output shaping and spill files +│ ├─ context/ masking and compaction +│ ├─ guards/ stop check, time budget, loop detector +│ ├─ hooks/ hook scripts and their protocol +│ ├─ mcp/ the MCP client +│ ├─ record/ trajectory, counters, events +│ ├─ repo/ clone, branch, commit, push +│ ├─ sandbox/ Landlock and seccomp; sandbox-exec on macOS +│ ├─ learn/ offline learning of lessons +│ └─ sdk/ the in-process Rust library +├─ clients/ python/ and typescript/: thin clients over --ipc +├─ gitagent_harbor/ the Harbor shim +├─ profiles/ anthropic, openai, google, compat +├─ prompts/ system, conversation, lessons, learner +├─ bench/ benchmark profiles, task split, job records +├─ scripts/ install, release builds, integrity audit, benchmark runs, dashboard +└─ docs/ how it works, integrity, learning +``` + +The Provider interface every adapter implements: + +```rust +pub trait Provider: Send + Sync { + async fn complete(&self, req: &ChatRequest) -> Result; + fn family(&self) -> Family; // Anthropic | OpenAI | Google | Compat +} +``` + +Dependencies are kept few: `tokio`, `reqwest` with rustls, `serde`, `serde_json`, a YAML parser, `clap`, `regex`, `portable-pty`, `anyhow`, `thiserror`, `async-trait`, and `landlock` with a seccomp filter for the sandbox (on macOS it uses the system's `sandbox-exec`, so no dependency). Token counts are estimated at four bytes per token instead of linking a tokenizer. Release builds are static musl binaries for x86_64 and aarch64 Linux, plus macOS. + +--- + +## 7. Configuration surfaces + +- **`agent.yaml`**, in the agent folder: what the agent is. Model with fallbacks and constraints, tools, runtime limits, parallel tasks, skills, sub-agents, MCP servers, plugins, compliance, `extends` and `dependencies`. The README's Architecture section shows a full example. +- **`gitagent.yaml`**, in the project or `~/.gitagent/`: how the binary runs. Default model, turn and time limits, profile, sandbox, skills and hooks on or off, learning, memory, MCP servers, repo-mode settings. +- **`profiles/.yaml`**: how the loop behaves for one model family. Tool calling, edit tool, effort, output budget, context and cache settings, the guards, the stop check's patterns, and every optional feature as a switch. `profile: auto` picks the family from the model. +- **`config/.yaml`**, in the agent folder: per-environment overrides selected with `--env`: model, temperature, timeout, environment variables for tools and hooks. + +--- + +## 8. Interfaces + +- **CLI.** `-p`, `-d`, `-m`, `-e`, `--repo`, `--push`, `--base`, `--branch`, `--bench`, `--instruction-file`, `--profile`, `--config`, `--out-dir`, `--events jsonl`, `--output-format stream-json`, `--ipc`, `--workflow`, and the subcommands `init`, `validate`, `install`, `plugin`, `schedule`, `learn`. +- **Events** (`--events jsonl`): one JSON object per line: `turn_start`, `model_text`, `tool_call`, `tool_result`, `stop_check`, `guard`, `plan`, `turn_end`, `repo`, `run_end`. +- **IPC** (`gitagent --ipc`): JSON lines on stdin and stdout. The client sends `initialize`, `user`, `steer`, `abort`, `kill`, `callback_response`, `end`; the binary sends `ready`, the conversation's messages, `callback_request` for hooks, client tools and approvals, `turn_end` and `session_end`. Sessions can be resumed across processes. +- **Harbor shim** (`gitagent_harbor/agent.py`): installs the binary, runs `--bench --linger`, and returns the trajectory and counters to Harbor. + +--- + +## 9. Observability + +Counters are written to `counters.json` after every turn and exported with telemetry when it is on. They cover the loop (`turns`, `tool_calls`, `malformed_calls`, `repairs`, `refusals`, `fallback_turns`, `provider_retries`, `rate_limit_retries`), the shell (`exec_timeouts`, `background_sessions`, `interrupts`, `shell_restarts`), shaping and context (`shaped_bytes_in`, `spill_reads`, `masks_applied`, `mask_checkpoints`, `compactions`, `cache_read_tokens`, `cache_write_tokens`), the guards (`stop_accepts`, `stop_rejections`, `loop_triggers`, `protected_writes_blocked`), hooks and compliance (`hook_blocks`, `hook_modifies`, `approvals_granted`, `approvals_denied`, `compliance_blocks`), memory, sub-agents and plans (`subagent_calls`, `plan_tasks`, `plan_failed_tasks`), workflows and MCP. + +A feature whose counters stay at zero across a measurement run is either broken or useless, and is investigated before it is kept. + +--- + +## 10. Testing + +- **Unit tests** next to the code: shaping, end markers, the stop check, the loop hash, masking, profile and agent-folder loading, cron parsing, the line editor. +- **End-to-end tests** with the scripted provider: model replies replayed from YAML drive the real loop against a real PTY in a temporary folder, covering every entry point, IPC, hooks, approvals, sub-agents, plans, schedules and repo mode. No network and no keys. +- **The benchmark guard**: the prompt and tools sent in `--bench` must match the published fixtures byte for byte. +- **Client tests**: the Python client's unit tests, the TypeScript client's type check. +- **Benchmark measurement**: every change that can affect results is measured with Harbor, three or more trials per task, compared task by task with the previous version, on tasks kept apart from the ones used to score. + +--- + +## 11. Open questions for the owner + +1. A SWE-bench Verified entry: run it, and with which model? +2. Publishing the clients: package names and registries for Python and TypeScript. +3. Releasing 3.0.0: the squash-merge into `main`, the tag and the release notes. +4. Token-by-token streaming over IPC, which version 1 of the protocol does not have. +5. Running a plugin's `entry` JavaScript, which gitagent 3 does not do. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e13b6c3f..48b40185 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,84 +1,87 @@ -# Contributing to GitAgent +# Contributing to gitagent -Thanks for your interest in contributing to GitAgent! Here's how to get started. +Thanks for helping. This guide covers gitagent 3, the Rust binary. -## Getting Started +## Getting started -1. Fork the repository -2. Clone your fork: - ```bash +1. Fork the repository and clone your fork: + ```sh git clone https://github.com//gitagent.git cd gitagent ``` -3. Install dependencies: - ```bash - npm install - ``` -4. Build the project: - ```bash - npm run build - ``` -5. Run tests: - ```bash - npm test +2. Install Rust. The toolchain is pinned in `rust-toolchain.toml`, so `rustup` picks the right version on its own. +3. Build and test: + ```sh + cargo build + cargo test --workspace ``` -## Development Workflow +The tests need no network and no API keys. A scripted provider replays model replies from YAML (`--model scripted:`), and the provider adapters are tested against local mock servers. To try a change by hand: -1. Create a feature branch from `main`: - ```bash - git checkout -b feat/my-feature - ``` -2. Make your changes in `src/` -3. Run `npm run build` to verify compilation -4. Run `npm test` to ensure tests pass -5. Commit your changes with a clear message -6. Push and open a pull request +```sh +cargo run -p gitagent-cli -- -d path/to/agent -m scripted:path/to/script.yaml +``` -## Project Structure +## Before you open a pull request -``` -src/ -├── index.ts # CLI entry point -├── sdk.ts # Core SDK (query function) -├── exports.ts # Public API surface -├── loader.ts # Agent config loader -├── tools/ # Built-in tools (cli, read, write, memory) -├── voice/ # Voice mode (OpenAI Realtime adapter) -├── hooks.ts # Script-based hooks -├── sdk-hooks.ts # Programmatic SDK hooks -├── skills.ts # Skill expansion -├── workflows.ts # Workflow metadata -├── agents.ts # Sub-agent metadata -├── compliance.ts # Compliance validation -└── audit.ts # Audit logging -``` +Run all four; CI runs the same: -## Guidelines +```sh +cargo fmt --all +cargo clippy --workspace --all-targets -- -D warnings +cargo test --workspace +bash scripts/integrity-audit.sh +``` -- **TypeScript** — all code is written in strict TypeScript -- **ESM** — the project uses ES modules (`"type": "module"`) -- **Keep it simple** — avoid over-engineering; minimal dependencies -- **Test your changes** — add or update tests in `test/` when applicable -- **One concern per PR** — keep pull requests focused and reviewable +- **The benchmark guard must pass.** `bench_prompt_and_tools_match_the_published_run` fails if the system prompt or the tool list sent to the model in `--bench` mode changes at all. Features for agent folders, the console or the SDKs must never reach benchmark mode. If a change to the benchmark setup is intended, say so in the pull request; it needs a measured run before it is merged. +- **Behaviour that changes results is a profile switch.** Add it to `profiles/*.yaml`, off by default, and describe it in `docs/HOW-IT-WORKS.md`. +- **Agents written for the TypeScript gitagent must keep working.** Tool names, hook scripts, `agent.yaml` fields and SDK options from 1.x and 2.x are accepted; if you change how one is read, keep the old form working and cover it with a test. + +## Project structure + +The workspace is split into crates under `crates/`: + +| Crate | What it does | +|---|---| +| `cli` | The `gitagent` binary: flags, the console, the IPC protocol, subcommands (`init`, `validate`, `install`, `plugin`, `schedule`, `learn`) | +| `core` | The agent loop, sub-agents, the `plan` tool, workflows, approvals and the audit log | +| `loader` | Reads agent folders and configuration into one spec: `agent.yaml`, identity files, skills, tools, hooks, memory, knowledge, workflows, schedules, plugins, environments, compliance | +| `provider` | Model adapters: Anthropic, OpenAI, Google, OpenAI-compatible, and the scripted provider for tests | +| `tools` | Built-in tools: shell, files, edits, memory, skills | +| `terminal` | Persistent shells on a pseudo-terminal | +| `shaper` | Shapes command output before the model sees it | +| `context` | Masking and compaction of the conversation | +| `guards` | The stop check, the time budget and the loop detector | +| `hooks` | Hook scripts and their protocol | +| `mcp` | The Model Context Protocol client | +| `record` | Trajectories, counters and events | +| `repo` | Repo mode: clones, branches, commits, pushes | +| `sandbox` | Landlock and seccomp for the agent's shell; `sandbox-exec` on macOS | +| `learn` | Offline learning of lessons from finished runs | +| `sdk` | The Rust library interface | + +The Python and TypeScript clients live under `clients/` and talk to the binary over the IPC protocol (`gitagent --ipc`). -## Commit Messages +## Guidelines -Use clear, descriptive commit messages: +- **No unsafe code.** The workspace forbids it. +- **Small dependencies.** Prefer a few lines of code to a new crate. +- **Test what you change.** Unit tests next to the code, end-to-end tests in `crates/cli/tests/`. +- **One concern per pull request.** Keep it focused and reviewable. +- **Write docs in plain language.** Short sentences, one idea each. -- `Add voice mode with OpenAI Realtime adapter` -- `Fix memory tool path resolution on Windows` -- `Update SDK query to support abort signals` +## Commit messages -## Reporting Issues +A short subject saying what changed, then a body saying why when it is not obvious: -- Search existing issues before opening a new one -- Include reproduction steps, expected vs actual behavior, and your environment (Node version, OS) +- `Parallel tasks: the plan tool and a live task board` +- `Hooks that crash are a warning, as in 1.x` +- `Repo mode reuses an earlier clone of the same repository` -## Code of Conduct +## Reporting issues -Be respectful and constructive. We're all here to build something useful together. +Search the existing issues first. Include what you ran, what you expected, what happened, `gitagent --version`, and your operating system. A trajectory (`trajectory.json` from the run's output folder) helps most. ## License -By contributing, you agree that your contributions will be licensed under the [MIT License](./LICENSE). +By contributing, you agree that your contributions are licensed under the [MIT License](./LICENSE). diff --git a/rust/gitagent-rs/Cargo.lock b/Cargo.lock similarity index 69% rename from rust/gitagent-rs/Cargo.lock rename to Cargo.lock index 98f16416..e859cac3 100644 --- a/rust/gitagent-rs/Cargo.lock +++ b/Cargo.lock @@ -3,16 +3,24 @@ version = 4 [[package]] -name = "adler2" -version = "2.0.1" +name = "aho-corasick" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] [[package]] -name = "allocator-api2" -version = "0.2.21" +name = "annotate-snippets" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" +dependencies = [ + "anstyle", + "memchr", + "unicode-width", +] [[package]] name = "anstream" @@ -71,19 +79,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] -name = "arbitrary" -version = "1.4.2" +name = "arraydeque" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" [[package]] name = "async-trait" @@ -93,7 +92,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.9", ] [[package]] @@ -102,17 +101,35 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "bitflags" -version = "2.13.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "bumpalo" @@ -126,26 +143,11 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "castaway" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" -dependencies = [ - "rustversion", -] - [[package]] name = "cc" -version = "1.4.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" dependencies = [ "find-msvc-tools", "shlex", @@ -153,9 +155,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "cfg_aliases" @@ -165,26 +173,20 @@ checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", "cpufeatures", "rand_core", ] -[[package]] -name = "chunked_transfer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" - [[package]] name = "clap" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", @@ -192,9 +194,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstream", "anstyle", @@ -204,21 +206,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.4" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck", "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.9", ] [[package]] name = "clap_lex" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "colorchoice" @@ -227,136 +229,85 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] -name = "compact_str" -version = "0.8.2" +name = "core_detect" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd622ebbb56a5b2ccb651b32b911cdeb2a9b4b11776b2473bf26a26a286244e" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", -] +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] [[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" - -[[package]] -name = "crossterm" -version = "0.28.1" +name = "displaydoc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ - "bitflags", - "crossterm_winapi", - "futures-core", - "mio", - "parking_lot", - "rustix", - "signal-hook", - "signal-hook-mio", - "winapi", + "proc-macro2", + "quote", + "syn 3.0.9", ] [[package]] -name = "crossterm_winapi" -version = "0.9.1" +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] -name = "darling" -version = "0.24.0" +name = "encoding_rs" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" dependencies = [ - "darling_core", - "darling_macro", + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] -name = "darling_core" -version = "0.24.0" +name = "encoding_rs_io" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4" +checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22" dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 3.0.3", + "encoding_rs", ] [[package]] -name = "darling_macro" -version = "0.24.0" +name = "enumflags2" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ - "darling_core", - "quote", - "syn 3.0.3", + "enumflags2_derive", ] [[package]] -name = "derive_arbitrary" -version = "1.4.2" +name = "enumflags2_derive" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", "syn 2.0.119", ] -[[package]] -name = "displaydoc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "either" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" - [[package]] name = "equivalent" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" [[package]] name = "errno" @@ -369,26 +320,33 @@ dependencies = [ ] [[package]] -name = "find-msvc-tools" -version = "0.1.10" +name = "fastrand" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] -name = "flate2" -version = "1.1.9" +name = "filedescriptor" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" dependencies = [ - "crc32fast", - "miniz_oxide", + "libc", + "thiserror 1.0.69", + "winapi", ] [[package]] -name = "foldhash" -version = "0.1.5" +name = "find-msvc-tools" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" @@ -401,60 +359,39 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" - -[[package]] -name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" - -[[package]] -name = "futures-macro" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", - "futures-io", - "futures-macro", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "slab", ] @@ -487,14 +424,243 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.15.5" +name = "gitagent-cli" +version = "3.0.0" +dependencies = [ + "anyhow", + "async-trait", + "clap", + "gitagent-core", + "gitagent-hooks", + "gitagent-learn", + "gitagent-loader", + "gitagent-mcp", + "gitagent-provider", + "gitagent-record", + "gitagent-repo", + "gitagent-sandbox", + "gitagent-sdk", + "gitagent-terminal", + "gitagent-tools", + "reqwest", + "serde-saphyr", + "serde_json", + "tempfile", + "tokio", +] + +[[package]] +name = "gitagent-context" +version = "3.0.0" +dependencies = [ + "gitagent-provider", + "serde_json", +] + +[[package]] +name = "gitagent-core" +version = "3.0.0" +dependencies = [ + "anyhow", + "async-trait", + "gitagent-context", + "gitagent-guards", + "gitagent-hooks", + "gitagent-loader", + "gitagent-mcp", + "gitagent-provider", + "gitagent-record", + "gitagent-shaper", + "gitagent-terminal", + "gitagent-tools", + "serde_json", + "tempfile", + "tokio", +] + +[[package]] +name = "gitagent-guards" +version = "3.0.0" +dependencies = [ + "anyhow", + "regex", +] + +[[package]] +name = "gitagent-hooks" +version = "3.0.0" +dependencies = [ + "async-trait", + "gitagent-loader", + "gitagent-record", + "regex", + "serde_json", + "tempfile", + "tokio", +] + +[[package]] +name = "gitagent-learn" +version = "3.0.0" +dependencies = [ + "anyhow", + "gitagent-loader", + "gitagent-provider", + "gitagent-record", + "serde", + "serde_json", + "tempfile", + "tokio", +] + +[[package]] +name = "gitagent-loader" +version = "3.0.0" +dependencies = [ + "anyhow", + "serde", + "serde-saphyr", + "serde_json", + "tempfile", +] + +[[package]] +name = "gitagent-mcp" +version = "3.0.0" +dependencies = [ + "gitagent-loader", + "gitagent-provider", + "reqwest", + "serde_json", + "tempfile", + "tokio", +] + +[[package]] +name = "gitagent-provider" +version = "3.0.0" +dependencies = [ + "anyhow", + "async-trait", + "gitagent-loader", + "reqwest", + "serde", + "serde-saphyr", + "serde_json", + "thiserror 2.0.21", + "tokio", +] + +[[package]] +name = "gitagent-record" +version = "3.0.0" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "tempfile", +] + +[[package]] +name = "gitagent-repo" +version = "3.0.0" +dependencies = [ + "anyhow", + "tempfile", +] + +[[package]] +name = "gitagent-sandbox" +version = "3.0.0" +dependencies = [ + "anyhow", + "landlock", + "libc", + "seccompiler", + "tempfile", +] + +[[package]] +name = "gitagent-sdk" +version = "3.0.0" +dependencies = [ + "anyhow", + "async-trait", + "gitagent-core", + "gitagent-hooks", + "gitagent-loader", + "gitagent-provider", + "gitagent-record", + "gitagent-tools", + "serde", + "serde_json", + "tempfile", + "tokio", +] + +[[package]] +name = "gitagent-shaper" +version = "3.0.0" +dependencies = [ + "tempfile", +] + +[[package]] +name = "gitagent-terminal" +version = "3.0.0" +dependencies = [ + "anyhow", + "portable-pty", + "tempfile", + "tokio", +] + +[[package]] +name = "gitagent-tools" +version = "3.0.0" +dependencies = [ + "anyhow", + "gitagent-context", + "gitagent-loader", + "gitagent-mcp", + "gitagent-provider", + "gitagent-record", + "gitagent-shaper", + "gitagent-terminal", + "reqwest", + "serde", + "serde_json", + "tempfile", + "tokio", +] + +[[package]] +name = "granit-parser" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "e20f99e46474f56bd905c56e817ebddcf377a611f94c53ac4649e4d3fa3c0cd0" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", + "arraydeque", + "smallvec", +] + +[[package]] +name = "h2" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -531,9 +697,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -548,22 +714,17 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -576,9 +737,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.9" +version = "0.27.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" dependencies = [ "http", "hyper", @@ -592,16 +753,17 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ - "base64", + "base64 0.23.1", "bytes", "futures-channel", "futures-util", "http", "http-body", + "httparse", "hyper", "ipnet", "libc", @@ -615,9 +777,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -629,9 +791,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -642,9 +804,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -656,16 +818,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -676,15 +839,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", @@ -695,12 +858,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.1.0" @@ -714,73 +871,29 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", -] - -[[package]] -name = "indoc" -version = "2.0.7" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ - "rustversion", + "icu_normalizer", + "icu_properties", ] [[package]] -name = "instability" -version = "0.3.13" +name = "indexmap" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ - "darling", - "indoc", - "proc-macro2", - "quote", - "syn 3.0.3", + "equivalent", + "hashbrown", ] [[package]] name = "ipnet" -version = "2.12.1" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" - -[[package]] -name = "ira" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "clap", - "crossterm", - "flate2", - "futures-util", - "ratatui", - "reqwest", - "serde", - "serde_json", - "serde_yaml", - "tiny_http", - "tokio", - "tokio-stream", - "tokio-util", - "zip", -] +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "is_terminal_polyfill" @@ -788,15 +901,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.18" @@ -805,9 +909,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.104" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ "cfg-if", "futures-util", @@ -815,52 +919,51 @@ dependencies = [ ] [[package]] -name = "libc" -version = "0.2.189" +name = "landlock" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "4cca98e95f35b29d469dade6724c6f96cec9236640f745a0e99b0334ec320ab1" +dependencies = [ + "enumflags2", + "libc", + "thiserror 2.0.21", +] [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "lazy_static" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" [[package]] -name = "litemap" -version = "0.8.2" +name = "libc" +version = "0.2.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" [[package]] -name = "lock_api" -version = "0.4.14" +name = "linux-raw-sys" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] -name = "log" -version = "0.4.33" +name = "litemap" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] -name = "lru" -version = "0.12.5" +name = "log" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.5", -] +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lru-slab" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" [[package]] name = "memchr" @@ -868,68 +971,55 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - [[package]] name = "mio" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", - "log", "wasi", "windows-sys 0.61.2", ] [[package]] -name = "once_cell" -version = "1.21.4" +name = "multiversion_no_op" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" [[package]] -name = "once_cell_polyfill" -version = "1.70.2" +name = "nix" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases 0.1.1", + "libc", +] [[package]] -name = "parking_lot" -version = "0.12.5" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "lock_api", - "parking_lot_core", + "autocfg", ] [[package]] -name = "parking_lot_core" -version = "0.9.12" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "paste" -version = "1.0.15" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "percent-encoding" @@ -943,11 +1033,32 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "portable-pty" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "downcast-rs", + "filedescriptor", + "lazy_static", + "libc", + "log", + "nix", + "serial2", + "shared_library", + "shell-words", + "winapi", + "winreg", +] + [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -963,19 +1074,19 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" dependencies = [ "bytes", - "cfg_aliases", + "cfg_aliases 0.2.2", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 2.0.21", "tokio", "tracing", "web-time", @@ -983,9 +1094,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.16" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" dependencies = [ "bytes", "getrandom 0.4.3", @@ -997,7 +1108,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.21", "tinyvec", "tracing", "web-time", @@ -1005,11 +1116,11 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" dependencies = [ - "cfg_aliases", + "cfg_aliases 0.2.2", "libc", "once_cell", "socket2", @@ -1034,9 +1145,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ "chacha20", "getrandom 0.4.3", @@ -1059,45 +1170,44 @@ dependencies = [ ] [[package]] -name = "ratatui" -version = "0.29.0" +name = "regex" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ - "bitflags", - "cassowary", - "compact_str", - "crossterm", - "indoc", - "instability", - "itertools", - "lru", - "paste", - "strum", - "unicode-segmentation", - "unicode-truncate", - "unicode-width 0.2.0", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "redox_syscall" -version = "0.5.18" +name = "regex-automata" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ - "bitflags", + "aho-corasick", + "memchr", + "regex-syntax", ] +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + [[package]] name = "reqwest" version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", - "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -1117,14 +1227,12 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", - "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", "webpki-roots", ] @@ -1151,22 +1259,22 @@ checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustix" -version = "0.38.44" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.43" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "once_cell", "ring", @@ -1188,9 +1296,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -1215,6 +1323,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "seccompiler" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae55de56877481d112a559bbc12667635fdaf5e005712fd4e2b2fa50ffc884" +dependencies = [ + "libc", +] + [[package]] name = "serde" version = "1.0.229" @@ -1225,6 +1342,20 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-saphyr" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8050abb251097357e24aff63ba2c52a6309ecb7d23a5474023df960a02694d8" +dependencies = [ + "annotate-snippets", + "encoding_rs_io", + "granit-parser", + "num-traits", + "serde_core", + "smallvec", +] + [[package]] name = "serde_core" version = "1.0.229" @@ -1242,14 +1373,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", @@ -1271,44 +1402,37 @@ dependencies = [ ] [[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" +name = "serial2" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +checksum = "b16809bc35793b19ce4e0c53924bc0dce3937f15487997cfdaed936004180730" dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", + "cfg-if", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "signal-hook" -version = "0.3.18" +name = "shared_library" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" dependencies = [ + "lazy_static", "libc", - "signal-hook-registry", ] [[package]] -name = "signal-hook-mio" -version = "0.2.5" +name = "shell-words" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" -dependencies = [ - "libc", - "mio", - "signal-hook", -] +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -1321,10 +1445,10 @@ dependencies = [ ] [[package]] -name = "simd-adler32" -version = "0.3.10" +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "slab" @@ -1334,9 +1458,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" [[package]] name = "socket2" @@ -1354,40 +1478,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.119", -] - [[package]] name = "subtle" version = "2.6.1" @@ -1407,9 +1503,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", @@ -1427,52 +1523,73 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.21", ] [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.119", ] [[package]] -name = "tiny_http" -version = "0.12.0" +name = "thiserror-impl" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", + "proc-macro2", + "quote", + "syn 3.0.9", ] [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -1480,24 +1597,15 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" -version = "1.53.1" +version = "1.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" dependencies = [ "bytes", "libc", @@ -1517,39 +1625,29 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.9", ] [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ "rustls", "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -1575,7 +1673,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags", + "bitflags 2.13.2", "bytes", "futures-util", "http", @@ -1626,44 +1724,15 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-truncate" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" -dependencies = [ - "itertools", - "unicode-segmentation", - "unicode-width 0.1.14", -] - -[[package]] -name = "unicode-width" -version = "0.1.14" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] name = "unicode-width" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "untrusted" @@ -1697,9 +1766,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "want" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" dependencies = [ "try-lock", ] @@ -1712,9 +1781,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -1725,19 +1794,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.77" +version = "0.4.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" dependencies = [ "js-sys", + "tokio", "wasm-bindgen", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1745,44 +1815,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.127" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" -version = "0.3.104" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" dependencies = [ "js-sys", "wasm-bindgen", @@ -1917,11 +1974,20 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "yoke" @@ -1936,13 +2002,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", "synstructure", ] @@ -1957,27 +2023,27 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", "synstructure", ] [[package]] name = "zeroize" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -1986,9 +2052,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -1997,30 +2063,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "zip" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" -dependencies = [ - "arbitrary", - "crc32fast", - "crossbeam-utils", - "displaydoc", - "flate2", - "indexmap", - "memchr", - "thiserror", - "zopfli", + "syn 3.0.9", ] [[package]] @@ -2028,15 +2077,3 @@ name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - -[[package]] -name = "zopfli" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..df75fb10 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,48 @@ +[workspace] +resolver = "2" +members = ["crates/*"] + +[workspace.package] +version = "3.0.0" +edition = "2021" +rust-version = "1.89" +publish = false + +[workspace.lints.rust] +unsafe_code = "forbid" + +[workspace.lints.clippy] +dbg_macro = "warn" +todo = "warn" + +[workspace.dependencies] +gitagent-cli = { path = "crates/cli" } +gitagent-context = { path = "crates/context" } +gitagent-core = { path = "crates/core" } +gitagent-guards = { path = "crates/guards" } +gitagent-hooks = { path = "crates/hooks" } +gitagent-learn = { path = "crates/learn" } +gitagent-loader = { path = "crates/loader" } +gitagent-mcp = { path = "crates/mcp" } +gitagent-provider = { path = "crates/provider" } +gitagent-record = { path = "crates/record" } +gitagent-repo = { path = "crates/repo" } +gitagent-sandbox = { path = "crates/sandbox" } +gitagent-sdk = { path = "crates/sdk" } +gitagent-shaper = { path = "crates/shaper" } +gitagent-terminal = { path = "crates/terminal" } +gitagent-tools = { path = "crates/tools" } + +anyhow = "1.0.104" +async-trait = "0.1.92" +clap = { version = "4.6.7", features = ["derive", "env"] } +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" +thiserror = "2.0.21" +tokio = { version = "1.53.1", features = ["rt-multi-thread", "macros", "time", "sync", "process", "fs", "io-util", "io-std", "signal"] } + +[profile.release] +lto = "thin" +codegen-units = 1 +strip = true +panic = "abort" diff --git a/Documentation.md b/Documentation.md deleted file mode 100644 index c7959662..00000000 --- a/Documentation.md +++ /dev/null @@ -1,1179 +0,0 @@ -# GitAgent Documentation - -> **GitAgent** — A universal git-native multimodal always-learning AI Agent -> Version 1.3.3 | MIT License | [github.com/open-gitagent/gitagent](https://github.com/open-gitagent/gitagent) - ---- - -## Table of Contents - -- [Quick Start](#quick-start) -- [Installation](#installation) -- [CLI Reference](#cli-reference) -- [Agent Configuration (agent.yaml)](#agent-configuration) -- [Models & Providers](#models--providers) -- [Voice Mode](#voice-mode) -- [Web UI](#web-ui) -- [Built-in Tools](#built-in-tools) -- [Skills](#skills) -- [Workflows & SkillFlows](#workflows--skillflows) -- [Hooks](#hooks) -- [Plugins](#plugins) -- [Memory System](#memory-system) -- [Schedules & Cron](#schedules--cron) -- [Integrations](#integrations) -- [Compliance & Audit](#compliance--audit) -- [SDK (Programmatic Usage)](#sdk) -- [Context Compaction](#context-compaction) -- [Cost Tracking](#cost-tracking) -- [Security & Password Protection](#security--password-protection) -- [Directory Structure](#directory-structure) -- [Environment Variables](#environment-variables) - ---- - -## Quick Start - -```bash -# One-line install & launch -curl -fsSL https://raw.githubusercontent.com/open-gitagent/gitagent/main/install.sh | bash -``` - -This installs GitAgent globally via npm, walks you through setup (API keys, voice adapter, model), and launches the web UI at `http://localhost:3333`. - ---- - -## Installation - -### Requirements - -- **Node.js 20+** (required by WhatsApp dependency) -- **Git** (for memory commits and session branches) -- **npm** (included with Node.js) - -### Install Methods - -**Interactive installer (recommended):** -```bash -curl -fsSL https://raw.githubusercontent.com/open-gitagent/gitagent/main/install.sh | bash -``` - -**Manual install:** -```bash -npm install -g @open-gitagent/gitagent @open-gitagent/voice # omit @open-gitagent/voice for slim CLI/SDK only -mkdir ~/assistant && cd ~/assistant && git init -gitagent --voice --dir . -``` - -### Setup Modes - -The installer offers four options: - -| Mode | Description | Keys Required | -|------|-------------|---------------| -| **Install with LYZR** | Easiest — uses Lyzr AI Studio cloud | `LYZR_API_KEY` | -| **Voice + Text** | Real-time voice + text chat | `OPENAI_API_KEY` + `ANTHROPIC_API_KEY` | -| **Text Only** | Browser text chat, no voice | `ANTHROPIC_API_KEY` | -| **Advanced Setup** | Choose voice adapter, model, port, integrations | varies | - -### Updating - -```bash -# The installer auto-detects existing installations and offers to update -curl -fsSL https://raw.githubusercontent.com/open-gitagent/gitagent/main/install.sh | bash - -# Or manually -npm update -g gitagent -``` - ---- - -## CLI Reference - -### Basic Usage - -```bash -# Launch voice/web UI -gitagent --voice --dir ~/assistant - -# Single-shot query (no REPL) -gitagent --dir ~/assistant "Build a REST API for user management" - -# Interactive REPL -gitagent --dir ~/assistant - -# With specific model -gitagent --model anthropic:claude-opus-4-6 --voice --dir ~/assistant -``` - -### Flags - -| Flag | Short | Description | Default | -|------|-------|-------------|---------| -| `--model` | `-m` | Model to use (`provider:model-id`) | from agent.yaml | -| `--dir` | `-d` | Agent directory | current directory | -| `--prompt` | `-p` | Single-shot prompt | — | -| `--env` | `-e` | Environment config (loads `config/.yaml`) | default | -| `--voice` | `-v` | Enable voice mode (optionally: `openai` or `gemini`) | — | -| `--sandbox` | `-s` | Run in E2B sandbox VM | false | -| `--sandbox-repo` | — | Repository URL for sandbox | — | -| `--sandbox-token` | — | E2B API token | `E2B_API_KEY` env | -| `--repo` | `-r` | Clone and work on remote repository | — | -| `--pat` | — | GitHub/GitLab personal access token | `GITHUB_TOKEN` env | -| `--session` | — | Git branch name for session isolation | auto-generated | - -### REPL Commands - -| Command | Description | -|---------|-------------| -| `/quit` or `/exit` | Exit the session | -| `/memory` | View the memory file | -| `/skills` | List installed skills | -| `/tasks` | Show active learning tasks | -| `/learned` | List learned skills with confidence scores | -| `/plugins` | List loaded plugins | -| `/skill:name args` | Invoke a specific skill | - -### Plugin CLI - -```bash -gitagent plugin install https://github.com/user/plugin-repo -gitagent plugin install ./local/path --name my-plugin --force -gitagent plugin list --dir ~/assistant -gitagent plugin enable my-plugin --dir ~/assistant -gitagent plugin disable my-plugin --dir ~/assistant -gitagent plugin remove my-plugin --dir ~/assistant -gitagent plugin init my-plugin --dir ~/assistant -``` - ---- - -## Agent Configuration - -GitAgent agents are configured via `agent.yaml` in the agent directory. - -### Full Schema - -```yaml -spec_version: "0.1.0" -name: my-agent -version: 1.0.0 -description: A description of what this agent does - -model: - preferred: "anthropic:claude-sonnet-4-6" - fallback: - - "openai:gpt-4o" - - "google:gemini-2.0-flash-001" - constraints: - temperature: 0.7 - max_tokens: 4096 - top_p: 0.9 - top_k: 40 - stop_sequences: ["---"] - -tools: - - cli - - read - - write - - memory - - capture_photo - - task_tracker - - skill_learner - -skills: - - code-review - - deployment - -runtime: - max_turns: 50 - timeout: 300 # seconds per tool call - -# Inheritance (optional) -extends: https://github.com/user/parent-agent.git - -# Dependencies (optional) -dependencies: - - name: shared-skills - source: https://github.com/team/shared-skills - version: main - mount: deps/shared - -# Sub-agents (optional) -agents: - researcher: - model: "anthropic:claude-haiku-4-5-20251001" - tools: [read, cli] - -delegation: - mode: auto # auto | explicit | router - -# Plugins (optional) -plugins: - my-plugin: - enabled: true - config: - api_key: "${MY_PLUGIN_KEY}" - -# Compliance (optional — for enterprise) -compliance: - risk_level: high - human_in_the_loop: true - data_classification: "confidential" - regulatory_frameworks: [SOX, GLBA] - recordkeeping: - audit_logging: true - retention_days: 2555 - review: - required_approvers: 2 - auto_review: false - -# Serve mode (optional) -serve: - port: 8080 - allowed_tools: [lookup_account, get_policy] - constraints: - temperature: 0 - max_tokens: 4000 -``` - -### Model Resolution Order - -1. Environment config `model_override` (from `config/.yaml`) -2. CLI flag `--model provider:model-id` -3. `agent.yaml` `model.preferred` - -### Identity Files - -| File | Purpose | -|------|---------| -| `SOUL.md` | Agent personality, identity, core values | -| `RULES.md` | Behavioral constraints and rules | -| `DUTIES.md` | Job responsibilities and tasks | -| `AGENTS.md` | Sub-agent relationships and delegation rules | - ---- - -## Models & Providers - -### Supported Providers - -GitAgent supports any model from the following providers out of the box: - -| Provider | Format | API Key Env Var | -|----------|--------|-----------------| -| Anthropic | `anthropic:claude-sonnet-4-6` | `ANTHROPIC_API_KEY` | -| OpenAI | `openai:gpt-4o` | `OPENAI_API_KEY` | -| Google | `google:gemini-2.0-flash-001` | `GEMINI_API_KEY` | -| Groq | `groq:llama-3.3-70b-versatile` | `GROQ_API_KEY` | -| xAI | `xai:grok-2-1212` | `XAI_API_KEY` | -| Mistral | `mistral:mistral-large-latest` | `MISTRAL_API_KEY` | -| OpenRouter | `openrouter:anthropic/claude-3.5-sonnet` | `OPENROUTER_API_KEY` | -| Cerebras | `cerebras:llama3.1-70b` | `CEREBRAS_API_KEY` | -| DeepSeek | `deepseek:deepseek-chat` | `DEEPSEEK_API_KEY` | -| Amazon Bedrock | `amazon-bedrock:anthropic.claude-3-sonnet` | AWS credentials | -| Google Vertex | `google-vertex:gemini-2.5-flash` | GCP ADC | -| Azure OpenAI | `azure-openai-responses:gpt-4o` | `AZURE_OPENAI_API_KEY` | - -### Custom / OpenAI-Compatible Endpoints - -Any endpoint that implements the OpenAI Chat Completions API: - -**Inline URL:** -```bash -gitagent --model "ollama:llama3@http://localhost:11434/v1" --voice --dir ~/assistant -``` - -**Environment variable:** -```bash -export GITAGENT_MODEL_BASE_URL=http://localhost:11434/v1 -gitagent --model "ollama:llama3" --voice --dir ~/assistant -``` - -**In agent.yaml:** -```yaml -model: - preferred: "custom:my-model@https://my-proxy.com/v1" -``` - -**Supported custom endpoints:** -- Ollama (`http://localhost:11434/v1`) -- LM Studio (`http://localhost:1234/v1`) -- vLLM (`http://localhost:8000/v1`) -- LiteLLM (`http://localhost:4000/v1`) -- Lyzr AI Studio (`https://agent-prod.studio.lyzr.ai/v4/chat`) -- Any OpenAI-compatible proxy - -### Lyzr Integration - -GitAgent integrates with [Lyzr AI Studio](https://studio.lyzr.ai) as an agent brain. The Lyzr completions endpoint is fully OpenAI-compatible. - -**Via installer (easiest):** -```bash -curl -fsSL https://raw.githubusercontent.com/open-gitagent/gitagent/main/install.sh | bash -# Pick option 1: "Install with LYZR" -# Enter your Lyzr API key — agent is created automatically -``` - -**Via CLI flag:** -```bash -export OPENAI_API_KEY="your-lyzr-api-key" # Lyzr uses standard Bearer auth -gitagent --model "lyzr:@https://agent-prod.studio.lyzr.ai/v4" --voice --dir ~/assistant -``` - -**Via SDK (programmatic):** -```typescript -import { query } from "gitagent"; - -// Set OPENAI_API_KEY to your Lyzr API key (uses standard Bearer auth) -process.env.OPENAI_API_KEY = process.env.LYZR_API_KEY; - -const result = query({ - prompt: "Hello! What can you help me with?", - dir: "/path/to/agent", - model: `lyzr:${LYZR_AGENT_ID}@https://agent-prod.studio.lyzr.ai/v4`, - constraints: { temperature: 0.7, maxTokens: 2000 }, -}); - -for await (const msg of result) { - if (msg.type === "assistant") console.log(msg.content); -} -``` - -**How it works:** -- Base URL: `https://agent-prod.studio.lyzr.ai/v4` (OpenAI SDK appends `/chat/completions`) -- Auth: `Authorization: Bearer ` (standard OpenAI-compatible) -- Model field: your Lyzr agent ID (e.g., `69d52b90a011dc91d7877bfd`) -- Full example: `examples/lyzr-sdk.ts` - ---- - -## Voice Mode - -GitAgent supports real-time bidirectional voice via two adapters: - -### OpenAI Realtime (default) - -- Model: `gpt-realtime-2025-08-28` -- Real-time audio streaming over WebSocket -- Supports image input (camera frames) -- Requires: `OPENAI_API_KEY` - -### Gemini Live - -- Model: `gemini-2.0-flash` -- Alternative voice provider -- Free tier available -- Requires: `GEMINI_API_KEY` - -```bash -# OpenAI voice (default) -gitagent --voice --dir ~/assistant - -# Gemini voice -gitagent --voice gemini --dir ~/assistant -``` - -### Text-Only Fallback - -If no voice API key is set, GitAgent still starts the web UI server but with voice disabled. A warning banner appears in the UI, mic/camera/speaker buttons are hidden, and text input routes directly to the agent via `query()`. - -### Camera - -- Front/back camera toggle (mobile) -- Captures frames every 1 second as JPEG -- Frames injected into conversation as images -- Auto-captures on "memorable moments" (laughter, excitement) - ---- - -## Web UI - -The voice server runs at `http://localhost:3333` and provides a full-featured web interface. - -### Tabs - -| Tab | Features | -|-----|----------| -| **Chat** | Real-time conversation, voice controls, camera, agent vitals, file system viewer | -| **Skills** | Browse and install skills from the marketplace | -| **Integrations** | Connect Composio services (Gmail, Calendar, Slack, GitHub) | -| **Communication** | Telegram bot setup, WhatsApp connection, phone/SMS webhook | -| **SkillFlows** | Visual workflow builder — chain skills into multi-step flows | -| **Scheduler** | Create cron jobs — run prompts on a schedule | -| **Settings** | Model selection, API keys, custom base URL — saves to `.env` and `agent.yaml` | - -### Agent Vitals - -Real-time metrics displayed in the Chat tab: -- **CPU** — Delta-based percentage (blue) -- **Memory** — RSS in MB (orange) -- **Tokens** — Total tokens used in session (purple) -- **Uptime** — Server uptime synced from backend (green) -- **Pulse** — CPU wave visualization - -### Mobile Responsive - -The UI is responsive under 700px: -- Tabs become a scrollable horizontal strip -- Camera panel stacks vertically -- Controls have 44px touch targets -- Sidebar overlays instead of pushing content -- All views stack vertically - ---- - -## Built-in Tools - -| Tool | Description | Concurrency Safe | Read Only | -|------|-------------|-----------------|-----------| -| `cli` | Run shell commands | No | No | -| `read` | Read file contents | Yes | Yes | -| `write` | Create/write files | No | No | -| `memory` | Load/save persistent memory | No | No | -| `capture_photo` | Capture camera frame as photo | No | No | -| `task_tracker` | Track task progress, search skills | No | No | -| `skill_learner` | Save/evaluate learned skills | No | No | - -### CLI Tool - -``` -Command: ls -la src/ -Timeout: 120s (configurable) -Output: stdout + stderr (truncated to ~100KB) -``` - -### Read Tool - -``` -Path: src/index.ts -Encoding: utf-8 (default) or base64 -Partial reads: start/end byte offsets -``` - -### Write Tool - -``` -Path: workspace/report.md -Content: "# Report\n..." -Append: false (default) — overwrites -Auto-creates parent directories -``` - -### Memory Tool - -- **load** — Returns current `memory/MEMORY.md` content -- **save** — Appends entry + git commits -- Supports layered memory via `memory.yaml` -- Auto-archives when `max_lines` exceeded (to `memory/archive/.md`) - -### Declarative Tools (Custom) - -Define tools in `tools/*.yaml`: - -```yaml -name: lookup-account -description: Look up account details by customer ID -input_schema: - properties: - customer_id: - type: string - description: The customer ID - required: [customer_id] -implementation: - script: scripts/lookup.sh - runtime: sh -``` - -The script receives JSON args on stdin and outputs plain text. - ---- - -## Skills - -Skills are reusable instruction sets that the agent follows for specific tasks. - -### Creating a Skill - -Create `skills//SKILL.md`: - -```markdown ---- -name: code-review -description: Review code for bugs, style, and security issues -license: MIT -allowed-tools: cli read write -metadata: - author: your-name - version: "1.0.0" - category: development ---- - -# Code Review - -## Instructions - -1. Read the specified file(s) using the read tool -2. Analyze for: - - Bugs and logic errors - - Security vulnerabilities (OWASP top 10) - - Code style and readability - - Performance issues -3. Write a review report to workspace/review.md - -## Output Format - -For each issue found: -- **File**: path -- **Line**: number -- **Severity**: critical / warning / info -- **Description**: what's wrong -- **Fix**: suggested change -``` - -### Invoking Skills - -```bash -# In REPL -/skill:code-review Review the auth module - -# In voice/text -"Use the code-review skill on src/auth.ts" -``` - -### Skill Learning - -The agent can learn new skills automatically: - -1. `task_tracker` begins tracking a task -2. Agent completes the task successfully -3. `skill_learner` evaluates if the approach is worth saving -4. If yes, crystallizes it as a new skill with `confidence: 0.7` -5. Future tasks search for matching skills -6. Confidence adjusts based on success/failure outcomes - ---- - -## Workflows & SkillFlows - -### Basic Workflow (reference) - -`workflows/cleanup.md`: -```markdown ---- -name: cleanup -description: Clean up temporary files ---- - -# Cleanup Workflow -Remove temp files and rebuild. -``` - -### SkillFlow (executable multi-step) - -`workflows/data-pipeline.yaml`: -```yaml -name: data-pipeline -description: Process data through validation, transformation, and storage -steps: - - skill: validate-input - prompt: "Validate the CSV data format" - - - skill: __approval_gate__ - prompt: "Data validation complete. Approve to continue?" - channel: telegram - - - skill: transform-data - prompt: "Transform to required schema" - - - skill: save-to-database - prompt: "Store results" -``` - -### Approval Gates - -Steps with `skill: __approval_gate__` pause execution and send an approval request via the specified channel (Telegram, WhatsApp). The user has 5 minutes to approve before timeout. - ---- - -## Hooks - -Hooks intercept agent lifecycle events for validation, logging, and control. - -### Configuration - -`hooks/hooks.yaml`: -```yaml -hooks: - on_session_start: - - script: hooks/check-auth.sh - description: "Verify user authorization" - - pre_tool_use: - - script: hooks/validate-command.sh - description: "Block dangerous CLI commands" - - post_tool_failure: - - script: hooks/notify-error.sh - - post_response: - - script: hooks/log-response.sh - - pre_query: - - script: hooks/rate-limit.sh - - file_changed: - - script: hooks/track-changes.sh - - on_error: - - script: hooks/incident-report.sh -``` - -### Hook Events - -| Event | When | Can Block | Can Modify Args | -|-------|------|-----------|----------------| -| `on_session_start` | Before agent runs | Yes | No | -| `pre_tool_use` | Before each tool call | Yes | Yes | -| `post_tool_failure` | After a tool errors | No | No | -| `pre_query` | Before LLM call | Yes | No | -| `post_response` | After LLM responds | No | No | -| `file_changed` | After file write | No | No | -| `on_error` | On agent error | No | No | - -### Hook Script Format - -Scripts receive JSON on stdin and output JSON on stdout: - -**Input:** -```json -{"event": "pre_tool_use", "session_id": "uuid", "tool": "cli", "args": {"command": "rm -rf /"}} -``` - -**Output:** -```json -{"action": "block", "reason": "Destructive command blocked"} -``` - -**Actions:** `allow`, `block`, `modify` (with `args` field for modified arguments) - -### Programmatic Hooks (SDK) - -```typescript -const result = query({ - hooks: { - preToolUse: async (ctx) => { - if (ctx.toolName === "cli" && ctx.args.command.includes("rm")) { - return { action: "block", reason: "Blocked rm command" }; - } - return { action: "allow" }; - }, - }, -}); -``` - ---- - -## Plugins - -Plugins extend GitAgent with tools, skills, hooks, memory layers, and prompt additions. - -### Plugin Manifest - -`plugins/my-plugin/plugin.yaml`: -```yaml -id: my-plugin -name: My Plugin -version: 1.0.0 -description: What this plugin does -author: Your Name -license: MIT -engine: ">=1.0.0" - -provides: - tools: true - skills: true - prompt: prompt.md - hooks: - pre_tool_use: - - script: hooks/validate.sh - -memory: - - name: plugin-data - path: memory/plugin-data.md - max_lines: 500 -``` - -### Plugin Structure - -``` -plugins/my-plugin/ - plugin.yaml # manifest - prompt.md # appended to system prompt - tools/ - my-tool.yaml # declarative tools - skills/ - my-skill/ - SKILL.md - hooks/ - validate.sh -``` - -### Plugin Management - -```bash -gitagent plugin install https://github.com/user/plugin -gitagent plugin list -gitagent plugin remove my-plugin -gitagent plugin init my-plugin # scaffold a new plugin -``` - ---- - -## Memory System - -GitAgent's memory is git-native — all memory changes are committed, versioned, and auditable. - -### Memory File - -`memory/MEMORY.md` — the primary memory file, loaded into every conversation. - -### Memory Layers - -Configure in `memory/memory.yaml`: -```yaml -layers: - - name: main - path: memory/MEMORY.md - max_lines: 200 - - name: technical - path: memory/technical.md - max_lines: 100 -``` - -### Auto-Archiving - -When a layer exceeds `max_lines`, old entries are moved to `memory/archive/.md`. - -### Additional Memory Features - -| Feature | Location | Description | -|---------|----------|-------------| -| **Mood log** | `memory/mood.md` | Session mood tracking (happy, frustrated, curious, excited, calm) | -| **Photos** | `memory/photos/` | Captured memorable moments with INDEX.md | -| **Journal** | `memory/journal/.md` | Auto-generated session reflections | -| **Learning** | `.gitagent/learning/` | Task history and learned skills (JSON) | - -### Memory Detection - -The agent automatically detects and saves personal information from voice transcripts: -- Names, preferences, locations -- Job titles, responsibilities -- Important dates, relationships - ---- - -## Schedules & Cron - -Schedule recurring or one-time tasks. - -### Schedule Definition - -`schedules/daily-standup.yaml`: -```yaml -id: daily-standup -prompt: "Summarize git commits from the last 24 hours and list open tasks" -cron: "0 9 * * 1-5" -mode: repeat -enabled: true -``` - -### One-Time Schedule - -```yaml -id: quarterly-review -prompt: "Generate Q1 performance report" -mode: once -runAt: "2026-04-01T09:00:00Z" -enabled: true -``` - -### Cron Patterns - -| Pattern | Meaning | -|---------|---------| -| `0 9 * * 1-5` | Weekdays at 9 AM | -| `0 9 * * 1` | Every Monday at 9 AM | -| `0 9 1 * *` | First of month at 9 AM | -| `0 9 1 */3 *` | Quarterly | -| `*/30 * * * *` | Every 30 minutes | - -### Managing via UI - -The **Scheduler** tab in the web UI lets you create, edit, enable/disable, trigger, and delete schedules. - ---- - -## Integrations - -### Composio (Gmail, Calendar, Slack, GitHub) - -Requires: `COMPOSIO_API_KEY` - -Enables 200+ integrations via Composio's toolkit system. Configure in the **Integrations** tab. - -### Telegram - -Requires: `TELEGRAM_BOT_TOKEN` - -- Create a bot via [@BotFather](https://t.me/botfather) -- Enter token in the **Communication** tab or during setup -- Configure allowed users for access control -- Files generated by the agent are auto-sent to Telegram - -### WhatsApp - -Uses the Baileys library (no phone number API needed): -- Connect via QR code in the **Communication** tab -- Session persists across restarts -- Auto-responds to messages from your number - -### Phone / SMS (Twilio) - -Configure a Twilio webhook pointing to: -``` -https://your-server:3333/api/phone/webhook -``` - ---- - -## Compliance & Audit - -### Compliance Configuration - -In `agent.yaml`: -```yaml -compliance: - risk_level: critical # low | medium | high | critical - human_in_the_loop: true - data_classification: "PCI-DSS" - regulatory_frameworks: [SOX, GLBA, OCC] - recordkeeping: - audit_logging: true - retention_days: 2555 # 7 years for banking - review: - required_approvers: 2 - auto_review: false -``` - -### Validation Rules - -| Rule | Condition | Severity | -|------|-----------|----------| -| `high_risk_hitl` | High/critical risk without `human_in_the_loop` | warning | -| `critical_audit` | Critical risk without `audit_logging` | **error (blocks startup)** | -| `regulatory_recordkeeping` | Regulatory frameworks without recordkeeping | warning | -| `high_risk_review` | High/critical risk without review config | warning | -| `audit_retention` | Audit logging without `retention_days` | warning | - -### Audit Log - -When `audit_logging: true`, all actions are logged to `.gitagent/audit.jsonl`: - -```json -{"timestamp":"2026-01-15T14:23:45Z","session_id":"uuid","event":"session_start"} -{"timestamp":"2026-01-15T14:23:46Z","session_id":"uuid","event":"tool_use","tool":"cli","args":{"command":"ls"}} -{"timestamp":"2026-01-15T14:23:47Z","session_id":"uuid","event":"tool_result","tool":"cli","result":"file.txt"} -{"timestamp":"2026-01-15T14:23:48Z","session_id":"uuid","event":"response"} -{"timestamp":"2026-01-15T14:23:49Z","session_id":"uuid","event":"session_end"} -``` - ---- - -## SDK - -GitAgent can be used programmatically as an npm package. - -### Installation - -```bash -npm install gitagent -``` - -### Basic Usage - -```typescript -import { query } from "gitagent"; - -const result = query({ - prompt: "Create a Python script that sorts a CSV file by the 'date' column", - dir: "/path/to/agent", -}); - -for await (const msg of result) { - if (msg.type === "assistant") { - console.log(msg.content); - } - if (msg.type === "tool_use") { - console.log(`Using tool: ${msg.toolName}`); - } -} - -// Get cost breakdown -console.log(result.costs()); -``` - -### Custom Tools - -```typescript -import { query, tool } from "gitagent"; - -const weatherTool = tool( - "get_weather", - "Get current weather for a city", - { properties: { city: { type: "string" } }, required: ["city"] }, - async (args) => { - const res = await fetch(`https://api.weather.com/${args.city}`); - return await res.text(); - } -); - -const result = query({ - prompt: "What's the weather in Tokyo?", - dir: "/path/to/agent", - tools: [weatherTool], -}); -``` - -### buildTool Factory - -```typescript -import { buildTool } from "gitagent"; - -const myTool = buildTool({ - name: "search_docs", - description: "Search documentation", - parameters: { properties: { query: { type: "string" } }, required: ["query"] }, - execute: async (args) => { - // ... search logic - return "Results: ..."; - }, - metadata: { - isConcurrencySafe: true, // safe to run in parallel - isReadOnly: true, // no side effects - maxResultSizeChars: 20000, // truncate large results - }, -}); -``` - -### Hooks - -```typescript -const result = query({ - prompt: "Deploy to production", - dir: "/path/to/agent", - hooks: { - onSessionStart: async (ctx) => ({ action: "allow" }), - preToolUse: async (ctx) => { - if (ctx.toolName === "cli" && ctx.args.command.includes("deploy")) { - console.log("Deployment detected — requiring approval"); - return { action: "block", reason: "Manual approval required" }; - } - return { action: "allow" }; - }, - postResponse: async (ctx) => { - console.log(`Session ${ctx.sessionId} responded`); - }, - onError: async (ctx) => { - console.error(`Error in session ${ctx.sessionId}: ${ctx.error}`); - }, - }, -}); -``` - -### Query Options - -```typescript -query({ - prompt: "...", // string or AsyncIterable - dir: "/path/to/agent", // agent directory - model: "anthropic:claude-opus-4-6", // override model - env: "production", // load config/production.yaml - systemPrompt: "Custom prompt...", // replace system prompt - systemPromptSuffix: "Extra context...", // append to system prompt - tools: [myTool], // inject custom tools - replaceBuiltinTools: true, // disable built-in tools - allowedTools: ["read", "write"], // whitelist - disallowedTools: ["cli"], // blacklist - maxTurns: 10, // limit agent turns - constraints: { temperature: 0 }, // model constraints - sessionId: "custom-id", // custom session ID - abortController: new AbortController(), // cancel execution -}); -``` - ---- - -## Context Compaction - -Utilities for managing context window limits in long conversations. - -```typescript -import { estimateTokens, estimateMessageTokens, needsCompaction, truncateToolResults, buildCompactPrompt } from "gitagent"; - -// Estimate tokens -const tokens = estimateTokens("Hello world"); // ~3 - -// Check if compaction needed (triggers at 75% of context window) -const { needed, ratio } = needsCompaction(messages, 200000); - -// Truncate oversized tool results (keeps first + last half) -const trimmed = truncateToolResults(messages, 10000); - -// Build a summarization prompt -const prompt = buildCompactPrompt(messages); -``` - ---- - -## Cost Tracking - -Track token usage and costs per model across sessions. - -```typescript -import { CostTracker } from "gitagent"; - -const tracker = new CostTracker(); - -// Automatically tracked when using query() -const result = query({ prompt: "...", dir: "..." }); -for await (const msg of result) { /* ... */ } - -const costs = result.costs(); -// { -// totalCostUsd: 0.05, -// totalInputTokens: 5000, -// totalOutputTokens: 2000, -// totalRequests: 3, -// modelUsage: { -// "anthropic:claude-sonnet-4-6": { inputTokens: 5000, ... } -// } -// } -``` - ---- - -## Security & Password Protection - -### Password Protection - -Set `GITAGENT_PASSWORD` to require authentication for the web UI: - -```bash -GITAGENT_PASSWORD=mysecret gitagent --voice --dir ~/assistant -``` - -When set: -- All HTTP routes show a login page instead of the UI -- WebSocket connections are rejected without valid auth cookie -- `/health` endpoint remains open (for load balancers) -- Cookie: `HttpOnly`, `SameSite=Strict`, 24-hour expiry -- Token is SHA-256 hash (password never stored in cookie) - -### Best Practices - -- Use HTTPS in production (via nginx, Caddy, or Cloudflare Tunnel) -- Set `GITAGENT_PASSWORD` when exposing to a network -- Use OpenShell for kernel-level sandboxing in enterprise deployments -- Enable audit logging for compliance (`compliance.recordkeeping.audit_logging: true`) - ---- - -## Directory Structure - -``` -~/assistant/ # Agent root (git repo) -├── agent.yaml # Agent manifest -├── SOUL.md # Agent identity -├── RULES.md # Behavior rules (optional) -├── DUTIES.md # Responsibilities (optional) -├── .env # API keys (gitignored) -├── .gitignore -│ -├── workspace/ # Output directory -│ -├── memory/ # Persistent memory -│ ├── MEMORY.md # Main memory -│ ├── mood.md # Mood tracking -│ ├── photos/ # Captured moments -│ │ └── INDEX.md -│ ├── journal/ # Session reflections -│ └── archive/ # Archived entries -│ -├── skills/ # Installed skills -│ └── skill-name/ -│ └── SKILL.md -│ -├── workflows/ # SkillFlows -│ └── pipeline.yaml -│ -├── schedules/ # Cron jobs -│ └── daily-standup.yaml -│ -├── hooks/ # Lifecycle hooks -│ ├── hooks.yaml -│ └── validate.sh -│ -├── tools/ # Custom declarative tools -│ └── my-tool.yaml -│ -├── plugins/ # Installed plugins -│ └── plugin-id/ -│ └── plugin.yaml -│ -├── config/ # Environment configs -│ ├── default.yaml -│ └── production.yaml -│ -├── knowledge/ # Knowledge base -│ └── domain.md -│ -├── compliance/ # Compliance config -│ ├── regulatory-map.yaml -│ └── validation-schedule.yaml -│ -└── .gitagent/ # Internal state (gitignored) - ├── state.json - ├── audit.jsonl - └── learning/ - ├── tasks.json - └── skills.json -``` - ---- - -## Environment Variables - -| Variable | Description | Required | -|----------|-------------|----------| -| `OPENAI_API_KEY` | OpenAI API key (voice mode) | For voice | -| `ANTHROPIC_API_KEY` | Anthropic API key (agent brain) | For Anthropic models | -| `GEMINI_API_KEY` | Google Gemini key | For Gemini voice/models | -| `COMPOSIO_API_KEY` | Composio integrations | Optional | -| `TELEGRAM_BOT_TOKEN` | Telegram bot token | Optional | -| `LYZR_API_KEY` | Lyzr AI Studio key | For Lyzr setup | -| `GITAGENT_LYZR_AGENT_ID` | Lyzr agent ID (auto-created) | For Lyzr setup | -| `GITAGENT_MODEL_BASE_URL` | Custom LLM endpoint URL | Optional | -| `GITAGENT_PASSWORD` | Password-protect the web UI | Optional | -| `GITAGENT_ENV` | Environment name (loads config/.yaml) | Optional | -| `GROQ_API_KEY` | Groq API key | For Groq models | -| `XAI_API_KEY` | xAI/Grok key | For xAI models | -| `MISTRAL_API_KEY` | Mistral key | For Mistral models | -| `OPENROUTER_API_KEY` | OpenRouter key | For OpenRouter | -| `DEEPSEEK_API_KEY` | DeepSeek key | For DeepSeek models | -| `E2B_API_KEY` | E2B sandbox key | For sandbox mode | -| `GITHUB_TOKEN` | GitHub PAT | For --repo mode | - ---- - -*Built with love by the GitAgent team. MIT License.* diff --git a/GitAgent_Redesigned.pptx b/GitAgent_Redesigned.pptx deleted file mode 100644 index 7fa214c0..00000000 Binary files a/GitAgent_Redesigned.pptx and /dev/null differ diff --git a/README.md b/README.md index 7d0257fb..620feacc 100644 --- a/README.md +++ b/README.md @@ -1,921 +1,666 @@

- GitAgent Logo + gitagent

+

gitagent

+

- npm version - node version - license - typescript + The agent that lives in a git repository.
+ One static binary. Its identity, rules, memory, skills, tools and schedules are files you commit.

-

Gitagent

-

- A universal git-native multimodal always learning AI Agent (TinyHuman)
- Your agent lives inside a git repo — identity, rules, memory, tools, and skills are all version-controlled files. + Terminal-Bench 2.1: 93.03% ±0.8 + version 3.0.0 + Rust + license

- Install • - Quick Start • - SDK • + Why • + Install • + Use • + CLI • + Results • + How it works • Architecture • - Tools • - Hooks • - Skills • - Plugins + SDKs • + Troubleshooting

---- +

+ A gitagent session in the terminal +

-## Why Gitagent? +**Terminal-Bench 2.1: 93.03% ±0.8** with Claude Opus 5.5, all 89 tasks, five runs each, code frozen at the published commit; the ± is the run-to-run variation, as on the official leaderboard. The published harnesses on the same benchmark: Terminus 2 with the same model 87.6%, Codex CLI with GPT-6 Astra 87.4%, Claude Code with Claude Fable 5 83.8%, and the highest published entry, Artificial Analysis's own harness with Claude Fable 5.1 at maximum effort, 91.4%. The numbers are under [Results](#results); the reason is under [How gitagent works](#how-gitagent-works). -Most agent frameworks treat configuration as code scattered across your application. Gitagent flips this — **your agent IS a git repository**: +## Why gitagent -- **`agent.yaml`** — model, tools, runtime config -- **`SOUL.md`** — personality and identity -- **`RULES.md`** — behavioral constraints -- **`memory/`** — git-committed memory with full history -- **`tools/`** — declarative YAML tool definitions -- **`skills/`** — composable skill modules -- **`hooks/`** — lifecycle hooks (script or programmatic) +Most agent frameworks treat the agent as code scattered across your application. gitagent flips this: **your agent is a git repository**. -Fork an agent. Branch a personality. `git log` your agent's memory. Diff its rules. This is **agents as repos**. +- **`agent.yaml`**: model, tools, runtime settings, compliance +- **`SOUL.md`**: who the agent is +- **`RULES.md`**: what it must never do +- **`memory/`**: what it remembers, committed to git with full history +- **`skills/`**: instructions it can follow on demand +- **`tools/`**: tools declared in YAML, backed by any script +- **`hooks/`**: scripts that check or change every action +- **`schedules/`**, **`workflows/`**, **`agents/`**: when it works on its own, step-by-step procedures, sub-agents it delegates to -## One-Command Install +Fork an agent. Branch a personality. `git log` its memory. Diff its rules. Review a change to its behaviour like a code change. Every result traces back to a commit. -Copy, paste, run. That's it — no cloning, no manual setup. The installer handles everything: +**gitagent 3** is the Rust rewrite: one static binary with no runtime to install, the harness that scored 93.03% on Terminal-Bench 2.1, a console in your terminal, parallel sub-agents with a live task board, and SDKs for Python, TypeScript and any other language over a simple line protocol. Agents, hooks, schedules and SDK code written for the TypeScript gitagent (1.x and 2.x) run unchanged, and agent folders in the OpenGAP format load too. See [Coming from the TypeScript gitagent](#coming-from-the-typescript-gitagent). -```bash -bash <(curl -fsSL "https://raw.githubusercontent.com/open-gitagent/gitagent/main/install.sh?$(date +%s)") -``` - -This will: -- Install gitagent globally via npm -- Walk you through API key setup (Quick or Advanced mode) -- Launch the voice UI in your browser at `http://localhost:3333` +## Install -> **Requirements:** Node.js 18+, npm, git +```sh +curl -fsSL https://raw.githubusercontent.com/open-gitagent/gitagent/main/scripts/install.sh | sh +``` -### Or install manually: +Or with npm, which also gives you the TypeScript SDK: -```bash -# Slim CLI + SDK (recommended in sandboxed/CI environments where supply-chain -# scanners reject larger bundles) +```sh npm install -g @open-gitagent/gitagent - -# Add voice mode + web UI (the same web UI install.sh launches at :3333) -npm install -g @open-gitagent/voice ``` -`install.sh` installs both packages by default. Set `GITAGENT_SLIM=1` before -the curl-bash to skip voice. +Either way you get the same binary for your system (Linux x86_64/arm64, macOS arm64/x86_64). The script puts it in `~/.local/bin` (set `INSTALL_DIR` to change that); npm installs the package for your platform, `@open-gitagent/gitagent--`, and a `gitagent` command that runs it. To build from source you need Rust (the version is pinned in `rust-toolchain.toml`): -## Migrating from 1.x → 2.0 +```sh +cargo build --release # ./target/release/gitagent +scripts/build-release.sh # static Linux binaries (needs cargo-zigbuild on macOS) +``` -Voice mode lives in `@open-gitagent/voice` now. The reason: as a single bundle, -the package was being blocked by some supply-chain scanners that flagged its -3,800-line `dist/voice/ui.html` and the unused `baileys` dependency. Splitting -voice out drops the slim-core tarball from ~180 kB to ~85 kB and removes the -scanner triggers entirely. +## Use -```bash -# If you were on v1.x and used voice: -npm install -g @open-gitagent/gitagent@latest @open-gitagent/voice - -# If you only use the SDK / non-voice CLI: -npm install -g @open-gitagent/gitagent@latest -``` +Set the key for your model provider, then: -The `gitagent` command and `@open-gitagent/gitagent` SDK exports are unchanged. -`gitagent --voice` dynamically loads `@open-gitagent/voice`; without it -installed, it prints a one-line install hint and exits cleanly. +```sh +export ANTHROPIC_API_KEY=... -## Quick Start +# One task in a folder; prints the agent's summary when done +gitagent -p "fix the failing test" --dir ./project --model anthropic:claude-opus-5-5 -**Run your first agent in one line:** +# Interactive: type tasks one per line; each continues the same session +gitagent --dir ./project --model anthropic:claude-opus-5-5 -```bash -export OPENAI_API_KEY="sk-..." -gitagent --dir ~/my-project "Explain this project and suggest improvements" +# Work in a fresh clone on a new branch, commit when done, push +GITHUB_TOKEN=... gitagent --repo https://github.com/org/app.git -p "fix the failing test" --push ``` -That's it. Gitagent auto-scaffolds everything on first run — `agent.yaml`, `SOUL.md`, `memory/` — and drops you into the agent. +In interactive mode, Ctrl-C stops the current task and Ctrl-D or `exit` quits. -### Local Repo Mode +### CLI options -Clone a GitHub repo, run an agent on it, auto-commit and push to a session branch: +`gitagent [OPTIONS] [PROMPT]`. Without a prompt it opens the console in `--dir`. -```bash -gitagent --repo https://github.com/org/repo --pat ghp_xxx "Fix the login bug" -``` +| Option | What it does | +|---|---| +| `[PROMPT]`, `-p, --prompt ` | The task, as plain words; runs it once and exits | +| `--instruction-file ` | Read the task from a file | +| `-d, --dir ` | The agent folder to work in (default: the current directory) | +| `-m, --model ` | The model; overrides `gitagent.yaml` | +| `-e, --env ` | Apply the agent's `config/.yaml` (default: `GITAGENT_ENV`) | +| `--profile ` | `auto`, a built-in profile, or a profile file | +| `--config ` | A `gitagent.yaml` to use; its `prompts/` and `profiles/` override the built-ins | +| `--conversation` | A plain reply ends the run, instead of `task_complete` (always on in the console) | +| `--max-turns ` | Stop after this many model calls | +| `--timeout-s ` | Time limit for the run. Without one, the console has no limit and `-p` runs stop after 30 minutes | +| `--allowed-tools `, `--disallowed-tools ` | Offer only these tools, or never these | +| `--append-system-prompt ` | Text added after the system prompt | +| `--workflow `, `--input key=value` | Run one of the agent's workflows instead of a task | +| `-r, --repo ` | Work in a clone of the repository on a new branch, and commit the result | +| `--base `, `--branch `, `--session ` | Repo mode: the branch to start from, or an existing branch to resume | +| `--push`, `--pat ` | Repo mode: push the branch after committing; the token to use (default: `GITAGENT_GIT_TOKEN`, `GITHUB_TOKEN` or `GIT_TOKEN`) | +| `--out-dir ` | Where the trajectory, counters and logs go (default: `/tmp/gitagent`) | +| `--session-id ` | Session id recorded in the trajectory | +| `--events jsonl` | Print every step as a JSON object per line | +| `--output-format text \| stream-json` | Print the summary, or the SDK's messages one per line | +| `--ipc` | Serve the IPC protocol for a client library (see [SDKs](#sdks-any-language-over-ipc)) | +| `--bench` | Benchmark mode: built-in configuration only, no memory, no git commits | +| `--linger` | Keep the shells, and anything running in them, open after the run | +| `-V, --version`, `-h, --help` | Version and help | + +Subcommands, each with its own `--help`: + +| Command | What it does | +|---|---| +| `gitagent init [dir]` | Create an agent folder: `agent.yaml`, `SOUL.md`, `memory/`, a git repository | +| `gitagent validate [dir] [--compliance]` | Check an agent folder against the format, with file and line for every problem | +| `gitagent install [dir]` | Fetch the agent's `extends` parent and its `dependencies` | +| `gitagent plugin install\|list\|enable\|disable\|remove\|init` | Manage the agent's plugins | +| `gitagent schedule list\|add\|run\|start\|enable\|disable\|remove` | Manage and run the agent's schedules | +| `gitagent learn --from ` | Learn lessons from finished runs | -Resume an existing session: +In the console, `/help` lists the session's commands, and typing `/` shows them as you type. -```bash -gitagent --repo https://github.com/org/repo --pat ghp_xxx --session gitagent/session-a1b2c3d4 "Continue" -``` +### Models -Token can come from env instead of `--pat`: +`--model provider:model`, or `model:` in `gitagent.yaml`. -```bash -export GITHUB_TOKEN=ghp_xxx -gitagent --repo https://github.com/org/repo "Add unit tests" +| Provider | Example | Key | +|---|---|---| +| Anthropic | `anthropic:claude-opus-5-5` | `ANTHROPIC_API_KEY` | +| OpenAI (Responses API) | `openai:` | `OPENAI_API_KEY` | +| Google Gemini | `google:` | `GEMINI_API_KEY` | +| Any OpenAI-compatible endpoint | `openrouter:`, `ollama:`, `vllm:`, `:` | `{NAME}_API_KEY`; endpoint from `{NAME}_BASE_URL` (OpenRouter, Ollama and vLLM have defaults) | + +The agent's shell never sees these keys. + +### Repo mode + +`--repo ` clones into `--dir` (default: `./`). + +- **Branch:** it works on a new branch `gitagent/-` created from `--base` (default: the remote's default branch). `--branch ` continues an existing branch instead. +- **Commit:** when the agent finishes, it commits with the author from `gitagent.yaml`. Nothing is committed when nothing changed. +- **Push:** only with `--push`. +- **Safety:** it never commits to or pushes the base branch, and never force-pushes. +- **Token:** `GITAGENT_GIT_TOKEN` or `GITHUB_TOKEN` is passed to git through a helper. It is never written to the repository, the logs or the events. + +### Parallel work + +With `runtime: {parallel_tasks: true}` in agent.yaml, the agent gets a +`plan` tool: it writes phases of tasks, every task in a phase runs at the +same time as its own sub-agent (fresh context, own shell), a later phase +sees the earlier results, and one more sub-agent reconciles everything into +the answer. `runtime.max_parallel` (default 4) says how many run at once; +the next task starts as soon as one finishes. Use it for work that splits +into similar pieces: many files to read, many items to check. It costs +tokens in proportion, so give it scoped tasks. + +`max_parallel` is a ceiling. When the model provider refuses a call for +rate limits, the call is retried after a backoff and the number of tasks +running at once is halved; after ten calls in a row go through, one slot +comes back, up to the ceiling. The task board says when this happens +("slowed to 2 at a time after 3 rate limits"), and every run counts its +`rate_limit_retries`. So set `max_parallel` to what you want when the +account allows it, and let gitagent slow down when it does not. + +Each task gets 30 model calls unless the agent gives it its own +`max_turns` (up to 100), which it can size to the task: more for a large +review, fewer for a quick look-up. A task is warned when 80% of its calls +are used and again before its last one, so it hands back a summary rather +than stopping mid-step. One that still runs out shows as "out of turns, +partial" on the task board and is marked so in the results the agent sees. + +```yaml +runtime: + parallel_tasks: true + max_parallel: 8 ``` -### CLI Options +### Schedules -| Flag | Short | Description | -|---|---|---| -| `--dir ` | `-d` | Agent directory (default: cwd) | -| `--repo ` | `-r` | GitHub repo URL to clone and work on | -| `--pat ` | | GitHub PAT (or set `GITHUB_TOKEN` / `GIT_TOKEN`) | -| `--session ` | | Resume an existing session branch | -| `--model ` | `-m` | Override model (e.g. `anthropic:claude-sonnet-4-5-20250929`) | -| `--sandbox` | `-s` | Run in sandbox VM | -| `--prompt ` | `-p` | Single-shot prompt (skip REPL) | -| `--env ` | `-e` | Environment config | - -### SDK - -```typescript -import { query } from "gitagent"; - -// Simple query -for await (const msg of query({ - prompt: "List all TypeScript files and summarize them", - dir: "./my-agent", - model: "openai:gpt-4o-mini", -})) { - if (msg.type === "delta") process.stdout.write(msg.content); - if (msg.type === "assistant") console.log("\n\nDone."); -} +`schedules/*.yaml` in the agent directory are prompts the agent runs on a +timetable, as in gitagent 1.x: -// Local repo mode via SDK -for await (const msg of query({ - prompt: "Fix the login bug", - model: "openai:gpt-4o-mini", - repo: { - url: "https://github.com/org/repo", - token: process.env.GITHUB_TOKEN!, - }, -})) { - if (msg.type === "delta") process.stdout.write(msg.content); -} +```yaml +id: daily-triage +description: Morning ticket triage +cron: "0 8 * * 1-5" # minute hour day month weekday, local time (six fields add seconds) +prompt: | + Review the open tickets and save a summary to memory. +mode: repeat # or once; `runAt: 2026-11-01T09:00:00Z` runs one time at a moment +enabled: true ``` -## SDK - -The SDK provides a programmatic interface to Gitagent agents. It mirrors the [Claude Agent SDK](https://github.com/anthropics/claude-code-sdk) pattern but runs **in-process** — no subprocesses, no IPC. - -### `query(options): Query` - -Returns an `AsyncGenerator` that streams agent events. - -```typescript -import { query } from "gitagent"; - -for await (const msg of query({ - prompt: "Refactor the auth module", - dir: "/path/to/agent", - model: "anthropic:claude-sonnet-4-5-20250929", -})) { - switch (msg.type) { - case "delta": // streaming text chunk - process.stdout.write(msg.content); - break; - case "assistant": // complete response - console.log(`\nTokens: ${msg.usage?.totalTokens}`); - break; - case "tool_use": // tool invocation - console.log(`Tool: ${msg.toolName}(${JSON.stringify(msg.args)})`); - break; - case "tool_result": // tool output - console.log(`Result: ${msg.content}`); - break; - case "system": // lifecycle events & errors - console.log(`[${msg.subtype}] ${msg.content}`); - break; - } -} +```sh +gitagent schedule list -d ./project # with each one's next run +gitagent schedule add nightly --cron "0 2 * * *" --prompt "Tidy the notes." -d ./project +gitagent schedule run nightly -d ./project # once, now +gitagent schedule start -d ./project # keep running; fires each schedule when due +gitagent schedule disable nightly -d ./project # also enable, remove ``` -### `tool(name, description, schema, handler): GCToolDefinition` - -Define custom tools the agent can call: - -```typescript -import { query, tool } from "gitagent"; - -const search = tool( - "search_docs", - "Search the documentation", - { - properties: { - query: { type: "string", description: "Search query" }, - limit: { type: "number", description: "Max results" }, - }, - required: ["query"], - }, - async (args) => { - const results = await mySearchEngine(args.query, args.limit ?? 10); - return { text: JSON.stringify(results), details: { count: results.length } }; - }, -); - -for await (const msg of query({ - prompt: "Find docs about authentication", - tools: [search], -})) { - // agent can now call search_docs -} -``` +`start` runs one job at a time, each as a fresh conversation, logs it to +`.gitagent/schedule-logs/.jsonl` and records `lastRunAt` and +`lastResult` in the schedule's file (a `once` schedule is then turned off). +Like 1.x's in-process scheduler, jobs fire only while `start` is running; +for anything that must not be missed, have an external scheduler call +`gitagent schedule run `. -### Hooks - -Programmatic lifecycle hooks for gating, logging, and control: - -```typescript -for await (const msg of query({ - prompt: "Deploy the service", - hooks: { - preToolUse: async (ctx) => { - // Block dangerous operations - if (ctx.toolName === "cli" && ctx.args.command?.includes("rm -rf")) - return { action: "block", reason: "Destructive command blocked" }; - - // Modify arguments - if (ctx.toolName === "write" && !ctx.args.path.startsWith("/safe/")) - return { action: "modify", args: { ...ctx.args, path: `/safe/${ctx.args.path}` } }; - - return { action: "allow" }; - }, - onError: async (ctx) => { - console.error(`Agent error: ${ctx.error}`); - }, - }, -})) { - // ... -} -``` +## Results -### QueryOptions Reference +

+ Terminal-Bench 2.1 results +

-| Option | Type | Description | -|---|---|---| -| `prompt` | `string \| AsyncIterable` | User prompt or multi-turn stream | -| `dir` | `string` | Agent directory (default: `cwd`) | -| `model` | `string` | `"provider:model-id"` | -| `env` | `string` | Environment config (`config/.yaml`) | -| `systemPrompt` | `string` | Override discovered system prompt | -| `systemPromptSuffix` | `string` | Append to discovered system prompt | -| `tools` | `GCToolDefinition[]` | Additional tools | -| `replaceBuiltinTools` | `boolean` | Skip cli/read/write/memory | -| `allowedTools` | `string[]` | Tool name allowlist | -| `disallowedTools` | `string[]` | Tool name denylist | -| `repo` | `LocalRepoOptions` | Clone a GitHub repo and work on a session branch | -| `sandbox` | `SandboxOptions \| boolean` | Run in sandbox VM (mutually exclusive with `repo`) | -| `hooks` | `GCHooks` | Programmatic lifecycle hooks | -| `maxTurns` | `number` | Max agent turns | -| `abortController` | `AbortController` | Cancellation signal | -| `constraints` | `object` | `temperature`, `maxTokens`, `topP`, `topK` | - -### Message Types - -| Type | Description | Key Fields | -|---|---|---| -| `delta` | Streaming text/thinking chunk | `deltaType`, `content` | -| `assistant` | Complete LLM response | `content`, `model`, `usage`, `stopReason` | -| `tool_use` | Tool invocation | `toolName`, `args`, `toolCallId` | -| `tool_result` | Tool output | `content`, `isError`, `toolCallId` | -| `system` | Lifecycle events | `subtype`, `content`, `metadata` | -| `user` | User message (multi-turn) | `content` | +The headline run's logs are public: every trial of it, with its trajectory and verifier output, is on the Harbor hub, one job per time limit (links in the table below). The headline run and the published comparisons: -## Architecture +| Harness | Model | Terminal-Bench 2.1 | Runs per task | Source | +|---|---|---|---|---| +| **gitagent 3** | Claude Opus 5.5 | **93.03% ±0.8** (414/445; 75 of 89 tasks passed every run) | 5 | [job logs on the Harbor hub](#the-same-run-by-time-limit) | +| Terminus 2 | Claude Opus 5.5 | 87.6% | 1 | [Vals.ai](https://www.vals.ai/benchmarks/terminal-bench-2-1) | +| Codex CLI | GPT-6 Astra | 87.4% ±0.9 | — | [Snorkel, the official leaderboard](https://snorkel.ai/leaderboard/terminal-bench-2-1/) | +| Artificial Analysis's own harness | Claude Fable 5.1, max effort | 91.4% | — | [Artificial Analysis](https://artificialanalysis.ai/evaluations/terminalbench-2-1) | +| Claude Code | Claude Fable 5 | 83.8% ±1.2 | — | [Snorkel, the official leaderboard](https://snorkel.ai/leaderboard/terminal-bench-2-1/) | -``` -my-agent/ -├── agent.yaml # Model, tools, runtime config -├── SOUL.md # Agent identity & personality -├── RULES.md # Behavioral rules & constraints -├── DUTIES.md # Role-specific responsibilities -├── memory/ -│ └── MEMORY.md # Git-committed agent memory -├── tools/ -│ └── *.yaml # Declarative tool definitions -├── skills/ -│ └── / -│ ├── SKILL.md # Skill instructions (YAML frontmatter) -│ └── scripts/ # Skill scripts -├── workflows/ -│ └── *.yaml|*.md # Multi-step workflow definitions -├── agents/ -│ └── / # Sub-agent definitions -├── plugins/ -│ └── / # Local plugins (plugin.yaml + tools/hooks/skills) -├── hooks/ -│ └── hooks.yaml # Lifecycle hook scripts -├── knowledge/ -│ └── index.yaml # Knowledge base entries -├── config/ -│ ├── default.yaml # Default environment config -│ └── .yaml # Environment overrides -├── examples/ -│ └── *.md # Few-shot examples -└── compliance/ - └── *.yaml # Compliance & audit config -``` +Claude Code has no published Terminal-Bench 2.1 entry with Opus 5.5; the table shows its best published entry. -### Agent Manifest (`agent.yaml`) +#### The same run by time limit -```yaml -spec_version: "0.1.0" -name: my-agent -version: 1.0.0 -description: An agent that does things +Pass@k is the chance that at least one of k runs passes: -model: - preferred: "anthropic:claude-sonnet-4-5-20250929" - fallback: ["openai:gpt-4o"] - constraints: - temperature: 0.7 - max_tokens: 4096 +| Time limit | Trials | Pass rate | pass@2 | pass@5 | Logs | +|---|---|---|---|---|---| +| 10 min | 5 | 100.0% | 100.0% | 100.0% | [job](https://hub.harborframework.com/jobs/c78a765b-7b37-41c8-b4b3-1b8c1d48ff44) | +| 12.5 min | 5 | 100.0% | 100.0% | 100.0% | [job](https://hub.harborframework.com/jobs/80c531b4-cc07-4c31-9b17-c329e6ab8195) | +| 15 min | 240 | 92.9% | 95.2% | 95.8% | [job](https://hub.harborframework.com/jobs/63bf2992-3fa9-464b-953d-e485576f7365) | +| 20 min | 25 | 100.0% | 100.0% | 100.0% | [job](https://hub.harborframework.com/jobs/535aafbc-8f4a-4257-b0d1-48680c3ceb79) | +| 30 min | 85 | 87.1% | 95.9% | 100.0% | [job](https://hub.harborframework.com/jobs/d2a97421-d057-4711-b8cc-0fefa35a84b0) | +| 40 min | 10 | 100.0% | 100.0% | 100.0% | [job](https://hub.harborframework.com/jobs/6c57fec8-d434-4020-b208-1a0769c9cf0e) | +| 60 min | 65 | 95.4% | 97.7% | 100.0% | [job](https://hub.harborframework.com/jobs/e1ea8dbd-7a55-4fa3-95de-b9338a3c1223) | +| 2 h | 5 | 100.0% | 100.0% | 100.0% | [job](https://hub.harborframework.com/jobs/127445ee-63ec-44c7-8206-189eb22cd912) | +| 3 h 20 min | 5 | 100.0% | 100.0% | 100.0% | [job](https://hub.harborframework.com/jobs/1d8e0bdb-7ae5-4ec7-8e54-329007590e9d) | +| **all** | **445** | **93.03% ±0.8** | | | | -tools: [cli, read, write, memory] +Two of the 89 tasks start a virtual machine inside the container, which this host can't do, so every harness fails them here; without them the score is 95.2%. The ±0.8 is the run-to-run variation, the measure the official leaderboard uses: the five runs scored 93.3, 95.5, 92.1, 92.1 and 92.1%. The run cost $112.85, about $0.25 per run, with 91% of the input tokens served from the prompt cache. -runtime: - max_turns: 50 - timeout: 120 +### Why it scores higher than the other harnesses -# Optional -extends: "https://github.com/org/base-agent.git" -skills: [code-review, deploy] -delegation: - mode: auto -compliance: - risk_level: medium - human_in_the_loop: true -``` +On a benchmark like this one, a strong model solves most tasks with any reasonable harness. The gap between harnesses comes from a smaller set of hard tasks: long builds, services that must keep running, strict output formats, and tasks close to their time limit. On those, runs rarely fail because the model lacks the skill. They fail because the model stops too early, loses track in a long context, repeats itself, or runs out of time. -## Tools +The model is the same across these comparisons, so the difference lies in what the harness does between model calls. gitagent addresses each of those failure modes directly. Four mechanisms account for most of the gain, and each was measured on its own on the benchmark before it was kept (see [How gitagent works](#how-gitagent-works) and the detail in [docs/HOW-IT-WORKS.md](./docs/HOW-IT-WORKS.md)). -### Built-in Tools +#### 1. A task is finished only when there is evidence that it works -| Tool | Description | -|---|---| -| `cli` | Execute shell commands | -| `read` | Read files with pagination | -| `write` | Write/create files | -| `memory` | Load/save git-committed memory | +**The problem.** The most common way to lose a task is not failing to solve it, but believing it is solved when it is not. A model writes a fix, reads it back, finds it convincing, and declares the task complete without running anything. The hidden tests then disagree. Other harnesses at most ask the model to confirm, and a second "yes" from the same model adds little. -### Declarative Tools +**What gitagent does.** It records every change the agent makes, such as a file write, an in-place edit or a package install, and every check it runs, such as a test suite, the program itself, or a request to a running service. When the model calls `task_complete` and the last change was not followed by a finished check, the call is refused. The model is told which change it never checked and is shown the task again. A task is sent back at most twice, and the check steps aside near the end of the time budget, when another round could no longer help. -Define tools as YAML in `tools/`: +**Why it helps.** The cost is one extra turn. On a run where the model was confident and wrong, that turn often turns a failure into a pass. The check never judges the work itself; it only asks that a verification actually ran, so it cannot make a correct run worse. -```yaml -# tools/search.yaml -name: search -description: Search the codebase -input_schema: - properties: - query: - type: string - description: Search query - path: - type: string - description: Directory to search - required: [query] -implementation: - script: search.sh - runtime: sh -``` +#### 2. The model reads less, and most of what it reads is cached -The script receives args as JSON on stdin and returns output on stdout. +**The problem.** Two costs grow with every turn. The first is attention: a model searching a ten-thousand-line build log for the one line that matters does worse than a model shown the relevant part, and a long run accumulates many such logs. The second is cost and speed: every call re-sends the whole conversation, so without care the cost of a run grows much faster than its length. -## Hooks +**What gitagent does.** -Script-based hooks in `hooks/hooks.yaml`: +- Every command result is cut to a budget of 8,000 tokens before the model sees it. The beginning, the end and the exit code are kept, and runs of similar lines are folded into one. The full output is saved to a file the model can open if it needs more. +- Old tool results are cleared in batches through the provider's own context editing once the conversation grows past 50,000 tokens. +- Reminders, such as the time left, are added at the end of the conversation and never written into earlier turns. -```yaml -hooks: - on_session_start: - - script: validate-env.sh - description: Check environment is ready - pre_tool_use: - - script: audit-tools.sh - description: Log and gate tool usage - post_response: - - script: notify.sh - on_error: - - script: alert.sh -``` +**Why it helps.** The last rule matters more than it seems. Providers cache the start of a conversation, and any change to an earlier message invalidates the cache from that point on. Because gitagent never rewrites history, 91% of the input tokens in the published run were served from the cache. That makes each call faster, and on Opus 5.5 a cache read costs a twentieth of normal input. The model's attention stays on recent, relevant output. -Hook scripts receive context as JSON on stdin and return: +#### 3. The harness keeps track of time and repetition -```json -{ "action": "allow" } -{ "action": "block", "reason": "Not permitted" } -{ "action": "modify", "args": { "modified": "args" } } -``` +**The problem.** In long runs, models are poor at two things: noticing that they are repeating themselves, and pacing their work against a clock they cannot see. A model can retry the same failing command until the time limit, and a run that times out scores zero even when the work was nearly done. -## Skills +**What gitagent does.** When the same command with the same arguments returns the same output three times, gitagent tells the model plainly that it is repeating itself and should change approach. Every tool result also shows the minutes remaining. At 70% of the budget the model is reminded to wrap up, and at 90% it is told to finish now. -Skills are composable instruction modules in `skills//`: +**Why it helps.** A loop is broken after three repetitions instead of running until the deadline. A finished and checked result, even a partial one, often passes, where an unfinished one never does. Runs rarely end in a timeout; none of the 445 trials in the published run did. -``` -skills/ - code-review/ - SKILL.md - scripts/ - lint.sh -``` +#### 4. The environment is surveyed once, before the first step -```markdown ---- -name: code-review -description: Review code for quality and security ---- +**The problem.** Without help, the first turns of almost every run go to orientation: listing directories, checking language versions, looking for a compiler, checking memory. Each of those is a full model call. -# Code Review +**What gitagent does.** Before the first model call, gitagent runs a single probe of at most fifteen seconds, outside the agent's shell. It collects the working directory listing, the operating system, the CPU and memory, and the versions of common tools, and puts the result in the first message. -When reviewing code: -1. Check for security vulnerabilities -2. Verify error handling -3. Run the lint script for style checks -``` +**Why it helps.** The model starts on the task with the facts it would otherwise spend several turns gathering. On tasks with short time limits, those turns are real slack. -Invoke via CLI: `/skill:code-review Review the auth module` +#### How the four work together -## Plugins +None of these makes the model more capable. Together they remove the ways a capable model loses a task it could have solved: stopping too early, losing focus in long output, repeating itself, running out of time, and spending its opening turns on orientation. They also reinforce each other. A smaller context makes loops easier to escape, the time reminders leave room for the completion check to send work back before the deadline, and the cache savings make the extra verification turns nearly free. -Plugins are reusable extensions that can provide tools, hooks, skills, prompts, and memory layers. They follow the same git-native philosophy — a plugin is a directory with a `plugin.yaml` manifest. +Outside benchmarks, the same mechanisms are on by default, with two differences. The time reminders appear only when a run has a time limit, and the environment probe stays a benchmark setting. See [Configuration](#configuration). -### CLI Commands +**The rules behind the numbers** (`docs/INTEGRITY.md`, checked by `scripts/integrity-audit.sh`): the agent never reads a task's tests or verifier output, and a built-in guard refuses writes under `/tests`; the tasks used while developing were kept apart from the ones used to score; benchmark mode uses only the built-in configuration, with a test that fails if the prompt or tool list sent to the model ever changes. -```bash -# Install from git URL -gitagent plugin install https://github.com/org/my-plugin.git +## How gitagent works -# Install from local path -gitagent plugin install ./path/to/plugin +Most agents are a prompt, a model and a bag of tools, and the interesting part is what happens in between: how the agent is kept on course for an hour of work inside a context window, without wasting the model's attention and without breaking its cache. That in-between is what gitagent is. Here is one run, start to finish. -# Install with options -gitagent plugin install --name custom-name --force --no-enable +**The agent is a folder.** Before anything runs, the loader reads the agent directory: `agent.yaml` for the model and the tools, `SOUL.md` for who it is, `RULES.md` for what it must never do, `memory/` for what it remembers, `skills/`, `hooks/`, `schedules/`, `agents/` for the rest. All of it is assembled into one system prompt in a fixed order, and all of it is in git, so the agent that produced a result is a commit you can check out. The same loader runs the console, the SDKs and a single task; only the files differ. -# List all discovered plugins -gitagent plugin list +**The prompt is a method.** It does not describe a character. It says how to work: note every concrete requirement in the task before touching anything, explore the environment, change things in small steps and check each one, and at the end verify the result by exercising it, running the tests, the program or the request. That last instruction is where most agents go wrong: the model finishes without proof, and the result does not hold. -# Enable / disable -gitagent plugin enable my-plugin -gitagent plugin disable my-plugin +**One loop, one shell.** The model is called with the prompt, the task and the history. It answers with text or tool calls. The tools run in one persistent shell, so working directory, environment and background processes carry from turn to turn like a person's terminal. The results go back; the loop repeats until the model calls `task_complete`. -# Remove -gitagent plugin remove my-plugin +**Output is shaped before the model sees it.** A build log is ten thousand lines; the model needs the head, the tail and the exit code. Every command result is cut to a budget: long output is spilled to a file, runs of similar lines (progress bars, repeated warnings) collapse to their first and last line, and a one-line status carries the exit code, the line count and the path of the spill file, so the model can ask for the rest if it matters. Fewer tokens per turn means more turns before the context is full, and more of each turn spent on the task. -# Scaffold a new plugin -gitagent plugin init my-plugin -``` +**Reminders are appended, never injected.** The time left is shown on every result, and a reminder fires when the budget runs low. These go after the turn's results, never into earlier turns. Changing an earlier turn would invalidate the provider's prompt cache and the model's thinking blocks, so the whole history stays byte-identical and 91% of the input is served from cache. That is where the cost per run comes from. -| Flag | Description | -|---|---| -| `--name ` | Custom plugin name (default: derived from source) | -| `--force` | Reinstall even if already present | -| `--no-enable` | Install without auto-enabling | +**The loop detector.** If the model makes the same tool call with the same arguments and gets the same output three times in a row, it is told so and asked to change approach. Runaway loops are where other harnesses burn a time budget and a bill: a model that returns empty replies can be re-sent the whole growing conversation until the clock runs out. gitagent breaks the loop at the third identical call. -### Plugin Manifest (`plugin.yaml`) +**Context is managed, not truncated.** Old tool results are cleared in batches through the provider's own context editing. Each clear invalidates the cache from the point it clears, so clearing waits until the prompt is large and then removes a lot at once. Near the end of the window the history is replaced by a structured summary, done, pending, key files, last failure, ruled out, with the original task repeated verbatim by the harness rather than paraphrased by the model, so the goal never drifts. -```yaml -id: my-plugin # Required, kebab-case -name: My Plugin -version: 0.1.0 -description: What this plugin does -author: Your Name -license: MIT -engine: ">=0.3.0" # Min gitagent version - -provides: - tools: true # Load tools from tools/*.yaml - skills: true # Load skills from skills/ - prompt: prompt.md # Inject into system prompt - hooks: - pre_tool_use: - - script: hooks/audit.sh - description: Audit tool calls - -config: - properties: - api_key: - type: string - description: API key - env: MY_API_KEY # Env var fallback - timeout: - type: number - default: 30 - required: [api_key] - -entry: index.ts # Optional programmatic entry point -``` +**Done means verified.** `task_complete` is accepted only when the run shows a verification after the last edit. A completion without evidence is sent back with the task repeated. It is a small rule with a large effect: it catches the case where the model is confident and wrong. -### Plugin Config in `agent.yaml` +**Services can stay up.** With `--linger` the agent's shells are kept open after it finishes, so a server it started keeps serving whoever needs it next. -```yaml -plugins: - my-plugin: - enabled: true - source: https://github.com/org/my-plugin.git # Auto-install on load - version: main # Git branch/tag - config: - api_key: "${MY_API_KEY}" # Supports env interpolation - timeout: 60 -``` +**Every run is a record.** Each run writes a trajectory in the ATIF format, every prompt, reply, tool call and result, plus counters for every decision the loop made: loops caught, completions rejected, context compacted, cache hits. A failed run can be read step by step, and a change to the harness can be measured run by run against the previous one. -Config resolution priority: `agent.yaml config` > `env var` > `manifest default`. - -### Discovery Order - -Plugins are discovered in this order (first match wins): - -1. **Local** — `/plugins//` -2. **Global** — `~/.gitagent/plugins//` -3. **Installed** — `/.gitagent/plugins//` - -### Programmatic Plugins - -Plugins with an `entry` field in their manifest get a full API: - -```typescript -// index.ts -import type { GitagentPluginApi } from "gitagent"; - -export async function register(api: GitagentPluginApi) { - // Register a tool - api.registerTool({ - name: "search_docs", - description: "Search documentation", - inputSchema: { - properties: { query: { type: "string" } }, - required: ["query"], - }, - handler: async (args) => { - const results = await search(args.query); - return { text: JSON.stringify(results) }; - }, - }); - - // Register a lifecycle hook - api.registerHook("pre_tool_use", async (ctx) => { - api.logger.info(`Tool called: ${ctx.tool}`); - return { action: "allow" }; - }); - - // Add to system prompt - api.addPrompt("Always check docs before answering questions."); - - // Register a memory layer - api.registerMemoryLayer({ - name: "docs-cache", - path: "memory/docs-cache.md", - description: "Cached documentation lookups", - }); -} -``` +The pieces are ordinary. What is unusual is that each one is a switch in a profile file, measured on its own before it stayed on, and that the whole thing ships as one static binary that runs the same way in a container, in your terminal and behind the SDK. The exact mechanics, with the numbers, formats and algorithms, are in [docs/HOW-IT-WORKS.md](./docs/HOW-IT-WORKS.md). -**Available API methods:** +

+ Parallel tasks on the task board +

-| Method | Description | -|---|---| -| `registerTool(def)` | Register a tool the agent can call | -| `registerHook(event, handler)` | Register a lifecycle hook (`on_session_start`, `pre_tool_use`, `post_response`, `on_error`) | -| `addPrompt(text)` | Append text to the system prompt | -| `registerMemoryLayer(layer)` | Register a memory layer | -| `logger.info/warn/error(msg)` | Prefixed logging (`[plugin:id]`) | -| `pluginId` | Plugin identifier | -| `pluginDir` | Absolute path to plugin directory | -| `config` | Resolved config values | +**Parallel work.** When a task has many similar pieces, the agent can write a plan of phases: every task in a phase runs at the same time as its own sub-agent, a later phase sees the earlier results, and one more sub-agent reconciles everything into the answer. The console shows it as a board that redraws as the tasks run. See [Parallel work](#parallel-work). -### Plugin Structure +## Architecture ``` -my-plugin/ -├── plugin.yaml # Manifest (required) -├── tools/ # Declarative tool definitions -│ └── *.yaml -├── hooks/ # Hook scripts -├── skills/ # Skill modules -├── prompt.md # System prompt addition -└── index.ts # Programmatic entry point +my-agent/ +├── agent.yaml # model, tools, runtime, compliance, plugins +├── SOUL.md # identity +├── RULES.md # hard rules +├── DUTIES.md # role and responsibilities +├── memory/ +│ ├── MEMORY.md # working memory, committed on every save +│ └── memory.yaml # optional layers and line limits +├── skills/ +│ └── /SKILL.md # instructions with YAML frontmatter, plus any files +├── tools/ +│ └── .yaml # declarative tools backed by a script or HTTP call +├── hooks/ +│ ├── hooks.yaml # scripts per lifecycle event +│ ├── bootstrap.md # what to do when a session starts +│ └── teardown.md # what to do when it ends +├── agents/ +│ └── / # sub-agents, each a full agent folder +├── workflows/ +│ └── .yaml # step-by-step procedures, run with @ +├── schedules/ +│ └── .yaml # prompts on a timetable +├── knowledge/ +│ └── index.yaml # reference documents, some always loaded +├── examples/ +│ └── *.md # worked examples for the prompt +├── plugins/ +│ └── / # local plugins: tools, skills, hooks, prompt text +├── config/ +│ ├── default.yaml # settings for every environment +│ └── .yaml # overrides for --env +└── .gitagent/ # runtime state, not committed: sessions, audit, schedule logs ``` -## MCP (Model Context Protocol) +The binary reads this folder on every start, assembles one system prompt from it in a fixed order (identity, working method, skills, sub-agents, workflows, knowledge, examples, compliance, memory), and runs the loop described in [How gitagent works](#how-gitagent-works). `gitagent init ` creates a minimal folder; `gitagent validate ` checks one against the format. -Gitagent is an **MCP client**: point it at any [MCP server](https://modelcontextprotocol.io) and that server's tools are automatically discovered and made available to the agent — no integration code to write. This unlocks the whole ecosystem of ready-made servers (filesystem, GitHub, Postgres, Slack, fetch, …). - -### Configure servers in `agent.yaml` +### The agent manifest (`agent.yaml`) ```yaml -mcp_servers: - filesystem: # local server over stdio (default) - command: npx - args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/data"] - env: - LOG_LEVEL: "${MCP_LOG_LEVEL}" # ${VAR} interpolated from the environment - timeoutMs: 30000 # connect/list timeout (default 30000) - - analytics: # remote server over Streamable HTTP - type: http - url: "https://mcp.example.com/mcp" - headers: - Authorization: "Bearer ${ANALYTICS_TOKEN}" - - legacy: # legacy SSE transport (deprecated) - type: sse - url: "https://old.example.com/sse" -``` +spec_version: "0.1.0" +name: lyra +version: 1.0.0 +description: Keeps the billing service healthy. -On startup gitagent connects to each server, lists its tools, and registers them as **`__`** (e.g. `filesystem__read_file`, `analytics__query`). Connections are torn down automatically when the session ends. +model: + preferred: anthropic:claude-sonnet-5-5 + fallback: [openai:gpt-6-astra] + constraints: {temperature: 0.2, max_tokens: 8000} -| Field | Applies to | Description | -|---|---|---| -| `command` / `args` / `env` / `cwd` | stdio | How to launch a local server | -| `type: http \| sse` + `url` + `headers` | remote | Connect to a remote server | -| `timeoutMs` | both | Connect + list-tools timeout (default `30000`) | - -### Use via the SDK - -```typescript -import { query } from "gitagent"; - -for await (const msg of query({ - prompt: "Summarize last week's signups from the database", - mcpServers: { - postgres: { - command: "npx", - args: ["-y", "@modelcontextprotocol/server-postgres", process.env.DB_URL!], - }, - }, -})) { - if (msg.type === "tool_use") console.log(`calling ${msg.toolName}`); -} -``` +tools: [cli, read, write, memory, word-count] # built-ins by their 1.x names, plus tools/*.yaml -SDK `mcpServers` are merged with any `agent.yaml` `mcp_servers` (the SDK value wins on a key collision). +runtime: + max_turns: 50 + timeout: 120 # seconds a command may run before it goes to the background + parallel_tasks: true # the plan tool: phases of sub-agents, then a reconciling step + max_parallel: 4 -### Behavior & guarantees +# Optional +extends: https://github.com/org/base-agent.git # inherit another agent's files +dependencies: [{name: shared, source: https://github.com/org/shared-agent.git, version: main, mount: agents/shared}] # gitagent install +skills: [release-notes] +agents: {reviewer: {description: Reviews a change for bugs.}} +mcp_servers: {notes: {command: python3, args: [notes-server.py]}} +plugins: {greeter: {enabled: true}} +compliance: + risk_tier: high + supervision: {human_in_the_loop: conditional, escalation_triggers: [{action_type: cli}]} + recordkeeping: {audit_logging: true} +``` -- **Fail-soft:** a server that can't start (or times out) is logged and skipped — other servers and built-in tools keep working. -- **Namespaced & sanitized:** tool names are prefixed with the server name and cleaned to satisfy provider naming rules. -- **Pagination:** servers that paginate their tool list are fully enumerated. -- **Cleanup:** stdio servers (child processes) are shut down on every exit path (normal, `/quit`, Ctrl+C, error). -- **Lazy:** if no servers are configured, the MCP SDK is never loaded. +Built-in tools: `cli` (a persistent shell), `read`, `write`, `edit`, `memory`, and on request `skill`, `subagent`, `plan`, `workflow`. Tools from `tools/*.yaml`, plugins and MCP servers are added by name. The model sees the built-ins under their native names (`exec`, `read_file`, …); everything a person writes, hooks and SDK messages included, uses the 1.x names. -> Note: v1 supports MCP **tools**. Resources and prompts are not yet exposed. +## SDKs: any language over IPC -## Multi-Model Support +The agent always runs in the binary. A client library starts `gitagent --ipc`, writes JSON lines to its stdin and reads JSON lines from its stdout. That is the whole interface, so any language that can start a process and read lines can drive an agent. The Python and TypeScript clients are thin wrappers over it. -Gitagent works with any LLM provider supported by [pi-ai](https://github.com/badlogic/pi-mono/tree/main/packages/ai): +### Python -```yaml -# agent.yaml -model: - preferred: "anthropic:claude-sonnet-4-5-20250929" - fallback: - - "openai:gpt-4o" - - "google:gemini-2.0-flash" +```sh +pip install ./clients/python # from this repository; needs `gitagent` on PATH or GITAGENT_BIN ``` -Supported providers: `anthropic`, `openai`, `google`, `xai`, `groq`, `mistral`, and more. - -## Inheritance & Composition +```python +from gitagent import query, tool -Agents can extend base agents: +lookup = tool("lookup_order", "Looks up an order.", + {"properties": {"id": {"type": "string"}}}, + lambda args: f"Order {args['id']} shipped on Monday.") -```yaml -# agent.yaml -extends: "https://github.com/org/base-agent.git" - -# Dependencies -dependencies: - - name: shared-tools - source: "https://github.com/org/shared-tools.git" - version: main - mount: tools - -# Sub-agents -delegation: - mode: auto +q = query("Where is order 42?", dir="./my-agent", tools=[lookup], + hooks={"pre_tool_use": lambda ctx: {"action": "allow"}}, + approver=lambda req: input(f"approve {req['toolName']}? [y/N] ") == "y") +for message in q: + if message["type"] == "assistant" and message["content"]: + print(message["content"]) +print(q.result()["outcome"], q.costs()) ``` -## Compliance & Audit +`q.send(text)` adds a turn, `q.steer(text)` speaks to the running one, `q.abort()` stops it, `q.end()` ends the session. All options are listed in [clients/python/README.md](./clients/python/README.md). -Built-in compliance validation and audit logging: +### TypeScript -```yaml -# agent.yaml -compliance: - risk_level: high - human_in_the_loop: true - data_classification: confidential - regulatory_frameworks: [SOC2, GDPR] - recordkeeping: - audit_logging: true - retention_days: 90 +```sh +npm install ./clients/typescript # from this repository; needs `gitagent` on PATH or GITAGENT_BIN ``` -Audit logs are written to `.gitagent/audit.jsonl` with full tool invocation traces. - -## Telemetry - -Gitagent ships with built-in OpenTelemetry instrumentation. Set `OTEL_EXPORTER_OTLP_ENDPOINT` and telemetry is on; leave it unset and runtime cost is zero. +```ts +import { query, tool } from "@open-gitagent/gitagent"; -Three layers of signals: +const lookup = tool("lookup_order", "Looks up an order.", + { properties: { id: { type: "string" } } }, + async (args) => `Order ${args.id} shipped on Monday.`); -1. **HTTP-level** — `@opentelemetry/instrumentation-undici` auto-patches `fetch`/`undici`, so every LLM provider call (Anthropic, OpenAI, Google, …) gets a client span with URL, status code, and timing. -2. **`gen_ai.chat` spans** — emitted on every assistant `message_end`. Carry `gen_ai.system`, `gen_ai.request.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `gen_ai.response.finish_reasons`, and `gitagent.cost_usd`. Span/metric content never contains the prompt or completion text. -3. **`gitagent.tool.execute` spans** — wrap every tool call with `tool.name`, `tool.call_id`, `tool.status` (`ok`/`error`), and `tool.error_message` on failure. +const q = query({ prompt: "Where is order 42?", dir: "./my-agent", tools: [lookup], + hooks: { preToolUse: () => ({ action: "allow" }) } }); +for await (const message of q) { + if (message.type === "assistant" && message.content) console.log(message.content); +} +console.log(q.result()?.outcome, q.costs()); +``` -A root `gitagent.agent.session` span opens at agent construction and closes on every exit path (success, hook-block, SIGINT, error). +The API is the TypeScript gitagent's `query()`, so existing code keeps working: messages, hooks and options have the same names, and tools appear under their 1.x names (`cli`, `read`, …). See [clients/typescript/README.md](./clients/typescript/README.md). -### CLI usage +### Any other language -Just set the endpoint — no `--import` flag, no extra install steps: +Start `gitagent --ipc`, send `initialize`, then one `user` message per turn. This is a complete client in bash: ```bash -OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 gitagent -p "your prompt" +coproc GA { gitagent --ipc; } +send() { printf '%s\n' "$1" >&"${GA[1]}"; } +send '{"type":"initialize","protocol":1,"options":{"dir":"./my-agent","toolNames":"v1"}}' +send '{"type":"user","content":"The billing tests fail on main. Find out why and fix it."}' +while IFS= read -r line <&"${GA[0]}"; do + case "$(jq -r '.type + "/" + (.subtype // "")' <<<"$line")" in + tool_use/) jq -r '"$ " + .toolName + " " + (.args | tostring)' <<<"$line" ;; + assistant/) jq -r 'select(.content != "") | .content' <<<"$line" ;; + system/turn_end) send '{"type":"end"}' ;; + system/session_end) break ;; + esac +done +``` + +| You send | It means | +|---|---| +| `{"type":"initialize","protocol":1,"options":{…},"callbacks":[…],"tools":[…]}` | Start. Options: `dir`, `model`, `env`, `systemPrompt`, `allowedTools`, `maxTurns`, `sessionId`, `resume`, `mcpServers`, `repo`, `conversation`, `toolNames`, … `callbacks` names the hooks your code answers (`preToolUse`, `postToolUse`, `approval`, …); `tools` declares tools your code runs | +| `{"type":"user","content":"…"}` | A turn | +| `{"type":"steer","content":"…"}` | Words for the running turn | +| `{"type":"abort"}` / `{"type":"kill"}` | Stop the turn / stop and end the session | +| `{"type":"callback_response","id":"…","result":{…}}` | Your answer to a hook, tool or approval request | +| `{"type":"end"}` | End the session after the current step | + +| You receive | When | +|---|---| +| `system/ready`, `system/session_start` | After `initialize`: the session id, the agent, the model, the tools | +| `user`, `delta`, `assistant`, `tool_use`, `tool_result` | As the turn runs; `assistant` carries `usage` and `costUsd` | +| `callback_request` (`kind`: `hook`, `tool` or `approval`) | The binary needs your answer; reply with `callback_response` and the same `id` | +| `system/turn_end` | A turn finished, with its `outcome` and usage | +| `system/warning`, `system/error`, `system/hook_blocked` | Something to show; the session goes on unless the error was fatal | +| `system/session_end` | Last message, with the session's costs | + +With `resume: true` the conversation is stored after every turn under `.gitagent/sessions//`, and a new process started with the same `sessionId` continues it. + +### Rust + + +The `gitagent-sdk` crate runs the loop in your own process, without IPC: `query(prompt, AgentOptions)` streams `system` / `assistant` / `user` / `result` messages, tools appear under the `.claude/` tool names (`Bash`, `Read`, `Edit`, …), and hooks are callbacks that receive `PreToolUse` / `PostToolUse` input and answer with `permissionDecision`, `updatedInput` or `updatedToolOutput`. + +```rust +use gitagent_sdk::{query, AgentOptions, AgentDefinition, HookEvent, HookMatcher, HookOutput, Message}; + +let mut q = query("Fix the failing test.", AgentOptions { + model: Some("claude-opus-5-5".into()), + allowed_tools: vec!["Bash".into(), "Read".into(), "Edit".into()], + max_turns: Some(40), + hooks: vec![HookMatcher::new(HookEvent::PreToolUse, Some("Bash"), |input| async move { + if input.tool_input["command"].as_str().unwrap_or("").contains("rm -rf") { + HookOutput::deny("no rm -rf") + } else { + HookOutput::allow() + } + })], + ..Default::default() +}).await?; +while let Some(message) = q.next().await { + if let Message::Result { result, total_cost_usd, .. } = &message { + println!("{result:?} (${total_cost_usd:.2})"); + } +} ``` -Telemetry is enabled automatically when the endpoint is set and disabled when it is not. To force-disable even when the endpoint is set, pass `GITAGENT_OTEL_ENABLED=false`. +The same shape is available from the command line: `gitagent -p "..." --output-format stream-json` prints one message per line, and `--max-turns`, `--allowed-tools`, `--disallowed-tools` and `--append-system-prompt` take the same values as `AgentOptions`. -### Environment variables +## Coming from the TypeScript gitagent -| Variable | Description | Default | -|----------|-------------|---------| -| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP/HTTP collector base URL (e.g. `http://localhost:4318`). When set, telemetry is auto-enabled. | (unset → telemetry off) | -| `GITAGENT_OTEL_ENABLED` | Set to `false` to disable telemetry even when the endpoint is set | (unset = auto) | -| `OTEL_SERVICE_NAME` | Resource `service.name` | `gitagent` | -| `OTEL_SERVICE_VERSION` | Resource `service.version` | (unset) | -| `OTEL_EXPORTER_OTLP_HEADERS` | Comma-separated key=value pairs, no quotes (e.g. `Authorization=Bearer xyz,x-tenant=abc`) | (unset) | -| `OTEL_TRACES_EXPORTER` | Set to `console` to print spans to stdout — no collector needed | (unset) | +gitagent 3 replaces the TypeScript packages (1.x and 2.x) with one binary. Step by step: [docs/MIGRATING.md](./docs/MIGRATING.md); what is new in this release: [docs/RELEASE-NOTES-3.0.0.md](./docs/RELEASE-NOTES-3.0.0.md). What carries over unchanged: -### SDK usage +- **Agent folders.** `agent.yaml`, `SOUL.md`, `RULES.md`, `memory/`, `skills/`, `tools/`, `hooks/`, `schedules/`, `workflows/`, `agents/`, `config/` and `.env` are read as they were. 1.x settings such as `tools: [cli, read, write, memory]`, `runtime.timeout` and the 1.x `compliance` shape are accepted; `gitagent validate` points out the newer spelling as a warning, never an error. +- **Hook scripts.** They get the same JSON and answer the same way; output that is not an answer, or a script that crashes, allows the action with a warning, as before. Only hooks marked `compliance: true` or `fail_open: false`, which are new, block in those cases. +- **The CLI.** `gitagent`, `-p`, `-d`, `-m`, `-e`, `--repo`, `--pat`, `--session` and the REPL's slash commands work as before. +- **SDK code.** `query()`, `tool()`, hooks, `steer()`, `abort()`, the message types and the `repo` option are the same; the TypeScript package now needs the `gitagent` binary installed. A `tool()` handler still gets the arguments and an `AbortSignal`, and may return a string, 1.x's `{text, details}` or `{content, isError}`. +- **Model settings.** `model.constraints` (`temperature`, `max_tokens`, `top_p`, `top_k`, `stop_sequences`) are sent to the providers whose API takes them. OpenAI's API has no `top_k` or stop sequences, so those two are ignored there with a warning. Current Claude models reject `temperature`, `top_p` and `top_k`, so the model call fails; `gitagent validate` warns about them. +- **Scripts.** A declared tool or hook ending in `.sh` runs under `sh`, as before, unless its `#!` line names another shell. Hooks get 10 seconds unless `hooks.yaml` sets `timeout`. A plugin's settings reach its script tools and hooks as environment variables (see `plugin.yaml`'s `env` names, or `GITAGENT_PLUGIN_CONFIG` for all of them as JSON). +- **Compliance files.** The frameworks named in `compliance/regulatory-map.yaml` and the checks in `compliance/validation-schedule.yaml` are added to the system prompt, as before. -For programmatic embedders, call `initTelemetry` explicitly — you control when initialisation happens: +### What changed -```ts -import { initTelemetry, shutdownTelemetry, query } from "gitagent"; +- **Install.** The binary must be installed (one `curl` line, above). The TypeScript and Python packages drive it. +- **Exit codes.** 1.x exited with 1 for every error. gitagent 3 exits with 0 when a run ends (also when it runs out of turns or time), 2 for usage and setup errors, 3 when the model provider fails, and 130 when the run is stopped. A script that only checks for a nonzero code works as before; one that checks for exactly 1 needs updating. +- **Not carried over.** `--voice`; the remote `--sandbox` (the binary prints a one-line note); a plugin's `entry` JavaScript (plugins contribute tools, skills, hooks and prompt text through files); the 1.x telemetry adapter (OTLP export replaces it). +- **Task list.** 1.x's `task_tracker` kept a searchable list of tasks. gitagent 3's `todo` is a plain checklist the agent rewrites whole; the name `task_tracker` still maps to it in tool lists and hooks. +- **Learning.** 1.x's online skill learning, which adjusted a skill's confidence after each use, is gone. Lessons are learned from finished runs instead (`learning: online` or `gitagent learn`, below) and go into the system prompt; skills are not changed. +- **Scripts without a runtime.** 1.x ran every declared tool with `sh` unless `runtime` was set. gitagent 3 picks the interpreter by extension (`.py` with `python3`, `.js` with `node`, `.rb` with `ruby`, …), so such tools now run instead of failing. -await initTelemetry({ serviceName: "my-app" }); +## Configuration -for await (const msg of query({ prompt: "hello", model: "anthropic:claude-4-6-sonnet-latest" })) { - // … -} +gitagent reads `gitagent.yaml` from `--config`, else the project folder (`gitagent.yaml` or `.gitagent/gitagent.yaml`), else `~/.gitagent/gitagent.yaml`. The other files are looked up one by one in the same places (the config's folder, the project's `.gitagent/`, then `~/.gitagent/`, then the project's and home `.claude/`), and fall back to the built-in versions. A key that `gitagent.yaml` does not know is ignored with a warning, and `gitagent validate` checks the project's copy. -await shutdownTelemetry(); -``` +| Path | What it is | +|---|---| +| `gitagent.yaml` | Model, turn and time limits, profile, sandbox, skills, hooks, learning, repo-mode settings | +| `prompts/system.md`, `prompts/rules.md` | The system prompt; `rules.md` is appended to it | +| `profiles/.yaml` | Per-model-family settings: tools, effort, output shaping, caching, and the optional features below | +| `skills//SKILL.md` | Instructions the agent can load on demand; their names and descriptions are listed in the system prompt | +| `hooks/pre_tool_use`, `hooks/post_tool_use` | Scripts that receive each tool call (and its output) as JSON and answer `allow`, `block` or `modify` | +| `prompts/lessons.md` | Learned lessons appended to the system prompt; unlike the other files, every layer's lessons are added up | +| `agents/.md` | Sub-agent definitions (`name`, `description`, `tools`, `disallowedTools`, `maxTurns`, `model`, `effort`; the body is the prompt), when the profile enables sub-agents | +| `.claude/agents/`, `.claude/skills/`, `.claude/settings.json` | An existing `.claude/` project layout, read as it is after the layers above: sub-agents and skills, and `PreToolUse` / `PostToolUse` hooks with their matchers, stdin JSON, exit codes and `hookSpecificOutput` answers (tool and argument names are translated both ways) | -`OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_EXPORTER_OTLP_HEADERS` are read automatically by the OTLP exporter when not supplied programmatically. Pass `exporterEndpoint` / `headers` only when you need to override env-based config in code. +`profile: auto` picks `anthropic`, `openai`, `google` or `compat` from the model's provider. Every behavior that changes results is a switch in the profile. The defaults for everyday work: -### Emitted spans +- **On:** output shaping, context compaction, the loop detector, the turn warning (at 80% of `max_turns` and before the last model call), and the stop check for runs that end with `task_complete` (`-p` and SDK task mode; a reply ends a console turn, so it never fires there). +- **On for Claude models:** old tool results are cleared by the provider once the conversation passes 50,000 tokens. +- **On when the run has a time limit:** the minutes left on every result, a reminder at 70% of the limit and a final call at 90%. +- **Benchmark profiles only:** the environment snapshot, which adds up to 15 seconds to each start. -| Name | Kind | Key attributes | -|------|------|----------------| -| `gitagent.agent.session` | INTERNAL | `gitagent.entry` (`sdk` / `cli`), `gitagent.cost_usd`, `gitagent.session.duration_ms` | -| `gitagent.tool.execute` | INTERNAL | `tool.name`, `tool.call_id`, `tool.status`, `tool.error_message` | -| `gen_ai.chat` | CLIENT | `gen_ai.system`, `gen_ai.request.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `gen_ai.response.finish_reasons`, `gitagent.cost_usd` | -| `HTTP …` | CLIENT | URL, status code, duration (auto from `instrumentation-undici`) | +**Time limits.** Everyday work needs none. In the console a turn runs until it is done or you press Ctrl-C. Runs nobody watches (`-p`, schedules, the SDKs) stop after 30 minutes unless you set another limit with `timeout_s` in `gitagent.yaml`, `--timeout-s`, or `timeoutMs` over IPC. Setting `timeout_s` applies to the console too. -### Emitted metrics +With `learning: online`, gitagent learns from each task it completes. The model reads the run's trajectory and proposes general lessons, which are saved in the project's `.gitagent/prompts/lessons.md`. A lesson is used only once two different tasks support it. Review the file like code: edit or delete lines freely. In repo mode the lessons are committed separately on the branch. `--bench` never learns and never loads project lessons. -| Name | Type | Description | -|------|------|-------------| -| `gitagent.tool.calls` | counter | Number of tool executions, labelled by `tool.name` | -| `gitagent.tool.duration_ms` | histogram | Tool execution duration | -| `gitagent.session.duration_ms` | histogram | Session duration | -| `gitagent.session.cost_usd` | counter (USD) | Cumulative session cost | -| `gen_ai.client.token.usage` | counter | Token usage by `gen_ai.system`, `gen_ai.request.model`, `gen_ai.token.type` | -| `gen_ai.client.operation.duration` | histogram | LLM call duration | +### Output, records and events -### Console quickstart (no collector) +- **Records:** every run writes `trajectory.json` (the ATIF trajectory format), `counters.json` and the full output of every command (`out/.log`) to `--out-dir`. They are updated after every turn. The console gives each session its own folder, `console-