bots-garden/mini-mepublic Fork
⑂ v0.4.0
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. 2d0776dUnverified · on v0.4.0 · k33g · 15d ago
asking-another-agent.md · 45 lines · 9.0 KBmarkdown
Blame HistoryOpen raw

Asking another agent — explanation

What is this about?

/ask-bob <prompt> lets the agent you are talking to consult another mm agent: another model, another system prompt, other tools and skills. Bob's answer is shown, then kept in your conversation so your own model can use it on your next question. The ask_agent tool lets your model do the same by itself, when a request matches what an agent is for. This page explains how that is built and why.

Why it is designed this way

Another agent is another process. Bob runs as a child mm -acp -config <bob's file>, the same binary as the agent that asks. Running him inside the same process was the obvious alternative and the wrong one: mm's configuration is one global, read by the engine, the tools, the compaction and the sessions alike, and bob's differs in all of them. A second engine in the same process would first have meant pulling that global apart everywhere. A child keeps each agent whole, with its own tools, skills and MCP servers.

The two talk ACP. mm already speaks the Agent Client Protocol as a server, for editors. Asking bob makes the asking agent an ACP client of its child. The protocol gives, for free, what scraping a terminal never would: bob's answer as text, his tool calls as structured events, his permission requests as requests that can be answered, and a standard way to cancel. mm's ACP server only ever calls two client methods — session updates and permission requests — so the client side is small; file and terminal requests are refused as absent.

Bob's permissions are yours to give. Bob is started with AGENT_YOLO=false, so every call he would gate becomes a permission request to your agent, and YOUR yolo decides: on, it runs; off, the question goes to the front end you are using — the editor's dialog, the window's box, the line mode's 🔐 prompt — exactly as for your own agent's tools there. The yolo in bob's own file does not count while he answers you — the one in YOUR file's entry for him does: mmConfigs: {bob: {path: …, yolo: true}} lets bob's calls run while yours still ask (a coder you trust with edits once you approved the hand-off), yolo: false asks for each of his calls even when yours run freely. Bob's tools are named bob/<tool>, so allowing your agent's bash "for this session" does not allow bob's. See Yolo mode and permissions.

The answer is a pair of messages. It enters your history as a user message ([asked bob (provider/model): …]) followed by an assistant message ([bob's answer] and the text). One message would have been simpler, but your next question is a user message too, and several chat templates (Gemma's, Mistral's) refuse two user messages in a row. The marks say who said what, so your model does not mistake bob's answer for its own. The answer is capped like a tool's output, at maxOutput characters: it lands in your model's window.

Each ask is a fresh conversation for bob. Bob keeps no memory between two asks. When he needs to know what you were doing, /ask-with-context-bob sends a transcript: questions, answers, one line per tool call — never the tool outputs, which would cost his window what they already cost yours, never your system prompt or reasoning. Continuity is explicit, chosen per question, rather than a second conversation drifting on its own.

One child per agent, started once. Starting mm costs its engine set-up, its server probe and its MCP servers. The first ask starts bob; later asks reuse him, each with a new session. He stops when your agent exits.

The child is fenced off from what is meant for its parent. Variables like AGENT_MODEL, AGENT_PROVIDER or AGENT_BASE_URL override a configuration; inherited by bob, they would silently turn him into a copy of you. They are removed from his environment. Bob also runs with AGENT_SESSIONS=off: he works in your project, and his session files would otherwise appear in your /sessions and /resume.

Your model can decide too: ask_agent. With mmConfigs set, your agent gets a built-in tool that asks one of the agents and returns its answer as the tool's result. Its description lists the agents with what each is for — the entry's description, else the agent's acp.title — and that is all the model has to choose from, so a description says when to call the agent, not who it is. The tool runs on the same pool and protocol as /ask, inside the turn: the agent's text, thoughts and tool calls stream through your front end as they happen, and the window names it while it works — its title and model in the header, asked by your agent, its answer labelled with its name. The call itself is gated like bash: with yolo: false you see the prompt your model wrote before it goes. The answer is a tool result, not a /ask pair in the history: your model reads it and carries on in the same turn — report it, check it, hand the next step to another agent.

Chains stop. A model calling ask_agent from an agent that was itself asked could go on asking; each child is told its depth (MM_ASK_DEPTH): your agent may ask bob, bob may ask riker, riker may not ask anyone — whether by a typed /ask or by ask_agent.

The same machinery answers scripts. mm --ask <agent> and mm --ask-with-context <agent> are the slash commands for whatever can only run a command — a skill, a script. They start the same child over ACP, with the same environment fence and depth limit, but the answer goes to stdout and everything else to stderr, so a caller captures the answer alone. mm exports MM_BIN and MM_CONFIG to the commands its bash tool runs, so a skill calls "this mm, with this configuration" without knowing either path. Two things differ from the slash commands, both because the caller is a command and not a person. The agent's tool calls follow the calling configuration's yolo: allowed when it is on — the person already approved the mm --ask command itself —, rejected when it is off, since stdin carried the question and nobody is there to answer. And mm --ask watches its parent process: the bash tool kills bash when its budget runs out, not bash's children, and an orphaned ask kept its agent running — and writing files — after nobody waited for it any more.

Time is the weak point. A local model can take a minute to answer, and one bash command had 30 seconds; bashTimeout now sets that budget. The watchdog of the asking agent also keeps counting while a command runs (ticket 57), so it has to be raised too until that ticket is fixed. ask_agent does better because it sees the agent work: every event the agent streams — text, a thought, a tool call — counts as a sign of life for your agent's watchdog. Only a long silence still ends the turn: an agent loading a large model, or reading a long first prompt, so watchdogTimeout must cover that much.

Rejected alternatives

  • mm -p "<prompt>" as a subprocess. One process per question and nothing to keep alive — but its output is a terminal screen (answer, reasoning, tool echoes, colours, the command recap) that would have to be parsed; it never asks permission, so bob would run commands unchecked; it cannot be cancelled except by killing it.
  • A second engine in the same process. Fast, no process — at the price of turning the global configuration into a per-agent one throughout the code base.
  • Letting the current model answer at once with bob's input. The user chose otherwise: bob's answer is shown and kept, and you decide what to ask next.
  • Keeping bob's own conversation across asks. Two conversations evolving in parallel, one of them invisible, are harder to reason about than an explicit transcript sent when asked.
  • Skills that run mm --ask for the model. It works — a skill per hand-off, the command in its text — and mm --ask stays for scripts. But the agent's work reaches you as the output of one bash command: no name in the header, no label on its answer, its tool calls as text lines. And a small coordinator must copy a long command exactly: measured with Gemma 4 E4B, it dropped the quotes and an AGENT_YOLO=true prefix, so the coder it called could not write. A tool call with an agent and a prompt has nothing to copy.
  • A generic /ask <name> only, in editors too. An editor's / menu completes a command name, never its argument; one command per agent is the only way for the menu to list the agents.

How it relates to the rest

  • The child is an ordinary ACP agent; the parent reuses the protocol from the other side.
  • Its tool calls and permissions go through the same event interface the three front ends implement for your own agent's tools.
  • The exchange is part of the history like any other turn: it is saved with the session, replayed by /resume, and summarised by context compression when it gets old.
  • Using it: how to ask another agent. Every command and message: slash commands reference.
 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
# Asking another agent — explanation

## What is this about?

`/ask-bob <prompt>` lets the agent you are talking to consult another mm agent: another model, another system prompt, other tools and skills. Bob's answer is shown, then kept in your conversation so your own model can use it on your next question. The `ask_agent` tool lets your model do the same by itself, when a request matches what an agent is for. This page explains how that is built and why.

## Why it is designed this way

**Another agent is another process.** Bob runs as a child `mm -acp -config <bob's file>`, the same binary as the agent that asks. Running him inside the same process was the obvious alternative and the wrong one: mm's configuration is one global, read by the engine, the tools, the compaction and the sessions alike, and bob's differs in all of them. A second engine in the same process would first have meant pulling that global apart everywhere. A child keeps each agent whole, with its own tools, skills and MCP servers.

**The two talk ACP.** mm already speaks the Agent Client Protocol as a server, for editors. Asking bob makes the asking agent an ACP *client* of its child. The protocol gives, for free, what scraping a terminal never would: bob's answer as text, his tool calls as structured events, his permission requests as requests that can be answered, and a standard way to cancel. mm's ACP server only ever calls two client methods — session updates and permission requests — so the client side is small; file and terminal requests are refused as absent.

**Bob's permissions are yours to give.** Bob is started with `AGENT_YOLO=false`, so every call he would gate becomes a permission request to your agent, and YOUR `yolo` decides: on, it runs; off, the question goes to the front end you are using — the editor's dialog, the window's box, the line mode's `🔐` prompt — exactly as for your own agent's tools there. The `yolo` in bob's own file does not count while he answers you — the one in YOUR file's entry for him does: `mmConfigs: {bob: {path: …, yolo: true}}` lets bob's calls run while yours still ask (a coder you trust with edits once you approved the hand-off), `yolo: false` asks for each of his calls even when yours run freely. Bob's tools are named `bob/<tool>`, so allowing your agent's `bash` "for this session" does not allow bob's. See [Yolo mode and permissions](yolo-and-permissions.md).

**The answer is a pair of messages.** It enters your history as a user message (`[asked bob (provider/model): …]`) followed by an assistant message (`[bob's answer]` and the text). One message would have been simpler, but your next question is a user message too, and several chat templates (Gemma's, Mistral's) refuse two user messages in a row. The marks say who said what, so your model does not mistake bob's answer for its own. The answer is capped like a tool's output, at `maxOutput` characters: it lands in your model's window.

**Each ask is a fresh conversation for bob.** Bob keeps no memory between two asks. When he needs to know what you were doing, `/ask-with-context-bob` sends a transcript: questions, answers, one line per tool call — never the tool outputs, which would cost his window what they already cost yours, never your system prompt or reasoning. Continuity is explicit, chosen per question, rather than a second conversation drifting on its own.

**One child per agent, started once.** Starting mm costs its engine set-up, its server probe and its MCP servers. The first ask starts bob; later asks reuse him, each with a new session. He stops when your agent exits.

**The child is fenced off from what is meant for its parent.** Variables like `AGENT_MODEL`, `AGENT_PROVIDER` or `AGENT_BASE_URL` override a configuration; inherited by bob, they would silently turn him into a copy of you. They are removed from his environment. Bob also runs with `AGENT_SESSIONS=off`: he works in your project, and his session files would otherwise appear in your `/sessions` and `/resume`.

**Your model can decide too: `ask_agent`.** With `mmConfigs` set, your agent gets a built-in tool that asks one of the agents and returns its answer as the tool's result. Its description lists the agents with what each is for — the entry's `description`, else the agent's `acp.title` — and that is all the model has to choose from, so a description says when to call the agent, not who it is. The tool runs on the same pool and protocol as `/ask`, inside the turn: the agent's text, thoughts and tool calls stream through your front end as they happen, and the window names it while it works — its title and model in the header, `asked by` your agent, its answer labelled with its name. The call itself is gated like `bash`: with `yolo: false` you see the prompt your model wrote before it goes. The answer is a tool result, not a `/ask` pair in the history: your model reads it and carries on in the same turn — report it, check it, hand the next step to another agent.

**Chains stop.** A model calling `ask_agent` from an agent that was itself asked could go on asking; each child is told its depth (`MM_ASK_DEPTH`): your agent may ask bob, bob may ask riker, riker may not ask anyone — whether by a typed `/ask` or by `ask_agent`.

**The same machinery answers scripts.** `mm --ask <agent>` and `mm --ask-with-context <agent>` are the slash commands for whatever can only run a command — a skill, a script. They start the same child over ACP, with the same environment fence and depth limit, but the answer goes to stdout and everything else to stderr, so a caller captures the answer alone. mm exports `MM_BIN` and `MM_CONFIG` to the commands its `bash` tool runs, so a skill calls "this mm, with this configuration" without knowing either path. Two things differ from the slash commands, both because the caller is a command and not a person. The agent's tool calls follow the calling configuration's `yolo`: allowed when it is on — the person already approved the `mm --ask` command itself —, rejected when it is off, since stdin carried the question and nobody is there to answer. And `mm --ask` watches its parent process: the `bash` tool kills `bash` when its budget runs out, not bash's children, and an orphaned ask kept its agent running — and writing files — after nobody waited for it any more.

**Time is the weak point.** A local model can take a minute to answer, and one `bash` command had 30 seconds; `bashTimeout` now sets that budget. The watchdog of the asking agent also keeps counting while a command runs (ticket 57), so it has to be raised too until that ticket is fixed. `ask_agent` does better because it sees the agent work: every event the agent streams — text, a thought, a tool call — counts as a sign of life for your agent's watchdog. Only a long silence still ends the turn: an agent loading a large model, or reading a long first prompt, so `watchdogTimeout` must cover that much.

## Rejected alternatives

- **`mm -p "<prompt>"` as a subprocess.** One process per question and nothing to keep alive — but its output is a terminal screen (answer, reasoning, tool echoes, colours, the command recap) that would have to be parsed; it never asks permission, so bob would run commands unchecked; it cannot be cancelled except by killing it.
- **A second engine in the same process.** Fast, no process — at the price of turning the global configuration into a per-agent one throughout the code base.
- **Letting the current model answer at once with bob's input.** The user chose otherwise: bob's answer is shown and kept, and you decide what to ask next.
- **Keeping bob's own conversation across asks.** Two conversations evolving in parallel, one of them invisible, are harder to reason about than an explicit transcript sent when asked.
- **Skills that run `mm --ask` for the model.** It works — a skill per hand-off, the command in its text — and `mm --ask` stays for scripts. But the agent's work reaches you as the output of one `bash` command: no name in the header, no label on its answer, its tool calls as text lines. And a small coordinator must copy a long command exactly: measured with Gemma 4 E4B, it dropped the quotes and an `AGENT_YOLO=true` prefix, so the coder it called could not write. A tool call with an `agent` and a `prompt` has nothing to copy.
- **A generic `/ask <name>` only, in editors too.** An editor's `/` menu completes a command name, never its argument; one command per agent is the only way for the menu to list the agents.

## How it relates to the rest

- The child is an ordinary [ACP agent](../reference/acp.md); the parent reuses the protocol from the other side.
- Its tool calls and permissions go through the same event interface the three front ends implement for your own agent's tools.
- The exchange is part of the history like any other turn: it is saved with the [session](persistent-sessions.md), replayed by `/resume`, and summarised by [context compression](context-compression.md) when it gets old.
- Using it: [how to ask another agent](../how-to/ask-another-agent.md). Every command and message: [slash commands reference](../reference/slash-commands.md#asking-another-agent).