The agent1 of 15
The two modes
On this page
Kotoba runs one agentic loop. It runs in one of two modes, and the mode decides three things: which tools she is offered, how many times she may call them, and whether you are waiting for her.
| Companion mode | Work mode | |
|---|---|---|
| What it is | The conversation you are in | A background job that outlives the turn that started it |
| Tools offered | 24 — or 33 inside the Discord bot process | 29 — or 38 inside the Discord bot process |
| Tool calls per turn | 8 | 40 |
| Model calls (iterations) per turn | 9 | 41 |
| Failed tool calls in the turn before tools are withdrawn | 2 | 6 |
| Time ceiling | none in the loop — each tool has its own timeout | 3600 s of compute, or 1500 s when an ElevenLabs agent call is holding the line |
| Who hears it | You, live | Nobody — its text goes to a queue nothing reads; only the final summary is spoken |
Every number above was read out of core/loop.py, core/transport.py and the live registry. The
work-mode numbers are settings, not constants — see Changing the caps.
Companion mode
This is the default. Every turn you type or speak runs here.
She gets her full companion toolset every turn. There is no intent router — no keyword matching
decides "this sounds like a search". The loop offers every tool that passes its own availability
check and the model picks. From core/loop.py:
"There is no keyword intent router: she gets her full toolset every turn, gated only by real AVAILABILITY through each tool's
check(), and the MODEL decides what to call."
The caps are per turn, not per session. A turn ends when the model answers without calling a tool. The next thing you say starts a fresh budget.
When the budget runs out, the loop does not stop mid-sentence: it withdraws every tool and runs one more iteration so she can answer in words with what she has. That is why the iteration cap is always at least one higher than the tool-call cap — the gate fires at the top of an iteration, so at the model's usual one call per iteration it needed iteration 41 of 40 and never ran. The loop just stopped mid-chain, and the work runner announced "Done." as a success.
What companion mode cannot reach
Seven tools are work-mode only. They are the ones that block on a human, spawn a process, or install third-party code:
delegate · mcp_find · mcp_install · activate_tools · ask_secret ·
request_credential · get_credential
Tools from a connected MCP server are also work-mode only — their toolset is named mcp:<server>,
which is not in the companion set. So the browser lives in work mode, always.
If the model calls one of those names in a conversation anyway, the loop refuses before dispatch and tells it plainly that nothing ran:
"'…' is not part of a conversation — it belongs to a background job. NOTHING ran and there is no result."
What work mode cannot reach
Exactly two, and they are the mode controls themselves: start_work and cancel_work. A job cannot
start another job or cancel itself.
Work mode
Starting one
She starts it, not you. There is no "run in background" button. When a request needs the browser,
several steps, code execution or a new capability, the model calls start_work(goal=…) — and that
call returns immediately, so the conversation keeps going.
Two things travel into the job:
- The goal, which the model writes.
- Your own message, verbatim. The goal is a paraphrase and paraphrases lose things — a URL, an exact spelling, the language you asked in. The brief tells the job that your words override the paraphrase above them.
One job per session. Ask for a second while one is running and start_work refuses and tells her to
say so.
The tool's own return value is deliberately narrow:
"The job has been LAUNCHED in the background — that is all that has happened so far: nothing has been opened, read, built or run yet."
Following one
The job emits frames on the same event channel the UI already listens to: work_started, working,
a step card per action, artifact when a file is written, task_list when she updates her plan,
subagent_spawned/subagent_done for helpers, then work_done.
Every frame carries the job's own run_id. That is what lets a client tell a background job's rows
from the turn it is currently awaiting — without it, a long job's steps drew inside a new reply.
When the job ends, the client fires a hidden __work_done__ turn — the web app sends it over the
voice channel, the terminal runs the same turn in process — and she announces the result in her own
words. The sentinel is never shown in the transcript and never reaches memory. It is announced once:
the work state marks it, so a second trigger does not repeat it.
Cancelling one
cancel_work is a tool, not a button. You say "stop that" and the model calls it. It cancels the
runner task, clears the state, and also cancels any deferred approval that was still waiting — a job
stopped without that left an approved-but-detached command running.
The web UI has no cancel button, on purpose. Grepping the frontend for cancel_work returns
nothing.
The terminal has /stop, which calls the same tool's execute() directly. It names what would die
and asks first:
stop the long job? 4m 12s in12 steps so far, on: <goal>. None of it is kept if you stop it now
/stop takes no argument. Typed with one (/stop 2) it answers the word instead of acting, because
the terminal numbers a session's past jobs and a misread number on a rail that kills something must
not go unanswered.
Cancelling is not failing. A requested stop clears the state and announces nothing; the bracket is
still closed so the UI stops showing work in progress. A CancelledError nobody requested is
told apart from a real stop (Task.cancelling() == 0) and recorded as the failure it is.
What else changes in work mode
- A system message is injected describing how to behave: act rather than describe, verify before claiming, keep the workspace organised. It is built from the tools actually offered, so she is never told how to drive a browser she does not have.
- Step zero is a skill. If one of her skills matches the job, her first call must be
skill_viewon that one skill. Everything after it acts. See Skills. - MCP servers are deferred. A connected server's tools are not offered until the model calls
activate_tools('<name>'), because tool-selection accuracy falls off past roughly 30–50 tools at once.browseris the exception and is always active. clarifyandask_userblock. In a conversation a question is the answer — she asks it and your reply is the next turn. A background job has no next turn, so both put a card on screen and wait (up to 300 s forask_user, 180 s forclarify). A question that cannot be drawn anywhere is refused before it is asked, not lost after.- Approval windows widen. The job runs on
channel="text", which buys the roomy 180-second approval window instead of a voice turn's 25 or 60 seconds. - Human waiting time is not charged to the budget. The compute ceiling is wall time minus the seconds spent blocked on you. A slow password plus 2FA must not fail as "took too long".
Changing the caps
Work-mode limits are read at runtime from ~/.kotoba/settings.yaml, so a change in Settings applies
to the next job with no restart. Companion caps are fixed at import from the environment.
| Setting | Default | Environment variable | Where |
|---|---|---|---|
work_timeout | 3600 (s) | KOTOBA_WORK_TIMEOUT | Settings → Advanced (work), CLI --settings |
work_max_iter | 40 | KOTOBA_WORK_MAX_ITER | same |
work_max_tool_calls | 40 | KOTOBA_WORK_MAX_TOOL_CALLS | same |
work_fail_limit | 6 | KOTOBA_WORK_FAIL_LIMIT | same |
| companion tool calls | 8 | KOTOBA_MAX_TOOL_CALLS | environment only |
| companion fail limit | 2 | KOTOBA_FAIL_LIMIT | environment only |
The four work fields appear in both the web Settings panel (under "Advanced (work)") and the
terminal's --settings view. The two companion caps are environment-only and are in neither.
Per-tool caps ride on top of the per-turn total and are environment-only:
| Limit | Default | Variable |
|---|---|---|
| Any one tool, per turn | 3 | KOTOBA_PER_TOOL_LIMIT |
todo and cronjob, per turn | 10 | KOTOBA_TODO_TOOL_LIMIT |
delegate, per turn | 3 | KOTOBA_DELEGATE_LIMIT |
delegate running at once | 2 | KOTOBA_DELEGATE_CONCURRENCY |
browser__* in work mode | 40 | KOTOBA_BROWSER_TOOL_LIMIT |
web_search, per run | 8 | KOTOBA_WEB_SEARCH_LIMIT |
An exact repeat of a call she already made this turn is not executed at all. It still counts against the per-tool cap, and the model is handed the earlier result back with an instruction not to retry it.
