Mission loop (technical reference) ********************************** Operator-facing concepts, use cases, and design patterns: :doc:`../ui/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_prompt is NULL`` | | **Loop** | ``loop_prompt`` has text | +------------------+------------------------------------------+ 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_prompt`` Supervisor template. Safe placeholders: ``{iteration}``, ``{max_iterations}``, ``{remaining_iterations}``, ``{mission_name}``, ``{last_summary}``. ``max_iterations`` 1–100 (default 10). Exhaustion → ``limit_reached``, not ``success``. ``loop_delay_seconds`` 0–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``: .. code-block:: json { "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: ``Message`` rows 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_mission`` Oneshot → ``Agent.execute()``; loop → ``bootstrap_loop`` + iteration 1. ``agency.run_loop_iteration(execution_id, expected_iteration)`` Idempotent claim on ``(execution_id, iteration)``. ``agency.resume_mission`` After 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 ``waiting`` executions * Mission cron (Celery Beat dispatch) * ``agency.display`` — visual confirm cards * Secure cancel API See also: :doc:`../../tutorials/first-mission`. Closed backlog note: ``todo/history/2026-08.md``.