Building Your Own Coding Agent on Top of zot
How zot ships its internals as importable Go packages, so you can write a working coding agent harness in about a hundred lines.
Most "AI coding agent" projects spend 90% of their code on plumbing: streaming protocol parsing, provider auth, tool schemas, a file sandbox, a system prompt. The interesting 10% (what your agent actually does) gets buried. zot flips that ratio. It hands you the plumbing as importable Go packages so you can write a working harness in about a hundred lines.
This article walks through coil, a tiny harness built on zot, and shows how the pieces fit together.
What zot gives you
zot ships its internals as ordinary Go packages under
github.com/patriceckhart/zot/packages/...:
provider: clients for Anthropic, OpenAI (Chat Completions and Responses), Gemini, and more, all behind oneprovider.Clientinterface.core: the agent loop, tool registry, and event stream.agent: helpers like a sane default system prompt builder.agent/tools: ready-made read, write, edit, and bash tools plus a path sandbox.
You import what you need and ignore the rest. There is no daemon, no config format you have to adopt, no TUI you are forced to render.
The whole harness
Here is the core of coil. The shape is the same for any harness you build.
prov := env("COIL_PROVIDER", "anthropic")
model := env("COIL_MODEL", defaultModel(prov))
client := newClient(prov, apiKey(prov))
sb := tools.NewSandbox(cwd)
sb.Lock() // confine file tools to the current directory
reg := core.NewRegistry(
&tools.ReadTool{CWD: cwd, Sandbox: sb},
&tools.WriteTool{CWD: cwd, Sandbox: sb},
&tools.EditTool{CWD: cwd, Sandbox: sb},
&tools.BashTool{CWD: cwd, Sandbox: sb},
)
system := zotagent.BuildSystemPrompt(zotagent.SystemPromptOpts{
CWD: cwd,
Custom: "You are coil, a small coding agent harness. Be concise.",
})
ag := core.NewAgent(client, model, system, reg)
ag.MaxSteps = 20Four building blocks: a provider client, a tool registry, a system prompt, and an agent that ties them together.
Picking a provider
Every provider is constructed the same way and returns the same interface, so swapping models is a one-line change:
func newClient(name, key string) provider.Client {
switch name {
case "anthropic":
return provider.NewAnthropic(key, "")
case "openai":
return provider.NewOpenAI(key, "")
case "gemini", "google":
return provider.NewGemini(key, "")
default:
panic("unknown provider: " + name)
}
}Because the agent loop only talks to provider.Client, your harness does not
care whether the bytes on the wire are Anthropic's Messages format or OpenAI's
Chat Completions format. zot normalizes streaming, tool calls, and usage for you.
Tools and the sandbox
Tools are just values that implement zot's tool interface. The built-in ones cover the common cases, and the sandbox keeps file access honest:
sb := tools.NewSandbox(cwd)
sb.Lock() // reads and writes outside cwd are rejectedAdding your own tool is the same pattern as the built-ins: implement the interface, register it. The model sees its JSON schema and can call it like any other.
Running the loop
zot's agent emits a stream of typed events. You decide how to render them. A CLI just prints; a TUI would draw. coil prints:
ag.Prompt(ctx, prompt, nil, func(ev core.AgentEvent) {
switch e := ev.(type) {
case core.EvTextDelta:
fmt.Print(e.Delta)
case core.EvToolCall:
fmt.Printf("\n[tool] %s %s\n", e.Name, string(e.Args))
case core.EvError:
fmt.Fprintf(os.Stderr, "\nerror: %v\n", e.Err)
}
})That callback is the entire UI layer. The agent handles the request, parses tool
calls, runs the registered tools, feeds results back, and loops until the model
is done or MaxSteps is reached.
Why build your own instead of using the TUI
zot has a full interactive TUI, so why write a harness at all? Because a custom harness lets you:
- Hard-code a workflow (one-shot prompts, batch jobs, CI checks) instead of an interactive session.
- Lock down the tool set and sandbox to exactly what a task needs.
- Embed agent behavior inside a larger Go program.
- Pin a system prompt and persona without user-visible configuration.
You get zot's battle-tested provider and tool layer while keeping full control of the surface your users (or your CI pipeline) actually touch.
Want your own vibe? Build your own TUI on top of it and make it yours.
Getting started
go mod init yourharness
go get github.com/patriceckhart/zotThen copy the four-block skeleton above, register the tools you want, write your system prompt, and run. You will have a working agent before you finish your coffee, and every line you add from there is about your product, not about reimplementing streaming parsers.
That is the point of building on zot: spend your effort on the 10% that makes your agent yours.
References
- Website: zot.sh
- GitHub repository (zot): github.com/patriceckhart/zot
- GitHub repository (coil): github.com/patriceckhart/coil