changedInput schema / properties / edges / description
Previous value: -"Directed edges: [{from, to}] node ids"New value: +"Directed edges between node ids. A plain {from, to} is a data edge; add `guard` to make it a CONDITIONAL route (stateful_graph loops: 'revise until good' = critique → revise guarded on the verdict, plus an exit edge)."
addedInput schema / properties / edges / items / properties
Added value: +{
+ "from": {
+ "description": "Source node id",
+ "type": "string"
+ },
+ "guard": {
+ "description": "Take this edge ONLY when the condition holds — {source, op, value?}. `source` is a `{{node.port}}` channel ref (e.g. '{{critique.out}}'); `op` is eq | not_eq | contains | gt | gte | lt | lte | truthy | exists (a bounded interpreter, never eval). e.g. {\"source\": \"{{critique.out}}\", \"op\": \"contains\", \"value\": \"REVISE\"}.",
+ "properties": {
+ "op": {
+ "enum": [
+ "eq",
+ "not_eq",
+ "contains",
+ "gt",
+ "gte",
+ "lt",
+ "lte",
+ "truthy",
+ "exists"
+ ],
+ "type": "string"
+ },
+ "source": {
+ "type": "string"
+ },
+ "value": {}
+ },
+ "required": [
+ "source",
+ "op"
+ ],
+ "type": "object"
+ },
+ "kind": {
+ "description": "'data' (default) | 'control' | 'conditional' (implied when guard is set)",
+ "enum": [
+ "data",
+ "control",
+ "conditional"
+ ],
+ "type": "string"
+ },
+ "to": {
+ "description": "Destination node id",
+ "type": "string"
+ }
+}
addedInput schema / properties / edges / items / required
Added value: +[
+ "from",
+ "to"
+]
addedInput schema / properties / input
Added value: +{
+ "description": "WHAT THIS RIG ASKS ITS RUNNER FOR — the labelled input box the Builder canvas and the rig's product page render, and the value that fills every `{{input}}` placeholder at run time. Pass a plain string (the label) or {label, kind?, hint?, required?, multiple?} — kind is text (default) | number | boolean | date | url | email | file | document | markdown | code | image | audio | video | data | table | list | contact | location | secret | embedding | skill | harness | rig | any. The LISTING kinds (skill/harness/rig) mean the value is a PICKED registry listing — the Explore/Audit shape. DECLARE IT whenever the flow works on something the user supplies: without it the rig saves with no input box and only works when something passes `input` to rig_run. e.g. {\"label\": \"token address\", \"kind\": \"text\", \"hint\": \"0x… on chain 4663\"}.",
+ "type": [
+ "string",
+ "object"
+ ]
+}
addedInput schema / properties / inputs
Added value: +{
+ "description": "ADDITIONAL input ports beyond `input` (or the full list on their own) — same shapes as `input`. The FIRST input fills `{{input}}`; a multi-input rig renders one labelled box per port.",
+ "items": {
+ "type": [
+ "string",
+ "object"
+ ]
+ },
+ "type": "array"
+}
changedInput schema / properties / key / description
Previous value: -"Rig storage key (default 'agent-rig')"New value: +"Rig storage key — EACH DISTINCT KEY IS ITS OWN SAVED RIG (default 'agent-rig' = the Working Rig; use a fresh kebab key per additional rig, e.g. 'news-digest')"
addedInput schema / properties / name
Added value: +{
+ "description": "THE RIG'S NAME — the Builder's header box, which gates BOTH Save and ▶ Run for a human. Defaults to `summary` so the rig is always nameable; pass a short product-style name ('X Sentiment Digest') when you have one.",
+ "type": "string"
+}
changedInput schema / properties / nodes / description
Previous value: -"Graph nodes: [{id, skill?, label?, instruction?, doc_input?, model?, model_policy?, endpoint?, tool?, params?}] — endpoint/tool/params wire a node to a LIVE MCP server (real tools/call at run time)"New value: +"Graph nodes. Each is one step."
addedInput schema / properties / nodes / items / properties
Added value: +{
+ "agent": {
+ "description": "The step's ACTOR — a SUB-AGENT persona handle (an account that toggled agent_mode; see /api/agents/available). The step's agentic/instruction execution runs AS that persona: its frame, bio, persona harnesses and voice. Authorized at RUN time against the RUNNER — their own agent always works, someone else's only while public; a private/missing actor is a typed agent_unavailable step error, never a silent Rokha fallback. Empty = Rokha performs the step, as always.",
+ "type": "string"
+ },
+ "data_key": {
+ "description": "The DATA collection key (data_op steps only). `{{input}}` templates the run input. The runtime FORCES the `data.` prefix and blocks any reach at system harnesses (profile/chat/keys) — e.g. 'data.x-signals.{{input}}'",
+ "type": "string"
+ },
+ "data_op": {
+ "description": "Makes this a DATA step — durable, owner-scoped cross-rig memory. 'write' overwrites, 'append' accumulates across runs, 'read' loads a stored collection into the flow. Writes are PRIVATE by default (never in the public pulse, not forkable). This is how one rig persists a collection a LATER rig reads.",
+ "enum": [
+ "write",
+ "read",
+ "append"
+ ],
+ "type": "string"
+ },
+ "data_type": {
+ "description": "The harness_type a data write lands as (default 'knowledge'); one of knowledge/strategy/preference/persona/compliance",
+ "enum": [
+ "knowledge",
+ "strategy",
+ "preference",
+ "persona",
+ "compliance"
+ ],
+ "type": "string"
+ },
+ "doc_input": {
+ "description": "true = LIGHT LISTING step: read this skill's real registry listing + document and hand it forward (no execution, no runtime needed)",
+ "type": "boolean"
+ },
+ "endpoint": {
+ "description": "Live MCP JSON-RPC endpoint URL — the run performs a REAL tools/call here (optional; empty = agentic step)",
+ "type": "string"
+ },
+ "expects": {
+ "description": "What this step EXPECTS from upstream, in plain words ('a list of post URLs') — the step's I/O story, shown on the canvas and carried into the published skeleton",
+ "type": "string"
+ },
+ "headers": {
+ "description": "Extra HTTP headers for a `url` fetch (string→string). Combine with secret_refs to inject a key: {\"Authorization\": \"Bearer {{secret:my-api-key}}\"}.",
+ "type": "object"
+ },
+ "id": {
+ "description": "Stable node id used by edges (e.g. 'draft', 'a')",
+ "type": "string"
+ },
+ "inputs": {
+ "description": "PER-NODE input ports — the labelled landing slots a multi-parent (fan-in) edge binds to. Same shapes as the rig-level `input`.",
+ "items": {
+ "type": [
+ "string",
+ "object"
+ ]
+ },
+ "type": "array"
+ },
+ "instruction": {
+ "description": "Per-step instruction for the agent (optional)",
+ "type": "string"
+ },
+ "label": {
+ "description": "Display label (optional; defaults to the skill)",
+ "type": "string"
+ },
+ "method": {
+ "description": "HTTP method for a `url` fetch: GET (default) or POST",
+ "enum": [
+ "GET",
+ "POST"
+ ],
+ "type": "string"
+ },
+ "model": {
+ "description": "Optional Anthropic model id to pin for this step's agentic execution (e.g. 'claude-sonnet-4-6'). Non-Haiku models need the owner's own API key; without one, 'preferred' swaps to Haiku and 'required' refuses the step",
+ "type": "string"
+ },
+ "model_policy": {
+ "description": "'preferred' (default — swap to the best usable model when the pin isn't available) or 'required' (the step refuses to run without its model, typed model_unavailable)",
+ "enum": [
+ "preferred",
+ "required"
+ ],
+ "type": "string"
+ },
+ "params": {
+ "description": "Arguments for the live tool call, or a plain-HTTP fetch's query/body params (optional). USE `{{input}}` ANYWHERE IN A VALUE to thread what the runner typed — {\"address\": \"{{input}}\"} is how a param comes from user input. `{{secret:<alias>}}` injects one of their saved keys (see secret_refs).",
+ "type": "object"
+ },
+ "pos": {
+ "description": "Explicit canvas position {x, y} (optional — omitted nodes are auto-laid by topological layer, parallel branches side by side)",
+ "properties": {
+ "x": {
+ "type": "number"
+ },
+ "y": {
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
+ "produces": {
+ "description": "What this step PRODUCES for downstream ('a sentiment table') — same story, output side",
+ "type": "string"
+ },
+ "questions": {
+ "description": "Makes this a DECISION step (the Jev decision model): typed questions answered with calibrated probabilities — no prose, no tools. Shape: {<id>: {type: 'noul' (yes/no → answers.<id>.noul = p) | 'choice' (pick one of named criteria → answers.<id>.choice + confidence + probabilities) | 'score' (ordinal over a criteria LIST → answers.<id>.score + confidence), instructions, criteria}}. Route on it with a guard, e.g. {source:'{{decide.answers.enter.noul}}', op:'gte', value:0.7}. USE for gates, routing, classification, scoring, trade entry/exit calls; NEVER for anything that writes words or uses tools.",
+ "type": "object"
+ },
+ "secret_refs": {
+ "description": "ATTACH THE RUNNER'S OWN SAVED KEYS to this step — the only way a rig calls an API that needs credentials. Each item {alias, as?}: `alias` is a key the user saved in Profile → API KEYS (or a connect alias like 'oauth-x' / 'mcp-<host>'); `as` binds HOW it lands — `param:<key>` (a tools/call argument), `header:<Name>[:<prefix>]` (e.g. 'header:Authorization:Bearer ' — the trailing space is meaningful), or omit `as` and put {{secret:<alias>}} inside any param/header value. The VALUE is resolved server-side at the call and never appears in params, traces, or the published listing — so a published rig is safe to share and each RUNNER supplies their own key.",
+ "items": {
+ "properties": {
+ "alias": {
+ "type": "string"
+ },
+ "as": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "alias"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "signet_action": {
+ "description": "What the Signet step does. WRITES (move value, need a mandate + target + value): 'pay'/'transfer' (send SOL or an SPL token), 'swap' (trade via Jupiter), 'x402' (BUY a paid HTTP resource — signet_target = the resource URL, signet_value = your USDC ceiling in base units; Signet does the 402→pay→retry and returns the content). READS (no mandate, no value moved, safe on any step): 'metrics' (stage-shaped budgets/counts/spend for this actor — point a STAGE dashboard at this step), 'mandates' (is a mandate ready for this actor?), 'portfolio' (SOL + SPL balances; `signet_target` optionally names an address), 'price' (a live quote; `signet_target` = mint, `signet_value` = amount). PAPER (no mandate, no actor, NO money moves — real Jupiter quotes, a recorded ledger on the runner's account; output carries paper:true): 'wallet_activity' (a wallet's recent swaps; `signet_target` = the wallet, `signet_value` = limit) · 'paper_positions' / 'paper_reset' (`signet_target` = ledger name) · 'paper_buy' / 'paper_sell' (`signet_target` = ledger; the fills come from the previous step's JSON `buys` / `sells` list; `signet_value` = the default USD per buy or the sell fraction; a paper_buy step passes `sells` through for a following paper_sell). Defaults to 'pay' — the narrowest action — rather than guessing a wider one.",
+ "enum": [
+ "pay",
+ "transfer",
+ "swap",
+ "x402",
+ "metrics",
+ "mandates",
+ "portfolio",
+ "price",
+ "wallet_activity",
+ "paper_buy",
+ "paper_sell",
+ "paper_positions",
+ "paper_reset"
+ ],
+ "type": "string"
+ },
+ "signet_actor": {
+ "description": "Makes this a SIGNET step — the rig acts with VALUE under a mandate the RUNNER already granted to this actor (e.g. `rig:trader`). The step carries NO credentials: no key, no mandate id, no address. The rail resolves (runner, actor) -> mandate, so a published or forked rig carries the strategy and none of the authority — run someone else's copy and it spends THEIR OWN mandate or refuses. If no mandate exists for the actor, the step refuses and names the fix.",
+ "type": "string"
+ },
+ "signet_target": {
+ "description": "Destination address (pay/transfer), the mint to trade into (swap), an address (portfolio) or the mint to price (price). For a WRITE it MUST be on the mandate's allowlist or the rail refuses. Templating: `{{input}}` is the run input and `{{upstream.<field>}}` reads the previous step's output. Not needed for 'metrics' or 'mandates'.",
+ "type": "string"
+ },
+ "signet_value": {
+ "description": "Amount in BASE units — lamports for SOL, the mint's base units for an SPL token. Never a decimal. Templating: `{{input}}` is the run input, and `{{upstream.<field>}}` reads the PREVIOUS step's output — which is how a decided position size actually reaches the trade (`{{upstream.size_lamports}}`). Hard-coding an amount in a rig whose earlier step computes one is a bug: the strategy's sizing is then calculated and discarded. If the reference does not resolve, the field is EMPTY and the step refuses — a trade sized by a leftover constant is a trade nobody decided. Not needed for a read action.",
+ "type": "string"
+ },
+ "skill": {
+ "description": "Registry skill name to bind this step to (discover via registry_search)",
+ "type": "string"
+ },
+ "state": {
+ "description": "DECISION step only: what to decide about — a template reading {{input}} and {{upstream.<field>}}; empty = the upstream output.",
+ "type": "string"
+ },
+ "sub_rig": {
+ "description": "Make this node a SUB-RIG step — the referenced SAVED rig runs here as a bounded sub-run (one level deep; runner-pays, shared budget, traces nested under this node). Pass {rig_id, name?} or a bare rig-id string (rig_search finds ids). Mutually exclusive with `skill`. ⚠ the canvas renders it as a labeled ⬡ block; its INNER edits go through you.",
+ "type": [
+ "string",
+ "object"
+ ]
+ },
+ "tool": {
+ "description": "Tool name to invoke on the endpoint (optional)",
+ "type": "string"
+ },
+ "url": {
+ "description": "Plain-HTTP fetch target — the run performs a REAL HTTP request here via the SSRF-guarded proxy (optional; use with method + params + headers + secret_refs). `{{input}}` substitutes into the PATH or QUERY too (e.g. 'https://api.example.com/tokens/{{input}}'); the host stays fixed.",
+ "type": "string"
+ }
+}
addedInput schema / properties / nodes / items / required
Added value: +[
+ "id"
+]
addedInput schema / properties / outputs
Added value: +{
+ "description": "What the rig DECLARES IT PRODUCES — the contract's output side, shown on the canvas Output box and the product page. Same shapes as `input` (required is input-only). e.g. [{\"label\": \"the audit report\", \"kind\": \"markdown\"}].",
+ "items": {
+ "type": [
+ "string",
+ "object"
+ ]
+ },
+ "type": "array"
+}
addedInput schema / properties / state
Added value: +{
+ "description": "SHARED STATE CHANNELS (stateful_graph) — named channels loop iterations read/write via `{{node.port}}` refs; `reducer` merges concurrent fan-out writes: 'last_write' (default) | 'append' | 'add'. Each {id, kind?, reducer?}.",
+ "items": {
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "kind": {
+ "type": "string"
+ },
+ "reducer": {
+ "enum": [
+ "last_write",
+ "append",
+ "add"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "type": "object"
+ },
+ "type": "array"
+}
changedInput schema / properties / structure_type / description
Previous value: -"dag (default) or stateful_graph"New value: +"'linear' (a simple chain — what the guided Builder produces), 'dag' (default; acyclic, parallel branches + real fan-in) or 'stateful_graph' (cyclic loops + conditional edge guards — the runtime evaluates guards and skips unfired branches for real; the Builder CANVAS can't edit it yet, so tell the owner edits go through you)"
changedInput schema / properties / structure_type / enum
Previous value: -[
- "dag",
- "stateful_graph"
-]New value: +[
+ "linear",
+ "dag",
+ "stateful_graph"
+]
changedInput schema / properties / summary / description
Previous value: -"Short rig summary"New value: +"Short human description of the flow (shows as the rig's name)"
changedInput schema / properties / wallet_address / description
Previous value: -"Owner scope — derived from your auth token over the public MCP door; you don't pass it, and the run door reads the Rig from the same scope."New value: +"Wallet address that owns the rig (the scope the run reads)"
changedInput schema / required
Previous value: -[
- "nodes"
-]New value: +[
+ "wallet_address",
+ "nodes"
+]