OpenCrabs is a small Go coding-agent harness. It gives a model a conversation, an explicit set of tools, and a loop that executes tool calls. Everything else is replaceable Go code.
The command has:
- Source-visible model-family prompts, environment and project context, and instruction-file loading. Every generated section is shown as a system message in the transcript rather than hidden by the harness.
- No telemetry, account system, background service, or update mechanism.
- Global YAML configuration, optional project overlays, and credentials kept in a separate private file.
- A full-screen terminal UI with persistent sessions, streamed responses and reasoning, markdown and table rendering, syntax-colored code, side-by-side edit diffs, a context-window gauge, structured tool activity, optional Nerd Font file icons, command completion, and explicit runtime controls.
- Automatic context compaction, so a long session summarizes its own history instead of failing once the model's window fills.
- A local SQLite index of the workspace, so the model can find code by name or description without re-reading the tree.
- No provider SDK. HTTP request and response formats are implemented locally for Chat Completions, Anthropic Messages, and the OpenAI Responses API.
Go 1.23 or newer is required.
go test ./...
go build ./cmd/opencrabsInstall it on GOBIN with:
go install ./cmd/opencrabsOpenAI-compatible Chat Completions endpoint:
export OPENAI_API_KEY=...
opencrabs --model gpt-4.1 "inspect this repository and run its tests"Anthropic Messages API:
export ANTHROPIC_API_KEY=...
opencrabs --provider anthropic --model claude-sonnet-4-20250514OpenAI Responses API, which newer OpenAI models are served over exclusively:
opencrabs --provider responses --model gpt-5.1 "review the diff on this branch"Local OpenAI-compatible endpoint:
opencrabs \
--base-url http://localhost:11434/v1 \
--model qwen3-coder \
--approval autoWith no prompt argument, OpenCrabs reads a piped prompt or starts the terminal
UI. A short ASCII launch screen gives way to the conversation view. Enter
submits the composer and Alt+Enter inserts a newline. The composer grows with
its content up to a configurable maximum and shrinks back as text is removed.
Typing / opens command completion. Model calls run asynchronously; Ctrl+C
cancels an active call before exiting the UI.
The UI commands are:
| Command | Operation |
|---|---|
/agent [name] |
Select an inspectable built-in or configured agent preset |
/settings |
Change model, agent, reasoning, approvals, limits, and display |
/model [provider/model] |
Select from configured models or switch directly |
/think [variant] |
Select the current model's reasoning variant |
/sessions [ID] |
List or resume global sessions as name time path |
/new [name] |
Start a persistent session in the current workspace |
/rename NAME |
Rename the current session |
/clear |
Reset context to generated, explicit, and active preset instructions |
/system |
Show or hide the generated system context, which is hidden by default |
/help |
Show the command list |
/exit, /quit |
Exit |
The footer carries a context-window gauge: elapsed session time, tokens spent, compaction count, and how full the current prompt is against the model's window. It fills from the token usage each provider reports, so a provider that reports nothing shows no proportion.
Sessions are private JSON files under
$XDG_DATA_HOME/opencrabs/sessions, or
~/.local/share/opencrabs/sessions when XDG_DATA_HOME is unset. They store
messages, provider-native reasoning needed for continuation, explicit base
system messages, workspace path, model reference, agent, and think variant, but
never resolved credentials or headers. Resuming a session restores its workspace
tools and project configuration overlay.
All three providers stream text through a bounded SSE parser. Readable reasoning
returned by a provider is kept separate from final answer text; signed or opaque
reasoning data is preserved only for model continuation. The TUI shows a live
thought band and per-subagent thought tails. Provider streaming, thought
visibility, and file icon mode can be changed independently under /settings.
Nerd Font mode can also default on through OPENCRABS_NERD_FONT=1; automatic mode
uses conservative Unicode fallbacks because terminal font coverage cannot be
detected reliably.
Assistant text is rendered as markdown: headings, emphasis, code spans, links,
nested lists, blockquotes, and pipe tables with per-column alignment. Fenced code
is syntax-colored with Chroma. Inline $...$ and display $$...$$ mathematics is
transliterated into Unicode. Identifiers such as read_file keep their
underscores rather than being read as emphasis.
A write_file or edit_file call renders as a side-by-side diff with line
numbers, added and removed lines tinted underneath the syntax colors, and a
stacked layout on narrow terminals. The diff is shown only in the transcript; the
model still receives the short summary, so a diff is never re-sent on later turns.
printf 'summarize the module layout' | opencrabs --model gpt-4.1The command exposes these tools:
| Tool | Operation | Approval |
|---|---|---|
read_file |
Read line-numbered text | No |
list_files |
Recursively list files with glob filtering | No |
search |
Search text with a Go regular expression | No |
write_file |
Create or replace a complete file | Yes |
edit_file |
Perform an exact, unique text replacement | Yes |
shell |
Run /bin/sh -lc in the workspace |
Yes |
git |
Read-only status, diff, log, blame, show, branch |
No |
web_fetch |
Fetch one HTTP or HTTPS URL as text, markdown, or HTML | Yes |
memorygrep |
Search the workspace index by symbol, path, or description | No |
find_definition |
Locate a symbol declaration by name, with file and line | No |
file_outline |
Summary, imports, and declarations for an indexed file | No |
index_status |
Report index contents, optionally rebuilding it | No |
notes |
Cross-turn scratchpad shared with delegated agents | No |
Presets that can delegate additionally receive task_spawn, task_status, and
task_kill. Delegation itself needs no approval because each delegated tool call
is still authorized by the same approval policy. These three are appended after a
preset's tool allowlist, so a preset cannot grant itself delegation by naming it.
git exposes no mutating operation, which is why it needs no approval. It runs
git with an explicit argument list and no shell, always with --no-pager, and
rejects values beginning with - so an argument cannot become an option.
web_fetch resolves DNS before connecting and refuses loopback, private,
link-local, and cloud-metadata addresses, checking every address a name resolves
to rather than only the first. Redirects are capped and each hop is revalidated,
so a public URL cannot redirect into an internal host.
The index and notes tools are optional. If the SQLite index or the notes file cannot be opened, that group is dropped, a notice is printed, and the rest of the session runs normally.
Filesystem tools reject lexical and symlink paths outside --workdir when each
path is resolved. These checks are not hardened against another process racing
to replace path components. shell is not a sandbox: a permitted command has
the same access as the OpenCrabs process.
Writes use same-directory temporary files and atomic rename. Existing
permission bits are preserved, but inode identity, ownership, ACLs, and extended
attributes are replaced. Newly created files use mode 0600.
Approval modes are explicit:
askprompts before each write, edit, or shell call. This is the default.autoruns approval-requiring tools without prompting.denyreturns a denial as the tool result without executing it.
For piped input, use auto or deny; the input stream cannot also answer
interactive approval prompts.
Global configuration defaults to ~/.config/opencrabs/config.yml. An optional
.opencrabs.yml in the workspace overlays defaults, providers, models, and
agents. Credentials default to ~/.config/opencrabs/credentials.yml; OpenCrabs
requires that file to be inaccessible by group and other users.
At session creation OpenCrabs selects an inspectable prompt family for Codex,
GPT/o-series, Gemini, Claude, or a generic fallback. It adds the canonical
working directory, git status, platform, date, selected model, and a bounded,
deterministic project tree. It then loads the first workspace rule file among
AGENTS.md, CLAUDE.md, and CONTEXT.md, the first available global rule file,
and configured instructions patterns. Project instructions append to global
patterns with stable deduplication; *, ?, and ** globs are supported.
The precedence for runtime selections is command-line flag, matching
OPENCRABS_* environment variable, project YAML, then global YAML. Provider
credentials can reference secret:NAME, env:NAME, ${NAME}, or an explicit
literal:VALUE. OPENCRABS_API_KEY overrides the selected provider credential;
the standard OPENAI_API_KEY and ANTHROPIC_API_KEY variables are final
protocol-specific fallbacks, with OPENAI_API_KEY covering both the Chat
Completions and Responses protocols.
Example global configuration:
version: 1
instructions:
- AGENTS.md
- docs/**/*.md
defaults:
model: custom/coder
think: high
approval: ask
providers:
custom:
name: Custom API
protocol: openai
base_url: https://api.example.test/v1
api_key: secret:custom
models:
coder:
name: Coder
family: gpt-5
reasoning: true
tool_call: true
variants:
high:
reasoningEffort: high
agents:
focused-review:
description: Review correctness without changing files
model: custom/coder
think: high
approval: deny
tools: [read_file, list_files, search]
prompt: |
Lead with concrete bugs and behavioral risks. Cite file locations and
identify missing tests. Do not modify files.The built-in general, build, plan, review, debug, explore, and
orchestrate definitions are Markdown documents with front matter, embedded in
the binary from preset/prompts/. A configured agent can override a built-in
name or add a new preset, either through the agents block or by dropping a
Markdown file into ~/.config/opencrabs/agents. Its prompt may be inline with
prompt or loaded from a workspace-relative or absolute prompt_file. Selecting
a preset records its name in the session and shows its prompt after generated and
explicit system messages.
plan, review, and explore deny approval and expose only read-only tools:
the three workspace readers plus git, notes, and the four index tools. A
preset that sets delegate also receives the delegation tools; a preset that sets
coordinator depends on them and is therefore never offered as a delegation
target.
Model-family prompts and tool guidance are embedded the same way, under
systemprompt/prompts/, and can be overridden from ~/.config/opencrabs/prompts.
Defaults are compiled in, so a fresh install behaves identically with no config
directory, and a malformed override fails at startup with its path rather than
silently weakening the prompt.
Presets that set delegate receive task_spawn, task_status, and task_kill,
and can hand independent workstreams to the other presets. task_spawn returns
task IDs immediately, so the primary agent keeps working while delegates run;
task_status polls or blocks until results arrive, and task_kill cancels tasks
to free concurrency slots. Delegated agents run concurrently, bounded by
--max-subagents, and each one gets:
- Its own conversation history, so no delegated agent sees the primary conversation or its siblings. Task prompts must be self-contained.
- Its preset's own model-family context, reasoning variant, tool set, and turn limit.
- The shared approval policy, so a delegated write or shell call is still authorized the same way.
- No delegation tools, so a delegated agent cannot delegate further. Recursion is prevented structurally rather than by a depth counter: delegated sessions are built from the shared tool set, which never contains them.
A failing task does not cancel its siblings, so the coordinator can reconcile
partial results. The terminal UI shows running and finished delegates with current
tools, thoughts, outcomes, and elapsed time; a task stopped with task_kill is
reported as canceled rather than failed.
Every turn re-sends the whole conversation, so the context window is the budget a session actually spends. OpenCrabs tracks it from the token usage each provider reports and shows it in the footer.
When the prompt reaches 80% of the model's window, the older half of the history is replaced by a model-written summary. Recent turns and the leading system messages survive verbatim, and the split never separates a tool result from the call that produced it, because providers reject that. Summarizing rather than dropping matters because the early turns usually hold the task definition and the decisions already made.
Compaction needs to know the window. A model's limit.context in configuration
always wins, since a gateway may cap a model below its upstream capacity. When no
limit is configured, OpenCrabs fills it from
models.dev, fetched at startup and cached for a day under
$XDG_CACHE_HOME/opencrabs/models.json. Lookup tries the exact provider first and
then the bare model ID, which is what makes it resolve for gateways that expose
upstream models under their own provider name. The catalog is advisory: a stale
cache is preferred to a failed launch, a fetch error is reported rather than
fatal, and --offline skips the request entirely. Without a known window the
gauge shows tokens spent and compaction stays off.
Manage and inspect configuration with:
opencrabs config path
opencrabs config show
opencrabs config providers
opencrabs config models
opencrabs config validate
printf '%s' "$CUSTOM_API_KEY" | opencrabs config set-secret custom
opencrabs config remove-secret customconfig show redacts inline API keys. To import OpenCode JSONC providers,
models, variants, instruction patterns, custom authentication, and credentials:
opencrabs config import-opencode \
--default-model packycode-openai/gpt-5.6-sol \
~/.config/opencode/opencode.jsoncUse --replace to replace imported provider IDs that already exist. Without
it, conflicting providers are rejected.
| Flag | Environment | Default |
|---|---|---|
--provider |
OPENCRABS_PROVIDER |
Inferred from model or openai |
--model |
OPENCRABS_MODEL |
YAML default, otherwise required |
--think |
OPENCRABS_THINK |
YAML default or provider default |
--base-url |
OPENCRABS_BASE_URL |
Provider API URL |
--api-key |
OPENCRABS_API_KEY |
Provider-specific key variable |
--config |
OPENCRABS_CONFIG |
Global XDG config path |
--credentials |
OPENCRABS_CREDENTIALS |
Global credentials path |
--project-config |
OPENCRABS_PROJECT_CONFIG |
<workdir>/.opencrabs.yml |
--no-project-config |
None | false |
--offline |
None | false |
--workdir |
OPENCRABS_WORKDIR |
. |
--system-file |
OPENCRABS_SYSTEM_FILE |
No system message |
--approval |
OPENCRABS_APPROVAL |
YAML default, otherwise ask |
--agent |
OPENCRABS_AGENT |
general |
--max-turns |
OPENCRABS_MAX_TURNS |
32 |
--max-subagents |
OPENCRABS_MAX_SUBAGENTS |
4 |
--max-tokens |
OPENCRABS_MAX_TOKENS |
4096 for Anthropic and Responses |
--max-output |
OPENCRABS_MAX_OUTPUT |
65536 bytes |
--command-timeout |
OPENCRABS_COMMAND_TIMEOUT |
2m |
--request-timeout |
OPENCRABS_REQUEST_TIMEOUT |
10m |
agentowns messages, interfaces, session history, the model-tool loop, token accounting, and automatic compaction.approvalsuppliesask,auto, anddenyimplementations.provider/openaiimplements OpenAI-compatible Chat Completions.provider/anthropicimplements Anthropic Messages.provider/responsesimplements the OpenAI Responses protocol, including encrypted reasoning replayed verbatim across turns.internal/sseis the bounded Server-Sent Events parser the providers share.configowns strict YAML loading, merging, credential references, model resolution, and OpenCode import.modelsdevsupplies advisory model metadata with an on-disk cache.sessionowns versioned global conversation persistence.presetowns visible built-in agent definitions.promptfileloads prompt documents with front matter from an embedded filesystem and an optional override directory.subagentruns delegated sessions in the background and exposes the spawn, status, and kill tools.systempromptowns visible model-family prompts, environment context, project trees, the generated tool inventory, and instruction loading.settingdefines runtime settings shared with interactive frontends.toolsupplies the workspace, shell, git, web, index, and notes tools.indexmaintains the SQLite codebase index the lookup tools query.diffcomputes line diffs as row-aligned edit scripts for side-by-side display.tuisupplies the Bubble Tea terminal interface, its markdown, table, LaTeX, and diff renderers, and the approval handoff.cmd/opencrabsis terminal wiring only.
Embed the loop by implementing the small interfaces in agent:
model := myModel{}
tools := []agent.Tool{myTool{}}
session, err := agent.NewSession(model, tools, agent.Options{
Approver: myApprover{},
MaxTurns: 16,
})
if err != nil {
return err
}
answer, err := session.Submit(ctx, "fix the failing test")agent.Model, agent.Tool, and agent.Approver are independent. A new
provider, tool protocol, UI, or permission model does not require changing the
session loop.
- Compaction depends on a provider reporting token usage and on a known context window. A provider that reports neither gets no gauge and no compaction, and the conversation is sent in full until the provider rejects it.
- A compacted conversation is lossy by construction. The summary keeps decisions, paths, and open work, but earlier turns are no longer available verbatim.
- OpenAI transport targets Chat Completions, not the Responses API.
- Visible reasoning depends on what the selected provider returns; OpenCrabs does not fabricate hidden reasoning for APIs that omit it.
- There is no plugin loader or MCP client. Tools are ordinary Go values.
- The workspace index extracts symbols with per-language regular expressions rather than real parsers, so results are approximate and unlisted file extensions are not indexed.
- Delegated agents share one workspace with no file locking, so concurrent writing agents can conflict. Prefer read-only agents for parallel work.
- Delegation is one level deep by design, and delegated results are returned only as tool output rather than as a shared conversation.
- Interactive approval prompts serialize, so several delegated agents requesting approval at once are answered one at a time.
- Workspace path checks reduce accidental filesystem escape but do not create an operating-system security boundary.
The source follows the Google Go Style Guide
and is formatted with gofmt.