Skip to navigation

Marathon: long-running agent tasks (CLI)

Marathon runs an Agent across multiple sessions from your terminal with the runtype CLI. It streams output in real time and gives the Agent local file tools and a verification-command tool. It also compacts context automatically, pauses at checkpoints, and saves resumable state on disk.

By default, a marathon moves through three phases: research, planning, and execution. In the planning phase, the Agent writes a plan to .runtype/marathons/<task>/plan.md in your project. The execution phase then carries out that plan. To replace these phases with your own milestones, use a playbook.

Before you begin

Complete these steps before you start a marathon:

  • Install the Runtype CLI with npm install -g @runtypelabs/cli.
  • Sign in with runtype login. For other sign-in options, see Authentication.
  • Open a terminal in the root of the project that the Agent works on. The local file tools can reach only files inside the directory where you run the command.

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. The runtype agents task command accepts the same arguments and flags.

To find an Agent by name, the CLI compares the name, without regard to case, against your 50 most recently created Agents. If none of them matches, the CLI creates a new Agent with that name and saves the selected model and tools on it. To avoid creating an Agent by mistake, pass the Agent ID, which starts with agent_.

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.
  • --planning-model <model-id> and --execution-model <model-id>: Set the model for the research and planning phases, or for the execution phase.
  • --fallback-model <model-id>: Set a model to use when the primary model fails.
  • --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.
  • -t, --tools <tool-name...>: Enable built-in tools, such as exa or firecrawl.
  • --sandbox <provider>: Enable sandboxed code execution. Set <provider> to runtype-sandbox, daytona, cloudflare-worker, or quickjs.
  • --no-session-search: Turn off search over earlier session output. By default, the Agent can call search_session_history to look up output from earlier sessions.
  • --no-auto-compact: Turn off automatic context compaction. To change when compaction starts, set --compact-threshold to a percentage, such as 90%, or to a token count.
  • --checkpoint-timeout <seconds>: Set how long a checkpoint waits before Marathon continues. For more information, see Use checkpoints.
  • --track: Sync progress to an agent-task record named after the task, so that you can follow the run in the dashboard.
  • --json: Print the final result as JSON.
  • --name <task-name>: Set the task name for state files. The default is the Agent ID.
  • --resume [message]: Continue from saved state, optionally with a new message.
  • --session <task-name>: Select a saved task by name. Use it with --resume.
  • --fresh: Start a new run and ignore saved state for the task.
  • --state-dir <path>: Store state files in a different directory.

Replace each angle-bracket value in a flag with the value for your task. For the full list of flags, run runtype marathon --help.

--fallback-model and a playbook’s fallbackModels change the Agent’s saved error-handling configuration. The fallback chain stays on the Agent after the marathon ends and applies to the Agent’s other runs. For more information, see Error handling.

When the marathon ends, the CLI prints a summary with the final status, the session count, the total cost, and the path to the state file.

Where Marathon stores state

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 where Marathon saves large tool outputs to keep them out of the model context. Keep <task>/outputs/ with <task>.json and <task>.tree.jsonl when you move or back up a marathon task.

The task plan is in your project at .runtype/marathons/<task>/plan.md, not in the state directory.

Local tools

Marathon gives the Agent the following local tools:

  • read_file, list_directory, search_repo, glob_files, and tree_directory: Read and explore project files.
  • write_file and edit_file: Create and change files. Marathon saves a checkpoint of each file before the Agent changes it.
  • restore_file_checkpoint: Restore a file from its checkpoint.
  • read_offloaded_output: Read a large tool output that Marathon saved to <task>/outputs/.
  • run_check: Run a verification command, such as a test or lint command. For the commands that it accepts, see Verification commands.
  • search_session_history: Search output from earlier sessions.
  • run_sandbox_code and deploy_sandbox: Run code and deploy a preview in a sandbox. These tools are available only when you pass --sandbox. deploy_sandbox requires the runtype-sandbox or daytona provider.

The file tools can reach only paths inside the directory where you started Marathon. A symlink that points outside that directory is also blocked. They also block the .git and .runtype directories and sensitive files, such as .env files and private keys. The Agent can still write its plan file. A blocked path returns an Access denied error to the Agent.

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. In the planning phase, the Agent can write only to the plan file. 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

By default, Marathon pauses at a checkpoint after each session and at the end of the task. At a checkpoint after a session, Marathon continues automatically after 10 seconds if you don’t type anything. The checkpoint at the end of the task waits for your input. At a checkpoint, press Enter to continue, enter a message to steer the next session, or enter a slash command.

To change how long Marathon waits before it continues, set --checkpoint-timeout <seconds>. The runtype marathon --help output lists --no-checkpoint and --no-local-tools, but neither flag has an effect: checkpoint pauses and the local file tools stay on.

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. Enter /sandbox with no provider to turn off the sandbox.
  • /reflect: Write guidance that asks the Agent to reassess its approach before the next session.
  • /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 from the --sandbox flag.

The help panel and the command menu also list /plan, /status, and /revert. These commands have no effect at a checkpoint. Marathon sends the text to the Agent as a steering message. To read the plan, open .runtype/marathons/<task>/plan.md in your project.

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. It labels entries that are not messages, such as saved artifacts and compaction summaries, as metadata.

Resume a marathon

To resume a saved task with its full history, run the following command:

Resume a marathon
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.

The session and cost caps include the sessions and cost from earlier runs of the task. For example, a task that stopped at 50 sessions has no sessions left when you resume it with the default --max-sessions of 50. To continue it, pass a higher --max-sessions value. If the task stopped at the cost cap, pass a higher --max-cost value.

If you pass --resume without --name or --session, Marathon offers the most recent task. In a script or CI job, always pass --name with --resume, or pass --fresh.

When a marathon ends

The summary at the end of a run shows one of these statuses:

  • complete: The Agent finished the task.
  • max_sessions: The run reached the session cap. Resume with a higher --max-sessions value.
  • budget_exceeded: The run reached the cost cap. Resume with a higher --max-cost value.
  • stalled: Several sessions in a row ended without a tool action. Change the goal or the model, and then resume.
  • paused: The run stopped before it finished, for example because you stopped it. Resume it with --resume.
  • error: The run failed. The summary shows the last error.

Troubleshoot resume errors

The following errors are the most common reasons that a start or resume fails:

  • Local marathon state already exists at <path>. Use --resume to continue it or --fresh to start over.: Saved state exists for the task, and the terminal can’t show the resume prompt. Pass --resume or --fresh.
  • --resume without --name or --session is ambiguous in non-interactive mode.: Pass --name <task-name> with --resume.
  • Cannot use --resume and --fresh together.: Pass only one of the two flags.
  • No state files found to resume.: No saved task exists in the state directory. Run without --resume to start a new task, or check --state-dir.

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
name: release-notes
policy:
allowedWriteGlobs: ['docs/releases/**']
requirePlanBeforeWrite: true
milestones:
- name: research
model: claude-haiku-4-5
instructions: |
Review the repository changes.
Then write a short outline to the plan file: {{planPath}}
completionCriteria:
type: evidence
minReadFiles: 2
transitionSummary: 'Outline done. Moving to {{nextPhase}}.'
- name: write
model: claude-sonnet-5
fallbackModels: [claude-opus-5]
instructions: |
Write the release notes under docs/releases/ following the outline.
recovery:
afterEmptySessions: 1
message: Write a file under docs/releases/ now.
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}}, which resolves to the task’s plan file.
  • 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. Marathon saves the fallback chain to the Agent.
  • 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: The milestone never advances. The run ends only when the Agent sends TASK_COMPLETE and the milestone sets canAcceptCompletion: 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 can’t 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 the discovery tools search_repo, glob_files, tree_directory, and list_directory.
  • requirePlanBeforeWrite: Require the plan before non-plan writes.
  • requireVerification: Require a passing verification command before Marathon accepts TASK_COMPLETE. The Agent runs the command with the run_check tool.
  • outputRoot: For creation tasks, limit new files to a directory.

Verification commands

The run_check tool accepts one non-destructive check command at a time. It accepts commands that match these patterns:

  • pnpm, npm, yarn, or bun followed by test, lint, build, check, or typecheck, directly or through run.
  • pnpm exec, npm exec, yarn exec, or bun exec followed by tsc, vitest, eslint, prettier, or jest.
  • vitest, jest, pytest, eslint, ruff, mypy, or tsc.
  • playwright test, prettier --check, cargo test, cargo check, go test, deno test, deno check, or git diff --check.
  • make or just followed by test, lint, build, check, or typecheck.

The tool rejects pipes, &&, ;, redirects, and command substitution. It also rejects commands such as rm, curl, git push, and package installs. A script command, such as pnpm test, is also rejected when package.json has uncommitted changes or the project isn’t a Git repository. The same rule applies to make with its Makefile and to just with its justfile. Each command times out after 120 seconds by default, and the Agent can raise the timeout to 600 seconds.

A rejected command can’t satisfy requireVerification. If the Agent’s verification is blocked for two sessions in a row, Marathon waives the requirement for the rest of the run.

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
milestones:
- name: research
instructions: builtin:research-instructions
completionCriteria: { type: builtin:research-complete }
intercept: builtin:research-guard

The following table lists each behavior slot and the built-in hooks that it accepts:

SlotLevelBuilt-in hooks
instructionsMilestonebuiltin:research-instructions, builtin:planning-instructions, builtin:execution-instructions
toolGuidanceMilestonebuiltin:research-tool-guidance, builtin:planning-tool-guidance, builtin:execution-tool-guidance
completionCriteria.typeMilestonebuiltin:research-complete, builtin:planning-complete
canAcceptCompletionMilestonebuiltin:research-accept-completion, builtin:execution-accept-completion
transitionSummaryMilestonebuiltin:research-transition-summary, builtin:planning-transition-summary
recoveryMilestonebuiltin:research-recovery, builtin:planning-recovery, builtin:execution-recovery
interceptMilestonebuiltin:research-guard, builtin:planning-guard, builtin:execution-guard
forceEndTurnMilestonebuiltin:research-force-end-turn, builtin:planning-force-end-turn, builtin:execution-force-end-turn
classifyVariantPlaybookbuiltin:classify-task-variant
bootstrapPlaybookbuiltin:repo-bootstrap-discovery
candidateBlockPlaybookbuiltin:best-candidate-block

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.

Plugins run with your user privileges when the playbook loads, and verification commands run with the same privileges. Load only playbooks that you trust.

The following YAML loads a custom hook module:

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

The module exports a recovery hook:

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

Register hooks under a namespace that you choose, such as acme:. Reference the hook from a behavior slot, such as recovery: acme:strict-recovery.

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
import { definePlaybook, type RunTaskStateSlice } from '@runtypelabs/sdk'
export default definePlaybook({
name: 'my-task',
stallPolicy: { nudgeAfter: 1, stopAfter: 4 },
milestones: [
{
name: 'build',
instructions: (state: RunTaskStateSlice) => `Build it. Plan: ${state.planPath}`,
recovery: (state) =>
`You went ${state.consecutiveEmptySessions ?? 0} sessions without a tool call. Write a file now.`,
canAcceptCompletion: true,
},
],
})

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: