sf-agent-sdk-headless — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited sf-agent-sdk-headless (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
npm install -g @spfunctions/cli| Mode | Who reasons? | Output |
|---|---|---|
sf agent --plain --once "..." | SimpleFunctions agent (its own LLM) | Plain text + tool progress on stderr |
sf agent --headless | Your external agent | NDJSON frames on stdin/stdout |
Use --plain when you want SF to reason for you. Use --headless when you want SF as a tool server.
sf agent --headless --deny trade,runtime,fsThe first stdout line is a ready frame with the policy + tools manifest:
{
"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": { ... }
},
{ ... }
]
}Use this manifest to discover what tools are available before sending calls.
{"type":"call","id":"calc-1","tool":"calculate","params":{"expression":"(0.57 - 0.52) * 100"}}
{"type":"done"}Each call has an id (your tracking id), a tool name (from the ready manifest), and params.
{"type":"result","id":"calc-1","tool":"calculate","output":"(0.57 - 0.52) * 100 = 4.999999999999993","ms":0}The result's id matches your call's id. Use this to correlate async responses.
| Frame | Direction | Purpose |
|---|---|---|
ready | sf → host | Manifest loaded. Contains policy + callable tools. |
call | host → sf | Invoke { id, tool, params }. |
result | sf → host | Tool output for the call id. |
error | sf → host | Tool error for the call id. |
wake | sf → host | A scheduled wake fired and the host should process it. |
done | host → sf | Close the headless process. |
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)
}
if (msg.type === "error") {
console.error(msg.error)
}
})
sf.on("exit", (code) => {
console.log(`sf headless exited with code ${code}`)
})Keep stderr separate from stdout. Operational warnings and tool progress can appear on stderr; stdout is the protocol stream you parse.
For Claude Code as the host:
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 jsonClaude Code's -p mode supports structured output with --output-format json and streaming with --output-format stream-json. Use it as the outer automation layer; use sf agent --headless NDJSON for the SimpleFunctions tool layer.
const calls = [
{ id: "c1", tool: "get_world_state", params: {} },
{ id: "c2", tool: "inspect_ticker", params: { ticker: "KXFED-27APR-T3.50" } },
{ id: "c3", tool: "search_markets", params: { query: "CPI", limit: 5 } },
]
let remainingResults = calls.length
rl.on("line", (line) => {
const msg = JSON.parse(line)
if (msg.type === "ready") {
for (const call of calls) {
sf.stdin.write(JSON.stringify({ type: "call", ...call }) + "\n")
}
}
if (msg.type === "result" || msg.type === "error") {
console.log(msg)
remainingResults--
if (remainingResults === 0) {
sf.stdin.write(JSON.stringify({ type: "done" }) + "\n")
}
}
})The server processes calls in receipt order. You can pipeline multiple before any results come back.
For long-running monitors, the server can emit wake frames when a scheduled event fires (e.g., world delta cadence hit). Handle them like results:
rl.on("line", (line) => {
const msg = JSON.parse(line)
if (msg.type === "wake") {
// A scheduled event fired. Decide what tool to call in response.
sf.stdin.write(JSON.stringify({
type: "call",
id: `wake-${msg.id}`,
tool: "get_world_delta",
params: { since: "5min" },
}) + "\n")
}
})This pattern: the host owns the LLM, SF owns the tool layer + scheduling.
sf agent --headless --deny trade,runtime,fsThe --deny (and corresponding --allow) flags enforce policy categories:
read — public market data, indicators, regimeuser_data — your portfolio, theses, intentsmarket_data — historical price + indicator dataresearch — investigate/query/inspect routerswrite — create thesis, signal, watchlist, alerttrade — buy/sell/cancel ordersruntime — start/stop local daemonsfs — file system operationsThe policy is enforced before tool execution. A blocked tool returns an error frame, not a result.
For unattended automation: --allow read,user_data,research --deny trade,runtime,fs. Never enable trade without a human approval step.
--deny policy. Either change policy or use a different tool.sf-agent-sdk-quickstart — Agent.create().send() when SF should own the loopsf-claude-code-setup — Claude Code with sf agent --plain (simpler than headless)sf-sdk-quickstart — TypeScript SDK for direct calls~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.