live_trade is no longer a hard-forbidden contract class when SDK or Agent policy explicitly opts in.
This RFC defines the identity and governance rules that must be settled before SimpleFunctions implements @spfunctions/agent.
It does not implement a package, endpoint, SDK method, Agent tool, or publish flow.
First principles
SimpleFunctions is not only a REST API, CLI, tool catalog, MCP adapter, or TypeScript SDK. The product direction is governed execution infrastructure for prediction-market, research, and trading-adjacent tools. Every serious tool call needs five answers:- who is calling
- what exact canonical tool is being called
- whether that tool is allowed
- whether it costs money, consumes quota, mutates state, exposes secrets, starts runtime, or crosses execution boundaries
- whether the call can be replayed and audited
| Primitive | Role |
|---|---|
| Identity | API key, account, and future scoped session token |
| Contract | GET /api/contracts/tools |
| Policy | permissions, sideEffect, costEffect, and risk gates |
| Execution | SDK resource call or Agent direct tool call |
| Trace | deterministic record, replay, and audit artifact |
Decision
SimpleFunctions SDK and Agent SDK are API-key-first. For@spfunctions/sdk:
- the constructor may keep
apiKey?: string - docs and examples should pass
apiKey: process.env.SF_API_KEY - no-key usage is allowed only for strict manifest inspection and explicitly allowlisted free public reads
- methods whose contracts require identity must throw
MissingApiKeyErrorwhen no key is configured
@spfunctions/agent:
- live execution requires
SF_API_KEYby default - no-key mode is allowed only for
replayOnlyand static manifest inspection - replay misses must never fall through to live execution
- anonymous live tool execution is forbidden
- never embed a shared SimpleFunctions service key in SDK or Agent packages
- never print, log, trace, or throw raw API keys
- browser examples must not expose long-lived API keys
- scoped browser or session tokens are a later design
Mode matrix
| Surface or mode | API key required? | Allowed without key? | Notes |
|---|---|---|---|
sf.manifest.list() | No | Yes | Strict contract bootstrap. |
sf.manifest.get("world.read") | No | Yes | Contract inspection only. |
| SDK public free read | Usually no | Only if explicitly allowlisted | Must have sideEffect:none, costEffect:none, no user_data, and anonymousAllowed:true. |
| SDK auth-gated read | Yes | No | Throws MissingApiKeyError. |
| SDK costly read | Yes | No | Search, LLM, venue, and upstream-cost surfaces need identity. |
| SDK user-specific read | Yes | No | Anything with user_data requires key. |
| Agent SDK static manifest inspection | No | Yes | No live execution. |
Agent SDK replayOnly | No | Yes | Replay miss must not call live. |
Agent SDK live call() | Yes | No | Required even for public tools. |
Agent SDK live stream() | Yes | No | Same rule as call(). |
Agent SDK v1 model-backed run() | Yes | No | Also requires budget and session policy. |
| Browser SDK with long-lived key | No | No | Defer scoped or short-lived tokens. |
ContractTool additions
The strict manifest currently uses0.2.0-draft. Adding identity and cost policy should move the semantic contract to:
access.anonymousAllowed is a positive allowlist. It prevents accidental anonymous execution if a tool is mistakenly marked authRequired:false, sideEffect:none, and costEffect:none.
sideEffect vs costEffect
llm_cost is not a sideEffect. It is a costEffect.
sideEffect describes the tool’s product semantics:
- state mutation
- secret exposure or creation
- runtime start or stop
- paper or live trade execution
costEffect describes quota, upstream, search, venue, or LLM cost:
- hosted API cost
- search cost
- venue request cost
- LLM cost
auth_telemetry_write merely because auth middleware updates key usage metadata. Contract sideEffect should describe the tool’s product semantics, not incidental platform telemetry.
Use auth_telemetry_write only for a tool whose primary purpose is auth, session, or key telemetry mutation.
Policy ranking
Side-effect gates:costEffect is categorical. It is not dollar-budget enforcement. budgetUsd should remain a later feature until per-call cost estimates or response headers are reliable.
Initial annotation rules
Do not hand-classify from vibes. For every active strict contract tool, inspect whether it:- requires account or user context
- reads user-specific data
- calls paid or quota-limited upstream services
- calls search
- calls an LLM
- hits venue or market-provider APIs
- mutates user state
- creates secrets
- executes paper or live trade behavior
| Behavior | sideEffect | costEffect | anonymousAllowed |
|---|---|---|---|
| Strict manifest inspection | none | none | true |
| Explicit free cached public read | none | none | true only if approved |
| Normal hosted API read | none | api_cost | false |
| Search-backed read | none | search_cost | false |
| Venue/provider-backed read | none | venue_request_cost | false |
| LLM-backed read | none | llm_cost | false |
| User-specific read | none | api_cost or higher | false |
| Saved object write | user_write | depends | false |
| Secret/key/session mutation | secret or auth_telemetry_write | depends | false |
| Runtime start/stop | runtime | depends | false |
| Paper trade | paper_trade | venue/request cost maybe | false |
| Live trade | live_trade | venue/request cost maybe | false; requires explicit SDK/Agent policy |
events.*market.relatedas a semantic graphinvestigations.createintents.proposewebhooks.create- unguarded
live_trade
SDK behavior
The SDK constructor can remain:auth.status for this. This is local SDK state only.
SDK preflight should map each resource method to a canonical contract name:
Agent SDK v0 behavior
@spfunctions/agent v0 is a governed direct tool runner.
It is not:
- a model-backed planner
- a LangChain replacement
- an MCP client
- a CLI shell wrapper
- a browser runtime
- a live-trading agent
ContractToolhascostEffectContractToolhasaccess.anonymousAllowed- every active strict tool has
sideEffectandcostEffect - SDK
MissingApiKeyErrorexists - SDK manifest behavior is stable
- Agent policy behavior has an approved test plan
/api/contracts/tools. It must not resolve broad /api/tools names, MCP aliases, or deprecated legacy names.
Agent execution flow
Live execution flow:- create
runIdandcallId - emit
run.started - load strict contract manifest
- resolve canonical tool
- emit
tool.resolved - reject non-implemented, deferred, deprecated, or forbidden tools
- reject
agent.callable=false - check API key
- evaluate policy
- emit
policy.checked - normalize input
- hash input
- validate input if schema is available
- emit
tool.started - execute through SDK/client contract executor
- redact output for trace safety
- write trace if configured
- emit
tool.completedortool.failed - return
ToolCallResult
Policy evaluation
Evaluation order should be deterministic:- tool existence
- tool status
agent.callable- API key identity
- forbidden risk
- deny permissions
- allow permissions
- max
sideEffect - max
costEffect user_dataauth invariant- future budget placeholder
allow is omitted, side/cost gates still apply. If allow is provided, the tool permissions must be covered.
Historical note: this draft originally treated live_trade as a hard stop. The implemented SDK/Agent policy now treats it as an explicit side-effect class that can be allowed with auth, side-effect/cost ceilings, and trade guardrails.
Typed errors
Shared SDK/Agent error codes:Authorization headers, secret values, or full sensitive payloads.
Trace and replay
Trace replay must be strict. Trace entry sketch:- sort object keys recursively
- remove
undefined - preserve
null - preserve array order
- normalize
Dateto ISO string if present - never include API key or headers
- match by canonical tool plus normalized input hash
- return recorded output on hit
- throw
ReplayMissErroron miss - never silently call live when replay is requested
Agent events
sf agent --tool and @spfunctions/agent v0.stream() should produce compatible event concepts:
Browser key policy
Long-lived SimpleFunctions API keys must not be exposed in browser code. Allowed:Test plan
Contract tests:/api/contracts/toolsreturns schema version0.3.0-draft- every active implemented tool has
sideEffect - every active implemented tool has
costEffect - every active implemented tool has
access.anonymousAllowed anonymousAllowed=trueimpliesauthRequired=false,sideEffect=none,costEffect=none, and nouser_datauser_datapermission impliesauthRequired=truelive_tradeis active only behind explicit SDK/Agent policy guardrailsevents.*is not active or callableget_world_stateis not canonicalget_regime_historyis not canonical
- no-key SDK can call
manifest.list() - no-key SDK can call
manifest.get("world.read") - no-key SDK call to auth-required tool throws
MissingApiKeyError - no-key SDK call to
costEffect=llm_costthrowsMissingApiKeyError - no-key SDK call to
costEffect=search_costthrowsMissingApiKeyError - no-key SDK call to side-effecting tool throws
MissingApiKeyError - no-key SDK call to
user_datatool throwsMissingApiKeyError - no-key SDK call to an explicit free public read succeeds only when
anonymousAllowed=true - errors never include raw API keys
- live constructor without key throws
MissingApiKeyError inspectOnlyconstructor without key succeedsreplayOnlyconstructor without key succeeds- live call with key resolves
world.read - live call rejects
get_world_state - live call rejects
get_regime_history - live call rejects
events.search - live call rejects
live_tradeunless policy explicitly opts in - Agent uses
/api/contracts/tools, not/api/tools - policy deny list wins
- max
sideEffectblocksuser_write - max
costEffectblocksllm_costwhen max isapi_cost - replay miss throws
ReplayMissError - replay miss does not call live
- no docs say
@spfunctions/sdkis publicly published - no docs say
@spfunctions/agentis publicly published - no docs say Agent SDK is the CLI
- no docs say
/api/toolsis SDK/Agent truth - SDK examples use
apiKey: process.env.SF_API_KEY - Agent SDK docs say live execution requires
SF_API_KEY - browser docs do not show long-lived key usage
PR sequence
- RFC only: API-Key-First Identity and Governed Execution.
- Contract metadata taxonomy: add
costEffect,access.anonymousAllowed, replay policy, and invariant tests. - SDK identity, preflight, and typed errors.
- CLI parity metadata: compact events include
costEffect; no semantic change. - Private
@spfunctions/agentv0 skeleton after metadata and SDK preflight are stable. - Published docs sync so live docs match repo truth.
Hard stops
Do not:- publish
@spfunctions/sdk - publish
@spfunctions/agent - publish CLI without separate approval
- run
npm version - push
maindirectly - create new endpoints
- implement
events.* - implement
market.related - implement
auth.status - implement
investigations.create - implement
intents.propose - implement
webhooks.create - implement unguarded
live_trade - treat
/api/toolsas SDK/Agent truth - shell out to CLI from Agent SDK
- bundle a shared platform API key
- show browser long-lived API key examples