# step_config.json Format Specification

## 📋 Overview

The `step_config.json` file stores step-specific agent configurations (LLM models, tool selections, execution settings) separate from the main plan structure. This separation allows plan.json to focus on workflow structure while step_config.json manages execution details.

**Key Benefits:**
- **Separation of concerns**: Plan structure vs execution configuration
- **Consistent format**: Object format with `steps` array (both frontend and backend)
- **Backward compatibility**: Legacy array format supported during migration

---

## 📁 Key Files & Locations

| Component | File Path | Key Functions |
|-----------|-----------|---------------|
| **Frontend Parser** | [`frontend/src/components/workflow/hooks/usePlanData.ts`](../../frontend/src/components/workflow/hooks/usePlanData.ts) | `normalizeStepConfigFile()`, `saveStepConfig()` |
| **Backend Parser** | [`agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/step_config.go`](../../agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/step_config.go) | `ParseStepConfigContent()`, `ReadStepConfigs()`, `WriteStepConfigs()` |
| **Type Definitions** | [`frontend/src/utils/stepConfigMatching.ts`](../../frontend/src/utils/stepConfigMatching.ts) | `StepConfig`, `AgentConfigs` |
| **Backend Types** | [`agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/planning_agent.go`](../../agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/planning_agent.go) | `AgentConfigs` struct definition |

## 📂 File Locations

The `step_config.json` file can exist in two locations (with priority):

1. **Run-specific config** (highest priority): `{workspacePath}/runs/{runFolder}/planning/step_config.json`
   - Used when a specific run folder is selected
   - Allows different configs for different execution runs

2. **Default config** (fallback): `{workspacePath}/planning/step_config.json`
   - Used when no run folder is selected or run-specific config doesn't exist
   - Shared across all runs

**File**: [`step_config.go:40-76`](../../agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/step_config.go#L40)

The backend automatically checks run-specific config first, then falls back to default config.

---

## 🔄 Format Structure

### Object Format (Only Supported Format)

Both frontend and backend **read and write** only the object format with `steps` field:

```json
{
  "steps": [
    {
      "id": "step-id-here",
      "title": "Optional step title",
      "agent_configs": {
        "selected_servers": ["server1", "server2"],
        "selected_tools": ["server1:tool1", "server1:tool2", "server2:*"],
        "enabled_custom_tools": ["workspace_advanced:*", "human_tools:*"],
        "execution_llm": {
          "provider": "openai",
          "model_id": "gpt-4o"
        },
        "execution_max_turns": 25,
        "validation_max_turns": 10,
        "learning_max_turns": 5,
        "conditional_llm": {
          "provider": "openai",
          "model_id": "gpt-4o-mini"
        },
        "use_code_execution_mode": true,
        "disable_validation": false,
        "llm_validation_mode": "skip",
        "learnings_access": "read-write",
        "learning_objective": "Capture target-system selectors, auth flow quirks, and session-expiry signals",
        "lock_learnings": false,
        "enable_context_offloading": true,
        "enable_prerequisite_detection": true,
        "prerequisite_rules": [
          {
            "depends_on_step": "step-0",
            "description": "If login session is missing or expired, go back to step 0"
          }
        ]
      }
    }
  ]
}
```

### Field Structure

| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `steps` | ✅ | `array` | Root array containing step configurations |
| `steps[].id` | ✅ | `string` | Stable step ID from plan.json |
| `steps[].title` | ❌ | `string` | Step title for reference/display only |
| `steps[].agent_configs` | ❌ | `object` | Nested configuration object |

---

## 🔄 Implementation Details

### Frontend Implementation

**File:** [`frontend/src/components/workflow/hooks/usePlanData.ts`](../../frontend/src/components/workflow/hooks/usePlanData.ts)

**Read:**
```typescript
function normalizeStepConfigFile(rawContent: unknown): StepConfig[] {
  // Handles object format: { "steps": [...] }
  if (rawContent && typeof rawContent === 'object' && !Array.isArray(rawContent)) {
    const obj = rawContent as Record<string, unknown>
    if ('steps' in obj && Array.isArray(obj.steps)) {
      return obj.steps as StepConfig[]
    }
  }
  // Legacy array format support (with warning)
  if (Array.isArray(rawContent)) {
    return rawContent as StepConfig[]
  }
  return []
}
```

**Write:**
```typescript
const content = JSON.stringify({ steps: stepConfigs }, null, 2)
await agentApi.updatePlannerFile(stepConfigPath, content, `Updated step config for step ${stepId}`)
```

### Backend Implementation

**File:** [`agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/step_config.go`](../../agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/step_config.go)

**Read:**
```go
type StepConfigFile struct {
    Steps []StepConfig `json:"steps"`
}

func ParseStepConfigContent(content string) ([]StepConfig, error) {
    var file StepConfigFile
    if err := json.Unmarshal([]byte(content), &file); err != nil {
        return nil, err
    }
    return file.Steps, nil
}
```

**Write:**
```go
file := StepConfigFile{Steps: configs}
content, err := json.MarshalIndent(file, "", "  ")
```

---

## ⚙️ Configuration Fields

**File**: [`planning_agent.go`](../../agent_go/pkg/orchestrator/agents/workflow/step_based_workflow/planning_agent.go) - `AgentConfigs` struct

### LLM Configuration

| Field | Type | Default | Purpose |
|-------|------|---------|---------|
| `execution_llm` | `object` | Preset default | LLM config for execution agent (`{ provider: string, model_id: string }`) |
| `validation_llm` | `object` | Preset default | LLM config for validation agent |
| `conditional_llm` | `object` | Preset default | Step-specific conditional LLM for conditional step evaluation |

### Max Turns Configuration

| Field | Type | Default | Purpose |
|-------|------|---------|---------|
| `execution_max_turns` | `number` | Preset default (typically 100) | Maximum conversation turns for execution agent |
| `validation_max_turns` | `number` | Preset default (typically 100) | Maximum conversation turns for validation agent |
| `learning_max_turns` | `number` | Preset default (typically 100) | Maximum conversation turns for learning agent |
| `orchestration_max_iterations` | `number` | Orchestrator max turns (typically 100) | Maximum iterations for orchestration step loop |

### Tool & Server Configuration

| Field | Type | Default | Purpose |
|-------|------|---------|---------|
| `selected_servers` | `string[]` | `[]` | MCP servers to use for this step (subset of preset servers) |
| `selected_tools` | `string[]` | `[]` | Specific tools (format: `"server:tool"` or `"server:*"` for all tools) |
| `enabled_custom_tools` | `string[]` | `[]` | **Unified format**: Custom tools to enable (format: `"category:tool"` or `"category:*"`). Current categories include `workspace_advanced` (shell/diff/media), `workspace_tools` (backward-compatible alias for the current workspace registry), `workspace_browser`, and `human_tools`. Legacy `workspace_basic` / `list_workspace_files` style tools are not exposed to current workflow agents. Example: `"workspace_advanced:execute_shell_command"` |
| `enabled_custom_tool_categories` | `string[]` | `[]` | **Legacy format**: Tool categories (e.g., `["workspace_tools", "human_tools"]`) - deprecated, use `enabled_custom_tools` instead |

### Validation Configuration

| Field | Type | Default | Purpose |
|-------|------|---------|---------|
| `disable_validation` | `boolean` | `true` (nil = disabled) | LLM validation control: `nil`/`true` = disabled (auto-approve), `false` = enabled. Pre-validation always runs if schema exists. |
| `llm_validation_mode` | `"skip"\|"auto"\|"always"` | `"skip"` | Validation strategy: `"skip"` (trust pre-validation), `"auto"` (skip after 3 successes), `"always"` (standard) |

### Learning Configuration

| Field | Type | Default | Purpose |
|-------|------|---------|---------|
| `learnings_access` | `"read"\|"read-write"\|"none"` | `"read"` (auto-migrated from `learning_objective` if unset) | Primary gate for the global learnings store. `"read"` — step sees `learnings/_global/SKILL.md` in its prompt; `"read-write"` — step also contributes (requires non-empty `learning_objective`); `"none"` — step neither reads nor contributes. Mirrors `knowledgebase_access`. |
| `learning_objective` | `string` | `""` | Extraction instruction for the post-step learning agent — describes what patterns/selectors/recipes should land in `SKILL.md`. Required when `learnings_access="read-write"`; the validator rejects the combination of write access with an empty objective. |
| `lock_learnings` | `boolean` | `false` (nil = unlocked) | Freeze SKILL.md writes for this step. Existing `SKILL.md` still flows into execution prompts, but no new writes. Runtime execution never auto-sets or auto-clears this field; it is a builder/user decision and should be paired with `review_notes`. |

### Execution Mode Configuration

| Field | Type | Default | Purpose |
|-------|------|---------|---------|
| `use_code_execution_mode` | `boolean` | Preset default (nil = use preset) | Step-level code execution mode override (nil = use preset default, true/false = override) |
| `enable_context_offloading` | `boolean` | `true` (nil = enabled) | Enable/disable context offloading virtual tools |

Legacy note:
`use_tool_search_mode` and `pre_discovered_tools` should not be treated as active workflow step config fields anymore. The current workflow editor strips those legacy keys on save, and the canonical documentation now lives in [Core Tool Search Mode](../core/tool_search_mode.md).

### Prerequisite Detection Configuration

| Field | Type | Default | Purpose |
|-------|------|---------|---------|
| `enable_prerequisite_detection` | `boolean` | `false` | Enable prerequisite failure detection for this step |
| `prerequisite_rules` | `array` | `[]` | Array of prerequisite rules, each with `depends_on_step` (string) and `description` (string) |

## 🛠️ Common Issues & Solutions

| Issue | Cause | Solution |
|-------|-------|----------|
| `"steps" field not found` | File uses legacy array format | Use `normalizeStepConfigFile()` to handle both formats (frontend) or ensure backend uses object format |
| `Step ID mismatch` | Step ID doesn't match plan.json | Ensure step IDs are stable and match plan.json exactly |
| `agent_configs is null` | Step has no configuration | This is valid - step will use preset defaults |
| `Parse error` | Invalid JSON structure | Verify file uses object format: `{ "steps": [...] }` |
| `Config not found` | Run-specific config missing | Backend automatically falls back to default config in `{workspacePath}/planning/step_config.json` |
| `enabled_custom_tool_categories` ignored | Using legacy format | Migrate to unified `enabled_custom_tools` format: `"category:tool"` or `"category:*"` |

---

## 🔍 For LLMs: Quick Reference

**Constraints:**
- ✅ **Allowed**: Object format `{ "steps": [...] }`
- ✅ **Allowed**: Empty `steps` array `[]`
- ✅ **Allowed**: Missing `agent_configs` (uses defaults)
- ❌ **Forbidden**: Root-level array format (legacy, deprecated)
- ❌ **Forbidden**: Missing `id` field in step config

**Example:**
```json
{
  "steps": [
    {
      "id": "step-1",
      "agent_configs": {
        "selected_servers": ["aws"],
        "execution_llm": {
          "provider": "openai",
          "model_id": "gpt-4o"
        }
      }
    }
  ]
}
```

**File Operations:**
- **Read**: Always parse object format, extract `steps` array
- **Write**: Always write object format: `JSON.stringify({ steps: stepConfigs }, null, 2)`
- **Update**: Find step by `id`, update `agent_configs`, save entire file
- **Config Priority**: Run-specific config (`runs/{runFolder}/planning/step_config.json`) takes precedence over default config (`planning/step_config.json`)

**Field Notes:**
- All boolean fields use `nil` (undefined) to mean "use default/preset value"
- `enabled_custom_tools` uses unified format: `"category:tool"` or `"category:*"`. Current categories include `workspace_advanced`, `workspace_tools`, `workspace_browser`, and `human_tools`; use `execute_shell_command` / `diff_patch_workspace_file` for file access instead of legacy basic workspace tool names.
- `learning_detail_level` accepts `"exact"`, `"general"`, or `"none"` (default: `"exact"`)
- `orchestration_max_iterations` only applies to orchestration step types

---

## 📖 Related Documentation

- [Workflow Docs](README.md) - Overall workflow architecture
- [Learn Code and Code Execution Modes](learn_code_flow.md) - Scripted execution configuration and flow
- [Step Config Matching](../../frontend/src/utils/stepConfigMatching.ts) - Type definitions and matching logic
