Agent loops¶
The agent loops that drive the tool registry: a deterministic planner for tests and a provider-backed loop that lets a language model call tools, observe results, and iterate toward a design.
agent
¶
Tool-calling agent loops for the Fugacio design copilot.
Two complementary loops share one tool registry:
-
run_agent: the original model-agnostic loop. A planner callable(goal, tool_schemas, transcript) -> decisiondecides the next tool call or the final answer, wheredecisionis{"tool": name, "arguments": {...}}or{"final_answer": text}, optionally with a"stop_reason"that explains an early stop. This keeps the control flow fully testable with a scripted planner and lets any decision policy drop in. -
run_llm_agent: a real multi-turn function-calling loop over anLLMProvider(OpenAI, Anthropic, or the testMockProvider). It maintains the full message history with tool-call ids and provider blocks, executes every requested tool (validating arguments and capturing errors so the model can self-correct), feeds the JSON results back with failures flagged, and returns when the model answers in plain text. A refused or truncated reply ends the loop with an explicit outcome instead of an answer.
llm_planner bridges the two: it turns a provider into a planner for the
simple loop. A deterministic heuristic_planner is provided for tests and
offline use.
Classes:
| Name | Description |
|---|---|
AgentResult |
Outcome of an agent run. |
Functions:
| Name | Description |
|---|---|
run_agent |
Run the plan/act loop until the planner returns a final answer. |
run_llm_agent |
Drive a real function-calling LLM through the tool registry to an answer. |
llm_planner |
Adapt an |
heuristic_planner |
A deterministic keyword planner: fire the first rule whose keyword is in the goal. |
AgentResult
dataclass
¶
AgentResult(
answer: str,
transcript: list[JsonDict] = list(),
stop_reason: str = "answer",
steps: int = 0,
)
Outcome of an agent run.
Attributes:
| Name | Type | Description |
|---|---|---|
answer |
str
|
The final natural-language (or structured) answer. |
transcript |
list[JsonDict]
|
Ordered tool calls with their arguments and results. |
stop_reason |
str
|
Why the loop ended: |
steps |
int
|
Number of planner / model turns taken. |
run_agent
¶
run_agent(
goal: str,
planner: Planner,
*,
registry: dict[str, ToolSpec] | None = None,
max_steps: int = 6,
) -> AgentResult
Run the plan/act loop until the planner returns a final answer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
goal
|
str
|
The natural-language design goal. |
required |
planner
|
Planner
|
Decision function (see module docstring); inject an LLM via
|
required |
registry
|
dict[str, ToolSpec] | None
|
Tool registry to expose (defaults to |
None
|
max_steps
|
int
|
Maximum number of tool calls before giving up. |
6
|
Returns:
| Type | Description |
|---|---|
AgentResult
|
An |
run_llm_agent
¶
run_llm_agent(
goal: str,
provider: LLMProvider,
*,
registry: dict[str, ToolSpec] | None = None,
system: str = DEFAULT_SYSTEM_PROMPT,
max_steps: int = 8,
max_tokens: int = 16000,
) -> AgentResult
Drive a real function-calling LLM through the tool registry to an answer.
Maintains the full chat history with tool-call ids and provider blocks,
executes each requested tool (validating arguments and turning errors into a
JSON error result, flagged as an error, that the model can recover from),
and loops until the model replies with plain text or the step budget is
exhausted. A "refusal" or "max_tokens" reply ends the loop with that
stop reason: its content never becomes the answer, and its tool calls never
run. A "pause_turn" reply is resent so the model can resume it. No
temperature is sent, so the provider's sampling defaults apply.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
goal
|
str
|
The user's natural-language request. |
required |
provider
|
LLMProvider
|
Any |
required |
registry
|
dict[str, ToolSpec] | None
|
Tool registry (defaults to |
None
|
system
|
str
|
System prompt; defaults to |
DEFAULT_SYSTEM_PROMPT
|
max_steps
|
int
|
Maximum model turns. |
8
|
max_tokens
|
int
|
Per-turn token cap (thinking included, where it applies). |
16000
|
Returns:
| Type | Description |
|---|---|
AgentResult
|
An |
llm_planner
¶
llm_planner(
provider: LLMProvider,
*,
system: str = DEFAULT_SYSTEM_PROMPT,
max_tokens: int = 16000,
) -> Planner
Adapt an LLMProvider into a Planner for run_agent.
On each call it reconstructs the conversation from the goal and transcript,
asks the model for the next step, and maps the first requested tool call to a
{"tool", "arguments"} decision (or the text reply to final_answer).
A paused reply is resent so the model can resume it. A refused or truncated
reply, or one still paused after the resends, becomes a final_answer
that explains the stop, with its stop_reason. No temperature is sent, so
the provider's sampling defaults apply.
heuristic_planner
¶
A deterministic keyword planner: fire the first rule whose keyword is in the goal.
Each rule is (keyword, decision); the first keyword found in the goal
(case-insensitive) triggers its decision once. After its tool runs (or if
no rule matches), the planner returns default_answer. Useful for offline
demos and tests without an LLM.
Accountable process design¶
run_design_agent completes only through a recorded, accepted current-case
run. Its final report is deterministic; unsupported model text is retained in
the transcript and can't become the engineering answer. See the
saved-case workflow.
design_agent
¶
A design-agent loop whose completion is grounded in computed case artifacts.
Classes:
| Name | Description |
|---|---|
DesignAgentResult |
Deterministic report and structured evidence from a submitted design. |
Functions:
| Name | Description |
|---|---|
run_design_agent |
Plan and compute a process design; ignore unsupported final-text claims. |
DesignAgentResult
dataclass
¶
DesignAgentResult(
answer: str,
design: dict[str, Any] | None = None,
transcript: list[dict[str, Any]] = list(),
stop_reason: str = "budget",
steps: int = 0,
)
Deterministic report and structured evidence from a submitted design.
Attributes:
| Name | Type | Description |
|---|---|---|
answer |
str
|
The deterministic design report, or why no design was submitted. |
design |
dict[str, Any] | None
|
The submitted design and its evidence, if one was submitted. |
transcript |
list[dict[str, Any]]
|
Tool calls with their arguments and results, plus events
such as |
stop_reason |
str
|
|
steps |
int
|
Number of model turns taken. |
run_design_agent
¶
run_design_agent(
goal: str,
provider: LLMProvider,
*,
workspace: CaseWorkspace,
max_steps: int = 12,
max_calls: int = 40,
max_tokens: int = 16000,
) -> DesignAgentResult
Plan and compute a process design; ignore unsupported final-text claims.
The general run_llm_agent remains suitable for explanatory questions.
This stricter loop requires an accepted artifact and generates its answer
from recorded values. Both model-turn and tool-call budgets are bounded. A
refused or truncated reply ends the loop with that stop reason and no
design; its content is recorded as an unaccepted event, and its tool calls
never run. A paused reply is resent so the model can resume it. No
temperature is sent, so the provider's sampling defaults apply.
case_tools
¶
Case-building tools and accountable design submission over trusted run artifacts.
Classes:
| Name | Description |
|---|---|
DesignSession |
Case revision and trusted run state for one accountable design-agent session. |
Functions:
| Name | Description |
|---|---|
case_format |
Return units, registry capabilities, and a complete editable example. |
case_tool_specs |
Stateless schema, validation, and audited solve tools for the general copilot. |
DesignSession
¶
DesignSession(workspace: CaseWorkspace)
Case revision and trusted run state for one accountable design-agent session.
Disk artifacts are inspectable, but only runs computed in this session are eligible for submission. A hash verifies integrity, not authorship. The explicit workspace is the only storage surface exposed to model tools.
Methods:
| Name | Description |
|---|---|
create_case |
Validate and select a new immutable case revision. |
load_case |
Select a verified saved revision; prior submission eligibility is cleared. |
update_case |
Apply a complete reviewed replacement only to the current base revision. |
set_parameters |
Create a new revision with dimension-checked parameter defaults. |
run_case |
Compute, audit, and save a run, preserving failed calculation evidence. |
study_case |
Run a bounded sweep, optimization, or independently checked sensitivity study. |
diagnose_case |
Inspect topology and declared dependencies without granting run eligibility. |
inspect |
Inspect a verified artifact without granting it trusted execution status. |
submit_design |
Submit selected recorded metrics from an accepted current-case run. |
registry |
Create tools bound to this session's explicit workspace and revision state. |
create_case
¶
Validate and select a new immutable case revision.
load_case
¶
Select a verified saved revision; prior submission eligibility is cleared.
update_case
¶
Apply a complete reviewed replacement only to the current base revision.
set_parameters
¶
Create a new revision with dimension-checked parameter defaults.
run_case
¶
run_case(
case_id: str,
overrides: dict[str, Any] | None = None,
backend: str = "sequential",
column_solver: str = "block",
plant_solver: str = "sparse",
) -> dict[str, Any]
Compute, audit, and save a run, preserving failed calculation evidence.
study_case
¶
study_case(
case_id: str,
kind: str,
request: dict[str, Any],
backend: str = "sequential",
column_solver: str = "block",
plant_solver: str = "sparse",
) -> dict[str, Any]
Run a bounded sweep, optimization, or independently checked sensitivity study.
diagnose_case
¶
Inspect topology and declared dependencies without granting run eligibility.
inspect
¶
Inspect a verified artifact without granting it trusted execution status.
submit_design
¶
Submit selected recorded metrics from an accepted current-case run.
Model-authored numerical claims and prose aren't accepted as arguments. The report is generated deterministically from the stored artifact.
case_format
¶
Return units, registry capabilities, and a complete editable example.