Skip to content

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

text
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 idle only if both pass; otherwise it stops at needs_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 idle vs needs_review.
  • Durable — each turn is saved before advancing; a session left active is 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):

bash
CLIENT_API_URL="https://<org>-<project>.org.machina.gg" \
API_TOKEN="<project X-Api-Token>" \
python3 docs/harness-loop-kit/provision.py

EVAL_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

text
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):

bash
machina config set client_api_url https://<org>-<project>.org.machina.gg
export MACHINA_API_KEY=<project X-Api-Token>

run — start a session

bash
machina loop run "Quanto é 1234 * 5678?" --watch

--watch streams each turn — including the tool step and the verification verdict:

text
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):

bash
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:

ToolWhat it does
calculateEvaluate arithmetic (the model never computes it itself).
get_datetimeCurrent UTC date/time.
echoEcho text back.
find_fixturesUpcoming fixtures + AI pre-match analysis from the project's sportradar-fixture docs.
read_documentsRead 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_conversationsRecent 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:

bash
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

bash
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 + status

sessions shows each session's terminal status, so a needs_review is easy to spot:

text
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:

bash
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:

bash
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:

bash
EVAL_MODEL="<a stronger model on your Vertex project>" \
CLIENT_API_URL=… API_TOKEN=… python3 docs/harness-loop-kit/provision.py

Self-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

StatusMeaning
activeA turn is in flight (or a session is awaiting automatic resume).
idleThe turn was answered and verified; awaiting the next say.
needs_reviewThe turn finished but failed the gate or the evaluator (or the attempt budget ran out) — a human checkpoint. Still continuable with say.
pausedStopped with loop stop.

--watch treats idle, needs_review, paused, completed, and failed as terminal.

Environment & config

Variable / keyPurposeDefault
client_api_url (config)Your project's API address
MACHINA_API_KEYProject X-Api-Token (direct API-key mode)stored credential
EVAL_MODEL (provisioner)Evaluator model — use a stronger one than the generator in prodthe generator's model
LOOP_MAX_ATTEMPTS (provisioner)Resume attempt budget (stop condition)3
  • Provisioning kitdocs/harness-loop-kit/ (provision.py) in the machina-cli repo.
  • Delegating from an external agent — SportsClaw's machina_loop tool routes durable work to this loop over MCP.

Troubleshooting

SymptomCause / fix
session started but no turns appearThe loop isn't provisioned in this pod — run provision.py, and confirm the pod runtime has Vertex AI credentials.
Every turn ends needs_reviewThe 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 wrongA same-model evaluator is lenient — set a stronger EVAL_MODEL and re-provision.
client-api-url-requiredSet the pod: machina config set client_api_url <url> (and export MACHINA_API_KEY).