Skip to content

Shared agent infrastructure

The runner is intentionally small: it loops, streams, dispatches tools, and emits events. The next layer of shared infrastructure lives beside it, not inside it. These packages are opt-in building blocks for agents that need RAG, deterministic workflows, resumable state, human review, or traces.

PackageWhat it provides
zkit/ai/retrievalProduct-neutral RAG primitives: Document, Chunker, Embedder, VectorStore, Retriever, Reranker, an indexing Pipeline, and an in-memory cosine vector store for tests/local corpora.
zkit/agent/retrievalAgent adapters for retrieval: render retrieved docs as prompt context, expose a retriever as a PromptSource, or expose it as a retrieve_context tool.
zkit/agent/workflowTyped graph/workflow composition: nodes, static edges, conditional routes, compiled Runnable, execution state, event sink, and workflow-as-tool adapter.
zkit/agent/checkpointTransport-neutral run snapshots and a concurrent in-memory Store. Durable filesystem/SQLite stores can implement the same interface later.
zkit/agent/hitlHuman-in-the-loop request/review model: risk levels, decisions, reviewer patches/comments, and policy hooks like RequireHuman and ApproveLowRisk.

The split matters: zkit/ai/retrieval has no dependency on the agent runtime, while zkit/agent/retrieval is where retrieved documents become prompts or tools.

zkit/ai/retrieval defines the reusable substrate. A typical local pipeline chunks documents, embeds each chunk, and stores the vectors:

store := retrieval.NewMemoryVectorStore()
pipe := retrieval.Pipeline{
Chunker: retrieval.TextChunker{Size: 1_000, Overlap: 100},
Embedder: embedder, // your OpenAI/Ollama/local implementation
Store: store,
}
err := pipe.Index(ctx, []retrieval.Document{{
ID: "readme",
Text: body,
Metadata: retrieval.Metadata{"source": "README.md"},
}})

Query-time retrieval composes an Embedder and VectorStore:

r := retrieval.VectorRetriever{
Embedder: embedder,
Store: store,
Limit: 5,
}
docs, err := r.Retrieve(ctx, "how does checkpointing work?")

Use typed metadata filters when callers need a constrained subset:

docs, err := r.Retrieve(ctx, "deployment rollback",
retrieval.WithFilter(retrieval.Filter{
Must: []retrieval.Condition{
retrieval.Eq("source", "README.md"),
},
}),
)

The in-memory store uses cosine similarity. It is useful for examples, tests, and small local corpora; production backends should implement VectorStore against Qdrant, pgvector, SQLite, or a hosted service.

Agent-facing retrieval stays separate from the runner. Use agent/retrieval.PromptSource when retrieved context should be part of the system prompt:

source := agentretrieval.PromptSource{
Retriever: r,
Format: agentretrieval.FormatOptions{
Title: "Relevant project context",
MaxDocs: 5,
MaxRunes: 2_000,
ShowScores: true,
},
}
runner.New(client, runner.WithPrompt(source))

Or expose retrieval as a normal tool so the model decides when to ask for context:

tool := agentretrieval.Tool{Retriever: r}
reg.Register(tool)

zkit/agent/workflow is for deterministic orchestration around agents: validate input, retrieve context, call a model, post-process output, or branch based on state. It does not replace the runner; it composes with it.

g := workflow.NewGraph()
workflow.AddNode(g, "validate", workflow.NodeFunc[Input, Input](validate))
workflow.AddNode(g, "answer", workflow.NodeFunc[Input, Output](answer))
g.AddEdge(workflow.Start, "validate")
g.AddEdge("validate", "answer")
g.AddEdge("answer", workflow.End)
run, _ := g.Compile()
out, state, err := run.InvokeState(ctx, input)

Conditional routes can loop or branch, and compiled workflows can be wrapped as tools for an agent. Runnable.Sink emits workflow lifecycle and per-node events for UIs and trace exporters.

Checkpointing is deliberately just the shared contract:

store := checkpoint.NewMemoryStore()
err := store.Save(ctx, checkpoint.Checkpoint{
ID: "approval-1",
RunID: "run-42",
Step: "before-edit",
State: map[string]any{"file": "main.go"},
})

agent/hitl models the review plane without choosing a transport:

req := hitl.Request{
ID: "review-1",
RunID: "run-42",
CheckpointID: "approval-1",
Action: "edit_file",
Summary: "Rewrite the config loader",
Risk: hitl.RiskMedium,
}
review, decided, err := hitl.ApproveLowRisk{}.Review(ctx, req)
if !decided {
// send req to a TUI, web UI, notification queue, etc.
}

zarlcode can route these requests through its own UI surface while sharing the same request, decision, and checkpoint types.