Global-State Procedures¶
Global-state procedures are lightweight, JSON-structured plans where every step can read any variable produced by earlier steps (plus the original problem_text). This keeps procedures simple to write and easy to execute deterministically.
Why global state?¶
Fewer wiring bugs: No long chains of pass-through arguments.
Readable steps: Each step declares only the variables it actually needs.
Deterministic execution: A single global
statedrives evaluation.
Hard rules (validated)¶
These are enforced by evoproc.validators:
Step 1 input must be exactly
["problem_text"].
(No other inputs exist yet.)The final step must output exactly
["final_answer"].
(A description of how to compute it—no numeric work here.)Resolvable inputs: Every step input must either be
problem_textor a variable produced by an earlier step.No silent redefinitions: Reusing the same variable name later is flagged (prefer
normalized_total,count_after_discount, etc).No dead outputs: Outputs never used by later steps are flagged for removal.
{note} These constraints are diagnostic-driven: the validator returns structured findings you can feed to an auto-repair prompt.
Minimal schema (Pydantic)¶
The canonical model is provided by the plugin’s {mod}evoproc_procedures.models:
class StepInputField(BaseModel):
name: str
description: str
class StepOutputField(BaseModel):
name: str
description: str
class Step(BaseModel):
id: int
inputs: List[StepInputField]
stepDescription: str
output: List[StepOutputField]
class Procedure(BaseModel):
NameDescription: str
steps: List[Step]
Call Procedure.model_json_schema() when prompting the LLM so it returns strictly-valid JSON.
A good procedure (toy)¶
{
"NameDescription": "Solve small arithmetic word problems",
"steps": [
{
"id": 1,
"inputs": [{"name": "problem_text", "description": "original question"}],
"stepDescription": "Extract primitive facts (numbers, units, relations) from the text.",
"output": [{"name": "facts", "description": "structured facts"}]
},
{
"id": 2,
"inputs": [{"name": "facts", "description": "structured facts"}],
"stepDescription": "Plan the arithmetic operations needed to obtain the result.",
"output": [{"name": "plan", "description": "ordered operations"}]
},
{
"id": 3,
"inputs": [{"name": "plan", "description": "ordered operations"}],
"stepDescription": "Describe the final answer (do not compute numeric value).",
"output": [{"name": "final_answer", "description": "answer description"}]
}
]
}
A bad procedure (violations)¶
Step 1 asks for facts (not allowed yet).
Final step outputs result instead of final_answer.
An unused foo output.
{
"NameDescription": "Bad example",
"steps": [
{
"id": 1,
"inputs": [{"name": "problem_text"}, {"name": "facts"}],
"stepDescription": "Do everything at once.",
"output": [{"name": "foo"}]
},
{
"id": 2,
"inputs": [{"name": "foo"}],
"stepDescription": "Compute number.",
"output": [{"name": "result"}]
}
]
}
Running validate_procedure_structured(...) will produce diagnostics such as:
REWRITE_FIRST_STEP (fatal): step 1 inputs wrong.
ADD_FINAL_STEP (fatal): missing final_answer.
PATCH_LOCALLY (repairable): remove unused foo.
Execution model¶
Use {mod}evoproc_procedures.runners:
Build visible inputs for the current step from the global state.
Prompt the LLM with a strict per-step JSON Schema (or your final-answer schema on the last step).
Merge only the declared outputs back into state.
from evoproc_procedures.runners import run_steps_stateful_minimal
from evoproc_procedures.schemas import get_schema
from evoproc_procedures.query_backends.ollama import query
state = run_steps_stateful_minimal(
proc,
problem_text=question,
answer_schema=get_schema("gsm"),
model="gemma3:latest",
query_fn=query,
print_bool=True,
)
print(state.get("final_answer"), state.get("answer_numerical"))
Prompting contract (creation)¶
When you ask the LLM to create a procedure, include:
Procedure.model_json_schema()verbatim in the prompt.The global-state rules (step 1 input, final step output).
A “no numeric computation” reminder.
The helper {func}evoproc_procedures.prompts.create_procedure_prompt already injects these.
Common pitfalls & fixes¶
“ModuleNotFoundError: evoproc_procedures…” Ensure your docs environment installs both packages and that conf.py adds both src/ paths.
Final step computes numbers Tighten your prompt: “Final step outputs a descriptive final_answer only—no numeric computation.”
Missing outputs The runner raises if the model omits a required key—keep that strict for reliability.