Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/openai/symphony/llms.txt

Use this file to discover all available pages before exploring further.

The hooks configuration section allows you to run custom shell scripts at key points in the workspace lifecycle.

Configuration

WORKFLOW.md
hooks:
  after_create: |
    git clone --depth 1 https://github.com/openai/symphony .
    if command -v mise >/dev/null 2>&1; then
      cd elixir && mise trust && mise exec -- mix deps.get
    fi
  before_remove: |
    cd elixir && mise exec -- mix workspace.before_remove
  timeout_ms: 60000

Fields

after_create
string
Shell script executed immediately after a new workspace directory is created.Runs only once when the workspace is first created for an issue. On subsequent runs (workspace reuse), this hook does not execute.Execution context:
  • Working directory: Workspace path
  • Shell: sh -lc <script> (POSIX-compatible)
  • Timeout: hooks.timeout_ms
Failure handling:
  • Hook failure aborts workspace creation
  • The agent run fails and schedules a retry
  • Workspace directory may be removed on creation failure
Common use cases:
  • Clone repository
  • Install dependencies
  • Set up environment (mise, asdf, etc.)
  • Initialize configuration files
before_run
string
Shell script executed before each agent run attempt.Runs every time an agent session starts, including:
  • First run
  • Retry attempts
  • Continuation runs after hitting max_turns
Execution context:
  • Working directory: Workspace path
  • Shell: sh -lc <script>
  • Timeout: hooks.timeout_ms
Failure handling:
  • Hook failure aborts the current run attempt
  • The orchestrator schedules a retry with exponential backoff
Common use cases:
  • Sync latest code from remote (git pull)
  • Refresh dependencies
  • Update configuration
  • Pre-run validation
after_run
string
Shell script executed after each agent run attempt completes.Runs every time an agent session ends, regardless of outcome:
  • Success
  • Failure
  • Timeout
  • Cancellation/stall
Execution context:
  • Working directory: Workspace path (if it exists)
  • Shell: sh -lc <script>
  • Timeout: hooks.timeout_ms
Failure handling:
  • Hook failure is logged and ignored
  • The agent run’s original outcome is preserved
  • Cleanup/reconciliation continues normally
Common use cases:
  • Upload logs/artifacts
  • Post-run metrics collection
  • Cleanup temporary files
  • Send notifications
before_remove
string
Shell script executed before a workspace directory is deleted.Runs when:
  • Issue transitions to a terminal state (orchestrator cleanup)
  • Service startup cleanup for terminal issues
  • Manual workspace removal
Execution context:
  • Working directory: Workspace path (only if directory exists)
  • Shell: sh -lc <script>
  • Timeout: hooks.timeout_ms
Failure handling:
  • Hook failure is logged and ignored
  • Workspace removal proceeds even if hook fails
Common use cases:
  • Archive workspace state
  • Upload final artifacts
  • Clean up external resources
  • Send completion notifications
timeout_ms
integer
default:60000
Maximum execution time for all hooks in milliseconds (1 minute default).If a hook exceeds this timeout:
  • The process is killed (Task.shutdown(task, :brutal_kill))
  • The hook is treated as failed
  • Failure handling depends on the hook type (see above)
Dynamic: Changes apply to future hook executions without restart.Non-positive values are treated as invalid and fall back to the default.

Execution Context

All hooks execute in a task with:
System.cmd("sh", ["-lc", command], cd: workspace, stderr_to_stdout: true)
  • Shell: sh -lc (POSIX login shell)
  • Working directory: Workspace path
  • Output: stderr redirected to stdout
  • Timeout: hooks.timeout_ms

Environment Variables

Hooks inherit the environment from the Symphony process, including:
  • HOME, USER, PATH
  • Any custom variables set before launching Symphony
  • Shell profile/rc files are loaded (-l flag)

Exit Codes

Hook success/failure is determined by exit code:
  • 0: Success
  • Non-zero: Failure (logged with output)

Hook Lifecycle Example

For issue ABC-123 across multiple runs:

First Run (New Workspace)

1. Orchestrator dispatches ABC-123
2. Workspace.create_for_issue(ABC-123)
   - Directory created: ~/workspaces/ABC-123/
   - Hook: after_create (runs: git clone, mise setup)
3. Workspace.run_before_run_hook(ABC-123)
   - Hook: before_run (runs: git pull, refresh deps)
4. AgentRunner.run(ABC-123)
   - Codex session executes
5. Workspace.run_after_run_hook(ABC-123)
   - Hook: after_run (runs: upload logs)

Second Run (Workspace Reuse)

1. Orchestrator dispatches ABC-123 (retry/continuation)
2. Workspace.create_for_issue(ABC-123)
   - Directory exists: ~/workspaces/ABC-123/
   - Hook: after_create (SKIPPED - not a new workspace)
3. Workspace.run_before_run_hook(ABC-123)
   - Hook: before_run (runs: git pull, refresh deps)
4. AgentRunner.run(ABC-123)
   - Codex session executes
5. Workspace.run_after_run_hook(ABC-123)
   - Hook: after_run (runs: upload logs)

Terminal Cleanup

1. Issue ABC-123 transitions to "Done"
2. Orchestrator reconciliation detects terminal state
3. Workspace.remove(ABC-123)
   - Hook: before_remove (runs: archive workspace)
   - Directory removed: ~/workspaces/ABC-123/

Hook Output Handling

Hook output is logged for debugging:
def sanitize_hook_output_for_log(output, max_bytes \\ 2_048) do
  binary_output = IO.iodata_to_binary(output)

  case byte_size(binary_output) <= max_bytes do
    true -> binary_output
    false -> binary_part(binary_output, 0, max_bytes) <> "... (truncated)"
  end
end
Example log entry:
Workspace hook failed hook=after_create issue_id=abc123 issue_identifier=ABC-123 workspace=/path/to/workspace status=1 output="fatal: destination path '.' already exists...\n... (truncated)"

Common Patterns

Repository Clone and Setup

hooks:
  after_create: |
    git clone --depth 1 https://github.com/org/repo .
    git checkout main
    npm install
  before_run: |
    git fetch origin
    git reset --hard origin/main
    npm install

Python Environment

hooks:
  after_create: |
    python -m venv .venv
    source .venv/bin/activate
    pip install -r requirements.txt
  before_run: |
    source .venv/bin/activate
    pip install -r requirements.txt --upgrade

Tool Version Management (mise/asdf)

hooks:
  after_create: |
    mise trust
    mise install
    mise exec -- npm install
  before_run: |
    mise exec -- git pull
    mise exec -- npm install

Artifact Upload

hooks:
  after_run: |
    if [ -f run.log ]; then
      aws s3 cp run.log s3://bucket/logs/$(date +%s).log
    fi
  before_remove: |
    tar czf workspace.tar.gz .
    aws s3 cp workspace.tar.gz s3://bucket/archives/$ISSUE_ID.tar.gz

Conditional Logic

hooks:
  after_create: |
    if [ -f package.json ]; then
      npm install
    elif [ -f Gemfile ]; then
      bundle install
    elif [ -f requirements.txt ]; then
      pip install -r requirements.txt
    fi

Debugging Hooks

To test hook execution manually:
# Navigate to a workspace
cd ~/workspaces/ABC-123

# Run the hook command
sh -lc 'git clone --depth 1 https://github.com/org/repo .'

# Check exit code
echo $?
To see hook logs from Symphony:
# Search logs for hook execution
grep "Running workspace hook" symphony.log
grep "Workspace hook failed" symphony.log
grep "Workspace hook timed out" symphony.log

Configuration Reloading

Hook configuration changes are applied dynamically:
  • New hook scripts take effect immediately
  • Timeout changes apply to future hook executions
  • Running hooks complete with their original configuration
No restart required.
  • workspace - Configure workspace root and layout
  • codex - Control agent execution environment
  • agent - Manage retry behavior when hooks fail

Build docs developers (and LLMs) love