Mission loop (technical reference)
Operator-facing concepts, use cases, and design patterns: Mission Control.
This page is the implementation reference for loop missions in
agency-django.
Mode
Derived from loop_prompt only — no mode field:
One shot Loop |
|
Runtime flow
dispatch → Execution + Thread
→ start_mission (loop) → bootstrap_loop → run_loop_iteration(1)
→ ThreadRunner.run(prompt)
→ [HITL? → interrupted, stop scheduling]
→ evaluate_loop → LoopDecision
→ complete | wait | blocked | failed | continue(+schedule)
Each iteration = one agency.run_loop_iteration Celery task. Never a long
while holding a worker.
continue enqueues iteration N+1 with optional countdown=loop_delay_seconds.
Mission fields
loop_promptSupervisor template. Safe placeholders:
{iteration},{max_iterations},{remaining_iterations},{mission_name},{last_summary}.max_iterations1–100 (default 10). Exhaustion →
limit_reached, notsuccess.loop_delay_seconds0–86400. Celery ETA/countdown between iterations.
is_loop(read-only API)loop_prompt is not NULL.
Execution fields (snapshot at dispatch)
iteration, max_iterations, loop_status, loop_decision,
next_run_at, metadata.loop_config.
Editing the Mission during an active Execution does not change the snapshot.
LoopDecision
Pydantic model in agency.mission.loop:
{
"status": "continue",
"summary": "Short state for UI and logs.",
"reason": "Why this status.",
"next_input": "Concrete next user message (continue only).",
"wait": null,
"required_input": null
}
Obtained via with_structured_output(LoopDecision) — not keyword parsing.
One repair retry; then failed.
follow_up is passed to ThreadRunner.continue_turn() — it appears as a
user message in the Thread. Never echo JSON field names as the message body.
Evaluation context
evaluate_loop_decision() sends the mission objective, iteration counters,
formatted loop_prompt, and a conversation transcript built by
agency.mission.context.build_thread_context:
Primary source:
Messagerows linked to the execution thread (checkpoint persistence).Fallback:
execution.result["messages"]when DB rows are not yet available.
The transcript is capped (message count and character budget) so evaluator calls stay bounded. Tool lines include a short result snippet when present.
LoopDecision rejects follow_up values that are placeholder tokens
(next_input, follow_up, continue, etc.) — the supervisor must return
a concrete instruction in natural language.
Celery tasks
agency.start_missionOneshot →
Agent.execute(); loop →bootstrap_loop+ iteration 1.agency.run_loop_iteration(execution_id, expected_iteration)Idempotent claim on
(execution_id, iteration).agency.resume_missionAfter HITL; re-enters loop evaluation when
metadata.loop_config.is_loop.
WebSocket events
mission_loop_started, mission_iteration_started,
mission_iteration_completed, mission_loop_waiting,
mission_loop_blocked, mission_loop_completed,
mission_loop_limit_reached, mission_loop_failed.
Small payloads only — no full history. Emitted after transaction.on_commit.
UI
Missions explorer: tabs Prompt / Loop. Execution view: loop N/M,
decision summary, waiting vs HITL interrupted.
Deferred
Evaluation with thread / checkpoint / log snapshot — transcript injection ✅; richer tool/log tail deferred
Watch — tail logs or events → resume
waitingexecutionsMission cron (Celery Beat dispatch)
agency.display— visual confirm cardsSecure cancel API
See also: Create and execute a Mission. Closed backlog note: todo/history/2026-08.md.