Skip to content

Workflow → Agent RPC ​

HMAC-authenticated HTTP endpoint that lets a deployed workflow worker call back into the owning agent for memory, messaging, contact lookup, and event emission.

Why this exists ​

Deployed workflows run as isolated Cloudflare Workers. They have their own bindings but no direct access to the agent's Durable Object — they can't read agent memory, send messages through gateways, or look up a contact. Without a callback surface, workflows are limited to whatever they were configured with at deploy time.

This endpoint exposes a narrow, signed RPC surface so workflows can ask the agent to do specific things on their behalf.

Endpoint ​

POST https://{hostname}/{agentId}/workflow-rpc

No user-cookie auth. Authentication is HMAC over a fixed envelope. The agent verifies the signature server-side and matches it against the workflow's stored callback_secret.

Request envelope ​

Three headers + a JSON body:

HeaderValue
X-Workflow-IdWorkflow ID minted by the agent
X-TimestampUnix epoch ms (must be within ±5 minutes of agent's clock)
X-SignatureHex-encoded HMAC-SHA256 of {workflow_id}.{timestamp}.{raw_body}
Content-Typeapplication/json

Body:

json
{
  "method": "remember",
  "args": { "key": "lead_status", "value": "qualified" }
}

Response ​

json
{ "ok": true, "result": { ... } }

Errors return JSON with a non-2xx status:

StatusReason
400invalid JSON body, or missing required args for the called method
401missing auth headers, or timestamp outside ±5 min tolerance
403bad signature, or method not in the whitelist
429rate limit exceeded (60 req/min/workflow); Retry-After header indicates seconds
500method threw

Whitelisted methods ​

The endpoint only invokes methods on this list. Anything else returns 403.

recall ​

json
{ "method": "recall", "args": { "key": "user_timezone" } }

Read a memory by key. Tries contact-scoped first, then agent-wide. Returns { found, key, value, scope, ... }. Pass no key to get all memories.

remember ​

json
{ "method": "remember", "args": { "key": "lead_status", "value": "qualified", "category": "context" } }

Write a memory. Contact-scoped if a contactId is in the workflow context, agent-wide otherwise. Confidence 0.9 (user-explicit). Returns { success, key, scope }.

send_message ​

json
{
  "method": "send_message",
  "args": {
    "gatewayType": "telegram",
    "recipient": "@username",
    "message": "Hello",
    "contactId": "..."
  }
}

Outbound via existing gateway plumbing. Honors gateway config and routing.

get_contact ​

json
{ "method": "get_contact", "args": { "contactId": "abc-123" } }

Look up a contact record. Read-only.

list_transactions ​

json
{
  "method": "list_transactions",
  "args": { "type": "crypto_in", "status": "confirmed", "contactId": "...", "limit": 10 }
}

Read recent transactions. All filters optional.

add_workflow_event ​

json
{
  "method": "add_workflow_event",
  "args": {
    "eventName": "review_complete",
    "executionId": "<the workflow's execution id>",
    "data": { "approved": true }
  }
}

Emit an event back into workflow_events for chaining with await-event nodes elsewhere.

Methods deliberately excluded ​

MethodReason
execute_workflowPrevents workflow → workflow → workflow loops.
Payment outbound (send_payment, charge_card, etc.)Approval-gated in the chat path; not safe to expose to autonomous workflows.
Mutation of contacts / deletion of recordsOutside the read+remember+send envelope.
recall_conversation (semantic)High-cost call; not needed for typical workflow patterns.

Rate limit ​

Token-bucket-ish counter per workflow, stored in agent_state (workflow_callback_rate:{workflow_id}). The window resets after 60 seconds.

Default: 60 requests / minute per workflow.

On exceed, the response is HTTP 429 with Retry-After: <seconds>.

Generated client ​

The codegen for deployed workflows (workflow-codegen.ts) emits a callAgent(env, method, args) helper. It builds the HMAC envelope and posts to the endpoint. The deployed worker needs four env vars:

Env varProvided by
AGENT_HOSTNAMEWorkflow deployer
AGENT_IDWorkflow deployer
WORKFLOW_IDWorkflow deployer
CALLBACK_SECRETGenerated by the agent on createWorkflow or activateDraftWorkflow, passed to the deployer

Deployer integration in progress

The workflow-deployer service does not yet pass CALLBACK_SECRET and friends as env vars. Until that lands, callAgent will throw inside the deployed worker. Tracked as a follow-up.

HMAC reference ​

Signing on the workflow side (TypeScript / Worker runtime):

ts
const body = JSON.stringify({ method, args });
const timestamp = String(Date.now());
const signMessage = `${WORKFLOW_ID}.${timestamp}.${body}`;
const key = await crypto.subtle.importKey(
  "raw",
  new TextEncoder().encode(CALLBACK_SECRET),
  { name: "HMAC", hash: "SHA-256" },
  false,
  ["sign"]
);
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(signMessage));
const sigHex = Array.from(new Uint8Array(sig), b => b.toString(16).padStart(2, "0")).join("");

Verification on the agent side uses constant-time string comparison after recomputing the expected signature with the workflow's stored callback_secret.

Schema ​

See migration 064_workflow_callback_secret.sql — adds callback_secret TEXT to the workflows table.