[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-80493":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":8,"htmlUrl":8,"language":9,"languages":8,"totalLinesOfCode":8,"stars":10,"forks":11,"watchers":12,"openIssues":13,"contributorsCount":13,"subscribersCount":13,"size":13,"stars1d":12,"stars7d":14,"stars30d":15,"stars90d":13,"forks30d":13,"starsTrendScore":16,"compositeScore":17,"rankGlobal":8,"rankLanguage":8,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":21,"topics":22,"createdAt":8,"pushedAt":8,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":13,"starSnapshotCount":13,"syncStatus":26,"lastSyncTime":27,"discoverSource":28},80493,"byo-coding-agent","betta-tech\u002Fbyo-coding-agent","betta-tech",null,"Go",79,22,1,0,6,20,5,4.09,"MIT License",false,"main",true,[],"2026-06-12 02:04:03","# Build Your Own Coding Agent\n\n[![ci](https:\u002F\u002Fgithub.com\u002Fbetta-tech\u002Fbyo-coding-agent\u002Factions\u002Fworkflows\u002Fci.yml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fbetta-tech\u002Fbyo-coding-agent\u002Factions\u002Fworkflows\u002Fci.yml)\n\n📖 **Read the book online:** [byoharness.dev](https:\u002F\u002Fbyoharness.dev) · [byoharness.dev\u002Fes](https:\u002F\u002Fbyoharness.dev\u002Fes\u002F)\n\n🌐 **Languages:** **English** · [Español](README.es.md)\n\nA hands-on introduction to **harness engineering** — the discipline of building the scaffolding around an LLM that turns it into a useful agent. You'll build a working AI coding agent in Go, then experiment with the parts that matter: providers, tools, compaction strategies, and permissions.\n\n## What is harness engineering?\n\nThe model is the engine. The harness is everything else: the loop that calls it, the tools it can use, how its conversation is shaped over time, what it's allowed to do, how the user talks to it.\n\nGet the harness right and a mid-tier model feels great. Get it wrong and a frontier model feels broken. Most of the interesting decisions in tools like Claude Code, OpenCode, and Aider live in their harnesses, not their models.\n\nHarness engineering happens at three layers — **building** the loop and abstractions, **extending** them with new tools or integrations, and **configuring** the behavior via files like `AGENTS.md` and `mcp.json`. Most practitioners spend the bulk of their time in the top layer; this book focuses on building because that's where the mental model is formed. Once you've built one harness, you read every config file with new eyes.\n\nThis project is a stripped-down, readable version of those tools, designed to be poked at.\n\n## What you'll build\n\nA terminal-based coding agent (~600 lines of Go) that:\n\n- Talks to Claude (or any LLM you plug in)\n- Calls tools — `bash`, `read_file`, `write_file` — to act on your filesystem\n- Asks for approval before each tool call\n- Compacts long conversations using pluggable strategies\n- Supports slash commands (`\u002Fhelp`, `\u002Fmodel`, `\u002Fcompact`, `\u002Fverbose`, …)\n- Has a TUI input with history, line editing, and a styled prompt\n\n## Prerequisites\n\n- Go 1.21+ (the project uses generics and the `max` builtin)\n- An Anthropic API key — [console.anthropic.com](https:\u002F\u002Fconsole.anthropic.com) → Settings → API Keys\n\n## Quick start\n\n```sh\ngit clone git@github.com:betta-tech\u002Fbyo-coding-agent.git\ncd byo-coding-agent\nexport ANTHROPIC_API_KEY=sk-ant-...\ngo run .\n```\n\nTo run against OpenAI as well, export the key once:\n\n```sh\nexport OPENAI_API_KEY=sk-...\ngo run .\n```\n\nThen switch providers mid-session with the slash command — no restart needed:\n\n```\n\u002Fprovider              # show current provider + the available choices\n\u002Fprovider openai       # swap to OpenAI (defaults to gpt-5-codex)\n\u002Fprovider openai gpt-4o-mini\n\u002Fprovider anthropic\n\u002Fmodel gpt-5           # change just the model on the current provider\n```\n\nBoth providers implement the same [`Provider`](internal\u002Fprovider\u002Fprovider.go) interface; the rest of the harness is unchanged when you swap. Setting `LLM_PROVIDER`\u002F`LLM_MODEL` env vars still works as a startup default if you'd rather not type the command every session.\n\nType `\u002Fhelp` to see commands. Try:\n\n- `list the files here`\n- `write a hello.txt with a haiku in it`\n- `read main.go and tell me how the agent loop works`\n\n## Architecture in 60 seconds\n\nThe harness is built around three orthogonal extension points. Each lives in its own `internal\u002F` package, with a small interface and an in-tree default implementation. Swapping is one line.\n\n```\n┌─────────────────────────────────────────────────────┐\n│  main.go        wiring · REPL loop · agent loop     │\n│  commands.go    \u002Fhelp · \u002Fmodel · \u002Fcompact · …       │\n└─────────────────────────────────────────────────────┘\n        │\n        ├── internal\u002Fapi\u002F             shared types (Message, Block, ToolDef, …)\n        │\n        ├── internal\u002Fprovider\u002F        Provider interface + Anthropic impl\n        │     Send messages → get response. Swap to add OpenAI etc.\n        │\n        ├── internal\u002Ftool\u002F            Tool interface + Registry + one file per tool\n        │     Self-registers via init() — drop a file in, it appears.\n        │\n        ├── internal\u002Fcompact\u002F         CompactionStrategy interface + strategies\n        │     SlidingWindow, Summarize, NoCompaction, WithLogging decorator.\n        │\n        └── internal\u002Fui\u002F              Banner, spinner, Bubble Tea input, styles\n              TUI affordances. Readable but less interesting.\n```\n\n## Project layout\n\n```\n.\n├── main.go              wiring + REPL + agent loop + executeTool wrapper\n├── commands.go          slash command registry\n└── internal\u002F\n    ├── api\u002F             Message, Block, ToolDef, Response, RenderTranscript\n    ├── provider\u002F        Provider interface + AnthropicProvider\n    ├── tool\u002F            Tool interface + Registry + bash \u002F readfile \u002F writefile\n    ├── compact\u002F         CompactionStrategy + SlidingWindow \u002F Summarize \u002F Logging\n    └── ui\u002F              banner, spinner, input (Bubble Tea), styling helpers\n```\n\n`internal\u002F` is enforced by the Go compiler — packages in there can only be imported by code in this same module, which is the right signal for \"these aren't meant to be reused as a library.\"\n\n## The three extension points\n\n### 1. Providers\n\nWant to use OpenAI, Bedrock, or a local model? Implement `Provider`:\n\n```go\ntype Provider interface {\n    Send(ctx context.Context, messages []Message, tools []ToolDef) (Response, error)\n    Model() string\n    SetModel(name string)\n}\n```\n\nThen change one line in `main.go`:\n\n```go\nllm = provider.NewOpenAIProvider(...)  \u002F\u002F instead of provider.NewAnthropicProvider\n```\n\nThe adapter is the only place that knows the SDK's wire format. The rest of the harness deals in generic `Message` \u002F `Block` \u002F `ToolDef` types. See `internal\u002Fprovider\u002Fanthropic.go` for the reference.\n\n### 2. Tools\n\nWant to add `git_diff`, `web_search`, `kubectl`? Create **one file** under `internal\u002Ftool\u002F`:\n\n```go\n\u002F\u002F internal\u002Ftool\u002Fgitdiff.go\npackage tool\n\nimport (\n    \"os\u002Fexec\"\n\n    \"github.com\u002Fbetta-tech\u002Fbyo-coding-agent\u002Finternal\u002Fapi\"\n)\n\ntype GitDiffTool struct{}\n\nfunc init() { Default.Register(&GitDiffTool{}) }\n\nfunc (GitDiffTool) Definition() api.ToolDef {\n    return api.ToolDef{\n        Name:        \"git_diff\",\n        Description: \"Show uncommitted changes in the current repo.\",\n        InputSchema: map[string]any{},\n        Required:    []string{},\n    }\n}\n\nfunc (GitDiffTool) Execute(_ string) (string, bool) {\n    out, err := exec.Command(\"git\", \"diff\").CombinedOutput()\n    if err != nil { return string(out), true }\n    return string(out), false\n}\n```\n\nDrop the file in. Run `go run .`. Type `\u002Ftools` — `git_diff` is in the list, the model can call it. **No edits to `main.go`** — `main` already imports `internal\u002Ftool`, so the new file's `init()` runs when the package loads.\n\n### 3. Compaction strategies\n\nWant to test different ways to handle long conversations? Implement `CompactionStrategy`:\n\n```go\ntype CompactionStrategy interface {\n    Compact(ctx context.Context, messages []Message) ([]Message, error)\n}\n```\n\nThree are included:\n\n| Strategy | What it does |\n|---|---|\n| `compact.NoCompaction{}` | Default — never modifies messages |\n| `&compact.SlidingWindow{KeepLast: 10}` | Keeps the last N messages, drops older |\n| `&compact.Summarize{Provider: llm, Threshold: 20, KeepRecent: 6}` | Asks the model to summarize old turns once history hits `Threshold` |\n\nWrap any of them with `compact.WithLogging(inner, \"compactions.log\")` to record before\u002Fafter diffs to a file — useful for comparing strategies.\n\nSwap by changing one line in `main.go`:\n\n```go\ncompactor = &compact.Summarize{Provider: llm, Threshold: 20, KeepRecent: 6}\n```\n\nThere's a subtle bit: a naive truncation can leave a `tool_use` block without its matching `tool_result`, and the API will 400. The `SafeSplitPoint` helper in `internal\u002Fcompact\u002Fstrategy.go` walks back until it finds a \"clean\" boundary. All strategies route through it.\n\n## Commands\n\n| Command | Effect |\n|---|---|\n| `\u002Fhelp` | List all commands |\n| `\u002Fprovider [anthropic\\|openai] [model]` | Show or swap the LLM provider |\n| `\u002Fmodel [name]` | Show current model or change it (provider-aware suggestions) |\n| `\u002Ftokens` | Show cumulative input\u002Foutput tokens and estimated cost |\n| `\u002Fdebug [on\\|off\\|clear]` | Toggle the live debug panel (provider calls, tool dispatch, compaction) |\n| `\u002Fclear` | Wipe conversation history |\n| `\u002Ftools` | List registered tools |\n| `\u002Fsubagents` | List registered and in-flight subagents |\n| `\u002Fcompact [sliding\\|summarize\\|none]` | Run compaction (configured strategy, or ad-hoc one) |\n| `\u002Fverbose [on\\|off]` | Toggle before\u002Fafter printing on compaction |\n| `\u002Fexit` | Quit |\n\n## Now try\n\nIn rough order of difficulty:\n\n1. **Add a `git_diff` tool.** Read `tool_bash.go` for the pattern, write a new `tool_git_diff.go`. Verify `\u002Ftools` lists it after.\n2. **Add a `TokenBudget` compaction strategy.** Drop oldest messages until estimated token count is under a configurable threshold. Start with a byte-count approximation; later swap in a real `count_tokens` call.\n3. **Add a `PermissionPolicy` abstraction.** Currently every tool call goes through `confirm`. Refactor so a policy decides — `AlwaysAllow`, `AlwaysAsk`, `AllowList{names}`. The policy slots into `main.go` like the other extension points.\n4. **Add a second provider.** OpenAI, a local Ollama, or a `MockProvider` that records calls (most useful for the next exercise).\n5. **Add tests.** With `MockProvider` you can test the agent loop end-to-end without an API call. Compaction strategies are easy to test on synthetic message histories.\n\n## What's not in here yet\n\n- **Streaming.** The model returns a full response before we render anything. Real coding agents stream tokens as they arrive.\n- **Tests.** Nothing's automated yet — see exercise 5.\n- **Prompt caching.** Every turn re-sends the full history at full price.\n- **Multi-line input.** Bubble Tea's `textarea` would unlock Shift-Enter for newlines.\n- **Permission policies.** Approval is hardcoded as \"ask every time\" — see exercise 3.\n- **MCP support.** No external tool servers.\n\nEach one is a worthwhile next chapter.\n\n## Documentation\n\nThree flavors of docs, for the three reasons people read this repo:\n\n- [`examples\u002Fminimal\u002F`](examples\u002Fminimal\u002F) — a single-file (~130 lines) version of the agent loop, no abstractions. The fastest way to see \"the essence\" before any harness machinery shows up. Run with `go run .\u002Fexamples\u002Fminimal`.\n- [`follow_along\u002F`](follow_along\u002Fen\u002FREADME.md) — chapter-length narrative on the *why* of every layer in the harness, in the order it was built. Read in order; about an hour total. Available in [English](follow_along\u002Fen\u002FREADME.md) and [Spanish](follow_along\u002Fes\u002FREADME.md).\n- [`how-to\u002F`](how-to\u002Fen\u002FREADME.md) — short recipe-style references for the extension tasks people actually do: [add a tool](how-to\u002Fen\u002Fadd-a-tool.md), [add a provider](how-to\u002Fen\u002Fadd-a-provider.md), [add a permission policy](how-to\u002Fen\u002Fadd-a-permission-policy.md). Also bilingual.\n\nIf you want a quick taste, start with `examples\u002Fminimal\u002F`. If you want the full story, read `follow_along\u002F` in order. If you've already built it and want to extend it, jump to `how-to\u002F`.\n\n## Acknowledgments\n\nThe structure draws on architectural decisions visible in Claude Code, OpenCode, and Aider. The \"build your own X\" framing comes from *Build Your Own Redis*, *Crafting Interpreters*, and Thorsten Ball's *Writing An Interpreter In Go*.\n","betta-tech\u002Fbyo-coding-agent 是一个用于构建自定义编码代理的项目。该项目通过 Go 语言实现了一个基于终端的 AI 编码助手，能够与 Claude 或其他大型语言模型（LLM）进行交互，并调用如 `bash`、`read_file` 和 `write_file` 等工具来操作文件系统。其核心功能包括对话压缩策略、用户批准机制以及支持多种斜杠命令以控制行为。特别适合于希望深入了解和实践如何围绕 LLM 构建实用代理的应用场景，如自动化代码生成或辅助开发等任务。此外，该工程还提供了一个简洁且易于理解的基础架构，便于开发者探索和扩展。",2,"2026-06-11 04:00:59","CREATED_QUERY"]