Skip to main content
@spfunctions/agent is the SimpleFunctions Agent SDK. It gives a TypeScript runtime a Cursor-style run lifecycle plus SimpleFunctions market/world context. The package has two layers: the v0 direct runner for deterministic canonical tool calls, trace, and replay, and the v1 model loop for Agent.create().send().stream() runs. It does not shell out to the CLI.
npm install @spfunctions/sdk@1.0.1 @spfunctions/agent@1.0.2
Use the stable 1.0.1 SDK and 1.0.2 Agent SDK packages in server-side TypeScript apps.

Primary Surface

Start here:
import { Agent } from "@spfunctions/agent/v1"

const agent = await Agent.create({
  apiKey: process.env.SF_API_KEY,
  openRouterApiKey: process.env.OPENROUTER_API_KEY,
  model: { id: "anthropic/claude-haiku-4.5" },
})

const run = agent.send("Read world state and summarize the largest market moves.")

for await (const event of run.stream()) {
  console.log(event.type)
}

const sameRun = await Agent.getRun(run.id, { agentId: run.agentId })
await sameRun?.wait()
The run handle supports:
  • run.stream()
  • run.wait()
  • run.cancel()
  • run.status
  • Agent.getRun(run.id, { agentId })
  • agent.send(prompt, { onDelta, onStep })
Agent.create({ apiKey }) mounts read-only SimpleFunctions strict tools by default. It does not mount user writes such as watchlists.add, alerts.create, or thesis writes unless you explicitly pass builtinTools: "all" or a named allowlist.

Why This Is Not A Generic Coding SDK

Claude Agent SDK, Codex SDK, and Cursor SDK give agents file, shell, editor, repo, or conversation control. SimpleFunctions gives agents a governed view of prediction markets:
  • strict canonical tools from /api/contracts/tools
  • world state, market search, market inspection, OHLCV candles, screeners, regime scans, event calendars, index snapshots, contagion scans, cross-venue pairs, yield curves, calibration summaries, and econ/gov/query reads
  • sideEffect and costEffect metadata
  • canUseTool() input shrinking and denial
  • hooks, sessions, trace, replay
  • semi-realtime watch.ticks() and watch.world() inputs; watch.ticks() reads live SimpleFunctions market inspection prices when you pass an API key or client, and otherwise marks deterministic development ticks as synthetic: true
Use CLI or MCP when Claude Code, Codex, Cursor, or another host owns the runtime. Use @spfunctions/agent when your TypeScript app owns the loop.

Market Watch Agent

import { Agent, OpenRouterProvider } from "@spfunctions/agent/v1"

const agent = await Agent.create({
  apiKey: process.env.SF_API_KEY,
  provider: new OpenRouterProvider({ apiKey: process.env.OPENROUTER_API_KEY }),
  model: { id: "anthropic/claude-haiku-4.5" },
  builtinTools: [
    "world.read",
    "markets.screen",
    "markets.search",
    "market.inspect",
    "market.candles",
    "regime.scan",
    "crossvenue.pairs",
  ],
  options: {
    watch: [
      { kind: "ticks", tickers: ["KXEXAMPLE"], cadence: "5min" },
    ],
    maxTurns: 4,
    maxBudgetUsd: 0.50,
    canUseTool(toolName, input) {
      if (toolName === "markets.search" && input && typeof input === "object") {
        return { behavior: "allow", updatedInput: { ...input, limit: 5 } }
      }
      return { behavior: "allow" }
    },
  },
})

const run = agent.send([
  "Watch Iran oil risk.",
  "If market ticks move sharply, inspect the ticker and explain what changed.",
  "Do not use write or trading tools.",
].join(" "))

for await (const event of run.stream()) {
  console.log(event.type)
}

Query Surface

Anthropic-style consumers can use query() directly:
import { OpenRouterProvider, query, tool } from "@spfunctions/agent/v1"

const inspect = tool("market.inspect", "Inspect one market", { type: "object" }, async input => input)

for await (const message of query({
  prompt: "Explain what prediction markets imply about Fed cuts.",
  options: {
    provider: new OpenRouterProvider({ apiKey: process.env.OPENROUTER_API_KEY }),
    model: "anthropic/claude-haiku-4.5",
    tools: [inspect],
    maxTurns: 3,
    maxBudgetUsd: 0.25,
  },
})) {
  console.log(message.type)
}
query() yields ordered SDKMessage events and supports interrupt(), setModel(), setPermissionMode(), setMcpServers(), streamInput(), close(), and initializationResult().

Identity, Execution, And Replay

Live Agent execution requires an API-keyed SDK client. The no-key path is for strict manifest inspection and replay-only harnesses, not live market execution. The v0 direct runner remains available for deterministic tool calls, file traces, and replay:
import { SimpleFunctions } from "@spfunctions/sdk"
import { FileTraceStore, ReplayMissError, SimpleFunctionsAgent } from "@spfunctions/agent"

const sf = new SimpleFunctions({
  baseUrl: "https://simplefunctions.dev",
  apiKey: process.env.SF_API_KEY,
})

const trace = new FileTraceStore("./world-read.trace.jsonl")
const direct = new SimpleFunctionsAgent({
  client: sf,
  policy: { maxSideEffect: "none", maxCostEffect: "api_cost" },
  trace,
})

await direct.tools.world.read({})

const replay = new SimpleFunctionsAgent({
  client: new SimpleFunctions({ baseUrl: "https://simplefunctions.dev" }),
  mode: "replayOnly",
  trace: new FileTraceStore("./world-read.trace.jsonl"),
})

try {
  await replay.tools.world.delta({ since: "1h" })
} catch (error) {
  if (error instanceof ReplayMissError) {
    console.log("No live request was made.")
  }
}
Execution tools are available only when the policy explicitly permits live-trade side effects, venue-request cost, and trade guardrails:
const executionAgent = new SimpleFunctionsAgent({
  client: sf,
  policy: {
    maxSideEffect: "live_trade",
    maxCostEffect: "venue_request_cost",
    trade: {
      allowedVenues: ["kalshi"],
      allowedTickers: ["KXFED-27APR-T3.50"],
      maxQuantity: 2,
      maxOrderCostCents: 100,
      requireLimitPrice: true,
      allowRuntimeStart: true,
      confirmToken: "operator-approved",
    },
  },
})

await executionAgent.tools.execution.place({
  ticker: "KXFED-27APR-T3.50",
  action: "buy",
  quantity: 1,
  limitPrice: 32,
  confirm: "operator-approved",
})
Polymarket uses the same governed tool with venue: "polymarket" and a CLOB token id. Add jurisdiction guardrails when the application needs venue-specific compliance controls:
const polyExecutionAgent = new SimpleFunctionsAgent({
  client: sf,
  policy: {
    maxSideEffect: "live_trade",
    maxCostEffect: "venue_request_cost",
    trade: {
      allowedVenues: ["polymarket"],
      blockedJurisdictions: ["US", "FR"],
      requireJurisdiction: true,
      maxQuantity: 2,
      maxOrderCostCents: 100,
      requireLimitPrice: true,
    },
  },
})

await polyExecutionAgent.tools.execution.place({
  venue: "polymarket",
  tokenId: "POLYMARKET_CLOB_TOKEN_ID",
  action: "buy",
  quantity: 1,
  limitPrice: 32,
  jurisdiction: "CA",
})
execution.place checks runtime candidates and starts or wakes a usable runtime before creating the intent. Set trade.allowRuntimeStart: false when runtime startup must be controlled outside the agent. Replay misses never fall through to live execution. Trace redaction removes API keys, authorization headers, tokens, secrets, passwords, signing secrets, and webhook secrets.

Boundaries

  • Live trading is available only through explicit strict execution tools and opt-in policy guardrails.
  • No browser long-lived API key examples.
  • The Agent SDK does not shell out to the CLI; runtime orchestration goes through SDK runtime resources.
  • /api/contracts/tools is the strict SDK/Agent truth.
  • /api/tools is broad compatibility inventory, not canonical Agent SDK truth.
  • get_world_state and get_regime_history are not canonical SDK/Agent tool names.