I’ve been building Forge, a terminal-native autonomous coding agent in Go, as a model-agnostic alternative to Claude Code.I wanted to share it with this community since it’s a fairly Go-idiomatic design.
What it does
You give it a task in plain English. It reads your repo, plans, calls tools, generates a unified diff, and asks for confirmation before applying anything. Standard agent loop, but a few things are different:
Six specialized model roles, not one model doing everything
Every LLM call is tagged with a role and routed independently through Costguard (an LLM gateway I also built):
type ModelRole string
const (
RolePlanner ModelRole = "planner" // reasoning, decides what to do next
RoleCoder ModelRole = "coder" // writes patches
RoleToolCaller ModelRole = "tool_caller" // intent -> structured tool call
RoleCompactor ModelRole = "compactor" // summarizes on context overflow
RoleReviewer ModelRole = "reviewer" // catches mistakes before patches reach you
)
Each role can point at a different model — e.g. Claude for planning, a cheap local Ollama model for tool-calling, GPT-4 class for code. Unconfigured roles fall back to the planner model, so single-model use still works with zero config:
func (c Config) selectModel(role ModelRole) string {
switch role {
case RoleCoder:
if c.CoderModel != "" {
return c.CoderModel
}
// ...
}
return c.PlannerModel
}
Planner → INTENT → tool-caller two-step protocol
The expensive reasoning model never has to emit a structured tool call directly. It emits INTENT: <natural language>, and a second, cheaper model translates that into the actual TOOL:/ARGS: call. This is the part I think is most interesting architecturally, it cleanly separates “deciding what to do” from “formatting it correctly,” This lets you use a powerful model for thinking, and a small local model for turning the result into structured tool calls.
Patch review gate
Before any patch reaches you, a reviewer-role model (can be a stronger model than the one that wrote the code) checks it. Runs even in autonomous mode.
Stdlib-heavy, no Docker dependency for file edits, it edits the working tree directly like Claude Code does.
Curious what this community thinks of the role-based routing pattern, and whether the INTENT two-step is the right way to split reasoning from structured output, or if there’s a cleaner Go pattern I’m missing.