The shared agentic loop that powers the interactive and autonomous strategy templates. It owns the middle of the state machine — the phases neither template needs to customize:
interpret → validate → [preview] → execute → summarize → (loop | done)A template supplies the ends: init/2 (how intent is seeded), the
:context_assembly phase (what data the synthesizer sees), and converge/2
(what the episode produces). Everything between is delegated here so the
Interactive and AgenticTask templates stay thin and share one tested loop.
The finish_agentic_task tool
A reserved tool named "finish_agentic_task" lets an autonomous run signal
completion explicitly (rather than relying on a plain-text explain_only,
which invites a model to stop early). When a parsed plan calls it, the loop
routes straight to :done and stashes the tool's args as state.conclusion —
the plan never reaches PlanGate or ToolExec, so there is no real capability
to register. Interactive never puts "finish_agentic_task" in its tool menu,
so the interception is inert there.
All functions operate on the shared strategy-state map. A template's next_step
and handle_result handle their custom :context_assembly phase and delegate
every other phase to next_step/2 and handle_result/3 here.
Summary
Functions
User-facing summary when a run stops on budget exhaustion.
Technical summary of what an executed plan did (converge fallback).
Normalize a list of finding maps (from plan meta or a finish tool's args) into
the {:raise, ...} / {:clear, key, reason} tuples ConvergeResult expects.
Normalize a list of output-proposal maps into %OutputProposal{} structs.
A tool-menu signature for the reserved finish_agentic_task tool. Templates
that want the explicit terminal action append this to allowed_tool_signatures
so the synthesizer offers it to the model. It is never validated by PlanGate
— the loop intercepts a finish_agentic_task call before the :validate phase.
The reserved tool name that terminates an agentic run.
Graceful budget-exhaustion convergence. A template's handle_budget_exhausted/2
can delegate here to end with an "out of budget" summary instead of failing.
Map.put keeps this safe for states resumed from a pre-:budget_exhausted
checkpoint.
Resolve an actor's strategy_config, checking persistent_term first (set by
the Actor DSL strategy_config option) and falling back to the
cyclium_agent_definitions table for dynamic actors.
Resolve strategy_config for a specific {actor_id, expectation_id} pair.
Resume after a human resolved an approval. Rebuilds the approved plan from the
journal (no state checkpoint) and jumps to :execute/:denied so we run
exactly what was approved — no extra LLM round-trip or re-prompt.
Append the finish_agentic_task signature to a strategy_config's tool list,
unless the app already declared a tool by that name.
Functions
User-facing summary when a run stops on budget exhaustion.
Technical summary of what an executed plan did (converge fallback).
Normalize a list of finding maps (from plan meta or a finish tool's args) into
the {:raise, ...} / {:clear, key, reason} tuples ConvergeResult expects.
Normalize a list of output-proposal maps into %OutputProposal{} structs.
A tool-menu signature for the reserved finish_agentic_task tool. Templates
that want the explicit terminal action append this to allowed_tool_signatures
so the synthesizer offers it to the model. It is never validated by PlanGate
— the loop intercepts a finish_agentic_task call before the :validate phase.
The reserved tool name that terminates an agentic run.
Graceful budget-exhaustion convergence. A template's handle_budget_exhausted/2
can delegate here to end with an "out of budget" summary instead of failing.
Map.put keeps this safe for states resumed from a pre-:budget_exhausted
checkpoint.
Resolve an actor's strategy_config, checking persistent_term first (set by
the Actor DSL strategy_config option) and falling back to the
cyclium_agent_definitions table for dynamic actors.
Prefer load_strategy_config/2 when the expectation id is known — this
actor-only lookup is first-match-by-actor and non-deterministic for a
multi-expectation actor.
Resolve strategy_config for a specific {actor_id, expectation_id} pair.
Prefer this over load_strategy_config/1: an actor with more than one
expectation (e.g. an interactive chat and an agentic appraisal) registers a
distinct config under each {:cyclium_strategy_config, actor, exp} key. The
actor-only lookup scans persistent_term for the first matching actor entry
in unspecified order, so it can hand an agentic run the chat's config — wrong
role/guidelines, missing objective, and the wrong allowed_tool_signatures
(so the run's own finding-recording tool may never be in the menu). Resolving
by the exact expectation key is deterministic; it falls back to the actor-only
path (then the DB) when no per-expectation config is registered.
Resume after a human resolved an approval. Rebuilds the approved plan from the
journal (no state checkpoint) and jumps to :execute/:denied so we run
exactly what was approved — no extra LLM round-trip or re-prompt.
Append the finish_agentic_task signature to a strategy_config's tool list,
unless the app already declared a tool by that name.