Appearance
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-rpcNo 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:
| Header | Value |
|---|---|
X-Workflow-Id | Workflow ID minted by the agent |
X-Timestamp | Unix epoch ms (must be within ±5 minutes of agent's clock) |
X-Signature | Hex-encoded HMAC-SHA256 of {workflow_id}.{timestamp}.{raw_body} |
Content-Type | application/json |
Body:
json
{
"method": "remember",
"args": { "key": "lead_status", "value": "qualified" }
}Response
json
{ "ok": true, "result": { ... } }Errors return JSON with a non-2xx status:
| Status | Reason |
|---|---|
| 400 | invalid JSON body, or missing required args for the called method |
| 401 | missing auth headers, or timestamp outside ±5 min tolerance |
| 403 | bad signature, or method not in the whitelist |
| 429 | rate limit exceeded (60 req/min/workflow); Retry-After header indicates seconds |
| 500 | method 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
| Method | Reason |
|---|---|
execute_workflow | Prevents 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 records | Outside 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 var | Provided by |
|---|---|
AGENT_HOSTNAME | Workflow deployer |
AGENT_ID | Workflow deployer |
WORKFLOW_ID | Workflow deployer |
CALLBACK_SECRET | Generated 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.
Related
- Workflows feature
- Agent Memory — what
recall/rememberinteract with
