> ## Documentation Index
> Fetch the complete documentation index at: https://docs.simplefunctions.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Headless agent

> Run the SimpleFunctions agent without a TUI, then let Claude Code or another coding agent drive it through CLI commands or NDJSON tool calls.

Use the headless agent workflow when an external process needs SimpleFunctions market intelligence without opening the interactive terminal UI. This is part of the primary CLI surface: prefer CLI automation first, HTTP/Data APIs for remote services second, and MCP only as a compatibility adapter.

There are two modes:

| Mode                 | Command                         | Who reasons?          | Output                                  | Use when                                                                            |
| -------------------- | ------------------------------- | --------------------- | --------------------------------------- | ----------------------------------------------------------------------------------- |
| Plain one-shot       | `sf agent --plain --once "..."` | SimpleFunctions agent | Plain text plus tool progress on stderr | You want one local agent run from cron, Claude Code, CI, or a script.               |
| Headless tool server | `sf agent --headless`           | Your external agent   | NDJSON frames on stdin/stdout           | Claude Code, Codex, or a custom harness wants to call SimpleFunctions tools itself. |

If someone says `sf --plain`, the copy-paste command you want is usually `sf agent --plain`. The `--plain` flag belongs to `sf agent`.

## What it can do

The agent has the same market tools as the interactive `sf agent` session:

| Task                  | Useful commands or tools                                                                                    |
| --------------------- | ----------------------------------------------------------------------------------------------------------- |
| Boot a market agent   | `sf status --json`, `sf doctor --agent --deep --json`, `sf me --json`, `sf brief --agent --json`            |
| Discover capabilities | `sf describe --all --json`, `sf tools search "<task>" --json`, `sf tools plan "<task>" --json`              |
| Research a market     | `sf investigate "<topic>" --json`, `sf query "<question>" --json`, `sf inspect <ticker> --json`             |
| Work a thesis         | `sf thesis context <id> --json`, `sf thesis signal <id> "..."`, `sf thesis evaluate <id>`                   |
| Find opportunities    | `sf discover --quality --json`, `sf screen --json`, `sf edges --json`, `sf cross-venue --json --preset arb` |
| Monitor state         | `sf world --delta --json --since 1h`, `sf feed --json --hours 6`, `sf portfolio history --json`             |
| Build audit trails    | `sf agent --record-trace <path>`, `sf agent --replay-trace <path>`, `sf trace receipt <path>`               |

Use direct `--json` commands for deterministic parsing. Use `sf agent --plain --once` when you want the SimpleFunctions agent to reason across those commands and return an analyst-style answer.

## First run

Install and verify the local control plane:

```bash theme={null}
npm install -g @spfunctions/cli
sf login
sf status --json
sf doctor --agent --deep --json
sf describe agent --json
```

For automation, start with read-only policy gates:

```bash theme={null}
sf agent --plain \
  --new \
  --allow read,user_data,research \
  --deny trade,runtime,fs \
  --budget-usd 0.25 \
  --once "Summarize active thesis risk, new signals, and the next read-only checks."
```

`--once` runs one turn and exits. `--plain` removes the TUI. `--new` avoids inheriting a stale local session. The `--allow` and `--deny` lists are enforced before tool execution.

## Plain mode from cron

This pattern is useful for hourly thesis review or morning portfolio briefs:

```cron theme={null}
0 * * * * /usr/bin/env bash -lc 'sf agent --plain --new --allow read,user_data,research --deny trade,runtime,fs --budget-usd 0.25 --record-trace "$HOME/.sf/traces/thesis-hourly-$(date +\%Y-\%m-\%d-\%H).ndjson" --once "Review my active theses. Use current market state, list material changes, and propose read-only follow-ups. Do not trade or start daemons." >> "$HOME/.sf/logs/thesis-hourly.log" 2>&1'
```

Use `--record-trace` for audit and regression replay:

```bash theme={null}
sf agent --plain \
  --record-trace traces/fed-cut.ndjson \
  --once "Inspect Fed cut markets and produce a risk brief."

sf agent --plain \
  --replay-trace traces/fed-cut.ndjson \
  --once "Inspect Fed cut markets and produce a risk brief."
```

## Headless NDJSON server

Use `sf agent --headless` when your own LLM loop should decide which SimpleFunctions tool to call while still staying on the CLI-first path. In this mode SimpleFunctions does not run its own LLM loop; it exposes tools over newline-delimited JSON.

Start the server:

```bash theme={null}
sf agent --headless --deny trade,runtime,fs
```

The first stdout line is a `ready` frame:

```json theme={null}
{
  "type": "ready",
  "policy": {
    "deny": ["trade", "runtime", "fs"],
    "categories": ["read", "user_data", "market_data", "research", "write", "trade", "runtime", "fs"]
  },
  "tools": [
    {
      "name": "inspect_ticker",
      "description": "Complete ticker dossier with actionable suggestion.",
      "parameters": {}
    }
  ]
}
```

Send tool calls on stdin:

```json theme={null}
{"type":"call","id":"calc-1","tool":"calculate","params":{"expression":"(0.57 - 0.52) * 100"}}
{"type":"done"}
```

Read result frames from stdout:

```json theme={null}
{"type":"result","id":"calc-1","tool":"calculate","output":"(0.57 - 0.52) * 100 = 4.999999999999993","ms":0}
```

Protocol frames:

| Frame    | Direction    | Purpose                                                |
| -------- | ------------ | ------------------------------------------------------ |
| `ready`  | `sf` to host | Manifest loaded. Contains policy and callable tools.   |
| `call`   | host to `sf` | Invoke `{ id, tool, params }`.                         |
| `result` | `sf` to host | Tool output for the call id.                           |
| `error`  | `sf` to host | Tool error for the call id.                            |
| `wake`   | `sf` to host | A scheduled wake fired and the host should process it. |
| `done`   | host to `sf` | Close the headless process.                            |

## Minimal Node harness

```ts theme={null}
import { spawn } from "node:child_process";
import readline from "node:readline";

const sf = spawn("sf", ["agent", "--headless", "--deny", "trade,runtime,fs"], {
  stdio: ["pipe", "pipe", "inherit"],
});

const rl = readline.createInterface({ input: sf.stdout });

rl.on("line", (line) => {
  const msg = JSON.parse(line);

  if (msg.type === "ready") {
    sf.stdin.write(JSON.stringify({
      type: "call",
      id: "world-1",
      tool: "get_world_delta",
      params: { since: "1h" },
    }) + "\n");
    sf.stdin.write(JSON.stringify({ type: "done" }) + "\n");
  }

  if (msg.type === "result") {
    console.log(msg.output);
  }
});
```

Keep stderr separate from stdout. Operational warnings and tool progress can appear on stderr; stdout is the protocol stream you parse.

## Claude Code with `sf agent --plain`

Claude Code can run non-interactively with `claude -p`, the current programmatic CLI mode documented by Anthropic. That makes it a good host for SimpleFunctions CLI calls: Claude Code handles repository context and shell orchestration, while SimpleFunctions handles prediction-market tools and thesis state.

Use a narrow Bash-only prompt:

```bash theme={null}
claude -p '
You are operating SimpleFunctions through shell commands.

Rules:
- Run only commands that begin with: sf status, sf doctor, sf describe, sf tools, sf me, sf brief, sf investigate, sf query, sf inspect, sf world, sf agent.
- Do not run sf buy, sf sell, sf cancel, sf intent, sf runtime, sf quoteengine, or any command that starts a daemon.
- Prefer --json for direct commands.
- For a reasoning pass, use:
  sf agent --plain --new --allow read,user_data,research --deny trade,runtime,fs --budget-usd 0.25 --once "<your prompt>"

Task:
Research whether the Fed cut thesis has materially changed. Return the commands you ran, the evidence, and the next read-only checks.
' \
  --tools "Bash" \
  --allowedTools "Bash(sf status:*),Bash(sf doctor:*),Bash(sf describe:*),Bash(sf tools:*),Bash(sf me:*),Bash(sf brief:*),Bash(sf investigate:*),Bash(sf query:*),Bash(sf inspect:*),Bash(sf world:*),Bash(sf agent:*)" \
  --output-format json
```

This is the safest default because Claude Code is restricted to Bash, the prompt only allows read-oriented `sf` command families, and SimpleFunctions still enforces its own `--allow` / `--deny` policy before any tool execution.

## Claude Code with the NDJSON server

For a tighter integration, have Claude Code write or run a small harness that starts `sf agent --headless`, reads the `ready` manifest, and sends only approved `call` frames.

Example prompt:

```bash theme={null}
claude -p '
Build a small Node script that starts:
  sf agent --headless --deny trade,runtime,fs

The script must:
1. Parse the ready frame.
2. Call get_world_delta with since=1h.
3. Call inspect_ticker only if the user provided a ticker.
4. Print a compact JSON report.
5. Send {"type":"done"} before exit.

Do not call trade, runtime, fs, wake, alert, or voice tools.
' \
  --tools "Read,Write,Bash" \
  --allowedTools "Bash(sf agent:*),Bash(node:*),Read,Write" \
  --output-format json
```

Claude Code's `-p` mode supports structured output with `--output-format json` and streaming with `--output-format stream-json`. Use that for the outer automation layer; use `sf agent --headless` NDJSON for the SimpleFunctions tool layer.

## Safety model

Default automation policy:

```bash theme={null}
--allow read,user_data,research --deny trade,runtime,fs
```

Use that for research, monitoring, and reporting. Add `write` only when the automation is allowed to mutate SimpleFunctions state, such as injecting a thesis signal or creating a watchlist item. Do not add `trade` or `runtime` to unattended Claude Code, cron, or CI jobs.

For execution workflows:

1. Read first: `sf me portfolio --json`, `sf intent list --json`, `sf runtime status --json`.
2. Propose the action in text.
3. Ask a human to approve.
4. Prefer `sf intent buy` / `sf intent sell` over direct `sf buy` / `sf sell`.
5. Record the run with `--record-trace`.

## Related docs

<CardGroup cols={2}>
  <Card title="Agentic CLI" href="/cli/agentic-cli">
    Full CLI control plane, permission categories, trace and replay.
  </Card>

  <Card title="Thesis lifecycle" href="/build/thesis-lifecycle">
    Create, signal, evaluate, augment, heartbeat, and publish a thesis.
  </Card>

  <Card title="Agent runtime" href="/guides/agent-runtime">
    Long-running execution daemon, intents, and cloud runtime.
  </Card>

  <Card title="Claude Code programmatic mode" href="https://code.claude.com/docs/en/headless">
    Anthropic reference for `claude -p`, JSON output, and scripted runs.
  </Card>
</CardGroup>
