-
Notifications
You must be signed in to change notification settings - Fork 3
learn_code_flow
This is the current source of truth for scripted workflow execution.
learn_code and code_exec are not separate systems. They are two execution modes built on the same code-execution foundation:
-
code_exec: the agent writes and runs code for the current run only. -
learn_code: the agent writes and maintains a reusablemain.pythat is retried on future runs before the LLM is called.
At the workflow step level, scripted execution is controlled by two fields in agent_configs:
use_code_execution_modedeclared_execution_mode
The current behavior is:
| Setting | Effect |
|---|---|
use_code_execution_mode: false |
Step uses normal direct-tool execution, not scripted code execution |
use_code_execution_mode: true + declared_execution_mode: "code_exec"
|
Step uses ephemeral code execution only |
use_code_execution_mode: true + declared_execution_mode: "learn_code"
|
Step uses persistent scripted execution with saved main.py fast path |
Important implementation detail:
-
learn_codeis detected only whendeclared_execution_mode == "learn_code". -
code_execis the fallback mode whenever scripted execution is enabled but the step is not explicitly marked aslearn_code. -
syncDeclaredExecutionModeConfig()forcesuse_code_execution_mode=truewhendeclared_execution_modeislearn_codeorcode_exec.
That means the recommended workflow config is to set both fields explicitly for every scripted step.
Default to code_exec. Promote a workflow step to learn_code only when all gates are satisfied:
- the user explicitly asked for scripted/learn-code execution
- the behavior is highly deterministic, with stable inputs, tools, output contract, and little/no per-instance judgment
- there is broad stability evidence, normally 10+ successful runs across the relevant variable groups/scenarios with eval or metric evidence still at target
Good learn_code candidates after those gates:
- structured data transforms
- report building
- deterministic validation logic
- fixed API call sequences
- repeatable file processing
- browser flows only when the user explicitly requested scripted browser automation and 10+ scenario-covering runs prove durable selectors and predictable navigation
Use code_exec when the step still benefits from scripting, but the exact logic changes from run to run:
- exploratory browser work
- adaptive investigations
- tasks where the agent must improvise heavily based on page state or live results
- one-off data collection patterns that are unlikely to stabilize into a reusable script
{
"id": "step-id",
"agent_configs": {
"use_code_execution_mode": true,
"declared_execution_mode": "learn_code",
"declared_execution_mode_reason": "Stable scripted flow with reusable Python"
}
}{
"id": "step-id",
"agent_configs": {
"use_code_execution_mode": true,
"declared_execution_mode": "code_exec",
"declared_execution_mode_reason": "Adaptive step that changes between runs"
}
}Workshop guidance treats code_exec as the default. learn_code is an opt-in promotion after explicit user request plus deterministic behavior and 10+ scenario-covering successful runs. The workshop tools also expose:
update_step_config(...)run_saved_main_py(step_id, group_id?)
run_saved_main_py is valid only for learn_code steps, because only those steps have a persistent saved-script fast path.
Both modes use the same bridge-based execution model.
The execution agent does not call most MCP tools directly. Instead it:
- Uses
get_api_spec(tool_name)to inspect a tool's HTTP contract. - Uses
execute_shell_commandto write and run Python or shell code. - Calls per-tool HTTP endpoints such as:
POST /tools/mcp/{server}/{tool}POST /tools/custom/{tool}
Core env vars injected into scripted runs include:
MCP_API_URLMCP_API_TOKENSTEP_OUTPUT_DIRSTEP_EXECUTION_DIR- resolved
SECRET_*andVAR_*values
This is the same bridge used by CLI-style providers that require HTTP tool routing.
The execution loop resolves mode in two layers:
- Determine whether the step is in persistent scripted mode:
-
isScriptedExecutionModeConfig(cfg)returns true only fordeclared_execution_mode == "learn_code".
-
- Determine whether code execution is enabled at all:
- step config
use_code_execution_mode - otherwise workflow/preset default
- then
learn_codeforces code execution on
- step config
Additional behavior:
- Step config overrides workflow default.
- Workflow default no longer auto-enables code execution globally.
- Provider-specific auto-enable is handled per agent for CLI providers such as
claude-code,pi-cli, andcodex-cli.
learn_code adds persistence and a saved-script fast path on top of normal code execution.
| Path | Purpose |
|---|---|
learnings/{step-id}/main.py |
Canonical saved script for future runs |
learnings/{step-id}/diffs/ |
Diffs between saved versions |
execution/{step-path}/code/main.py |
Per-run working copy that the LLM edits |
execution/{step-path}/code/fix-diffs/ |
Diffs between repair iterations in the same run |
execution/{step-path}/ |
Output folder for artifacts validated by the step |
Before the LLM runs, the controller attempts tryRunSavedLearnCodeScript(...).
High-level flow:
- Check whether
learnings/{step-id}/main.pyexists. - Run static review on the saved script.
- Copy the saved script into
execution/{step-path}/code/when needed. - Clean the step output directory while preserving
code/. - Run
python3 main.pywith workflow env vars and step arguments. - Run pre-validation on outputs.
- If script execution and validation pass, finish with zero LLM tokens for that run.
The controller reviews the saved script before trusting it. It rejects fast path when it sees patterns such as:
- hardcoded execution paths
- hardcoded fallbacks for required env vars
- sibling-step path hacks
- writes outside the managed step output area
- direct writes into system-managed directories like
knowledgebase/orlearnings/
When static review fails, the system skips the fast path and falls back to LLM repair/generation.
If fast path fails or no saved script exists:
- The execution agent writes or repairs
execution/{step-path}/code/main.py. - The controller reruns pre-validation.
- On failure, it starts a learn-code repair loop.
Repair loop behavior:
- up to 3 fix iterations (configurable via
LearnCodeMaxFixIter) - fresh Tier 1 (High) repair agent each iteration
- feedback message includes: task description, pointer to current
main.pyon disk (not inlined), static code review issues, last execution output + exit code, and attempt counter - validation details are intentionally omitted from feedback to prevent the LLM from fabricating outputs that match the schema
- diffs are written under
execution/{step-path}/code/fix-diffs/
After learn-code execution, the controller saves the latest script back into learnings/{step-id}/ unless the script has syntax errors or lock_code freezes the saved script.
This means learn_code is not only a fast path. It is also the persistent script-maintenance path.
Learning writes use an access level; saved code has a separate lock:
| Setting | Controls | Effect |
|---|---|---|
learnings_access ("read"|"read-write"|"none") |
SKILL.md read/write at a coarse level | Default "read" — step sees _global/SKILL.md but doesn't contribute. "read-write" (+ non-empty learning_objective) opts into contribution. "none" opts out of both. Mirrors knowledgebase_access. |
lock_code: true |
main.py | Prevents LLM-rewritten scripts from being saved back to learnings. Skips the fix loop entirely (falls back directly to code_exec mode). |
When lock_code: true is set on a step:
- Fast path: Saved script is still copied from learnings to execution and run normally
-
Fix loop: Skipped entirely (
maxFixIter = -1) — no repair agents are created, no tokens spent on fixes that would be discarded - Save-back: Blocked — the LLM's rewritten script is NOT copied back to learnings
- Fallback: Falls through directly to code_exec mode (tools directly, no main.py)
-
Metadata:
script_metadata.jsonis still updated (run history, failure patterns) for observability
This means a locked script that keeps failing will repeat the same failure every run. The user must manually fix learnings/{step-id}/main.py or set lock_code: false to let the system fix it.
To force a complete rewrite: delete learnings/{step-id}/main.py (not the execution copy), then run execute_step. The LLM will generate fresh.
If the learn-code repair loop is exhausted (or skipped due to locked learnings), the controller disables persistent scripted mode for the remaining outer retries and continues in plain code_exec mode.
That fallback is important:
-
learn_codeis the explicitly requested, proven deterministic fast path -
code_execis the default and the recovery path when the saved script is not currently salvageable within the repair budget
code_exec uses the same bridge and env model, but it does not rely on a persistent saved script.
Behavior:
- the agent writes and runs code for the current step run
- no saved
learnings/{step-id}/main.pyfast path is attempted - no
run_saved_main_pysupport - the step still benefits from script-based batching, loops, parsing, and multi-tool orchestration
This is the correct mode when scripting is useful but persistence would create more churn than value.
The controller prompt for scripted execution expects:
- outputs to be written under
STEP_OUTPUT_DIR - script working files to live under
STEP_EXECUTION_DIR/code/ - variables to be passed through env vars or runtime args, not hardcoded
- diagnostic output to go to stdout/stderr so repair loops can reason over failures
For learn_code, the prompt also emphasizes:
- maintaining a reusable
main.pyand repairing it incrementally - no fabricated data: every output value must trace to a real data source (MCP tool call, API response, or input file)
- browser automation rules: snapshot-first agent_browser interaction, fresh refs, and durable persisted selectors
-
tool discovery: call
get_api_specbefore writing browser/MCP code to learn exact parameter schemas instead of guessing -
script_metadata.jsonis referenced by path (not inlined) so the LLM reads it on demand
Choose learn_code only when:
- the user explicitly asked for learn_code/scripted persistence
- the task shape is highly deterministic
- 10+ successful runs cover the relevant scenarios/groups
- eval/metric evidence is still at target
- you want future runs to be cheap and fast and accept the risk of freezing assumptions into
main.py
Choose code_exec when:
- the task shape changes too much between runs
- persistence would encode brittle assumptions
- the agent needs exploratory or dynamic behavior each time
- CLI providers may force code execution behavior because they route tools through the HTTP bridge.
-
learn_codesteps forceUseCodeExecutionMode = trueregardless of provider — this ensures the agent gets the tool index andget_api_specvirtual tool for proper tool discovery when writingmain.py. - Learning agents are still separate from execution agents; code execution mode mainly affects execution-time tool access and scripting behavior.
-
learn_code_script_executionevents exist specifically for saved-script runs and repair visibility in the UI. -
error_summaryinscript_metadata.jsonrun records is stored in full (not truncated).error_snippetinlast_failureis capped at 2000 chars for prompt inclusion.
| File | Role |
|---|---|
agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/controller_execution.go |
Main execution loop, fast-path invocation, repair loop, fallback handling |
agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/controller_learn_code.go |
Saved-script execution, static review, save-back, diff capture |
agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/interactive_workshop_manager.go |
Mode semantics, workshop guidance, run_saved_main_py, config sync helpers |
agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/step_config.go |
Applies step config and syncs declared mode to boolean flags |
agent_go/cmd/server/server.go |
Per-tool HTTP endpoints and bridge env setup |
agent_go/pkg/workspace/execute_shell_command.go |
Shell execution guardrails and tool-routing constraints |
This doc covers regular-step learn_code. Todo-task orchestrators also have a learn_code fast path with different semantics:
-
Read-only at runtime — builder writes
main.pyonce; no repair loop, no save-back, no fix iterations - Eligibility:
declared_execution_mode="learn_code"+len(predefined_routes) >= 1 - Script calls sub-agents via
POST ${MCP_API_URL}/tools/custom/call_sub_agent - Fallback is fresh — LLM orchestrator starts from zero, no script state carried over
- See orchestrator-step-type.md for full details
Auto-synced from docs/ on main. Edit there, not here.