A local-first, terminal-native agent harness that can prove what it did.
Most agent tools ask you to trust a summary. Agent CLI is built so you never have to: every action passes one policy gate, every consequence is recorded to an append-only log, and a file is only called verified when a real process exited zero after that file's last change. When it cannot prove something, it says so — in the prompt, in the report, and in this README.
It is a general-purpose harness rather than a coding assistant. The same kernel that edits code also writes documents, researches the web, drives a browser, and publishes to GitHub — because they all run through the same authority, evidence and recovery contracts.
An agent that can run commands on your machine has to answer three questions before it is useful for real work:
- What is it allowed to do, and who decided? Not "which mode am I in" — which specific action, approved by whom, recorded where.
- What actually happened? Not the model's account of it. The exit codes, the diffs, the artifacts.
- What if it goes wrong? Mid-task, mid-command, mid-crash — can you get back?
Agent CLI is an attempt to answer all three structurally, in a codebase small enough to read.
One policy gate, and everything goes through it. There is no permission "mode" to choose. Every tool call is classified by the facts the tool declares — does it run a command, mutate files, read a remote, send data off the machine — and the engine decides allow, ask or deny, deny-first, before anything executes. A tool that declares conflicting facts is refused by construction. That is why "the read tool cannot publish" is something you verify by grepping for a second fact and finding none, rather than by reading a promise.
Evidence, not narration. Everything lands in an append-only JSONL log: decisions, approvals,
commands with typed termination, file mutations with before-and-after hashes, checks with their
exit codes. agent report is a pure function of that log. A file is marked CHECKED only when a
real process exited zero after its last mutation, scoped to the right project — a green build in
web/ never vouches for a change in api/. A killed command has no exit code anywhere in the
system, so it can never read as a pass.
Reversible by default. In-workspace writes run without asking, but they are snapshotted first,
so /undo walks them back — and refuses to overwrite a file that drifted, rather than clobbering
it. Bigger jumps get hidden-ref git checkpoints that leave your branches, index and HEAD untouched.
A crash is reconciled against the log on resume: a completed edit whose completion record was lost
is recognized by its hash, not guessed at.
Verification the model cannot fake. The model names a kind — build, test, lint,
typecheck — and the harness resolves it to a command from the project's own manifest. The exit
code is the verdict; parsers may only enrich the summary. A plan task can declare gates, and its
dependents stay blocked until those gates are genuinely green.
Bounded planning and real multi-agent boundaries. A plan is a structured document whose approval binds its content hash, so a status change does not invalidate it but a semantic edit does. Delegated tasks are bounded child sessions with inherited-or-narrower authority; the mutating executor role works in a disposable git worktree — never your workspace — and its changes reach you only through a reviewed, per-file drift-refusing integration.
Five providers, one runtime, honest degradation. Anthropic, OpenAI, DeepSeek, Kimi and GLM across two genuinely different protocols. A shipped capability catalog carries each model's limits, so differences degrade honestly instead of hiding behind a false lowest common denominator: a model without image input gets a stored-as-evidence pointer rather than silently dropped pixels.
Honest limits, stated where they matter. The OS sandbox is real, Windows-only, and does not stop reads or network. Approved commands run unsandboxed. Command output is not scrubbed for secrets. Each of those sentences appears in the product, not just the docs. See Safety.
flowchart LR
U([you]) --> R[REPL or one-shot CLI]
R --> T["runTurn — the one agent loop"]
T --> M[["model<br/>(one of five providers)"]]
M -- tool call --> P{{"policy engine<br/>allow · ask · deny"}}
P -- ask --> U
P -- allow --> X["typed tools<br/>files · shell · checks · browser<br/>documents · research · git · remote"]
X --> E[("append-only<br/>event log")]
X --> S[("content-addressed<br/>snapshots")]
E --> T
E --> V["report · diff · resume · acceptance"]
V --> U
S -.->|undo| X
The loop the kernel runs is understand → plan → act → observe → verify → record → resume, and
every arrow above is the same one for a code edit, a PDF render, a web search or a git push. Both
interfaces — the interactive REPL and the one-shot CLI — are thin consumers of the same
runTurn, so there is no second execution path that could behave differently.
State lives outside your workspace (%USERPROFILE%\.agent-cli\), and the harness refuses to start
if it would land inside. Full detail: docs/ARCHITECTURE.md.
Requires Node 22+. The CLI checks and refuses older runtimes with one actionable line.
git clone https://github.com/earthwalker17/agent-cli.git
cd agent-cli
npm install # also builds src → dist (the `prepare` script)
npm link # optional: puts `agent` on your PATHNot on npm, deliberately. The name
agent-clihas belonged to an unrelated package since 2019, so this package is markedprivateand installs from a clone.npm linkinstalls a binary calledagent, which is a generic global name — if it collides with something you already have, runnode dist/cli/index.jsdirectly instead.
To actually run the agent you need an API key for at least one provider. Credentials are read only from the environment — never from a config file, a CLI flag, or a slash command, so they cannot end up in a log, a report, or an event:
export ANTHROPIC_API_KEY=sk-ant-... # PowerShell: $env:ANTHROPIC_API_KEY="sk-ant-..."agent providers lists every provider, which env var it needs, whether that var is set, and where
to get a key — without touching the network.
Running the agent costs money (it calls a model API). The test suite does not — it is hermetic and needs no key.
mkdir my-project && cd my-project
agentThe first run in a folder asks for trust — recorded consent, not a sandbox. Then just talk:
agent session 20260715-101730-5d56
workspace: C:\demo
› create a small node utility that counts words in a file
• write_file wordstats.mjs ✓
• run: node --test
⚠ approval required [shell command — labeled observe] run_command
[y] allow once [n] deny [q] deny & stop
Then, when you want to know what really happened:
agent diff # exactly what this session changed, with each file's CHECKED verdict
agent report # the evidence record, derived purely from the event log
agent undo # walk the last file change backYou mostly just type instructions. When a decision is genuinely required, the harness asks inline — one keystroke, at that moment. On a terminal every prompt is also an arrow-key menu, and the highlight always starts on the decline row, so Enter never grants anything. Typing still works exactly as before, and piped runs never see a menu.
The dozen commands worth knowing:
/diff /report [section] /status |
What changed, the evidence, the session state |
/undo [all] /checkpoint [list | restore <n>] |
Walk back a change, or a whole workspace state |
/commit [-m "msg"] |
Commit session-attributed changes, with preview and confirmation |
/plan [show | approve | discard] /accept |
The plan document, and the delivery boundary |
@plan <request> |
Investigate read-only, write a plan, and wait for your approval |
@review [focus] |
A read-only inspector over the codebase — blocks nothing, costs no review round |
@search / @research <question> |
One bounded web lookup, or a delegated research subagent |
/help · / · Tab · Ctrl+E |
The command surface, its menu, completion, and expanding folded output |
agent help prints the full surface without starting a session, and
docs/USAGE.md documents every command, flag, exit code and configuration knob.
One runtime, five providers, two protocols — plus a scripted mock provider that needs no network
and is how the whole loop is tested deterministically.
| Provider | Env var | Protocol | Default model |
|---|---|---|---|
anthropic |
ANTHROPIC_API_KEY |
Messages API | claude-opus-5 |
openai |
OPENAI_API_KEY |
Responses API | gpt-5.6-sol |
deepseek |
DEEPSEEK_API_KEY |
Chat Completions | deepseek-v4-pro |
kimi |
MOONSHOT_API_KEY · KIMI_API_KEY |
Chat Completions | kimi-k3 |
glm |
ZAI_API_KEY · ZHIPU_API_KEY |
Chat Completions | glm-5.2 |
/provider and /model switch mid-session, validate the key with a bounded probe, and record the
change as evidence — the env var name and the API host, never a credential. All five have been
exercised live through the real tool loop, on their default models; the other catalog entries are
documented, not individually live-tested. Base URLs are redirectable per provider (including the
China endpoints). See docs/USAGE.md.
| Capability | What that means here |
|---|---|
| Code | Ranked repository intelligence under a hard context budget, typed file tools, and typed verification across Node/TS, Python, Rust/Cargo and Go — with a missing toolchain as a first-class answer that names the exact install cure |
| Multi-project workspaces | Several projects, across ecosystems, discovered and kept apart: checks, previews and setups each name a project, and the harness refuses to guess when one is ambiguous |
| Shell | Managed execution with typed termination, real mid-command cancellation, verified tree kill, and demonstrably read-only commands auto-running inside the OS sandbox |
| Verification | The model names kinds, the harness names commands; managed preview servers; Playwright browser flows over the system browser with a typed failure taxonomy |
| Documents and PDFs | A spec authored as an ordinary workspace file, byte-deterministic DOCX, browser-printed PDF, parse-back validation of every artifact, and page rasterization so a vision model judges the real pages |
| Research | A bounded, budgeted, read-only path to the web that returns sourced claims rather than raw pages — and never counts as verification |
| Git | Probed repository context, an attributable session diff, deliberate session-scoped commits, and hidden-ref recovery checkpoints that leave your history untouched |
| GitHub delivery | Reading a remote and changing one are two separate authorities; a publish must cite a fresh observation of that exact ref, and asks every single time |
| Memory | Six bounded, auto-updating documents — your constitutions plus a journal, codebase summary, lessons, and perishable research notes — injected as context, never authority |
Trust, approval and sandbox are three different controls, and Agent CLI keeps them separate.
- Trust is recorded consent that you allowed the agent into a folder. It is not isolation.
- Approval asks before anything consequential, showing the exact command, target and effect.
- The sandbox is the OS technically confining a process — and it is narrow and Windows-only.
On Windows, when the startup probe passes, an auto-run command executes at Low integrity inside a Job Object. That genuinely denies writes to your workspace, profile, system directories and the harness state — at the kernel — and reaps the whole process tree on kill, including a detached grandchild. It does not stop reads, does not gate the network, and cannot hold service-reparented work. On any other platform, or if the probe fails, there is no enforcement and auto-run is disabled — every command asks. Fail closed.
Commands you approve run unsandboxed, at your full privileges, and their effects are not snapshotted and not undoable. The sandbox backs the auto-run decision; it is not applied to an approval. Undo is file-only. Command output is not scrubbed for secrets. Path checks are TOCTOU-racy by nature. Stronger isolation — network egress control, a read/confidentiality boundary, macOS/Linux enforcement — is honest future work.
Read docs/SAFETY.md before trusting this with anything sensitive. It is the
complete model, including every limitation above stated in full.
Lightweight is a claim about dependency surface and kernel size, not about a small line count —
so here are the real numbers, reproducible with find src -name '*.ts' | xargs wc -l:
| Source | 189 TypeScript files · 50,513 lines (36,196 excluding blanks and comments) |
| Runtime kernel | 14,123 lines — the loop, policy, event log, snapshots, exec, sandbox, providers, contracts. Everything else is a capability pack plugged into it through the same contracts |
| Runtime dependencies | 9 — and eight of them are confined to exactly one module each (@anthropic-ai/sdk, undici, playwright-core, diff, ignore, fflate, @rgrove/parse-xml, unpdf). Only zod is pervasive, and only as schema validation at the tool boundary |
| Frameworks | none. No web framework, no CLI framework (argv is node:util's parseArgs), no logger, no daemon |
| Tests | 2,416 hermetic tests across 151 files — real-OS sandbox, real-repository git, a local bare repo standing in as a real remote, real browser flows, real PDF print and rasterization |
Module boundaries are enforced by a test rather than by convention: no ../../ imports, shared/
is a leaf, sandbox/ is reachable only through its index, and the set of module cycles is frozen
and removal-only.
| Document | What it is for |
|---|---|
docs/USAGE.md |
The complete surface: commands, flags, exit codes, providers, configuration, memory, and every capability pack in detail |
docs/SAFETY.md |
The security model in full, and every honest limitation |
docs/ARCHITECTURE.md |
How the system is built: modules, contracts, load-bearing orderings. Start here to understand the code |
docs/ROADMAP.md |
How it evolved, and the deferred pool — what is deliberately not built yet, and why |
docs/PROJECT.md |
The long-term thesis, principles, and reference context |
CHANGELOG.md |
Release notes |
CLAUDE.md |
The operating contract given to the AI agent that develops this repository. Part of the build-in-public record, not user documentation |
npm run typecheck # tsc --noEmit, strict + noUncheckedIndexedAccess
npm test # vitest
npm run build # emit dist/The suite is hermetic — no network, no API key, no billing — and CI gates every change on Windows and Linux. Windows is the load-bearing leg: the Low-integrity sandbox suites and the win32 path rules execute only there, so it is the run that can actually falsify this project's claims. On Linux those suites skip (30 skipped versus 11), and the rest proves the runtime is genuinely cross-platform.
npm test needs dist/ to exist for the CLI smoke suite to run rather than skip — npm install
builds it for you, and CI verifies the entry point exists before testing.
Live provider smokes are opt-in and gated twice: they need AGENT_LIVE_TEST=1 and the
relevant provider's key env var, so a missing key skips cleanly instead of failing. They are not
wired into an npm script on purpose — a test that spends money should be typed out, not inherited.
$env:AGENT_LIVE_TEST=1; npx vitest run test/anthropic.test.ts # Anthropic adapter + default model id
$env:AGENT_LIVE_TEST=1; npx vitest run test/live-providers.test.ts # every provider whose key is setIssues and pull requests are welcome, and so is criticism — the useful parts of harsh review have
historically been the most valuable input this project gets. Start with
CONTRIBUTING.md: it covers the verification bar (evidence over narration), what
tends to get pushback, and which suites are platform-gated.
Security problems go through SECURITY.md, privately, not the public issue tracker.
v1.10.x — feature-complete for V1, and an open, build-in-public engineering effort. Every capability listed here has been exercised end to end against a real model provider on real work; where a claim rests on hermetic tests alone, the documentation says so in the same breath.
MIT © Eric Mono