Marathon: long-running agent tasks (CLI)

Marathon lets you run a saved Agent across multiple sessions with the runtype CLI. It streams output in real time and provides local file and shell tools, automatic context compaction, checkpoints, and resumable state on disk.

Start a marathon

To start a marathon with a goal, run the following command:

Start a marathon
$runtype marathon <agent-id-or-name> -g "Refactor the auth module and add tests"

Replace <agent-id-or-name> with the Agent ID or name. Replace the goal text with the work that you want the Agent to complete.

Use the following flags to control the marathon:

  • --max-sessions <session-count>: Set the session cap. The default is 50.
  • --max-cost <amount-usd>: Set a cost cap in USD.
  • --model <model-id>: Override the Agent’s configured model.
  • --max-tokens <token-count> and --temperature <temperature>: Override the per-session output-token budget and sampling temperature. Set the temperature from 0 to 2. The CLI applies these values to every phase. Playbook milestones can override them per phase.
  • --name <task-name>: Set the task name for state files. The default is the Agent name.
  • --resume [message]: Continue from saved state, optionally with a new message.
  • --fresh: Start a new run and ignore saved state for the task.
  • --sandbox <provider>: Enable sandboxed code execution.
  • --session-search: Enable session context indexing and the search_session_history local tool. Both are enabled by default.
  • --no-session-search: Disable session context indexing and the search_session_history local tool.

Replace each angle-bracket value in a flag with the value for your task.

Marathon stores state in ~/.runtype/projects/<hash>/marathons/. The <hash> value identifies the project. The directory contains a <task>.json snapshot and a <task>.tree.jsonl session tree. It also contains a <task>.events/ directory for raw stream events and a <task>/outputs/ directory for large tool outputs that the context ledger offloads. Keep <task>/outputs/ with <task>.json and <task>.tree.jsonl when you move or back up a marathon task.

Steer a running Agent

While Marathon streams an Agent session, press Enter to open the steering composer. Use the following keys in the composer:

  • Press Enter to queue your message. The current session wraps up at the next tool call, and Marathon delivers the message at the start of the next session.
  • Press Tab to switch delivery between next turn and after all work. A follow-up message runs after the task would otherwise finish.
  • Press Esc to close the composer and keep your draft.

The CLI displays queued-message counts while messages wait. If Marathon is in a planning phase, file writes are restricted to the plan. A steering message that requests an immediate write takes effect after the plan is updated.

To stop the current session, press Esc twice while the Agent works. The session stops immediately. Marathon preserves observed cost and progress and creates a stopped checkpoint. It restores queued steering messages and the composer draft in the checkpoint input. Edit the input and press Enter to continue, or press Enter with empty input to exit. Resume a stopped task with --resume.

Use checkpoints

With checkpoint pauses enabled, Marathon pauses after each session and at the end of the task. At a checkpoint, press Enter to continue, enter a message to steer the next session, or enter a slash command.

Use the following commands at a checkpoint:

  • /model: Change the model for subsequent sessions.
  • /tools: Toggle local tools on or off.
  • /sandbox <provider>: Switch the sandbox provider.
  • /plan: Show the task plan.
  • /status: Show subtask completion.
  • /revert <file>: Restore a file from its checkpoint.
  • /reflect: Open the reflection editor to reassess the approach.
  • /tree: Browse session branches and switch between them.
  • /fork: Branch the conversation from an earlier message.
  • /copy: Copy the session JSON to the clipboard.
  • /copy-trimmed: Copy the session JSON with large payloads trimmed.
  • /open: Open the state file.
  • /stop: Stop the marathon and save state.
  • /help: Show all commands and shortcuts.

Replace <provider> with a sandbox provider and <file> with the path to a checkpointed file.

Manage session branches

Marathon records the conversation in <task>.tree.jsonl as a tree with parent links. Use /fork to branch from an earlier user message, or use /tree to switch to another checkpoint.

Use these commands to navigate session branches:

  • Enter /fork and select a user message on the current branch. Enter a new instruction to create a branch from that message. The next session uses history truncated at that message, and the original branch remains in the tree.
  • Enter /tree and select a checkpoint to switch branches. The next session continues from that checkpoint and its branch history. The tree view marks the current head and labels ledger rows such as artifacts and compaction summaries as metadata.

Resume a marathon

To resume a saved task with its full history, run runtype marathon <agent-id-or-name> --name <task-name> --resume. Replace <agent-id-or-name> with the Agent ID or name and <task-name> with the task name.

Marathon opens a resume checkpoint before the next session. Use it to steer the task, switch branches, or change settings.

Use playbooks

Use a playbook to replace the default Marathon workflow of research, planning, and execution with milestones that define instructions, models, completion rules, and runtime guardrails. To run a playbook named release-notes, use the following command:

Run a playbook
$runtype marathon <agent-id-or-name> -g "Write release notes for the repository changes" --playbook release-notes

The CLI checks playbook paths in this order:

  1. An exact file path.
  2. .runtype/marathons/playbooks/<playbook-name>.yaml, .yml, .json, .ts, or .mts in the current project.
  3. The same paths under ~/.runtype/.

Replace <playbook-name> with the value that you pass to --playbook.

Define milestones

The CLI runs milestones in order. The following YAML defines a two-milestone playbook:

release-notes.yaml
1name: release-notes
2policy:
3 allowedWriteGlobs: ['docs/releases/**']
4 requirePlanBeforeWrite: true
5milestones:
6 - name: research
7 model: claude-haiku-4-5
8 instructions: |
9 Review the repository changes.
10 Then write a short outline to the plan file: {{planPath}}
11 completionCriteria:
12 type: evidence
13 minReadFiles: 2
14 transitionSummary: 'Outline done. Moving to {{nextPhase}}.'
15 - name: write
16 model: claude-sonnet-4-6
17 fallbackModels: [claude-opus-4-8]
18 instructions: |
19 Write the release notes under docs/releases/ following the outline.
20 recovery:
21 afterEmptySessions: 1
22 message: Write a file under docs/releases/ now.
23 canAcceptCompletion: true

Use these fields to configure each milestone:

  • instructions: Define the prompt for the milestone. Support {{key}} interpolation from run state, such as {{planPath}}.
  • toolGuidance: Add guidance lines to the prompt’s tools section.
  • model and fallbackModels: Set the milestone’s model and its fallback chain for errors and stall escalation.
  • maxTokens and temperature: Set the output-token budget and sampling temperature from 0 to 2. These fields take precedence over the CLI flags and top-level defaults.
  • fallbackOnEmpty: Enable fallback models when the model returns no visible text after a successful request. The default is off. Set fallbackModels to provide a fallback chain.
  • completionCriteria: Define when the milestone advances.
  • recovery: Add a message after the configured number of consecutive empty sessions. The default is 2.
  • transitionSummary: Add a message when the milestone completes. Use {{nextPhase}} to reference the next phase.
  • canAcceptCompletion: Set this field to true on the final milestone to accept the Agent’s TASK_COMPLETE signal. Without an accepting milestone, the loader warns and the run can end only by stalling or exhausting its budget.

Set these fields at the playbook level:

  • policy: Narrow runtime behavior.
  • stallPolicy: Configure recovery, model escalation, and stalled-run thresholds.
  • maxTokens and temperature: Set defaults for milestones without their own values. CLI flags take precedence.
  • fallbackOnEmpty: Enable empty-output fallback for milestones without their own setting.
  • plugins: Load custom hook modules.

Set completion criteria

Choose a built-in completion criterion or a hook reference:

  • evidence: Advance after the Agent reads at least minReadFiles files. The default is 1.
  • sessions: Advance after at least minSessions sessions run in the milestone. The default is 1.
  • planWritten: Advance after the Agent writes its plan artifact.
  • never: Do not advance automatically. Only the Agent’s TASK_COMPLETE signal can finish when canAcceptCompletion is true.
  • Hook reference: Set type to a completion hook reference for custom logic.

Set policies

Use the policy block to narrow runtime behavior. A playbook policy cannot override global safety denials, so paths such as .env files and private keys remain blocked. Marathon adds the policy rules to the Agent’s tool guidance.

Use the following fields in the policy block:

  • allowedReadGlobs: Block reads outside matching paths.
  • allowedWriteGlobs: Block writes outside matching paths. The plan file remains allowed.
  • blockedTools: Block named tools.
  • blockDiscoveryTools: Block broad discovery tools.
  • requirePlanBeforeWrite: Require the plan before non-plan writes.
  • requireVerification: Require a passing verification command before TASK_COMPLETE.
  • outputRoot: For creation tasks, limit new files to a directory.

Configure stall handling

Use stallPolicy to react when consecutive sessions perform no tool actions. A text-only session counts as empty. Use the following fields to configure the response:

  • nudgeAfter: Inject a recovery message into the next session after the configured number of consecutive empty sessions.
  • escalateModelAfter: Switch the milestone to its next fallback model and restart after the configured number of consecutive empty sessions.
  • stopAfter: Mark the run as stalled after the configured number of consecutive empty sessions. The default is 3.

Milestone recovery messages and stallPolicy use the same counter. A session that contains text but no tool action still increments the counter.

Reference hooks

Use a registered hook name in any behavior slot instead of inline content. The builtin: namespace exposes hooks from the default workflow. Use these hooks to reuse one behavior without redefining it.

The following YAML uses built-in hooks for instructions, completion, and interception:

hooked-playbook.yaml
1milestones:
2 - name: research
3 instructions: builtin:research-instructions
4 completionCriteria: { type: builtin:research-complete }
5 intercept: builtin:research-guard

The loader rejects unknown hooks and hooks used in the wrong behavior slot while it loads the playbook.

Load plugins

To add custom hooks to a YAML playbook, list JavaScript modules in its plugins field. Use paths relative to the playbook. Each path must resolve inside the playbook directory.

The following YAML loads a custom hook module:

release-notes.yaml
1plugins:
2 - ./marathon-hooks.mjs

The module exports a recovery hook:

marathon-hooks.mjs
1export default function register({ registerWorkflowHook }) {
2 registerWorkflowHook('acme:strict-recovery', {
3 kind: 'recovery',
4 fn: () => 'Stop narrating. Your next response must contain a tool call.',
5 })
6}

Register hooks under a namespace that you choose, such as acme:. Reference the hook from a behavior slot, such as recovery: acme:strict-recovery. The plugin runs with your privileges when the playbook loads. Verification commands that Marathon runs use the same privilege level.

Write a TypeScript playbook

Create a playbook as a TypeScript module with a .ts or .mts extension. The CLI loads it at runtime without a build step. TypeScript playbooks accept plain functions in every behavior slot, so you can define custom logic without a plugin or hook registration.

The following TypeScript module defines a playbook:

.runtype/marathons/playbooks/my-task.ts
1import { definePlaybook, type RunTaskStateSlice } from '@runtypelabs/sdk'
2
3export default definePlaybook({
4 name: 'my-task',
5 stallPolicy: { nudgeAfter: 1, stopAfter: 4 },
6 milestones: [
7 {
8 name: 'build',
9 instructions: (state: RunTaskStateSlice) => `Build it. Plan: ${state.planPath}`,
10 recovery: (state) =>
11 `You went ${state.consecutiveEmptySessions ?? 0} sessions without a tool call. Write a file now.`,
12 canAcceptCompletion: true,
13 },
14 ],
15})

The definePlaybook helper comes from @runtypelabs/sdk. Install the package as a devDependency to get editor types. You can also export a plain object with the same shape without installing the package. To register named hooks, export a factory that receives { registerWorkflowHook }.

Next steps

Continue with these guides: