mini-me from Go: package minime
A Go client that uses mini-me as a library by driving mm -acp over ACP (ticket 168). It is the Go twin of the Node.js client. It is its own module (minime), built on the ACP SDK mm itself uses, github.com/coder/acp-go-sdk v0.13.5, which has no dependency of its own. See ../README.en.md for the ACP messages behind it.
agent, err := minime.Start(ctx, minime.Options{Config: "agent.yaml"})
if err != nil {
log.Fatal(err)
}
defer agent.Close()
res, err := agent.Complete(ctx, "Explain this repository") // not streamed
for ev, err := range agent.Stream(ctx, "And the tests?") { // streamed
if err != nil {
log.Fatal(err)
}
if ev.Kind == minime.KindText {
fmt.Print(ev.Text)
}
}
ctx, cancel := context.WithTimeout(ctx, 3*time.Second) // cancelled after 3 s
defer cancel()
res, err = agent.Complete(ctx, "A long task") // res.StopReason == "cancelled"
Run the examples
llmman pull hf.co/unsloth/Qwen3.5-4B-GGUF:Q4_K_M
llmman serve
cd demo.acp.sdk/go
go run ./examples/complete # not streamed
go run ./examples/stream # streamed, with the tool calls
go run ./examples/cancel # cancelled after 3 seconds
go run ./examples/permission # a command refused, then allowed
go run ./examples/clienttools # a to-do list in this program, offered to the agent as two tools
mm must be on the PATH, or named by MM_BIN. The examples use agent.yaml (Qwen3.5 4B on llmman, bash only, yolo: false); any agent file works. Go 1.24 or later (range-over-func).
To use the package from another module, until it is published:
go mod edit -require=minime@v0.0.0 -replace=minime=../path/to/demo.acp.sdk/go
go mod tidy
The API
minime.Start(ctx, minime.Options{...}) (*Agent, error) |
starts mm -acp -config <Config> (made absolute) in Cwd, then initialize and session/new {cwd}, within 60 s. |
Options.Config |
the agent file. Required. |
Options.Cwd |
the session's working directory, where the tools run. Default: the current directory. |
Options.MM |
the mm binary. Default: $MM_BIN, then mm on the PATH. |
Options.OnPermission func(ctx, ToolCall) Decision |
asked when the agent wants to run a tool that needs a confirmation (yolo: false). Without it, every such call is refused. |
Options.Tools []Tool |
Go functions offered to the agent as tools (mm's ACP client tools, acp.clientTools, on by default). The model sees client_<Name>; each call runs Handle. Start refuses a tool with no Name, no Handle, a duplicate name, or a Method that does not start with _. |
Options.ToolDiscovery |
minime.DiscoveryMeta (default: the tools travel in initialize) or minime.DiscoveryRequest (only true travels; the list is answered to _org.mini-me/tools/list). Must match acp.clientTools.discovery in the agent file. |
Options.OnStderr func(line string) |
mm's stderr, line by line (banner, warnings, [acp] lines). Otherwise it is read and dropped; its last line is added to errors. |
agent.Complete(ctx, prompt) (Result, error) |
one turn; returns Result{Text, StopReason, Usage} once it is over. |
agent.Stream(ctx, prompt) iter.Seq2[Event, error] |
one turn as a range-over-func sequence; the last event is KindDone. Leaving the loop early cancels the turn. |
cancelling ctx |
sends session/cancel; the turn ends with StopReason: "cancelled", the text so far and a nil error. If mm does not answer within 30 s, the turn ends with an error. |
agent.Reset(ctx) error |
a new session on the same process: the conversation starts over. |
agent.Close() error |
closes mm's stdin, waits for it to exit (kills it after 5 s). Calling it twice is harmless. |
agent.SessionID() string |
the ACP session id. |
minime.ErrBusy |
returned by a turn or a Reset while another turn runs on the same Agent. |
Decision, the answer of OnPermission:
minime.Allow |
run the tool this once (allow_once) |
minime.Reject, or the zero Decision{} |
refuse it this once (reject_once) |
minime.ChooseOption(id) |
one of the options mm offers by its id: "allow", "allow_always" (this tool, for the session), "reject", "never" (this tool, for the session). An id mm did not offer counts as Reject. |
A panicking callback counts as Reject. Its ctx is the turn's.
The events (Event.Kind):
| Kind | Fields set |
|---|---|
KindText |
Text: a chunk of the answer |
KindThinking |
Text: a chunk of a thinking model's reasoning |
KindToolCall |
Call: ID, Title (e.g. bash: ls), Kind (execute, read, edit…), Input (the arguments) |
KindToolResult |
Call.ID, Status (completed or failed), Output |
KindDone |
StopReason, Usage (nil when mm sent none), Text: the whole answer |
Rules worth knowing:
- One Agent = one mm process = one conversation. Calls continue it, as in an editor. One turn at a time per Agent (
ErrBusy); start several Agents to work in parallel. - A turn mm ends with an error returns it with mm's own words and the last line of its stderr, e.g.
minime: turn failed: Internal error (-32603): the model repeated the same bash call 6 times…. If mm crashed, the error saysmm exited: exit status N. - A cancelled question is dropped from the conversation by mm: the next question does not know about it.
- The client advertises no file-system, terminal or form capability: mm needs none, and its
ask_usertool is not offered. Its only capability is_meta["org.mini-me/tools"], whenOptions.Toolshas some.
Tool, an entry of Options.Tools:
| Field | |
|---|---|
Name |
required, unique; the model sees client_<Name>. |
Description |
what the model reads to decide when to call it. |
Method |
the ACP extension method mm calls; must start with _. Default: _minime.go/tools/<Name>. |
InputSchema map[string]any |
JSON schema of the arguments (type: object); optional. |
Handle func(ctx, ToolCallRequest) (string, error) |
required. ToolCallRequest{SessionID, Arguments}. The text goes back to the model; an error (or a panic) goes back as a failed call (isError: true) and the client lives on. Calls may run concurrently. |
With yolo: false in the agent file, each call is first submitted to OnPermission, titled client: <Name>.
Tests
cd demo.acp.sdk/go
go test ./... # 47 tests and subtests, no model, no mm, ~1 s
The test binary plays a fake mm (fakemm_test.go): started again with MINIME_FAKE_MM=1, it answers over ACP with mm's message shapes (four permission options, rawOutput.output, usage on the prompt's answer): answers, a thought, a tool call with its permission, a slow turn to cancel, a turn deaf to the cancel, an error, a crash; and, like mm, it learns the client tools (from initialize, or with _org.mini-me/tools/list at session/new) and calls one. go test -race needs cgo and a C compiler.
Measured (2026-11-01, llmman on the author's host, mm v1.0.3 built from the working tree)
| Example | Result |
|---|---|
complete |
one-sentence answer, end_turn, 663 tokens, 8.3 s (cold model) |
stream |
find examples -name "*.go" | wc -l allowed by the callback (read-only), output 4, answer streamed chunk by chunk, end_turn |
cancel |
story cut after 3.0 s (cancelled, 394 characters kept); the next question on the same Agent answered at once, without knowing the story, which mm dropped |
permission |
pwd refused: the model invented an answer (/root); allowed: the real directory. A refusal does not stop a small model from guessing. |
Added on 2026-11-10 (same host and model, mm built from branch feature/acp-client-tools):
| Example | Result |
|---|---|
clienttools |
client_add_todo called twice (buy milk, write the blog post), client_list_todos twice; the answer lists both items, and the program's own list holds them. Same result with ToolDiscovery: DiscoveryRequest and acp.clientTools.discovery: request. |
When to choose this, and when not
- This client needs the
mmbinary, but works today with no change to mm, and isolates each agent in its own process (mm's global state is no problem). - Ticket 139 (an in-process SDK) would need no binary, but mm's code would have to become an importable library first.
Not done
- Not a published Go module: the
mmbinary would have to ship with it, per platform. session/load/session/resume(stored sessions), images and file attachments in prompts,ask_userforms: not exposed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
|