Cyclium.Strategy.Template.Agentic.Loop (Cyclium v0.3.4)

Copy Markdown View Source

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

budget_exhausted_summary(state)

User-facing summary when a run stops on budget exhaustion.

build_execution_summary(state)

Technical summary of what an executed plan did (converge fallback).

build_findings_list(findings)

Normalize a list of finding maps (from plan meta or a finish tool's args) into the {:raise, ...} / {:clear, key, reason} tuples ConvergeResult expects.

build_interpret_prompt(state)

build_outputs_list(outputs)

Normalize a list of output-proposal maps into %OutputProposal{} structs.

build_summarize_prompt(state)

coerce_explanation(text)

finish_signature()

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.

finish_tool_name()

The reserved tool name that terminates an agentic run.

handle_budget_exhausted(state, episode_ctx)

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.

handle_result(state, step, result)

load_strategy_config(actor_id)

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.

load_strategy_config(actor_id, expectation_id)

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.

next_step(state, episode_ctx)

parse_action_plan(result)

resume_from_block(state, episode)

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.

tool_menu_from_config(strategy_config)

with_finish_tool(strategy_config)

Append the finish_agentic_task signature to a strategy_config's tool list, unless the app already declared a tool by that name.