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
If any primitive is missing, an Agent SDK becomes either unsafe or not useful.
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
The key distinction is that SDK no-key public reads are a product concession, while Agent SDK no-key live calls are forbidden.
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
Deferred or forbidden surfaces must remain out:
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