Skip to content

Architecture

zkit is the substrate beneath the applications in this repo. Everything here runs in real sessions daily; nothing is speculative API design. The packages are layered — each one is usable without the ones above it, and dependencies only ever point downward.

The runner never imports an application; zkit/ai/* never imports zkit/agent/*. Take any horizontal slice and it stands alone — if you only want the tool system, take the tool system.

A production agent is the runner plus whichever layers you opt into. Each is one option on runner.New:

import (
"time"
"github.com/zarldev/zarlmono/zkit/agent/coderunner"
"github.com/zarldev/zarlmono/zkit/agent/compact"
"github.com/zarldev/zarlmono/zkit/agent/guardrails"
"github.com/zarldev/zarlmono/zkit/agent/pursue"
"github.com/zarldev/zarlmono/zkit/agent/runner"
"github.com/zarldev/zarlmono/zkit/ai/llm/anthropic"
"github.com/zarldev/zarlmono/zkit/ai/tools"
"github.com/zarldev/zarlmono/zkit/ai/tools/code"
)
provider, _ := anthropic.NewProvider(apiKey)
// the standard coding toolset: read, write, edit, bash, grep, ls, plan
ws, _ := code.NewWorkspace(root)
reg := tools.NewRegistry()
coderunner.RegisterStandardTools(reg, ws, code.NewProcessManager(ws))
// guardrails wrap the registry — a GuardedSource is itself a ToolSource
guarded := guardrails.NewGuardedSource(reg,
guardrails.NewSchemaGuardrail(reg), // repair malformed args
guardrails.NewShellGuardrail(code.ToolNameBash), // reject destructive shell
guardrails.NewFanoutGuardrail(map[tools.ToolName]int{ // cap exploration
code.ToolNameRead: 30, code.ToolNameGrep: 30,
}),
)
r := runner.New(runner.ClientFromProvider(provider),
runner.WithTools(guarded),
runner.WithPromptText(systemPrompt),
runner.WithCompactor(compact.NewTiered(ctxWindow)), // shrink history under pressure
runner.WithMaxIterations(40),
runner.WithToolTimeout(60*time.Second),
)
// drive it to a verified outcome instead of trusting "done"
goal, watcher := pursue.Until(testsPass, "tests still failing — read the output and fix")
out, _ := pursue.Drive(ctx,
pursue.NewRequest(r.Run, runner.TaskSpec{Prompt: task},
pursue.WithGoal(goal), pursue.WithWatcher(watcher)),
pursue.WithMaxAttempts(4),
)

Strip any line and it still runs — no guardrails, no compactor, no pursue is a valid agent, just a barer one. Each page in this section covers one of those layers in depth. For a coding agent specifically, coderunner.GuardedSource assembles the whole standard chain in one call — it’s what zarlcode and the eval harness both use, so they can’t drift.

The toolkit grows through six interfaces. Implement one and the runner takes it without changes:

InterfaceShapeImplement it for
runner.Clientone method (Complete)a new LLM transport
runner.ToolSourceiterate + executetool middleware — guardrails are exactly this
compact.Compactor (+ Prober)compact + cheap pre-checka new compaction strategy
pursue.Goal (+ Watcher)decide + early-stopa new completion oracle
spawn.AgentResolvername → runnerrouting sub-agents to different models
runner.EventSinksix typed sub-sinks (embed NopSink)a new observability surface

EventSink is compile-time exhaustive: add an event method and every implementer fails to build until it handles the new kind, so a UI can’t silently drop events.