Skip to main content

Headless mode

Mastra Code can run non-interactively — both from your shell and programmatically from Node or CI. Both paths share one core runner, runMC. The CLI is a thin adapter over it.

  • CLI: mastracode --prompt "..." parses flags, bootstraps Mastra Code, runs the task, prints output, and exits with a status code.
  • Programmatic: you bootstrap Mastra Code once with createMastraCode(), then call runMC() to run a task as an async-iterable that also resolves to a typed result.

CLI usage​

Pass --prompt (or -p) to run a single task and exit:

mastracode --prompt "Fix the bug in auth.ts"

To open the interactive UI with a first message already sent, use --tui-prompt instead, or --tui-initial-prompt to send it only when there is no earlier conversation for the directory to resume.

Prompts can also be piped via stdin by passing - as the prompt:

echo "Summarize the repo" | mastracode --prompt -

Flags​

FlagDescription
--prompt, -p <text>The task to execute (required, or pipe via stdin)
--continue, -cResume the most recent thread instead of creating a new one
--thread, -t <id>Resume a specific thread by ID
--title <title>Set or rename the thread title
--clone-threadClone the current thread before running (work on a copy)
--resource-id <id>Set the resource ID for thread scoping
--timeout <seconds>Exit with code 2 if not complete within the timeout
--max-turns <n>Abort after N agentic turns (exit code 1)
--permission-mode <mode>How tool approvals/suspensions resolve: auto (default) approves everything; deny refuses approvals and aborts on suspension
--output, -o <mode>Output mode: human (default), json, or jsonl
--model, -m <id>Model override (e.g. a provider/model id)
--mode <build|plan|fast>Execution mode — defaults to build if omitted
--thinking-level <level>Thinking level: off, low, medium, high, xhigh
--settings <path>Path to a settings.json file (default: global settings)
--help, -hShow usage

--continue and --thread cannot be used together.

Output modes​

ModeBehavior
humanStreaming assistant text to stdout; tool activity and errors to stderr
jsonA single final JSON object (text, usage, tool calls/results, threadId, status)
jsonlNewline-delimited JSON: one line per event, then a final { "type": "result", ... } line

Exit codes​

CodeMeaning
0Agent completed successfully
1Error, aborted, or max turns reached
2Timeout

Examples​

# JSON output with a timeout
mastracode --prompt "Add tests" --timeout 300 --output json

# Event stream
mastracode --prompt "Refactor" --output jsonl

# Gate a CI run: no unattended tool execution, bounded turns
mastracode --prompt "Review this PR" --permission-mode deny --max-turns 10

# Use a CI-specific settings file
mastracode --settings ./settings-ci.json --prompt "Run tests"

# Resume the most recent thread
mastracode -c --prompt "Continue where you left off"

Programmatic usage​

Bootstrap Mastra Code with createMastraCode(), then pass the returned controller and session to runMC(). createMastraCode() is async; runMC() is synchronous and returns a run handle immediately.

TypeScriptsrc/run.ts
import { createMastraCode, runMC } from 'mastracode'

const { controller, session } = await createMastraCode()

const run = runMC({
  controller,
  session,
  prompt: 'Fix the failing test in auth.test.ts',
})

// Optional: stream live events as they happen.
for await (const event of run) {
  console.log(event.type)
}

// Resolves once the run completes, times out, errors, or is aborted.
const result = await run.result
console.log(result.status, result.text)

runMC never calls process.exit and never writes to global streams, so it is safe to embed in CI scripts and servers.

The run handle​

runMC returns an MCRun, which is both an async-iterable over controller events and a handle that resolves to a final result:

const run = runMC({ controller, session, prompt })

run.result // Promise<RunMCResult>
run.abort() // abort the in-flight run
for await (const event of run) {
  /* ... */
}

Both for await (const event of run) and await run.result work on the same run. Awaiting result without iterating still drains events internally.

runMC options​

OptionTypeDescription
controllerAgentControllerController from createMastraCode() (required)
sessionSessionSession from createMastraCode() (required)
promptstringThe task to run (required)
modelstringExplicit model id override; takes precedence over mode
mode'build' | 'plan' | 'fast'Execution mode; resolves a model from modeDefaults when model is absent
modeDefaultsRecord<string, string>Per-mode default model ids
thinkingLevel'off' | 'low' | 'medium' | 'high' | 'xhigh'Thinking-effort level
thread{ id?: string; continueLatest?: boolean; clone?: boolean }Thread selection / mutation
resourceIdstringResource id for thread scoping
titlestringSet or rename the thread title before running
timeoutMsnumberAbort with status: 'timeout' (exit code 2) if not complete in time
maxTurnsnumberAbort with status: 'max_turns' (exit code 1) after N assistant turns
policyResolutionPolicyHow approvals/suspensions resolve (defaults to autoApprovePolicy)
signalAbortSignalExternal abort signal; aborting it aborts the run

RunMCResult​

FieldTypeDescription
status'completed' | 'error' | 'aborted' | 'timeout' | 'max_turns'How the run ended
textstringAggregated assistant text across the run
finishReasonstringUnderlying finish reason when the run finished normally
usage{ inputTokens?; outputTokens?; totalTokens? }Token usage
toolCallsArray<{ id; name; args }>Tool calls made during the run
toolResultsArray<{ id; name; result; isError }>Tool results
threadIdstringThe thread the run executed in
error{ name; message; stack? }Present when status is error
exitCodenumber0 success, 1 error/aborted/max_turns, 2 timeout

Resolution policies​

A resolution policy decides how the run resumes tool_approval_required and tool_suspended events when there is no human in the loop. The default is autoApprovePolicy, which reproduces the historical headless behavior: approve every tool, auto-resolve sandbox and submit_plan suspensions, and answer any other suspension with a "use your best judgment" instruction.

Two built-in policies and a resolver are exported:

ExportBehavior
autoApprovePolicyApprove every tool; auto-resolve suspensions (default)
denyPolicyRefuse every tool approval; abort on any suspension
permissionModeToPolicy(mode)Resolve a PermissionMode ('auto' or 'deny') to a built-in policy

The CLI --permission-mode flag maps directly onto these via permissionModeToPolicy.

Custom policies​

Implement the ResolutionPolicy interface to make per-event decisions:

TypeScriptsrc/strict-policy.ts
import { createMastraCode, runMC, type ResolutionPolicy } from 'mastracode'

const readOnlyPolicy: ResolutionPolicy = {
  // Only approve read_file; deny everything else.
  onToolApproval(event) {
    return event.toolName === 'read_file' ? 'approve' : 'deny'
  },
  // Abort rather than answer suspensions unattended.
  onSuspension() {
    return { abort: true }
  },
}

const { controller, session } = await createMastraCode()

const run = runMC({
  controller,
  session,
  prompt: 'Audit the codebase for secrets',
  policy: readOnlyPolicy,
})

const result = await run.result

onToolApproval returns 'approve' or 'deny'. onSuspension returns either { resumeData } to resume the suspended tool or { abort: true } to abort the run.

Next steps​

  • Customization — extend Mastra Code with custom modes, tools, and subagents.
  • API reference — full createMastraCode() options and return values.