Skip to content
Letta Code Letta Code Letta Docs
Sign up
Get started

Headless mode

Run Letta Code non-interactively for scripting and automation

Headless mode allows you to run Letta Code non-interactively, making it easy to integrate into scripts, CI/CD pipelines, or compose with other UNIX tools.

Use the -p flag to pass a prompt directly:

Run a one-off prompt
letta -p "Look around this repo and write a README.md documenting it"

You can also pipe input to Letta Code:

Pipe input
echo "Explain this error" | letta -p

Letta Code supports three output formats in headless mode:

Returns the agent’s response as plain text:

Terminal window
letta -p "What files are in this directory?"

Returns a structured JSON response with metadata:

Terminal window
letta -p "List all TypeScript files" --output-format json
Example output
{
"type": "result",
"result": "Found 15 TypeScript files...",
"agent_id": "agent-abc123",
"conversation_id": "conversation-xyz789",
"usage": {
"prompt_tokens": 1250,
"completion_tokens": 89
}
}

Returns line-delimited JSON events for real-time streaming. This is useful for preventing timeouts and getting incremental progress:

Terminal window
letta -p "Explain this codebase" --output-format stream-json

Each line is a JSON event:

Example stream output
{"type":"system","subtype":"init","agent_id":"agent-...","conversation_id":"conversation-...","session_id":"agent-...","model":"claude-sonnet-4-5","tools":[...]}
{"type":"message","message_type":"reasoning_message","reasoning":"The user is asking...","otid":"...","seq_id":1}
{"type":"message","message_type":"assistant_message","content":"Here's an overview...","otid":"...","seq_id":5}
{"type":"message","message_type":"stop_reason","stop_reason":"end_turn"}
{"type":"message","message_type":"usage_statistics","prompt_tokens":294,"completion_tokens":97}
{"type":"result","subtype":"success","result":"Here's an overview...","agent_id":"...","conversation_id":"...","session_id":"...","uuid":"..."}

Messages are streamed at the token level - each chunk has the same otid (output turn ID) and incrementing seq_id.

For programmatic control, use --input-format stream-json to enable bidirectional JSON communication over stdin/stdout. This allows external programs to send messages and receive responses in a structured format.

Start bidirectional mode
letta -p --input-format stream-json --output-format stream-json

Send JSON messages to stdin (one per line):

User message
{"type": "user", "message": {"role": "user", "content": "What files are here?"}}
Initialize control request
{"type": "control_request", "request_id": "init_1", "request": {"subtype": "initialize"}}
Interrupt control request
{"type": "control_request", "request_id": "int_1", "request": {"subtype": "interrupt"}}

The CLI emits JSON messages to stdout:

Init event (emitted at session start)
{"type": "system", "subtype": "init", "agent_id": "agent-xxx", "conversation_id": "conversation-xxx", "session_id": "agent-xxx", "model": "...", "tools": [...]}
Control response
{"type": "control_response", "response": {"subtype": "success", "request_id": "init_1", "response": {...}}}
Streaming messages
{"type": "message", "message_type": "assistant_message", "content": "Hello!", "session_id": "...", "uuid": "..."}
Result (emitted after each turn)
{"type": "result", "subtype": "success", "result": "Hello!", "session_id": "...", "agent_id": "...", "conversation_id": "..."}

The process stays alive until stdin closes, allowing multi-turn conversations:

Multi-turn example
(
echo '{"type": "user", "message": {"role": "user", "content": "Remember: secret is BANANA"}}'
sleep 5
echo '{"type": "user", "message": {"role": "user", "content": "What was the secret?"}}'
) | letta -p --input-format stream-json --output-format stream-json

Add --include-partial-messages to receive token-level streaming events:

Terminal window
letta -p --input-format stream-json --output-format stream-json --include-partial-messages

This wraps each chunk in a stream_event:

{"type": "stream_event", "event": {"message_type": "assistant_message", "content": "Hel"}, "session_id": "...", "uuid": "..."}

By default, headless mode uses the last agent from the current directory and its “default” conversation. Your agent retains memory across all runs, and the default conversation preserves message history between sessions.

To create a new conversation for parallel sessions, use --new:

Create a new conversation
letta -p "..." --new
Create a new agent
letta -p "..." --new-agent
Use a specific agent
letta -p "..." --agent <agent-id>
Resume last session (agent + conversation)
letta -p "..." --continue
Resume a specific conversation
letta -p "..." --conversation <conversation-id>

The JSON and stream-json output formats include a conversation_id field, which you can use to continue the same conversation in subsequent calls:

Get conversation ID from output
result=$(letta -p "Start a new task" --output-format json)
conv_id=$(echo $result | jq -r '.conversation_id')
# Continue the same conversation
letta -p "Continue where we left off" --conversation $conv_id --output-format json

Specify a model for the headless run:

Terminal window
letta -p "..." --model sonnet-4.5
letta -p "..." -m gpt-5-codex
letta -p "..." -m haiku

See Models for the full list of supported model IDs.

Use --yolo to bypass all permission prompts (use with caution):

Terminal window
letta -p "Refactor this file" --yolo

The --tools flag controls which tools are attached to the agent (removing them from the context window entirely):

Only load specific tools
letta -p "Analyze this codebase" --tools "Read,Glob,Grep"
No tools (conversation only)
letta -p "What do you think about this approach?" --tools ""

This is different from --allowedTools/--disallowedTools which control permissions but keep tools in context. See Permissions for more details.

Auto-allow edits only
letta -p "Fix the type errors" --permission-mode acceptEdits
Read-only mode
letta -p "Review this PR" --permission-mode plan

Use -n or --name to resume an agent by name (case-insensitive). Matches pinned agents or recent agents:

Terminal window
letta -p "Continue where we left off" --name myproject

Customize the agent’s system prompt when creating new agents:

Use a preset
letta -p "..." --new-agent --system letta-claude
Use a custom prompt
letta -p "..." --new-agent --system-custom "You are a Python expert who writes clean code."
Extend a preset with additional instructions
letta -p "..." --new-agent --system letta-claude --system-append "Always respond in Spanish."

Available presets:

  • default / letta-claude - Full Letta Code prompt (Claude-optimized)
  • letta-codex - Full Letta Code prompt (Codex-optimized)
  • letta-gemini - Full Letta Code prompt (Gemini-optimized)
  • claude - Basic Claude (no skills/memory instructions)
  • codex - Basic Codex
  • gemini - Basic Gemini

Customize which memory blocks the agent uses:

Specify which preset blocks to include
letta -p "..." --new-agent --init-blocks "persona,project"
Set values for preset blocks
letta -p "..." --new-agent --init-blocks "persona,project" \
--block-value persona="You are a Go expert" \
--block-value project="CLI tool for Docker"
Use completely custom memory blocks (JSON)
letta -p "..." --new-agent --memory-blocks '[
{"label": "context", "value": "API documentation for Acme Corp..."},
{"label": "rules", "value": "Always use TypeScript"}
]'
No optional blocks (only core skills blocks)
letta -p "..." --new-agent --init-blocks ""

Available preset blocks:

  • persona - Agent’s personality and behavior
  • human - Information about the user
  • project - Current project context

Core blocks (always included):

  • skills - Available skills directory
  • loaded_skills - Currently loaded skill instructions

Force a specific toolset instead of auto-detection based on model:

Terminal window
letta -p "..." --toolset codex # Codex-style tools
letta -p "..." --toolset gemini # Gemini-style tools
letta -p "..." --toolset default # Default Letta tools

When creating a new agent with --new-agent, specify which base tools to attach:

Specify which base tools to attach
letta -p "..." --new-agent --base-tools "memory,web_search"

Create an agent from an AgentFile template:

Terminal window
letta -p "..." --from-af ./my-agent.af

Customize the agent’s system prompt:

Replace system prompt entirely
letta -p "..." --system-custom "You are a helpful assistant that only responds in haiku."
Append to default system prompt
letta -p "..." --system-append "Always respond in JSON format."

Configure memory blocks when creating agents:

Set memory blocks via JSON
letta -p "..." --new-agent --memory-blocks '{"persona": "You are a code reviewer", "project": "React app"}'
Set individual block values
letta -p "..." --block-value "persona=You are a security auditor" --block-value "project=Backend API"
Run lint and fix errors
letta -p "Run the linter and fix any errors" --yolo

Use JSON output to parse results programmatically:

Terminal window
result=$(letta -p "What is the main entry point of this project?" --output-format json)
echo $result | jq '.result'

Use --tools to restrict the agent to read-only operations:

Terminal window
letta -p "Review this codebase for potential security issues" --tools "Read,Glob,Grep"

Run Letta Code on a schedule using cron:

Daily code review at 9am
0 9 * * * cd /path/to/project && letta -p "Review recent changes and summarize any issues" --tools "Read,Glob,Grep" --output-format json >> /var/log/letta-review.log 2>&1
Weekly dependency check
0 10 * * 1 cd /path/to/project && letta -p "Check for outdated dependencies and security vulnerabilities" --yolo >> /var/log/letta-deps.log 2>&1