Documentation · v0.1文档 · v0.1
Playing the part怎么演这个声部
Install, wire a model backend, hand it tools, decide which ones need a human. Then, if you need it, hand the whole thing to the cluster. 装上、接一个模型后端、给它工具、决定哪些工具需要人点头。之后如果需要,把整件事交给集群。
Install安装
Go 1.22 or newer. No cgo, no subprocess, no runtime beyond the standard library and an HTTP client. Go 1.22+。无 cgo、无子进程,除标准库和一个 HTTP client 外没有任何运行时依赖。
$
go get github.com/memohai/gorondo
One turn, start to finish从头到尾跑一个 turn
The engine exposes one entry point. Run plays a whole turn — however many rounds of model-and-tools that takes — and returns when the model stops asking for tools.
引擎只有一个入口。Run 演完整个 turn——中间要转几轮模型与工具由模型决定——直到模型不再点工具时返回。
main.gogo
loop := gorondo.New(gorondo.Config{
APIKey: os.Getenv("GORONDO_API_KEY"),
BaseURL: os.Getenv("GORONDO_BASE_URL"), // "" = api.anthropic.com
Model: "claude-opus-5",
// local tools; platform tools arrive with the run context
Tools: []gorondo.Tool{clock, readFile},
RequireApproval: true,
MaxTokens: 4096,
})
out, err := loop.Run(ctx, rc, io)
if err != nil {
return err
}
log.Printf("%s · in=%d out=%d",
out.Kind, out.Usage.InputTokens, out.Usage.OutputTokens)
The two ports两个端口
Everything the engine needs comes through two values you pass to Run. On the cluster both are handed to you; standalone, you implement them.
引擎需要的一切都经传给 Run 的两个值进来。在集群上这两个都是现成的;单机跑时由你实现。
-
rc— the run context: system prompt, message history, model config, the platform tools this run may use, the MCP endpoint they are bound to, and limits such asMaxRounds.rc—— 运行上下文:system 提示、消息历史、模型配置、本次 run 可用的平台工具、这些工具绑定的 MCP 端点,以及MaxRounds之类的限额。 -
io— the loop's three wires out:Emitfor live stream events (droppable),CompleteRoundfor durable round facts (must not be lost — an error here ends the turn), andControl, a channel of inbound signals.io—— 循环对外的三根线:Emit发实时流事件(可丢)、CompleteRound上报可持久化的轮次事实(不可丢——这里报错则终止本 turn)、Control是入向信令通道。
type LoopIO interface {
// droppable: failures degrade to a log line
Emit(ev StreamEvent)
// durable: an error here must end the turn
CompleteRound(msgs []Message, usage Usage) error
// approvals and user input; abort cancels ctx instead
Control() <-chan Control
}
ProvidersProvider
A provider is one method with a callback. The loop knows nothing about SSE framing, auth headers, or retry policy — that is entirely inside whichever provider you inject. Provider 就是一个带回调的方法。循环本身对 SSE 分帧、鉴权头、重试策略一无所知——这些完全在你注入的那个 provider 内部。
type Provider interface {
Name() string
Stream(ctx context.Context, req Request,
emit func(Delta)) (*Completion, error)
}
Shipped: any Anthropic-compatible endpoint内置:任意 Anthropic 兼容端点
Leave Provider nil and the engine builds an API client from your key and base URL. Auth style is detected rather than configured, and transient failures back off and retry.
不填 Provider,引擎会用你的 key 和 baseURL 构造 API 客户端。鉴权方式是探测出来的而不是配出来的,瞬时失败自动退避重试。
Tools and MCP工具与 MCP
Two kinds of tools land in the same registry. Local ones are Go closures you inject at construction; platform ones are declared by the run context and dispatched over HTTP to your MCP endpoint as JSON-RPC tools/call.
两类工具落进同一张注册表。本地工具是你在构造时注入的 Go 闭包;平台工具由运行上下文声明,经 HTTP 以 JSON-RPC tools/call 打到你的 MCP 端点。
clock := gorondo.FuncTool{
Definition: gorondo.ToolDef{
Name: "now",
Description: "current server time, RFC3339",
InputSchema: json.RawMessage(`{"type":"object"}`),
},
Fn: func(ctx context.Context, id string,
args json.RawMessage) (string, bool, error) {
return time.Now().Format(time.RFC3339), false, nil
},
}
The two return values that aren't the result matter: isError means the call failed in a way the model should see and route around; a Go error means your executor itself is broken. Business failure keeps the piece going, executor failure ends it.
除结果外的两个返回值很重要:isError 表示这次调用失败了,但该让模型看见并自己绕路;Go 的 error 表示执行器本身坏了。业务失败让乐曲继续,执行器故障则终止。
Approvals审批门
Set RequireApproval and every tool call pauses at a barrier before it runs. The engine emits a tool_approval_request stream event carrying the tool name, its arguments and a call id — then waits for a decision addressed to that id.
打开 RequireApproval,每次工具执行前都会停在门口。引擎发出携带工具名、参数和调用 id 的 tool_approval_request 流事件——然后等待指向该 id 的决定。
// out — on the live stream
{"type": "tool_approval_request",
"tool_call_id": "toolu_01…",
"name": "bash",
"input": {"cmd": "rm -rf ./build"}}
// in — on the control channel
{"type": "tool_approval",
"tool_call_id": "toolu_01…",
"decision": "allow"} // or "deny"
A denial is delivered to the model as an error result, not an exception: the turn continues and the model is free to try another approach. Cancelling the context aborts instead — that path does not go through the control channel. 拒绝是以错误结果的形式交给模型的,不是抛异常:turn 继续,模型可以换个路子。真要中止是取消 context——那条路不走控制通道。
Steering, mid-turn轮间注入
User input that arrives while a turn is running is queued and injected at the next round boundary, appended to the same user message that carries the tool results. The model doesn't experience an interruption — it just reads a longer message next round. turn 运行期间到达的用户输入进队列,在下一轮边界注入,附在承载工具结果的同一条 user 消息之后。模型感受不到打断——它只是在下一轮读到一条更长的消息。
{"type": "user_input",
"text": "actually, skip the tests for now"}
Guard rails护栏
| Guard护栏 | Default默认 | What it stops挡住什么 |
|---|---|---|
| MaxRounds | 8 | A backstop, not a plan — the model decides how many rounds it needs. Exhausting it fails the run.兜底而非计划——转几轮由模型决定。耗尽则 run 失败。 |
| loop fingerprint循环指纹 | 3 identical rounds连续 3 轮相同 | The same tool calls, same arguments, over and over. Fails loudly instead of billing quietly.同样的调用、同样的参数反复出现。大声失败,而不是安静地计费。 |
| MaxToolResultBytes | 32 KiB | One chatty tool eating the whole context window. Over-long results are truncated and marked.一个话痨工具吃掉整个上下文窗口。超长结果被截断并标注。 |
| MaxTokens | 4096 | Per-round output ceiling. A truncated response ends the run as a failure rather than passing off half an answer.单轮输出上限。被截断的回复以失败结束,而不是把半个答案当成品交出去。 |
Five channels五通道
On the cluster the engine is one voice among several. Everything crossing the boundary does so on one of five channels, and each channel's promise is part of the contract — not an implementation detail you have to reverse-engineer. 在集群里,引擎只是若干声部之一。所有跨边界的东西都走五个通道之一,每个通道的承诺是契约的一部分——而不是让你去反推的实现细节。
| # | Channel通道 | Promise承诺 |
|---|---|---|
| ① | task.turn | At-least-once delivery of work. Duplicates are expected and deduplicated by the lease.任务至少送达一次。重复是预期内的,由租约去重。 |
| ② | run context | Pulled, not pushed, and retried. A run already in a terminal state is refused its context — that's what stops a late redelivery replaying a whole turn.拉取而非推送,可重试。已终态的 run 会被拒发上下文——这正是挡住迟到重投整 turn 重跑的机制。 |
| ③ | live stream | Droppable by design. Sequence-numbered so a reconnecting reader can spot a gap.设计上就是可丢的。带序号,重连的读者能发现空洞。 |
| ④ | turn.results | Must not be lost. Idempotent on arrival, fenced by session epoch.不可丢。落地幂等,按 session epoch 围栏。 |
| ⑤ | control | Directed at a live run: approvals, steering. Abort does not travel here — it cancels the context directly.定向发给在跑的 run:审批、注入。abort 不走这里——它直接取消 context。 |
One turn per session每 session 一个 turn
Ordering is a scheduling property, not a locking one. Upstream admits at most one non-terminal run per session; the cluster's session lease is the backstop that makes duplicate deliveries and mid-rebalance handoffs harmless. 顺序是调度性质,不是加锁性质。上游保证每 session 至多一个未终态 run;集群的 session 租约只是兜底,让重复投递和 rebalance 交接变得无害。
That makes lease contention a two-case decision, and the distinction matters more than it looks: 于是抢锁冲突必须分两种情况,这个区分比看上去重要:
- Same run — a duplicate delivery. Ack it and drop it. Nothing happened.同一个 run —— 重复投递。ack 掉丢弃,什么都没发生。
- Different run — the session is genuinely busy. Postpone: back off, requeue unchanged, warn. The attempt counter is not touched, because yielding is not failing.不同的 run —— session 真的在忙。让路:退避、原样回队、告警。attempt 计数不动,因为让路不是失败。
Requeue — the one that does burn an attempt and eventually reports a failure — is reserved for things that actually went wrong. 而 requeue——会消耗 attempt、耗尽后上报失败的那种——只留给真正出错的情况。
Environment环境变量
Configuration is environment-first: one binary, one composition root, no config file format to learn. 配置以环境变量为先:一个二进制、一个装配点,没有需要学的配置文件格式。
Engine引擎
| GORONDO_API_KEY | Model credential. Falls back to ANTHROPIC_AUTH_TOKEN, then ANTHROPIC_API_KEY.模型凭据。依次回退到 ANTHROPIC_AUTH_TOKEN、ANTHROPIC_API_KEY。 |
| GORONDO_BASE_URL | Anthropic-compatible endpoint. Empty = the official API.Anthropic 兼容端点。留空 = 官方 API。 |
| GORONDO_MODEL | Force a model name; empty means the run context decides.强制模型名;留空则由运行上下文下发。 |
| GORONDO_MAX_TOKENS | Per-round output ceiling. Default 4096.单轮输出上限,默认 4096。 |
| GORONDO_THINKING | adaptive to request extended thinking where the endpoint supports it.填 adaptive 请求自适应思考(端点支持时)。 |
| GORONDO_REQUIRE_APPROVAL | 1 gates every tool call behind an approval.填 1,每次工具调用都过审批门。 |
Cluster, self-hosted自建集群
| KAFKA_BROKERS | Broker list for the task and fact channels.任务与事实通道的 broker 列表。 |
| KAFKA_TASK_TOPIC KAFKA_RESULT_TOPIC KAFKA_GROUP | Channels ① and ④, plus the consumer group that spreads work across workers.通道 ① 与 ④,以及把任务摊到各 worker 的 consumer group。 |
| REDIS_ADDR | Backs the live stream, control signals and session leases.承载实时流、控制信令与 session 租约。 |
| CLUSTER_CONCURRENCY | Concurrent runs per worker. A semaphore, not a pool — each run is its own goroutine.单 worker 并发 run 数。是信号量不是常驻池——每个 run 一个 goroutine。 |
| CLUSTER_LEASE_TTL | Session lease lifetime; heartbeats renew it, a dead worker lets it expire.session 租约存活期;心跳续期,worker 死了就让它过期。 |
| CLUSTER_TASK_MAX_ATTEMPTS | How many real failures a turn gets before it is reported failed. Postponements don't count.一个 turn 在被判失败前允许几次真实失败。让路不计入。 |
| CLUSTER_LOOP | Which engine runs the turn: gorondo, or an alternative you registered.用哪个引擎跑 turn:gorondo,或你自己登记的实现。 |
| CLUSTER_INSTANCE_ID | Identity written into leases and facts. Set it to the pod name.写进租约与事实的身份标识,填 pod 名即可。 |
Stream events流事件
What a client reading the live channel actually sees over one turn. Text deltas dominate; everything else is punctuation. 一个读实时通道的客户端在一个 turn 里实际看到什么。文本增量占绝大多数,其余都是标点。
| agent_start · agent_end | The turn opened and closed.turn 的开始与结束。 |
| text_start · text_delta · text_end | The model speaking, token by token.模型说话,一个 token 一个 token。 |
| thinking | Extended reasoning, when the endpoint provides it.扩展思考,端点提供时才有。 |
| tool_use | A tool was named and dispatched.某个工具被点名并派发。 |
| tool_approval_request | Waiting on a human. Answer on the control channel.正在等人。经控制通道应答。 |
| progress | Housekeeping notes — input queued, input injected, control received.杂务提示——输入已入队、已注入、收到控制信令。 |