OPCODE
Execution runtime for AI agents. You reason, OPCODE executes — zero tokens per run after the first define. Workflows persist across sessions, run on schedules, and coordinate multiple agents. Persistent SSE daemon: 1 server, N agents, 1 database. JSON-defined DAGs, level-by-level execution, automatic parallelism. 6 MCP tools over SSE (JSON-RPC).
Why use OPCODE instead of reasoning through each step yourself? Every repeated workflow burns tokens re-reasoning decisions you already made. OPCODE templates your reasoning once and executes it deterministically — zero inference cost, identical output every run, survives context resets.
Which Tool?
| I want to... | Tool |
|---|---|
| Create/update a workflow template | opcode.define |
| Execute a workflow | opcode.run |
| Check status or pending decisions | opcode.status |
| Resolve a decision / cancel / retry | opcode.signal |
| List workflows, events, or templates | opcode.query |
| Visualize a workflow DAG | opcode.diagram |
Quick Start
Install:
go install github.com/rendis/opcode/cmd/opcode@latest
First-time setup (writes config and starts daemon):
opcode install --listen-addr :4100 --vault-key "my-passphrase"
Restart after stop: OPCODE_VAULT_KEY="my-passphrase" opcode
MCP client configuration:
{
"mcpServers": {
"mcpServers": {
"opcode": {
"type": "sse",
"url": "http://localhost:4100/sse"
}
}
}
Each agent self-identifies via agent_id in tool calls. Opcode auto-registers unknown agents. Choose a stable ID per agent (e.g., "content-writer", "deploy-bot").
Workflows survive restarts. On startup, orphaned active workflows become suspended. Query with opcode.query({ "resource": "workflows", "filter": { "status": "suspended" } }), then resume or cancel via opcode.signal.
See operations.md for full configuration, subcommands, SIGHUP hot-reload, security model, web panel, and benchmarks.
MCP Tools
opcode.define
Registers a reusable workflow template. Version auto-increments (v1, v2, v3...).
| Param | Type | Required | Description |
|---|---|---|---|
name | string | yes | Template name |
definition | object | yes | Workflow definition (see below) |
agent_id | string | yes | Defining agent ID |
description | string | no | Template description |
input_schema | object | no | JSON Schema for input validation |
output_schema | object | no | JSON Schema for output validation |
triggers | object | no | Trigger config (seeworkflow-schema.md) |
Returns: { "name": "...", "version": "v1" }
opcode.run
Executes a workflow from a registered template.
| Param | Type | Required | Description |
|---|---|---|---|
template_name | string | yes | Template to execute |
agent_id | string | yes | Initiating agent ID |
version | string | no | Version (default: latest) |
params | object | no | Input parameters |
Returns:
{
"workflow_id": "uuid",
"status": "completed | suspended | failed",
"output": { ... },
"started_at": "RFC3339",
"completed_at": "RFC3339",
"steps": {
"step-id": { "step_id": "...", "status": "completed", "output": {...}, "duration_ms": 42 }
}
}
If status is "suspended", call opcode.status to see pending_decisions.
opcode.status
Gets workflow execution status.
| Param | Type | Required | Description |
|---|---|---|---|
workflow_id | string | yes | Workflow to query |
Returns:
{
"workflow_id": "uuid",
"status": "suspended",
"steps": { "step-id": { "status": "...", "output": {...} } },
"pending_decisions": [
{
"id": "uuid",
"step_id": "reason-step",
"context": { "prompt": "...", "data": {...} },
"options": [ { "id": "approve", "description": "Proceed" } ],
"timeout_at": "RFC3339",
"fallback": "reject",
"status": "pending"
}
],
"events": [ ... ]
}
Workflow statuses: pending, active, suspended, completed, failed, cancelled.
opcode.signal
Sends a signal to a suspended workflow.
| Param | Type | Required | Description |
|---|---|---|---|
workflow_id | string | yes | Target workflow |
signal_type | enum | yes | decision / data / cancel / retry / skip |
payload | object | yes | Signal payload (see below) |
step_id | string | no | Target step |
agent_id | string | no | Signaling agent |
reasoning | string | no | Agent's reasoning |
Payload by signal type:
| Signal | step_id | Payload | Behavior |
|---|---|---|---|
decision | required | { "choice": "<option_id>" } | Resolves decision, auto-resumes |
data | optional | { "key": "value", ... } | Injects data into workflow |
cancel | no | {} | Cancels workflow |
retry | required | {} | Retries failed step |
skip | required | {} | Skips failed step |
Returns (decision): { "ok": true, "resumed": true, "status": "completed", ... }
Returns (other): { "ok": true, "workflow_id": "...", "signal_type": "..." }
opcode.query
Queries workflows, events, or templates.
| Param | Type | Required | Description |
|---|---|---|---|
resource | enum | yes | workflows / events / templates |
filter | object | no | Filter criteria |
Filter fields by resource:
| Resource | Fields |
|---|---|
workflows | status, agent_id, since (RFC3339), limit |
events | workflow_id, step_id, event_type, since, limit |
templates | name, agent_id, limit |
Note: event queries require either event_type or workflow_id in filter.
Returns: { "<resource>": [...] } -- results wrapped in object keyed by resource type.
opcode.diagram
Generates a visual DAG diagram from a template or running workflow.
| Param | Type | Required | Description |
|---|---|---|---|
template_name | string | no* | Template to visualize (structure preview) |
version | string | no | Template version (default: latest) |
workflow_id | string | no* | Workflow to visualize (with runtime status) |
format | enum | yes | ascii / mermaid / image |
include_status | bool | no | Show runtime status overlay (default: true if workflow_id) |
* One of template_name or workflow_id required.
template_name-- preview DAG structure before executionworkflow_id-- visualize with live step statusformat: "ascii"-- CLI-friendly text with box-drawing charactersformat: "mermaid"-- markdown-embeddable flowchart syntaxformat: "image"-- base64-encoded PNG for visual channels
Returns: { "format": "ascii", "diagram": "..." }
Workflow Definition
{
"steps": [ ... ],
"inputs": { "key": "value or ${{secrets.KEY}}" },
"context": { "intent": "...", "notes": "..." },
"timeout": "5m",
"on_timeout": "fail | suspend | cancel",
"on_complete": { /* step definition */ },
"on_error": { /* step definition */ },
"metadata": {}
}
| Field | Type | Required | Description |
|---|---|---|---|
steps | StepDefinition[] | yes | Workflow steps |
inputs | object | no | Input parameters (supports ${{}}) |
context | object | no | Workflow context, accessible via ${{context.*}} |
timeout | string | no | Workflow deadline (e.g.,"5m", "1h") |
on_timeout | string | no | fail (default), suspend, cancel |
on_complete | StepDefinition | no | Hook step after completion |
on_error | StepDefinition | no | Hook step on workflow failure |
metadata | object | no | Arbitrary metadata |
Step Definition
{
"id": "step-id",
"type": "action | condition | loop | parallel | wait | reasoning",
"action": "http.get",
"params": { ... },
"depends_on": ["other-step"],
"condition": "CEL guard expression",
"timeout": "30s",
"retry": { "max": 3, "backoff": "exponential", "delay": "1s", "max_delay": "30s" },
"on_error": { "strategy": "ignore | fail_workflow | fallback_step | retry", "fallback_step": "id" },
"config": { /* type-specific */ }
}
type defaults to action. See workflow-schema.md for all config blocks.
Step Types
action (default)
Executes a registered action. Set action to the action name, params for input.
condition
Evaluates a CEL expression and branches.
{
"id": "route",
"type": "condition",
"config": {
"expression": "inputs.env",
"branches": { "prod": [...], "staging": [...] },
"default": [...]
}
}
loop
Iterates over a collection or condition. Loop variables: ${{loop.item}}, ${{loop.index}}.
{
"id": "process-items",
"type": "loop",
"config": {
"mode": "for_each",
"over": "[\"a\",\"b\",\"c\"]",
"body": [
{
"id": "hash",
"action": "crypto.hash",
"params": { "data": "${{loop.item}}" }
}
],
"max_iter": 100
}
}
Modes: for_each (iterate over), while (loop while condition true), until (loop until condition true).
parallel
Executes branches concurrently.
{
"id": "fan-out",
"type": "parallel",
"config": {
"mode": "all",
"branches": [
[{ "id": "a", "action": "http.get", "params": {...} }],
[{ "id": "b", "action": "http.get", "params": {...} }]
]
}
}
Modes: all (wait for all branches), race (first branch wins).
wait
Delays execution or waits for a named signal.
{ "id": "pause", "type": "wait", "config": { "duration": "5s" } }
reasoning
Suspends workflow for agent decision. Empty options = free-form (any choice accepted).
{
"id": "review",
"type": "reasoning",
"config": {
"prompt_context": "Review data and decide",
"options": [
{ "id": "approve", "description": "Proceed" },
{ "id": "reject", "description": "Stop" }
],
"data_inject": { "analysis": "steps.analyze.output" },
"timeout": "1h",
"fallback": "reject",
"target_agent": ""
}
}
Variable Interpolation
Syntax: ${{namespace.path}}
| Namespace | Example | Available fields |
|---|---|---|
steps | ${{steps.fetch.output.body}} | <id>.output.*, <id>.status |
inputs | ${{inputs.api_key}} | Keys from params in opcode.run |
workflow | ${{workflow.run_id}} | run_id, name, template_name, template_version, agent_id |
context | ${{context.intent}} | Keys from context in workflow definition |
secrets | ${{secrets.DB_PASS}} | Keys stored in vault |
loop | ${{loop.item}}, ${{loop.index}} | item (current element), index (0-based) |
Two-pass resolution: non-secrets first, then secrets via AES-256-GCM vault.
CEL gotcha: loop is a reserved word in CEL. Use iter.item / iter.index in CEL expressions. The ${{loop.item}} interpolation syntax is unaffected.
See expressions.md for CEL, GoJQ, Expr engine details.
Built-in Actions
| Category | Actions |
|---|---|
| HTTP | http.request, http.get, http.post |
| Filesystem | fs.read, fs.write, fs.append, fs.delete, fs.list, fs.stat, fs.copy, fs.move |
| Shell | shell.exec |
| Crypto | crypto.hash, crypto.hmac, crypto.uuid |
| Assert | assert.equals, assert.contains, assert.matches, assert.schema |
| Expression | expr.eval |
| Workflow | workflow.run, workflow.emit, workflow.context, workflow.fail, workflow.log, workflow.notify |
Quick reference (most-used actions):
-
http.get:url(req),headers,timeout,fail_on_error_status-- output:{ status_code, headers, body, duration_ms } -
shell.exec:command(req),args,stdin,timeout,env,workdir-- output:{ stdout, stderr, exit_code, killed } -
fs.read:path(req),encoding-- output:{ path, content, encoding, size } -
workflow.notify:message(req),data-- output:{ notified: true/false }-- pushes real-time notification to agent via MCP SSE (best-effort) -
expr.eval:expression(req),data-- output:{ result: <value> }-- evaluates Expr expression against workflow scope (steps, inputs, workflow, context)
See actions.md for full parameter specs of all 26 actions.
Scripting with shell.exec
shell.exec auto-parses JSON stdout. Convention: stdin=JSON, stdout=JSON, stderr=errors, non-zero exit=failure. Use stdout_raw for unprocessed text.
See patterns.md for language-specific templates (Bash, Python, Node, Go).
Reasoning Node Lifecycle
-
Workflow reaches a reasoning step
-
Executor creates PendingDecision, emits
decision_requestedevent -
Workflow status becomes
suspended -
Agent calls
opcode.statusto see pending decision with context and options -
Agent resolves via
opcode.signal:{ "workflow_id": "...", "signal_type": "decision", "step_id": "reason-step", "payload": { "choice": "approve" } } -
Workflow auto-resumes after signal
-
If timeout expires:
fallbackoption auto-selected, or step fails if no fallback
Common Patterns
See patterns.md for full JSON examples: linear pipeline, conditional branching, for-each loop, parallel fan-out, human-in-the-loop, error recovery, sub-workflows, and MCP lifecycle.
Error Handling
| Strategy | Behavior |
|---|---|
ignore | Step skipped, workflow continues |
fail_workflow | Entire workflow fails |
fallback_step | Execute fallback step |
retry | Defer to retry policy |
Backoff: none, linear, exponential, constant. Non-retryable errors (validation, permission, assertion) are never retried.
See error-handling.md for circuit breakers, timeout interactions, error codes.
Performance
10-step parallel workflows complete in ~50µs, 500-step in ~2.4ms. The event store sustains ~15k appends/sec with <12% drop under 100 concurrent writers. Worker pool overhead is ~0.85µs/task (>1M tasks/sec at any pool size).
Full benchmark charts, per-scenario breakdowns, and methodology: docs/benchmarks.md.