A lean, extensible, language‑model–backed Go agent that can call simple tools to list, read, and edit files. Uses Anthropic’s Messages API by default.
export ANTHROPIC_API_KEY=sk-ant-...
export AGT_TOKEN_BUDGET=16000
make run # runs inside ./sandboxSee docs/configuration.md for all configuration options.
- Basic chat loop
- File tools:
list_files,read_file,edit_file - Persistence: JSON text‑only conversation history
- Provider: Anthropic Messages API (default)
- Model:
claude-3-7-sonnet-latest(default; can be changed in internal/provider/anthropic.go) - Model override via env is not yet supported; edit
internal/provider/anthropic.goto change the default.
Optional (opt-in):
- Calibration mode for ground-truth usage and local feature emission (tools disabled)
list_files— list directory entries (deterministic paging).read_file— read file content with offset/limit and truncation sentinel.edit_file— create/overwrite files when allowed by policy.
Details on paging, caps, and error codes: see docs/tools.md.
-
Go 1.24+
-
Clone the repository:
git clone https://github.com/petasbytes/go-agent.git
cd go-agent- Set required environment variables:
export ANTHROPIC_API_KEY=sk-ant-...
export AGT_TOKEN_BUDGET=16000 # input-window budget (currently rune-based; see "Context windowing" below).make run # or: go run ./cmd/agentBy default, the Makefile runs the agent from a dedicated ./sandbox directory (via a subshell cd). This keeps file operations contained during development.
Note (development): The agent stores conversation state under .agent/ in the current working directory (e.g., sandbox/.agent/). This directory is gitignored and can be safely deleted to reset state.
Optional: override sandbox roots (only if you aren't using the Makefile or want a different directory; by default the current working directory is used, and make run runs inside ./sandbox):
export AGT_READ_ROOT="./my-workdir"
export AGT_WRITE_ROOT="./my-workdir"
make runmake build # or: go build -o bin/agent ./cmd/agentThen run the built binary:
./bin/agentmake test # or: go test ./... -count=1make cover # or: go test ./... -count=1 -cover | tail -n 1
make cover-html # HTML report for local viewingThe CLI starts an interactive session. Type natural language instructions; the agent may call file tools under the hood.
Example:
You: list the files in the current directory
Claude: Here are all the files and directories in the current directory:
- cmd/
- go.mod
- go.sum
- internal/
- memory/
- tools/
- README.md
- mysteriousotherfile.txt
Let me know if you need anything else!
You: create a new file called superimportantfile.txt
Claude: I'll create a new file called "superimportantfile.txt" for you.
Claude: I've successfully created the file "superimportantfile.txt"
You: please read the new file you just created
Claude: I'll read the superimportantfile.txt file for you.
Claude: Here's the content of superimportantfile.txt:
"This is a new super important file."
cmd/agent/— CLI entrypoint and wiringinternal/provider/— Anthropic client wrapper andDefaultModelinternal/runner/— message send loop and tool dispatchinternal/windowing/— grouping, heuristic token counter, budgeted window preparationinternal/fsops/— path validation + I/O helpers for read/list/writeinternal/safety/— sandbox roots, validators, andToolErrorinternal/telemetry/— JSONL emitter and turn-id context helpersinternal/metrics/— local text feature counters for calibration eventstools/—ToolDefinition, JSON‑schema helper, and file toolsmemory/— JSON persistence for text‑only messages
flowchart LR
U[User, stdin] --> CLI[cmd/agent/main.go]
subgraph Agent
CLI --> RUN[internal/runner.Runner]
RUN --> WIN[internal/windowing: group, count, prepare]
RUN --> TOOLS[tools/*: list, read, edit]
RUN --> TEL[internal/telemetry]
TOOLS --> FSOPS[internal/fsops]
FSOPS --> SAFETY[internal/safety]
CLI --> MEM[memory/*: text-only transcript]
end
WIN --> PROV[internal/provider.AnthropicClient]
PROV <--> API[Anthropic Messages API]
SAFETY --> FS[(Local file system)]
TEL --> EVE[(.agent/events.jsonl: window_prepared, tool_exec, local_features, api_usage)]
RUN --> PAY[(.agent/payloads/: request.json, response.json)]
CLI -. calibration enables: local_features .-> TEL
- Tool definitions (tools/*.go) with registration in the runner
- JSON text-only persistence for simplicity (memory/conversation.go)
- Centralised provider and model selection (internal/provider/)
- Pair-safe context windowing with a deterministic heuristic counter (internal/windowing/*)
- Conservative tool caps for predictably small latest pairs (read_file offset/limit + sentinel; list_files paging + deterministic sort)
- The runner prepares a pair-safe, budgeted input window before sending to the API. Tool-use pairs (
assistant(tool_use)immediately followed byuser(tool_result)) are atomic and never split. - Budget is controlled by
AGT_TOKEN_BUDGET(required - see "Environment variables"). Groups are accumulated newest→oldest while staying within budget. - If the newest group alone exceeds
AGT_TOKEN_BUDGET, the run fails fast with:windowing: newest group exceeds AGT_TOKEN_BUDGET; increase budget with headroom or tighten tool caps. - Note: this input-window budget is separate from the SDK
MaxTokensused for model output tokens.
- Windowing currently uses a (very approximate) Go rune-based heuristic. A rune is a Unicode code point (not always a user‑perceived character).
- For English text, a practical rule of thumb is:
tokens ≈ runes / 3.5–4.0. Use the upper end (4.0) when sizing budgets to be safe. - Examples (approximate, ÷4.0):
AGT_TOKEN_BUDGET=8,000runes → ~2,000 tokensAGT_TOKEN_BUDGET=12,000runes → ~3,000 tokensAGT_TOKEN_BUDGET=16,000runes → ~4,000 tokensAGT_TOKEN_BUDGET=20,000runes → ~5,000 tokens
- Caveats: actual token counts vary by model/tokenizer, language/script (e.g., CJK), and content (code, JSON tool results, etc). Near budget or for billing/limits, prefer exact counts via Anthropic’s CountTokens (see "Roadmap").
-
File tools enforce sandboxed access via path validation and deny/policy rules.
-
Sandbox roots:
AGT_READ_ROOT(default: current working directory)AGT_WRITE_ROOT(default: same as read root)
-
Path validation:
- Clean + join relative paths
- Symlink resolution (including deepest existing ancestor when the leaf doesn’t exist)
- Robust boundary check using
filepath.Rel
-
Read denylist:
- Denies reads under
.git/and.agent/with codeERR_DENIED_READ
- Denies reads under
-
Write policy:
- Denies writes under
.git/and.agent/ - Denies
go.modandgo.sumby filename at any depth - Violations return machine‑readable
ToolErrorJSON (e.g.,{ "code": "ERR_DENIED_WRITE", ... })
- Denies writes under
-
macOS note: paths under
/var/...may resolve to/private/var/...; validators normalize roots to avoid false boundary failures. -
Defaults:
- If
AGT_READ_ROOT/AGT_WRITE_ROOTare unset, both default to the current working directory. make runexecutes inside./sandbox(via subshellcd), so the effective roots default to./sandbox.
Internally, safety violations are represented as structured ToolError codes (see
internal/safety/paths.go). - If
See docs/tools.md for tool safety and policy details, and docs/configuration.md for sandbox root configuration.
-
Minimal to run:
ANTHROPIC_API_KEY— required.AGT_TOKEN_BUDGET— e.g.,16000(rune-based heuristic).
-
Common optional:
AGT_OBSERVE_JSON=1— JSONL events (opt-in).AGT_VERBOSE_WINDOW_LOGS=1— compact window logs (opt-in).
Full list and details: see docs/configuration.md.
- Enable
AGT_OBSERVE_JSON=1to emit compact JSONL events (no raw text). OptionalAGT_VERBOSE_WINDOW_LOGS=1prints a one-line window summary. - Event types include
window_prepared,tool_exec, andapi_usage;local_featuresis emitted when calibration mode is on.
Details and examples: see docs/observability.md.
- 401/403 from API: Ensure
ANTHROPIC_API_KEYis set and valid. - Network/proxy errors: Retry
make runor check your proxy/firewall. - 429 (rate limit): Wait and retry; this project is single-attempt by default (retries yet to be added).
windowing: newest group exceeds AGT_TOKEN_BUDGET: IncreaseAGT_TOKEN_BUDGETwith some headroom, or use tool pagination to reduce the size of the latest tool-use pair (e.g.,read_fileoffset/limit,list_filespage/page_size)- Large file reads: files larger than 20MB are rejected with
ERR_FILE_TOO_LARGEto avoid excessive memory use.
AGT_CALIBRATION_MODE=1disables tools and enablesapi_usage+ per-turnlocal_features. By default it also enables observation and payload persistence.- When payload persistence is on, exact request/response JSON is written under
${AGT_ARTIFACTS_DIR|.agent}/payloads/{turn_id}/.
Details and steps: see docs/calibration.md.
Planned:
- Near-budget token counting
- Optional retries/limits
- Anthropic Messages: https://docs.anthropic.com/en/api/messages
- Anthropic Go SDK: https://github.com/anthropics/anthropic-sdk-go
- Tool use: https://docs.anthropic.com/en/docs/build-with-claude/tool-use
See LICENSE in this repository.
- Inspired by “How to Build an Agent” (AmpCode): https://ampcode.com/how-to-build-an-agent