Guidance for agents (and humans) working on this repository. Read this before any non-trivial change.
bee is a pure-Go single-binary coding agent. Three intentional wedges over other CLI coding agents:
- Skills are
bee <name>subcommands.~/.bee/skills/<name>.mdis invokable asbee <name> [args...]— one binary, one PATH entry, no shell shims sprayed onto$PATH. Unknownarg[1]falls through to skill-registry lookup viadispatchSkill. - Skills are agent endpoints. Four kinds:
prompt|exec|mcp|http. The same skill is surfaced both as abee <name>subcommand AND a model-callable tool the agent can invoke mid-task. - Tiny-context friendly. System-prompt budget is configurable per-profile (
tiny|normal|large|auto); memory injection is lazy top-k; tool descriptions and skill manifest are token-budgeted. Designed to run against a 4k-context local Ollama as well as frontier APIs.
Other load-bearing choices: apply_patch collapses write/edit/multi-edit on capable models (tiny profile swaps it out for write+edit+hashline_edit); codex-style two-axis sandbox; frontmatter knowledge store with lazy top-K selection; textmode wrapper that emits XML-style tool calls for local models that ignore tool_calls.
bee is designed to work well with local LLMs. Key features:
- Text mode (
ToolFormat="xml"in profile): injects an XML-style tool advert into the system prompt and parses<tool>{...}</tool>envelopes out of the assistant content stream. Designed for tiny/local models that ignoretool_calls. - Profile auto-detection: local providers (
ollama/lmstudio) always resolve to thetinyprofile. - Caveman rules: prompt-injection compression rules (
rules/{full,lite,ultra}.md) that compress bee's responses without affecting user input. - Stub provider:
BEE_TEST_PROVIDER=stubfor offline development and testing. - 4k-context friendly: the whole system is designed to run on a 4k-context local Ollama as well as frontier APIs.
go build ./... # full build (must stay clean)
go build -o ~/.local/bin/bee ./cmd/bee # install local
go test ./... # all tests
go test ./internal/<pkg>/... -run TestName # single package or test
go vet ./... # vet (must stay clean)
golangci-lint run # uses .golangci.yml (govet/errcheck/staticcheck/ineffassign/unused/misspell)End-to-end smoke without network (used by CI and during development):
BEE_TEST_PROVIDER=stub ./bee run --headless "anything"
BEE_TEST_PROVIDER=scripted BEE_TEST_SCRIPT=<fixture.jsonl> ./bee run "..."Real OpenRouter smoke (requires OPENROUTER_API_KEY):
./bee run "say hi in three words"
BEE_PROVIDER=anthropic BEE_MODEL=claude-sonnet-4-5 ./bee run "say hi in three words" # override provider/model inlineFirst-run is implicit: bee run / bee / bee <skill> all call ensureFirstRun, which creates ~/.bee/skills and drops the bundled skills the first time it sees an empty dir. User edits are preserved on subsequent installs.
Override $HOME via BEE_HOME=/tmp/iso for hermetic install tests.
cmd/bee/main.go dispatches a small fixed set; everything else falls through to skill lookup.
| Command | What it does |
|---|---|
run / -p / --print |
Headless single-shot run. Engine + stdout, no TUI. |
| (none) | TUI (tui.go). Same Engine wiring as run. |
back |
Re-enter a previous session by id or tree branch — replays history. |
fan |
N independent engines, same prompt, parallel. |
swarm |
Planner decomposes → worker pool executes → planner synthesizes. |
hyperplan |
5 critic engines + 1 synthesizer queen over a plan draft. |
hive |
Long-running multi-bee pool view (same runtime as swarm/fan). |
bg |
Re-exec headless with a pinned session id, detached via Setsid. |
agents |
TUI overview of parallel detached agents — each chat submit spawns one in its own worktree. |
remote-control |
Serves a local web relay (URL + QR) to drive bee from another device over the LAN. |
zzz |
Overnight autonomous-commit loop. Same engine, sentinel-driven stop. |
doctor |
Read-only preflight: provider keys, sandbox tools, ollama probe, models cache. |
bench |
Small-model benchmark harness — scored task suite with ledger + holdout split. |
version / -v / --version |
Build version. |
help / -h / --help |
Usage. |
| anything else | dispatchSkill(arg[1], rest...) → headless run with --skill <name>. |
stub_provider.go is gated by BEE_TEST_PROVIDER=stub (or scripted for fixture-driven runs) so the binary works offline in tests.
Clean types → provider → tools → loop → ui stack. Internal packages talk via the interfaces in internal/types, internal/llm/provider.go, internal/tools/registry.go. Implementations stay swappable.
-
cmd/bee/— entry + subcommand wiring.main.gois a stdlib switch (see table above).run.gois the headless engine path;run_tools.gobuilds the tool registry (with optionalwriteRepath filter for confined runs and anapproval.Approverfor dangerous-command gating);run_provider.goresolves provider/model from config + env.tui.gowires the same Engine into the Bubbletea app.fan.go/swarm.go/hyperplan.go/hive.gobuild N engines for multi-bee work.bg.godaemonizes;agents.goopens the parallel-agents pane;zzz.goruns the overnight loop.doctor.gois the preflight.stub_provider.gois gated byBEE_TEST_PROVIDER. -
internal/loop/— the agent turn loop.Engine.Run(ctx, userMsg)selects knowledge entries → assembles system prompt → streams provider events → dispatches tool calls serially → folds results → recurses. HardMaxIterationscap (config + profile override).Role ∈ {worker, scout, queen}(role.go): worker = full surface with a per-turn read|act classifier (a read-only turn uses thereadOnlyToolswhitelist), scout = read-only research + web (web_search/web_fetch), queen = spawns a hive (TUI only). Each role bakes its own reasoning budget (RoleThinking);yolois a separate auto-approve toggle, not a role. Legacymode/mastermindconfig keys migrate forward on load (config/load.go).compact.gosummarizes mid-history when context fills;recap.goproduces post-turn end-of-task recaps when enabled.done_signal.go+ sentinel markers let unattended loops detect "I'm done".sandbox_wrap.gowraps shell calls with the active sandbox policy.KnowledgeStoreis an interface so the loop never importsinternal/knowledgedirectly. -
internal/llm/—Providerinterface + adapters. Built-ins:openai_compat.go— OpenRouter / OpenAI / DeepSeek / Groq / Ollama / LM Studio viabase_url + wire_api=chat. Streaming inopenai_compat_stream.go; stall-watchdog inopenai_compat_stall_test.go.claude.go/anthropic.go— native Anthropic Messages API (wire_api=anthropic-messages), streaming inclaude_stream.go, thinking-block aware.chatgpt.go— OAuth-backed ChatGPT account viainternal/auth(wire_api=responses); request/stream split acrosschatgpt_request.go/chatgpt_stream.go, withchatgpt_auth.go/chatgpt_models.gofor token + model handling.gemini.go— native Google Gemini (wire_api=gemini).textmode.go— wraps anotherProvider, injects an XML-style tool advert into the system prompt and parses<tool>{...}</tool>envelopes out of the assistant content stream. Opt-in per profile viaToolFormat="xml"for tiny/local models that ignoretool_calls. Parser intextmode_parse.go.thinking_hybrid.go— handles providers that emit reasoning in a side channel vs. inline<thinking>tags.models.go+models_cache.go+models_hardcoded.go— model registry with on-disk cache; pricing fuelsinternal/cost.wire/— translates internaltypes.Message/ToolUse/ToolResultto/from each provider's wire format:openai.go/openai_stream.go,anthropic_messages.go/anthropic_messages_stream.go,responses.go/responses_stream.go,gemini.go. Internal message types are agent-owned — never leak provider SDK types upward.mockprov/— fixture-drivenProviderfor scripted e2e tests.
Built-in provider blocks ship in internal/config/defaults.go (Providers map); override per-project in ~/.bee/config.toml. The env-var in the EnvKey column is read at startup unless KeyOptional=true.
| Provider | Base URL | Wire API | Env Key | Auth |
|---|---|---|---|---|
openrouter |
https://openrouter.ai/api/v1 |
chat |
OPENROUTER_API_KEY |
key |
openai |
https://api.openai.com/v1 |
chat |
OPENAI_API_KEY |
key |
anthropic |
https://api.anthropic.com/v1 |
anthropic-messages |
ANTHROPIC_API_KEY |
key |
gemini |
https://generativelanguage.googleapis.com/v1beta |
gemini |
GEMINI_API_KEY |
key |
ollama |
http://localhost:11434/v1 |
chat |
— | none (local) |
omlx |
http://localhost:8000/v1 |
chat |
OMLX_API_KEY |
key (optional) |
chatgpt |
https://chatgpt.com/backend-api/codex |
responses |
— | OAuth (/login chatgpt) |
Any new OpenAI-compatible endpoint (DeepSeek, Groq, Together, custom proxy, …) is a config-only addition — no code, just a [providers.<name>] block:
default_provider = "openrouter"
default_model = "anthropic/claude-sonnet-4.5"
[providers.openrouter]
base_url = "https://openrouter.ai/api/v1"
wire_api = "chat"
env_key = "OPENROUTER_API_KEY"
default_model = "anthropic/claude-sonnet-4.5"
reports_cost = true
[providers.deepseek]
base_url = "https://api.deepseek.com/v1"
wire_api = "chat"
env_key = "DEEPSEEK_API_KEY"
default_model = "deepseek-chat"-
internal/tools/— current surface (Spec name intools/<dir>/<dir>.go):- Read-side:
read,search(regex grep, code ininternal/tools/grep/),glob(filename match, code ininternal/tools/find/),ls,godoc(go doc -shortfor a package/symbol),codegraph(symbol-relationship queries over the project's CodeGraph index). - Web:
web_search(Brave Search API, top-5 results),web_fetch(fetch + extract a URL). - Write-side:
apply_patch(unified-diff multi-edit; tiny profile skips it),write,edit(search-and-replace, code ininternal/tools/edit_diff/),hashline_edit(line-number based, robust on tiny models). - Shell:
bash(code ininternal/tools/shell/, wrapped by sandbox policy +internal/approvalfor dangerous-command gating). - Knowledge:
knowledge_search,knowledge_write— frontend tointernal/knowledge. Disabled when[memory] enabled=false. - Procedure memory:
waggle_lookup— list or run a crystallized read-only route (waggle) on demand. Registered only when the library is non-empty and[waggle] enabled(default on); seeinternal/waggle/. - Meta:
tool_lookup— model-callable "what tools do I have, and how do I use them?" Reads back from the registry so it always sees the live filtered surface, including user-defined tools.ask_user— pose a multiple-choice question and block on the user's pick (auto-resolves headless viainternal/ask).escalate— signal "stuck, same approach failed repeatedly" for unattended-loop handling. - Config-defined:
usertoolwraps[[user_tools]]entries from~/.bee/config.tomlas model-callable subprocess tools. - Common:
truncate.gocaps tool-result payload at the profile'sToolOutputTokens;relpath.gokeeps paths repo-relative;argparse.gonormalizes mixed-shape inputs from different providers.
buildToolsFiltered(cwd, writeRe)incmd/bee/run_tools.gothreads a write-path regex into every mutation tool for confined runs.Engine.Rundispatches a turn's read-only tools concurrently and runs mutators/shell serially as barriers (seedispatchToolsininternal/loop/turn_tools.go): all in-flight reads complete before a mutator runs, and nothing new starts until it returns, preserving happens-before and avoiding sandbox contention. - Read-side:
-
internal/prompt/— assembles the per-turn system prompt: caveman rules + identity + tool manifest + skills + selected memories. Honors the active profile'sSystemPromptBudgetby truncating low-priority sections.atexpand.goresolves@filereferences;context.go/context_warning.gotrack approaching window limits. -
internal/commands/— slash-command registry for the TUI (/login,/logout,/compact,/model, etc.). Commands depend on aSideinterface implemented by the TUI so the registry stays decoupled from Engine/TUI internals. -
internal/auth/— OAuth 2.0 PKCE flow for the ChatGPT provider.flow.godoes the token exchange,server.gois the loopback callback listener,jwt.godecodes the OIDCid_tokento cachechatgpt_account_id,storage.gopersists tokens to~/.bee/auth/. -
internal/approval/— gates dangerous shell commands behind a user decision.safety.DetectDangerousflags a command →Approver.Requestasks. Decisions cache for the session;AllowAlwayspersists via the caller-supplied callback (writesconfig.command_allowlist). CLI implementation for headless; TUI implements its own approver.Static{AllowOnce}is the auto-approve path for the--yes/--yoloflag or the persistedyolotoggle (cfg.Yolo). -
internal/cost/— process-local thread-safe tracker for per-turn token usage and dollar cost. Consumed by the TUI status bar (live session total) and the cost-monitor pane (historical breakdown). Prices come fromllm/models.go. -
internal/safety/— defense-in-depth guards on top of the sandbox: secret redaction on tool output, path/shell-command checks that refuse obviously sensitive targets (~/.ssh,.env, etc.) even when sandbox scope would allow it.DetectDangerousfeeds the approval gate. -
internal/jsonmode/— NDJSON event emitter forbee run --json. Decoupled fromllm.Usageto avoid an import cycle. -
internal/skills/— parser (parse.go), in-memory registry (registry.go). Skills are surfaced via thebee <name>dispatcher incmd/bee/main.go— there are no shell shims or PATH mutations.bundled/ships defaults (about.md,calc.md,caveman-commit.md,caveman-review.md,check-tests.md,criticize.md,efficient-search.md,explore.md,hermes.md,plan.md,research.md,session.md,ultraplan.md) asembed.FS;WriteDefaultsis called on first run and preserves user edits. -
internal/knowledge/— per-project on-disk knowledge store. Frontmatter MD records with freeform tags + explicit priority + optional expiry. Parallelscan.goreads headers only (mtime-sorted, capped);query.gocalls a side-channel LLM to extract 1–3 keyword hints and ranks entries by tag overlap + priority + recency.age.goproduces freshness annotations; >1d-old records get a "verify before asserting" warning when injected. -
internal/waggle/— procedure memory. The miner (recorder.go/miner.go/manager.go) watches the loop's read-only tool stream and crystallizes a route seenK+ times into a runnable exec-skill under~/.bee/waggle/{proj/<hash>,user}/skills/*.md. Predictive replay (replay.go) matches a stored prefix in Go at zero prompt cost and runs the literal remainder off the model's path, folding output into the triggering tool result; read-only, so a wrong match wastes a little read work and never mutates.ledger.gologs reuse and divergences;bee waggle ls|gc(list.go/curate.go) ranks by estimated tokens saved, prunes stale routes, demotes chronically-diverging ones, and promotes cross-project routes to user scope.factory.gobuilds the per-engineManager/Replayer. Wired viaEngine.Waggle/Engine.Replay, gated by[waggle] enabled(default on), active in worker/scout/queen and queen-spawned hive workers (each worker gets its own instances). Never enters the system prompt, so context cost stays O(1) in library size. -
internal/sandbox/— codex two-axis policy:scope ∈ {read-only, workspace-write, danger-full-access}×approval ∈ {untrusted, on-request, on-failure, never}.macos.gobuildssandbox-execprofiles;linux.gobuildsbwrapinvocations;windows.gostubs to WSL2.Wrap(p, cmd)is dispatch-on-runtime.GOOS. Graceful degrade: whenbwrap/sandbox-execis missing, returns the original cmd plus a warning — the sandbox is best-effort hardening, not a security boundary. -
internal/caveman/— prompt-injection compression. Rules embedded asembed.FS(rules/{full,lite,ultra}.md).Inject(systemPrompt, level)prepends. Default isFull. Caveman applies to bee's responses, not user input. -
internal/config/— TOML config with merge chain:Defaults() → ~/.bee/config.toml → env (BEE_MODEL, BEE_PROVIDER, BEE_CAVEMAN, BEE_PROFILE). Profiles (tiny|normal|large|auto) tune the system-prompt budget, memory top-k + body cap, tool description chars, skill manifest chars, caveman level, iter cap, tool-output token cap, sampling temperature/top-p, read default/max lines, grep match cap, and whetherapply_patchships in the manifest (SkipApplyPatch).autoresolves viaResolveAutoProfileForProvider(provider, model)— local providers (ollama/lmstudio) always resolve totiny.local_provider.gohandles ollama/lmstudio probing;scale.gorescales budgets on context-window changes. -
internal/session/— append-only JSONL rollouts under~/.bee/sessions/<uuid>.jsonl. Parent-pointer tree viabranch.go(BuildTree,LinearPath) — message history is a tree, not a list.Appendis mutex-guarded with sync-on-write. -
internal/hive/— multi-bee swarm runtime.Pool(fan-out, semaphore-bounded, ctx-cancellable) andQueen(planner decomposes a task into ≤8 sub-tasks → workers execute → planner synthesizes). The runtime concept (hive.Worker) is intentionally separate from the UI concept (tui.Bee). -
internal/agents/— per-agent worktree + lockfile lifecycle forbee agents.spawn.godetaches a headless engine into its own git worktree with a pinned session id;lock.goclaims the worktree so a second spawn can't collide;detach_unix.go/detach_other.goare the platform branches;merger.gohandles bringing changes back;clear.gocleans up finished agents. -
internal/worktree/— low-level ephemeralgit worktreecheckout helper so concurrent workers each mutate files without racing on one shared tree. Used byagents/hive/zzz; distinct from the higher-level lifecycle ininternal/agents. -
internal/remote/— thebee remote-controlweb relay:server.goserves a small control UI,sse.gostreams turn events,lan.goresolves the LAN address,qr.gorenders the connect URL as a terminal QR code. -
internal/ask/— backs theask_usertool: poses a multiple-choice question and blocks until the user picks. Mirrors the approval gate — TUI surfaces an interactive picker, headless runs auto-resolve so nothing hangs. -
internal/goal/— powers the/goalcompletion-condition loop: keep running turns until a fast model judges a user-specified condition met. Pure state + bookkeeping, nollmimport. -
internal/bench/— thebee benchsmall-model benchmark:task.go/checks.godefine scored tasks,score.go/metrics.gograde runs,ledger.gopersists results,holdout.gokeeps a held-out split,report.gorenders the summary. -
internal/bgreg/— per-session status sidecar for background bees. The bg engine writes one JSON file per session at<beeHome>/sessions/bg/<id>.status.json(temp+rename for atomic replacement); the agent-view TUI reads it.gc.goevicts stale entries;inbox.gois the cross-agent message hand-off. -
internal/sentinel/— centralized loop-control markers an unattended agent uses to signal turn outcomes. Bothbee zzzandbee agentsspeak the same regex protocol; the status enums they write to disk stay distinct (zzz tracks RUN lifecycle, bgreg tracks AGENT-turn state). -
internal/zzz/— the overnight-loop driver.loop.goruns turn → commit → next-objective with sentinel detection;git.godoes the commit;gc.goevicts old artifacts;drive.gois the supervisor. -
internal/update/— probes GitHub for new commits onmain, applies updates by re-runninginstall.shin a subprocess. Used by the TUI background-checker.Probeis cheap + side-effect-free (safe on a timer);Applyis only invoked from an explicit user decision in the modal. -
internal/tui/— Bubbletea.app.gois the root model;app_update*.gosplits the update reducer by concern (stream, panes, pickers, session, gates);app_pumps.goruns the per-turn side calls (recap, mode classifier).view.gorenders top bar + scrollback + bottom bar;stream.godoes role glyphs (▸for user; tool turns intentionally have none), markdown via glamour, and ANSI-strips tool output before display (raw escapes from subprocesses likego testwould otherwise blit over chrome in altscreen).palette.go/picker.gois the fzf-styleCtrl+Ppalette (provider / model / skills / slash-commands in one);hive.go/workspace.go/session_tree.go/agents/are auxiliary panes (Ctrl+H/Ctrl+W/Ctrl+T/Ctrl+A).csi_input.godecodes CSI-u keyboard input. Slash commands route throughinternal/commandsvia theSideadapter inside.go.
- Pure Go, no CGo. Single static binary on darwin/linux/windows. New deps must be CGo-free.
- ≤300 lines per file. Split if a file grows; see
wire/openai_stream.gofor an example split. - Internal types own the wire boundary. Add a new provider by writing an adapter under
internal/llm/that translates to/fromtypes.Message/ToolUse/ToolResult. Do not propagate provider SDK types into other packages. Engine.Rundispatches read-only tools concurrently, mutators serially. A mutator/shell call is a barrier: in-flight reads drain before it runs, and nothing new starts until it returns (dispatchToolsininternal/loop/turn_tools.go). A read-only tool that blocks delays only its own batch; a mutator that blocks stalls the whole turn — keep that in mind when adding one.- No provider name-drops in code comments. Describe behavior, not vendor ("OpenAI-compatible chat completions wire" beats "OpenAI / DeepSeek / Groq"). Vendor names are fine in user-facing strings and config defaults.
- Pre-set
lipglossdark background + glamourWithStandardStyle("dark")before Bubbletea grabs stdin. Seecmd/bee/tui.gofor why (Ghostty/iTerm reply to OSC 11 queries with bytes that leak into the textinput in altscreen mode). - TUI styles live in
internal/tui/style.go. Palette is a layered neutral scale (Oyster → Squid → Smoke → Ash → Butter foregrounds; Pepper → BBQ → Charcoal → Iron backgrounds) with a single honey accent (#FFB000). Borrowed from charmbracelet/charmtone but inlined to avoid the dep. Chrome stays dim; the bee glyph carries the accent. - Tests are first-class. Every package has
_test.gosiblings;go test ./...must stay green. UseBEE_TEST_PROVIDER=stuborscriptedfor offline e2e — never hit a real API from a unit test.
-
A new tool: create
internal/tools/<name>/, implementtools.Tool(Spec+Run), exportNew() tools.Tool(andNewWithFilterif it mutates files —buildToolsFilteredexpects it). Wire it into bothbuildToolsWithApproverandbuildToolsFilteredWithApproverincmd/bee/run_tools.goso headless + TUI + fan + swarm + agents + write-confined runs all pick it up. If it should be read-only safe (available to scout and read-only worker turns), also list it ininternal/loop/role.go(readOnlyTools, orscoutExtraToolsfor scout-only web tools). -
A new slash command: implement
commands.Commandand register it ininternal/commands/builtins.go. If it needs Engine/TUI state, add the method to theSideinterface and implement it ininternal/tui/side.go. -
A new provider: add
internal/llm/<name>.goreturning theProviderinterface plus awire/<name>.gotranslator. Wire it into theWireAPIswitch incmd/bee/run_provider.go. If it's OpenAI-compatible, just add a[providers.<name>]block tointernal/config/defaults.go—openai_compat.gohandles it without code. For local/tiny models, consider settingToolFormat="xml"in the matching profile sotextmodewraps the provider. -
A bundled skill: add a
.mdfile underinternal/skills/bundled/with the right frontmatter (type,description, etc.). It auto-installs on first run and is invokable asbee <skill-name>. The skill is also exposed as a model-callable tool unless the frontmatter opts out. -
A new TUI pane: define a sentinel
openXMsginapp.go, bind a key inkeymap.go, write the component ininternal/tui/<pane>.go(orinternal/tui/<pane>/if it grows). UselipglossWidth/truncateVisiblefromutil.gofor ANSI-safe sizing. Route any slash command for it throughinternal/commands+side.go, not directly into the model. -
A new profile: add an entry to
internal/config/defaults.go'sProfilesmap and a branch inResolveAutoProfileForProviderif it should be selectable viaprofile="auto". TuneSystemPromptBudget,MemoryTopK,ToolDescChars,SkillManifestChars,ToolOutputTokens,ReadDefaultLines/ReadMaxLines,GrepMaxMatches,Caveman, and the sampling params together.
| Var | Purpose |
|---|---|
BEE_HOME |
Override ~/.bee (hermetic tests). |
BEE_PROVIDER |
Override config default_provider. |
BEE_MODEL |
Override config default_model. |
BEE_PROFILE |
Override config profile (tiny/normal/large/auto). |
BEE_CAVEMAN |
Override caveman level. |
BEE_STREAM_STALL_SECONDS |
Override the streaming idle-stall window (default 600s). <=0 disables the watchdog. |
BEE_TEST_PROVIDER |
stub for canned replies, scripted for fixture-driven runs. |
BEE_TEST_SCRIPT |
Path to scripted fixture when BEE_TEST_PROVIDER=scripted. |
OPENROUTER_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY / ... |
Provider keys; resolved from EnvKey on the active provider block. |