loop
The loop command group drives the durable agentic turn loop — a conversation loop that runs server-side inside your Machina pod, using your project's agents, workflows, prompts, and documents. Every turn is persisted before it advances and independently verified before it finalizes, so the loop survives crashes, async tools, and waiting on input — and never silently ships an unchecked answer.
The CLI is a thin driver/observer (same pattern as factory): it starts sessions, streams turns, injects follow-ups, and reads state. All loop state lives in your pod — the CLI holds none, so a session survives between CLI invocations.
How a turn works
reason → run tool → respond → verify → finalize(idle | needs_review)- Reason → tool → respond — the loop decides whether to call a tool, runs it, and synthesizes an answer.
- Verify — a cheap deterministic check (non-empty answer, no error marker, tool succeeded) plus an independent evaluator (a separate model call, fresh context, "assume it's wrong"). A turn finalizes
idleonly if both pass; otherwise it stops atneeds_review— a human checkpoint, never a silent pass. - Self-repair — if the deterministic check passes but the evaluator rejects the answer, the loop repairs it once (fed the rejection reason) and re-verifies before deciding
idlevsneeds_review. - Durable — each turn is saved before advancing; a session left
activeis automatically resumed.
INFO
Prerequisite — provision the loop in your pod. The loop's server-side resources must exist in the project pod. Stand them up once with the provisioning kit (idempotent — safe to re-run):
CLIENT_API_URL="https://<org>-<project>.org.machina.gg" \
API_TOKEN="<project X-Api-Token>" \
python3 docs/harness-loop-kit/provision.pyEVAL_MODEL (evaluator model — use a model stronger than the generator in production) and LOOP_MAX_ATTEMPTS (resume attempt budget) are tunable at provisioning time.
Usage
machina loop run "<task>" [--persona <prompt>] [--watch]
machina loop watch <session-id>
machina loop say <session-id> "<message>" [--watch]
machina loop stop <session-id>
machina loop sessions [--limit N]Point the CLI at the pod first (direct API-key mode):
machina config set client_api_url https://<org>-<project>.org.machina.gg
export MACHINA_API_KEY=<project X-Api-Token>run — start a session
machina loop run "Quanto é 1234 * 5678?" --watch--watch streams each turn — including the tool step and the verification verdict:
session started ses_1a2b3c…
turn 1 user Quanto é 1234 * 5678?
turn 1 assistant → calculate({"expression": "1234*5678"})
turn 1 tool ← 7006652
turn 1 assistant O resultado de 1234 * 5678 é 7006652.
idle · 1 turns · ses_1a2b3c…
✓ verified (evaluator: gemini-3.1-flash-lite)
Continue with machina loop say ses_1a2b3c… "<message>"A turn that uses real project data (where a pod has sportradar-fixture documents):
machina loop run "Quais os próximos 2 jogos? Liste com horário." --watch
# turn 1 assistant → find_fixtures({"limit": 2})
# turn 1 tool ← [{"match":"Senegal vs Iraq","kickoff_brt":"26/06/26 16:00", …}]
# turn 1 assistant Os próximos 2 jogos são: 1. Senegal vs Iraq às 16:00 …
# idle · 1 turns · ses_…
# ✓ verified (evaluator: gemini-3.1-flash-lite)Tools the loop can call
The loop picks a tool from a catalog and calls it in your pod. Out of the box:
| Tool | What it does |
|---|---|
calculate | Evaluate arithmetic (the model never computes it itself). |
get_datetime | Current UTC date/time. |
echo | Echo text back. |
find_fixtures | Upcoming fixtures + AI pre-match analysis from the project's sportradar-fixture docs. |
read_documents | Read recent documents on the project pod by name (copilot threads, harness sessions, fixtures, config) — via the same in-pod document_search the MCP uses. |
fetch_conversations | Recent real end-user chat transcripts from PostHog (user context + bot answer + category) — for analyzing conversation quality and suggesting bot improvements. |
So a turn can reason over real project data or live conversations, not just do math:
machina loop run "Analise as conversas recentes e sugira melhorias no bot" --watch
# turn 1 assistant → fetch_conversations({"limit": 5})
# turn 1 tool ← [{"category":"faq_bonus","user_ctx":"…","bot":"…"}, …]
# turn 1 assistant Sugestões: 1. Respostas truncadas … 2. Promoções sem link direto …
# ✓ verified (evaluator: gemini-3.1-flash-lite)Adding a custom tool means editing the provisioning kit's catalog and re-provisioning — see the provisioning kit link below.
watch / say / stop / sessions
machina loop watch ses_1a2b3c… # stream an existing session
machina loop say ses_1a2b3c… "E o próximo da França?" --watch # multi-turn (prior turns feed back)
machina loop stop ses_1a2b3c… # pause a running session
machina loop sessions --limit 20 # list recent sessions + statussessions shows each session's terminal status, so a needs_review is easy to spot:
ses_1a2b3c… idle turn=2
ses_9f8e7d… needs_review turn=1(A trailing column shows the --persona name for sessions started with one — blank otherwise, as in these examples.)
Verification & self-repair — how to test it
The verdict is part of the turn. These examples make each path visible.
Pass — a good answer is verified and finalizes idle:
machina loop run "Quanto é 1234 * 5678?" --watch
# ✓ verified (evaluator: gemini-3.1-flash-lite)Gate fails closed → needs_review — a tool error never silently passes:
machina loop run "Quanto é 10 / 0?" --watch
# needs_review · 1 turns · ses_…
# ⚠ needs review — …The calculate tool returns an error:, so the deterministic gate fails, the LLM evaluator is skipped, and the session stops at the human checkpoint instead of idle.
TIP
Use a stronger evaluator in production. A same-model evaluator (the generator judging itself) is lenient on plausible-but-unsupported facts. Provision with a different/stronger EVAL_MODEL:
EVAL_MODEL="<a stronger model on your Vertex project>" \
CLIENT_API_URL=… API_TOKEN=… python3 docs/harness-loop-kit/provision.pySelf-repair — when the evaluator rejects an otherwise-passing answer, the loop repairs it once and re-verifies; the CLI then appends · self-repaired to the verified line. The evaluator rarely rejects a correct answer, so seeing self-repaired in normal use is uncommon — it means the first draft needed one correction before it passed.
Status model
| Status | Meaning |
|---|---|
active | A turn is in flight (or a session is awaiting automatic resume). |
idle | The turn was answered and verified; awaiting the next say. |
needs_review | The turn finished but failed the gate or the evaluator (or the attempt budget ran out) — a human checkpoint. Still continuable with say. |
paused | Stopped with loop stop. |
--watch treats idle, needs_review, paused, completed, and failed as terminal.
Environment & config
| Variable / key | Purpose | Default |
|---|---|---|
client_api_url (config) | Your project's API address | — |
MACHINA_API_KEY | Project X-Api-Token (direct API-key mode) | stored credential |
EVAL_MODEL (provisioner) | Evaluator model — use a stronger one than the generator in prod | the generator's model |
LOOP_MAX_ATTEMPTS (provisioner) | Resume attempt budget (stop condition) | 3 |
Related
- Provisioning kit —
docs/harness-loop-kit/(provision.py) in the machina-cli repo. - Delegating from an external agent — SportsClaw's
machina_looptool routes durable work to this loop over MCP.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
session started but no turns appear | The loop isn't provisioned in this pod — run provision.py, and confirm the pod runtime has Vertex AI credentials. |
Every turn ends needs_review | The evaluator is rejecting — check EVAL_MODEL is a real, enabled model; inspect the verdict reason with machina loop watch <id>. |
idle but the answer looks wrong | A same-model evaluator is lenient — set a stronger EVAL_MODEL and re-provision. |
client-api-url-required | Set the pod: machina config set client_api_url <url> (and export MACHINA_API_KEY). |

