Skip to content

learn_code_flow

github-actions[bot] edited this page Oct 6, 2026 · 5 revisions

Learn Code and Code Execution Modes

This is the current source of truth for scripted workflow execution.

Update 2026-10-04 (PLAT-436): runs never self-heal. In any run (schedule, webhook, run_full_workflow, a route called by an agent) a scripted step runs its saved main.py and nothing else. A missing or failing script fails the step with its real error and Pulse reports it; no LLM generates, repairs or stands in for the script, nothing is saved back, and there is no code_exec fallback. Generation, repair and save-back below happen only when the Builder runs the step itself with execute_step (Workshop mode, not fast_path_only, not a scheduled session). Decision: docs/DECISIONS.md, ticket bugs/pulse_platform/goals/steps/scripted/plat-436.md.

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 reusable main.py that is retried on future runs before the LLM is called.

Overview

At the workflow step level, scripted execution is controlled by two fields in agent_configs:

  • use_code_execution_mode
  • declared_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_code is detected only when declared_execution_mode == "learn_code".
  • code_exec is the fallback mode whenever scripted execution is enabled but the step is not explicitly marked as learn_code.
  • syncDeclaredExecutionModeConfig() forces use_code_execution_mode=true when declared_execution_mode is learn_code or code_exec.

That means the recommended workflow config is to set both fields explicitly for every scripted step.

Recommended Usage

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

Configuration

Preferred learn_code config

{
  "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"
  }
}

Ephemeral code_exec config

{
  "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 defaults

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.

Shared Architecture

Both modes use the same bridge-based execution model.

The execution agent does not call most MCP tools directly. Instead it:

  1. Uses get_api_spec(tool_name) to inspect a tool's HTTP contract.
  2. Uses execute_shell_command to write and run Python or shell code.
  3. 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_URL
  • MCP_API_TOKEN
  • STEP_OUTPUT_DIR
  • STEP_EXECUTION_DIR
  • resolved SECRET_* and VAR_* values

This is the same bridge used by CLI-style providers that require HTTP tool routing.

Mode Resolution and Precedence

The execution loop resolves mode in two layers:

  1. Determine whether the step is in persistent scripted mode:
    • isScriptedExecutionModeConfig(cfg) returns true only for declared_execution_mode == "learn_code".
  2. Determine whether code execution is enabled at all:
    • step config use_code_execution_mode
    • otherwise workflow/preset default
    • then learn_code forces code execution on

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, and codex-cli.

learn_code Flow

learn_code adds persistence and a saved-script fast path on top of normal code execution.

Persistent paths

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

Fast path

Before the LLM runs, the controller attempts tryRunSavedLearnCodeScript(...).

High-level flow:

  1. Check whether learnings/{step-id}/main.py exists.
  2. Run static review on the saved script.
  3. Copy the saved script into execution/{step-path}/code/ when needed.
  4. Clean the step output directory while preserving code/.
  5. Run python3 main.py with workflow env vars and step arguments.
  6. Run pre-validation on outputs.
  7. If script execution and validation pass, finish with zero LLM tokens for that run.

Static review before fast path

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/ or learnings/

When static review fails, the system skips the fast path and falls back to LLM repair/generation.

Authoring and repair (the Builder's own execute_step only)

If the saved script fails or does not exist and the Builder is running the step itself (see the update above; a run fails the step instead):

  1. The execution agent writes or repairs execution/{step-path}/code/main.py.
  2. The controller reruns pre-validation.
  3. On failure, it starts a learn-code repair loop.

Repair loop behavior:

  • up to 3 fix iterations
  • fresh Tier 1 (High) repair agent each iteration
  • feedback message includes: task description, pointer to current main.py on 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/

Save-back behavior (the Builder's own execute_step only)

After a Builder-run learn-code execution, the controller saves the latest script back (code/{step-id}/, or learnings/{step-id}/ in the legacy layout) unless the script has syntax errors or lock_code freezes the saved script. A run never saves anything back.

Learning access vs code lock

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 Stops the Builder's own execute_step from repairing or rewriting the script. Runs are unaffected: they never rewrite a script whether or not it is locked.

When lock_code: true is set on a step:

  • Builder execute_step: the saved script runs; the repair loop is skipped (maxFixIter = -1), no repair agents are created, and nothing is saved back.
  • Runs: unchanged. The saved script runs and a failure fails the step.
  • Metadata: script_metadata.json is still updated (run history, failure patterns) for observability.

A script that keeps failing repeats the same failure every run until the Builder fixes main.py (set lock_code: false first if it is locked, then execute_step).

To force a complete rewrite: delete the saved main.py (not the execution copy), then run execute_step as the Builder. The LLM will generate fresh.

After repair exhaustion (the Builder's own execute_step only)

If the repair loop is exhausted, the Builder's execute_step continues in plain code_exec mode for its remaining retries so the Builder can see what the agent can do. This is an authoring aid; a run never falls back to code_exec.

code_exec Flow

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.py fast path is attempted
  • no run_saved_main_py support
  • 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.

Prompting Expectations for Scripted Steps

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.py and 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_spec before writing browser/MCP code to learn exact parameter schemas instead of guessing
  • script_metadata.json is referenced by path (not inlined) so the LLM reads it on demand

When to Use Which Mode

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

Operational Notes

  • CLI providers may force code execution behavior because they route tools through the HTTP bridge.
  • learn_code steps force UseCodeExecutionMode = true regardless of provider — this ensures the agent gets the tool index and get_api_spec virtual tool for proper tool discovery when writing main.py.
  • Learning agents are still separate from execution agents; code execution mode mainly affects execution-time tool access and scripting behavior.
  • learn_code_script_execution events exist specifically for saved-script runs and repair visibility in the UI.
  • error_summary in script_metadata.json run records is stored in full (not truncated). error_snippet in last_failure is capped at 2000 chars for prompt inclusion.

Key Files

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

Orchestrator (todo_task) learn_code

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.py once; 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

Related Docs

Clone this wiki locally