# Hyph-en - Documentation Context > SYSTEM INFO: This document is optimized for LLM/AI agent ingestion. > It contains the complete documentation in a structured, context-dense format. > Navigation chrome, styling, and interactive elements have been stripped. > GENERATED: 2026-04-08T23:54:32.535Z --- ## PATH: Actions > Http (Source: actions/01-http.md) HTTP Actions ============ HTTP actions call external REST API endpoints. Use them to integrate with any system that has an API — CRMs, ERPs, payment processors, notification services. Registration ============ ``bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "actionname": "createsalesforcelead", "kind": "http", "description": "Create a new lead in Salesforce", "url": "https://api.salesforce.com/services/data/v58.0/sobjects/Lead", "httpmethod": "POST", "headers": { "Authorization": "Bearer orgconfig:api:salesforcetoken", "Content-Type": "application/json" }, "passthrough": true, "outputKey": "newlead" }' ` Properties ========== | Property | Type | Required | Description | |----------|------|----------|-------------| | actionname | string | Yes | Unique name for this action | | kind | "http" | Yes | | | url | string | Yes | Endpoint URL. Supports {{ }} template syntax for path parameters | | httpmethod | string | Yes | "GET", "POST", "PUT", "PATCH", "DELETE" | | headers | object | No | Request headers. Values support orgconfig: prefix for secrets | | contenttype | string | No | Request content type (default: "application/json") | | passthrough | boolean | No | If true, the full response body is available in context | | outputKey | string | No | Context key for the response | URL Templates ============= Use {{ }} syntax for dynamic URL segments: `json { "actionname": "getorder", "kind": "http", "url": "https://api.store.com/orders/{{orderid}}", "httpmethod": "GET" } ` When used in a workflow step, the orderid comes from the step properties: `json { "type": "getorder", "properties": { "orderid": "@input.orderid" } } ` Secret References ================= Use orgconfig: to reference encrypted secrets stored in org config (/platform/multi-tenancy): `json { "headers": { "Authorization": "Bearer orgconfig:api:stripekey", "X-Custom-Token": "orgconfig:api:vendortoken" } } ` The execution engine resolves orgconfig: references at runtime. The actual secret value never appears in the workflow definition or reasoning traces. Usage in a Workflow =================== `json { "type": "createsalesforcelead", "properties": { "FirstName": "@input.firstname", "LastName": "@input.lastname", "Email": "@input.email", "Company": "@input.company" }, "outputKey": "newlead" } ` With passthrough: true, the full API response is available at @newlead for subsequent steps. Usage as an Agent Tool ====================== List the action name in the agent's tools array: `json { "mode": "react", "objective": "Look up customer and update their record", "tools": [{ "type": "action", "name": "createsalesforcelead" }, { "type": "action", "name": "getorder" }] } ` The agent can call createsalesforcelead` with the required parameters. The action schema is included in the agent's prompt so it knows what parameters are needed. Error Handling ============== If the HTTP request fails (non-2xx status, timeout, connection error), the action returns an error result. In a workflow step, this fails the run. In a ReAct agent, the error is returned as an observation and the agent can decide how to proceed. HTTP timeouts default to 30 seconds. For long-running API calls, consider using an async pattern with polling. → Next: LLM Actions (/actions/llm) --- ## PATH: Agents > Deployment Patterns > Agent As Step (Source: agents/deployment-patterns/01-agent-as-step.md) Pattern A: Agent as Workflow Step ================================= The agent operates as one step within a larger deterministic workflow. The workflow handles sequencing, data flow, and branching. The agent handles the reasoning-intensive step. ``mermaid flowchart LR S1["Step 1: Matcher
(deterministic)"] --> S2["Step 2: ReAct Agent
(reasoning)"] S2 --> S3["Step 3: PbotApproval
(human review)"] S3 --> S4["Step 4: Custom Table
(log results)"] style S2 fill:#f3e8ff,stroke:#9333ea ` When to Use =========== - Most of the process is deterministic, but one step requires judgment - You want the workflow to control overall flow while delegating reasoning to the agent - The agent is a specialist embedded in a structured pipeline - You need predictable sequencing before and after the reasoning step Complete Example: Invoice Exception Investigation ================================================= A reconciliation workflow that matches invoices to payments, then uses an agent to investigate any unmatched exceptions. `json { "name": "invoicereconciliationwithinvestigation", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["invoiceid"], "tolerance": 0.02, "outputMatched": "reconciled", "outputUnmatchedLeft": "exceptions" } }, { "type": "loop", "filter": { "condition": { "greaterThan": [{ "length": "@exceptions" }, 0] } }, "properties": { "mode": "foreach", "itemspath": "@exceptions", "itemvariablename": "exception", "actionstoexecute": [ { "type": "loop", "properties": { "mode": "react", "objective": "Investigate why invoice {{exception.invoiceid}} for ${{exception.amount}} from {{exception.vendor}} has no matching payment. Check for partial payments, alternate vendor names, or recent credits. Recommend: approve, reject, or escalate.", "tools": [ { "type": "action", "name": "searchpayments" }, { "type": "action", "name": "lookupvendor" }, { "type": "action", "name": "checkcredits" } ], "maxiterations": 8, "onstuck": { "iterations": 3, "action": "escalate" }, "resultkey": "investigation" } } ], "maxconcurrency": 3, "failurestrategy": "continueonerror", "collectresults": true, "resultkey": "allinvestigations" } }, { "type": "PbotApproval", "filter": { "condition": { "greaterThan": [{ "length": "@exceptions" }, 0] } }, "properties": { "comment": "Review {{exceptions.length}} investigated exceptions", "requestpayload": { "investigations": "@allinvestigations" } } }, { "type": "custom-table", "properties": { "table": "reconciliationlog", "operation": "write", "keys": ["runid"], "values": ["@runid"], "fields": { "matchedcount": "@reconciled.length", "exceptioncount": "@exceptions.length", "processedat": "@now" } } } ] } } ` How It Works ============ 1. Matcher (deterministic) — matches invoices to payments by ID with 2% tolerance 2. Foreach + ReAct (agent) — for each unmatched invoice, an agent investigates why. Up to 3 run in parallel 3. PbotApproval (human) — a reviewer sees all investigations and approves or rejects 4. Custom Table (deterministic) — logs the reconciliation run The workflow controls the pipeline. The agent only runs within its step. It can't skip the approval step or modify the matcher configuration. Key Properties ============== - filter on the agent step — the agent loop only runs if there are exceptions. If everything matched, it's skipped entirely - maxconcurrency: 3 — limits parallel agent executions to avoid LLM rate limits - onstuck: escalate — if an agent can't figure out an exception, it pauses for human help rather than failing - collectresults: true — all investigation results are gathered into @all_investigations` for the approval step → Next: Pattern B: Agent as Trigger (/agents/deployment-patterns/agent-as-trigger) --- ## PATH: Agents > Deployment Patterns > Index (Source: agents/deployment-patterns/index.md) Deployment Patterns =================== Hyphen agents operate in three patterns depending on where autonomy lives in your architecture. All three share the same governance model: structural permissioning, reasoning traces, stuck detection, and human escalation. Decision Matrix =============== | Pattern | Agent's Role | Workflow Calls | Waits for Results? | Typical Iterations | Best For | |---------|-------------|----------------|-------------------|-------------------|----------| | A: Agent as Step (/agents/deployment-patterns/agent-as-step) | One step in a deterministic workflow | 0 (agent IS inside a workflow) | N/A | 5–15 | Mostly-deterministic processes with one reasoning-heavy step | | B: Agent as Trigger (/agents/deployment-patterns/agent-as-trigger) | Smart router — classify and dispatch | 1 (fire-and-forget) | No | 3–5 | Unstructured input that needs classification before processing | | C: Agent as Orchestrator (/agents/deployment-patterns/agent-as-orchestrator) | Coordinator of multiple workflows | 2+ (sequential/conditional) | Yes | 10–20 | Multi-step processes requiring dynamic coordination and synthesis | ``mermaid flowchart LR subgraph "Pattern A: Step" direction LR WA1["Step 1:
Matcher"] --> AA["Step 2:
ReAct Agent"] --> WA2["Step 3:
Approval"] end subgraph "Pattern B: Trigger" direction LR Input["Unstructured
Input"] --> AB["Agent classifies
& dispatches"] AB -->|"one workflow"| WB["Target
Workflow"] end subgraph "Pattern C: Orchestrator" direction TB AC["Agent coordinates"] --> W1["Workflow 1"] W1 -->|"result"| AC AC --> W2["Workflow 2"] W2 -->|"result"| AC AC -->|"conflict"| Human["Human"] Human -->|"decision"| AC AC --> W3["Workflow 3"] end ` The Key Distinction: B vs C =========================== Patterns B and C both use the standalone agent API (POST /agents/execute) and both can trigger workflows. The difference is fundamental: Pattern B fires and forgets. The agent classifies input, triggers one workflow with wait: false, and completes. It's a smart router — once it dispatches, its job is done. Think of it as a mailroom that opens letters and sends them to the right department. Pattern C waits and coordinates. The agent triggers a workflow with wait: true, examines the result, decides what to do next, potentially triggers more workflows, synthesizes across results, and escalates when things conflict. Think of it as a project manager running a multi-step process where each step depends on the previous one. The telltale sign: if your agent's reasoning trace shows wait: true and conditional logic based on workflow results, you're in Pattern C. Choosing a Pattern ================== Start with Pattern A (agent as step) if you have an existing deterministic process that needs AI judgment at one point — for example, a reconciliation workflow where matched records are processed automatically but exceptions need investigation. Use Pattern B (agent as trigger) when input arrives in unstructured form and you need to classify, extract, and route before processing begins — for example, incoming emails that could be invoices, support requests, or vendor inquiries. The agent makes one routing decision and is done. Use Pattern C (agent as orchestrator) when the process itself is dynamic — the agent needs to decide what to do next based on results from previous steps — for example, customer onboarding where sanctions screening results determine whether enhanced due diligence is needed, and the agent synthesizes results across multiple verification workflows. Tool Declaration Across Patterns ================================ All three patterns use typed tool declarations, but the composition differs: ` Pattern A: Actions only (agent is a specialist within a workflow) tools: [{ type: "action", name: "searchrecords" }, { type: "action", name: "analyzeexception" }] Pattern B: Actions + workflow targets (agent classifies then routes) tools: [{ type: "action", name: "classifydocument" }, { type: "workflow", id: "wfinv001" }, ...] Pattern C: Mostly workflows + a few actions (agent coordinates sub-processes) tools: [{ type: "workflow", id: "wfkyc001" }, { type: "workflow", id: "wfsan002" }, ..., { type: "action", name: "gmailsend" }] ` Implicit tools (complete, _pauseforhuman, storememory, retrievememory, logprogress__`) are auto-injected in all patterns. --- ## PATH: Agents > React Loop (Source: agents/01-react-loop.md) ReAct Loop ========== ReAct stands for Reasoning + Acting. The agent thinks about its current state, chooses an action, observes the result, and repeats until it completes the objective or hits a limit. Iteration Lifecycle =================== Each iteration follows a strict cycle: ``mermaid sequenceDiagram participant Engine participant LLM participant Tool Engine->>LLM: System prompt + history + "What next?" LLM->>Engine: { thought, action, actioninput } Engine->>Engine: Validate action is in tool allowlist Engine->>Tool: Execute action with actioninput Tool->>Engine: Observation (result) Engine->>Engine: Append to history, check limits Note over Engine: Repeat until complete
or maxiterations reached ` Step 1 — Prompt. The engine constructs a prompt containing the objective, available tools, and the history of all previous iterations. Step 2 — Think + Decide. The LLM responds with a structured JSON object: what it's thinking, what action to take, and with what parameters. Step 3 — Validate. The engine checks that the chosen action is in the declared tool list. Undeclared actions are rejected. Step 4 — Execute. The engine runs the tool and captures the result as an observation. Step 5 — Accumulate. The thought, action, and observation are appended to the iteration history. The engine checks whether to continue, stop, or trigger stuck detection. LLM Response Format =================== The agent must respond with this JSON structure on every iteration: `json { "thought": "I need to look up the customer's order history before I can assess the refund request.", "action": "getorderhistory", "actioninput": { "customerid": "cust-12345", "limit": 10 } } ` | Field | Type | Description | |-------|------|-------------| | thought | string | The agent's reasoning — what it's considering, what it knows, what it needs | | action | string | The tool to call — must be in the declared tools list | | actioninput | object | Parameters for the tool call | The thought field is critical for auditability. It captures why the agent chose this action, not just what it did. Every thought is stored in the reasoning trace (/agents/reasoning-traces). Prompt Construction =================== The engine constructs the LLM prompt from three parts: System Prompt ============= Sets the agent's role, available tools, and response format requirements. The prompt includes the objective, each tool's description and parameter schema, and instructions for the agent to respond with structured JSON containing thought, action, and actioninput fields. Security hardening rules are appended to help the model resist prompt injection from untrusted input data. Iteration History ================= Every previous step is appended as context, giving the agent full visibility into what it has already done: ` Step 1: Thought: "I need to look up ticket #12345 to understand the issue." Action: lookupticket Input: { "ticketid": "12345" } Observation: { "subject": "Billing Error", "status": "open", "priority": "high" } Step 2: Thought: "The ticket is about a billing error. Let me analyze the sentiment." Action: analyzesentiment Input: { "text": "I've been charged twice for my subscription..." } Observation: { "sentiment": "negative", "urgency": "high" } ` Current Turn Prompt =================== After the objective, tools, and history, the engine asks the model to determine its next action based on what it has learned so far. Context Accumulation ==================== As iterations progress, the agent builds up knowledge: | Iteration | What the Agent Knows | |-----------|---------------------| | 1 | Objective only | | 2 | Objective + first tool result | | 3 | Objective + two tool results + its own reasoning about them | | N | Full history of thoughts, actions, and observations | Each observation becomes available for the agent to reference in subsequent reasoning. The agent effectively has a growing "working memory" of everything it has done and learned. Token Management ================ The iteration history grows with each cycle. For long-running agents, this can approach the LLM's context window limit. Hyphen manages this by: - Including the full history up to the context window limit - Summarizing older iterations when the history exceeds available tokens - Prioritizing recent iterations and the original objective Set maxiterations conservatively to prevent excessive token consumption. Most tasks complete in 5–10 iterations. Complex orchestration tasks may need 15–20. Termination Conditions ====================== The loop ends when any of these occur: | Condition | Result | |-----------|--------| | Agent calls complete | Success — the agent's answer is stored in context | | maxiterations reached | Failure — timeout, no answer produced | | timeoutms exceeded | Failure — execution time limit | | Agent calls pauseforhuman | Paused — waiting for human input | | Stuck detection triggers with action: "fail" | Failure — agent was looping | | Stuck detection triggers with action: "escalate" | Paused — routed to human | Configuration Reference ======================= `json { "type": "loop", "properties": { "mode": "react", "objective": "Your task description here", "tools": [ { "type": "action", "name": "toola" }, { "type": "action", "name": "toolb" } ], "model": "gpt-4", "maxiterations": 10, "timeoutms": 300000, "temperature": 0.7, "onstuck": { "iterations": 3, "action": "escalate" }, "includereasoningtrace": true, "resultkey": "agentResult" } } ` Implicit tools are auto-injected. Built-in tools like complete, pauseforhuman, storememory, retrievememory, and logprogress__` are always available — you don't need to declare them. Only declare your custom action and workflow tools. See Tool Declarations (/agents/tool-declarations) for the full format reference. → Next: Built-in Tools (/agents/built-in-tools) --- ## PATH: Gateway > Quickstart (Source: gateway/01-quickstart.md) Gateway Quickstart ================== Base URL: https://apisvr.tryhyphen.com 1. Sign up ========== ``bash curl -X POST https://apisvr.tryhyphen.com/gateway/signup \ -H "Content-Type: application/json" \ -d '{ "email": "ops@acme.com", "name": "Acme Ops" }' ` Save: - managementtoken (mt-hyp-) - apikey.key (sk-hyp-) 2. Check account/orgs ===================== `bash curl https://apisvr.tryhyphen.com/gateway/account \ -H "Authorization: Bearer mt-hyp-..." ` 3. Create another runtime API key (optional) ============================================ `bash curl -X POST https://apisvr.tryhyphen.com/gateway/orgs/:orgId/keys \ -H "Authorization: Bearer mt-hyp-..." \ -H "Content-Type: application/json" \ -d '{ "label": "prod-runtime", "permissions": "full" }' ` 4. Execute workflow through gateway =================================== `bash curl -X POST https://apisvr.tryhyphen.com/workflows/:id/execute \ -H "Authorization: Bearer sk-hyp-..." \ -H "Content-Type: application/json" \ -d '{ "input": { "invoice_id": "INV-001" } }' `` --- ## PATH: Getting Started > Quickstart (Source: getting-started/01-quickstart.md) Quickstart ========== From zero to a governed operational workflow in 15 minutes. Who This Is For =============== Operations lead — "My team spends hours investigating exceptions manually. I need AI that works within our approval process." → Skip to Your First Governed Workflow (#your-first-governed-workflow) Platform engineer — "We need to embed governed agent execution into our product." → Start at The Graduated Pipeline (#the-graduated-pipeline) Developer — "Just show me the API." → Skip to API Fast Path (#api-fast-path) What You'll Build ================= A working governed workflow that demonstrates the core pattern: deterministic rules handle clear cases, a bounded AI agent investigates exceptions, a human reviewer makes the final call on edge cases, and every decision is logged. We'll use invoice-to-payment reconciliation as the example — but the same architecture applies to incident response, contract review, employee onboarding, claims processing, or any operational process where you need autonomous AI within boundaries. The Graduated Pipeline ====================== Every Hyphen deployment follows this architecture: ``mermaid flowchart TD A["Operational Event"] --> B["Deterministic Rules"] B -->|"~80% auto-resolved"| F["✅ Done"] B -->|"Exceptions"| C["ReAct Agent — Bounded AI"] C -->|"~15% AI-resolved"| F C -->|"Low confidence"| D["PbotApproval — Human Review"] D -->|"~5% human-resolved"| F F --> E["Audit Trail"] ` Rules handle the clear cases. AI handles the ambiguous middle. Humans handle the edge cases. Every layer feeds the next. Nothing falls through. This pattern works because it matches how real operations teams already work — experienced staff handle the obvious cases, specialists investigate the tricky ones, managers approve the exceptions. Hyphen encodes that same graduated judgment into infrastructure. Your First Governed Workflow ============================ Step 1: Prepare Your Data ========================= Two datasets — invoices and payments — with real-world messiness: Invoices: `json [ { "invoiceid": "INV-001", "vendor": "Acme Corp", "amount": 10000.00, "date": "2026-01-15" }, { "invoiceid": "INV-002", "vendor": "Globex Inc", "amount": 4500.00, "date": "2026-01-18" }, { "invoiceid": "INV-003", "vendor": "Initech", "amount": 7250.00, "date": "2026-01-20" }, { "invoiceid": "INV-004", "vendor": "Umbrella Ltd", "amount": 15000.00, "date": "2026-01-22" }, { "invoiceid": "INV-005", "vendor": "Stark Industries", "amount": 3200.00, "date": "2026-01-25" } ] ` Payments: `json [ { "invoiceid": "INV-001", "vendor": "Acme Corp", "amount": 10000.00, "date": "2026-01-16" }, { "invoiceid": "INV-002", "vendor": "Globex Inc", "amount": 4480.00, "date": "2026-01-19" }, { "invoiceid": "INV-004", "vendor": "Umbrela Ltd", "amount": 15000.00, "date": "2026-01-23" }, { "invoiceid": "INV-006", "vendor": "Wayne Enterprises", "amount": 8800.00, "date": "2026-01-27" } ] ` Notice: INV-002 has a $20 discrepancy, INV-004 has a vendor typo ("Umbrela"), INV-003 and INV-005 have no payments, and INV-006 is a payment without an invoice. Step 2: Create the Workflow =========================== api POST /workflows Create a workflow with matching, AI investigation, human approval, and audit logging. `bash curl -X POST https://apisvr.tryhyphen.com/workflows \ -H "X-Org-Id: your-org" \ -H "Content-Type: application/json" \ -d '{ "name": "invoicepaymentreconciliation", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["invoiceid"], "tolerance": 50, "dateWindowDays": 3, "fuzzyThreshold": 85, "descriptionKey": "vendor", "outputMatched": "matched", "outputUnmatchedLeft": "unmatchedinvoices", "outputUnmatchedRight": "unmatchedpayments" } }, { "type": "loop", "filter": { "condition": { "greaterThan": [{ "length": "@unmatchedinvoices" }, 0] } }, "properties": { "mode": "react", "objective": "Investigate these unmatched invoices. For each, determine the most likely reason (timing delay, data entry error, duplicate, or missing payment). Recommend: wait, follow up with vendor, or escalate.", "tools": [ { "type": "action", "name": "lookupvendorhistory" } ], "maxiterations": 10, "onstuck": { "iterations": 3, "action": "escalate" }, "resultkey": "investigation" } }, { "type": "PbotApproval", "filter": { "condition": { "greaterThan": [{ "length": "@unmatchedinvoices" }, 0] } }, "properties": { "comment": "AI investigated unmatched invoices. Review findings and approve recommended actions.", "requestpayload": { "matchedcount": "@matched.length", "unmatchedinvoices": "@unmatchedinvoices", "aiinvestigation": "@investigation" } } }, { "type": "custom-table", "properties": { "table": "reconciliationlog", "operation": "write", "keys": ["runid", "timestamp"], "values": ["@_runid", "@now"], "fields": { "matchedcount": "@matched.length", "exceptioncount": "@unmatchedinvoices.length", "status": "completed" } } } ] } }' ` Note on tools: Built-in agent tools (complete, pauseforhuman, storememory, retrievememory, logprogress_) are automatically injected into every ReAct agent — you don't need to declare them. Only declare your custom action tools and workflow tools. What this defines: | Step | Primitive | What Happens | |------|-----------|-------------| | 1 | matcher | Compares invoices to payments: exact invoiceid, $50 absolute amount tolerance, ±3 day date window, 85% fuzzy vendor match | | 2 | loop (react) | AI agent investigates unmatched invoices — bounded to 10 iterations with stuck detection | | 3 | PbotApproval | Human reviewer sees results + AI reasoning — makes final call | | 4 | custom-table | Logs the run to an audit table | Step 3: Execute =============== api POST /workflows/:id/execute Run the workflow with your invoice and payment data. `bash curl -X POST https://apisvr.tryhyphen.com/workflows/{workflowid}/execute \ -H "X-Org-Id: your-org" \ -H "Content-Type: application/json" \ -d '{ "input": { "invoices": [ { "invoiceid": "INV-001", "vendor": "Acme Corp", "amount": 10000.00, "date": "2026-01-15" }, { "invoiceid": "INV-002", "vendor": "Globex Inc", "amount": 4500.00, "date": "2026-01-18" }, { "invoiceid": "INV-003", "vendor": "Initech", "amount": 7250.00, "date": "2026-01-20" }, { "invoiceid": "INV-004", "vendor": "Umbrella Ltd", "amount": 15000.00, "date": "2026-01-22" }, { "invoiceid": "INV-005", "vendor": "Stark Industries", "amount": 3200.00, "date": "2026-01-25" } ], "payments": [ { "invoiceid": "INV-001", "vendor": "Acme Corp", "amount": 10000.00, "date": "2026-01-16" }, { "invoiceid": "INV-002", "vendor": "Globex Inc", "amount": 4480.00, "date": "2026-01-19" }, { "invoiceid": "INV-004", "vendor": "Umbrela Ltd", "amount": 15000.00, "date": "2026-01-23" }, { "invoiceid": "INV-006", "vendor": "Wayne Enterprises", "amount": 8800.00, "date": "2026-01-27" } ] } }' ` Response: `json { "id": "run-123e4567-e89b-12d3-a456-426614174000", "status": "running" } ` Production tip: Instead of embedding data in the payload, upload files to Document Storage (/platform/document-storage) and pass doc: references inside input, for example: { "input": { "invoices": "doc:docabc123" } }. This supports CSVs, JSON, and Excel files up to 50 MB. Step 4: Check Results ===================== api GET /runs/:runId/status Poll for status. The run will pause at PbotApproval waiting for human review. `bash curl https://apisvr.tryhyphen.com/runs/run-123e4567-e89b-12d3-a456-426614174000/status \ -H "X-Org-Id: your-org" ` Expected matcher output: | Record | Result | Why | |--------|--------|-----| | INV-001 | ✅ Matched | Exact match, 1-day date offset | | INV-002 | ✅ Matched | $4,500 vs $4,480 — $20 difference within $50 tolerance | | INV-004 | ✅ Matched | "Umbrella" vs "Umbrela" — 87% fuzzy match | | INV-003 | ❌ Unmatched | No payment found | | INV-005 | ❌ Unmatched | No payment found | | INV-006 | ❌ Unmatched payment | No invoice found | The matcher handled 3 of 5 invoices automatically — including the amount discrepancy and vendor typo. No AI cost. Near-instant. The 2 unmatched invoices flow to the ReAct agent. Sample reasoning trace: ` Iteration 1: Thought: "INV-003 from Initech — no payment match. Dated Jan 20, only 16 days ago. Standard terms are net-30." Action: logprogress Input: { "message": "INV-003 likely within payment terms" } Iteration 3: Thought: "Both INV-003 and INV-005 are recent. Recommend: monitor for 30 days, then follow up if still unmatched." Action: complete_ Input: { "answer": "2 unmatched invoices — both within payment terms. Recommend: wait 30 days.", "confidence": 0.85 } ` Step 5: Approve =============== The workflow pauses at PbotApproval. The reviewer sees matched records, exceptions, and the AI investigation. api POST /approvals/:runId/:stepId Submit the human decision. It becomes part of the permanent audit trail. `bash curl -X POST https://apisvr.tryhyphen.com/approvals/run-123e4567-e89b-12d3-a456-426614174000/2 \ -H "X-Org-Id: your-org" \ -H "Content-Type: application/json" \ -d '{ "approved": true, "comment": "Agree with AI recommendation. Monitor and follow up in 30 days." }' ` The workflow resumes, logs to the audit table, and completes. What just happened. One API call matched 60% of records automatically, got AI investigation of exceptions, routed edge cases to a human with full context, and logged everything. Manual process: 4+ hours. Hyphen: ~30 seconds of compute. API Fast Path ============= For developers who want the five-minute tour. Setup ===== `bash BASEURL=https://apisvr.tryhyphen.com ORGID=your-org ` 1. Health Check =============== api GET /health `bash curl $BASEURL/health → OK ==== ` 2. Register an Action ===================== api POST /actions Register a reusable operation — HTTP, LLM, DB, matcher, or custom-table. `bash curl -X POST $BASEURL/actions \ -H "X-Org-Id: $ORGID" \ -H "Content-Type: application/json" \ -d '{ "actionname": "fetchcustomer", "kind": "http", "url": "https://api.example.com/customers/{{customerid}}", "httpmethod": "GET", "passthrough": true }' → { "id": "actabc123", "actionname": "fetchcustomer" } ========================================================= ` 3. Create a Workflow ==================== api POST /workflows `bash curl -X POST $BASEURL/workflows \ -H "X-Org-Id: $ORGID" \ -H "Content-Type: application/json" \ -d '{ "name": "myfirstworkflow", "definition": { "actions": [ { "type": "fetchcustomer", "properties": { "customerid": "@input.id" } }, { "type": "custom-table", "properties": { "table": "auditlog", "operation": "write", "keys": ["action"], "values": ["customerlookup"] }} ] } }' → { "id": "wfl-123e4567-e89b-12d3-a456-426614174000" } ====================================================== ` 4. Execute ========== api POST /workflows/:id/execute `bash curl -X POST $BASEURL/workflows/wfl-123e4567-e89b-12d3-a456-426614174000/execute \ -H "X-Org-Id: $ORGID" \ -H "Content-Type: application/json" \ -d '{ "input": { "id": "cust-12345" } }' → { "id": "run-123e4567-e89b-12d3-a456-426614174000", "status": "running" } =========================================================================== ` 5. Check Status =============== api GET /runs/:runId/status `bash curl $BASEURL/runs/run-123e4567-e89b-12d3-a456-426614174000/status -H "X-Org-Id: $ORGID" → { "status": "completed", "context": { ... } } =============================================== ` 6. Run a Standalone Agent ========================= api POST /agents/execute Execute an AI agent outside of a workflow — agent-as-trigger or agent-as-orchestrator pattern. `bash curl -X POST $BASEURL/agents/execute \ -H "X-Org-Id: $ORGID" \ -H "Content-Type: application/json" \ -d '{ "objective": "Analyze this support ticket and draft a response", "tools": [ { "type": "action", "name": "fetchcustomer" }, { "type": "action", "name": "gmailsend" } ], "config": { "model": "gpt-4", "maxiterations": 10, "onstuck": { "iterations": 3, "action": "escalate" } } }' ` 7. Get the Reasoning Trace ========================== api GET /agents/:id/trace Full audit trail — every thought, action, and observation. `bash curl $BASEURL/agents/{agentrunid}/trace -H "X-Org-Id: $ORGID" ` Next Steps: Connect Your Systems ================================ Register actions to connect your systems, then reference them as agent tools using typed declarations. HTTP action (any business API): `bash curl -X POST $BASEURL/actions \ -H "X-Org-Id: $ORGID" \ -H "Content-Type: application/json" \ -d '{ "actionname": "lookuprecord", "kind": "http", "url": "https://your-system.com/api/records/{{recordid}}", "httpmethod": "GET", "headers": { "Authorization": "Bearer orgconfig:api:systemtoken" } }' ` LLM action (AI analysis): `bash curl -X POST $BASEURL/actions \ -H "X-Org-Id: $ORGID" \ -H "Content-Type: application/json" \ -d '{ "actionname": "analyzeexception", "kind": "llm", "template": "Analyze this operational exception and recommend a resolution:\n\n{{exceptiondata}}", "model": "gpt-4", "maxtokens": 300 }' ` Then use them as typed tool references in any agent: `json { "mode": "react", "objective": "Investigate this exception and notify the responsible party", "tools": [ { "type": "action", "name": "lookuprecord" }, { "type": "action", "name": "analyzeexception" }, { "type": "action", "name": "gmailsend" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" } ] } ` Note: Built-in tools (complete, pauseforhuman, storememory, retrievememory, logprogress__`) are automatically injected — you never need to list them. Same Architecture, Different Domains ==================================== The reconciliation example above demonstrates the graduated pipeline. Here's how the same pattern applies across industries: | Domain | What plays the "Matcher" role | What the Agent investigates | Who reviews | |--------|-----|------|------| | IT Security | Correlate alerts against known-benign indicators | Enrich with threat intel, assess severity, recommend containment | SOC analyst | | Legal | Compare extracted contract terms against standard playbook | Classify deviation risk, search precedent, draft redlines | Legal counsel | | People Ops | N/A — agent orchestrates directly | Coordinate IT provisioning, benefits, training, equipment | HR coordinator | | Healthcare | Match claims to policies, detect duplicates | Analyze denial codes, check medical necessity, draft appeals | Clinical reviewer | See all templates → (/templates) Three Things to Remember ======================== 1. You don't need AI for everything. The matcher handles the majority of operational data work with zero AI cost. Start with deterministic rules. Add AI only for exceptions that require judgment. 2. Governance is structural, not policy. The agent can only use tools you've declared, can only iterate up to the cap you set, and escalates automatically when stuck. This is architecture, not a policy document. 3. Every decision is auditable. Matcher results, agent reasoning traces, human approval decisions — all captured, all queryable, all persistent. Continue learning: Core Concepts (/getting-started/core-concepts) explains the six building blocks. Your First Workflow (/getting-started/your-first-workflow) walks through building a more complex pipeline step by step. Templates (/templates) show production-ready patterns across finance, IT, legal, healthcare, and people operations. --- ## PATH: Guides > Invoice Matching (Source: guides/01-invoice-matching.md) End-to-End: Invoice Reconciliation ================================== This guide walks through building a complete invoice reconciliation workflow from scratch. By the end, you'll have a pipeline that matches invoices to payments, investigates exceptions with an AI agent, routes edge cases to human reviewers, and logs everything to an audit table — running on a daily schedule. Prerequisites ============= You need a running Hyphen instance and curl. All examples use X-Org-Id: acme-corp. Step 1: Register Org Config =========================== Store the API keys your workflow actions will use: ``bash curl -X POST https://apisvr.tryhyphen.com/org-config \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "key": "api:llmapikey", "value": "sk-your-openai-key" }' ` `bash curl -X POST https://apisvr.tryhyphen.com/org-config \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "key": "api:erptoken", "value": "your-erp-api-token" }' ` Config values are encrypted at rest and referenced in workflows with the orgconfig: prefix. Step 2: Register HTTP Actions ============================= Register the actions the AI agent will use as tools: `bash Look up a purchase order from your ERP ====================================== curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "actionname": "lookuppurchaseorder", "kind": "http", "url": "https://erp.acme.com/api/purchase-orders/{{ponumber}}", "httpmethod": "GET", "headers": { "Authorization": "Bearer orgconfig:api:erptoken" }, "passthrough": true }' ` `bash Search payment history by vendor ================================ curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "actionname": "checkpaymenthistory", "kind": "db", "datasource": "orgconfig:db:financepg", "query": "SELECT FROM payments WHERE vendorid = $1 AND paymentdate BETWEEN $2 AND $3", "params": ["@input.vendorid", "@input.startdate", "@input.enddate"], "passthrough": true }' ` `bash Check for duplicate invoices ============================ curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "actionname": "searchduplicateinvoices", "kind": "db", "datasource": "orgconfig:db:financepg", "query": "SELECT FROM invoices WHERE amount = $1 AND vendorid = $2 AND invoicedate BETWEEN $3 AND $4 AND invoiceid != $5", "params": ["@input.amount", "@input.vendorid", "@input.startdate", "@input.enddate", "@input.invoiceid"], "passthrough": true }' ` Step 3: Create the Matcher Workflow =================================== Start simple — just the matcher step: `bash curl -X POST https://apisvr.tryhyphen.com/workflows \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "name": "invoicereconciliationv1", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["ponumber", "vendorid"], "tolerance": 50, "dateWindowDays": 5, "fuzzyThreshold": 85, "descriptionKey": "vendorname", "outputMatched": "reconciled", "outputUnmatchedLeft": "unmatchedinvoices", "outputUnmatchedRight": "unmatchedpayments" } } ] } }' ` Response: `json { "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "name": "invoicereconciliationv1" } ` Step 4: Execute with Sample Data ================================ `bash curl -X POST https://apisvr.tryhyphen.com/workflows/wfl-123e4567-e89b-12d3-a456-426614174000/execute \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "input": { "invoices": [ { "invoiceid": "INV-001", "ponumber": "PO-100", "vendorid": "V-50", "vendorname": "Acme Corp", "amount": 1000.00, "date": "2026-01-15" }, { "invoiceid": "INV-002", "ponumber": "PO-101", "vendorid": "V-51", "vendorname": "Beta LLC", "amount": 2500.00, "date": "2026-01-16" }, { "invoiceid": "INV-003", "ponumber": "PO-999", "vendorid": "V-52", "vendorname": "Gamma Inc", "amount": 750.00, "date": "2026-01-17" } ], "payments": [ { "paymentid": "PAY-001", "ponumber": "PO-100", "vendorid": "V-50", "vendorname": "ACME Corporation", "amount": 1000.00, "date": "2026-01-18" }, { "paymentid": "PAY-002", "ponumber": "PO-101", "vendorid": "V-51", "vendorname": "Beta LLC", "amount": 2450.00, "date": "2026-01-20" } ] } }' ` Check the result: `bash curl https://apisvr.tryhyphen.com/runs/run-123e4567-e89b-12d3-a456-426614174000/status \ -H "X-Org-Id: acme-corp" ` You should see INV-001 matched to PAY-001 (exact match), INV-002 matched to PAY-002 ($50 difference within $50 tolerance), and INV-003 as an unmatched exception (no matching PO-999 payment). Using uploaded files instead of inline data. For production, upload your datasets to Document Storage (/platform/document-storage) and reference them with the doc: prefix: `bash Upload invoices CSV =================== curl -X POST https://apisvr.tryhyphen.com/documents -H "X-Org-Id: acme-corp" \ -F "file=@invoices.csv" -F 'tags=["invoices"]' → { "document": { "id": "docinv123..." } } =========================================== Execute with doc: references ============================ curl -X POST https://apisvr.tryhyphen.com/workflows/wfl-123e4567-e89b-12d3-a456-426614174000/execute \ -H "X-Org-Id: acme-corp" -H "Content-Type: application/json" \ -d '{ "input": { "invoices": "doc:docinv123", "payments": "doc:docpay456" } }' ` This avoids embedding large datasets in API payloads and enables webhook-triggered automation when new files are uploaded. Step 5: Add the Agent Step ========================== Update the workflow to investigate exceptions: `bash curl -X PUT https://apisvr.tryhyphen.com/workflows/wfl-123e4567-e89b-12d3-a456-426614174000 \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "name": "invoicereconciliationv2", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["ponumber", "vendorid"], "tolerance": 50, "dateWindowDays": 5, "fuzzyThreshold": 85, "descriptionKey": "vendorname", "outputMatched": "reconciled", "outputUnmatchedLeft": "unmatchedinvoices", "outputUnmatchedRight": "unmatchedpayments" } }, { "type": "loop", "filter": { "condition": { "greaterThan": [{ "length": "@unmatchedinvoices" }, 0] } }, "properties": { "mode": "foreach", "itemspath": "@unmatchedinvoices", "itemvariablename": "exception", "actionstoexecute": [ { "type": "loop", "properties": { "mode": "react", "objective": "Investigate unmatched invoice {{exception.invoiceid}} from {{exception.vendorname}} for ${{exception.amount}}. Determine root cause and recommend action.", "tools": [ { "type": "action", "name": "lookuppurchaseorder" }, { "type": "action", "name": "checkpaymenthistory" }, { "type": "action", "name": "searchduplicateinvoices" } ], "maxiterations": 8, "onstuck": { "iterations": 3, "action": "retrywithhint", "hint": "Complete with your best assessment." }, "resultkey": "investigation" } } ], "maxconcurrency": 5, "failurestrategy": "continueonerror", "collectresults": true, "resultkey": "allinvestigations" } } ] } }' ` Step 6: Add Approval for Write-Offs =================================== Add a PbotApproval step so a human reviews the agent's findings before any write-offs: Add this step after the loop in the actions array: `json { "type": "PbotApproval", "filter": { "condition": { "greaterThan": [{ "length": "@unmatchedinvoices" }, 0] } }, "properties": { "comment": "{{unmatchedinvoices.length}} exceptions investigated. Review AI findings.", "requestpayload": { "reconciledcount": "@reconciled.length", "investigations": "@allinvestigations", "unmatchedpayments": "@unmatchedpayments" } } } ` When the workflow pauses, submit the approval: `bash curl -X POST https://apisvr.tryhyphen.com/approvals/run-123e4567-e89b-12d3-a456-426614174000/2 \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "approved": true, "comment": "Reviewed. Write off INV-003 as vendor error." }' ` Step 7: Add Audit Logging ========================= Add a custom-table step to persist the reconciliation results: `json { "type": "custom-table", "properties": { "table": "reconciliationlog", "operation": "write", "keys": ["runid", "rundate"], "values": ["@runid", "@now"], "fields": { "totalinvoices": "@input.invoices.length", "autoreconciled": "@reconciled.length", "exceptions": "@unmatchedinvoices.length", "status": "completed" } } } ` First, create the table: `bash curl -X POST https://apisvr.tryhyphen.com/custom-tables \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "name": "reconciliationlog", "fields": [ { "name": "runid", "type": "text", "required": true }, { "name": "rundate", "type": "timestamptz", "required": true }, { "name": "totalinvoices", "type": "integer" }, { "name": "autoreconciled", "type": "integer" }, { "name": "exceptions", "type": "integer" }, { "name": "status", "type": "text", "required": true } ] }' ` Step 8: Schedule for Daily Execution ==================================== Add a schedule block to the workflow definition: `json { "name": "invoicereconciliationv3", "definition": { "schedule": { "every": "1d", "at": "02:00", "timezone": "America/New_York" }, "actions": [ ... ] } } `` The workflow now runs automatically at 2 AM Eastern every day. What You Built ============== A complete reconciliation pipeline that: 1. Matches invoices to payments on PO number, vendor ID, amount (within $50), and date (within 5 days) 2. Investigates unmatched exceptions using an AI agent with access to your ERP and payment database 3. Routes edge cases to a human reviewer with full context and agent reasoning 4. Logs every run to an audit table for compliance and trend analysis 5. Runs automatically on a daily schedule For the full production-ready version with all steps combined, see the AP Invoice Reconciliation template (/templates/ap-invoice-reconciliation). --- ## PATH: Integrations > Gmail (Source: integrations/01-gmail.md) Gmail ===== Send, read, and reply to emails from workflows and agents using the Gmail API. Available Actions ================= | Action | Description | |--------|-------------| | gmailsend | Send a new email | | gmailread | Read emails matching a query | | gmailreply | Reply to an existing email thread | Setup ===== 1. Store OAuth App Credentials ============================== Register your Google OAuth app credentials with Hyphen. You'll need a Google Cloud project with the Gmail API enabled and OAuth consent screen configured. ``bash curl -X POST https://apisvr.tryhyphen.com/oauth/gmail/app-credentials \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "clientid": "123456789.apps.googleusercontent.com", "clientsecret": "GOCSPX-your-client-secret" }' ` api POST /oauth/:provider/app-credentials Store OAuth app credentials (clientid, clientsecret) for a provider. Credentials are encrypted at rest. 2. Authorize a User Account =========================== Get the authorization URL and redirect the user to grant access: `bash curl "https://apisvr.tryhyphen.com/oauth/gmail/authorize?redirect=true" \ -H "X-Org-Id: acme-corp" ` api GET /oauth/:provider/authorize Returns the OAuth authorization URL. Pass redirect=true to be redirected directly to the provider's consent page. The callback URL is configured server-side via the OAUTHCALLBACKBASEURL environment variable. The user completes the Google consent flow. Hyphen handles the callback, exchanges the auth code for tokens, and stores them encrypted. 3. Verify Connection ==================== `bash curl https://apisvr.tryhyphen.com/oauth/connections \ -H "X-Org-Id: acme-corp" ` Response: `json [ { "provider": "gmail", "account": "notifications@acme.com", "status": "active", "scopes": ["gmail.send", "gmail.readonly"], "connectedat": "2026-02-01T10:00:00Z" } ] ` Using in Workflows ================== Reference Gmail actions as workflow steps. The oauthaccount_ property specifies which connected account to use. Send Email ========== `json { "type": "gmailsend", "properties": { "_oauthaccount_": "notifications@acme.com", "to": "@input.recipientemail", "subject": "Invoice {{input.invoiceid}} Processed", "body": "Dear {{input.customername}},\n\nYour invoice has been processed successfully.\n\nAmount: ${{input.amount}}\nReference: {{input.invoiceid}}" } } ` Read Emails =========== `json { "type": "gmailread", "properties": { "_oauthaccount_": "billing@acme.com", "query": "from:vendor@supplier.com subject:invoice after:2026/01/01", "maxresults": 10 } } ` Reply to Thread =============== `json { "type": "gmailreply", "properties": { "oauthaccount_": "support@acme.com", "threadid": "@input.threadid", "body": "Thank you for your inquiry. We've processed your request." } } ` Using as Agent Tools ==================== Include Gmail actions in an agent's tools array. The agent decides when and how to use them. `json { "mode": "react", "objective": "Process this support request and send a response", "tools": [ "lookupcustomer", "checkorderhistory", { "type": "action", "name": "gmailsend" }, { "type": "action", "name": "gmailread" } ], "maxiterations": 10 } ` The agent might search for previous emails from the customer, draft a response, and send it — or pause for human review if the issue is complex. Complete Example: Batch Statement Sender ======================================== `json { "name": "monthlystatementsender", "definition": { "actions": [ { "type": "loop", "properties": { "mode": "foreach", "itemspath": "@input.customers", "itemvariablename": "customer", "actionstoexecute": [ { "type": "gmailsend", "properties": { "oauthaccount_": "billing@acme.com", "to": "@customer.email", "subject": "Monthly Statement — {{customer.name}}", "body": "Dear {{customer.name}},\n\nPlease find your statement for {{input.month}}.\n\nBalance: ${{customer.balance}}\nDue: {{input.duedate}}" } } ], "maxconcurrency": 10, "failurestrategy": "continueonerror", "collectresults": true, "resultkey": "sendResults" } } ] } } `` Token Management ================ Hyphen automatically refreshes Gmail OAuth tokens before they expire. If a token refresh fails (e.g., user revoked access), the workflow step fails with an authentication error. You can check token status and force a refresh: api GET /oauth/connections/:provider/:account Validate a specific OAuth connection. api POST /oauth/connections/:provider/:account/refresh Force refresh an OAuth token. --- ## PATH: Platform > Architecture (Source: platform/01-architecture.md) Architecture ============ Hyphen operates on a three-phase execution model unified by a single idea: AI as Compiler. ``mermaid sequenceDiagram participant User participant AI Compiler participant Spec participant Engine participant Agent participant Human User->>AI Compiler: "Match invoices to payments,
investigate exceptions,
get approval for write-offs" AI Compiler->>Spec: JSON workflow specification Note over Spec: Frozen intent —
deterministic from here Spec->>Engine: Execute step by step Engine->>Engine: Step 1: Matcher (deterministic) Engine->>Agent: Step 2: ReAct loop (bounded) Agent->>Agent: Think → Act → Observe → Repeat Agent->>Engine: complete with answer Engine->>Human: Step 3: PbotApproval (pause) Human->>Engine: Approved Engine->>Engine: Step 4: Custom Table (log) Engine->>User: Run completed ✓ ` Phase 1: AI as Compiler (Design Time) ===================================== A user describes intent in plain language: > "Create a workflow that matches invoices to payments by invoice number with 2% tolerance. For unmatched invoices over $5,000, have an AI agent investigate. For anything the agent can't resolve, get human approval." Hyphen's AI compiler translates this into a precise JSON workflow specification — the conditions, data references, branching logic, escalation paths. `json { "name": "invoicereconciliation", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["invoiceid"], "tolerance": 0.02, "outputMatched": "matched", "outputUnmatchedLeft": "exceptions" } }, { "type": "loop", "filter": { "condition": { "greaterThan": ["@exceptions.length", 0] } }, "properties": { "mode": "react", "objective": "Investigate unmatched invoices...", "tools": [ { "type": "action", "name": "lookuppurchaseorder" }, { "type": "action", "name": "checkpaymenthistory" } ], "maxiterations": 10, "resultkey": "investigation" } }, { "type": "PbotApproval", "properties": { "comment": "Review AI investigation findings", "requestpayload": { "investigation": "@investigation" } } } ] } } ` The human describes the what. The AI produces the how. Humans are bad at writing recursive JSON specs with nested condition trees and context path references. AI is great at it. AI as Compiler, not AI as Runtime. The AI's job ends once the spec is produced. It doesn't make decisions during execution — it compiles intent into a deterministic blueprint. This distinction is critical for auditability: you can review the spec before it runs, and the execution engine follows it exactly. api POST /ai/generate-workflow Submit a natural language description. Returns a generationid for polling. api POST /workflows/create-from-ai Deploy the generated spec — creates the workflow, registers any needed actions, and provisions custom tables. Phase 2: Deterministic Runtime (Execution) ========================================== The execution engine runs the compiled specification exactly as written. It does not improvise. If the spec says "wait for human approval," it waits. If a condition evaluates to false, it follows the defined fallback path. If a step has an onFalse branch, it takes it. No hallucination. No skipped steps. `mermaid flowchart TD A["Workflow Spec"] --> B{"Top-level
condition?"} B -->|"true"| C["Step 1"] B -->|"false"| X["conditionnotmet"] C --> D{"Step 2
filter?"} D -->|"true"| E["Execute step"] D -->|"false"| F["Execute onFalse"] E --> G["Step 3"] F --> G G --> H["Step N..."] H --> Z["Run completed"] ` Each step executes in sequence. The engine resolves @path references from the accumulated context, resolves doc: references by streaming content from Document Storage (/platform/document-storage), evaluates filter conditions, and routes to onFalse branches when conditions fail. Every step output is captured and added to context for subsequent steps. Run statuses: | Status | Meaning | |--------|---------| | running | Steps executing | | paused | Waiting for human approval or form input | | completed | All steps finished | | failed | A step threw an error | | conditionnotmet | Top-level gate evaluated false | Phase 3: Bounded Agentic Runtime (Governed Autonomy) ==================================================== For tasks that require reasoning — analyzing exceptions, interpreting documents, making judgment calls — Hyphen provides ReAct (Reasoning + Acting) loops. Within these loops, an AI agent thinks, uses tools, observes results, and iterates toward an objective. But the agent operates inside a cage defined by the spec: Structural permissioning. Only tools explicitly declared in the workflow definition are available. The agent cannot discover or invent capabilities. This isn't a policy layer — it's architectural. If you declare ["lookupticket", "gmailsend", "complete"], those are the only three actions the agent can take. Bounded iteration. maxiterations caps prevent runaway execution. A typical production agent runs 5–15 iterations. Stuck detection. The engine identifies when an agent is looping without progress (repeating the same action with the same parameters) and triggers the configured recovery strategy: fail, retry with a hint, or escalate to a human. Human-in-the-loop. The agent can call pauseforhuman when confidence is low. Execution pauses, the human sees full context, their decision becomes part of the reasoning trace, and the agent resumes. Reasoning traces. Every iteration captures the agent's thought, the action it chose, the parameters it used, and the observation it received. This is the audit trail — every step of the agent's reasoning, queryable and persistent. Checkpoint and resume. Agent state is persisted. If execution pauses for human input or fails mid-run, it can resume from where it left off. `mermaid flowchart LR subgraph "Agent Cage (defined by spec)" T["Think"] --> A["Act
(declared tools only)"] A --> O["Observe"] O --> T end O -->|"maxiterations
or complete"| Done["Result"] O -->|"stuck detected"| Recovery["Escalate / Retry / Fail"] O -->|"_pauseforhuman_"| Human["Human Input"] Human --> T ` *The spec defines what the agent can do. The agent decides what it will do. Everything it does is recorded.* This is governed autonomy — the agent reasons independently within explicit boundaries. Enterprises audit specs, not vibes. Position in the Stack ===================== Hyphen sits between the AI models (which provide intelligence) and your business systems (which provide capabilities): ` ┌─────────────────────────────────────┐ │ Your Application / UI │ ├─────────────────────────────────────┤ │ LLM Provider (OpenAI, Anthropic) │ ← Intelligence ├─────────────────────────────────────┤ │ Hyphen │ ← Orchestration, governance, audit ├─────────────────────────────────────┤ │ Your APIs, databases, services │ ← Capabilities └─────────────────────────────────────┘ `` Hyphen integrates with any LLM provider. It connects to your existing infrastructure via registered actions. It doesn't require rearchitecting your backend — it wraps what you already have in a governed execution environment. → Next: Workflow DSL (/platform/workflow-dsl) — the complete JSON specification reference --- ## PATH: Primitives > Matcher (Source: primitives/01-matcher.md) Matcher ======= The matcher takes two datasets and finds records that correspond to each other. It outputs three arrays: matched pairs, unmatched records from the left side, and unmatched records from the right side. ``mermaid flowchart LR L["Left Dataset
(e.g. invoices)"] --> M{"Matcher"} R["Right Dataset
(e.g. payments)"] --> M M --> Matched["Matched Pairs"] M --> UL["Unmatched Left"] M --> UR["Unmatched Right"] ` Basic Usage =========== `json { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["invoiceid"], "outputMatched": "matched", "outputUnmatchedLeft": "unmatchedinvoices", "outputUnmatchedRight": "unmatchedpayments" } } ` This matches invoices to payments by exact invoiceid. Records with matching IDs land in @matched. Invoices with no payment go to @unmatchedinvoices. Payments with no invoice go to @unmatchedpayments. Properties Reference ==================== | Property | Type | Required | Description | |----------|------|----------|-------------| | left | array / @path / doc: | Yes | First dataset — inline array, @path reference, or doc: uploaded document | | right | array / @path / doc: | Yes | Second dataset — inline array, @path reference, or doc: uploaded document | | matchOn | string[] | Yes | Fields that must match exactly | | tolerance | number | No | Absolute numeric tolerance applied to the amount field. A tolerance of 0.02 means amounts differing by more than $0.02 won't match | | dateWindowDays | number | No | Date tolerance in days (±N). Applied to the date field | | fuzzyThreshold | number | No | Text similarity threshold 0–100. Applied to the field specified by descriptionKey | | descriptionKey | string | No | Field name for fuzzy text matching | | rules | array | No | Custom matching rules | | outputMatched | string | No | Context key for matched pairs (default: "matched") | | outputUnmatchedLeft | string | No | Context key for unmatched left records (default: "unmatchedLeft") | | outputUnmatchedRight | string | No | Context key for unmatched right records (default: "unmatchedRight") | Matching Criteria ================= Exact Key Matching (matchOn) ============================== Fields listed in matchOn must match exactly. This is the primary matching criteria — records are only compared if their matchOn fields align. `json { "matchOn": ["invoiceid"] } ` Multiple keys create a composite match — all must match: `json { "matchOn": ["vendorid", "invoicenumber"] } ` Numeric Tolerance (tolerance) =============================== Allow the amount field to differ by an absolute value. A tolerance of 0.02 means amounts within $0.02 of each other are still considered a match. `json { "matchOn": ["invoiceid"], "tolerance": 0.02 } ` With this configuration, an invoice for $1,000.00 would match a payment of $999.98–$1,000.02. For larger absolute tolerances, use values like 50 to allow a $50 difference. Date Window (dateWindowDays) ============================== Allow date fields to differ by up to N days: `json { "matchOn": ["invoiceid"], "dateWindowDays": 3 } ` An invoice dated January 10 would match a payment dated January 7–13. Fuzzy Text Matching (fuzzyThreshold + descriptionKey) ========================================================= Compare text fields using fuzzy string similarity. The threshold is 0–100 where 100 is an exact match: `json { "matchOn": ["vendorid"], "fuzzyThreshold": 85, "descriptionKey": "description" } ` This matches records where vendorid is identical and the description fields are at least 85% similar. Useful for matching line-item descriptions that may be worded differently across systems. Custom Rules (rules) ====================== Define additional matching rules evaluated by the condition engine: `json { "matchOn": ["invoiceid"], "rules": [ { "condition": { "lessOrEqual": [ { "abs": { "subtract": ["@left.amount", "@right.amount"] } }, 50 ] } } ] } ` Custom rules use the same condition operators as workflow conditions (/platform/conditional-logic), with @left and @right referencing the current pair being compared. Output Format ============= Matched Pairs ============= Each matched record contains both the left and right record: `json [ { "a": { "invoiceid": "INV-001", "amount": 1000, "vendor": "Acme" }, "b": { "invoiceid": "INV-001", "amount": 1000, "vendor": "Acme Corp" }, "matchscore": 0.95, "amountdifference": 0 } ] ` The a field is the left record, b is the right record. matchscore reflects overall match quality. amountdifference shows numeric deviation when tolerance matching is used. Unmatched Records ================= Unmatched arrays contain the original records with no modifications: `json [ { "invoiceid": "INV-099", "amount": 5000, "vendor": "NewVendor" } ] ` Worked Example ============== Input: `json { "invoices": [ { "invoiceid": "INV-001", "amount": 1000.00, "date": "2025-01-10", "description": "Monthly service fee" }, { "invoiceid": "INV-002", "amount": 2500.00, "date": "2025-01-15", "description": "Equipment rental" }, { "invoiceid": "INV-003", "amount": 750.00, "date": "2025-01-20", "description": "Consulting hours" } ], "payments": [ { "invoiceid": "INV-001", "amount": 1000.00, "date": "2025-01-12", "description": "Monthly service" }, { "invoiceid": "INV-002", "amount": 2475.00, "date": "2025-01-15", "description": "Equip rental Jan" } ] } ` Matcher configuration: `json { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["invoiceid"], "tolerance": 50, "dateWindowDays": 3, "fuzzyThreshold": 80, "descriptionKey": "description", "outputMatched": "reconciled", "outputUnmatchedLeft": "exceptions" } } ` Results: - @reconciled: INV-001 (exact match), INV-002 (amount difference $25 within tolerance, descriptions 80%+ similar) - @exceptions: INV-003 (no matching payment found) Using Uploaded Documents ======================== Instead of embedding datasets in the execution payload, upload files to Document Storage (/platform/document-storage) and reference them with the doc: prefix: `json { "type": "matcher", "properties": { "left": "doc:doca1b2c3d4e5f6", "right": "doc:docx7y8z9w0v1u2", "matchOn": ["invoiceid"], "tolerance": 50, "outputMatched": "matched", "outputUnmatchedLeft": "exceptions" } } ` CSV files resolve to Array with header rows as keys. JSON files resolve as-is. Pin a specific version with doc:doc_xxx@2 for audit reproducibility. Large Dataset Optimization ========================== For large datasets (10,000+ records per side), the matcher automatically switches to an indexed matching strategy when the infrastructure supports it. This provides significant performance improvements by pre-indexing records by their matchOn` keys rather than performing pairwise comparison. No configuration change is needed — the matcher detects the optimal strategy based on dataset size automatically. Matcher as the foundation. Most Hyphen workflows start with a matcher step. The matched records flow into deterministic processing, while exceptions route to AI agents or human review. This is the graduated exception handling pattern: deterministic rules for clear cases, AI for ambiguous cases, humans for edge cases. → Next: Loop (/primitives/loop) --- ## PATH: Sdk > Quickstart (Source: sdk/01-quickstart.md) SDK Quickstart ============== Time: ~10 minutes | Prerequisites: A Hyphen account with at least one workflow 1. Get a Publishable Key ======================== Publishable keys are created by your org admin via the gateway management API. ``bash curl -X POST https://apisvr.tryhyphen.com/sdk/admin/publishable-keys \ -H "Authorization: Bearer mt-hyp-..." \ -H "Content-Type: application/json" \ -d '{ "label": "ops-dashboard", "allowedorigins": ["https://internal.acme.com"], "allowedtables": ["invoices", "payments"], "allowedworkflows": ["wfinvoiceprocessing"] }' ` Save the returned pklive* key. This key is safe to include in client-side code — it cannot modify workflows, access admin endpoints, or perform actions outside its configured scope. Key Scoping =========== Each publishable key is restricted to: | Scope | Description | |-------|-------------| | Allowed origins | Which domains can use this key (CORS enforcement) | | Allowed workflows | Which workflows are visible through SDK components | | Allowed tables | Which custom tables are accessible in the data grid | | Allowed actions | Which actions can be triggered | | Rate limit | Requests per minute (default: 60) | 2. Add the SDK to Your Page =========================== Include the script tag in your HTML: `html ` Initialize the SDK: `html ` The SDK creates an anonymous session scoped to your publishable key. This is sufficient for read-only dashboards and task views. 3. Embed Components =================== Task Sidebar ============ Show pending approval tasks from your workflows: `html ` Data Grid ========= Display a sortable, paginated data table with inline editing: `html ` Document Feed ============= Let your team upload files that trigger workflow runs: `html ` 4. Enable Authentication (Optional) =================================== For user-level audit trails and per-user permissions, enable OTP authentication: `javascript const sdk = await HyphenSDK.init({ publishableKey: 'pklive...', onSessionExpired: () => sdk.login() }); // Show the login modal when the user clicks a button document.querySelector('#login-btn').addEventListener('click', () => { sdk.login(); }); ` Users must be pre-registered by an org admin against the publishable key before they can authenticate. The SDK does not support self-registration. 5. Enable Real-Time Updates (Optional) ====================================== Start the server-sent event stream for live updates across all components: `javascript sdk.startEventStream(); // Components automatically update when events arrive sdk.on('task:created', (e) => { console.log('New task:', e.data); }); ` Complete Example ================ `html Ops Console

Invoices

Upload Documents

`` Next Steps ========== - Components (/sdk/components) — full attribute and event reference for all five components - Authentication (/sdk/authentication) — anonymous vs. authenticated sessions, OTP flow - Theming (/sdk/theming) — match your brand with CSS custom properties - Events (/sdk/events) — real-time event system and cross-component updates --- ## PATH: Templates > Ap Invoice Reconciliation (Source: templates/01-ap-invoice-reconciliation.md) AP Invoice Reconciliation ========================= Three-way match across purchase orders, invoices, and payments. The universal starting point — every company has this problem, most solve it with spreadsheets. What Gets Automated =================== PO-to-invoice matching on PO number, amount tolerance, and date windows. Fuzzy vendor name matching catches data entry inconsistencies. Agent investigates discrepancies: wrong amounts, missing POs, duplicate invoices, partial payments. What Humans Still Own ===================== Write-off approvals above threshold. Vendor dispute resolution. New vendor onboarding decisions. Pipeline ======== ``mermaid flowchart TD A[Input: Invoices + Payments + POs] --> B[Matcher] B -->|~80% matched| C[Auto-Reconcile] B -->|~20% exceptions| D{Exception Type} D -->|Amount discrepancy| E[ReAct Agent] D -->|Missing PO| E D -->|Duplicate suspected| E E -->|High confidence| F[Auto-Resolve + Log] E -->|Low confidence| G[Human Approval] G -->|Approved| H[Process + Log] G -->|Rejected| I[Flag for Review] C --> J[Custom Table: Reconciliation Log] F --> J H --> J I --> J style B fill:#e8a84c,color:#09090b,stroke:none style E fill:#4ade80,color:#09090b,stroke:none style G fill:#60a5fa,color:#09090b,stroke:none ` Workflow Definition =================== `json { "name": "apinvoicereconciliation", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["ponumber", "vendorid"], "tolerance": 0.02, "dateWindowDays": 5, "fuzzyThreshold": 85, "descriptionKey": "vendorname", "outputMatched": "reconciled", "outputUnmatchedLeft": "unmatchedinvoices", "outputUnmatchedRight": "unmatchedpayments" } }, { "type": "loop", "filter": { "condition": { "greaterThan": [{ "length": "@unmatchedinvoices" }, 0] } }, "properties": { "mode": "foreach", "itemspath": "@unmatchedinvoices", "itemvariablename": "exception", "actionstoexecute": [ { "type": "loop", "properties": { "mode": "react", "objective": "Investigate this unmatched invoice: {{exception.invoiceid}} from {{exception.vendorname}} for ${{exception.amount}}. Determine the root cause: timing delay (check payment terms), amount discrepancy (compare to PO), duplicate invoice, missing PO, or data entry error. Recommend: auto-resolve, follow up with vendor, or escalate for write-off review.", "tools": [ { "type": "action", "name": "lookuppurchaseorder" }, { "type": "action", "name": "checkpaymenthistory" }, { "type": "action", "name": "searchduplicateinvoices" } ], "maxiterations": 8, "onstuck": { "iterations": 3, "action": "retrywithhint", "hint": "If you cannot determine the root cause, complete with your best assessment and low confidence." }, "resultkey": "investigation" } } ], "maxconcurrency": 5, "failurestrategy": "continueonerror", "collectresults": true, "resultkey": "allinvestigations" } }, { "type": "PbotApproval", "filter": { "condition": { "greaterThan": [{ "length": "@unmatchedinvoices" }, 0] } }, "properties": { "comment": "{{unmatchedinvoices.length}} exceptions investigated. Review AI findings and approve recommended actions.", "requestpayload": { "reconciledcount": "@reconciled.length", "exceptioncount": "@unmatchedinvoices.length", "investigations": "@allinvestigations", "unmatchedpayments": "@unmatchedpayments" } } }, { "type": "custom-table", "properties": { "table": "reconciliationlog", "operation": "write", "keys": ["runid", "rundate"], "values": ["@_runid", "@now"], "fields": { "totalinvoices": "@input.invoices.length", "autoreconciled": "@reconciled.length", "exceptionsinvestigated": "@unmatchedinvoices.length", "unmatchedpayments": "@unmatchedpayments.length", "status": "completed" } } } ] } } ` Required Registered Actions =========================== | Action | Kind | Purpose | |--------|------|---------| | lookuppurchaseorder | http | Query ERP for PO details by PO number | | checkpaymenthistory | db | Search payment records for a vendor within date range | | searchduplicateinvoices | db | Check for invoices with matching amounts and close dates | Customization Notes =================== Tolerance. The default 2% (0.02`) handles typical rounding differences. Lower to 0.5% for high-precision environments; raise to 5% if partial payments are common. Date window. 5 days covers standard payment processing lag. Extend to 15–30 days for international vendors with longer settlement cycles. Fuzzy threshold. 85 catches minor name variations ("Acme Corp" vs "ACME Corporation"). Lower to 75 if vendor names are highly inconsistent across systems. Agent iterations. 8 iterations gives the agent room to check multiple data sources. Reduce to 5 for simpler investigations; increase to 12 if your exception patterns are complex. --- ## PATH: Actions > Llm (Source: actions/02-llm.md) LLM Actions =========== LLM actions generate text using AI models. Define a prompt template with dynamic placeholders, and the action fills them in and calls the LLM at execution time. Registration ============ ``bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "actionname": "summarizedocument", "kind": "llm", "description": "Summarize a document in 3 bullet points", "template": "Summarize the following document in 3 bullet points:\n\n{{documenttext}}", "model": "gpt-4", "maxtokens": 500, "outputKey": "summary" }' ` Properties ========== | Property | Type | Required | Description | |----------|------|----------|-------------| | actionname | string | Yes | Unique name for this action | | kind | "llm" | Yes | | | template | string | Yes | Prompt template with {{ }} placeholders | | model | string | No | LLM model (default: configured in environment) | | maxtokens | number | No | Maximum tokens in the response (default: 1024) | | temperature | number | No | Sampling temperature 0–1 (default: 0.7) | | outputKey | string | No | Context key for the generated text | Prompt Templates ================ Use {{ }} syntax for dynamic content in the prompt: `json { "actionname": "extractinvoicefields", "kind": "llm", "template": "Extract the following fields from this invoice text and return as JSON:\n- invoicenumber\n- vendorname\n- amount\n- duedate\n\nInvoice text:\n{{invoicetext}}", "model": "gpt-4", "maxtokens": 256 } ` Multiple placeholders are supported: `json { "template": "You are a {{role}} at {{company}}.\n\nAnalyze this customer complaint and draft a response:\n\n{{complainttext}}" } ` Usage in a Workflow =================== `json { "type": "summarizedocument", "properties": { "documenttext": "@input.documentcontent" }, "outputKey": "summary" } ` The documenttext property fills the {{documenttext}} placeholder in the template. The LLM response is stored at @summary. Usage as an Agent Tool ====================== `json { "mode": "react", "objective": "Read the contract and identify key terms", "tools": [{ "type": "action", "name": "summarizedocument" }, { "type": "action", "name": "extractinvoicefields" }] } ` The agent sees the action's description and template parameters in its tool list. It can call summarizedocument with the required documenttext parameter whenever it needs a summary during its reasoning. Structured Output ================= For actions that need structured responses (JSON, specific fields), instruct the model in the template: `json { "actionname": "classifyticket", "kind": "llm", "template": "Classify this support ticket. Return ONLY a JSON object with:\n- category: one of [billing, technical, account, other]\n- priority: one of [low, medium, high, critical]\n- summary: one sentence summary\n\nTicket:\n{{tickettext}}", "model": "gpt-4", "max_tokens": 200 } `` LLM actions vs ReAct agents. LLM actions run a single prompt and return the response — no iteration, no tool use. Use LLM actions for one-shot tasks: summarization, classification, extraction. Use ReAct agents when the task requires multi-step reasoning or tool interaction. → Next: DB Actions (/actions/db) --- ## PATH: Agents > Built In Tools (Source: agents/02-built-in-tools.md) Built-in Tools ============== Every agent has access to these built-in tools. Include them in your tools array by name. | Tool | Purpose | |------|---------| | complete | Signal task completion with final answer | | _runworkflow | Trigger another Hyphen workflow | | pauseforhuman | Pause and request human input | | storememory | Save data for later retrieval within the session | | retrievememory | Recall previously stored data | | logprogress | Record milestones for observability | complete ============== Signal that the agent has finished its task and provide the final answer. Parameters ========== | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | answer | string | Yes | The final answer or result | | confidence | number | No | Confidence score 0–1 (default: 1.0) | | summary | string | No | Brief summary of work performed | Example ======= ``json { "thought": "I've verified the receipt, checked policy compliance, and the expense is within limits. Ready to approve.", "action": "complete", "actioninput": { "answer": "Approve. Expense of $847 for client dinner is within the $1,000 entertainment policy limit. Receipt verified, date matches credit card statement.", "confidence": 0.95, "summary": "Verified receipt authenticity, checked against entertainment policy, confirmed credit card match." } } ` Result ====== The agent loop terminates successfully. The answer, confidence, and summary are stored in context under the resultkey configured on the loop. `json { "@agentResult": { "answer": "Approve. Expense of $847...", "confidence": 0.95, "summary": "Verified receipt authenticity..." } } ` Always include complete in your tools list. Without it, the agent has no way to finish and will exhaust maxiterations. _runworkflow_ ================== Trigger another Hyphen workflow from within the agent. The agent can dispatch sub-tasks to specialized workflows and use the results in its reasoning. Parameters ========== | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | workflowid | string | No | ID of the workflow to run | | workflowname | string | No | Name of the workflow (alternative to ID) | | input | object | Yes | Input payload for the workflow | | wait | boolean | No | Wait for completion (default: true) | | timeoutms | number | No | Timeout in milliseconds (default: 60000) | *Either workflowid or workflowname is required. Example ======= `json { "thought": "I need to run the sanctions screening workflow for this customer before proceeding with onboarding.", "action": "_runworkflow_", "actioninput": { "workflowname": "sanctionsscreening", "input": { "customername": "Acme Corp", "country": "DE", "registrationnumber": "HRB 12345" }, "wait": true, "timeoutms": 120000 } } ` Result ====== `json { "workflowrunid": "run-123e4567-e89b-12d3-a456-426614174000", "workflowid": "wfl-123e4567-e89b-12d3-a456-426614174000", "status": "completed", "result": { "screeningresult": "clear", "matchesfound": 0 }, "context": { } } ` The agent receives the workflow's output as an observation and can reason about it in the next iteration. Async dispatch. Set wait: false to trigger a workflow without waiting. The agent gets back a workflowrunid immediately and can check status later or move on. _pauseforhuman ===================== Pause the agent and request human input. The agent's state is checkpointed — when the human responds, the agent resumes from exactly where it left off. Parameters ========== | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | question | string | Yes | Question for the human reviewer | | options | string[] | No | List of choices to present | | contextsummary | string | No | Summary of work done so far | | urgency | string | No | "low", "medium", or "high" | Example ======= `json { "thought": "I found two matching customers. I can't determine which one is correct from the data alone — I need a human to decide.", "action": "_pauseforhuman", "actioninput": { "question": "Two customers match 'John Smith'. Which should I use for this refund?", "options": [ "John Smith (ID: C-001) — Premium, member since 2019, $45K lifetime value", "John Smith (ID: C-047) — Standard, member since 2023, $2K lifetime value" ], "contextsummary": "Processing refund #RF-789 for order #12345. Amount: $234.00.", "urgency": "medium" } } ` What Happens ============ 1. Agent state is persisted (checkpointed) 2. Agent status changes to paused 3. An approval-style request is created 4. Human reviews context and responds via POST /agents/:id/resume 5. Agent resumes with the human's response as the next observation Resuming ======== `bash curl -X POST https://apisvr.tryhyphen.com/agents/{agentRunId}/resume \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "humaninput": "Use John Smith C-001, the premium customer.", "approved": true }' ` The agent's next iteration sees the human's response as an observation and continues reasoning. _storememory_ ================== Store a key-value pair for later retrieval within the same agent execution. Use this to save intermediate results, extracted data, or decisions that you'll need in a later iteration. Parameters ========== | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | key | string | Yes | Storage key | | value | any | Yes | Value to store (string, number, object, array) | | ttlseconds | number | No | Time-to-live before automatic expiration | Example ======= `json { "thought": "I found the customer's account details. Let me save this for when I draft the email.", "action": "_storememory_", "actioninput": { "key": "customerinfo", "value": { "name": "Jane Doe", "email": "jane@acme.com", "tier": "premium", "accountageyears": 4 } } } ` Result ====== `json { "stored": true, "key": "customerinfo", "message": "Stored value under key \"customerinfo\"" } ` retrievememory ===================== Retrieve a previously stored value by key. Parameters ========== | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | key | string | Yes | Storage key to retrieve | Example ======= `json { "thought": "Now I need the customer info I saved earlier to personalize this email.", "action": "retrievememory", "actioninput": { "key": "customerinfo" } } ` Result (found) ============== `json { "found": true, "key": "customerinfo", "value": { "name": "Jane Doe", "email": "jane@acme.com", "tier": "premium", "accountageyears": 4 }, "storedat": "2026-02-01T10:30:00Z" } ` Result (not found) ================== `json { "found": false, "key": "unknownkey", "message": "No memory found for key \"unknownkey\"" } ` Memory scope. storememory and retrievememory are scoped to a single agent execution. Memory does not persist across separate agent runs. For persistent storage across runs, use Custom Tables (/primitives/custom-table). logprogress ================== Record a progress milestone without affecting execution flow. Useful for observability — tracking where the agent is in a long-running task. Parameters ========== | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | message | string | Yes | Progress description | | milestone | string | No | Named milestone identifier | | data | object | No | Structured data associated with the milestone | Example ======= `json { "thought": "Research phase is done. I found 3 articles and 2 contacts. Moving to drafting.", "action": "logprogress", "actioninput": { "message": "Research phase complete. Found 3 relevant articles and 2 key contacts.", "milestone": "researchcomplete", "data": { "articlesfound": 3, "contacts_found": 2, "sources": ["TechCrunch", "Company Blog", "SEC Filing"] } } } ` Result ====== `json { "logged": true, "message": "Research phase complete. Found 3 relevant articles and 2 key contacts.", "timestamp": "2026-02-01T10:35:00Z" } `` Progress logs appear in the agent's reasoning trace (/agents/reasoning-traces) and are queryable for monitoring dashboards. → Next: Tool Declarations (/agents/tool-declarations) --- ## PATH: Agents > Deployment Patterns > Agent As Trigger (Source: agents/deployment-patterns/02-agent-as-trigger.md) Pattern B: Agent as Workflow Trigger ==================================== The agent operates as a smart ingestion layer. It receives unstructured input, reasons about it, and dispatches to one downstream workflow with structured parameters. The agent's job is classification and routing -- once it triggers the workflow, it's done. ``mermaid flowchart LR Input["Unstructured Input
(email, document, webhook)"] --> Agent["ReAct Agent
classify > extract > route"] Agent -->|"invoice detected"| W1["Invoice Processing
Workflow"] Agent -->|"support request"| W2["Support Ticket
Workflow"] Agent -->|"vendor inquiry"| W3["Vendor Onboarding
Workflow"] style Agent fill:#f3e8ff,stroke:#9333ea ` When to Use =========== - Input arrives in unstructured form (emails, documents, free-text webhooks) - You don't know upfront which workflow to run - The agent needs to classify, extract fields, and dispatch -- then it's finished - The agent is a router, not a coordinator. It triggers one workflow and completes Pattern B vs Pattern C Pattern B = the agent figures out which workflow to run, triggers it, and is done. One decision, one dispatch. Pattern C = the agent coordinates multiple workflows, waits for results, synthesizes across them, and makes further decisions based on what comes back. Ongoing reasoning across several steps. If your agent triggers a workflow and doesn't need to care about the result, that's Pattern B. If it triggers a workflow, waits for the result, then decides what to do next -- that's Pattern C. How It Differs From Pattern C ============================= | | Pattern B: Trigger | Pattern C: Orchestrator | |---|---|---| | Workflow calls | Triggers one workflow | Coordinates multiple workflows | | Waits for result? | No -- fires and completes (wait: false) | Yes -- waits for each result before deciding next step | | Decision complexity | Single classification decision | Multi-step reasoning with synthesis across results | | Typical iterations | 3-5 (classify, extract, dispatch, complete) | 10-20 (run workflow, analyze, run next, synthesize, escalate) | | Agent's role | Intelligent router | Decision-maker and coordinator | | After dispatch | Agent completes immediately | Agent continues reasoning | Tool Declaration ================ Pattern B agents need classification actions and workflow targets declared as typed tools. Since the agent routes to one of several possible workflows, declare each target workflow in the tools array: `json { "tools": [ { "type": "action", "name": "classifydocument" }, { "type": "action", "name": "extractfields" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Process and reconcile invoices against POs" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Triage and respond to support tickets" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Onboard new vendors with verification" } ] } ` The engine resolves these in two batched queries -- one for all actions, one for all workflows -- and presents them to the LLM by their human-readable names. Implicit tools (complete, _logprogress_, etc.) are auto-injected; you don't declare them. Typed declarations Use { "type": "action", "name": "..." } for registered actions and { "type": "workflow", "id": "..." } for workflows. Workflows are referenced by ID (survives renames) but presented to the LLM by their name from the database. See Tool Declaration Reference (/agents/tools/declaration-formats) for details. Complete Example: Document Classifier and Router ================================================ A standalone agent that receives a document, classifies it, extracts relevant fields, and triggers the correct processing workflow. Agent Configuration =================== `json { "objective": "Classify this incoming document and route it to the correct processing workflow.\n\nDocument content:\n{{input.documenttext}}\n\nSender: {{input.senderemail}}\nReceived: {{input.receivedat}}", "tools": [ { "type": "action", "name": "classifydocument" }, { "type": "action", "name": "extractfields" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Process invoices -- match to POs, flag exceptions, route for approval" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Handle support requests -- triage, investigate, respond" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Vendor onboarding -- verify business, set up account" } ], "config": { "model": "gpt-4", "maxiterations": 8, "onstuck": { "iterations": 3, "action": "escalate" } } } ` Execute via Standalone Agent API ================================ `bash curl -X POST https://apisvr.tryhyphen.com/agents/execute \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "objective": "Classify this incoming document and route it...", "tools": [ { "type": "action", "name": "classifydocument" }, { "type": "action", "name": "extractfields" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" } ], "config": { "model": "gpt-4", "maxiterations": 8 } }' ` Sample Reasoning Trace ====================== Notice the short iteration count -- the agent classifies, extracts, dispatches, and finishes. No waiting for workflow results. ` Iteration 1: Thought: "I need to classify this document first." Action: classifydocument Input: { "text": "Invoice #INV-2024-0891..." } Observation: { "category": "invoice", "confidence": 0.97 } Iteration 2: Thought: "It's an invoice with high confidence. Let me extract the key fields." Action: extractfields Input: { "text": "Invoice #INV-2024-0891...", "documenttype": "invoice" } Observation: { "invoicenumber": "INV-2024-0891", "vendor": "Acme Supplies", "amount": 4250.00, "duedate": "2026-03-15" } Iteration 3: Thought: "Classification and extraction done. Triggering the invoice processing workflow. I don't need to wait for it -- my job is routing." Action: invoiceprocessing <-- resolved from wfinv001 Input: { "invoices": [{ "invoicenumber": "INV-2024-0891", "vendor": "Acme Supplies", "amount": 4250.00, "duedate": "2026-03-15" }], "source": "emailingestion", "sender": "billing@acme.com", "wait": false } Observation: { "workflowrunid": "run-123e4567-e89b-12d3-a456-426614174000", "status": "running" } Iteration 4: Thought: "Invoice workflow triggered successfully. I'm done." Action: complete Input: { "answer": "Document classified as invoice (97% confidence). Triggered invoiceprocessing workflow (run-123e4567-e89b-12d3-a456-426614174000) with extracted fields.", "confidence": 0.97 } ` Four iterations. Classify, extract, dispatch, complete. That's the Pattern B signature. Key Properties ============== - Typed workflow tools give the agent a menu of possible destinations -- it picks the right one based on classification - wait: false on the workflow trigger -- the agent doesn't need to wait for invoice processing to complete. It classifies and routes - Standalone execution -- this agent runs via POST /agents/execute, not inside a workflow. It is the entry point - Short iteration budget -- maxiterations: 8 is plenty. If the agent needs more, you're probably looking at Pattern C Scaling the Pattern =================== For high-volume ingestion, combine with a foreach loop in a wrapper workflow: `json { "name": "batchdocumentprocessor", "definition": { "actions": [ { "type": "loop", "properties": { "mode": "foreach", "itemspath": "@input.documents", "itemvariablename": "doc", "actionstoexecute": [ { "type": "loop", "properties": { "mode": "react", "objective": "Classify and route: {{doc.text}}", "tools": [ { "type": "action", "name": "classifydocument" }, { "type": "action", "name": "extractfields" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" } ], "maxiterations": 6 } } ], "maxconcurrency": 5, "failurestrategy": "continueonerror" } } ] } } ` Anti-Pattern: When Pattern B Becomes Pattern C ============================================== If you find yourself writing an objective like this, you've outgrown Pattern B: ` "Classify this document, trigger the right workflow, WAIT for the result, and if it fails, try a different workflow, then send a summary email." `` The moment your agent needs to wait for results and make further decisions, you're in Pattern C territory. Pattern B agents fire and forget. Next: Pattern C: Agent as Orchestrator (/agents/deployment-patterns/agent-as-orchestrator) --- ## PATH: Getting Started > Core Concepts (Source: getting-started/02-core-concepts.md) Core Concepts ============= Six concepts cover everything you need to build with Hyphen. ``mermaid graph LR W["Workflow
(JSON spec)"] --> P["Primitives
(built-in steps)"] W --> A["Actions
(registered ops)"] P --> C["Context
(@path data flow)"] A --> C C --> AG["Agents
(ReAct loops)"] AG --> R["Runs
(execution + audit)"] P --> R style W fill:#e3f2fd,stroke:#1565c0 style P fill:#e8f5e9,stroke:#2e7d32 style A fill:#fff3e0,stroke:#e65100 style C fill:#f3e5f5,stroke:#6a1b9a style AG fill:#fce4ec,stroke:#c62828 style R fill:#e0f2f1,stroke:#00695c ` 1. Workflows ============ A workflow is a JSON spec that defines a process. It has a name, an optional top-level condition gate, and an ordered array of actions (steps). `json { "name": "invoicereconciliation", "definition": { "condition": { "greaterThan": ["@input.invoices.length", 0] }, "actions": [ { "type": "matcher", "properties": { ... } }, { "type": "loop", "properties": { "mode": "react", ... } }, { "type": "PbotApproval", "properties": { ... } } ] } } ` Workflows are created via POST /workflows, executed via POST /workflows/:id/execute, and produce runs with full audit trails. They can also be generated from plain language via POST /ai/generate-workflow. AI as Compiler. Hyphen's core philosophy: AI generates the workflow spec upfront (compile time), then the engine executes it deterministically (runtime). AI doesn't improvise during execution — it produces the blueprint, and the engine follows it exactly. 2. Actions ========== Actions are reusable operations you register once and reference by name in any workflow. Five kinds: | Kind | What It Does | Example | |------|-------------|---------| | http | Calls REST APIs | POST to Salesforce, GET from Stripe | | llm | AI text generation | Summarize a document, extract entities | | db | Database queries | SELECT from your warehouse | | matcher | Pre-configured matching | Matching rules saved as a reusable action | | custom-table | Table operations | Read/write to Hyphen-managed tables | Register an action: `bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: your-org" \ -H "Content-Type: application/json" \ -d '{ "actionname": "fetchcustomer", "kind": "http", "url": "https://api.example.com/customers/{{customerid}}", "httpmethod": "GET", "passthrough": true }' ` Use it in a workflow step: `json { "type": "fetchcustomer", "properties": { "customerid": "@input.id" } } ` Use it as a ReAct agent tool: `json { "tools": [{ "type": "action", "name": "fetchcustomer" }] } ` All action kinds work everywhere — in workflow steps, foreach loops, and as agent tools. → Full reference: Actions (/actions) 3. Primitives ============= Primitives are built-in workflow steps that don't require registration. They handle core orchestration patterns: | Primitive | Purpose | When to Use | |-----------|---------|-------------| | Matcher (/primitives/matcher) | Multi-criteria data matching | Reconciling two datasets (invoices↔payments, claims↔records) | | Loop (/primitives/loop) | Batch processing (foreach) or AI reasoning (react) | Processing N items, or letting an agent investigate a problem | | PbotApproval (/primitives/approval) | Human-in-the-loop | Manager sign-off, compliance review, edge case decisions | | PbotForm (/primitives/form) | External input collection | Vendor submits shipping details, customer provides documents | | Custom Table (/primitives/custom-table) | Multi-tenant data storage | Audit logs, operational state, cross-run memory | Primitives use the step type field directly — no registration required: `json { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", ... } } { "type": "PbotApproval", "properties": { "comment": "Review this transaction" } } ` 4. Context ========== Context is how data flows between steps. Every workflow run maintains a context object that grows as steps execute. @path References ================ The @ prefix references values from context: | Path | What It References | |------|-------------------| | @input.field | Data passed when the workflow was executed | | @matched | Matched records from a matcher step | | @unmatchedLeft | Left-side unmatched records from a matcher | | @item | Current item inside a foreach loop | | @_runid | The current run's ID | | @_approved | Boolean result from a PbotApproval step | | @stepOutput.field | Output from a named step (via outputKey) | | doc:docxxx | Uploaded document content resolved at execution time (Document Storage (/platform/document-storage)) | Template Interpolation ====================== Use {{ }} for string interpolation inside property values: `json { "message": "Hello {{input.customername}}, your balance is ${{context.balance}}" } ` Data Flow Example ================= `mermaid flowchart LR subgraph "Step 1: Matcher" M["@input.invoices + @input.payments"] end subgraph "Step 2: Loop" L["@unmatchedinvoices → @item"] end subgraph "Step 3: Approval" A["@investigation (from agent resultkey)"] end M -->|"matched, unmatchedinvoices"| L L -->|"investigation"| A ` → Full reference: Context Resolution (/platform/context-resolution) 5. Agents ========= Agents are ReAct (Reasoning + Acting) loops — AI that thinks, uses tools, observes results, and iterates toward an objective. They operate within boundaries defined by the workflow spec. `json { "type": "loop", "properties": { "mode": "react", "objective": "Investigate this unmatched invoice and recommend an action", "tools": [ { "type": "action", "name": "lookuperpinvoice" }, { "type": "action", "name": "gmailsend" } ], "maxiterations": 10, "onstuck": { "iterations": 3, "action": "escalate" }, "resultkey": "investigation" } } ` Governance Model ================ Every agent runs inside a cage defined by its spec: Structural permissioning — only tools explicitly declared in tools are available. The agent cannot discover or invent capabilities. Bounded iteration — maxiterations caps prevent runaway execution. Stuck detection — onstuck identifies when an agent loops without progress and triggers recovery (fail, retry with hint, or escalate to human). Human escalation — _pauseforhuman lets the agent request human input when confidence is low. The human's decision becomes part of the reasoning trace. Reasoning traces — every iteration captures thought, action, parameters, and observation. Full chain of reasoning, queryable and auditable. Tool Declaration ================ Tools support two formats — string shorthand for simple references, and full object definitions for explicit parameter schemas: `json { "tools": [ { "type": "action", "name": "lookupticket" }, { "type": "action", "name": "verifyidentity" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" } ] } ` Action tools reference registered actions by name — the resolver automatically fetches descriptions and parameters. Workflow tools reference workflows by ID, letting the agent trigger sub-workflows. Built-in tools (complete, pauseforhuman, storememory, retrievememory, logprogress_) are always auto-injected. Three Deployment Patterns ========================= | Pattern | Agent Role | Example | |---------|-----------|---------| | Agent as Step | One step in a larger workflow | Matcher finds exceptions → agent investigates them | | Agent as Trigger | Smart ingestion layer | Agent classifies incoming document → triggers the right workflow | | Agent as Orchestrator | Coordinates multiple workflows | Agent runs KYC, sanctions screening, and account setup in sequence | → Full reference: Agents (/agents) 6. Runs ======= A run is a single execution of a workflow. It captures everything: input, step outputs, agent reasoning traces, human decisions, timing, and final status. `bash Execute a workflow → get a run ============================== curl -X POST https://apisvr.tryhyphen.com/workflows/{id}/execute \ -H "X-Org-Id: your-org" \ -H "Content-Type: application/json" \ -d '{ "input": { "invoices": [...], "payments": [...] } }' → { "id": "run-123e4567-e89b-12d3-a456-426614174000", "status": "running" } =========================================================================== Check status ============ curl https://apisvr.tryhyphen.com/runs/run-123e4567-e89b-12d3-a456-426614174000/status \ -H "X-Org-Id: your-org" ` Run Statuses ============ | Status | Meaning | |--------|---------| | running | Execution in progress | | paused | Waiting for human approval or form input | | completed | Successfully finished | | failed | Execution failed | | conditionnot_met | Top-level condition evaluated to false | What's Captured =============== Every run persists: the full input payload, each step's output, all agent reasoning traces (thought → action → observation for every iteration), human approval decisions with reviewer identity and comments, and timestamps for every state transition. This is the audit trail — queryable, exportable, and persistent. → API reference: Run Status (/api) How They Fit Together ===================== A typical production deployment: 1. Register actions — connect your ERP, CRM, databases, and communication tools 2. Define a workflow — compose primitives and actions with conditional logic 3. Execute — send data in, get a run back 4. Monitor — poll status, handle approvals, read reasoning traces 5. Audit — every decision has a paper trail `mermaid sequenceDiagram participant You participant Hyphen participant AI participant Human You->>Hyphen: POST /workflows/:id/execute Hyphen->>Hyphen: Step 1: Matcher (deterministic) Hyphen->>AI: Step 2: ReAct loop (bounded) AI->>Hyphen: complete with answer Hyphen->>Human: Step 3: PbotApproval (pause) Human->>Hyphen: Approved Hyphen->>Hyphen: Step 4: Custom Table (log) Hyphen->>You: Run completed ✓ `` Continue building: Your First Workflow (/getting-started/your-first-workflow) walks through creating a multi-step workflow with conditional branching. Your First Agent (/getting-started/your-first-agent) builds a standalone ReAct agent with custom tools. --- ## PATH: Guides > Building An Agent Orchestrator (Source: guides/02-building-an-agent-orchestrator.md) End-to-End: Agent Orchestrator ============================== Build a support ticket processing agent that looks up ticket details, analyzes sentiment, drafts a response, and notifies the team on Slack when urgency is high. This guide covers action registration, ReAct workflow creation, reasoning trace monitoring, and human escalation handling. Prerequisites ============= A running Hyphen instance with curl. Slack OAuth configured for your org (see Slack integration (/integrations/slack)). Step 1: Register the Zendesk Lookup Action ========================================== ``bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "actionname": "lookupticket", "kind": "http", "url": "https://acme.zendesk.com/api/v2/tickets/{{ticketid}}.json", "httpmethod": "GET", "headers": { "Authorization": "Bearer orgconfig:api:zendesktoken" }, "passthrough": true }' ` Step 2: Register the Sentiment Analysis Action ============================================== This is an LLM action — it uses an AI model to analyze text: `bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "actionname": "analyzesentiment", "kind": "llm", "template": "Analyze this support ticket. Return JSON with: sentiment (positive/negative/neutral), urgency (low/medium/high), category (billing/technical/account/other), and a one-sentence summary.\n\nSubject: {{subject}}\nBody: {{body}}", "model": "gpt-4o-mini", "maxtokens": 256 }' ` Step 3: Create the Orchestrator Workflow ======================================== `bash curl -X POST https://apisvr.tryhyphen.com/workflows \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "name": "supportticketorchestrator", "definition": { "actions": [ { "type": "loop", "properties": { "mode": "react", "objective": "Process support ticket {{input.ticketid}}. Steps: 1) Look up the ticket details, 2) Analyze sentiment and urgency, 3) Draft a response approach, 4) If high urgency, notify #support-escalations on Slack, 5) Complete with your recommendation and the drafted response.", "tools": [ { "type": "action", "name": "lookupticket" }, { "type": "action", "name": "analyzesentiment" }, { "type": "action", "name": "slackpost" } ], "model": "gpt-4o", "maxiterations": 10, "onstuck": { "iterations": 3, "action": "escalate" }, "resultkey": "agentresult" } }, { "type": "custom-table", "properties": { "table": "ticketprocessinglog", "operation": "write", "keys": ["ticketid"], "values": ["@input.ticketid"], "fields": { "result": "@agentresult.answer", "confidence": "@agentresult.confidence", "runid": "@runid", "processedat": "@now" } } } ] } }' ` Step 4: Execute and Monitor =========================== Trigger the workflow: `bash curl -X POST https://apisvr.tryhyphen.com/workflows/wfl-123e4567-e89b-12d3-a456-426614174000/execute \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "input": { "ticketid": "12345" } }' ` Poll for status: `bash curl https://apisvr.tryhyphen.com/runs/run-123e4567-e89b-12d3-a456-426614174000/status \ -H "X-Org-Id: acme-corp" ` Once complete, retrieve the full reasoning trace to see exactly how the agent worked through the problem: `bash curl https://apisvr.tryhyphen.com/agents/agentabc/trace \ -H "X-Org-Id: acme-corp" ` The trace shows each iteration — the agent's thought process, which tool it chose, the parameters it used, and the observation it received: `json { "reasoningtrace": [ { "iteration": 1, "thought": "First I need to look up the ticket details", "action": "lookupticket", "actioninput": { "ticketid": "12345" }, "observation": { "subject": "Billing Error", "description": "I was charged twice...", "status": "open" } }, { "iteration": 2, "thought": "Now I'll analyze the sentiment and urgency", "action": "analyzesentiment", "actioninput": { "subject": "Billing Error", "body": "I was charged twice..." }, "observation": { "sentiment": "negative", "urgency": "high", "category": "billing" } }, { "iteration": 3, "thought": "High urgency billing issue. I should notify the team on Slack.", "action": "slackpost", "actioninput": { "channel": "#support-escalations", "text": "🚨 High urgency: Ticket #12345 — Billing error, customer charged twice" }, "observation": { "ok": true, "ts": "1706..." } }, { "iteration": 4, "thought": "I've gathered all info and notified the team. Time to complete.", "action": "complete", "actioninput": { "answer": "High urgency billing issue. Customer double-charged. Team notified on Slack.", "confidence": 0.95 } } ] } ` Step 5: Handle Human Escalation =============================== If the agent encounters something it can't resolve — say stuck detection triggers, or the agent explicitly calls _pauseforhuman — the run pauses. Check for paused runs: `bash curl "https://apisvr.tryhyphen.com/agents?status=paused" \ -H "X-Org-Id: acme-corp" ` Resume with human guidance: `bash curl -X POST https://apisvr.tryhyphen.com/agents/agentabc/resume \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "humaninput": "This is a known issue with our payment processor. Refund the customer and escalate to payments team.", "approved": true }' ` The agent continues from where it left off, incorporating the human's guidance into its next iteration. Step 6: Add Slack Notification on Completion ============================================ You can also notify your team when the workflow completes. Register a listener for workflowruncompleted, or add a Slack step after the agent in the workflow: `json { "type": "slackpost", "properties": { "_oauthaccount_": "acme-workspace", "channel": "#support-processed", "text": "✅ Ticket {{input.ticketid}} processed. Confidence: {{agentresult.confidence}}. Result: {{agentresult.answer}}" } } `` What You Built ============== A support ticket processing system that: 1. Looks up ticket details from Zendesk 2. Analyzes sentiment and urgency using an LLM action 3. Escalates high-urgency tickets to Slack 4. Pauses for human input when stuck or uncertain 5. Logs every decision with full reasoning traces for audit 6. Notifies the team on completion For more complex orchestration patterns where agents coordinate multiple workflows, see Pattern C: Agent as Orchestrator (/agents/deployment-patterns/agent-as-orchestrator). --- ## PATH: Integrations > Slack (Source: integrations/02-slack.md) Slack ===== Post messages to channels, read channel history, and send direct messages from workflows and agents. Available Actions ================= | Action | Description | |--------|-------------| | slackpost | Post a message to a channel | | slackreadchannel | Read recent messages from a channel | | slackdm | Send a direct message to a user | Setup ===== 1. Store OAuth App Credentials ============================== Create a Slack app at api.slack.com/apps (https://api.slack.com/apps) with the required scopes, then store the credentials: ``bash curl -X POST https://apisvr.tryhyphen.com/oauth/slack/app-credentials \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "clientid": "1234567890.1234567890", "clientsecret": "your-slack-client-secret" }' ` 2. Authorize a Workspace ======================== `bash curl "https://apisvr.tryhyphen.com/oauth/slack/authorize?redirect=true" \ -H "X-Org-Id: acme-corp" ` A workspace admin completes the Slack OAuth flow. Hyphen stores the bot token and workspace access. 3. Verify Connection ==================== `bash curl https://apisvr.tryhyphen.com/oauth/connections \ -H "X-Org-Id: acme-corp" ` Using in Workflows ================== Post to Channel =============== `json { "type": "slackpost", "properties": { "oauthaccount_": "acme-workspace", "channel": "#finance-alerts", "text": "🔔 Reconciliation complete: {{matched.length}} matched, {{exceptions.length}} exceptions requiring review." } } ` Read Channel History ==================== `json { "type": "slackreadchannel", "properties": { "oauthaccount_": "acme-workspace", "channel": "#vendor-communications", "limit": 20 } } ` Send Direct Message =================== `json { "type": "slackdm", "properties": { "_oauthaccount_": "acme-workspace", "user": "U0123ABCDEF", "text": "Your expense report for ${{input.amount}} has been approved." } } ` Using as Agent Tools ==================== Agents use Slack to notify teams, read context from channels, and alert on-call personnel: `json { "mode": "react", "objective": "Investigate this alert and notify the team if action is needed", "tools": [ "checksystemstatus", "querylogs", { "type": "action", "name": "slackpost" }, { "type": "action", "name": "slackreadchannel" } ], "maxiterations": 10 } ` Complete Example: Exception Alert Workflow ========================================== Post a summary to Slack when reconciliation finds exceptions, with conditional urgency: `json { "name": "reconciliationwithslackalert", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["invoiceid"], "tolerance": 50, "outputMatched": "matched", "outputUnmatchedLeft": "exceptions" } }, { "type": "slackpost", "filter": { "condition": { "greaterThan": [{ "length": "@exceptions" }, 0] } }, "properties": { "oauthaccount_": "acme-workspace", "channel": "#finance-exceptions", "text": "⚠️ Reconciliation Alert\n\nMatched: {{matched.length}} records\nExceptions: {{exceptions.length}} unmatched invoices\n\nPlease review in the Hyphen dashboard." }, "onFalse": { "type": "slackpost", "properties": { "_oauthaccount__": "acme-workspace", "channel": "#finance-ops", "text": "✅ Reconciliation complete — all {{matched.length}} records matched." } } } ] } } `` --- ## PATH: Platform > Workflow Dsl (Source: platform/02-workflow-dsl.md) Workflow DSL ============ Every Hyphen workflow is a JSON document with a defined structure. This page is the complete specification. Workflow Structure ================== ``json { "name": "workflowname", "definition": { "condition": { }, "actions": [ ], "schedule": { } } } ` Top-Level Fields ================ | Field | Type | Required | Description | |-------|------|----------|-------------| | name | string | Yes | Unique identifier within the organization | | definition | object | Yes | The workflow specification | | definition.condition | object | No | Top-level gate — if false, workflow ends with conditionnotmet | | definition.actions | array | Yes | Ordered array of steps to execute | | definition.schedule | object | No | Scheduling configuration (see Scheduling (/platform/scheduling)) | Step Structure ============== Each element in the actions array is a step: `json { "type": "steptype", "properties": { }, "filter": { }, "onFalse": { }, "outputKey": "resultname" } ` Step Fields =========== | Field | Type | Required | Description | |-------|------|----------|-------------| | type | string | Yes | Primitive name (matcher, loop, PbotApproval, PbotForm, custom-table) or registered action name | | properties | object | Yes | Step-specific configuration — varies by type | | filter | object | No | Condition evaluated before execution. If false, step is skipped (or onFalse runs) | | filter.condition | object | No | The condition expression (see Conditional Logic (/platform/conditional-logic)) | | onFalse | object | No | Alternative step to execute when filter evaluates to false. Same structure as a step | | outputKey | string | No | Custom key name for storing this step's output in context | Step Types ========== Primitives (built-in, no registration required): | Type | Reference | |------|-----------| | matcher | Matcher (/primitives/matcher) | | loop | Loop (/primitives/loop) — foreach or react mode | | PbotApproval | Approval (/primitives/approval) | | PbotForm | Form (/primitives/form) | | custom-table | Custom Table (/primitives/custom-table) | Registered actions — use the actionname you registered as the type: `json { "type": "fetchcustomer", "properties": { "customerid": "@input.id" } } ` OAuth business tools — pre-registered actions available after OAuth setup: `json { "type": "gmailsend", "properties": { "oauthaccount_": "ops@company.com", "to": "@input.email", "subject": "Update" } } ` → See Actions (/actions) for registration details Context Resolution: @path =========================== The @ prefix references values from the execution context. Context accumulates as steps execute — each step's output is added for subsequent steps to reference. | Path | Description | Example | |------|-------------|---------| | @input.field | Workflow input payload | @input.customerid | | @input.field.nested | Nested input fields | @input.invoice.amount | | @outputKey.field | Step output by outputKey | @matched.0.invoiceid | | @item | Current item in a foreach loop | @item.email | | @item.field | Field on current loop item | @item.amount | | @runid | Current run ID | | | @step | Current step index | | | @now | Current timestamp | | | @approved | Boolean from PbotApproval | | | doc:docxxx | Uploaded document content (Document Storage (/platform/document-storage)) | doc:doca1b2c3d4e5f6 | | doc:docxxx@N | Specific document version | doc:doca1b2c3d4e5f6@2 | Array indexing is supported: @matched.0.invoiceid references the first matched record's invoiceid. → Full deep dive: Context Resolution (/platform/context-resolution) Template Syntax: {{ }} ======================== Double braces perform string interpolation inside property values. Use for constructing dynamic strings: `json { "subject": "Invoice {{input.invoiceid}} requires review", "body": "Dear {{input.vendorname}},\n\nYour invoice for ${{input.amount}} is pending." } ` @path returns the actual value (object, array, number). {{template}} performs string interpolation — it converts the value to a string and embeds it in surrounding text. Use @path for data references in properties. Use {{ }} when building human-readable strings. Condition Expressions ===================== Conditions are used in definition.condition (top-level gate) and filter.condition (step-level branching). `json { "condition": { "and": [ { "greaterThan": ["@input.amount", 1000] }, { "equal": ["@input.status", "pending"] } ] } } ` → Complete operator reference: Conditional Logic (/platform/conditional-logic) Schedule Configuration ====================== Optional — makes the workflow run on a recurring basis: `json { "schedule": { "every": "1d", "at": "02:00", "timezone": "America/NewYork" } } ` → Full reference: Scheduling (/platform/scheduling) Complete Example ================ A production workflow combining all DSL features: `json { "name": "dailyinvoicereconciliation", "definition": { "condition": { "and": [ { "equal": ["@input.type", "invoice"] }, { "greaterThan": ["@input.items.length", 0] } ] }, "actions": [ { "type": "matcher", "properties": { "left": "@input.items", "right": "@input.purchaseorders", "matchOn": ["ponumber", "amount"], "tolerance": 50, "outputMatched": "matched", "outputUnmatchedLeft": "unmatcheditems" } }, { "type": "loop", "filter": { "condition": { "greaterThan": ["@unmatcheditems.length", 0] } }, "properties": { "mode": "react", "objective": "Review {{unmatcheditems.length}} unmatched items", "tools": [ { "type": "action", "name": "searchpurchaseorders" }, { "type": "action", "name": "slackpost" } ], "maxiterations": 15, "onstuck": { "iterations": 3, "action": "escalate" }, "resultkey": "reviewresult" } }, { "type": "custom-table", "properties": { "table": "processinglog", "operation": "write", "keys": ["invoiceid"], "values": ["@input.invoiceid"], "fields": { "matchedcount": "@matched.length", "unmatchedcount": "@unmatcheditems.length", "reviewresult": "@reviewresult", "processedat": "@now" } } } ], "schedule": { "every": "1d", "at": "02:00", "timezone": "America/New_York" } } } ` api POST /workflows Create the workflow definition. api POST /workflows/:id/execute Execute with an input payload. The @input.*` paths resolve to the data you send. --- ## PATH: Primitives > Loop (Source: primitives/02-loop.md) Loop ==== The loop primitive has two modes: foreach for deterministic batch processing and react for AI agent reasoning. Both share the same type: "loop" — the mode property determines behavior. Foreach Mode ============ Process an array of items with configurable concurrency and failure handling. ``json { "type": "loop", "properties": { "mode": "foreach", "itemspath": "@input.customers", "itemvariablename": "customer", "actionstoexecute": [ { "type": "gmailsend", "properties": { "_oauthaccount_": "notifications@company.com", "to": "@customer.email", "subject": "Monthly Statement for {{customer.name}}", "body": "Dear {{customer.name}},\n\nPlease find your statement attached." } } ], "maxconcurrency": 10, "failurestrategy": "continueonerror", "collectresults": true, "resultkey": "emailResults" } } ` Foreach Properties ================== | Property | Type | Required | Description | |----------|------|----------|-------------| | mode | "foreach" | Yes | Selects foreach mode | | itemspath | @path / doc: | Yes | Array of items to iterate over. Accepts @path references or doc: uploaded documents (CSV/JSON resolve to arrays) | | itemvariablename | string | Yes | Variable name for the current item (accessible as @{name}) | | actionstoexecute | array | Yes | Steps to run for each item — same structure as workflow actions | | maxconcurrency | number | No | Maximum parallel executions (default: 5, max: 50) | | failurestrategy | string | No | "continueonerror" (default) or "failfast" | | collectresults | boolean | No | Whether to gather results from all iterations (default: true) | | resultkey | string | No | Context key for collected results | Item Access =========== Inside actionstoexecute, reference the current item using the variable name: `json { "itemvariablename": "order", "actionstoexecute": [ { "type": "processpayment", "properties": { "orderid": "@order.id", "amount": "@order.total", "currency": "@order.currency" } } ] } ` Failure Strategies ================== continueonerror (default): Failed items are logged but processing continues. Use for best-effort batch operations like sending notifications. failfast: If any item fails, the loop stops and the run fails. Use when all items must succeed. React Mode ========== Run an AI agent that reasons step-by-step toward an objective. See Agents (/agents) for full details. `json { "type": "loop", "properties": { "mode": "react", "objective": "Investigate this expense report. Check policy compliance, verify receipts, recommend approval or rejection.", "tools": [ { "type": "action", "name": "lookupemployee" }, { "type": "action", "name": "checkexpensepolicy" }, { "type": "action", "name": "verifyreceipt" } ], "model": "gpt-4", "maxiterations": 15, "timeoutms": 300000, "temperature": 0.7, "onstuck": { "action": "escalate", "iterations": 3 }, "includereasoningtrace": true, "resultkey": "expenseDecision" } } ` React Properties ================ | Property | Type | Required | Description | |----------|------|----------|-------------| | mode | "react" | Yes | Selects react mode | | objective | string | Yes | What the agent should accomplish. Supports {{ }} templates | | tools | array | Yes | Available tools — typed declarations or strings (action names). See Tool Declarations (/agents/tool-declarations) | | model | string | No | LLM model to use (default: configured in environment) | | maxiterations | number | No | Maximum think-act-observe cycles (default: 10) | | timeoutms | number | No | Maximum execution time in milliseconds (default: 300000) | | temperature | number | No | LLM temperature 0–1 (default: 0.7) | | onstuck | object | No | Recovery when agent loops without progress | | onstuck.iterations | number | No | Repeated iterations before triggering (default: 3) | | onstuck.action | string | No | "fail", "escalate", or "retrywithhint" | | onstuck.hint | string | No | Guidance text for retrywithhint | | includereasoningtrace | boolean | No | Store full reasoning trace (default: true) | | resultkey | string | No | Context key for the agent's final answer | When to Use Which Mode ====================== | Use Foreach When | Use React When | |-----------------|---------------| | You know exactly what to do with each item | The task requires judgment or reasoning | | Processing is deterministic | The approach depends on intermediate results | | Items are independent of each other | The agent needs to decide what to do next | | You need parallel processing | You need natural language understanding | Nested Loops: Foreach Containing React ====================================== A common pattern: iterate over a list of items, and for each item, run an AI agent: `json { "type": "loop", "properties": { "mode": "foreach", "itemspath": "@unmatchedinvoices", "itemvariablename": "exception", "actionstoexecute": [ { "type": "loop", "properties": { "mode": "react", "objective": "Investigate why invoice {{exception.invoiceid}} for ${{exception.amount}} has no matching payment. Check vendor history, look for partial payments, and recommend next steps.", "tools": [ { "type": "action", "name": "searchpayments" }, { "type": "action", "name": "lookupvendor" } ], "maxiterations": 8, "resultkey": "investigation" } } ], "maxconcurrency": 3, "failurestrategy": "continueonerror", "collectresults": true, "resultkey": "allinvestigations" } } ` This processes each exception in parallel (up to 3 at a time), with an AI agent investigating each one independently. Results are collected into @allinvestigations. Concurrency for nested react loops. When running agents in parallel via foreach, keep maxconcurrency moderate (3–5). Each agent makes LLM API calls, so high concurrency can hit rate limits. Use continueon_error` to ensure one failed investigation doesn't block the others. → Next: Approval (/primitives/approval) --- ## PATH: Sdk > Components (Source: sdk/02-components.md) Components ========== The SDK provides five Web Components. Each renders inside Shadow DOM, isolated from your host page styles. ======================= A sidebar panel showing pending human-in-the-loop tasks from your workflows. Each task displays the workflow name, step context, urgency level, and approve/reject controls. ``html ` Attributes ========== | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | auto-refresh | flag | off | Automatically poll for new tasks | | refresh-interval | ms | 15000 | Polling interval when auto-refresh is on | | show-urgency | flag | off | Display urgency badges on tasks | Events ====== | Event | Data | When | |-------|------|------| | task:decided | { runId, stepId, approved, comment } | User approves or rejects a task | How It Works ============ The sidebar fetches pending tasks scoped to your publishable key. When a user clicks approve or reject, the SDK sends the decision to the gateway, which forwards it to the workflow engine. The workflow resumes from the approval step with the decision in context (@approved, @comment). Connected to workflows. The tasks shown are real PbotApproval steps paused in your workflows. Approving a task in the sidebar resumes the workflow — no additional API call required. ==================== A sortable, paginated data table connected to Hyphen custom tables. Supports inline editing for operations teams. `html ` Attributes ========== | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | table | string | required | The custom table name to display | | page-size | number | 20 | Rows per page | | editable | flag | off | Allow inline row editing (double-click a cell to edit) | | highlight-agent | flag | off | Highlight rows last modified by an AI agent | Events ====== | Event | Data | When | |-------|------|------| | table:updated | { table, rowId, changes } | A row is created or modified | Inline Editing ============== When editable is set, users can double-click any cell to edit its value: - Enter saves the change - Escape cancels the edit - Blur (clicking away) saves automatically Edits are sent as PATCH requests to the gateway, which updates the custom table and logs the change in the audit trail. The authenticated user's identity is attached to every edit. Status Pill Detection ===================== The data grid automatically detects status columns (by column name or known value patterns) and renders values as colored pills: | Status | Color | |--------|-------| | paid, completed, approved | Green | | pending, processing, inprogress | Amber | | overdue, failed, rejected | Red | =================== A document management surface with upload capabilities. Shows existing documents for the configured workflow, their processing status, and provides a compact upload bar for new files. `html ` Attributes ========== | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | workflow-id | string | -- | Workflow to trigger on upload | | accept | string | * | Accepted file types (MIME or extensions) | | max-size | bytes | 52428800 | Max file size (default 50 MB) | | multiple | flag | on | Allow multiple files | Events ====== | Event | Data | When | |-------|------|------| | run:status | { runId, status, documentId } | A triggered workflow run changes status | How It Works ============ 1. User uploads a file via the upload bar 2. The SDK sends the file to the document storage API 3. If workflow-id is set, the SDK triggers the workflow with the document reference as input 4. The feed shows the document's processing status in real time (queued, processing, completed, failed) Documents display with file type icons (PDF, CSV, XLSX) and their trigger status. Each document links to its associated workflow run. ===================== An OTP-based login modal for pre-registered team members. Handles the full email + 8-digit code verification flow. `html ` The auth modal is not placed in HTML directly — it's triggered programmatically via sdk.login(). The modal renders as a fullscreen overlay with three steps: 1. Email entry — user enters their registered email address 2. OTP verification — user enters the 8-digit code sent to their email 3. Success confirmation — brief confirmation before the modal auto-closes Events ====== | Event | Data | When | |-------|------|------| | authenticated | { email, sessionId } | OTP verification succeeds (bubbles, composed) | Session Upgrade =============== On successful authentication, the SDK upgrades the session from anonymous to authenticated. All components automatically reflect user-level permissions — the task sidebar may show additional tasks, and the data grid may enable editing that was previously restricted. Users must be pre-registered. The SDK does not support self-registration. An org admin must add users to the publishable key's allowed user list before they can authenticate. ======================== A full agent launch, monitoring, and results console. Lets operations teams run ReAct agents by writing an objective, selecting tools, and watching the reasoning trace in real time. `html ` Attributes ========== | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | max-iterations | number | 10 | Default maximum iterations for the agent | | show-trace | flag | on | Show the reasoning trace timeline | | poll-interval | ms | 3000 | Status polling interval during monitoring | | available-tools | JSON | -- | Pre-select tools by ID (JSON array of strings) | | accept | string | .pdf,.csv,.json,.xlsx,.txt | Accepted file types for attachments | | max-file-size | number | 50 | Max attachment size in MB | | previous-run-id | string | -- | Reference a prior agent run to inject its context into the new run | Events ====== | Event | Data | When | |-------|------|------| | agent-launched | { agentRunId, objective, tools } | Agent execution starts | | agent-completed | { agentRunId, finalAnswer, confidence } | Agent finishes successfully | | agent-failed | { agentRunId, error } | Agent fails or is cancelled | Three Modes =========== Launch — The user writes an objective, optionally attaches files (PDF, CSV, etc.), selects tools from registered actions and workflows, and configures max iterations. Clicking "Launch Agent" uploads any files and executes the agent asynchronously. Monitor — A vertical animated timeline shows each reasoning step as it happens: - Completed steps display with a checkmark and the action name - The active step shows an animated spinner - Click any step to expand the full thought and observation The monitor also handles paused agents — when the agent requests human input, a resume section appears with approve/reject controls. Result — Shows the final answer, confidence score, any triggered workflow runs, and the full reasoning trace. If the run used previous-run-id, the result view displays the linked prior run. A "New Agent" button resets back to launch mode. Cross-Run Context ================= Agents can reference a prior run's outcome. Set previous-run-id to a completed agent run ID, and the new agent receives the prior run's objective, final answer, status, and a condensed reasoning trace as context in its system prompt. This enables multi-stage workflows where one agent's conclusion feeds into the next — without re-executing the prior work. The engine validates org ownership of the referenced run, so cross-org references are rejected. `html ` Or via the API: `json { "objective": "Review the findings from the prior audit and recommend next steps", "tools": ["actreviewer"], "previousrunid": "agent-abc123" } ` How It Works ============ The agent console uses the same scoped authentication as all SDK components. Tools available in the picker are filtered to the publishable key's allowedactions and allowedworkflows. The gateway validates tool access before proxying to the execution engine. Scoped to permissions. The tools shown in the picker and the agents a user can launch are governed by the publishable key scope. Users only see and can use tools they're authorized for. Combining Components ==================== Components work together through the SDK's event system. When a document is uploaded via , the triggered workflow may create approval tasks that appear in . Approved tasks may update rows in . `html
` All components share the same SDK instance and session. Events from one component are visible to others via sdk.on()`. --- ## PATH: Templates > Insurance Claims Adjudication (Source: templates/02-insurance-claims-adjudication.md) Insurance Claims Adjudication ============================= Claim-to-policy matching, coverage verification, and graduated adjudication. The insurance industry processes billions of claims annually — the majority through manual review queues that this pipeline replaces. What Gets Automated =================== Matching claims to policies on policy number, member ID, and date of service. Auto-adjudicating clean claims that fall within coverage limits and have standard procedure codes. AI analysis of coding ambiguities, coverage edge cases, and multi-provider claims. What Humans Still Own ===================== Complex multi-party claims. Fraud referrals. Policy exception decisions. Appeal adjudication on contested denials. Pipeline ======== ``mermaid flowchart TD A[Input: New Claims + Policy Records] --> B[Matcher: Claim-to-Policy] B -->|Policy found| C[Matcher: Duplicate Detection] B -->|No policy match| D[Deny: No Coverage] C -->|No duplicate| E{Auto-Approve Eligible?} C -->|Duplicate found| F[Flag + Halt] E -->|Clean claim ~60%| G[Auto-Adjudicate] E -->|Requires review ~40%| H[ReAct Agent] H --> H1[Verify coverage terms] H1 --> H2[Validate procedure codes] H2 --> H3[Calculate allowed amount] H3 -->|High confidence| I[Auto-Resolve] H3 -->|Low confidence| J[Human Adjudicator] J -->|Approve| K[Process Payment] J -->|Deny| L[Issue Denial Code] J -->|Refer SIU| M[Flag for Investigation] D --> N[Custom Table: Adjudication Log] G --> N I --> N K --> N L --> N F --> N style B fill:#e8a84c,color:#09090b,stroke:none style C fill:#e8a84c,color:#09090b,stroke:none style H fill:#4ade80,color:#09090b,stroke:none style J fill:#60a5fa,color:#09090b,stroke:none ` This template uses two matcher steps in sequence — first for claim-to-policy matching, then for duplicate detection. This is the only template with chained matchers. Workflow Definition =================== `json { "name": "claimsadjudication", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.claims", "right": "@input.policies", "matchOn": ["policynumber", "memberid"], "dateWindowDays": 0, "outputMatched": "claimswithpolicy", "outputUnmatchedLeft": "nopolicyclaims", "outputUnmatchedRight": "unusedpolicies" } }, { "type": "matcher", "filter": { "condition": { "greaterThan": [{ "length": "@claimswithpolicy" }, 0] } }, "properties": { "left": "@claimswithpolicy", "right": "@input.recentclaims", "matchOn": ["memberid", "procedurecode", "servicedate"], "tolerance": 0, "dateWindowDays": 1, "outputMatched": "potentialduplicates", "outputUnmatchedLeft": "uniqueclaims", "outputUnmatchedRight": "nodupmatch" } }, { "type": "loop", "filter": { "condition": { "greaterThan": [{ "length": "@uniqueclaims" }, 0] } }, "properties": { "mode": "foreach", "itemspath": "@uniqueclaims", "itemvariablename": "claim", "actionstoexecute": [ { "type": "loop", "properties": { "mode": "react", "objective": "Adjudicate claim {{claim.a.claimid}}. Policy: {{claim.b.policynumber}}. Procedure: {{claim.a.procedurecode}}. Billed: ${{claim.a.billedamount}}. Verify: 1) Coverage active for service date, 2) Procedure covered under plan, 3) Deductible and benefit limits, 4) In-network status. Recommend: approve with allowed amount, deny with reason code, or escalate.", "tools": [ { "type": "action", "name": "verifycoverage" }, { "type": "action", "name": "lookupprocedurecodes" }, { "type": "action", "name": "checkbenefitlimits" }, { "type": "action", "name": "calculateallowedamount" } ], "maxiterations": 12, "onstuck": { "iterations": 4, "action": "escalate" }, "resultkey": "adjudication" } } ], "maxconcurrency": 10, "failurestrategy": "continueonerror", "collectresults": true, "resultkey": "alladjudications" } }, { "type": "PbotApproval", "properties": { "comment": "{{uniqueclaims.length}} claims adjudicated. {{potentialduplicates.length}} duplicates flagged. Review AI decisions.", "requestpayload": { "adjudications": "@alladjudications", "duplicates": "@potentialduplicates", "nopolicydenials": "@nopolicyclaims.length" } } }, { "type": "custom-table", "properties": { "table": "adjudicationlog", "operation": "write", "keys": ["batchid"], "values": ["@runid"], "fields": { "claimsprocessed": "@input.claims.length", "autodeniednopolicy": "@nopolicyclaims.length", "duplicatesflagged": "@potentialduplicates.length", "adjudicated": "@uniqueclaims.length", "completedat": "@now" } } } ] } } ` Required Registered Actions =========================== | Action | Kind | Purpose | |--------|------|---------| | verifycoverage | http | Check policy status and effective dates | | lookupprocedurecodes | db | Retrieve allowed procedure codes for policy type | | checkbenefitlimits | db | Query remaining benefit limits for member/year | | calculateallowed_amount | http | Compute allowed amount per fee schedule | Customization Notes =================== Duplicate detection window. The 1-day dateWindowDays on the second matcher catches same-day duplicate submissions. Extend to 3–5 days if your intake has batch delays. Agent concurrency. 10 concurrent adjudications works for typical batch sizes. Reduce if your coverage verification API has rate limits. Stuck detection. The escalate` action on stuck sends complex claims directly to a human adjudicator rather than retrying — appropriate for claims where incorrect auto-decisions have financial and regulatory consequences. --- ## PATH: Actions > Db (Source: actions/03-db.md) DB Actions ========== DB actions execute queries against your databases. Connection strings are stored encrypted in org config (/platform/multi-tenancy) and referenced via orgconfig: prefix. Registration ============ ``bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "actionname": "getpendingorders", "kind": "db", "description": "Fetch pending orders from the warehouse database", "datasource": "orgconfig:db:warehousepg", "query": "SELECT FROM orders WHERE status = $1 AND createdat > $2", "params": ["pending", "@input.sincedate"], "passthrough": true, "outputKey": "orders" }' ` Properties ========== | Property | Type | Required | Description | |----------|------|----------|-------------| | actionname | string | Yes | Unique name for this action | | kind | "db" | Yes | | | datasource | string | Yes | Connection string, typically orgconfig:db:keyname | | query | string | Yes | SQL query or database command. Uses $1, $2, etc. for parameters | | params | array | No | Ordered parameters for the query. Supports @path and literal values | | passthrough | boolean | No | If true, raw query results are passed to context | | outputKey | string | No | Context key for query results | Parameterized Queries ===================== Always use parameterized queries ($1, $2, etc.) instead of string interpolation for security: `json { "query": "SELECT FROM customers WHERE region = $1 AND annualrevenue > $2", "params": ["@input.region", "@input.minrevenue"] } ` Parameters can be literal values or @path references: `json { "query": "UPDATE invoices SET status = $1 WHERE invoiceid = $2", "params": ["reconciled", "@matched.0.invoiceid"] } ` Datasource Configuration ======================== Store connection strings in org config: `bash PostgreSQL ========== curl -X POST https://apisvr.tryhyphen.com/org-config \ -H "X-Org-Id: acme-corp" \ -d '{ "key": "db:warehousepg", "value": "postgresql://user:pass@host:5432/warehouse" }' MySQL ===== curl -X POST https://apisvr.tryhyphen.com/org-config \ -H "X-Org-Id: acme-corp" \ -d '{ "key": "db:crmmysql", "value": "mysql://user:pass@host:3306/crm" }' ` Then reference in the action: `json { "datasource": "orgconfig:db:warehousepg" } ` The connection string is resolved at execution time and never stored in the action definition. Usage in a Workflow =================== `json { "type": "getpendingorders", "properties": { "sincedate": "@input.startdate" }, "outputKey": "orders" } ` Query results are stored at @orders — an array of row objects. Usage as an Agent Tool ====================== `json { "mode": "react", "objective": "Find all overdue invoices and summarize by vendor", "tools": [{ "type": "action", "name": "getpendingorders" }, { "type": "action", "name": "getvendor_info" }] } ` The agent can query the database to gather information during its reasoning process. Results are returned as observations the agent can analyze. Supported Databases =================== | Database | Datasource Format | |----------|-------------------| | PostgreSQL | postgresql://user:pass@host:5432/dbname | | MySQL | mysql://user:pass@host:3306/dbname | | MongoDB | mongodb://user:pass@host:27017/dbname | | Neo4j | neo4j://user:pass@host:7687` | Security. DB actions execute queries with the permissions of the connection string's user. Use database users with the minimum necessary privileges — read-only users for query actions, limited write access for mutation actions. Never use admin credentials. → Next: Matcher Actions (/actions/matcher) --- ## PATH: Agents > Custom Tools (Source: agents/03-custom-tools.md) Tool Declarations ================= Tools give ReAct agents the ability to take actions. Hyphen uses a typed tool declaration system — you declare what tools are available, and the resolver handles the rest. The Two Tool Types ================== Action Tools ============ Reference any registered action by name. The resolver automatically fetches the action's description, parameters, and kind from the database. ``json { "type": "action", "name": "classifydocument" } ` That's it. The resolver looks up classifydocument in your registered actions, builds parameter hints from its kind and properties, and presents a fully enriched tool definition to the LLM. You don't need to repeat the description or parameters. Workflow Tools ============== Reference a workflow by ID. The agent can trigger it during execution via _runworkflow_. `json { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" } ` The resolver fetches the workflow's name and description, then presents it to the LLM by its human-readable name. When the agent invokes the tool, Hyphen executes the workflow as a sub-process. Complete Example ================ An agent that researches companies, enriches data through a pipeline, and sends outreach: `json { "type": "loop", "properties": { "mode": "react", "objective": "Research {{input.companyname}} and draft an outreach email", "tools": [ { "type": "action", "name": "searchcompany" }, { "type": "action", "name": "searchnews" }, { "type": "action", "name": "gmailsend" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" } ], "model": "gpt-4", "maxiterations": 10, "onstuck": { "iterations": 3, "action": "escalate" }, "resultkey": "agentResult" } } ` The agent gets four tools (searchcompany, searchnews, gmailsend, and the enrichment workflow) plus all implicit tools — without any inline descriptions or parameter specs. Implicit Tools (Always Available) ================================= These tools are automatically injected into every ReAct agent. You never need to declare them: | Tool | Purpose | |------|---------| | complete | Signal task completion with answer and confidence | | pauseforhuman | Pause and request human input | | storememory | Store data for later retrieval within the session | | retrievememory | Retrieve previously stored data | | logprogress_ | Record milestones for observability | See Built-in Tools (/agents/built-in-tools) for the full parameter reference. Tool Resolution Order ===================== The resolver processes the tools array in this order: 1. Typed objects ({ type: "action" | "workflow" }) — preferred format. Resolved via batched DB queries (max 2 queries regardless of tool count). 2. Legacy strings ("actionname") — still supported. Treated as { type: "action", name: "..." } internally. 3. Legacy inline objects ({ name, action, description, parameters }) — still supported. Routed as custom tool definitions with full parameter specs. 4. Implicit tools — always auto-injected after all declared tools are resolved. Unresolved actions (not found in registered actions or built-in OAuth actions) are still added to the tool set — the agent receives feedback if it tries to use an unknown tool. Structural Permissioning ======================== The tools array is an architectural constraint, not a policy layer. The agent cannot discover, invent, or access capabilities beyond what you've declared: `json { "tools": [ { "type": "action", "name": "blockip" }, { "type": "action", "name": "isolatehost" } ] } ` This agent can block IPs and isolate hosts. It cannot delete firewall rules, shut down servers, or take any other action — even if those actions are registered in your org. The tool list defines the boundary. This is what makes governed autonomy possible. The agent reasons freely within the boundary. The boundary itself is structural. Legacy Formats (Backward Compatible) ==================================== String References ================= The simplest legacy format. Still fully supported and will remain so indefinitely: `json { "tools": ["lookupcustomer", "gmailsend"] } ` Internally converted to { type: "action", name: "lookupcustomer" }. Inline Object Definitions ========================= Full control over what the agent sees. Still supported for backward compatibility, but not recommended for new workflows: `json { "tools": [ { "name": "calculatediscount", "action": "discountcalculator", "description": "Calculate discount based on customer tier and order value", "parameters": { "customertier": { "type": "string", "required": true, "description": "Customer tier: bronze, silver, gold, platinum" }, "ordervalue": { "type": "number", "required": true, "description": "Total order value in USD" } } } ] } ` | Field | Type | Required | Description | |-------|------|----------|-------------| | name | string | Yes | Tool name the agent uses in the action field | | action | string | Yes | Registered action this tool maps to | | description | string | Yes | What the tool does — shown to the agent in its prompt | | parameters | object | Yes | Parameter definitions with type, required, description, enum | The inline object format may be deprecated in a future version. New workflows should use typed declarations exclusively. Mixing Formats ============== All three formats can coexist in the same tools array: `json { "tools": [ { "type": "action", "name": "searchcompany" }, "gmailsend", { "name": "customscorer", "action": "leadscoringapi", "description": "Score a lead based on engagement data", "parameters": { "lead_id": { "type": "string", "required": true } } } ] } ` The resolver handles each format appropriately. However, for consistency and maintainability, prefer typed declarations for all new work. When to Use Each Format ======================= | Format | When to Use | |--------|-------------| | Typed action { type: "action" } | Default for all new workflows. Let the resolver pull metadata from the action registration. | | Typed workflow { type: "workflow" } | When the agent needs to trigger sub-workflows (Pattern B or C). | | Legacy string "name" | Quick prototyping, or when migrating existing workflows that already use strings. | | Legacy inline object | Only when you need to override the registered action's description or expose a subset of parameters to the agent. | Tips for Effective Tool Sets ============================ Fewer tools = better agent performance. Agents reason more effectively with 3–7 tools than with 15. Scope the tool set to what the specific task requires. Action descriptions matter. The resolver pulls descriptions from your registered actions. Write good action descriptions at registration time, and every agent that references the action benefits. Use workflow tools for complex sub-processes. Instead of giving the agent 10 granular tools, consider wrapping related steps into a workflow and giving the agent one workflow tool. Test with reasoning traces. After executing an agent, inspect the reasoning trace at GET /agents/:id/trace`. If the agent is misusing tools, the trace shows exactly where the confusion occurs — usually a description problem. → Next: Stuck Detection (/agents/stuck-detection) --- ## PATH: Agents > Deployment Patterns > Agent As Orchestrator (Source: agents/deployment-patterns/03-agent-as-orchestrator.md) Pattern C: Agent as Orchestrator ================================ The agent coordinates multiple workflows, making decisions about what to trigger, in what order, and how to synthesize results across them. The agent is the decision-maker; each workflow is a capability it can invoke. Unlike Pattern B (which classifies and dispatches once), the orchestrator waits for results, reasons about them, and decides what to do next. ``mermaid flowchart TD Agent["Orchestrator Agent"] -->|"1. verify identity"| KYC["Identity Verification
Workflow"] KYC -->|"result: verified"| Agent Agent -->|"2. screen sanctions"| Sanctions["Sanctions Screening
Workflow"] Sanctions -->|"result: partial match"| Agent Agent -->|"3. conflict - escalate"| Human["Human Review"] Human -->|"decision: clear"| Agent Agent -->|"4. provision account"| Account["Account Provisioning
Workflow"] Account -->|"result: provisioned"| Agent Agent -->|"5. complete with full trace"| Done["complete"] style Agent fill:#f3e8ff,stroke:#9333ea style Human fill:#dbeafe,stroke:#3b82f6 ` When to Use =========== - The process requires dynamic coordination across multiple sub-processes - The sequence of steps depends on intermediate results (not predetermined) - You need to synthesize information from multiple workflows before deciding the next step - The agent makes judgment calls -- not just routing, but resolving conflicts and escalating Pattern C vs Pattern B Pattern B = classify, dispatch to one workflow, done. The agent is a router. Pattern C = trigger workflow, wait for result, trigger another workflow based on that result, synthesize, escalate if conflicted, trigger more workflows, complete. The agent is a coordinator making ongoing decisions. The telltale sign of Pattern C: the agent calls wait: true on workflows and its reasoning trace shows conditional branching based on workflow results. How It Differs From Pattern B ============================= | | Pattern B: Trigger | Pattern C: Orchestrator | |---|---|---| | Core question | "Which workflow should handle this?" | "How do I coordinate these workflows to complete a complex process?" | | Workflow calls | One, fire-and-forget | Multiple, sequential or conditional | | Uses wait: true? | No -- dispatches and completes | Yes -- needs results to make next decision | | Result synthesis | None -- delegates entirely | Combines results across workflows, resolves conflicts | | Human escalation | Rare -- classification is usually clear | Common -- conflicts between workflow results need judgment | | Typical iterations | 3-5 | 10-20 | | Iteration budget | maxiterations: 8 | maxiterations: 20 | | Timeout | 60s | 300-600s (waiting for workflow completions) | Tool Declaration ================ Pattern C agents need workflow tools declared with wait: true semantics. Since the orchestrator coordinates multiple workflows and may also use actions for direct lookups, the tools array typically includes both: `json { "tools": [ { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Verify identity against government databases and trade registers" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Screen against OFAC, EU, UN, and UK sanctions lists" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Enhanced due diligence -- deep background check for flagged entities" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Provision customer account, set up billing, assign account manager" }, { "type": "action", "name": "gmailsend" } ] } ` Implicit tools (complete, pauseforhuman, storememory, retrievememory, logprogress_) are auto-injected. You don't need to declare them. Why workflows dominate Pattern C tools In Pattern B, the tools array is action-heavy (classification tools) with a few workflow targets. In Pattern C, it's workflow-heavy -- the orchestrator's primary capability is triggering and coordinating sub-processes. Actions are secondary, used for direct communication (email, Slack) or lookups between workflow calls. Complete Example: Customer Onboarding ===================================== An orchestrator agent that onboards a business customer -- running identity verification, sanctions screening, conditionally triggering enhanced due diligence, and escalating to a human when signals conflict. Agent Configuration =================== `json { "objective": "Onboard business customer: {{input.companyname}}\n\nCustomer details:\n- Company: {{input.companyname}}\n- Country: {{input.country}}\n- Registration: {{input.registrationnumber}}\n- Contact: {{input.contactname}} ({{input.contactemail}})\n\nProcess:\n1. Run identity verification\n2. Run sanctions screening\n3. If any concerns, run enhanced due diligence\n4. If still uncertain, request human review\n5. If approved, provision the account\n6. Send welcome email to contact", "tools": [ { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Verify company identity -- checks trade registers, document authenticity, address verification" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Screen against global sanctions lists (OFAC, EU, UN, UK). Returns: clear, partialmatch, or hit" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Enhanced due diligence -- deep background check, adverse media, PEP screening" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000", "description": "Provision customer account -- create org, set up billing, assign account manager" }, { "type": "action", "name": "gmailsend" } ], "config": { "model": "gpt-4", "maxiterations": 20, "timeoutms": 600000, "onstuck": { "iterations": 3, "action": "escalate" } } } ` Execute via Standalone Agent API ================================ `bash curl -X POST https://apisvr.tryhyphen.com/agents/execute \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "objective": "Onboard business customer: Acme GmbH...", "tools": [ { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" }, { "type": "workflow", "id": "wfl-123e4567-e89b-12d3-a456-426614174000" }, { "type": "action", "name": "gmailsend" } ], "config": { "model": "gpt-4", "maxiterations": 20, "timeoutms": 600000 } }' ` Sample Reasoning Trace ====================== Notice the long trace with conditional branching -- the agent waits for each workflow result and adapts its plan based on what comes back. ` Iteration 1: Thought: "Starting onboarding for Acme GmbH. First step is identity verification." Action: _logprogress_ Input: { "message": "Starting onboarding", "milestone": "begin" } Iteration 2: Thought: "Running identity verification." Action: identityverification <-- resolved from wfkyc001 Input: { "companyname": "Acme GmbH", "country": "DE", "registrationnumber": "HRB 12345", "wait": true, <-- orchestrator waits for result "timeoutms": 120000 } Observation: { "status": "completed", "result": { "verified": true, "confidence": 0.92, "source": "traderegister" } } Iteration 3: Thought: "Identity verified (92% confidence). Saving this and moving to sanctions." Action: _storememory_ Input: { "key": "kycresult", "value": { "verified": true, "confidence": 0.92 } } Iteration 4: Thought: "Now running sanctions screening." Action: sanctionsscreening <-- resolved from wfsan002 Input: { "companyname": "Acme GmbH", "country": "DE", "directors": ["Hans Mueller", "Maria Schmidt"], "wait": true, "timeoutms": 120000 } Observation: { "status": "completed", "result": { "overall": "partialmatch", "matches": [{ "name": "Hans Mueller", "list": "EU", "score": 0.72, "note": "Similar name on EU consolidated list" }] } } Iteration 5: Thought: "Partial sanctions match -- 72% similarity on Hans Mueller. Identity passed but sanctions flagged. This is a conflict. I need enhanced due diligence before deciding." Action: _logprogress_ Input: { "message": "Conflict: KYC pass but sanctions partial match. Running EDD.", "milestone": "conflictdetected" } Iteration 6: Thought: "Running enhanced due diligence to resolve the conflict." Action: enhancedduediligence <-- resolved from wfedd003 Input: { "companyname": "Acme GmbH", "flaggedindividual": "Hans Mueller", "flagreason": "72% name match on EU consolidated list", "wait": true, "timeoutms": 180000 } Observation: { "status": "completed", "result": { "conclusion": "likelyfalsepositive", "confidence": 0.78, "reasoning": "Different date of birth, different nationality from listed entity" } } Iteration 7: Thought: "EDD says likely false positive but only 78% confident. That's below my threshold. I should pause for human compliance review." Action: _pauseforhuman Input: { "question": "Sanctions screening flagged Hans Mueller (72% name match, EU list). EDD concludes likely false positive (78% confidence). Should we proceed with onboarding?", "options": ["Approve -- proceed with onboarding", "Reject -- deny application", "Request additional documentation"], "contextsummary": "KYC: verified (92%). Sanctions: partial match on director. EDD: likely false positive (78%).", "urgency": "medium" } --- AGENT PAUSED -- waiting for human review --- Human response: "Approve -- proceed with onboarding. Add monitoring note." Iteration 8: Thought: "Human approved. Provisioning the account with a monitoring note." Action: accountprovisioning <-- resolved from wfacc004 Input: { "companyname": "Acme GmbH", "country": "DE", "contactemail": "hans@acme.de", "risknotes": "Sanctions false positive cleared by compliance (2026-02-08)", "monitoringlevel": "enhanced", "wait": true, "timeoutms": 60000 } Observation: { "status": "completed", "result": { "accountid": "acctxyz789", "status": "active" } } Iteration 9: Thought: "Account provisioned. Sending welcome email." Action: gmailsend Input: { "to": "hans@acme.de", "subject": "Welcome to Hyphen -- Your Account is Ready", "body": "Dear Hans,\n\nYour Acme GmbH account has been provisioned..." } Iteration 10: Thought: "Onboarding complete. All verification passed, account provisioned." Action: complete Input: { "answer": "Acme GmbH onboarded successfully. Identity verified, sanctions false positive cleared by compliance, account provisioned (acctxyz789). Enhanced monitoring applied per compliance decision.", "confidence": 0.95, "summary": "KYC: pass | Sanctions: cleared (human review) | EDD: false positive | Account: active" } ` Ten iterations. The agent ran four workflows sequentially, detected a conflict between KYC and sanctions results, ran additional due diligence, escalated to a human when confidence was insufficient, then completed provisioning after approval. That's the Pattern C signature -- ongoing decision-making across multiple workflow results. Key Properties ============== - wait: true on workflow triggers -- the orchestrator needs results to make the next decision. This is the defining characteristic vs Pattern B - Result synthesis -- the agent compares results across workflows (KYC pass + sanctions flag = conflict) and decides what to do - Conditional workflow triggering -- EDD only runs because sanctions flagged. The sequence is dynamic, not predetermined - Human escalation with full context -- when the agent pauses, the human sees the complete chain of reasoning and results - Higher iteration budget -- maxiterations: 20 accounts for the multi-workflow coordination. Each workflow call + reasoning takes ~2 iterations - Longer timeout -- timeoutms: 600000 (10 minutes) because the agent is waiting for workflow completions Pattern C vs Building It as a Deterministic Workflow ==================================================== You could encode this same process as a deterministic workflow with conditional branches. When should you use Pattern C instead? Use Pattern C when: - The process has too many conditional paths to enumerate upfront - You need natural language judgment ("is this a false positive?") between steps - The order of operations depends on results (not just branching on a value) - You want the agent to explain its reasoning for audit purposes Use a deterministic workflow when: - The process is well-defined with clear branching rules - Every conditional can be expressed as a comparison operator - You don't need natural language reasoning between steps - Volume is high enough that LLM cost per run matters In practice, many teams start with Pattern C for new processes (letting the agent figure out the logic), then graduate to deterministic workflows once the process stabilizes and the branching rules are well-understood. Governance Model ================ Pattern C agents have the highest autonomy level, which means governance is most important: - Structural permissioning -- the agent can only trigger workflows and actions declared in its tools array. It cannot discover or invoke other workflows in your org - Reasoning traces -- every iteration is captured: thought, action, input, observation. The full chain of reasoning is queryable and auditable - Stuck detection -- if the agent loops without progress for 3 iterations, it escalates to a human rather than burning through iterations - Checkpoint/resume -- when the agent pauses for human input, its full state is persisted. The human's decision becomes part of the trace - Timeout enforcement -- timeout_ms` prevents runaway orchestration. If workflows take longer than expected, the agent fails gracefully Back to: Deployment Patterns Overview (/agents/deployment-patterns) --- ## PATH: Getting Started > Your First Workflow (Source: getting-started/03-your-first-workflow.md) Your First Workflow =================== Build a working workflow from scratch: register an action, create a matcher with conditional branching, execute it with sample data, add human approval, and read the final output. Prerequisites ============= A running Hyphen instance at https://apisvr.tryhyphen.com. All examples use X-Org-Id: tutorial-org. Step 1: Register a Mock HTTP Action =================================== Before building the workflow, register an action the workflow can call. This simulates updating an ERP system: ``bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: tutorial-org" \ -H "Content-Type: application/json" \ -d '{ "actionname": "updateerpstatus", "kind": "http", "description": "Mark a record as reconciled in the ERP", "url": "https://httpbin.org/post", "httpmethod": "POST", "passthrough": true }' ` We're using httpbin.org as a mock endpoint. In production, this would be your actual ERP API. Verify it was registered: `bash curl https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: tutorial-org" ` Step 2: Create the Workflow =========================== Create a workflow that matches invoices to payments and conditionally processes the results: `bash curl -X POST https://apisvr.tryhyphen.com/workflows \ -H "X-Org-Id: tutorial-org" \ -H "Content-Type: application/json" \ -d '{ "name": "firstworkflow", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["invoiceid"], "tolerance": 0.05, "outputMatched": "matched", "outputUnmatchedLeft": "unmatchedinvoices", "outputUnmatchedRight": "unmatchedpayments" } }, { "type": "updateerpstatus", "filter": { "condition": { "greaterThan": [{ "length": "@matched" }, 0] } }, "properties": { "records": "@matched", "action": "markreconciled" } } ] } }' ` Save the returned workflow ID — you'll need it to execute. What this does: Takes two datasets (invoices and payments), matches them on invoiceid with 5% amount tolerance, then calls the updateerpstatus action if any matches were found. Step 3: Execute with Sample Data ================================ `bash curl -X POST https://apisvr.tryhyphen.com/workflows/WORKFLOWID/execute \ -H "X-Org-Id: tutorial-org" \ -H "Content-Type: application/json" \ -d '{ "input": { "invoices": [ { "invoiceid": "INV-001", "vendor": "Acme Corp", "amount": 1000.00 }, { "invoiceid": "INV-002", "vendor": "Beta LLC", "amount": 2500.00 }, { "invoiceid": "INV-003", "vendor": "Gamma Inc", "amount": 500.00 } ], "payments": [ { "invoiceid": "INV-001", "vendor": "Acme Corp", "amount": 1000.00 }, { "invoiceid": "INV-002", "vendor": "Beta LLC", "amount": 2480.00 } ] } }' ` Replace WORKFLOWID with the ID returned in Step 2. Step 4: Read the Run Output =========================== `bash curl https://apisvr.tryhyphen.com/runs/RUNID/status \ -H "X-Org-Id: tutorial-org" ` Expected output: `json { "status": "completed", "context": { "matched": [ { "a": { "invoiceid": "INV-001", "amount": 1000.00 }, "b": { "invoiceid": "INV-001", "amount": 1000.00 } }, { "a": { "invoiceid": "INV-002", "amount": 2500.00 }, "b": { "invoiceid": "INV-002", "amount": 2480.00 } } ], "unmatchedinvoices": [ { "invoiceid": "INV-003", "vendor": "Gamma Inc", "amount": 500.00 } ], "unmatchedpayments": [] } } ` INV-001 matched exactly. INV-002 matched within the 5% tolerance ($2500 vs $2480 = 0.8% difference). INV-003 had no matching payment, so it appears in unmatchedinvoices. Step 5: Add a PbotApproval Step =============================== Update the workflow to require human approval when there are unmatched invoices: `bash curl -X PUT https://apisvr.tryhyphen.com/workflows/WORKFLOWID \ -H "X-Org-Id: tutorial-org" \ -H "Content-Type: application/json" \ -d '{ "name": "firstworkflowv2", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["invoiceid"], "tolerance": 0.05, "outputMatched": "matched", "outputUnmatchedLeft": "unmatchedinvoices", "outputUnmatchedRight": "unmatchedpayments" } }, { "type": "updateerpstatus", "filter": { "condition": { "greaterThan": [{ "length": "@matched" }, 0] } }, "properties": { "records": "@matched", "action": "markreconciled" } }, { "type": "PbotApproval", "filter": { "condition": { "greaterThan": [{ "length": "@unmatchedinvoices" }, 0] } }, "properties": { "comment": "{{unmatchedinvoices.length}} invoices have no matching payment. Review and decide.", "requestpayload": { "unmatched": "@unmatchedinvoices", "matchedcount": "@matched.length" } } } ] } }' ` Execute it again with the same data. This time the run will pause at the approval step. Step 6: Submit Approval ======================= Check the run status — it should show paused: `bash curl https://apisvr.tryhyphen.com/runs/RUNID/status \ -H "X-Org-Id: tutorial-org" ` Submit the approval: `bash curl -X POST https://apisvr.tryhyphen.com/approvals/RUNID/2 \ -H "X-Org-Id: tutorial-org" \ -H "Content-Type: application/json" \ -d '{ "approved": true, "comment": "INV-003 is a known timing issue. Will resolve next cycle." }' ` The /2 in the URL refers to step index 2 (the third step, zero-indexed). Step 7: Check Final State ========================= `bash curl https://apisvr.tryhyphen.com/runs/RUNID/status \ -H "X-Org-Id: tutorial-org" ` The run should now show completed with the approval decision captured: `json { "status": "completed", "context": { "matched": [ ... ], "unmatchedinvoices": [ ... ], "approved": true, "comment": "INV-003 is a known timing issue." } } ` What You Built ============== A workflow that: 1. Matches invoices to payments on invoice_id` with 5% amount tolerance 2. Calls an external action to mark matched records as reconciled 3. Pauses for human review when unmatched invoices exist 4. Captures the reviewer's decision as part of the permanent audit trail Next: Your First Agent (/getting-started/your-first-agent) — add an AI agent that investigates the unmatched exceptions. --- ## PATH: Guides > Human In The Loop (Source: guides/03-human-in-the-loop.md) Human-in-the-Loop ================= Hyphen supports two human-control patterns: 1. PbotApproval for deterministic workflow checkpoints 2. _pauseforhuman for agent-driven runtime escalation Base URL used below: https://apisvr.tryhyphen.com Workflow Approvals (PbotApproval) =================================== Use PbotApproval when the review gate is known at design time. ``json { "type": "PbotApproval", "properties": { "comment": "Review expense report for ${{input.amount}}", "requestpayload": { "expenseid": "@input.expenseid", "amount": "@input.amount", "category": "@input.category" } } } ` When reached, run status becomes paused. Submit decision =============== `bash curl -X POST https://apisvr.tryhyphen.com/approvals/:runId/:stepId \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "approved": true, "comment": "Verified receipts, approved." }' ` Decision contract: `json { "approved": true, "comment": "optional" } ` Context added after decision: - @approved (boolean) - @comment (string, when provided) Branching after approval ======================== `json { "type": "processpayment", "filter": { "condition": { "equal": ["@approved", true] } }, "onFalse": { "type": "notifyemployee", "properties": { "message": "Rejected. Reviewer note: {{comment}}" } } } ` Agent Escalation (pauseforhuman) ======================================== Use this when the agent discovers ambiguity at runtime. Example tool call by the agent: `json { "action": "pauseforhuman_", "actioninput": { "question": "Two records match. Which should I use?", "options": ["C-001", "C-047"], "urgency": "medium" } } ` Resume the paused agent: `bash curl -X POST https://apisvr.tryhyphen.com/agents/:id/resume \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "humaninput": "Use C-001 - premium customer.", "approved": true }' ` Review UI Pattern ================= Use this API flow in your dashboard: 1. Receive webhook pbotapprovalrequested 2. Fetch approval: GET /approvals/:runId 3. Show comment and requestpayload 4. Submit decision: POST /approvals/:runId/:stepId For paused agents: 1. Poll GET /agents?status=paused 2. Fetch details via GET /agents/:id/status 3. Resume with POST /agents/:id/resume` --- ## PATH: Integrations > Outlook (Source: integrations/03-outlook.md) Outlook ======= Send emails, read inbox messages, and create calendar events from workflows and agents using the Microsoft Graph API. Available Actions ================= | Action | Description | |--------|-------------| | outlooksend | Send a new email | | outlookread | Read emails matching a query | | outlookcalendarcreate | Create a calendar event | Setup ===== 1. Store OAuth App Credentials ============================== Register an app in Azure Active Directory with Microsoft Graph permissions, then store the credentials: ``bash curl -X POST https://apisvr.tryhyphen.com/oauth/outlook/app-credentials \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "clientid": "your-azure-app-client-id", "clientsecret": "your-azure-app-client-secret" }' ` 2. Authorize a User Account =========================== `bash curl "https://apisvr.tryhyphen.com/oauth/outlook/authorize?redirect=true" \ -H "X-Org-Id: acme-corp" ` The user completes the Microsoft consent flow. Hyphen stores the tokens and handles refresh. 3. Verify Connection ==================== `bash curl https://apisvr.tryhyphen.com/oauth/connections \ -H "X-Org-Id: acme-corp" ` Using in Workflows ================== Send Email ========== `json { "type": "outlooksend", "properties": { "oauthaccount_": "ops@acme.com", "to": "@input.recipient", "subject": "Action Required: Review Pending Approvals", "body": "You have {{input.pendingcount}} approvals waiting for your review." } } ` Read Inbox ========== `json { "type": "outlookread", "properties": { "oauthaccount_": "procurement@acme.com", "query": "from:vendor@supplier.com subject:quote", "maxresults": 5 } } ` Create Calendar Event ===================== `json { "type": "outlookcalendarcreate", "properties": { "_oauthaccount_": "scheduling@acme.com", "subject": "Onboarding Call — {{input.companyname}}", "start": "@input.meetingtime", "durationminutes": 30, "attendees": ["@input.contactemail", "onboarding@acme.com"], "body": "Welcome call for new customer {{input.companyname}}." } } ` Using as Agent Tools ==================== `json { "mode": "react", "objective": "Schedule onboarding for the new customer and send confirmation", "tools": [ "lookupcustomer", { "type": "action", "name": "outlooksend" }, { "type": "action", "name": "outlookcalendarcreate" } ], "maxiterations": 8 } ` Complete Example: Post-Approval Notification ============================================ When an approval is granted, send an Outlook email and schedule a follow-up: `json { "name": "approvalfollowup", "definition": { "actions": [ { "type": "PbotApproval", "properties": { "comment": "Approve vendor {{input.vendorname}} for ${{input.contractvalue}}?", "requestpayload": { "vendor": "@input.vendorname", "value": "@input.contractvalue" } } }, { "type": "outlooksend", "filter": { "condition": { "equal": ["@approved", true] } }, "properties": { "oauthaccount": "procurement@acme.com", "to": "@input.vendoremail", "subject": "Vendor Approval Confirmed — {{input.vendorname}}", "body": "Your vendor application has been approved. Contract value: ${{input.contractvalue}}. Our team will be in touch to finalize onboarding." } }, { "type": "outlookcalendarcreate", "filter": { "condition": { "equal": ["@approved", true] } }, "properties": { "oauthaccount": "procurement@acme.com", "subject": "Vendor Onboarding — {{input.vendorname}}", "start": "@input.proposedmeetingtime", "durationminutes": 45, "attendees": ["@input.vendoremail", "procurement@acme.com"] } } ] } } `` --- ## PATH: Platform > Context Resolution (Source: platform/03-context-resolution.md) Context Resolution ================== Every workflow run maintains a context object. It starts with the input payload and grows as each step adds its output. The @path syntax lets any step reference data produced by earlier steps. How Context Accumulates ======================= ``mermaid flowchart TD subgraph "Context after input" I["@input.invoices
@input.payments"] end subgraph "Context after Step 1 (Matcher)" M["+ @matched
+ @unmatchedinvoices
+ @unmatchedpayments"] end subgraph "Context after Step 2 (ReAct)" R["+ @investigation"] end subgraph "Context after Step 3 (Approval)" A["+ @approved
+ @comment"] end I --> M --> R --> A ` Each step can read anything from context that was set before it. Steps cannot read from steps that haven't executed yet. @path Reference Table ===================== | Path | Source | Description | |------|--------|-------------| | @input. | Workflow execution payload | The JSON body sent to POST /workflows/:id/execute | | @input.field.nested | Nested input | Dot notation traverses nested objects | | @outputKey. | Step output | References output by outputKey or default output name | | @matched | Matcher | Array of matched record pairs | | @unmatchedLeft | Matcher | Left-side records with no match (or custom name via outputUnmatchedLeft) | | @unmatchedRight | Matcher | Right-side records with no match (or custom name via outputUnmatchedRight) | | @item | Foreach loop | Current item being processed | | @item.field | Foreach loop | Field on the current loop item | | @_runid | System | Current run identifier | | @step | System | Current step index (0-based) | | @now | System | Current ISO timestamp | | @approved | PbotApproval | Boolean — whether the reviewer approved | | @_comment | PbotApproval | Comment from the reviewer's decision | | doc:docxxx | Document Storage | Resolves uploaded document content (latest version). CSVs → Array, JSON → Object, text → String, binary → handle | | doc:docxxx@N | Document Storage | Resolves specific document version N — for audit reproducibility | Naming Step Outputs =================== By default, matcher outputs use their configured output names (outputMatched, outputUnmatchedLeft, outputUnmatchedRight). Other steps store results under their outputKey or under a default name. `json { "type": "loop", "properties": { "mode": "react", "objective": "Investigate exceptions", "tools": [{ "type": "action", "name": "lookuprecord" }], "resultkey": "investigation" } } ` The agent's final answer is stored at @investigation in context, accessible by all subsequent steps. For registered actions, outputKey controls the context key: `json { "type": "fetchcustomer", "properties": { "customerid": "@input.id" }, "outputKey": "customerdata" } ` Result available at @customerdata for subsequent steps. Nested Path Resolution ====================== Dot notation traverses nested objects to any depth: `json { "type": "sendnotification", "properties": { "email": "@customerdata.contact.email", "name": "@customerdata.contact.firstname", "company": "@customerdata.organization.name" } } ` Array Indexing ============== Access specific array elements by index: `json { "firstmatchid": "@matched.0.invoiceid", "secondmatchamount": "@matched.1.amount" } ` Access array length: `json { "condition": { "greaterThan": [{ "length": "@unmatchedinvoices" }, 0] } } ` Template Interpolation: {{ }} =============================== Double braces perform string interpolation — they convert a value to a string and embed it in surrounding text. Use for building human-readable messages, email subjects, Slack posts, and log entries. `json { "type": "gmailsend", "properties": { "subject": "Invoice {{input.invoiceid}} — {{input.vendorname}}", "body": "Dear {{input.vendorname}},\n\nYour invoice for ${{input.amount}} has been processed.\n\nMatched records: {{matched.length}}\nExceptions: {{unmatchedinvoices.length}}" } } ` @path vs {{ }} — These serve different purposes. @path returns the raw value (object, array, number, boolean) and is used for data references in properties. {{template}} converts to string and embeds in text. Use @path when passing data. Use {{ }} when constructing messages. `json { "datareference": "@input.invoices", "message": "Processing {{input.invoices.length}} invoices" } ` Worked Example: 4-Step Workflow =============================== `json { "name": "fullcontextexample", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["invoiceid"], "tolerance": 0.02, "outputMatched": "reconciled", "outputUnmatchedLeft": "exceptions" } }, { "type": "loop", "properties": { "mode": "foreach", "itemspath": "@exceptions", "itemvariablename": "exception", "actionstoexecute": [ { "type": "lookupvendor", "properties": { "vendorid": "@exception.vendorid" } } ], "collectresults": true, "resultkey": "vendorlookups" } }, { "type": "PbotApproval", "filter": { "condition": { "greaterThan": ["@exceptions.length", 5] } }, "properties": { "comment": "{{exceptions.length}} exceptions found — review before proceeding", "requestpayload": { "exceptioncount": "@exceptions.length", "vendordetails": "@vendorlookups", "reconciledcount": "@reconciled.length" } } }, { "type": "custom-table", "properties": { "table": "reconlog", "operation": "write", "keys": ["runid"], "values": ["@runid"], "fields": { "matched": "@reconciled.length", "exceptions": "@exceptions.length", "approved": "@_approved", "timestamp": "@now" } } } ] } } ` Context at each step: | After Step | Keys Added | Available | |------------|-----------|-----------| | Input | @input.invoices, @input.payments | input | | Step 1 (Matcher) | @reconciled, @exceptions | input + matcher output | | Step 2 (Loop) | @vendorlookups | input + matcher + loop results | | Step 3 (Approval) | @approved, @comment` | input + matcher + loop + approval | | Step 4 (Table) | — (writes to storage) | everything | → Next: Conditional Logic (/platform/conditional-logic) --- ## PATH: Primitives > Approval (Source: primitives/03-approval.md) Approval (PbotApproval) ======================= PbotApproval pauses workflow execution and waits for a human to review and decide. The reviewer sees the context you provide, makes a decision, and the workflow resumes with their response in context. Basic Usage =========== ``json { "type": "PbotApproval", "properties": { "comment": "Please review this expense for ${{input.amount}}", "requestpayload": { "expenseid": "@input.id", "amount": "@input.amount", "category": "@input.category", "submittedby": "@input.employeename" } } } ` When execution reaches this step, the run status changes to paused. The workflow stays paused until the approval is submitted. Properties Reference ==================== | Property | Type | Required | Description | |----------|------|----------|-------------| | comment | string | Yes | Human-readable description of what needs review. Supports {{ }} templates | | requestpayload | object | No | Structured data presented to the reviewer | Approval Flow ============= `mermaid sequenceDiagram participant Workflow participant Engine participant Reviewer Workflow->>Engine: PbotApproval step Engine->>Engine: Run status → "paused" Engine-->>Reviewer: Webhook: pbotapprovalrequested Note over Reviewer: Reviews context,
makes decision Reviewer->>Engine: POST /approvals/:runId/:stepId Engine->>Engine: Run status → "running" Engine->>Workflow: Resume with approval context ` 1. Workflow pauses ================== The engine changes the run status to paused and emits a pbotapprovalrequested webhook. 2. Reviewer decides =================== The reviewer sees the comment and requestpayload. They submit their decision: `bash curl -X POST https://apisvr.tryhyphen.com/approvals/{runId}/0 \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "approved": true, "comment": "Reviewed and approved - receipt verified" }' ` api POST /approvals/:runId/:stepId Submit an approval decision for a paused workflow. 3. Workflow resumes =================== After submission, the run resumes from the next step. The approval response is available in context. Approval Response Format ======================== | Field | Type | Required | Description | |-------|------|----------|-------------| | approved | boolean | Yes | Whether the reviewer approved | | comment | string | No | Reviewer's comment | Context After Approval ====================== After a PbotApproval step completes, the following keys are added to context: | Context Key | Value | |-------------|-------| | @approved | Boolean — true if approved, false if rejected | | @comment | Comment text from the review decision | Use these in subsequent steps to branch on the approval result: `json { "type": "processexpense", "filter": { "condition": { "equal": ["@approved", true] } }, "properties": { "expenseid": "@input.id", "approvalcomment": "@comment" }, "onFalse": { "type": "notifyemployee", "properties": { "message": "Your expense was rejected. Reason: {{_comment}}" } } } ` Conditional Approval ==================== Combine with filter to only require approval under certain conditions: `json { "type": "PbotApproval", "filter": { "condition": { "or": [ { "greaterThan": ["@input.amount", 10000] }, { "equal": ["@input.vendorstatus", "new"] } ] } }, "properties": { "comment": "High-value or new-vendor expense — requires review", "requestpayload": { "amount": "@input.amount", "vendor": "@input.vendorname" } } } ` When the filter evaluates to false, the step is skipped and the workflow continues. The @_approved context key is not set in this case. Listing Pending Approvals ========================= api GET /approvals/:runId List all pending approval requests for a workflow run. Webhook Notification ==================== When a PbotApproval step pauses a workflow, a pbotapprovalrequested webhook event is emitted. Use this to notify reviewers via your application, Slack, email, or any external system. The webhook payload includes the runid, stepid, comment, and requestpayload` so your notification system can provide full context. → Next: Form (PbotForm) (/primitives/form) --- ## PATH: Sdk > Authentication (Source: sdk/03-authentication.md) Authentication ============== The SDK supports two session modes: anonymous (default) and authenticated (OTP). Both are scoped to your publishable key's permissions. Anonymous Sessions ================== When you call HyphenSDK.init(), the SDK creates an anonymous session: ``javascript const sdk = await HyphenSDK.init({ publishableKey: 'pklive...' }); sdk.isAuthenticated(); // false sdk.getUser(); // null ` Anonymous sessions are sufficient for: - Read-only dashboards - Viewing pending tasks - Browsing data tables (without editing) All actions in anonymous sessions are logged with the session ID but without a user identity. Authenticated Sessions (OTP) ============================ For user-level audit trails, per-user permissions, and edit access, enable OTP authentication. Login Flow ========== `javascript // Show the login modal sdk.login(); ` The modal guides the user through: 1. Email entry — the user enters their registered email 2. Code verification — the user enters the 8-digit OTP sent to their email 3. Success — the session is upgraded and the modal auto-closes `javascript // Listen for successful authentication sdk.on('authenticated', (e) => { console.log('Signed in as:', e.data.email); }); ` Pre-Registration Requirement ============================ Users must be added to your publishable key's allowed user list before they can authenticate. The SDK does not support self-registration or open signup. Add users via the gateway management API: `bash curl -X POST https://apisvr.tryhyphen.com/sdk/admin/publishable-keys/:keyId/users \ -H "Authorization: Bearer mt-hyp-..." \ -H "Content-Type: application/json" \ -d '{ "email": "analyst@acme.com", "role": "reviewer" }' ` Session Lifecycle ================= Token Expiry ============ Session tokens have a fixed time-to-live. When a session expires, the SDK emits a session:expired event: `javascript const sdk = await HyphenSDK.init({ publishableKey: 'pklive...', onSessionExpired: () => { // Prompt re-authentication sdk.login(); } }); ` Checking Session State ====================== `javascript sdk.isAuthenticated(); // true if OTP-verified sdk.getSession(); // { sessionId, expiresAt, ... } sdk.getUser(); // { email, role } or null ` Logout ====== `javascript sdk.logout(); // Clears session, reverts to anonymous ` After logout, all components revert to anonymous-level access. The task sidebar may show fewer tasks, and editable grids become read-only (depending on your publishable key's anonymous permissions). Security Model ============== | Property | Detail | |----------|--------| | Origin binding | Publishable keys are restricted to configured domains (CORS enforcement) | | Session tokens | Short-lived, non-renewable | | OTP codes | Expire after 10 minutes; locked out after 5 failed attempts | | Org resolution | Server-side from publishable key — the SDK never sends X-Org-Id` | | Audit logging | Every action is logged with session identity (anonymous or authenticated user) | | Shadow DOM isolation | Components are isolated from host page scripts — no DOM access to internal state | Publishable keys are not secret. They are designed for client-side use. Security is enforced server-side through origin restrictions, scope limits, and rate limiting. A publishable key cannot modify workflows, access admin endpoints, or perform actions outside its configured scope. --- ## PATH: Templates > Healthcare Denial Management (Source: templates/03-healthcare-denial-management.md) Healthcare Denial Management ============================ Remittance-to-claim matching, denial pattern analysis, and automated appeal drafting. Revenue cycle teams spend 65+ hours per week on denials — most of which follow predictable patterns that an agent can identify, analyze, and draft responses for. What Gets Automated =================== Matching ERA/835 remittance records to submitted claims. Parsing denial reason codes (CARC/RARC). AI agent analyzes denial patterns, cross-references payer-specific rules, identifies root causes, and drafts appeal letters with supporting documentation references. What Humans Still Own ===================== Final review of appeal language before submission. Payer negotiation on contested amounts. Process changes based on systemic denial patterns. Pipeline ======== ``mermaid flowchart TD A[Input: Remittances + Submitted Claims] --> B[Matcher: Remittance-to-Claim] B -->|Paid in full ~45%| C[Close Claim] B -->|Denied or underpaid ~55%| D[Parse Denial Codes] B -->|No remittance found| E[Flag: No Response] D --> F[ReAct Agent: Denial Analysis] F --> F1[Decode CARC/RARC codes] F1 --> F2[Check payer-specific rules] F2 --> F3[Search prior appeal outcomes] F3 -->|Correctable| G[Draft Corrected Claim] F3 -->|Appealable| H[Draft Appeal Letter] F3 -->|Non-recoverable| I[Recommend Write-Off] G --> J[Human Review: RCM Analyst] H --> J I --> J J -->|Approve| K[Submit to Clearinghouse] J -->|Revise| L[Edit + Resubmit] C --> M[Custom Table: Denial Tracking] K --> M L --> M E --> M style B fill:#e8a84c,color:#09090b,stroke:none style F fill:#4ade80,color:#09090b,stroke:none style J fill:#60a5fa,color:#09090b,stroke:none ` Workflow Definition =================== `json { "name": "denialmanagement", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.submittedclaims", "right": "@input.remittances", "matchOn": ["claimid", "payerid"], "tolerance": 0.005, "dateWindowDays": 90, "outputMatched": "remittancematched", "outputUnmatchedLeft": "noresponseclaims", "outputUnmatchedRight": "unmatchedremittances" } }, { "type": "loop", "properties": { "mode": "foreach", "itemspath": "@remittancematched", "itemvariablename": "pair", "actionstoexecute": [ { "type": "loop", "filter": { "condition": { "notEqual": ["@pair.b.paymentstatus", "paidinfull"] } }, "properties": { "mode": "react", "objective": "Analyze denial for claim {{pair.a.claimid}}. Payer: {{pair.b.payername}}. Denial codes: {{pair.b.denialcodes}}. Billed: ${{pair.a.billedamount}}. Paid: ${{pair.b.paidamount}}. Steps: 1) Parse CARC/RARC codes and identify denial category, 2) Look up payer-specific rules for this denial type, 3) Search for prior appeals on same denial pattern and their outcomes, 4) Classify as correctable (resubmit with fix), appealable (submit appeal), or non-recoverable (write off), 5) If correctable or appealable, draft the response citing contract terms and clinical rationale.", "tools": [ { "type": "action", "name": "lookupdenialcodes" }, { "type": "action", "name": "getpayerrules" }, { "type": "action", "name": "searchdenialhistory" }, { "type": "action", "name": "getclinicaldocumentation" }, { "type": "action", "name": "draftappealletter" } ], "maxiterations": 10, "onstuck": { "iterations": 4, "action": "escalate" }, "resultkey": "denialanalysis" } } ], "maxconcurrency": 5, "failurestrategy": "continueonerror", "collectresults": true, "resultkey": "allanalyses" } }, { "type": "PbotApproval", "properties": { "comment": "Denial analyses complete. Review appeal drafts, corrected claims, and write-off recommendations before submission.", "requestpayload": { "totalmatched": "@remittancematched.length", "noresponse": "@noresponseclaims.length", "analyses": "@allanalyses" } } }, { "type": "custom-table", "properties": { "table": "denialtracking", "operation": "write", "keys": ["batchid", "rundate"], "values": ["@runid", "@now"], "fields": { "claimsanalyzed": "@remittancematched.length", "noresponseflagged": "@noresponseclaims.length", "status": "completed" } } } ] } } ` Required Registered Actions =========================== | Action | Kind | Purpose | |--------|------|---------| | lookupdenialcodes | db | Translate CARC/RARC codes to reasons and appeal guidance | | getpayerrules | http | Retrieve payer-specific billing and appeal rules | | searchdenialhistory | db | Find prior denials with same codes for pattern detection | | getclinicaldocumentation | http | Pull relevant clinical notes for appeal support | | draftappealletter | llm | Generate appeal letter from denial analysis and documentation | The draftappealletter action is an LLM action — it generates text using an AI model. The agent uses it as a tool to produce appeal drafts that the human reviewer edits before submission. Customization Notes =================== Date window. 90 days covers typical payer response timelines. Some payers may take longer — adjust based on your payer mix. Tolerance. The tight 0.5% tolerance (0.005) is appropriate for claims where payment amounts should closely match billed amounts. Widen for payers with known contractual adjustment patterns. Prior appeal search. The searchdenialhistory` action is critical for pattern detection. The agent uses historical appeal outcomes to predict which denials are worth appealing vs. writing off. Concurrency. 5 concurrent analyses balances throughput with the need for sequential tool calls within each denial investigation. --- ## PATH: Actions > Matcher (Source: actions/04-matcher.md) Matcher Actions =============== Matcher actions are pre-configured matching rules. Instead of specifying matcher properties in every workflow, register a configuration once and reference it by name. Registration ============ ``bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "actionname": "invoicepaymentmatcher", "kind": "matcher", "description": "Match invoices to payments by invoice number with 2% tolerance and 5-day date window", "left": "@input.invoices", "right": "@input.payments", "matchOn": ["invoicenumber", "vendorid"], "tolerance": 0.02, "dateWindowDays": 5, "fuzzyThreshold": 85, "descriptionKey": "description" }' ` Properties ========== | Property | Type | Required | Description | |----------|------|----------|-------------| | actionname | string | Yes | Unique name for this action | | kind | "matcher" | Yes | | | left | @path | Yes | Left dataset path | | right | @path | Yes | Right dataset path | | matchOn | string[] | Yes | Fields for exact matching | | tolerance | number | No | Numeric tolerance (decimal) | | dateWindowDays | number | No | Date window (±days) | | fuzzyThreshold | number | No | Text similarity 0–100 | | descriptionKey | string | No | Field for fuzzy matching | All matching properties work identically to the Matcher primitive (/primitives/matcher). The difference is that a matcher action saves the configuration for reuse. Registered vs Primitive ======================= Use the matcher primitive when matching configuration varies per workflow: `json { "type": "matcher", "properties": { "matchOn": ["invoiceid"], "tolerance": 0.01 } } ` Use a registered matcher action when you have a standard matching configuration used across multiple workflows: `json { "type": "invoicepaymentmatcher", "properties": { } } ` Usage in a Workflow =================== `json { "type": "invoicepaymentmatcher", "properties": { "invoices": "@input.newinvoices", "payments": "@input.recentpayments" } } ` Properties passed at execution time can override the registered defaults. Usage as an Agent Tool ====================== `json { "mode": "react", "objective": "Reconcile this batch of transactions", "tools": [{ "type": "action", "name": "invoicepayment_matcher" }] } `` The agent can invoke the matcher during its reasoning when it needs to compare datasets. → Next: Custom Table Actions (/actions/custom-table) --- ## PATH: Agents > Stuck Detection (Source: agents/04-stuck-detection.md) Stuck Detection =============== Agents can get stuck — repeating the same action, cycling between two tools, or failing to make progress toward the objective. Stuck detection identifies these patterns and triggers a recovery strategy. Configuration ============= ``json { "onstuck": { "iterations": 3, "action": "escalate", "hint": "Try a different approach or use complete with your best answer." } } ` | Property | Type | Required | Description | |----------|------|----------|-------------| | iterations | number | No | Number of repeated iterations before triggering (default: 3) | | action | string | No | Recovery strategy: "fail", "escalate", or "retrywithhint" (default: "fail") | | hint | string | No | Guidance text injected into the prompt (only used with "retrywithhint") | How Detection Works =================== The engine monitors the agent's action history across iterations. It detects stuck patterns when: - The agent calls the same action with the same parameters for N consecutive iterations - The agent alternates between two actions without producing new information - The agent produces repeated identical thoughts suggesting it's not incorporating new observations When any pattern is detected for the configured number of iterations, the recovery strategy fires. Recovery Strategies =================== "fail" — Terminate ==================== The agent loop ends immediately with a failure status. Use when you'd rather fail fast than risk incorrect results. `json { "onstuck": { "iterations": 3, "action": "fail" } } ` The run status becomes failed with a clear error message indicating the agent was stuck. "escalate" — Pause for Human ============================== The agent pauses and the run is routed to a human reviewer, just like _pauseforhuman. The human sees the agent's full reasoning trace up to the point it got stuck, plus context about the stuck pattern. `json { "onstuck": { "iterations": 3, "action": "escalate" } } ` The human can provide guidance via POST /agents/:id/resume, and the agent continues with the human's input as additional context. This is the recommended strategy for production agents in critical workflows. "retrywithhint" — Redirect ============================== The engine injects a hint into the agent's next prompt, nudging it toward a different approach. The agent gets one more chance to make progress. `json { "onstuck": { "iterations": 3, "action": "retrywithhint", "hint": "You seem to be repeating yourself. Try using complete to provide your best answer with the information you have, or try a different tool." } } ` If the agent gets stuck again after the hint, it fails. The hint is a one-shot redirect, not an infinite retry. Setting Thresholds ================== iterations: 2 — Aggressive. Triggers quickly but may false-positive on agents that legitimately need to retry a tool (e.g., polling for an async result). iterations: 3 — Balanced. The default recommendation. Catches genuine loops while tolerating one legitimate retry. iterations: 5 — Conservative. Use for agents with tools that may legitimately return different results on repeated calls (e.g., searching with refined queries). Example: Stuck Detection in Practice ==================================== An agent investigating an unmatched invoice: ` Iteration 1: Thought: "Let me search for related payments." Action: searchpayments { "invoiceid": "INV-099" } Observation: { "results": [] } Iteration 2: Thought: "No results. Let me try searching again with a broader query." Action: searchpayments { "invoiceid": "INV-099" } ← same action, same params Observation: { "results": [] } Iteration 3: Thought: "Still nothing. Let me search once more." Action: searchpayments { "invoiceid": "INV-099" } ← stuck detected! ` With "action": "escalate", the agent pauses here and a human reviews the trace. The human might respond: "This invoice is from a new vendor not yet in the payment system. Mark as pending vendor setup." With "action": "retrywithhint" and hint "No payments exist for this invoice. Consider whether it might be from a new vendor or a different system.", the agent gets one more iteration with this additional context. Best Practices ============== - Always configure onstuck for production agents. The default "fail" is safe but gives you no recovery path. "escalate" is almost always better. - Pair stuck detection with maxiterations as a hard ceiling. Stuck detection catches loops; maxiterations` catches everything else. - Write hints that suggest alternatives, not just "try again." Good: "If the search returns no results, use complete to report that no match was found." Bad: "Try harder." - Monitor stuck rates via platform metrics. High stuck rates indicate the objective is unclear, tools are insufficient, or the model needs more guidance. → Next: Reasoning Traces (/agents/reasoning-traces) --- ## PATH: Getting Started > Your First Agent (Source: getting-started/04-your-first-agent.md) Your First Agent ================ Build an AI agent that reasons through a task: register tools, create a workflow with a ReAct loop, execute it, watch the reasoning trace unfold, handle a human escalation, and read the final answer. Prerequisites ============= A running Hyphen instance at https://apisvr.tryhyphen.com with an LLM API key configured. All examples use X-Org-Id: tutorial-org. Store your LLM key if you haven't already: ``bash curl -X POST https://apisvr.tryhyphen.com/org-config \ -H "X-Org-Id: tutorial-org" \ -H "Content-Type: application/json" \ -d '{ "key": "api:llmapikey", "value": "sk-your-openai-key" }' ` Step 1: Register Tools ====================== Agents need tools to work with. Register an HTTP action and an LLM action: `bash An HTTP action that looks up order details ========================================== curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: tutorial-org" \ -H "Content-Type: application/json" \ -d '{ "actionname": "lookuporder", "kind": "http", "description": "Look up order details by order ID", "url": "https://httpbin.org/anything/orders/{{orderid}}", "httpmethod": "GET", "passthrough": true }' ` `bash An LLM action that drafts a customer response ============================================= curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: tutorial-org" \ -H "Content-Type: application/json" \ -d '{ "actionname": "draftresponse", "kind": "llm", "description": "Draft a professional customer service response", "template": "Draft a professional, empathetic response to this customer issue:\n\nIssue: {{issue}}\nOrder details: {{orderdetails}}\n\nKeep it under 3 sentences.", "model": "gpt-4o-mini", "maxtokens": 256 }' ` Step 2: Create the Agent Workflow ================================= Create a workflow with a ReAct loop step. The agent gets an objective, a set of tools, and autonomy to reason through the problem: `bash curl -X POST https://apisvr.tryhyphen.com/workflows \ -H "X-Org-Id: tutorial-org" \ -H "Content-Type: application/json" \ -d '{ "name": "customerissueagent", "definition": { "actions": [ { "type": "loop", "properties": { "mode": "react", "objective": "Handle this customer issue: {{input.issue}}. Order ID: {{input.orderid}}. Steps: 1) Look up the order details, 2) Draft a response to the customer, 3) If the issue involves a refund over $100, pause for human approval before completing. 4) Complete with your drafted response and a confidence score.", "tools": [ { "type": "action", "name": "lookuporder" }, { "type": "action", "name": "draftresponse" } ], "model": "gpt-4o", "maxiterations": 8, "onstuck": { "iterations": 3, "action": "escalate" }, "resultkey": "agentresult" } } ] } }' ` What this does: The agent receives an objective in natural language and a list of tools. It reasons step by step — thinking about what to do, choosing a tool, observing the result, and repeating until it calls complete. If it encounters a refund over $100, it pauses for human approval. Step 3: Execute with an Objective ================================= `bash curl -X POST https://apisvr.tryhyphen.com/workflows/WORKFLOWID/execute \ -H "X-Org-Id: tutorial-org" \ -H "Content-Type: application/json" \ -d '{ "input": { "issue": "I was charged twice for my order and I want a refund of $150", "orderid": "ORD-12345" } }' ` Step 4: Watch the Reasoning Trace ================================= Check the run status: `bash curl https://apisvr.tryhyphen.com/runs/RUNID/status \ -H "X-Org-Id: tutorial-org" ` If the agent is still running, you'll see status: "running". Once it pauses or completes, the reasoning trace shows exactly how it worked through the problem: `json { "status": "paused", "reasoningtrace": [ { "iteration": 1, "thought": "The customer reports being charged twice and wants a $150 refund. Let me first look up the order details.", "action": "lookuporder", "actioninput": { "orderid": "ORD-12345" }, "observation": { "url": "https://httpbin.org/anything/orders/ORD-12345", "method": "GET" } }, { "iteration": 2, "thought": "I have the order details. Now I'll draft a response to the customer.", "action": "draftresponse", "actioninput": { "issue": "charged twice, requesting $150 refund", "orderdetails": "..." }, "observation": "Dear valued customer, I sincerely apologize for the duplicate charge..." }, { "iteration": 3, "thought": "The refund is $150, which is over $100. Per my instructions, I need to pause for human approval before completing.", "action": "pauseforhuman", "actioninput": { "question": "Customer requests $150 refund for duplicate charge on order ORD-12345. Approve the refund?", "options": ["Approve full refund ($150)", "Approve partial refund", "Deny — investigate further"], "contextsummary": "Order looked up. Response drafted. Awaiting refund approval.", "urgency": "medium" } } ] } ` The agent looked up the order, drafted a response, recognized the refund exceeds $100, and paused for human approval — exactly as instructed. Step 5: Handle the Pause ======================== The agent is waiting for human input. Provide it: `bash curl -X POST https://apisvr.tryhyphen.com/agents/AGENTRUNID/resume \ -H "X-Org-Id: tutorial-org" \ -H "Content-Type: application/json" \ -d '{ "humaninput": "Approved. Process the full $150 refund. Also add a 10% discount code for their next order as a goodwill gesture.", "approved": true }' ` The agent resumes, incorporating the human's guidance into its next iteration. Step 6: Read the Final Answer ============================= After resuming, the agent completes: `bash curl https://apisvr.tryhyphen.com/runs/RUNID/status \ -H "X-Org-Id: tutorial-org" ` `json { "status": "completed", "context": { "agentresult": { "answer": "Refund of $150 approved and processed. Customer response drafted with apology and 10% discount code for next order.", "confidence": 0.95, "summary": "Looked up order, drafted response, obtained human approval for refund." } } } ` The confidence score tells you how certain the agent was about its answer. The full reasoning trace is preserved as an audit trail. Step 7: View the Complete Trace =============================== For the full audit trail including all iterations (before and after the pause): `bash curl https://apisvr.tryhyphen.com/agents/AGENTRUNID/trace \ -H "X-Org-Id: tutorial-org" ` This returns every thought, action, and observation — the complete record of the agent's reasoning and decisions. What You Built ============== An AI agent that: 1. Reasons through a customer issue step by step 2. Uses tools — looking up order data and drafting responses 3. Pauses for human approval when a refund exceeds a threshold 4. Resumes with human guidance incorporated into its reasoning 5. Completes with a confidence-scored answer and full audit trail Key concepts demonstrated: | Concept | How It Appeared | |---------|----------------| | ReAct loop | Agent thinks → acts → observes → repeats | | Tool usage | lookuporder (HTTP), draftresponse (LLM) | | Human escalation | _pauseforhuman_ when refund > $100 | | Reasoning trace | Full chain of thought captured per iteration | | Confidence scoring | Agent reports 0.95 confidence in final answer | | Stuck detection | Configured at 3 iterations with escalate` fallback | Next: Explore the templates (/templates) for production-ready patterns, or read the agent deployment patterns (/agents/deployment-patterns) guide to learn about agents as workflow triggers and orchestrators. --- ## PATH: Guides > Graduated Exception Handling (Source: guides/04-graduated-exception-handling.md) Graduated Exception Handling ============================ The core Hyphen philosophy: don't automate everything and don't review everything. Build a pipeline where deterministic rules handle the majority, an AI agent investigates the ambiguous middle, and humans make the final call on genuine edge cases. We call this the 80/15/5 pattern. Why Graduated Beats Binary ========================== Most automation projects fail because they frame the choice as binary: automate all of it (and accept error risk) or review all of it (and accept the labor cost). Neither works. Full automation fails because real data is messy. Tolerances, fuzzy matches, and exception patterns mean some percentage of cases will always need judgment. Full manual review fails because 80% of the work is straightforward. Reviewing clear cases is waste — it's expensive, slow, and demoralizing for the people doing it. The graduated approach matches the effort to the difficulty: | Tier | Volume | Handler | Cost per Case | |------|--------|---------|--------------| | Clear cases | ~80% | Matcher (deterministic) | Near zero | | Ambiguous cases | ~15% | ReAct agent (bounded AI) | Low | | Edge cases | ~5% | Human reviewer | High | The exact split varies by domain. Insurance claims might be 60/30/10. Bank reconciliation might be 90/8/2. The structure stays the same. Configuring the 80%: The Matcher ================================ The matcher handles clear cases — records that match on key fields within defined tolerances. Configuration determines what counts as "clear." ``json { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["ponumber", "vendorid"], "tolerance": 50, "dateWindowDays": 5, "fuzzyThreshold": 85, "descriptionKey": "vendorname", "outputMatched": "autoreconciled", "outputUnmatchedLeft": "exceptions", "outputUnmatchedRight": "unmatchedpayments" } } ` Tuning the Matcher ================== Tighter tolerances → more exceptions, higher accuracy on auto-reconciled records. Looser tolerances → fewer exceptions, but some auto-reconciled records may be incorrect. Start tight and loosen based on data: | Parameter | Conservative | Balanced | Aggressive | |-----------|-------------|----------|------------| | tolerance | 5 ($5) | 50 ($50) | 500 ($500) | | dateWindowDays | 1 | 5 | 14 | | fuzzyThreshold | 90 | 85 | 75 | Run the matcher against a historical dataset where you already know the correct matches. Compare the auto-reconciled output to your ground truth. This tells you your false positive rate at each tolerance level. Configuring the 15%: The Agent ============================== Exceptions from the matcher go to an AI agent for investigation. The agent uses registered actions as tools to look up additional data, cross-reference records, and form a recommendation. `json { "type": "loop", "filter": { "condition": { "greaterThan": [{ "length": "@exceptions" }, 0] } }, "properties": { "mode": "foreach", "itemspath": "@exceptions", "itemvariablename": "exception", "actionstoexecute": [ { "type": "loop", "properties": { "mode": "react", "objective": "Investigate exception: {{exception.invoiceid}} for ${{exception.amount}}. Determine root cause and recommend action.", "tools": [ { "type": "action", "name": "lookuppurchaseorder" }, { "type": "action", "name": "checkpaymenthistory" }, { "type": "action", "name": "searchduplicateinvoices" } ], "maxiterations": 8, "onstuck": { "iterations": 3, "action": "retrywithhint", "hint": "Complete with your best assessment and low confidence." }, "resultkey": "investigation" } } ], "maxconcurrency": 5, "failurestrategy": "continueonerror", "collectresults": true, "resultkey": "allinvestigations" } } ` Tuning the Agent ================ maxiterations controls how deep the agent can investigate. Too low and it gives up before finding the answer. Too high and you burn tokens on dead ends. | Complexity | Recommended Iterations | |-----------|----------------------| | Simple lookups (1–2 data sources) | 5 | | Standard investigation (3–4 sources) | 8 | | Complex analysis (multiple sources + reasoning) | 12–15 | onstuck determines what happens when the agent loops without progress. For the graduated pipeline, retrywithhint is usually best — give the agent one more chance with guidance before escalating: `json "onstuck": { "iterations": 3, "action": "retrywithhint", "hint": "If you cannot determine the root cause, complete with recommendation to escalate for human review." } ` maxconcurrency limits parallel investigations. Higher concurrency means faster batch processing but more load on your data source APIs. Configuring the 5%: Human Review ================================ Cases the agent can't resolve with high confidence get routed to a human. The approval step presents the agent's findings — not raw data — so the reviewer has context. `json { "type": "PbotApproval", "filter": { "condition": { "greaterThan": [{ "length": "@exceptions" }, 0] } }, "properties": { "comment": "{{exceptions.length}} exceptions investigated. Review AI findings and approve recommended actions.", "requestpayload": { "autoreconciledcount": "@autoreconciled.length", "exceptioncount": "@exceptions.length", "investigations": "@allinvestigations", "unmatchedpayments": "@unmatchedpayments" } } } ` What the Reviewer Sees ====================== The requestpayload gives the reviewer everything they need: - How many records matched automatically (provides confidence the pipeline is working) - The agent's investigation for each exception (root cause analysis, recommendation, confidence score) - Raw unmatched records for reference The reviewer's job is to approve, reject, or modify the agent's recommendations — not to redo the investigation from scratch. The Complete Pipeline ===================== Putting it together in one workflow: `json { "name": "graduatedreconciliation", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["ponumber", "vendorid"], "tolerance": 50, "dateWindowDays": 5, "fuzzyThreshold": 85, "descriptionKey": "vendorname", "outputMatched": "autoreconciled", "outputUnmatchedLeft": "exceptions", "outputUnmatchedRight": "unmatchedpayments" } }, { "type": "loop", "filter": { "condition": { "greaterThan": [{ "length": "@exceptions" }, 0] } }, "properties": { "mode": "foreach", "itemspath": "@exceptions", "itemvariablename": "exception", "actionstoexecute": [ { "type": "loop", "properties": { "mode": "react", "objective": "Investigate and recommend action for exception {{exception.invoiceid}}.", "tools": [ { "type": "action", "name": "lookuppurchaseorder" }, { "type": "action", "name": "checkpaymenthistory" } ], "maxiterations": 8, "onstuck": { "iterations": 3, "action": "retrywithhint", "hint": "Complete with best assessment." }, "resultkey": "investigation" } } ], "maxconcurrency": 5, "collectresults": true, "resultkey": "allinvestigations" } }, { "type": "PbotApproval", "filter": { "condition": { "greaterThan": [{ "length": "@exceptions" }, 0] } }, "properties": { "comment": "{{exceptions.length}} exceptions reviewed by AI. Approve actions.", "requestpayload": { "autocount": "@autoreconciled.length", "investigations": "@allinvestigations" } } }, { "type": "custom-table", "properties": { "table": "reconciliationmetrics", "operation": "write", "keys": ["runid"], "values": ["@runid"], "fields": { "total": "@input.invoices.length", "autoreconciled": "@autoreconciled.length", "agentinvestigated": "@exceptions.length", "completedat": "@now" } } } ] } } ` Measuring the Split =================== Track the ratio over time using the custom-table logging step. Query the reconciliationmetrics table to measure: - Auto-reconciled rate — autoreconciled / total. If this drops below 70%, your matcher tolerances may need adjustment or your data quality has degraded. - Exception rate — agent_investigated / total`. If this spikes, investigate whether new vendors, format changes, or system migrations are creating more mismatches. - Agent confidence distribution — Track confidence scores from agent completions. Low average confidence suggests the tools need better data or the objective needs refinement. The goal is continuous improvement: as you tune tolerances and agent objectives, the auto-reconciled rate should climb and the human review rate should shrink. --- ## PATH: Integrations > Webhooks (Source: integrations/04-webhooks.md) Webhooks ======== Hyphen sends outbound webhooks when workflow, approval, document, and generation events occur. Base URL used below: https://apisvr.tryhyphen.com Registration API ================ Register per-org webhook targets with /webhooks: ``bash curl -X POST https://apisvr.tryhyphen.com/webhooks \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "event": "document.uploaded", "url": "https://example.com/hyphen/webhooks", "secret": "whsecabc123", "filter": { "tags": ["invoices"], "contenttype": "text/csv" }, "autopayload": { "source": "hyphen" } }' ` Supported registration endpoints: - POST /webhooks - GET /webhooks - GET /webhooks/:id - PATCH /webhooks/:id - DELETE /webhooks/:id Current validation for registrations allows these events: - document.uploaded - document.versioncreated - document.deleted - document.expired Note: runtime emits additional events too (listed below). If you need registration support for all emitted events, expand the event allow-list in the server validation schema. Payload Envelope ================ Every delivery uses this envelope: `json { "event": "document.uploaded", "timestamp": "2026-03-03T10:30:00.000Z", "payload": { "documentid": "doca1b2c3d4e5f6", "name": "Q4 Transactions", "contenttype": "text/csv" } } ` Field meanings: - event: event name - timestamp: ISO timestamp at send time - payload: event-specific payload data Signature Verification ====================== If a secret is configured on the webhook target, Hyphen sends: - X-Hyphen-Signature: sha256= HMAC algorithm: SHA-256 over the raw JSON body. Runtime Event Names =================== Observed emitted events in current runtime: - workflowrunstarted - workflowruncompleted - workflowrunfailed - pbotapprovalrequested - pbotapprovaldecided - document.uploaded - document.deleted - externalformcreated - externalformupdated - externalformdeleted - externalformsubmitted - workflowcreationstatus - llmgenerationstatus - llmkeymissing Retry Policy ============ Delivery behavior: - Success: any 2xx - Retryable HTTP: 429, 408, 5xx - Non-retryable HTTP: all other 4xx - Backoff: exponential (starting at 1 second) - Max attempts: 5 - For 429, Retry-After is honored when larger than current delay If all targets fail for an event, sender raises an error for that event path; webhook delivery still remains non-blocking for most request handlers. Legacy Compatibility ==================== Hyphen also supports a legacy org config key webhookurl and will deliver to it if set. Use new /webhooks` registrations for event-level routing, filtering, activation state, and signing. --- ## PATH: Platform > Conditional Logic (Source: platform/04-conditional-logic.md) Conditional Logic ================= Hyphen supports conditional execution at two levels: gating an entire workflow, and branching individual steps. Top-Level Conditions ==================== A condition on the workflow definition gates the entire run. If it evaluates to false, the workflow ends immediately with status conditionnotmet and no steps execute. ``json { "name": "processhighvalue", "definition": { "condition": { "greaterThan": ["@input.ordertotal", 10000] }, "actions": [ ] } } ` Use top-level conditions to prevent unnecessary execution — for example, only running a reconciliation workflow when there are actually records to process. Step-Level Branching: filter + onFalse ========================================== Each step can have a filter that determines whether it executes. If the filter is false, the step is skipped — unless an onFalse alternative is defined. `json { "type": "sendnotification", "filter": { "condition": { "greaterThan": ["@input.amount", 5000] } }, "properties": { "channel": "urgent", "message": "High-value: ${{input.amount}}" }, "onFalse": { "type": "sendnotification", "properties": { "channel": "standard", "message": "Processed: ${{input.amount}}" } } } ` `mermaid flowchart TD A{"amount > $5,000?"} -->|Yes| B["Send to #urgent"] A -->|No| C["Send to #standard"] B --> D["Next step"] C --> D ` The onFalse step has the same structure as any other step — it can have its own type, properties, and even nested filter/onFalse for multi-branch logic. Comparison Operators ==================== | Operator | Syntax | Description | |----------|--------|-------------| | equal | { "equal": [left, right] } | Strict equality | | notEqual | { "notEqual": [left, right] } | Not equal | | greaterThan | { "greaterThan": [left, right] } | Greater than (numeric) | | lessThan | { "lessThan": [left, right] } | Less than (numeric) | | greaterOrEqual | { "greaterOrEqual": [left, right] } | Greater or equal | | lessOrEqual | { "lessOrEqual": [left, right] } | Less or equal | | hasKey | { "hasKey": "path" } | Key exists in context | | in | { "in": [value, array] } | Value is in list | | matches | { "matches": [value, regex] } | Regex match | Operands can be literal values or @path references: `json { "equal": ["@input.status", "active"] } { "greaterThan": ["@input.amount", 1000] } { "in": ["@input.country", ["US", "CA", "MX"]] } { "matches": ["@input.email", ".@company\\.com$"] } { "hasKey": "@input.metadata.priority" } ` The length helper extracts array or string length: `json { "greaterThan": [{ "length": "@unmatchedinvoices" }, 0] } ` Combinators: and, or, not =============================== Combine conditions for complex logic: and — all conditions must be true: `json { "and": [ { "greaterThan": ["@input.amount", 1000] }, { "equal": ["@input.verified", true] } ] } ` or — at least one must be true: `json { "or": [ { "equal": ["@input.customertype", "premium"] }, { "greaterThan": ["@input.yearsactive", 5] } ] } ` not — inverts a condition: `json { "not": { "equal": ["@input.status", "cancelled"] } } ` Nested Condition Trees ====================== Combinators nest to any depth. Here's a real-world example that gates an approval step: Require approval when: amount over $10,000 AND (vendor is new OR country is high-risk) AND the invoice is not already pre-approved.* `json { "type": "PbotApproval", "filter": { "condition": { "and": [ { "greaterThan": ["@input.amount", 10000] }, { "or": [ { "equal": ["@input.vendorstatus", "new"] }, { "in": ["@input.country", ["RU", "CN", "IR", "KP"]] } ] }, { "not": { "equal": ["@input.preapproved", true] } } ] } }, "properties": { "comment": "High-value invoice from {{input.vendorstatus}} vendor in {{input.country}}", "requestpayload": { "amount": "@input.amount", "vendor": "@input.vendorname", "country": "@input.country" } } } ` Evaluation order. Conditions evaluate depth-first. Inner conditions resolve before outer combinators. All @path references resolve against the current execution context at the time the condition is evaluated. Multi-Branch Pattern ==================== Chain filter/onFalse for multi-way branching: `json { "type": "routepremium", "filter": { "condition": { "equal": ["@input.tier", "premium"] } }, "properties": { "queue": "premium" }, "onFalse": { "type": "routestandard", "filter": { "condition": { "equal": ["@input.tier", "standard"] } }, "properties": { "queue": "standard" }, "onFalse": { "type": "routebasic", "properties": { "queue": "basic" } } } } `` This evaluates as: premium → standard → basic (fallback). → Next: Scheduling (/platform/scheduling) --- ## PATH: Primitives > Form (Source: primitives/04-form.md) Form (PbotForm) =============== PbotForm pauses workflow execution to collect structured input from an external party. Unlike PbotApproval (which collects a decision from a designated reviewer), PbotForm collects data from anyone — customers, vendors, partners. Basic Usage =========== ``json { "type": "PbotForm", "properties": { "expectedkeys": ["shippingaddress", "deliverydate", "specialinstructions"], "ttlseconds": 86400, "reminderintervals": [3600, 7200] } } ` When execution reaches this step, the run pauses and waits for form submission. Properties Reference ==================== | Property | Type | Required | Description | |----------|------|----------|-------------| | expectedkeys | string[] | Yes | Fields the form expects. Submission must include these keys | | ttlseconds | number | No | Time-to-live — how long the form stays open before expiring (default: no expiration) | | reminderintervals | number[] | No | Seconds after creation to send reminders. [3600, 7200] sends reminders at 1 hour and 2 hours | Submission Flow =============== `mermaid sequenceDiagram participant Workflow participant Engine participant External Workflow->>Engine: PbotForm step Engine->>Engine: Run status → "paused" Note over External: Receives form link
(via your app/email) External->>Engine: POST /forms/:runId/:stepId/submit Engine->>Engine: Validate expectedkeys Engine->>Engine: Run status → "running" Engine->>Workflow: Resume with form data in context ` Submitting Form Data ==================== `bash curl -X POST https://apisvr.tryhyphen.com/forms/{runId}/0/submit \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "data": { "shippingaddress": "123 Main St, City, ST 12345", "deliverydate": "2025-02-15", "specialinstructions": "Leave at door" } }' ` api POST /forms/:runId/:stepId/submit Submit form data for a paused PbotForm step. api GET /forms/pending List all pending form requests across the organization. Context After Submission ======================== The submitted data is added directly to the execution context. Each key from the submission becomes accessible via @path: `json { "type": "scheduledelivery", "properties": { "address": "@shippingaddress", "date": "@deliverydate", "notes": "@specialinstructions" } } ` TTL and Expiration ================== When ttlseconds is set, the form expires after the specified duration. If the form is not submitted before expiration, the workflow run fails with a timeout error. Use TTL for time-sensitive workflows where stale input would be harmful — for example, a vendor onboarding form that's only valid for 24 hours. Reminder Intervals ================== The reminderintervals array specifies when reminders should be sent (in seconds after the form is created). This triggers reminder webhook events that your application can use to send follow-up notifications. `json { "reminderintervals": [3600, 7200, 43200] } ` This sends reminders at 1 hour, 2 hours, and 12 hours after the form is created. PbotForm vs External Forms ========================== Hyphen has two form mechanisms: | Feature | PbotForm | External Forms | |---------|----------|----------------| | Purpose | Collect input within a workflow | Standalone data collection | | Tied to a run | Yes — pauses and resumes a workflow | No — independent | | Created via | Workflow step | POST /external-forms` | | Submissions | Single submission resumes the run | Multiple submissions collected | | Best for | Vendor provides shipping details mid-workflow | Customer feedback survey | → Next: Custom Table (/primitives/custom-table) --- ## PATH: Sdk > Theming (Source: sdk/04-theming.md) Theming ======= All SDK components use CSS custom properties for styling and render inside Shadow DOM. Override the defaults on the host element or a parent container to match your brand. Basic Usage =========== ``css hyphen-task-sidebar { --hyphen-color-text: #1a1a1a; --hyphen-color-bg: #ffffff; --hyphen-color-border: #e5e5e5; --hyphen-color-bg-hover: #f0f0f0; --hyphen-font-family: 'Inter', sans-serif; --hyphen-radius: 8px; } ` Properties set on a parent element cascade to all SDK components within it: `css / Apply to all components at once / .ops-console { --hyphen-font-family: 'Inter', sans-serif; --hyphen-color-text: #1a1a1a; --hyphen-color-bg: #ffffff; --hyphen-radius: 8px; } ` Custom Properties Reference =========================== Typography ========== | Property | Default | Description | |----------|---------|-------------| | --hyphen-font-family | System stack | Font family for all text | | --hyphen-font-size | 14px | Base font size | Colors ====== | Property | Default | Description | |----------|---------|-------------| | --hyphen-color-text | #1a1a1a | Primary text | | --hyphen-color-text-secondary | #6b6b6b | Secondary text (labels, timestamps) | | --hyphen-color-bg | #fff | Component background | | --hyphen-color-bg-secondary | #f7f7f7 | Alternate background (table rows, sections) | | --hyphen-color-bg-hover | #f0f0f0 | Hover state background | | --hyphen-color-border | #e5e5e5 | Border color | | --hyphen-color-focus | #000 | Focus ring color | | --hyphen-color-error | #dc2626 | Error states and destructive actions | | --hyphen-color-success | #16a34a | Success states and confirmations | Layout ====== | Property | Default | Description | |----------|---------|-------------| | --hyphen-radius | 6px | Border radius for cards and inputs | | --hyphen-shadow | 0 1px 2px rgba(0,0,0,0.05) | Card shadow | Dark Mode ========= Override the color properties to implement dark mode: `css @media (prefers-color-scheme: dark) { .ops-console { --hyphen-color-text: #e5e5e5; --hyphen-color-text-secondary: #a0a0a0; --hyphen-color-bg: #1a1a1a; --hyphen-color-bg-secondary: #2a2a2a; --hyphen-color-bg-hover: #333333; --hyphen-color-border: #404040; --hyphen-color-focus: #e5e5e5; --hyphen-shadow: 0 1px 2px rgba(0,0,0,0.3); } } ` Per-Component Overrides ======================= Each component can be styled independently: `css / Compact sidebar / hyphen-task-sidebar { --hyphen-font-size: 13px; --hyphen-radius: 4px; } / Larger data grid / hyphen-data-grid { --hyphen-font-size: 15px; } ` Shadow DOM Isolation ==================== Components render inside Shadow DOM. This means: - Your page styles don't leak in. A table { border: 2px solid red } rule on your page won't affect the data grid. - Component styles don't leak out. SDK styles won't interfere with your page layout. - CSS custom properties pass through. This is the only way to style SDK components — by design. You cannot target internal elements with external CSS selectors (like hyphen-data-grid td`). Use the custom properties listed above for all styling customization. --- ## PATH: Templates > Kyc Customer Onboarding (Source: templates/04-kyc-customer-onboarding.md) KYC Customer Onboarding ======================= Agent-as-orchestrator pattern. Unlike the matching-first templates, this one starts with an AI agent that coordinates multiple verification workflows, synthesizes results across them, and escalates when signals conflict. What Gets Automated =================== Identity verification, sanctions screening, adverse media scanning, and risk scoring — run as separate sub-workflows coordinated by an orchestrator agent. The agent synthesizes pass/fail/flag signals across all checks, identifies conflicts, and makes an onboarding recommendation. What Humans Still Own ===================== Final approval on flagged or conflicted cases. Enhanced due diligence investigations. Policy exception decisions for high-profile applicants. Pipeline ======== ``mermaid flowchart TD A[Input: Applicant Data] --> B[Orchestrator Agent] B --> C[runworkflow: Identity Verification] B --> D[runworkflow: Sanctions Screening] B --> E[runworkflow: Adverse Media Scan] C -->|pass / fail / flag| F[Agent Synthesizes Results] D -->|pass / fail / flag| F E -->|pass / fail / flag| F F -->|All pass| G[Auto-Approve] F -->|Conflict detected| H{Agent Resolves} F -->|Hard fail| I[Auto-Deny] H -->|Resolved| J[Approve with Note] H -->|Unresolvable| K[pauseforhuman: Compliance Review] K -->|Approved| L[Provision Account] K -->|Denied| M[Reject with Reason] K -->|EDD required| N[runworkflow: Enhanced Due Diligence] N --> K G --> O[Custom Table: Onboarding Log] J --> O L --> O M --> O I --> O style B fill:#4ade80,color:#09090b,stroke:none style K fill:#60a5fa,color:#09090b,stroke:none style C fill:#e8a84c,color:#09090b,stroke:none style D fill:#e8a84c,color:#09090b,stroke:none style E fill:#e8a84c,color:#09090b,stroke:none ` This template demonstrates Pattern C: Agent as Orchestrator (/agents/deployment-patterns/agent-as-orchestrator). The agent coordinates multiple sub-workflows using _runworkflow_ and synthesizes their results to make a decision. Workflow Definition =================== `json { "name": "kyccustomeronboarding", "definition": { "actions": [ { "type": "loop", "properties": { "mode": "react", "objective": "Onboard applicant {{input.applicantname}} ({{input.applicantid}}). Execute verification sequence: 1) Run identity verification workflow, 2) Run sanctions screening workflow, 3) Run adverse media scan workflow, 4) Synthesize results — if all pass, approve with risk score. If any conflict (e.g., identity passes but sanctions flags a partial name match), investigate the discrepancy. If confidence below 0.8, pause for human compliance review with full context and reasoning. 5) Provide final recommendation with risk level (low/medium/high).", "tools": [ { "type": "workflow", "name": "identityverification" }, { "type": "workflow", "name": "sanctionsscreening" }, { "type": "workflow", "name": "adversemediascan" } ], "maxiterations": 15, "timeoutms": 300000, "onstuck": { "iterations": 4, "action": "escalate" }, "resultkey": "onboardingdecision" } }, { "type": "loop", "filter": { "condition": { "equal": ["@onboardingdecision.answer.decision", "approved"] } }, "properties": { "mode": "react", "objective": "Provision account for approved applicant {{input.applicantid}}. Create account, assign tier based on risk score, and trigger welcome sequence.", "tools": [ { "type": "action", "name": "createcustomeraccount" }, { "type": "action", "name": "assignaccounttier" }, { "type": "workflow", "name": "welcomesequence" } ], "maxiterations": 5, "resultkey": "provisioningresult" } }, { "type": "gmailsend", "filter": { "condition": { "equal": ["@onboardingdecision.answer.decision", "approved"] } }, "properties": { "_oauthaccount_": "onboarding@company.com", "to": "@input.applicantemail", "subject": "Welcome — your account is ready", "body": "Your account has been verified and provisioned." }, "onFalse": { "type": "gmailsend", "properties": { "oauthaccount_": "onboarding@company.com", "to": "@input.applicantemail", "subject": "Application update", "body": "We were unable to complete your application. A team member will follow up within 2 business days." } } }, { "type": "custom-table", "properties": { "table": "onboardinglog", "operation": "write", "keys": ["applicantid"], "values": ["@input.applicantid"], "fields": { "decision": "@onboardingdecision.answer.decision", "riskscore": "@onboardingdecision.answer.risklevel", "confidence": "@onboardingdecision.confidence", "runid": "@runid", "completedat": "@now" } } } ] } } ` Required Workflows ================== These workflows are triggered by the orchestrator agent via runworkflow_: | Workflow | Purpose | |----------|---------| | identityverification | Document verification, liveness check, PII validation | | sanctionsscreening | OFAC, EU, UN sanctions list matching | | adversemediascan | News and media screening for negative coverage | | enhancedduediligence | Deep investigation for flagged applicants | Required Registered Actions =========================== | Action | Kind | Purpose | |--------|------|---------| | createcustomeraccount | http | Provision account in your system | | assignaccounttier | http | Set account tier based on risk assessment | Customization Notes =================== Confidence threshold. The 0.8 confidence threshold in the objective controls when the agent escalates to a human. Lower to 0.7 for a more cautious approach; raise to 0.9 if you trust the verification workflows and want fewer escalations. Timeout. 300 seconds (5 minutes) covers the round-trip time for multiple sub-workflow executions. Extend if your verification providers have slow response times. Iterations. 15 iterations is higher than other templates because the orchestrator needs to run multiple workflows, store intermediate results, synthesize, and potentially escalate. Don't reduce below 10. Sub-workflows. Each verification workflow (identityverification, sanctions_screening`, etc.) must be created separately in Hyphen before running this template. They can be simple HTTP-action workflows that call your verification providers. --- ## PATH: Actions > Custom Table (Source: actions/05-custom-table.md) Custom Table Actions ==================== Custom table actions register pre-configured table operations. Like the Custom Table primitive (/primitives/custom-table), they read and write to Hyphen-managed tables — but as registered actions, they can be reused by name across workflows and called by agents as tools. Registration ============ ``bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "actionname": "logreconciliation", "kind": "custom-table", "description": "Log a reconciliation result to the audit table" }' ` Custom table actions are lightweight registrations — the specific table, operation, and fields are typically provided at the workflow step level or by the agent at runtime. Properties ========== | Property | Type | Required | Description | |----------|------|----------|-------------| | actionname | string | Yes | Unique name for this action | | kind | "custom-table" | Yes | | | description | string | No | Human-readable description (shown to agents) | Usage in a Workflow =================== When used as a workflow step, provide the table operation details in properties: `json { "type": "logreconciliation", "properties": { "table": "reconaudit", "operation": "write", "keys": ["runid", "timestamp"], "values": ["@_runid", "@now"], "fields": { "matchedcount": "@matched.length", "exceptioncount": "@exceptions.length", "status": "completed" } } } ` The operation types and field references work identically to the Custom Table primitive (/primitives/custom-table): read, write, update, and upsert. Usage as an Agent Tool ====================== `json { "mode": "react", "objective": "Check if this invoice was previously processed, and log the current result", "tools": [{ "type": "action", "name": "logreconciliation" }] } ` When used as an agent tool, the agent provides the table, operation, and fields in its actioninput. The agent sees the action's description to understand what the tool does. Example agent response: `json { "thought": "I should log this reconciliation result before completing.", "action": "logreconciliation", "actioninput": { "table": "reconaudit", "operation": "write", "keys": ["invoiceid"], "values": ["INV-001"], "fields": { "status": "matched", "confidence": 0.95 } } } ` Registered vs Primitive ======================= Use the custom-table primitive for simple, one-off table operations within a specific workflow: `json { "type": "custom-table", "properties": { "table": "log", "operation": "write", ... } } `` Use a registered custom-table action when you want agents to be able to use table operations as tools, or when the same table operation pattern is reused across multiple workflows. → Back to Actions overview (/actions) --- ## PATH: Agents > Reasoning Traces (Source: agents/05-reasoning-traces.md) Reasoning Traces ================ Every agent execution produces a reasoning trace — a complete record of every thought, action, input, and observation across all iterations. Traces are stored persistently and are queryable via API. Trace Format ============ Each iteration in the trace contains: ``json { "iteration": 1, "timestamp": "2026-02-01T10:30:01Z", "thought": "I need to look up the ticket details to understand the customer's issue.", "action": "lookupticket", "actioninput": { "ticketid": "TK-12345" }, "observation": { "subject": "Billing Error — Double Charge", "status": "open", "priority": "high", "customerid": "cust-789", "createdat": "2026-01-31T14:22:00Z" }, "success": true } ` | Field | Description | |-------|-------------| | iteration | Sequence number (1-indexed) | | timestamp | When this iteration executed | | thought | The agent's reasoning — why it chose this action | | action | Which tool was called | | actioninput | Parameters passed to the tool (sensitive fields automatically redacted) | | observation | The tool's response (truncated for large payloads) | | success | Whether the tool call succeeded | A complete trace is an array of these objects, one per iteration, plus metadata about the overall run. Accessing Traces ================ Get Full Trace ============== `bash curl https://apisvr.tryhyphen.com/agents/{agentRunId}/trace \ -H "X-Org-Id: acme-corp" ` api GET /agents/:id/trace Returns the complete reasoning trace for an agent run. Get Status with Trace ===================== Include the trace inline with the status response: `bash curl https://apisvr.tryhyphen.com/agents/{agentRunId}/status?includetrace=true \ -H "X-Org-Id: acme-corp" ` api GET /agents/:id/status?includetrace=true Returns agent status plus the full reasoning trace. Response ======== `json { "agentrunid": "agent-123e4567-e89b-12d3-a456-426614174000", "status": "completed", "objective": "Process support ticket TK-12345", "iterations": 4, "finalanswer": "Ticket resolved. Refund of $49.99 processed, confirmation email sent.", "confidence": 0.95, "triggeredruns": [], "createdat": "2026-02-01T10:30:00Z", "completedat": "2026-02-01T10:30:12Z", "errordetails": null, "reasoningtrace": [ { "iteration": 1, "thought": "...", "action": "lookupticket", "..." : "..." }, { "iteration": 2, "thought": "...", "action": "checkbilling", "..." : "..." }, { "iteration": 3, "thought": "...", "action": "processrefund", "..." : "..." }, { "iteration": 4, "thought": "...", "action": "complete", "..." : "..." } ] } ` Querying Traces =============== List Agent Runs =============== Filter by status to find runs that need review: `bash All paused agents (waiting for human input) =========================================== curl "https://apisvr.tryhyphen.com/agents?status=paused" \ -H "X-Org-Id: acme-corp" All failed agents ================= curl "https://apisvr.tryhyphen.com/agents?status=failed" \ -H "X-Org-Id: acme-corp" Recent runs with pagination =========================== curl "https://apisvr.tryhyphen.com/agents?limit=20&offset=0" \ -H "X-Org-Id: acme-corp" ` api GET /agents?status={status} List agent runs filtered by status: running, paused, completed, failed. Compliance Use Cases ==================== Reasoning traces serve critical compliance and governance functions: Regulatory audit. When a regulator asks "why was this transaction flagged?" you can show the exact reasoning chain — what the agent checked, what it found, and why it made its decision. Decision explainability. Every agent decision has a thought field explaining the reasoning. This satisfies explainability requirements in regulated industries (financial services, healthcare, insurance). Process verification. Traces prove that required steps were followed. If the process requires sanctions screening before account opening, the trace shows the screening tool was called and the result was checked. Incident investigation. When something goes wrong, the trace shows exactly where and why. You can see the agent's last thought, which tool failed, and what observation led to an incorrect decision. Human oversight documentation. When an agent pauses for human input, the trace records both the agent's question and the human's response. This documents the human-in-the-loop review for audit purposes. Secret Redaction ================ Secrets that appear in reasoning traces are automatically redacted before storage. This includes: - API keys and tokens referenced via orgconfig: - OAuth access tokens and refresh tokens - Database connection strings - Any value matching common secret patterns If an agent's thought or observation contains a secret value (because the LLM included it in its reasoning or a tool returned it), the redaction layer strips it: `json { "thought": "I'll call the API with the authentication token.", "action": "fetchdata", "actioninput": { "url": "https://api.example.com/data", "token": "[REDACTED]" }, "observation": { "status": 200, "data": { "..." : "..." } } } ` Redaction happens at storage time. The actual secret is used during execution but never persisted in the trace. Trace Storage ============= Traces are stored in the database alongside the agent run record. They are scoped per organization (same multi-tenant isolation (/platform/multi-tenancy) as all other data) and persist indefinitely unless explicitly deleted. For high-volume deployments, configure trace retention policies to manage storage. The includereasoning_trace property on the loop can be set to false` to disable trace storage for non-critical agents — though this is not recommended for production workflows that may need audit review. → Next: Deployment Patterns (/agents/deployment-patterns) --- ## PATH: Getting Started > Trust Safety (Source: getting-started/05-trust-safety.md) Trust & Safety Architecture =========================== Every enterprise deploying AI agents faces the same question: how do we trust a system that reasons on its own? The answer isn't better prompts. It's better infrastructure. Hyphen was built from day one to make AI agents auditable, bounded, and recoverable — so that enterprises can deploy autonomous AI in regulated environments without accepting unbounded risk. This document describes the security and reliability architecture that makes that possible. Governed Autonomy: The Core Principle ===================================== Most AI agent frameworks give the model an open canvas — any tool, any action, any number of steps. Hyphen inverts this. The workflow specification defines what an agent can do. The agent decides what it will do. Everything it does is recorded. We call this governed autonomy, and it's enforced at four levels: Structural permissioning. An agent can only use tools explicitly declared in its workflow definition. It cannot discover, invent, or invoke capabilities it hasn't been given. This isn't a policy layer that can be bypassed — it's an architectural constraint. An unlisted tool simply doesn't exist. Bounded execution. Every agent loop has a maximum iteration cap, a timeout, and stuck detection. If an agent repeats the same action without progress, the system intervenes automatically — terminating, retrying with guidance, or escalating to a human. There is no scenario where an agent runs indefinitely. Recursive depth limits. Agents can trigger sub-workflows, but nesting depth, per-agent trigger counts, and per-run trigger counts are all hard-capped. This prevents cascade failures and cost runaway by design. Complete reasoning traces. Every iteration captures what the agent thought, what action it chose, what parameters it used, and what result it observed. These traces are persisted, queryable, and exportable. When an auditor asks why did the system make this decision?, the answer is in the trace — not in a model's weights. Security Controls ================= Data Protection =============== Hyphen handles sensitive business data — financial records, customer information, API credentials — across multi-tenant deployments. The security model is designed accordingly. Automatic credential redaction. Before any data reaches an audit trail, reasoning trace, or log, it passes through multi-layer redaction. Field-name pattern matching and value-shape detection automatically replace sensitive data — passwords, tokens, keys, card numbers, and more — with typed redaction labels. Credentials never persist in plain text outside of encrypted storage. Encryption at rest. Organisation secrets — API keys, database credentials, OAuth tokens — are encrypted before storage using industry-standard authenticated encryption. The platform refuses to start without a properly configured encryption key. Multi-tenant isolation. Every API request is scoped to an organisation. All data — workflows, executions, credentials, agent runs, custom tables — is isolated at the query level. There is no cross-tenant access path. Network Security ================ SSRF protection. When agents make HTTP requests, all URLs are validated against blocked ranges before execution — cloud metadata endpoints, private networks, and loopback addresses are all rejected. DNS resolution is validated to prevent rebinding attacks. OAuth security. OAuth state parameters are cryptographically signed with nonces and expiration timestamps, preventing CSRF and replay attacks. Each organisation configures their own OAuth application credentials, maintaining credential isolation. Prompt Security =============== AI agents process untrusted input. Hyphen includes defence-in-depth measures against prompt injection: Injection detection. Input context is scanned for known injection patterns — instruction overrides, role-switching attempts, jailbreak phrases, prompt format manipulation, and tool invocation attempts. Detections are scored by severity and flagged in the agent context without halting execution, allowing the system to proceed with awareness. System prompt hardening. Agent system prompts include immutable security rules instructing the model to treat all user-provided data as untrusted, ignore embedded instructions, and never deviate from the declared objective. Untrusted data is bracketed with explicit markers to aid model-level disambiguation. Administrative Security ======================= All administrative endpoints — feature flags, metrics, and operational management — require a dedicated API key. If the key is not configured, admin endpoints are disabled entirely. The system fails closed. Reliability Engineering ======================= Hyphen is designed for unattended operation. Workflows run overnight, over weekends, and across time zones. The reliability architecture ensures that failures are detected, contained, and recovered — without human intervention in the common case. Crash Recovery ============== Every running workflow maintains a heartbeat. The platform continuously monitors for stale heartbeats — indicating the execution process has failed. Stale workflows are automatically re-orchestrated from their last checkpoint, up to a configurable recovery limit. Workflows that exceed the recovery limit are marked as failed with a clear diagnostic, rather than remaining in limbo indefinitely. This eliminates "zombie runs" — the most common operational failure in distributed workflow systems. Message Durability ================== Hyphen uses message queues for distributed step processing. All message handling includes: - Automatic retry with exponential backoff — Transient failures (network blips, temporary service outages) are retried with increasing delay and jitter, without operator intervention. - Dead-letter persistence — Messages that fail after all retries are saved to a persistent dead-letter store with full diagnostic context: the original payload, error details, retry history, and associated workflow run. - One-click replay — Once the root cause is fixed, operators can replay failed messages individually or in bulk through the admin API. The workflow resumes from where it left off. No message is silently lost. Every failure is visible and actionable. Idempotent Execution ==================== Distributed systems deliver messages at-least-once. Hyphen ensures that redelivered messages don't cause duplicate side effects. Each workflow step carries a unique idempotency key. If a step has already been processed, the duplicate is safely rejected. Race Condition Prevention ========================= Human-in-the-loop approvals and form submissions are protected against concurrent access. When two reviewers click "approve" simultaneously, only one succeeds. The second reviewer receives a clear notification that the decision has already been made. No double-approvals. No conflicting state. Resource Governance =================== Internal resources are bounded and pooled. The system cannot exhaust connection limits under sustained load. Connection failures are handled gracefully with automatic drain and recovery. Audit & Compliance ================== Hyphen produces three layers of audit data, all queryable via API: Reasoning traces — Every agent decision: thought process, tool selected, parameters used, result observed. Available per-run and per-step. Step event log — An append-only, immutable record of every workflow step lifecycle event: dispatched, started, completed, failed, skipped, retried. Events are never modified or deleted. Execution history — Complete run records with input payloads, step outputs, human decisions, and final results. Scoped per-organisation. For regulated industries, this means: - Examiners can reconstruct exactly how any decision was made - Compliance teams can query decision patterns across runs - No custom logging infrastructure is required — it's built in - Human approvals are captured as part of the permanent record with reviewer identity and timestamp Observability ============= Hyphen exposes metrics covering agent executions, tool usage, error rates, latency distributions, stuck detection events, and active run counts. Metrics are labelled by organisation, enabling per-tenant monitoring in multi-tenant deployments. Standard export formats are supported for integration with existing monitoring stacks. Deployment Model ================ Hyphen is infrastructure, not SaaS. It deploys into your environment — your VPC, your database, your message broker. Credentials never leave your perimeter. All configuration is via environment variables. Feature flags enable incremental rollout per-organisation. Rollback is instant — disable a flag and the capability is removed without redeployment. Summary ======= Hyphen doesn't make AI agents smarter. It makes them deployable. Governed autonomy means agents operate within explicit boundaries. Structural permissioning means they can only use tools you've declared. Reasoning traces mean every decision is auditable. Crash recovery means failures are transient, not terminal. Multi-tenant isolation means your data stays yours. The gap between "works in demo" and "runs in production" is trust infrastructure. That's what Hyphen is. --- ## PATH: Guides > Ai Workflow Generation (Source: guides/05-ai-workflow-generation.md) AI Workflow Generation ====================== Hyphen's AI compiler translates natural language descriptions into deterministic JSON workflow specifications. You describe the what. The AI produces the how — the step sequence, data references, conditions, agent objectives, and tool declarations. This guide walks through the generation flow: submitting a prompt, monitoring generation, reviewing the output, deploying the workflow, and iterating. Step 1: Submit a Generation Request =================================== Describe what you want the workflow to do in plain language: ``bash curl -X POST https://apisvr.tryhyphen.com/ai/generate-workflow \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Create a workflow that processes incoming invoices. For each invoice, match it against our payment records using PO number and vendor ID. If a match is found within 2% tolerance, auto-reconcile it. If no match is found, have an AI agent investigate the exception by looking up the purchase order and checking payment history. For invoices over $10,000 that the agent cannot resolve, require manager approval before writing off. Log everything to an audit table.", "llmOptions": { "model": "gpt-4o", "temperature": 0.7 } }' ` Response: `json { "generationid": "llmgen-123e4567-e89b-12d3-a456-426614174000", "status": "processing", "message": "Workflow generation started" } ` The generation runs asynchronously. Complex workflows with multiple steps, conditions, and agent configurations typically take 10–30 seconds. Step 2: Poll Generation Status ============================== `bash curl https://apisvr.tryhyphen.com/ai/generate-workflow/llmgen-123e4567-e89b-12d3-a456-426614174000/status \ -H "X-Org-Id: acme-corp" ` Response while processing: `json { "generationid": "llmgen-123e4567-e89b-12d3-a456-426614174000", "status": "processing" } ` Response when complete: `json { "generationid": "llmgen-123e4567-e89b-12d3-a456-426614174000", "status": "completed" } ` Step 3: Retrieve the Generated Spec =================================== `bash curl https://apisvr.tryhyphen.com/ai/generate-workflow/llmgen-123e4567-e89b-12d3-a456-426614174000 \ -H "X-Org-Id: acme-corp" ` The response includes the complete workflow definition, any actions that need to be registered, and custom tables to create: `json { "generationid": "llmgen-123e4567-e89b-12d3-a456-426614174000", "status": "completed", "hyphenworkflowdefinition": { "name": "invoiceprocessing", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.invoices", "right": "@input.payments", "matchOn": ["ponumber", "vendorid"], "tolerance": 0.02, "outputMatched": "reconciled", "outputUnmatchedLeft": "exceptions", "outputUnmatchedRight": "unmatchedpayments" } }, ... ] } }, "actionstoregister": [ { "actionname": "lookuppurchaseorder", "kind": "http", "description": "Look up purchase order details from ERP" } ], "customtablestocreate": [ { "name": "reconciliationaudit", "fields": [...] } ], "workflowdescription": "Invoice matching with agent exception handling and manager approval for high-value write-offs" } ` Review Checklist ================ Before deploying, verify: - Matcher configuration — Are the matchOn fields correct for your data schema? Is the tolerance appropriate? - Agent objective — Does it accurately describe the investigation you want? Are the right tools listed? - Conditions — Do the filter conditions match your business rules ($10K threshold, etc.)? - Action references — Do the action names match actions you've already registered, or do you need to register new ones? - Context paths — Are @path references pointing to the right data? Step 4: Deploy the Workflow =========================== Use the create-from-ai endpoint to create the workflow, register actions, and create tables in one call: `bash curl -X POST https://apisvr.tryhyphen.com/workflows/create-from-ai \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "hyphenworkflowdefinition": { ... }, "actionstoregister": [ ... ], "customtablestocreate": [ ... ], "workflowdescription": "Invoice matching with exception handling", "hasexception": false }' ` Response: `json { "processId": "wfcre-123e4567-e89b-12d3-a456-426614174000", "status": "processing" } ` Poll for completion: `bash curl https://apisvr.tryhyphen.com/workflows/generation-status/wfcre-123e4567-e89b-12d3-a456-426614174000 \ -H "X-Org-Id: acme-corp" ` Once complete, the workflow is live and ready to execute. Actions registered via create-from-ai are stubs — they have the correct names and kinds but may need their URLs, queries, or templates filled in. Review and update each registered action before executing the workflow with real data. Step 5: Iterate on the Prompt ============================= If the generated workflow doesn't match your requirements, refine the prompt and regenerate. Tips for better results: Be specific about data fields. Instead of "match invoices to payments," say "match on ponumber and vendorid` with 2% amount tolerance." Name your thresholds. "Invoices over $10,000" is clearer than "high-value invoices." Describe the agent's investigation steps. "Have the agent check the purchase order, look at payment history for the vendor, and search for duplicate invoices" gives the AI compiler clear tool requirements. Specify what humans should review. "Require manager approval for write-offs" is more precise than "add a human review step." Include scheduling if needed. "Run this workflow daily at 2 AM Eastern" generates the schedule block. Prompt Refinement Example ========================= First attempt: > "Create a workflow to process invoices" Too vague — the AI has to guess your matching criteria, exception handling, and approval rules. Refined: > "Create a workflow that matches incoming invoices to payment records on PO number and vendor ID with 2% amount tolerance and 5-day date window. Use fuzzy matching on vendor names at 85% threshold. For unmatched invoices, have an AI agent investigate by looking up the PO in our ERP, checking payment history, and searching for duplicates. If the agent's confidence is below 0.8, pause for human review. Log all results to a reconciliationaudit table. Run daily at 6 AM Eastern." This prompt gives the AI compiler everything it needs to produce a complete, accurate specification on the first attempt. --- ## PATH: Platform > Scheduling (Source: platform/05-scheduling.md) Scheduling ========== Workflows can run on a recurring schedule. Add a schedule object to the workflow definition. Configuration ============= ``json { "name": "dailyreconciliation", "definition": { "schedule": { "every": "1d", "at": "02:00", "timezone": "America/NewYork" }, "actions": [ ] } } ` Schedule Fields =============== | Field | Type | Required | Description | |-------|------|----------|-------------| | every | string | Yes | Interval — 30m, 1h, 6h, 1d, 7d | | at | string | No | Time of day in 24h format ("02:00", "14:30"). Only applies when every is 1d or longer | | timezone | string | No | IANA timezone ("America/NewYork", "Europe/London", "Asia/Tokyo"). Defaults to UTC | Examples ======== Daily at 2:00 AM Eastern: `json { "schedule": { "every": "1d", "at": "02:00", "timezone": "America/NewYork" } } ` Every 6 hours: `json { "schedule": { "every": "6h" } } ` Every 30 minutes: `json { "schedule": { "every": "30m" } } ` Weekly (every 7 days) at midnight UTC: `json { "schedule": { "every": "7d", "at": "00:00" } } ` How Scheduled Runs Work ======================= Scheduled workflows execute with an empty @input unless a default input payload is configured. The @now context variable reflects the timestamp at execution time. Each scheduled run creates a standard workflow run with its own run_id, full context, and audit trail — identical to manually triggered runs. If a scheduled run is still in progress when the next interval fires, the new run is queued. Runs do not overlap by default. Scheduling + conditions. You can combine schedule with a top-level condition` to create conditional scheduled workflows — for example, a daily run that only proceeds if there are new records to process. The schedule fires the workflow, but the condition gates execution. → Next: Multi-Tenancy (/platform/multi-tenancy) --- ## PATH: Primitives > Custom Table (Source: primitives/05-custom-table.md) Custom Table ============ Custom tables provide multi-tenant data storage within Hyphen. Create tables with typed fields, then read, write, update, and upsert records from workflows. Every mutation is audit-logged. Creating a Table ================ Tables are created via API before use in workflows: ``bash curl -X POST https://apisvr.tryhyphen.com/custom-tables \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "name": "reconciliationlog", "fields": [ { "name": "runid", "type": "text", "required": true }, { "name": "invoiceid", "type": "text", "required": true }, { "name": "amount", "type": "numeric", "required": false }, { "name": "status", "type": "text", "required": true }, { "name": "matched", "type": "boolean", "required": false }, { "name": "processedat", "type": "timestamptz", "required": false } ] }' ` api POST /custom-tables Create a new custom table with typed field definitions. Field Types =========== | Type | Description | Example Values | |------|-------------|----------------| | text | String value, optional maxLength | "INV-001", "pending" | | integer | Whole numbers | 42, 0, -5 | | numeric | Decimal numbers | 1500.50, 0.02 | | boolean | True/false | true, false | | timestamptz | Timestamp with timezone | "2025-01-15T10:30:00Z" | Usage in Workflows ================== Use type: "custom-table" as a workflow step with the desired operation. Write (Insert) ============== `json { "type": "custom-table", "properties": { "table": "reconciliationlog", "operation": "write", "keys": ["runid", "invoiceid"], "values": ["@runid", "@input.invoiceid"], "fields": { "amount": "@input.amount", "status": "processed", "matched": true, "processedat": "@now" } } } ` Read (Query) ============ `json { "type": "custom-table", "properties": { "table": "reconciliationlog", "operation": "read", "keys": ["invoiceid"], "values": ["@input.invoiceid"], "outputKey": "previousruns" } } ` Results are stored in context under the outputKey. Update ====== `json { "type": "custom-table", "properties": { "table": "reconciliationlog", "operation": "update", "keys": ["runid"], "values": ["@_runid"], "fields": { "status": "approved", "matched": true } } } ` Updates records matching the keys/values criteria with the new fields. Upsert (Insert or Update) ========================= `json { "type": "custom-table", "properties": { "table": "vendorpreferences", "operation": "upsert", "keys": ["vendorid"], "values": ["@input.vendorid"], "fields": { "preferredpayment": "@input.paymentmethod", "lastupdated": "@now" } } } ` If a record with the matching keys exists, it's updated. Otherwise, a new record is created. Properties Reference ==================== | Property | Type | Required | Description | |----------|------|----------|-------------| | table | string | Yes | Table name | | operation | string | Yes | "read", "write", "update", or "upsert" | | keys | string[] | Yes | Field names used to identify records | | values | array | Yes | Values corresponding to keys (supports @path) | | fields | object | No | Additional fields to write/update (supports @path values) | Direct API Access ================= Tables can also be accessed directly via REST API, outside of workflows: api GET /custom-tables/:tableName View all data in a custom table. api POST /custom-tables/:tableName/insert Insert a single record. api POST /custom-tables/:tableName/bulk-insert Insert multiple records at once. api PUT /custom-tables/:tableName/update Update a record by ID. Body: { "id": "", "updates": { "field": "value" } } Row-Based Endpoints =================== These endpoints provide SDK-compatible row-level access: api GET /custom-tables/:tableName/rows List all rows in the table. api POST /custom-tables/:tableName/rows Insert a single row. Body is the record object. api PATCH /custom-tables/:tableName/rows/:rowId Update a single row by ID. Body contains the fields to update. Audit Log ========= Every mutation (write, update, upsert) is logged in the table's audit trail. The audit log records who made the change, when, what the previous value was, and what the new value is. `bash curl -X GET https://apisvr.tryhyphen.com/custom-tables/reconciliationlog/audit \ -H "X-Org-Id: acme-corp" ` api GET /custom-tables/:tableName/audit View the audit log for a custom table. Multi-Tenant Isolation ====================== Custom tables are fully scoped per organization. A table named reconciliation_log in org acme-corp is completely separate from a table with the same name in org globex-inc`. There is no cross-org data access. Common Patterns =============== Workflow audit log: Write a record after every workflow run with key metrics — matched count, exception count, approval status, timestamp. Operational memory: Store intermediate results across workflow runs. An agent can read from a custom table to check whether an invoice was previously processed before investigating it again. State tracking: Track the status of long-running multi-step processes. Each workflow run updates the record as it progresses through stages. → Next: Actions (/actions) — registering reusable operations --- ## PATH: Sdk > Events (Source: sdk/05-events.md) Events & Real-Time ================== The SDK provides a pub/sub event system for cross-component communication and an SSE connection for real-time updates from the server. Event Subscription ================== Subscribe to events using sdk.on(): ``javascript // Listen for a specific event const unsubscribe = sdk.on('task:decided', (e) => { console.log('Task decided:', e.data); }); // Unsubscribe later unsubscribe(); ` Listen to all events with a wildcard: `javascript sdk.on('', (e) => { console.log([${e.type}], e.data); }); ` Event Types =========== Component Events ================ | Event | Data | Source | |-------|------|--------| | task:created | { runId, workflowName, urgency } | New task requires attention | | task:decided | { runId, stepId, approved, comment } | Task approved or rejected via sidebar | | table:updated | { table, rowId, changes } | Row modified via data grid | | run:status | { runId, status, documentId } | Workflow run status changed | | agent:launched | { agentRunId, objective, tools } | Agent execution started | | agent:iteration | { agentRunId, iteration } | Agent completed a reasoning step | | agent:paused | { agentRunId } | Agent paused, awaiting human input | | agent:completed | { agentRunId, finalAnswer, confidence, previousRunId? } | Agent finished successfully | | agent:failed | { agentRunId, status } | Agent failed, timed out, or was cancelled | Session Events ============== | Event | Data | Source | |-------|------|--------| | authenticated | { email, sessionId } | OTP login succeeded | | session:expired | null | Session token expired | System Events ============= | Event | Data | Source | |-------|------|--------| | error | { message, code } | SDK error occurred | Real-Time Updates (SSE) ======================= Enable server-sent events for live updates across all components: `javascript sdk.startEventStream(); ` When the event stream is active: - Task sidebar updates when new approval tasks are created or existing tasks are decided - Data grid reflects row changes made by workflows or other users - Doc feed shows real-time processing status as uploaded documents move through workflows Stop the event stream: `javascript sdk.stopEventStream(); ` Automatic reconnection. If the SSE connection drops (network interruption, server restart), the SDK automatically reconnects with exponential backoff. No manual intervention required. SDK API Reference ================= HyphenSDK.init(config) ======================== Initialize the SDK. Returns a promise that resolves to an SDK instance. `typescript interface HyphenSDKConfig { publishableKey: string; // Required. Your pklive key. baseUrl?: string; // Gateway URL. Default: '/api' onSessionExpired?: () => void; // Called when session expires. } ` Instance Methods ================ | Method | Description | |--------|-------------| | sdk.login() | Show the OTP login modal | | sdk.logout() | Clear the current session | | sdk.getSession() | Get the current session object | | sdk.getUser() | Get the authenticated user (null if anonymous) | | sdk.isAuthenticated() | Check if authenticated via OTP | | sdk.on(event, handler) | Subscribe to SDK events. Returns unsubscribe function. | | sdk.startEventStream() | Start real-time SSE event streaming | | sdk.stopEventStream() | Stop event streaming | | sdk.destroy() | Cleanup all resources (event stream, subscriptions, DOM) | Cross-Component Patterns ======================== Upload-to-Approval Pipeline =========================== When a document is uploaded, the triggered workflow may create approval tasks. Listen for both events to show the user the full pipeline: `javascript sdk.on('run:status', (e) => { if (e.data.status === 'paused') { showNotification('New task waiting for your review'); } }); sdk.on('task:decided', (e) => { showNotification(Task ${e.data.approved ? 'approved' : 'rejected'}); }); ` Refresh on External Changes =========================== If your application modifies data outside the SDK (e.g., via direct API calls), you can refresh individual components: `javascript // Components expose a refresh method via their DOM element document.querySelector('hyphen-data-grid').refresh(); document.querySelector('hyphen-task-sidebar').refresh(); ` Cleanup ======= When removing SDK components from the page (e.g., navigating away in a SPA), clean up resources: `javascript sdk.destroy(); `` This stops the event stream, removes all event subscriptions, and cleans up component state. --- ## PATH: Templates > Vendor Payment Reconciliation (Source: templates/05-vendor-payment-reconciliation.md) Vendor Payment Reconciliation ============================= Bank statement to AP ledger matching. Distinct from AP Invoice Reconciliation (/templates/ap-invoice-reconciliation) because the data is messier — bank descriptions rarely match vendor names cleanly, payments are often consolidated, and timing gaps are wider. Fuzzy matching and date windows do the heavy lifting. What Gets Automated =================== Matching bank transactions to AP ledger entries despite format mismatches. Investigating consolidated payments (one bank debit covering multiple invoices). Identifying timing differences between payment date and bank clearing date. Agent resolves partial payments and unknown transactions. What Humans Still Own ===================== Unidentified transactions after AI investigation. Month-end reconciliation sign-off. Bank fee disputes and error corrections. Pipeline ======== ``mermaid flowchart TD A[Input: Bank Statement + AP Ledger] --> B[Matcher: Fuzzy Match] B -->|Exact match ~50%| C[Auto-Clear] B -->|Fuzzy match ~25%| D{Within Tolerance?} B -->|No match ~25%| E[Exception Queue] D -->|Yes| F[Auto-Clear with Note] D -->|No| E E --> G[ReAct Agent: Investigate] G --> G1[Check consolidated payments] G1 --> G2[Search for timing delays] G2 --> G3[Fuzzy vendor name match] G3 -->|Identified| H[Auto-Resolve] G3 -->|Partial match| I[Human Review] G3 -->|Unidentified| I I -->|Matched| J[Manual Clear + Log] I -->|Unknown| K[Flag for Bank Inquiry] C --> L[Custom Table: Bank Recon Log] F --> L H --> L J --> L K --> L style B fill:#e8a84c,color:#09090b,stroke:none style G fill:#4ade80,color:#09090b,stroke:none style I fill:#60a5fa,color:#09090b,stroke:none ` This template includes a schedule block — it runs automatically every day at 6 AM Eastern. Remove or adjust the schedule for manual execution. Workflow Definition =================== `json { "name": "vendorpaymentreconciliation", "definition": { "schedule": { "every": "1d", "at": "06:00", "timezone": "America/NewYork" }, "actions": [ { "type": "matcher", "properties": { "left": "@input.banktransactions", "right": "@input.apledger", "matchOn": ["referencenumber"], "tolerance": 0.005, "dateWindowDays": 7, "fuzzyThreshold": 75, "descriptionKey": "description", "outputMatched": "cleared", "outputUnmatchedLeft": "unmatchedbank", "outputUnmatchedRight": "outstandingpayments" } }, { "type": "loop", "filter": { "condition": { "greaterThan": [{ "length": "@unmatchedbank" }, 0] } }, "properties": { "mode": "foreach", "itemspath": "@unmatchedbank", "itemvariablename": "txn", "actionstoexecute": [ { "type": "loop", "properties": { "mode": "react", "objective": "Investigate unmatched bank transaction: '{{txn.description}}' for ${{txn.amount}} on {{txn.date}}. Determine if this is: 1) A consolidated payment — search for AP entries that sum to this amount within ±7 days, 2) A timing delay — check for a matching AP entry in the next period, 3) A vendor name mismatch — the bank description may not match AP vendor name, try fuzzy matching, 4) A bank fee or interest charge. Provide: root cause, matched AP entries if found, and recommended action.", "tools": [ { "type": "action", "name": "searchapbyamount" }, { "type": "action", "name": "searchapbydaterange" }, { "type": "action", "name": "searchapbyvendor" }, { "type": "action", "name": "getknownbankfees" } ], "maxiterations": 8, "onstuck": { "iterations": 3, "action": "retrywithhint", "hint": "If you cannot identify the transaction, complete with recommendation to escalate for manual review." }, "resultkey": "investigation" } } ], "maxconcurrency": 5, "failurestrategy": "continueonerror", "collectresults": true, "resultkey": "bankinvestigations" } }, { "type": "PbotApproval", "filter": { "condition": { "greaterThan": [{ "length": "@unmatchedbank" }, 0] } }, "properties": { "comment": "Bank reconciliation: {{cleared.length}} auto-cleared, {{unmatchedbank.length}} investigated. Review findings and approve close entries.", "requestpayload": { "clearedcount": "@cleared.length", "outstandingcount": "@outstandingpayments.length", "investigations": "@bankinvestigations" } } }, { "type": "custom-table", "properties": { "table": "bankreconciliationlog", "operation": "write", "keys": ["runid", "reconciliationdate"], "values": ["@runid", "@now"], "fields": { "banktransactions": "@input.banktransactions.length", "autocleared": "@cleared.length", "investigated": "@unmatchedbank.length", "outstandingpayments": "@outstandingpayments.length", "status": "completed" } } } ] } } ` Required Registered Actions =========================== | Action | Kind | Purpose | |--------|------|---------| | searchapbyamount | db | Find AP entries matching an amount or sum of amounts | | searchapbydaterange | db | Find AP entries within a date window | | searchapbyvendor | db | Fuzzy search AP entries by vendor/payee name | | getknownbankfees | db | Retrieve known fee patterns for the bank account | Customization Notes =================== Fuzzy threshold. 75 is lower than other templates because bank descriptions are notoriously messy ("WIRE TRF ACME" vs. "Acme Corporation"). Lower to 65 if your bank formats are especially terse; raise to 85 if descriptions are reasonably clean. Date window. 7 days covers typical bank clearing delays. Extend to 10–14 days for international wires or ACH batches that clear slowly. Consolidated payment detection. The searchapbyamount action should support sum-matching — finding multiple AP entries whose amounts sum to the bank transaction amount. This is the most common exception type in bank reconciliation. Bank fee patterns. The getknownbank_fees` action returns known fee types and amounts for the bank account. Preloading common patterns (monthly maintenance fees, wire fees, etc.) lets the agent quickly classify these without investigation. --- ## PATH: Guides > Embedding Hyphen (Source: guides/06-embedding-hyphen.md) Embedding Hyphen ================ This guide covers the three supported integration modes: 1. Gateway mode (recommended for SaaS): https://apisvr.tryhyphen.com 2. Direct engine mode (common for private/on-prem): private engine URL + X-Org-Id 3. Ops Console SDK (recommended for UI embedding): drop-in Web Components Mode A: Gateway (Recommended) ============================= Use Hyphen Gateway as your public edge. Gateway responsibilities: - API key auth (sk-hyp-) - Tenant management token auth (mt-hyp-) - Rate limiting and usage logs - Injecting X-Org-Id before proxying to engine Onboarding flow =============== 1. Create account (self-serve): ``bash curl -X POST https://apisvr.tryhyphen.com/gateway/signup \ -H "Content-Type: application/json" \ -d '{ "email": "ops@acme.com", "name": "Acme Ops" }' ` 2. Store returned credentials securely: - managementtoken (mt-hyp-) for tenant admin API - apikey.key (sk-hyp-) for runtime API calls 3. Manage orgs and keys with management token: - GET /gateway/account - GET /gateway/orgs - POST /gateway/orgs - POST /gateway/orgs/:orgId/keys - POST /gateway/orgs/:orgId/keys/:keyId/rotate - DELETE /gateway/orgs/:orgId/keys/:keyId 4. Call workflow APIs with API key: `bash curl -X POST https://apisvr.tryhyphen.com/workflows/:id/execute \ -H "Authorization: Bearer sk-hyp-..." \ -H "Content-Type: application/json" \ -d '{ "input": { "customerid": "cust123" } }' ` Gateway strips the API key before forwarding and injects tenant context for the engine. Mode B: Direct Engine ===================== Use this when deploying engine privately (including on-prem). Engine auth contract: - Every tenant-scoped request must include X-Org-Id `bash curl -X POST https://your-onprem-domain/workflows/:id/execute \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "input": { "customerid": "cust123" } }' ` Admin engine routes require an admin API key header. Mode C: Ops Console SDK ======================= For teams that want operational UI without building it from scratch, the Ops Console SDK (/sdk) provides drop-in Web Components: `html ` The SDK authenticates with a publishable key (pklive*) — scoped to specific origins, workflows, and tables. Organisation context is resolved server-side from the key. The SDK never sends X-Org-Id directly. For the full setup guide, see SDK Quickstart (/sdk/quickstart). For component details, see SDK Components (/sdk/components). Approval UX Integration ======================= If you build your own approval UI (without the SDK): 1. Receive webhook pbotapprovalrequested 2. Fetch request via GET /approvals/:runId 3. Submit reviewer decision: `bash curl -X POST https://apisvr.tryhyphen.com/approvals/:runId/:stepId \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "approved": true, "comment": "Looks good" }' ` Webhook Contract ================ Webhook body format: `json { "event": "pbotapprovalrequested", "timestamp": "2026-03-03T10:30:00.000Z", "payload": { "runid": "run-...", "stepid": "2", "comment": "Review required" } } ` If webhook secret is configured, verify X-Hyphen-Signature` (HMAC SHA-256 over raw body). --- ## PATH: Platform > Multi Tenancy (Source: platform/06-multi-tenancy.md) Multi-Tenancy ============= Every Hyphen resource is scoped to an organization. Workflows, runs, actions, tables, OAuth credentials, and agent executions are all isolated per tenant. Organization Identification =========================== Every API call requires the X-Org-Id header: ``bash curl -X GET https://apisvr.tryhyphen.com/workflows \ -H "X-Org-Id: acme-corp" ` The header determines which tenant's data is accessed. There is no cross-org data leakage — a request with X-Org-Id: acme-corp will never see workflows belonging to X-Org-Id: globex-inc. api GET /workflows Returns only workflows belonging to the organization identified by X-Org-Id. Encrypted Secret Storage (Org Config) ===================================== Each organization stores API keys, database connection strings, and other secrets in encrypted org config. Values are stored encrypted at rest and never returned in plaintext via API. `bash Store a secret ============== curl -X POST https://apisvr.tryhyphen.com/org-config \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "key": "api:openaikey", "value": "sk-proj-abc123..." }' ` `bash List all config (values are masked) =================================== curl -X GET https://apisvr.tryhyphen.com/org-config \ -H "X-Org-Id: acme-corp" ` api POST /org-config Store or update an encrypted configuration key. api GET /org-config List all configuration keys (values masked). Key Naming Conventions ====================== | Prefix | Purpose | Example | |--------|---------|---------| | api: | Third-party API keys | api:openaikey, api:salesforcetoken | | db: | Database connection strings | db:orderspg, db:analyticsmongo | | oauth: | OAuth app credentials | oauth:gmailclientid | Referencing Secrets in Workflows ================================ Use the orgconfig: prefix to reference encrypted values in workflow steps. The execution engine resolves these at runtime — the actual secret value never appears in the workflow definition. `json { "actionname": "fetchcrmdata", "kind": "http", "url": "https://api.salesforce.com/query", "headers": { "Authorization": "Bearer orgconfig:api:salesforcetoken" } } ` `json { "actionname": "querywarehouse", "kind": "db", "datasource": "orgconfig:db:warehousepg", "query": "SELECT * FROM orders WHERE status = $1" } ` The orgconfig: prefix works in action registration, workflow properties, and any string value that the execution engine resolves. Data Isolation ============== Every data type is scoped per organization: | Resource | Isolation | |----------|-----------| | Workflows | Definitions visible only to owning org | | Runs | Execution history, context, and status per org | | Actions | Registered actions scoped per org | | Custom Tables | Table definitions and data per org | | OAuth Connections | Tokens and credentials per org | | Agent Runs | Agent executions, traces, and memory per org | | External Forms | Form definitions and submissions per org | | Org Config | Encrypted secrets per org | Credential Scoping ================== OAuth tokens are scoped to both the organization and the specific connected account. When an agent uses gmailsend, it sends from the account that was authorized for that org — not a shared platform account. `json { "type": "gmailsend", "properties": { "_oauthaccount": "ops@acme-corp.com", "to": "@input.recipient", "subject": "Invoice Update" } } ` The oauthaccount_ property identifies which connected account to use. Each org connects their own accounts through the OAuth flow. Multi-Tenant Embedding ====================== Hyphen's multi-tenancy is designed for platform embedding. If you're building a product that offers workflow automation to your customers, each of your customers becomes a Hyphen organization: ` Your Platform └── Customer A → X-Org-Id: customer-a └── Customer B → X-Org-Id: customer-b └── Customer C → X-Org-Id: customer-c ` Each customer's workflows, data, credentials, and agent executions are fully isolated. Your platform makes API calls on behalf of your customers by setting the appropriate X-Org-Id` header. → Next: Security (/platform/security) --- ## PATH: Templates > It Incident Response (Source: templates/06-it-incident-response.md) IT Incident Response ==================== Automate SOC alert triage and containment. The agent ingests security alerts, enriches indicators with threat intelligence, correlates across SIEM data, takes containment actions, and escalates to analysts when confidence is low. The reasoning trace is the incident report. Architecture ============ ``mermaid flowchart TD A["Security Alert Ingested
(SIEM, EDR, firewall)"] --> B["Matcher — Correlate
against known indicators"] B -->|"Known benign / duplicate"| F["✅ Auto-close"] B -->|"Unknown or suspicious"| C["ReAct Agent — Investigate
Enrich + correlate + recommend"] C -->|"Clear threat, auto-contain"| G["Containment Action
(block IP, isolate host)"] C -->|"Ambiguous / high severity"| D["Human Review
SOC Analyst"] D --> G G --> E["Audit Trail
Incident report auto-generated"] style B fill:#fef3c7,stroke:#d97706 style C fill:#dcfce7,stroke:#16a34a style D fill:#dbeafe,stroke:#2563eb ` Graduated response: Known-benign alerts auto-close (~70%). Clear threats get auto-containment with agent reasoning (~20%). Ambiguous or high-severity incidents escalate to SOC analysts (~10%). Required Actions ================ Register these before deploying the workflow: | Action | Kind | Purpose | |--------|------|---------| | enrichindicator | http | Query threat intel feeds (VirusTotal, AbuseIPDB, MISP) | | querysiem | http | Search SIEM for correlated events (Splunk, Elastic, Sentinel) | | lookupasset | http | Map IP/hostname to asset inventory and owner | | blockip | http | Add IP to firewall blocklist | | isolatehost | http | Trigger host isolation via EDR (CrowdStrike, SentinelOne) | | createticket | http | Open incident ticket in ServiceNow / Jira | | classifyseverity | llm | AI-assess severity from enrichment context | `bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: your-org" \ -H "Content-Type: application/json" \ -d '{ "actionname": "enrichindicator", "kind": "http", "url": "https://www.virustotal.com/api/v3/ipaddresses/{{indicator}}", "httpmethod": "GET", "headers": { "x-apikey": "orgconfig:api:virustotalkey" }, "passthrough": true }' ` `bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: your-org" \ -H "Content-Type: application/json" \ -d '{ "actionname": "classifyseverity", "kind": "llm", "template": "Given this security alert and enrichment data, classify severity as critical/high/medium/low and recommend containment action.\n\nAlert: {{alert}}\nEnrichment: {{enrichment}}\nAsset info: {{asset}}\n\nRespond with JSON: {\"severity\": \"...\", \"recommendation\": \"...\", \"confidence\": 0.0-1.0}", "model": "gpt-4", "maxtokens": 500 }' ` Workflow Definition =================== `json { "name": "incidentresponsepipeline", "definition": { "actions": [ { "type": "matcher", "properties": { "left": "@input.alerts", "right": "@input.knownbenignindicators", "matchOn": ["indicatorvalue"], "outputMatched": "knownbenign", "outputUnmatchedLeft": "alertstoinvestigate", "outputUnmatchedRight": "unusedbenign" } }, { "type": "loop", "filter": { "condition": { "greaterThan": [{ "length": "@alertstoinvestigate" }, 0] } }, "properties": { "mode": "foreach", "itemspath": "@alertstoinvestigate", "itemvariablename": "alert", "actionstoexecute": [ { "type": "loop", "properties": { "mode": "react", "objective": "Investigate this security alert and recommend containment:\n\nAlert type: {{alert.type}}\nIndicator: {{alert.indicatorvalue}}\nSource: {{alert.source}}\nTimestamp: {{alert.timestamp}}\n\nSteps: 1) Enrich the indicator with threat intelligence, 2) Look up the affected asset and its owner, 3) Query SIEM for correlated events in the last 24 hours, 4) Classify severity, 5) If critical/high severity with high confidence — take containment action (block IP or isolate host). If medium/low or uncertain — escalate for human review. 6) Complete with your assessment.", "tools": [ { "type": "action", "name": "enrichindicator" }, { "type": "action", "name": "querysiem" }, { "type": "action", "name": "lookupasset" }, { "type": "action", "name": "classifyseverity" }, { "type": "action", "name": "blockip" }, { "type": "action", "name": "isolatehost" }, { "type": "action", "name": "createticket" } ], "model": "gpt-4", "maxiterations": 12, "timeoutms": 300000, "onstuck": { "iterations": 3, "action": "escalate" }, "resultkey": "investigation" } } ], "maxconcurrency": 5, "failurestrategy": "continueonerror", "resultkey": "investigationresults" } }, { "type": "custom-table", "properties": { "table": "incidentlog", "operation": "write", "keys": ["runid", "timestamp"], "values": ["@runid", "@now"], "fields": { "alertsreceived": "@input.alerts.length", "autoclosed": "@knownbenign.length", "investigated": "@alertstoinvestigate.length", "status": "completed" } } } ] } } ` Structural Permissioning in Action ================================== This is where governed autonomy matters most. The agent's tool list is an architectural constraint, not a policy: | Tool declared | Agent can... | |---------------|-------------| | blockip | Block an IP address at the firewall | | isolatehost | Isolate a host via EDR | | createticket | Open an incident ticket | | Tool NOT declared | Agent cannot... | |-------------------|----------------| | deletefirewallrule | Modify existing firewall rules | | shutdownserver | Shut down production systems | | accesscredentialsvault | Read or modify stored secrets | The agent cannot discover or invent tools. It works with exactly the capabilities you've declared. If you want the agent to block IPs but not isolate hosts, remove isolatehost from the tools list. No prompt engineering required — it's structural. Sample Reasoning Trace ====================== ` Iteration 1: Thought: "Suspicious source indicator flagged by firewall. Let me check threat intel." Action: enrichindicator Input: { "indicator": "src-indicator-001" } Result: { "maliciousscore": 87, "tags": ["torexitnode", "scanning"], "lastseen": "2026-02-06" } Iteration 2: Thought: "Known Tor exit node with high malicious score. Let me check what asset it targeted." Action: lookupasset Input: { "assetref": "asset-045" } Result: { "hostname": "web-prod-03", "owner": "Platform Team", "criticality": "high" } Iteration 3: Thought: "High-criticality production server targeted by known malicious IP. Checking SIEM for more context." Action: querysiem Input: { "query": "sourceindicator=src-indicator-001 last 24h", "limit": 50 } Result: { "eventcount": 247, "patterns": ["portscan", "sshbruteforce"] } Iteration 4: Thought: "247 events including SSH brute force against a production server. This is critical. Blocking immediately." Action: blockip Input: { "indicator": "src-indicator-001", "reason": "Tor exit node — SSH brute force against web-prod-03" } Result: { "blocked": true, "ruleid": "fw-rule-8823" } Iteration 5: Thought: "IP blocked. Creating incident ticket for follow-up investigation." Action: createticket Input: { "severity": "high", "title": "SSH brute force from Tor exit node against web-prod-03", ... } Result: { "ticketid": "INC-4521" } Iteration 6: Thought: "Contained and documented. Completing with assessment." Action: complete Input: { "answer": "Critical: Tor exit node conducting SSH brute force against production server. IP blocked. Ticket INC-4521 created.", "confidence": 0.95 } ` This trace is the incident report. Every decision — why the IP was flagged, what intelligence confirmed it, what containment was taken — is queryable and auditable. Customization ============= Alert sources: Replace @input.alerts with webhook ingestion from your SIEM (Splunk, Elastic, Microsoft Sentinel, CrowdStrike). Threat intel feeds: Register additional enrichindicator actions for multiple feeds — VirusTotal, AbuseIPDB, MISP, Recorded Future. The agent will use whichever it deems most relevant. Containment actions: Control the blast radius by tuning the tool list. Start with blockip only, add isolatehost after trust is established. Escalation threshold: Adjust maxiterations and onstuck settings. For SOC teams with fast SLAs, set on_stuck.iterations: 2 with action: "escalate"` to route to analysts quickly. Compliance alignment: The audit table + reasoning trace satisfies SOC 2 CC7.3 (incident response), ISO 27001 A.16 (incident management), and NIST CSF RS.AN (response analysis). No additional logging required. --- ## PATH: Guides > Demo Playbook (Source: guides/07-demo-playbook.md) Hyphen — Hands-On Demo Playbook =============================== Time: ~20–25 minutes | Prerequisites: Terminal with curl and jq > All calls go through the Hyphen gateway. The gateway validates your API key and automatically injects your organization context — you never need to set X-Org-Id. Setup ===== Your sales engineer provided you with an API key for the Acme demo organization. Set these in your terminal: ``bash export BASEURL="https://apisvr.tryhyphen.com" export APIKEY="sk-hyp-YOURKEYHERE" ` Check gateway connectivity (no API key required): `bash curl -s $BASEURL/health | jq . Expected: { "status": "healthy", "service": "hyphen-gateway", ... } =================================================================== ` Verify your API key: `bash curl -s $BASEURL/gateway/verify \ -H "Authorization: Bearer $APIKEY" | jq . Expected: { "valid": true, "orgid": "...", "tier": "pro", ... } ================================================================ ` If valid is false, check the key with your Hyphen contact. Part 1: Describe a workflow in plain English (~8 min) ===================================================== Hyphen turns a natural-language description into a structured, executable workflow spec — no code required. Workflow A — Refund Eligibility Check ===================================== Step 1 — Generate the workflow: `bash curl -s -X POST $BASEURL/ai/generate-workflow \ -H "Authorization: Bearer $APIKEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "When a customer requests a refund, check if the order is within a 30-day return window. If within window, auto-approve the refund. If outside the window, pause for manager approval with the order details." }' | jq . ` Save the generation ID: `bash export GENA="llmgen-PASTEIDHERE" ` Step 2 — Poll until ready (may take 15–30 seconds): `bash curl -s $BASEURL/ai/generate-workflow/$GENA/status \ -H "Authorization: Bearer $APIKEY" | jq .status Repeat until: "completed" ========================= ` Step 3 — View the generated spec: `bash curl -s $BASEURL/ai/generate-workflow/$GENA \ -H "Authorization: Bearer $APIKEY" | jq . ` > What to look at: A full hyphenworkflowdefinition with conditional logic and approval steps — generated from one sentence. Step 4 — Materialize it (make it executable): `bash Capture the generated output ============================ GENOUTPUT=$(curl -s $BASEURL/ai/generate-workflow/$GENA \ -H "Authorization: Bearer $APIKEY") Sanity check — make sure it's a valid spec, not an error ======================================================== echo "$GENOUTPUT" | jq .hyphenworkflowdefinition.name Should print a workflow name. If it prints null, re-check GENA. ================================================================ Create the workflow =================== curl -s -X POST $BASEURL/workflows/create-from-ai \ -H "Authorization: Bearer $APIKEY" \ -H "Content-Type: application/json" \ -d "$GENOUTPUT" | jq . ` Save the process ID and poll: `bash export PROCA="wfcre-PASTEIDHERE" curl -s $BASEURL/workflows/generation-status/$PROCA \ -H "Authorization: Bearer $APIKEY" | jq . Look for "createdworkflowid" ============================== ` Save the workflow ID: `bash export WFLA="PASTEWORKFLOWIDHERE" ` Workflow B — Invoice–Payment Matching ===================================== Step 5 — Generate the second workflow: `bash curl -s -X POST $BASEURL/ai/generate-workflow \ -H "Authorization: Bearer $APIKEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Take a list of invoices and a list of payments, match them by invoice number with a 2% tolerance on amounts, and output the matched pairs, unmatched invoices, and unmatched payments." }' | jq . ` Save the generation ID: `bash export GENB="llmgen-PASTEIDHERE" ` Step 6 — Poll until ready: `bash curl -s $BASEURL/ai/generate-workflow/$GENB/status \ -H "Authorization: Bearer $APIKEY" | jq .status Repeat until: "completed" ========================= ` Step 7 — Capture, verify, and materialize: `bash GENOUTPUTB=$(curl -s $BASEURL/ai/generate-workflow/$GENB \ -H "Authorization: Bearer $APIKEY") Sanity check ============ echo "$GENOUTPUTB" | jq .hyphenworkflowdefinition.name curl -s -X POST $BASEURL/workflows/create-from-ai \ -H "Authorization: Bearer $APIKEY" \ -H "Content-Type: application/json" \ -d "$GENOUTPUTB" | jq . ` Save the process ID and poll: `bash export PROCB="wfcre-PASTEIDHERE" curl -s $BASEURL/workflows/generation-status/$PROCB \ -H "Authorization: Bearer $APIKEY" | jq . ` Save the workflow ID: `bash export WFLB="PASTEWORKFLOWIDHERE" ` Checkpoint: You now have two executable workflows built from English. `bash curl -s $BASEURL/workflows \ -H "Authorization: Bearer $APIKEY" | jq '.[].name' ` Part 2: Register two helper actions (~3 min) ============================================ These are reusable capabilities the AI agent will have access to. Action 1 — Summarize text (LLM-powered): `bash curl -s -X POST $BASEURL/actions \ -H "Authorization: Bearer $APIKEY" \ -H "Content-Type: application/json" \ -d '{ "actionname": "summarizetext", "kind": "llm", "template": "Summarize the following in 2 sentences:\n\n{{input.text}}", "model": "gpt-4o", "maxtokens": 150 }' | jq . ` Action 2 — Look up customer (HTTP): `bash curl -s -X POST $BASEURL/actions \ -H "Authorization: Bearer $APIKEY" \ -H "Content-Type: application/json" \ -d '{ "actionname": "lookupcustomer", "kind": "http", "url": "https://apisvr.tryhyphen.com/demo/customers/{{input.customerid}}", "httpmethod": "GET" }' | jq . ` > This calls a lightweight demo endpoint that returns scenario-appropriate customer data. Checkpoint: `bash curl -s $BASEURL/actions \ -H "Authorization: Bearer $APIKEY" | jq '.[].actionname' Should show: "summarizetext", "lookupcustomer" ================================================ ` Part 3: Register a webhook (~2 min) =================================== Hyphen fires webhooks for key lifecycle events. Register one so you can see what gets delivered when the agent pauses for human review. Register a webhook for approval requests: `bash curl -s -X POST $BASEURL/webhooks \ -H "Authorization: Bearer $APIKEY" \ -H "Content-Type: application/json" \ -d '{ "event": "pbotapprovalrequested", "url": "https://webhook.site/YOURUNIQUEID", "secret": "demo-signing-secret-123" }' | jq . ` > Tip: Get a free temporary webhook URL at webhook.site (https://webhook.site). Paste it above to see live deliveries. List your registered webhooks: `bash curl -s $BASEURL/webhooks \ -H "Authorization: Bearer $APIKEY" | jq . ` When the agent pauses for human review (Part 4), your webhook will receive a payload like this: `json { "event": "pbotapprovalrequested", "timestamp": "2026-03-04T15:30:45.123Z", "payload": { "runid": "run-abc123", "stepid": "step-1", "workflowid": "wf-xyz789", "comment": "Please review this for approval", "requestpayload": { "orderid": "ORD-8891", "customerid": "C-1", "amount": 340.00, "reason": "doublecharge" }, "status": "pending" } } ` Webhooks are signed with HMAC-SHA256 using the secret you provided — verify via the X-Hyphen-Signature header. Other available webhook events: pbotapprovaldecided, document.uploaded, externalformsubmitted Part 4: Launch an AI orchestrator (~7 min) ========================================== This is Pattern C from the article. An AI agent that reasons step-by-step, calling your workflows and actions as tools — but can only use the tools you declare. It cannot discover or invent capabilities at runtime. > Note: Tools like complete, _pauseforhuman, logprogress_, and memory are implicit — the engine auto-injects them into every agent. You don't declare them; they're always available. The tools array below only contains your four explicit tools. Launch the agent: `bash curl -s -X POST "$BASEURL/agents/execute?async=true" \ -H "Authorization: Bearer $APIKEY" \ -H "Content-Type: application/json" \ -d '{ "objective": "Customer C-1 filed complaint #5012. They say they were double-charged $340 for order ORD-8891. Summarize the complaint, look up the customer, check refund eligibility for the order, and match their recent invoices against payments. If anything looks off, pause for human review with your findings.", "tools": [ { "type": "action", "name": "summarizetext" }, { "type": "action", "name": "lookupcustomer" }, { "type": "workflow", "id": "'"$WFLA"'" }, { "type": "workflow", "id": "'"$WFLB"'" } ], "config": { "model": "gpt-4o", "maxiterations": 12 } }' | jq . ` Save the agent run ID: `bash export AGENTRUN="agent-PASTEIDHERE" ` Poll for progress: `bash curl -s "$BASEURL/agents/$AGENTRUN/status?includetrace=true" \ -H "Authorization: Bearer $APIKEY" | jq . ` > What to look at: The reasoningtrace array shows every Think → Act → Observe cycle. Watch the agent decide which tool to call next based on what it learned from the previous step. If the agent pauses for human review (check your webhook.site for the delivery): `bash curl -s -X POST $BASEURL/agents/$AGENTRUN/resume \ -H "Authorization: Bearer $APIKEY" \ -H "Content-Type: application/json" \ -d '{ "humaninput": "Approve the refund and flag for reconciliation review", "approved": true }' | jq . ` Then poll status again to watch the agent continue from where it stopped. What You Just Did ================= | Step | What happened | Why it matters | |------|--------------|----------------| | Part 1 | English → executable workflow | No dev team needed to build automation | | Part 2 | Registered reusable actions | Connect any API or AI model as a building block | | Part 3 | Registered a webhook | Real-time event delivery for approvals, docs, forms | | Part 4 | AI agent orchestrated everything | Agent reasons, but can only use declared tools — governed autonomy | Key things to notice in the reasoning trace: - The agent cannot call tools you didn't declare — that's architectural, not a prompt instruction - Every step is captured — full audit trail for compliance - When it paused for human review, no compute was consumed while waiting - The human decision is stored alongside the reasoning that triggered the escalation - If you registered a webhook, you saw the approval request arrive in real time Explore further =============== `bash Execute a workflow directly (no agent needed) ============================================= curl -s -X POST $BASEURL/workflows/$WFLA/execute \ -H "Authorization: Bearer $APIKEY" \ -H "Content-Type: application/json" \ -d '{ "input": { "orderid": "ORD-8891", "customerid": "C-1", "requestdate": "2026-03-04" } }' | jq . View the full reasoning trace ============================= curl -s "$BASEURL/agents/$AGENTRUN/trace" \ -H "Authorization: Bearer $APIKEY" | jq '.reasoningtrace[] | {thought, action}' List all webhooks ================= curl -s $BASEURL/webhooks \ -H "Authorization: Bearer $APIKEY" | jq . `` Questions? Reach out to your Hyphen contact or visit tryhyphen.com (https://tryhyphen.com) --- ## PATH: Platform > Security (Source: platform/07-security.md) Security ======== Hyphen's security model is architectural, not bolted on. Protections are enforced by the execution engine, not by policies that can be bypassed. Structural Permissioning ======================== Agents can only use tools explicitly declared in the workflow definition. This is enforced at the engine level — the agent's LLM prompt only includes declared tools, and the execution engine rejects any action call not in the allowlist. ``json { "tools": [{ "type": "action", "name": "lookupticket" }, { "type": "action", "name": "gmailsend" }] } ` With this declaration, the agent can look up tickets, send emails, and signal completion. It cannot access databases, post to Slack, trigger other workflows, or use any capability not in the list. If the LLM hallucinates a tool call to deletedatabase, the engine rejects it — the action isn't registered in the tool allowlist. This isn't a guardrail on top of broad access. The agent literally has no mechanism to invoke undeclared tools. Secret Leakage Prevention ========================= API keys, tokens, connection strings, and other secrets referenced via orgconfig: are automatically redacted from: - Workflow run context (returned by status endpoints) - Agent reasoning traces - Log output - Error messages If an agent's reasoning trace contains a secret value (because the LLM included it in its "thought"), the redaction layer strips it before storage. Secrets resolve at execution time only and are never persisted in plaintext. SSRF Protection =============== HTTP actions and agent tool calls that make outbound requests are protected against Server-Side Request Forgery (SSRF). The following are blocked: - Cloud metadata endpoints - Private network ranges - Loopback addresses Requests to blocked addresses fail with a clear error. This prevents agents from being tricked into accessing private infrastructure. Prompt Injection Defense ======================== Hyphen implements multiple layers of defense against prompt injection in ReAct agent loops: Input sanitization. User-provided data that flows into agent prompts is sanitized to prevent instruction override. Structured output parsing. The engine expects structured JSON responses from the LLM (thought, action, actioninput). Freeform responses that don't match the expected format are rejected, preventing the agent from being redirected by injected instructions. Tool allowlist enforcement. Even if a prompt injection convinces the LLM to attempt an unauthorized action, structural permissioning blocks it at the engine level. Fork Bomb Prevention ==================== Agents can trigger other workflows via _runworkflow_. To prevent infinite recursion (workflow A triggers workflow B which triggers workflow A), Hyphen enforces depth limits on nested workflow execution. If the nesting depth exceeds the configured limit, the execution fails with an error rather than running indefinitely. OAuth CSRF Protection ===================== The OAuth authorization flow uses signed JWT state tokens with short expiration times. This prevents cross-site request forgery attacks during the OAuth callback: - State token is generated server-side with a secret key - Token includes the org ID, provider, and expiration - Callback validates the token signature and expiration before exchanging the authorization code Rate Limiting ============= API requests are rate-limited per organization to prevent abuse and ensure fair resource allocation: | Resource | Limit | |----------|-------| | API requests | Per-org configurable | | Agent executions | Per-org configurable | | Concurrent workflow runs | Per-org configurable | | OAuth token refreshes | Per-provider limits | Rate limit headers are included in API responses. When limits are exceeded, requests return 429 Too Many Requests with a Retry-After header. Security Checklist ================== When deploying Hyphen in production: - Store all secrets in org config (never hardcode in workflow definitions) - Use the minimum necessary tool set for each agent (principle of least privilege) - Set appropriate maxiterations and timeoutms on ReAct loops - Configure onstuck recovery strategies to prevent runaway agents - Review agent reasoning traces for unexpected behavior patterns - Enable webhook notifications for pbotapprovalrequested` events to ensure human review is timely - Use top-level conditions to gate workflows that should only run under specific circumstances → Next: Primitives (/primitives) — the five built-in building blocks --- ## PATH: Templates > Contract Review (Source: templates/07-contract-review.md) Contract Review & Extraction ============================ Automate contract analysis at scale. The agent reads uploaded contracts, extracts key terms into structured data, compares against your standard playbook, flags deviations and risky clauses, and routes findings for legal review. Every extraction and flag is auditable. Architecture ============ ``mermaid flowchart TD A["Contract Uploaded
(PDF, DOCX)"] --> B["Matcher — Compare
extracted terms against playbook"] B -->|"Standard terms"| F["✅ Auto-approved"] B -->|"Deviations found"| C["ReAct Agent — Analyze
Classify risk, draft comments"] C -->|"Low risk, minor deviations"| F C -->|"High risk / novel clauses"| D["Legal Review
PbotApproval"] D --> F F --> E["Custom Table
Contract term database"] style B fill:#fef3c7,stroke:#d97706 style C fill:#dcfce7,stroke:#16a34a style D fill:#dbeafe,stroke:#2563eb ` Graduated review: Standard-term contracts auto-approve (~60%). Minor deviations get AI risk analysis and recommended redlines (~25%). Novel clauses, high-value agreements, or high-risk provisions escalate to legal counsel (~15%). Required Actions ================ | Action | Kind | Purpose | |--------|------|---------| | extractcontractterms | llm | Parse contract and extract structured key terms | | lookupplaybook | http | Fetch standard acceptable terms for this contract type | | assessclauserisk | llm | Classify risk level of a specific clause | | draftredline | llm | Generate suggested alternative language | | searchprecedent | http | Search past executed contracts for similar clauses | | logcontract | custom-table | Write extracted terms to contract database | `bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: your-org" \ -H "Content-Type: application/json" \ -d '{ "actionname": "extractcontractterms", "kind": "llm", "template": "Extract the following key terms from this contract as JSON:\n- parties (array of names)\n- effectivedate\n- terminationdate\n- autorenewal (boolean)\n- renewalnoticedays\n- governinglaw\n- liabilitycap (amount or \"unlimited\")\n- indemnificationscope (\"mutual\", \"one-way\", \"none\")\n- paymentterms\n- slacommitments (array)\n- terminationforconvenience (boolean)\n- changeofcontrolclause (boolean)\n- datahandlingprovisions (summary)\n- noncompetescope (summary or \"none\")\n\nContract text:\n{{contracttext}}\n\nRespond with valid JSON only.", "model": "gpt-4", "maxtokens": 2000 }' ` `bash curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: your-org" \ -H "Content-Type: application/json" \ -d '{ "actionname": "assessclauserisk", "kind": "llm", "template": "Assess the risk of this contract clause for our organization.\n\nClause: {{clausetext}}\nOur standard position: {{standardposition}}\nContract value: {{contractvalue}}\n\nClassify as:\n- low: Minor deviation, commercially acceptable\n- medium: Notable deviation, should be negotiated but not a blocker\n- high: Material risk, requires legal review before signing\n- critical: Unacceptable as written, must be changed\n\nRespond with JSON: {\"risklevel\": \"...\", \"reasoning\": \"...\", \"recommendation\": \"...\"}", "model": "gpt-4", "maxtokens": 500 }' ` Workflow Definition =================== `json { "name": "contractreviewpipeline", "definition": { "actions": [ { "type": "extractcontractterms", "properties": { "contracttext": "@input.contracttext" }, "outputKey": "extractedterms" }, { "type": "matcher", "properties": { "left": "@extractedterms.clauses", "right": "@input.playbookterms", "matchOn": ["clausetype"], "fuzzyThreshold": 80, "descriptionKey": "clausetext", "outputMatched": "standardclauses", "outputUnmatchedLeft": "deviations", "outputUnmatchedRight": "missingclauses" } }, { "type": "loop", "filter": { "condition": { "or": [ { "greaterThan": [{ "length": "@deviations" }, 0] }, { "greaterThan": [{ "length": "@missingclauses" }, 0] } ] } }, "properties": { "mode": "react", "objective": "Review these contract deviations and missing clauses.\n\nDeviations from playbook: {{deviations}}\nMissing standard clauses: {{missingclauses}}\nContract value: {{input.contractvalue}}\nCounterparty: {{input.counterparty}}\n\nFor each deviation: 1) Assess the risk level, 2) Search for precedent in past contracts, 3) If high/critical risk, draft redline language. 4) For missing clauses, note whether they're required for this contract type. 5) Complete with a summary and overall risk score.", "tools": [ { "type": "action", "name": "assessclauserisk" }, { "type": "action", "name": "searchprecedent" }, { "type": "action", "name": "draftredline" } ], "model": "gpt-4", "maxiterations": 15, "timeoutms": 300000, "onstuck": { "iterations": 4, "action": "escalate" }, "resultkey": "reviewanalysis" } }, { "type": "PbotApproval", "filter": { "condition": { "or": [ { "greaterThan": ["@input.contractvalue", 100000] }, { "in": ["@reviewanalysis.overallrisk", ["high", "critical"]] } ] } }, "properties": { "comment": "Contract review complete for {{input.counterparty}}. Value: ${{input.contractvalue}}. Risk: {{reviewanalysis.overallrisk}}. Review AI analysis and approve or request changes.", "requestpayload": { "counterparty": "@input.counterparty", "contractvalue": "@input.contractvalue", "extractedterms": "@extractedterms", "deviations": "@deviations", "missingclauses": "@missingclauses", "aianalysis": "@reviewanalysis" } } }, { "type": "custom-table", "properties": { "table": "contractregistry", "operation": "write", "keys": ["contractid"], "values": ["@input.contractid"], "fields": { "counterparty": "@input.counterparty", "contractvalue": "@input.contractvalue", "effectivedate": "@extractedterms.effectivedate", "terminationdate": "@extractedterms.terminationdate", "liabilitycap": "@extractedterms.liabilitycap", "overallrisk": "@reviewanalysis.overallrisk", "deviationscount": "@deviations.length", "reviewedat": "@now", "reviewerapproved": "@_approved" } } } ] } } ` What the Agent Investigates =========================== For each deviation from your standard playbook, the agent: 1. Classifies risk. Uses the assessclauserisk LLM action with your standard position as context. A liability cap of $1M on a $50K contract is low risk. Unlimited liability on a $500K contract is critical. 2. Searches precedent. Queries your contract database for past agreements with similar deviations. "We accepted a 12-month non-compete from Vendor X in 2024" provides negotiating context. 3. Drafts redlines. For high/critical risks, generates alternative language that protects your position while remaining commercially reasonable. 4. Flags missing clauses. If your playbook requires a data processing addendum and the contract doesn't include one, the agent notes this as a gap requiring attention. Sample Reasoning Trace ====================== ` Iteration 1: Thought: "3 deviations found. Starting with the most concerning: unlimited liability clause." Action: assessclauserisk Input: { "clausetext": "Vendor shall be liable for all damages without limitation...", "standardposition": "Liability capped at 2x annual contract value", "contractvalue": "250000" } Result: { "risklevel": "critical", "reasoning": "Unlimited liability on $250K contract exposes us...", "recommendation": "Require cap at 2x annual value ($500K)" } Iteration 2: Thought: "Critical risk. Let me check if we've accepted unlimited liability from this vendor before." Action: searchprecedent Input: { "counterparty": "TechVendor Inc", "clausetype": "liability" } Result: { "found": true, "precedent": "2024 contract: liability capped at $300K" } Iteration 3: Thought: "We had a cap last time. This is a regression. Drafting redline." Action: draftredline Input: { "original": "Vendor shall be liable for all damages without limitation...", "position": "Cap at 2x annual value, consistent with 2024 agreement" } Result: { "suggestedlanguage": "Vendor's aggregate liability shall not exceed two times the annual fees..." } Iteration 4: Thought: "Logging progress and moving to next deviation." Action: logprogress Input: { "message": "Liability clause: CRITICAL. Redline drafted. Precedent: 2024 cap at $300K." } ... [continues for remaining deviations] ... Iteration 8: Thought: "All deviations reviewed. 1 critical (liability), 1 medium (auto-renewal), 1 low (governing law)." Action: complete_ Input: { "answer": "Overall risk: HIGH. 1 critical clause (unlimited liability — redline drafted), 1 medium (auto-renewal without notice period), 1 low (governing law difference). Recommend negotiation before signing.", "confidence": 0.9 } ` Customization ============= Contract types: Adjust the extractcontractterms template for your contract types — SaaS agreements, NDAs, MSAs, SOWs, vendor agreements. Each type can have a different extraction schema. Playbook terms: The @input.playbookterms dataset is your standard acceptable positions. Maintain this as a custom table that legal updates as policies change. Risk thresholds: The approval filter triggers on contracts over $100K or high/critical risk. Adjust to your organization's materiality thresholds. Approval routing: For different contract types, configure different approval flows — procurement approves vendor agreements, legal approves customer-facing terms, finance approves payment terms beyond standard. Term database: The contract_registry` custom table builds a searchable database of all extracted terms across every contract. This enables portfolio-level analysis — "how many contracts have unlimited liability?" — and feeds the precedent search tool. --- ## PATH: Platform > Document Storage (Source: platform/08-document-storage.md) Document Storage ================ Hyphen provides built-in document storage so workflows can operate on uploaded files rather than requiring data to be embedded in API payloads. Upload a CSV, PDF, or JSON file once, get back a documentid, and reference it anywhere in your workflow using the doc: prefix. This separates data ingestion from workflow execution — no base64-encoded payloads, no external URL fetching at runtime, no SSRF risk. Uploading Documents =================== Upload via multipart form data: ``bash curl -X POST https://apisvr.tryhyphen.com/documents \ -H "X-Org-Id: acme-corp" \ -F "file=@transactionsq4.csv" \ -F 'name=Q4 Transactions' \ -F 'tags=["finance", "quarterly"]' \ -F 'metadata={"department": "accounting"}' \ -F 'ttldays=90' ` Response: `json { "document": { "id": "doca1b2c3d4e5f6", "name": "Q4 Transactions", "contenttype": "text/csv", "sizebytes": 245890, "checksumsha256": "e3b0c44298fc...", "currentversion": 1, "status": "ready", "tags": ["finance", "quarterly"], "createdat": "2026-02-01T00:00:00Z" } } ` Supported Content Types ======================= | Content Type | Extensions | Max Size | |-------------|-----------|----------| | CSV | .csv | 50 MB | | JSON | .json | 50 MB | | Plain text | .txt | 50 MB | | PDF | .pdf | 50 MB | | Excel | .xlsx, .xls | 50 MB | | Images | .png, .jpg, .jpeg | 50 MB | Deduplication ============= If you upload a file with the same SHA-256 checksum as an existing document in your org, Hyphen returns the existing documentid instead of creating a duplicate. This is transparent — you get back a 201 with the existing document, and the deduplicated: true flag indicates what happened. Upload from URL =============== For files already hosted elsewhere: `bash curl -X POST https://apisvr.tryhyphen.com/documents/from-url \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "url": "https://storage.example.com/reports/q4.csv", "name": "Q4 Report", "tags": ["finance"] }' ` Hyphen fetches the file, validates it, and stores it internally. The external URL is not accessed at workflow execution time. Referencing Documents in Workflows ================================== Use the doc: prefix anywhere you would normally pass inline data: `json { "type": "matcher", "properties": { "left": "doc:doca1b2c3d4e5f6", "right": "doc:docx7y8z9w0v1u2", "matchOn": ["invoiceid"], "tolerance": 0.02, "outputMatched": "matched", "outputUnmatchedLeft": "unmatchedinvoices" } } ` At execution time, the engine resolves doc: references by streaming the file from managed storage and parsing it based on content type. Resolution by Content Type ========================== | Content Type | Resolves To | Example Use | |-------------|-------------|-------------| | text/csv | Array — header row becomes keys | Matcher left/right, Loop itemspath | | application/json | Object or Array as-is | Any property expecting structured data | | text/plain | String | LLM template input, text analysis | | application/pdf | Binary handle: { docid, contenttype, storagekey, sizebytes } | Agent tool input for document processing | | image/* | Binary handle | Agent tool input for image analysis | | Excel (.xlsx) | Array — first sheet, header row becomes keys | Same as CSV | CSV parsing is strict. UTF-8 only, comma-delimited, first row as header, consistent column counts. Malformed files fail at upload time with diagnostic errors (e.g., "Row 47 has 6 columns, expected 5 based on header row"). Fix the file and re-upload. Using doc: with Loop ====================== `json { "type": "loop", "properties": { "mode": "foreach", "itemspath": "doc:doccustomerlist", "itemvariablename": "customer", "actionstoexecute": [ { "type": "sendwelcomeemail", "properties": { "email": "@customer.email" } } ] } } ` The CSV resolves to an array, each row becomes @customer inside the loop. Versioning ========== Upload a new version of an existing document without changing the documentid: `bash curl -X POST https://apisvr.tryhyphen.com/documents/doca1b2c3d4e5f6/versions \ -H "X-Org-Id: acme-corp" \ -F "file=@transactionsq4updated.csv" \ -F 'changenote=Updated with December corrections' ` The currentversion increments automatically. Workflows referencing doc:doca1b2c3d4e5f6 always get the latest version. Pinning to a Version ==================== For audit reproducibility, pin a specific version: `json { "left": "doc:doca1b2c3d4e5f6@2" } ` The @N suffix locks to version N. This is important for regulated workflows where you need to prove exactly which dataset was used in a given run. Reading Version History ======================= Use the document audit endpoint for version history: `bash curl https://apisvr.tryhyphen.com/documents/doca1b2c3d4e5f6/audit \ -H "X-Org-Id: acme-corp" ` The audit stream includes versioncreated entries with version metadata. Webhook Triggers ================ Register webhooks to trigger your own automation service when documents are uploaded: `json { "event": "document.uploaded", "url": "https://automation.example.com/hyphen/hooks/document-uploaded", "filter": { "tags": ["invoices"], "contenttype": "text/csv" }, "autopayload": { "workflowid": "wfl-123e4567-e89b-12d3-a456-426614174000" } } ` Your automation service can then call POST /workflows/:id/execute with: `json { "input": { "invoices": "doc:" } } ` This enables the upload-to-execution pattern: data providers drop files, workflows run automatically. See Webhooks (/integrations/webhooks) for full configuration. Audit Trail =========== Every document action is tracked: | Event | When | |-------|------| | uploaded | New document created | | versioncreated | New version uploaded | | downloaded | File content retrieved | | metadataupdated | Metadata changed | | deleted | Document soft-deleted | `bash curl https://apisvr.tryhyphen.com/documents/doca1b2c3d4e5f6/audit?limit=50 \ -H "X-Org-Id: acme-corp" ` The document.referenced event links the document to a specific runid, creating a bidirectional audit trail: from the document, you can see which workflows used it; from the workflow run, you can see which documents it consumed. Storage Limits ============== | Limit | Default | |-------|---------| | Storage per organization | 10 GB | | Documents per organization | 10,000 | | Maximum file size | 50 MB | | Maximum versions per document | 100 | Check current usage: `bash curl https://apisvr.tryhyphen.com/documents/storage-usage \ -H "X-Org-Id: acme-corp" ` Common Patterns =============== Reconciliation with uploaded datasets. Upload invoices and payments as CSVs → reference both in a matcher step → agent investigates exceptions → results logged to custom table. Agent document processing. Upload a PDF → agent receives a binary handle → agent uses an LLM action to extract text → agent reasons about the content → routes to appropriate workflow. Scheduled reconciliation with versioned data. Upload new data monthly → version the same document → scheduled workflow always runs against the latest version. Pin previous versions for historical comparisons. Webhook-driven ingestion. External system drops a CSV → document.uploaded webhook fires → agent classifies the document → triggers the correct processing workflow. This is the Agent as Trigger (/agents/deployment-patterns/agent-as-trigger) pattern with document storage. Document storage is infrastructure, not a primitive. It enhances existing primitives — matcher can consume doc: references for its datasets, loop can iterate over uploaded CSVs, agents can process uploaded PDFs. Documents plug into the same @path` context resolution system used everywhere else. → Next: Conditional Logic (/platform/conditional-logic) --- ## PATH: Templates > Employee Onboarding (Source: templates/08-employee-onboarding.md) Employee Onboarding =================== Automate the full employee onboarding lifecycle. An orchestrator agent coordinates across IT provisioning, benefits enrollment, training assignments, equipment ordering, and compliance documentation — triggering specialized workflows for each domain and handling exceptions when systems conflict or approvals are needed. Architecture ============ ``mermaid flowchart TD A["New Hire Record
from HRIS"] --> B["Orchestrator Agent
Coordinates full onboarding"] B --> C["_runworkflow
IT Provisioning"] B --> D["runworkflow
Benefits Enrollment"] B --> E["runworkflow
Training Assignment"] B --> F["runworkflow
Equipment Ordering"] C --> G{"All systems
provisioned?"} D --> G E --> G F --> G G -->|"Yes"| H["✅ Onboarding Complete
Welcome email sent"] G -->|"Conflicts / failures"| I["Human Review
HR Coordinator"] I --> H style B fill:#dcfce7,stroke:#16a34a style C fill:#f3e8ff,stroke:#7c3aed style D fill:#f3e8ff,stroke:#7c3aed style E fill:#f3e8ff,stroke:#7c3aed style F fill:#f3e8ff,stroke:#7c3aed style I fill:#dbeafe,stroke:#2563eb ` Deployment pattern: Agent-as-orchestrator (Pattern C). The agent is the decision-maker, each sub-workflow is a capability it invokes via runworkflow_. The agent decides sequencing, handles dependencies, and manages exceptions. Sub-Workflows ============= The orchestrator coordinates four domain-specific workflows. Each is a standard Hyphen workflow that can also be run independently. IT Provisioning Workflow ======================== Provisions accounts across all required systems: `json { "name": "itprovisioning", "definition": { "actions": [ { "type": "createadaccount", "properties": { "firstname": "@input.firstname", "lastname": "@input.lastname", "department": "@input.department", "role": "@input.role" } }, { "type": "provisionemail", "properties": { "username": "@input.email", "distributionlists": "@input.dlmembership" } }, { "type": "assignlicenses", "properties": { "useremail": "@input.email", "licenses": "@input.requiredlicenses" } }, { "type": "grantsystemaccess", "filter": { "condition": { "greaterThan": [{ "length": "@input.systemaccess" }, 0] } }, "properties": { "useremail": "@input.email", "systems": "@input.systemaccess" } }, { "type": "PbotApproval", "filter": { "condition": { "in": ["@input.accesslevel", ["admin", "elevated"]] } }, "properties": { "comment": "New hire {{input.firstname}} {{input.lastname}} requires {{input.accesslevel}} access. Manager approval needed.", "requestpayload": { "employee": "@input.firstname", "role": "@input.role", "systems": "@input.systemaccess", "accesslevel": "@input.accesslevel" } } } ] } } ` Benefits Enrollment Workflow ============================ `json { "name": "benefitsenrollment", "definition": { "actions": [ { "type": "checkeligibility", "properties": { "employeetype": "@input.employeetype", "startdate": "@input.startdate", "location": "@input.location" } }, { "type": "createbenefitsprofile", "properties": { "employeeid": "@input.employeeid", "eligibleplans": "@eligibility.plans" } }, { "type": "PbotForm", "properties": { "expectedkeys": ["healthplan", "dentalplan", "visionplan", "retirementcontribution", "lifeinsurance"], "ttlseconds": 604800, "reminderintervals": [172800, 432000] } }, { "type": "enrollbenefits", "properties": { "employeeid": "@input.employeeid", "selections": "@formData" } } ] } } ` Training Assignment Workflow ============================ `json { "name": "trainingassignment", "definition": { "actions": [ { "type": "lookuprolerequirements", "properties": { "role": "@input.role", "department": "@input.department", "location": "@input.location" } }, { "type": "loop", "properties": { "mode": "foreach", "itemspath": "@rolerequirements.requiredcourses", "itemvariablename": "course", "actionstoexecute": [ { "type": "assigncourse", "properties": { "employeeid": "@input.employeeid", "courseid": "@course.id", "duedate": "@course.duedate" } } ], "maxconcurrency": 5, "failurestrategy": "continueonerror", "resultkey": "enrollmentresults" } } ] } } ` Equipment Ordering Workflow =========================== `json { "name": "equipmentordering", "definition": { "actions": [ { "type": "lookupstandardkit", "properties": { "role": "@input.role", "location": "@input.location" } }, { "type": "PbotApproval", "filter": { "condition": { "greaterThan": ["@standardkit.totalcost", 3000] } }, "properties": { "comment": "Equipment order for {{input.firstname}} {{input.lastname}} exceeds $3,000. Items: {{standardkit.items}}. Approve?", "requestpayload": { "items": "@standardkit.items", "totalcost": "@standardkit.totalcost" } } }, { "type": "submitequipmentorder", "properties": { "employeeid": "@input.employeeid", "items": "@standardkit.items", "shipto": "@input.worklocation" } } ] } } ` Orchestrator Agent ================== The top-level agent coordinates everything: `json { "name": "employeeonboardingorchestrator", "definition": { "actions": [ { "type": "loop", "properties": { "mode": "react", "objective": "Onboard new employee:\n\nName: {{input.firstname}} {{input.lastname}}\nRole: {{input.role}}\nDepartment: {{input.department}}\nStart date: {{input.startdate}}\nLocation: {{input.location}}\nEmployee type: {{input.employeetype}}\n\nSteps:\n1) Run IT provisioning workflow — wait for completion\n2) Run benefits enrollment workflow — this may take days (PbotForm), run async\n3) Run training assignment workflow — wait for completion\n4) Run equipment ordering workflow — wait for completion\n5) If any workflow fails, log the failure and pause for HR review\n6) Once IT provisioning and training are done, send welcome email\n7) Complete with a summary of all onboarding steps and their status", "tools": [ { "type": "action", "name": "gmailsend" }, { "type": "workflow", "name": "itprovisioning" }, { "type": "workflow", "name": "benefitsenrollment" }, { "type": "workflow", "name": "trainingassignment" }, { "type": "workflow", "name": "equipmentordering" } ], "model": "gpt-4", "maxiterations": 20, "timeoutms": 600000, "onstuck": { "iterations": 4, "action": "escalate" }, "resultkey": "onboardingresult" } }, { "type": "custom-table", "properties": { "table": "onboardinglog", "operation": "write", "keys": ["employeeid"], "values": ["@input.employeeid"], "fields": { "name": "@input.firstname", "role": "@input.role", "startdate": "@input.startdate", "onboardingstatus": "@onboardingresult.answer", "completedat": "@now" } } } ] } } ` Sample Reasoning Trace ====================== ` Iteration 1: Thought: "Starting onboarding for Sarah Chen, Senior Engineer, Platform Team. First: IT provisioning." Action: runworkflow_ Input: { "workflowname": "itprovisioning", "input": { "firstname": "Sarah", "lastname": "Chen", "department": "Engineering", "role": "Senior Engineer", "email": "sarah.chen@company.com", "requiredlicenses": ["GitHub Enterprise", "AWS Console", "Datadog"], "systemaccess": ["productionk8s", "stagingdb"], "accesslevel": "elevated" }, "wait": true } Result: { "status": "completed", "context": { "approved": true } } Iteration 2: Thought: "IT provisioned. Elevated access was approved by manager. Storing this and moving to benefits." Action: storememory Input: { "key": "itstatus", "value": "completed — elevated access approved" } Iteration 3: Thought: "Benefits enrollment requires employee input (plan selections). Running async — it may take days." Action: _runworkflow_ Input: { "workflowname": "benefitsenrollment", "input": { "employeeid": "EMP-4521", "employeetype": "fulltime", "startdate": "2026-02-15", "location": "San Francisco" }, "wait": false } Result: { "workflowrunid": "runben789", "status": "running" } Iteration 4: Thought: "Benefits running async. Proceeding with training and equipment in parallel." Action: logprogress Input: { "message": "IT ✅ | Benefits ⏳ (async) | Training: starting | Equipment: pending" } Iteration 5: Thought: "Assigning required training." Action: runworkflow Input: { "workflowname": "trainingassignment", "input": { "employeeid": "EMP-4521", "role": "Senior Engineer", "department": "Engineering", "location": "San Francisco" }, "wait": true } Result: { "status": "completed", "context": { "coursesassigned": 7 } } Iteration 6: Thought: "7 courses assigned. Now equipment." Action: runworkflow_ Input: { "workflowname": "equipmentordering", "input": { "employeeid": "EMP-4521", "firstname": "Sarah", "lastname": "Chen", "role": "Senior Engineer", "location": "San Francisco", "worklocation": "SF Office" }, "wait": true } Result: { "status": "completed", "context": { "orderid": "EQ-8832" } } Iteration 7: Thought: "IT, training, and equipment complete. Benefits still async. Sending welcome email now." Action: gmailsend Input: { "oauthaccount": "hr@company.com", "to": "sarah.chen@company.com", "subject": "Welcome to the team, Sarah!", "body": "Hi Sarah,\n\nWe're excited to have you join the Platform Team on Feb 15!\n\nYour accounts are set up:\n- Email: sarah.chen@company.com\n- GitHub, AWS, and Datadog access: ready\n- 7 training courses assigned in the LMS\n- Equipment shipping to SF Office\n\nYou'll receive a separate email about benefits enrollment. Please complete your plan selections within 7 days.\n\nSee you on the 15th!" } Result: { "sent": true } Iteration 8: Thought: "Welcome email sent. All synchronous steps complete. Benefits enrollment is async and will complete when Sarah submits her selections." Action: complete_ Input: { "answer": "Onboarding 90% complete. IT provisioned (elevated access approved), 7 training courses assigned, equipment ordered (EQ-8832), welcome email sent. Benefits enrollment pending employee input (async).", "confidence": 0.95, "summary": "4 workflows triggered, 3 completed, 1 async pending" } ` Why Agent-as-Orchestrator ========================= This template demonstrates why a human coordinator is the bottleneck in onboarding — they manually sequence across disconnected systems, track which steps are done, handle exceptions, and send follow-up communications. The orchestrator agent replicates that judgment: | Human coordinator does | Orchestrator agent does | |----------------------|------------------------| | Checks which systems need provisioning | Reads role requirements, triggers itprovisioning | | Follows up on pending approvals | Waits for PbotApproval, escalates if stuck | | Decides sequencing (IT before training) | Reasons about dependencies, runs workflows in logical order | | Sends welcome email once ready | Sends email after confirming IT + training are done | | Tracks overall status | _logprogress at each milestone | | Escalates when things go wrong | pauseforhuman__ when workflows fail | The difference: the agent does it in minutes, across any number of simultaneous new hires, with a complete audit trail. Customization ============= HRIS integration: Replace @input with a webhook trigger from your HRIS (Workday, BambooHR, Rippling) that fires when a new hire record is created. Sub-workflows: Add or remove sub-workflows based on your onboarding process — background check, office badge provisioning, parking assignment, team buddy assignment. Each is a standard Hyphen workflow the agent can invoke. Approval routing: IT provisioning requires manager approval for elevated access. Equipment requires finance approval above $3K. Adjust thresholds and approval routing per your policies. Timeline handling: Benefits enrollment uses PbotForm` with a 7-day TTL and reminders at 48h and 5 days. The orchestrator runs this async so it doesn't block the rest of onboarding. Offboarding: Mirror this template for employee offboarding — revoke access, recover equipment, process final pay, transfer knowledge base ownership. Same orchestrator pattern, reverse direction. --- ## PATH: Platform > Llm Providers (Source: platform/09-llm-providers.md) LLM Providers ============= Hyphen supports multiple LLM providers out of the box. Use any supported model in LLM actions, ReAct agents, and standalone agents — the platform automatically detects which provider to use based on the model name. Supported Providers =================== | Provider | Models | OrgConfig Key | |----------|--------|---------------| | OpenAI | gpt-4, gpt-4o, gpt-4o-mini, o1-, o3- | api:openaikey | | Anthropic | claude-sonnet-4-20250514, claude-3-opus-20240229, claude-3-haiku-20240307, claude- | api:anthropickey | | Google Gemini | gemini-pro, gemini-1.5-pro, gemini- | api:geminikey | | Together AI | mistralai/, meta-llama/, Qwen/, and other open models | api:togetherkey | Auto-Inference ============== You don't configure providers explicitly. Set the model field in any LLM action, ReAct loop, or standalone agent — Hyphen infers the provider from the model name. ``json { "actionname": "summarizedocument", "kind": "llm", "model": "claude-sonnet-4-20250514", "template": "Summarize this:\n\n{{text}}" } ` The model prefix claude- resolves to the Anthropic provider. No additional configuration beyond the API key. Model → Provider Mapping ======================== | Model Prefix | Provider | |-------------|----------| | claude- | Anthropic | | gpt-, o1-, o3-, chatgpt-, ft:gpt- | OpenAI | | gemini- | Google Gemini | | mistralai/, meta-llama/, Qwen/ | Together AI | If the model name doesn't match a known prefix, OpenAI is used as the default provider. API Key Resolution ================== When a model is called, Hyphen resolves the API key in this order: ` 1. Provider-specific orgConfig key → api:anthropickey, api:openaikey, etc. 2. Generic orgConfig key → api:llmapikey (backward compatible) 3. None found → error with diagnostic message ` Step 1 checks for a provider-specific key stored via POST /org-config. This is the recommended approach — store one key per provider your org uses. Step 2 exists for backward compatibility. Orgs that stored a single api:llmapikey before multi-provider support was added continue to work without changes. Per-Org Setup ============= Each tenant configures their own API keys via orgConfig (/platform/multi-tenancy). Keys are encrypted at rest and never appear in workflow definitions or reasoning traces. Store a Provider Key ==================== `bash Anthropic (Claude) ================== curl -X POST {{baseUrl}}/org-config \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "key": "api:anthropickey", "value": "sk-ant-api03-xxxxxxxxxxxx" }' OpenAI ====== curl -X POST {{baseUrl}}/org-config \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "key": "api:openaikey", "value": "sk-xxxxxxxxxxxx" }' Google Gemini ============= curl -X POST {{baseUrl}}/org-config \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "key": "api:geminikey", "value": "AIzaSy-xxxxxxxxxxxx" }' Together AI =========== curl -X POST {{baseUrl}}/org-config \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "key": "api:togetherkey", "value": "xxxxxxxxxxxx" }' ` An org can store keys for multiple providers simultaneously. Each workflow step uses whichever key matches its model. Verify Keys Are Configured ========================== `bash curl {{baseUrl}}/org-config \ -H "X-Org-Id: acme-corp" ` Response shows stored keys (values masked): `json [ { "key": "api:anthropickey", "value": "sk-ant-" }, { "key": "api:openaikey", "value": "sk-" } ] ` Using Models in Workflows ========================= LLM Actions =========== Set the model field when registering the action: `json { "actionname": "claudeclassifier", "kind": "llm", "template": "Classify this document:\n\n{{text}}", "model": "claude-sonnet-4-20250514", "maxtokens": 200 } ` → Full reference: LLM Actions (/actions/llm) ReAct Agents (Workflow Step) ============================ Set model in the loop properties: `json { "type": "loop", "properties": { "mode": "react", "objective": "Investigate this compliance exception", "model": "claude-sonnet-4-20250514", "tools": ["lookupcase", "_pauseforhuman", "complete"], "maxiterations": 10 } } ` → Full reference: ReAct Loop (/agents/react-loop) Standalone Agents ================= Set model in the config: `json { "objective": "Analyze this customer complaint and draft a response", "tools": ["lookupcustomer", "gmailsend", "complete"], "config": { "model": "claude-sonnet-4-20250514", "maxiterations": 10, "temperature": 0.7 } } ` → Full reference: Standalone Agents (/agents/deployment-patterns/agent-as-orchestrator) Mixing Providers in One Workflow ================================ Different steps can use different providers. The provider switches automatically based on the model string — no configuration needed. `json { "name": "hybridproviderworkflow", "definition": { "actions": [ { "type": "fastclassifier", "properties": { "text": "@input.document", "model": "claude-3-haiku-20240307" } }, { "type": "loop", "properties": { "mode": "react", "model": "gpt-4o", "objective": "Deep analysis based on classification: {{fast_classifier.result}}" } } ] } } `` This works because provider resolution happens per-call, not per-workflow. The org just needs valid API keys stored for each provider used. Cost optimization pattern. Use fast, cheap models (Claude Haiku, GPT-4o-mini) for classification and extraction steps, then more capable models (Claude Sonnet, GPT-4o) for complex reasoning in ReAct loops. The per-call provider switching makes this seamless. → Next: Document Storage (/platform/document-storage) --- ## PATH: About (Source: about.md) About Hyphen ============ The AI engine you control. Operations teams are the quiet engine of every company. They reconcile the numbers, match the records, catch the exceptions, route the approvals, and hold the institutional knowledge that keeps everything moving. Most of that knowledge lives in people's heads — and leaves when they do. We built Hyphen because that problem deserved a better answer than "hire more people" or "hope the automation holds." Hyphen is governed AI execution infrastructure for operational work. We give operations teams the ability to define their processes in plain language and execute them with the reliability, auditability, and human oversight that regulated, high-stakes environments actually require. Not AI that freestyles. Not automation that breaks on the first exception. Something in between — intelligence at design time, determinism at runtime, a complete audit trail at every step. We didn't design this in a lab. We built it under pressure, in production, processing real financial operations where messy data and real consequences left no room for fragile infrastructure. What we run today is the platform we wished existed back then. The companies we work with are done choosing between brittle workflows and ungovernable agents. Their ops leaders own the process. Their AI follows it. Nothing happens outside the audit trail. That's the only kind of automation worth trusting with work that actually matters. --- ## PATH: Actions > Index (Source: actions/index.md) Actions ======= Actions are reusable operations you register once and reference by name. Every action has a kind that determines how it executes. The Five Action Kinds ===================== | Kind | What It Does | Example | |------|-------------|---------| | HTTP (/actions/http) | Call external REST APIs | POST to Salesforce, GET from Stripe | | LLM (/actions/llm) | AI text generation | Summarize a document, extract entities | | DB (/actions/db) | Query databases | SELECT from your data warehouse | | Matcher (/actions/matcher) | Pre-configured matching rules | Invoice-to-payment matching with saved config | | Custom Table (/actions/custom-table) | Table operations | Read/write to Hyphen-managed tables | Works Everywhere ================ All registered actions work in three contexts: | Context | How It's Used | |---------|--------------| | Workflow step | "type": "myaction" in the actions array | | Foreach loop | Inside actionstoexecute — runs once per item | | ReAct agent tool | Listed in tools — agent decides when to call it | Registration ============ Register an action via API, then use its actionname as a step type: ``bash Register ======== curl -X POST https://apisvr.tryhyphen.com/actions \ -H "X-Org-Id: acme-corp" \ -H "Content-Type: application/json" \ -d '{ "actionname": "fetchcustomer", "kind": "http", "url": "https://api.crm.com/customers/{{customerid}}", "httpmethod": "GET", "headers": { "Authorization": "Bearer orgconfig:api:crmtoken" } }' ` `json // Use in a workflow { "type": "fetchcustomer", "properties": { "customerid": "@input.id" } } ` api POST /actions Register a new action. api GET /actions List all registered actions for the organization. api GET /actions/:id Get details of a specific action. api PUT /actions/:id Update an existing action. Common Properties ================= These properties apply to all action kinds: | Property | Type | Required | Description | |----------|------|----------|-------------| | actionname | string | Yes | Unique name within the org — used as type in workflows | | kind | string | Yes | "http", "llm", "db", "matcher", or "custom-table" | | description | string | No | Human-readable description | | passthrough | boolean | No | If true, the raw response is passed through to context (default: false) | | outputKey` | string | No | Custom context key for the action's output | --- ## PATH: Agents > Index (Source: agents/index.md) Agents ====== Hyphen agents are operational decision-makers. They reason step-by-step, use tools, observe results, and iterate toward an objective. They are not chatbots. They process, investigate, classify, recommend, and act — with every thought and action captured in a reasoning trace. The key constraint: agents operate inside a cage defined by the workflow spec. They can only use tools you've explicitly declared. They can't discover or invent capabilities. If the spec says they can look up tickets and send emails, that's all they can do — even if the LLM is capable of much more. ``mermaid flowchart TD subgraph "The Agent Cage" O["Objective"] --> T["Think"] T --> A["Act (declared tools only)"] A --> Ob["Observe result"] Ob --> T Ob --> C["Complete"] end Spec["Workflow Spec"] -.->|defines tools,
iterations, guardrails| O H["Human"] -.->|reviews when
agent escalates| A ` Two Ways to Run Agents ====================== As a workflow step. The agent is one step inside a larger deterministic workflow. Use type: "loop" with mode: "react" — see Loop primitive (/primitives/loop). `json { "type": "loop", "properties": { "mode": "react", "objective": "Investigate this exception", "tools": [{ "type": "action", "name": "lookuprecord" }], "maxiterations": 10 } } ` As a standalone agent. Execute directly via the agent API, outside any workflow. Use for ad-hoc tasks, agent-as-trigger, and orchestrator patterns. `bash POST /agents/execute ` api POST /agents/execute Execute a standalone agent. Supports sync (wait for result) and async (?async=true, poll for status) modes. api GET /agents/:id/status Get agent run status, optionally with full reasoning trace (?includetrace=true). api GET /agents/:id/trace Get the complete reasoning trace for an agent run. api POST /agents/:id/resume Resume a paused agent with human input. What's in This Section ====================== | Page | Description | |------|-------------| | ReAct Loop (/agents/react-loop) | The think → act → observe cycle, prompt construction, iteration lifecycle | | Built-in Tools (/agents/built-in-tools) | complete, runworkflow, pauseforhuman__`, memory, logging | | Tool Declarations (/agents/tool-declarations) | Typed tool declarations — action tools, workflow tools, and legacy formats | | Stuck Detection (/agents/stuck-detection) | Recovery strategies when agents loop without progress | | Reasoning Traces (/agents/reasoning-traces) | Audit trail format, querying, compliance, secret redaction | | Deployment Patterns (/agents/deployment-patterns) | Three patterns: agent as step, trigger, or orchestrator | --- ## PATH: Api > Index (Source: api/index.md) API Reference ============= Base URL ======== ``text https://apisvr.tryhyphen.com ` Authentication ============== Hyphen supports three access patterns: - Gateway mode: Authorization: Bearer sk-hyp- (gateway injects tenant context) - Direct engine mode: X-Org-Id: - SDK mode: publishable key pklive (org resolved server-side, scoped access) Admin routes are available for platform operators. Core Engine Endpoints ===================== Health ====== - GET /health - GET /health/ready - GET /builtin-actions Workflows and Runs ================== - POST /workflows - GET /workflows - GET /workflows/:id - PUT /workflows/:id - DELETE /workflows/:id - POST /workflows/:id/execute (body must be { "input": { ... } }) - GET /runs/:runId/status Approvals and Forms =================== - GET /approvals — List all pending approvals for the org - POST /approvals/:runId/:stepId - GET /approvals/:runId - POST /forms/:runId/:stepId/submit (body: { "data": { ... } }) - GET /forms/pending Agents ====== - POST /agents/execute — body accepts optional previousrunid to inject prior run context - GET /agents - GET /agents/:id/status — response includes previousrunid when set - GET /agents/:id/trace - POST /agents/:id/resume - DELETE /agents/:id Actions ======= - POST /actions - GET /actions - GET /actions/:id - PUT /actions/:id Custom Tables ============= - POST /custom-tables - GET /custom-tables/:name - DELETE /custom-tables/:name - POST /custom-tables/:name/insert - POST /custom-tables/:name/bulk-insert - PUT /custom-tables/:name/update - GET /custom-tables/:name/rows — List rows (SDK-compatible) - POST /custom-tables/:name/rows — Insert a row (SDK-compatible) - PATCH /custom-tables/:name/rows/:rowId — Update a row by ID (SDK-compatible) - GET /custom-tables/:name/audit Documents ========= - POST /documents - POST /documents/from-url - POST /documents/bulk-upload - GET /documents - GET /documents/:id - GET /documents/:id/download - PATCH /documents/:id - POST /documents/:id/versions - DELETE /documents/:id - GET /documents/:id/audit - GET /documents/storage-usage Webhooks (Registrations) ======================== - POST /webhooks - GET /webhooks - GET /webhooks/:id - PATCH /webhooks/:id - DELETE /webhooks/:id OAuth ===== - GET /oauth/providers - POST /oauth/:provider/app-credentials - GET /oauth/:provider/app-credentials - GET /oauth/:provider/authorize - GET /oauth/:provider/callback - GET /oauth/connections - GET /oauth/connections/:provider/:accountId - DELETE /oauth/connections/:provider/:accountId - POST /oauth/connections/:provider/:accountId/refresh AI Generation ============= - POST /ai/generate-workflow - GET /ai/generate-workflow/:generationid/status - GET /ai/generate-workflow/:generationid - POST /workflows/create-from-ai - GET /workflows/create-from-ai/:processId - GET /workflows/generation-status/:processId Org Config and Schema ===================== - POST /org-config - GET /org-config - POST /schemas - GET /schemas/:id - POST /utils/generate-dag SDK Endpoints ============= These endpoints are called by the Ops Console SDK (/sdk) using a publishable key (pklive*). They provide scoped access to tasks, tables, documents, and authentication for embedded UI components. Sessions ======== - POST /sdk/auth/session — Create anonymous session - GET /sdk/config — Get SDK configuration Tasks ===== - GET /sdk/tasks — List pending tasks - POST /sdk/tasks/:taskId/decide — Approve or reject a task Tables ====== - GET /sdk/tables/:name/rows — List table rows (paginated) - POST /sdk/tables/:name/rows — Insert a row - PATCH /sdk/tables/:name/rows/:rowId — Update a row (inline editing) Documents ========= - GET /sdk/documents — List documents - POST /sdk/documents — Upload a document Authentication ============== - POST /sdk/auth/otp/request — Send OTP code to registered email - POST /sdk/auth/otp/verify — Verify OTP and upgrade session Agents ====== - POST /sdk/agents/execute — Launch an agent (async, returns agentrunid). Accepts optional previousrunid for cross-run context - GET /sdk/agents — List agent runs - GET /sdk/agents/:agentId/status — Get agent status with reasoning trace - GET /sdk/agents/:agentId/trace — Get full reasoning trace - POST /sdk/agents/:agentId/resume — Resume a paused agent with human input - DELETE /sdk/agents/:agentId — Cancel a running agent Discovery ========= - GET /sdk/actions — List registered actions (for tool picker) - GET /sdk/workflows — List workflows (for tool picker) Events ====== - GET /sdk/events — Server-sent event stream (real-time updates) Gateway Endpoints ================= - GET /health - POST /gateway/signup - Tenant (management token): /gateway/account, /gateway/orgs, /gateway/orgs/:orgId/keys, /gateway/orgs/:orgId/usage, /gateway/licenses - Licensing: POST /licensing/validate, GET /licensing/public-key Error Shape =========== Typical error body: `json { "error": "Validation failed", "message": "..." } `` --- ## PATH: Api > Openapi (Source: api/openapi.json) # Hyphen API

Complete REST API for the Hyphen platform — infrastructure for governed autonomous AI. Build workflows, execute AI agents, manage approvals, and orchestrate business operations with full audit trails.

Version: 2.0.0 Base URL: https://apisvr.tryhyphen.com ## Actions

Register and manage custom actions

### GET /actions List All Actions

List all registered actions for the organization

**Parameters:** - `undefined` [undefined]: string **Responses:** - 200: Success ### POST /actions Register DB Action

Register a DB action for database queries (MySQL, Postgres, MongoDB, Neo4j)

**Parameters:** - `undefined` [undefined]: string **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### GET /actions/{actionId} Get Action by ID

Get details of a specific registered action

**Parameters:** - `undefined` [undefined]: string - `actionId` [path]: string (required) **Responses:** - 200: Success ### PUT /actions/{actionId} Update Action

Update an existing registered action

**Parameters:** - `undefined` [undefined]: string - `actionId` [path]: string (required) **Request Body:** Content-Type: application/json **Responses:** - 200: Success ## Agents

Standalone AI agent execution

### GET /agents List Agent Runs by Status

List agent runs filtered by status (running, paused, completed, failed)

**Parameters:** - `undefined` [undefined]: string - `status` [query]: string **Responses:** - 200: Success ### POST /agents/execute Execute Agent (Async)

Execute an agent asynchronously and poll for status

**Parameters:** - `undefined` [undefined]: string - `async` [query]: string **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### DELETE /agents/{agentRunId} Cancel Agent

Cancel a running agent

**Parameters:** - `undefined` [undefined]: string - `agentRunId` [path]: string (required) **Responses:** - 200: Success ### POST /agents/{agentRunId}/resume Resume Paused Agent

Resume a paused agent with human input

**Parameters:** - `undefined` [undefined]: string - `agentRunId` [path]: string (required) **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### GET /agents/{agentRunId}/status Get Agent Status with Trace

Get agent status including full reasoning trace

**Parameters:** - `undefined` [undefined]: string - `agentRunId` [path]: string (required) - `include_trace` [query]: string **Responses:** - 200: Success ### GET /agents/{agentRunId}/trace Get Agent Reasoning Trace

Get full reasoning trace for an agent run (audit trail)

**Parameters:** - `undefined` [undefined]: string - `agentRunId` [path]: string (required) **Responses:** - 200: Success ## AI Generation

AI-powered workflow generation from natural language

### POST /ai/generate-workflow Initiate AI Generation

Submit natural language description for AI workflow generation

**Parameters:** - `undefined` [undefined]: string **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### GET /ai/generate-workflow/{generationId} Get Generated Workflow

Retrieve the AI-generated workflow specification

**Parameters:** - `undefined` [undefined]: string - `generationId` [path]: string (required) **Responses:** - 200: Success ### GET /ai/generate-workflow/{generationId}/status Get Generation Status

Check status of AI generation request

**Parameters:** - `undefined` [undefined]: string - `generationId` [path]: string (required) **Responses:** - 200: Success ### POST /workflows/create-from-ai Create Workflow from AI Output

Create workflow, actions, and tables from AI-generated specification

**Parameters:** - `undefined` [undefined]: string **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### GET /workflows/generation-status/{processId} Get Creation Process Status

Get status of workflow creation process

**Parameters:** - `undefined` [undefined]: string - `processId` [path]: string (required) **Responses:** - 200: Success ## Approvals

Human-in-the-loop approval management

### GET /approvals/{runId} List Pending Approvals

List all approval requests for a workflow run

**Parameters:** - `undefined` [undefined]: string - `runId` [path]: string (required) **Responses:** - 200: Success ### POST /approvals/{runId}/{stepId} Submit Approval

Submit approval for a paused workflow step

**Parameters:** - `undefined` [undefined]: string - `runId` [path]: string (required) - `stepId` [path]: integer (required) Step index of the approval step in the workflow **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ## Health

Health check and discovery

### GET /builtin-actions List Built-in Actions

List all pre-registered OAuth business tools:

  • Gmail: send, read, reply

  • Slack: post, read_channel, dm

  • Outlook: send, read, calendar_create

**Parameters:** - `undefined` [undefined]: string **Responses:** - 200: Success ### GET /health Health Check

Verify API is running and healthy

**Responses:** - 200: Success ## Custom Tables

Multi-tenant data storage

### POST /custom-tables Create Custom Table

Create a new custom table

**Parameters:** - `undefined` [undefined]: string **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### GET /custom-tables/{customTableName} View Table Data

View all data in a custom table

**Parameters:** - `undefined` [undefined]: string - `customTableName` [path]: string (required) **Responses:** - 200: Success ### DELETE /custom-tables/{customTableName} Drop Custom Table

Delete a custom table

**Parameters:** - `undefined` [undefined]: string - `customTableName` [path]: string (required) **Responses:** - 200: Success ### GET /custom-tables/{customTableName}/audit View Table Audit Log

View audit log for a custom table

**Parameters:** - `undefined` [undefined]: string - `customTableName` [path]: string (required) **Responses:** - 200: Success ### POST /custom-tables/{customTableName}/bulk-insert Bulk Insert Records

Insert multiple records at once

**Parameters:** - `undefined` [undefined]: string - `customTableName` [path]: string (required) **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### POST /custom-tables/{customTableName}/insert Insert Record

Insert a single record into a custom table

**Parameters:** - `undefined` [undefined]: string - `customTableName` [path]: string (required) **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### PUT /custom-tables/{customTableName}/update Update Records

Update records matching criteria

**Parameters:** - `undefined` [undefined]: string - `customTableName` [path]: string (required) **Request Body:** Content-Type: application/json **Responses:** - 200: Success ## External Forms

External-facing form management

### GET /external-forms List Forms

List all external forms

**Parameters:** - `undefined` [undefined]: string **Responses:** - 200: Success ### POST /external-forms Create External Form

Create an external form

**Parameters:** - `undefined` [undefined]: string **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### GET /external-forms/{formId} Get Form

Get form details

**Parameters:** - `undefined` [undefined]: string - `formId` [path]: string (required) **Responses:** - 200: Success ### PUT /external-forms/{formId} Update Form

Update an external form

**Parameters:** - `undefined` [undefined]: string - `formId` [path]: string (required) **Request Body:** Content-Type: application/json **Responses:** - 200: Success ### DELETE /external-forms/{formId} Delete Form

Delete an external form

**Parameters:** - `undefined` [undefined]: string - `formId` [path]: string (required) **Responses:** - 200: Success ### GET /external-forms/{formId}/analytics Get Form Analytics

Get analytics for a form

**Parameters:** - `undefined` [undefined]: string - `formId` [path]: string (required) **Responses:** - 200: Success ### GET /external-forms/{formId}/submissions Get Form Submissions

Get all submissions for a form

**Parameters:** - `undefined` [undefined]: string - `formId` [path]: string (required) **Responses:** - 200: Success ### POST /external-forms/{formId}/submit Submit Form

Submit a form response

**Parameters:** - `undefined` [undefined]: string - `formId` [path]: string (required) **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ## Internal Forms

Workflow-bound form submissions (PbotForm)

### GET /forms/pending List Pending Forms

List all pending form requests

**Parameters:** - `undefined` [undefined]: string **Responses:** - 200: Success ### POST /forms/{runId}/{stepId}/submit Submit Form Data

Submit form data for a paused PbotForm step

**Parameters:** - `undefined` [undefined]: string - `runId` [path]: string (required) - `stepId` [path]: integer (required) Step index of the PbotForm step **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ## OAuth

OAuth business tool integrations

### GET /oauth/connections List OAuth Connections

List all OAuth connections for the organization

**Parameters:** - `undefined` [undefined]: string **Responses:** - 200: Success ### GET /oauth/connections/{provider}/{accountId} Validate OAuth Connection

Validate a specific OAuth connection

**Parameters:** - `undefined` [undefined]: string - `provider` [path]: string (required) - `accountId` [path]: string (required) Email of the connected OAuth account **Responses:** - 200: Success ### DELETE /oauth/connections/{provider}/{accountId} Remove OAuth Connection

Remove an OAuth connection

**Parameters:** - `undefined` [undefined]: string - `provider` [path]: string (required) - `accountId` [path]: string (required) Email of the connected OAuth account **Responses:** - 200: Success ### POST /oauth/connections/{provider}/{accountId}/refresh Refresh OAuth Token

Force refresh an OAuth token

**Parameters:** - `undefined` [undefined]: string - `provider` [path]: string (required) - `accountId` [path]: string (required) Email of the connected OAuth account **Responses:** - 200: Success - 201: Created ### GET /oauth/providers List OAuth Providers

List available OAuth providers and their configuration status

**Parameters:** - `undefined` [undefined]: string **Responses:** - 200: Success ### GET /oauth/{provider}/app-credentials Check OAuth App Credentials

Check if OAuth app credentials are configured (doesn't return secrets)

**Parameters:** - `undefined` [undefined]: string - `provider` [path]: string (required) **Responses:** - 200: Success ### POST /oauth/{provider}/app-credentials Store OAuth App Credentials

Store OAuth app credentials (clientid, clientsecret) for a provider

**Parameters:** - `undefined` [undefined]: string - `provider` [path]: string (required) **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### GET /oauth/{provider}/authorize Get Authorization URL

Get OAuth authorization URL to redirect user for consent

**Parameters:** - `undefined` [undefined]: string - `provider` [path]: string (required) - `redirect` [query]: boolean Set to true to redirect directly to the provider's consent page **Responses:** - 200: Success ## Org Config

Organization-level configuration and secrets

### GET /org-config List All Config

List all configuration keys for the organization (values are masked)

**Parameters:** - `undefined` [undefined]: string **Responses:** - 200: Success ### POST /org-config Upsert Config Key

Store or update a configuration key for the organization

**Parameters:** - `undefined` [undefined]: string **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ## Execution

Execute workflows and check run status

### GET /runs/{runId}/status Get Run Status

Get current status of a workflow run including context and step details

**Parameters:** - `undefined` [undefined]: string - `runId` [path]: string (required) **Responses:** - 200: Success ### POST /workflows/{workflowId}/execute Execute Workflow

Execute a workflow with input payload

**Parameters:** - `undefined` [undefined]: string - `workflowId` [path]: string (required) **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ## Schemas

JSON schema registry

### POST /schemas Create Schema

Register a JSON schema

**Parameters:** - `undefined` [undefined]: string **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### GET /schemas/{id} Get Schema

Retrieve a registered schema

**Parameters:** - `undefined` [undefined]: string - `id` [path]: string (required) Name of the registered schema **Responses:** - 200: Success ## Utilities

Utility endpoints

### POST /utils/generate-dag Generate Workflow DAG

Generate DAG visualization data from workflow definition

**Parameters:** - `undefined` [undefined]: string **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ## Workflows

Create and manage workflow definitions

### GET /workflows List All Workflows

List all workflow definitions for the organization

**Parameters:** - `undefined` [undefined]: string **Responses:** - 200: Success ### POST /workflows Create Workflow with ReAct Agent

Create a workflow with ReAct agent for AI-powered reasoning

**Parameters:** - `undefined` [undefined]: string **Request Body:** Content-Type: application/json **Responses:** - 200: Success - 201: Created ### GET /workflows/{workflowId} Get Workflow by ID

Get details of a specific workflow

**Parameters:** - `undefined` [undefined]: string - `workflowId` [path]: string (required) **Responses:** - 200: Success ### PUT /workflows/{workflowId} Update Workflow

Update an existing workflow definition

**Parameters:** - `undefined` [undefined]: string - `workflowId` [path]: string (required) **Request Body:** Content-Type: application/json **Responses:** - 200: Success ### DELETE /workflows/{workflowId} Delete Workflow

Delete a workflow definition

**Parameters:** - `undefined` [undefined]: string - `workflowId` [path]: string (required) **Responses:** - 200: Success --- ## PATH: Gateway > Index (Source: gateway/index.md) Gateway ======= Hyphen Gateway is the public front door for SaaS and hybrid deployments. Base URL: https://apisvr.tryhyphen.com What it does ============ - Authenticates API keys and management tokens - Maps key to tenant and injects X-Org-Id upstream - Applies request-level rate limiting - Exposes self-serve signup and tenant management APIs - Exposes license APIs for on-prem validation Key routes ========== Public ====== - GET /health - POST /gateway/signup Tenant management (Authorization: Bearer mt-hyp-...) ==================================================== - GET /gateway/account - POST /gateway/account/regenerate-token - GET /gateway/orgs - POST /gateway/orgs - PUT /gateway/orgs/:orgId - GET /gateway/orgs/:orgId/keys - POST /gateway/orgs/:orgId/keys - DELETE /gateway/orgs/:orgId/keys/:keyId - POST /gateway/orgs/:orgId/keys/:keyId/rotate - GET /gateway/orgs/:orgId/usage - GET /gateway/licenses Platform Administration ======================= Admin routes are available for platform operators managing tiers, accounts, licensing, and usage monitoring. Licensing ========= - POST /licensing/validate - GET /licensing/public-key Runtime proxy calls =================== For product/runtime API calls, send: - Authorization: Bearer sk-hyp-... Gateway forwards to engine with tenant headers injected. --- ## PATH: Getting Started > Index (Source: getting-started/index.md) Getting Started =============== Prerequisites ============= You need three things: - An API endpoint — Hyphen runs on https://apisvr.tryhyphen.com by default, or your deployed instance URL - An org ID — Any string; this isolates your data (X-Org-Id header on every request) - curl or Postman — Every example uses curl; the Postman collection (/api) works too No SDK required. Hyphen is API-first — every capability is an HTTP endpoint. Learning Path ============= | Page | Time | What You'll Learn | |------|------|-------------------| | Quickstart (/getting-started/quickstart) | 15 min | Register an action, build a workflow with matching + approval, execute it, check the audit trail | | Core Concepts (/getting-started/core-concepts) | 10 min | The six building blocks: workflows, actions, primitives, context, agents, runs | | Your First Workflow (/getting-started/your-first-workflow) | 20 min | Step-by-step: matcher → conditional → approval → custom table | | Your First Agent (/getting-started/your-first-agent) | 20 min | Build a ReAct agent with custom tools, stuck detection, and human escalation | Skip ahead if you're a developer. The Quickstart (/getting-started/quickstart) has a fast-path section — register an action, create a workflow, execute it, read the trace. No narrative. --- ## PATH: Guides > Index (Source: guides/index.md) Guides ====== Step-by-step walkthroughs that build real workflows from scratch. Each guide takes you from zero to a working system. | Guide | What You'll Build | Time | |-------|-------------------|------| | Invoice Reconciliation (/guides/invoice-matching) | Matcher + agent + approval pipeline for invoice-to-payment matching | 30 min | | Agent Orchestrator (/guides/building-an-agent-orchestrator) | Multi-workflow agent that processes support tickets | 30 min | | Human-in-the-Loop (/guides/human-in-the-loop) | Approval flows and agent pause/resume patterns | 20 min | | Graduated Exception Handling (/guides/graduated-exception-handling) | The 80/15/5 pattern — rules, AI, humans in one pipeline | 25 min | | AI Workflow Generation (/guides/ai-workflow-generation) | Natural language to workflow using the AI compiler | 15 min | | Embedding Hyphen (/guides/embedding-hyphen) | Multi-tenant platform integration for SaaS companies | 30 min | --- ## PATH: Home (Source: home.md) Hyphen ====== Infrastructure for governed autonomous AI. Hyphen is the execution layer for AI agents that make real decisions in real businesses. Define workflows in JSON or plain language, execute them deterministically, and let bounded AI agents handle the reasoning — with full audit trails, structural permissioning, and human-in-the-loop at every critical juncture. Start Building ============== - Quickstart Guide (/getting-started/quickstart) — Register an action, create a workflow, execute it. 15 minutes. - Core Concepts (/getting-started/core-concepts) — The six building blocks you need to understand. - Ops Console SDK (/sdk) — Embed task approvals, data tables, and document uploads into your internal tools. 10 minutes. - API Reference (/api) — Full endpoint spec with request/response examples. - Gateway (/gateway) — Signup, API keys, tenant management, and licensing endpoints. - Templates (/templates) — Production-ready workflow patterns across industries. Governed Autonomy ================= AI agents that reason and act on their own — within boundaries you define. ``mermaid flowchart TD A["Operational Event
(alert, document, request, exception)"] --> B["Deterministic Rules
Matcher + Conditions"] B -->|"~80% auto-handled"| C["✅ Resolved"] B -->|"Exceptions"| D["ReAct Agent
Bounded AI — thinks, acts, observes"] D -->|"~15% AI-resolved"| C D -->|"Low confidence"| E["Human Review
PbotApproval"] E -->|"~5% human-resolved"| C C --> F["Audit Trail
Every step, every decision, every reasoning trace"] style B fill:#e8f5e9,stroke:#2e7d32 style D fill:#e3f2fd,stroke:#1565c0 style E fill:#fff3e0,stroke:#e65100 style F fill:#f3e5f5,stroke:#6a1b9a ` 80 / 15 / 5 — The exact ratio varies by use case, but the architecture is always the same: rules first, AI second, humans last. This graduated approach gives you automation speed with human-grade accuracy. How It Works ============ Hyphen operates on a three-phase execution model: Phase 1 — AI as Compiler. Describe what you want in plain language. Hyphen's AI compiles your intent into a precise JSON workflow specification — the conditions, data references, branching logic, and escalation paths. Humans describe the what. AI produces the how. Phase 2 — Deterministic Runtime. The execution engine runs the compiled spec exactly as written. No improvisation. If the spec says "wait for approval," it waits. If a condition fails, it follows the defined fallback. Enterprises audit specs, not vibes. Phase 3 — Bounded Agentic Runtime. For tasks requiring judgment — investigating exceptions, interpreting documents, making recommendations — ReAct agents reason within cages defined by the spec. Only declared tools are available. Every thought and action is captured. Iteration caps and stuck detection prevent runaway execution. What You Build With It ====================== The same primitives — matching, agents, approvals, storage — compose into workflows across any operational domain. | Domain | Use Case | Pattern | |--------|----------|---------| | Finance | Invoice reconciliation, payment matching, exception investigation | Matcher → Agent → Approval (/templates/ap-invoice-reconciliation) | | IT & Security | Alert triage, incident response, access reviews | Agent enriches → containment → escalation (/templates/it-incident-response) | | Legal | Contract review, clause extraction, playbook deviation flagging | LLM extraction → Matcher → Agent (/templates/contract-review) | | People Ops | Employee onboarding/offboarding, leave processing | Agent-as-orchestrator (/templates/employee-onboarding) | | Healthcare | Claims adjudication, denial management, prior authorization | Dual matcher → Agent → Clinical review (/templates/insurance-claims-adjudication) | | Supply Chain | Supplier risk, PO exception handling, RFP processing | Scheduled agent → threshold escalation | | Customer Ops | Escalation investigation, SLA monitoring, renewals | Agent pulls context → drafts resolution → approval | See all templates → (/templates) The Primitives ============== Four building blocks compose into any operational workflow: | Primitive | Purpose | Example | |-----------|---------|---------| | Matcher (/primitives/matcher) | Multi-criteria data matching | Compare invoices to payments, alerts to known indicators, contract terms to playbook | | Loop (/primitives/loop) | Batch processing (foreach) or AI reasoning (react) | Process 10K records, investigate an exception, orchestrate across systems | | PbotApproval (/primitives/approval) | Human-in-the-loop | Manager sign-off, legal review, SOC analyst escalation | | Custom Table (/primitives/custom-table) | Multi-tenant storage | Audit log, contract registry, incident database | Document Storage (/platform/document-storage) provides built-in file management — upload CSVs, PDFs, and JSON files, then reference them anywhere in workflows with the doc: prefix. Upload once, version over time, auto-trigger workflows on new uploads via webhooks. Three Deployment Patterns ========================= The ReAct agent operates in three patterns depending on where autonomy lives: `mermaid flowchart LR subgraph A["Pattern A: Agent as Step"] direction TB A1["Workflow Step 1"] --> A2["🤖 Agent Step"] A2 --> A3["Workflow Step 3"] end subgraph B["Pattern B: Agent as Trigger"] direction TB B1["Inbound Event"] --> B2["🤖 Agent classifies"] B2 --> B3["→ Workflow A or B or C"] end subgraph C["Pattern C: Agent as Orchestrator"] direction TB C1["🤖 Agent coordinates"] --> C2["→ Workflow 1"] C1 --> C3["→ Workflow 2"] C1 --> C4["→ Workflow 3"] end ` | Pattern | When to Use | Example | |---------|-------------|---------| | Agent as Step (/agents/deployment-patterns/agent-as-step) | Most of the process is deterministic, one step requires judgment | Invoice matching → agent investigates exceptions | | Agent as Trigger (/agents/deployment-patterns/agent-as-trigger) | You don't know upfront which workflow to run | Agent classifies incoming document → routes to correct pipeline | | Agent as Orchestrator (/agents/deployment-patterns/agent-as-orchestrator) | Dynamic coordination across sub-processes | Agent coordinates onboarding across IT, HR, training, equipment | For AI Agents ============= If you're an AI agent or coding assistant, fetch /llms.txt` (/llms.txt) for the complete documentation in a single structured text file optimized for LLM consumption. Built by the Hyphen team. API Reference (/api) · GitHub (https://github.com/hyphen-platform) --- ## PATH: Integrations > Index (Source: integrations/index.md) Integrations ============ Hyphen includes built-in OAuth integrations for common business tools and a webhook system for event-driven architectures. Business Tools ============== | Provider | Actions | Auth | |----------|---------|------| | Gmail (/integrations/gmail) | gmailsend, gmailread, gmailreply | OAuth 2.0 | | Slack (/integrations/slack) | slackpost, slackreadchannel, slackdm | OAuth 2.0 | | Outlook (/integrations/outlook) | outlooksend, outlookread, outlookcalendarcreate | OAuth 2.0 | Multi-Tenant OAuth Model ======================== Each organization manages their own OAuth credentials. Hyphen stores and refreshes tokens automatically. ``mermaid sequenceDiagram participant Org as Your Org participant Hyphen participant Provider as Google / Slack / Microsoft Org->>Hyphen: Store OAuth app credentials (clientid, client_secret) Org->>Hyphen: Request authorization URL Hyphen->>Org: Redirect URL Org->>Provider: User authorizes access Provider->>Hyphen: OAuth callback with auth code Hyphen->>Hyphen: Store encrypted tokens Note over Hyphen: Tokens auto-refresh before expiry Org->>Hyphen: Use tools in workflows and agents `` The setup flow is the same for every provider: store your OAuth app credentials, authorize user accounts, then use the tools in workflows and agents. Webhooks ======== Webhooks (/integrations/webhooks) let you receive notifications when events occur in Hyphen — run lifecycle changes, approval events, document events, and generation status events. What's in This Section ====================== | Page | Description | |------|-------------| | Gmail (/integrations/gmail) | Send, read, and reply to emails via Gmail API | | Slack (/integrations/slack) | Post messages, read channels, and send DMs | | Outlook (/integrations/outlook) | Send emails, read inbox, and create calendar events | | Webhooks (/integrations/webhooks) | Event notifications with signed payloads, filtering, and retry logic | --- ## PATH: Platform > Index (Source: platform/index.md) Platform ======== The Hyphen platform architecture, from execution model to security controls. | Page | What It Covers | |------|---------------| | Architecture (/platform/architecture) | Three-phase execution model — AI as Compiler, Deterministic Runtime, Bounded Agentic Runtime | | Workflow DSL (/platform/workflow-dsl) | Complete JSON DSL specification — every field, type, and option | | Context Resolution (/platform/context-resolution) | @path syntax, data flow between steps, template interpolation | | Conditional Logic (/platform/conditional-logic) | filter, onFalse, condition operators, combinators, nested trees | | Scheduling (/platform/scheduling) | Interval, time-of-day, and timezone-based execution | | Multi-Tenancy (/platform/multi-tenancy) | X-Org-Id isolation, encrypted secrets, credential scoping | | Security (/platform/security) | Structural permissioning, SSRF protection, prompt injection defense, secret redaction | | Document Storage (/platform/document-storage) | Upload files, doc: reference resolution, versioning, webhook-triggered execution | --- ## PATH: Primitives > Index (Source: primitives/index.md) Primitives ========== Primitives are built into Hyphen. They don't need to be registered — use them directly as step types in any workflow. ``mermaid flowchart LR subgraph "Workflow" M["Matcher"] --> L["Loop"] L --> A["PbotApproval"] A --> F["PbotForm"] F --> T["Custom Table"] end ` | Primitive | Type Value | What It Does | |-----------|-----------|--------------| | Matcher (/primitives/matcher) | matcher | Compare two datasets — exact keys, numeric tolerance, date windows, fuzzy text. Outputs matched pairs and exceptions from both sides | | Loop (/primitives/loop) | loop | Two modes: foreach processes items in parallel with configurable concurrency; react runs an AI agent with tools, guardrails, and stuck detection | | Approval (/primitives/approval) | PbotApproval | Pause execution for human review. The reviewer sees context, makes a decision, and execution resumes | | Form (/primitives/form) | PbotForm | Pause execution to collect external input via a form. Supports TTL and reminder intervals | | Custom Table (/primitives/custom-table) | custom-table` | Multi-tenant data storage — read, write, update, and upsert operations with full audit logging | All primitives produce output that flows into context via @ path resolution (/platform/context-resolution), making their results available to subsequent steps. --- ## PATH: Sdk > Index (Source: sdk/index.md) Ops Console SDK =============== Embed Hyphen's operational surfaces directly into your internal tools. The SDK provides drop-in Web Components that connect your operations team to the workflow engine — without building custom UI. What It Does ============ The SDK gives your team five components: | Component | Purpose | |-----------|---------| | (/sdk/components#hyphen-task-sidebar) | Pending approval tasks from your workflows | | (/sdk/components#hyphen-data-grid) | Sortable, paginated data tables with inline editing | | (/sdk/components#hyphen-doc-feed) | Document upload surface that triggers workflow runs | | (/sdk/components#hyphen-auth-modal) | OTP-based login for per-user audit trails | | (/sdk/components#hyphen-agent-console) | Interactive agent execution and reasoning trace viewer | Each component renders inside Shadow DOM — fully isolated from your host page's styles and scripts. No framework dependency. No build step required. Why Embed ========= Ship an ops console in one sprint, not one quarter. Instead of building approval UIs, data tables, and upload forms from scratch, drop in the SDK and connect it to your Hyphen workflows. User-level audit trails. Every action taken through an SDK component — approving a task, editing a row, uploading a document — is logged with the authenticated user's identity. Real-time updates. Components subscribe to server-sent events. When a workflow status changes or a new task arrives, the UI updates automatically. Works everywhere. Web Components run in any modern browser. Embed them in React, Vue, Angular, plain HTML, or any internal tool that renders a web view. Architecture ============ `` Your Internal Tool | v [SDK Components] ──> [Hyphen Gateway] ──> [Workflow Engine] ^ | | v [Publishable Key] [Org Resolution] ` The SDK authenticates with a publishable key (pklive*). The gateway resolves the key to an organisation, enforces origin restrictions and scope limits, and proxies requests to the engine. The SDK never sends X-Org-Id directly — organisation context is resolved server-side. Quick Start =========== Three steps to a working ops console: 1. Add the script tag ===================== `html ` 2. Initialize with your publishable key ======================================= `html ` 3. Drop in a component ====================== `html `` That's it. The sidebar renders pending tasks from your workflows and lets your team approve or reject them inline. Full setup guide: SDK Quickstart (/sdk/quickstart) walks through getting a publishable key, initializing the SDK, and embedding all five components — in under 10 minutes. Sections ======== | Page | What You'll Learn | |------|-------------------| | Quickstart (/sdk/quickstart) | Get a publishable key, initialize the SDK, embed your first component | | Components (/sdk/components) | All five components — attributes, events, and usage examples | | Authentication (/sdk/authentication) | Anonymous vs. authenticated sessions, OTP flow, session lifecycle | | Theming (/sdk/theming) | CSS custom properties for matching your brand | | Events & Real-Time (/sdk/events) | SDK event system, server-sent events, and cross-component updates | Browser Support =============== | Browser | Minimum Version | |---------|----------------| | Chrome | 80+ | | Firefox | 78+ | | Safari | 14+ | | Edge | 80+ | The SDK targets ES2020 and uses Web Components (Custom Elements v1, Shadow DOM). No polyfills required for modern browsers. Bundle Size =========== | Format | Size | Gzipped | |--------|------|---------| | ES Module | 58 kB | 13 kB | | UMD | 42 kB | 11 kB | Zero runtime dependencies. The entire SDK is a single file. --- ## PATH: Templates > Index (Source: templates/index.md) Templates ========= Production-ready workflow patterns. Each template includes the complete JSON workflow definition, required action registrations, sample data, and expected execution flow. The Graduated Pipeline ====================== Every template follows the same architecture — deterministic rules first, AI reasoning second, human review last: ``mermaid flowchart LR A["Operational
Event"] --> B["Deterministic
Rules"] B -->|"Auto-resolved"| E["✅"] B -->|"Exceptions"| C["Bounded
Agent"] C -->|"AI-resolved"| E C -->|"Low confidence"| D["Human
Review"] D --> E style B fill:#fef3cd,stroke:#856404 style C fill:#d1ecf1,stroke:#0c5460 style D fill:#d4edda,stroke:#155724 ` The ratio varies — reconciliation workflows route ~80% through rules, while incident response may route ~70% — but the pattern is always the same. Finance & Accounting ==================== Templates for matching, reconciliation, and payment processing. AP Invoice Reconciliation (/templates/ap-invoice-reconciliation) ================================================================= Match invoices to payments with tolerance for amount discrepancies, date offsets, and vendor name variations. Agent investigates unmatched exceptions. Human approves write-offs and adjustments. Primitives used: Matcher → Loop (react) → PbotApproval → Custom Table Graduated split: ~75% auto-matched, ~15% AI-investigated, ~10% human-reviewed Vendor Payment Reconciliation (/templates/vendor-payment-reconciliation) ========================================================================= Cross-border payment reconciliation handling FX volatility, intermediary bank fees, and asynchronous settlement windows. Multi-stage matching with cascading tolerance. Primitives used: Matcher (two-pass) → Loop (foreach) → Loop (react) → PbotApproval → Custom Table Graduated split: ~70% auto-matched, ~20% AI-investigated, ~10% human-reviewed Healthcare ========== Templates for claims processing and revenue cycle management. Insurance Claims Adjudication (/templates/insurance-claims-adjudication) ========================================================================= Dual-matcher pipeline: first match claims to policies, then match service codes to fee schedules. Agent analyzes complex cases involving medical necessity, bundling disputes, and authorization gaps. Clinical reviewer handles edge cases. Primitives used: Matcher (dual) → Loop (react) → PbotApproval → Custom Table Graduated split: ~70% auto-adjudicated, ~20% AI-analyzed, ~10% clinical review Healthcare Denial Management (/templates/healthcare-denial-management) ======================================================================= Automated denial analysis, categorization, and appeal generation. Agent classifies denial reasons, searches for supporting documentation, and drafts appeal letters. Compliance reviewer approves before submission. Primitives used: Loop (foreach) → Loop (react) → PbotApproval → Custom Table Graduated split: ~60% auto-categorized, ~25% AI-drafted appeals, ~15% compliance review IT & Security ============= Templates for incident response and operational monitoring. IT Incident Response (/templates/it-incident-response) ======================================================= SOC alert triage workflow. Matcher correlates alerts against known benign indicators. Agent enriches with threat intelligence, queries SIEM, classifies severity, and recommends containment. SOC analyst reviews high-severity and ambiguous cases. Primitives used: Matcher → Loop (react) → PbotApproval → Custom Table Graduated split: ~70% known-benign auto-closed, ~20% auto-contained, ~10% SOC escalation Structural permissioning demo: Agent can blockip and isolatehost but cannot deletefirewallrule or shutdownserver — tool list = architectural constraint. Legal & Compliance ================== Templates for contract analysis and regulatory processing. Contract Review (/templates/contract-review) ============================================= Automated contract analysis pipeline. LLM extracts terms, matcher compares against standard playbook, agent assesses deviations and drafts redlines. Legal counsel reviews high-risk and high-value contracts. Custom table builds searchable contract registry. Primitives used: LLM extraction → Matcher → Loop (react) → PbotApproval → Custom Table Graduated split: ~60% standard-term auto-approved, ~25% AI-analyzed deviations, ~15% legal review People Operations ================= Templates for employee lifecycle management. Employee Onboarding (/templates/employee-onboarding) ===================================================== Agent-as-orchestrator pattern. A coordinator agent sequences four sub-workflows — IT provisioning, benefits enrollment, training assignment, equipment ordering — handling dependencies, async form collection, and failure recovery. Demonstrates why agent-as-orchestrator replaces human coordinator judgment. Primitives used: Loop (react) with runworkflow → PbotApproval (within sub-workflows) → PbotForm (benefits) → Custom Table Pattern: Agent as Orchestrator (Pattern C) Identity & Risk =============== Templates for customer verification and compliance screening. KYC Customer Onboarding (/templates/kyc-customer-onboarding) ============================================================= Identity verification, sanctions screening, risk assessment, and account provisioning. Agent coordinates across verification providers, handles conflicting signals, and escalates high-risk applications. Compliance officer makes final determination on edge cases. Primitives used: Loop (react) with runworkflow → PbotApproval → Custom Table Pattern: Agent as Orchestrator (Pattern C) Customization Guide =================== Matching Thresholds =================== Adjust based on your tolerance for false positives vs. false negatives: | Profile | tolerance | fuzzyThreshold | dateWindowDays | Use When | |---------|------------|-------------------|-------------------|----------| | Conservative | 0.005 (0.5%) | 95 | 1 | High-value transactions, regulated reporting | | Balanced | 0.02 (2%) | 85 | 3 | General reconciliation, invoice matching | | Permissive | 0.05 (5%) | 75 | 7 | Cross-border payments, legacy system data | Agent Boundaries ================ | Profile | maxiterations | onstuck | timeoutms | Use When | |---------|-----------------|------------|--------------|----------| | Tight | 5 | fail after 2 | 60000 | Simple lookups, low-risk decisions | | Standard | 10–15 | escalate after 3 | 300000 | Exception investigation, document analysis | | Exploratory | 20–25 | retrywithhint after 4 | 600000 | Complex research, multi-system coordination | Tool Declarations ================= Use typed tool declarations to give agents access to your registered actions and workflows: `json { "tools": [ { "type": "action", "name": "yourregisteredaction" }, { "type": "workflow", "id": "wfl-your-workflow-id" } ] } ` Built-in tools (complete, _pauseforhuman, storememory, retrievememory, logprogress__`) are always auto-injected. See Tool Declarations (/agents/tool-declarations) for the full reference. Building Your Own Template ========================== Every template follows the same structure. To create a custom workflow: 1. Identify the graduated split — What percentage of events can be handled by rules? What needs AI? What needs a human? 2. Define the matcher — What two datasets are you comparing? What fields should match? What tolerances make sense? 3. Scope the agent — What tools does the agent need? What's the objective? How many iterations is reasonable? 4. Set the approval trigger — What conditions require human review? Amount thresholds? Confidence thresholds? Risk levels? 5. Design the audit trail — What fields should the custom table capture for compliance and analysis? Start with the Quickstart (/getting-started/quickstart), then adapt the template closest to your use case. --- --- END OF DOCUMENTATION CONTEXT Total documents: 72 Generated at: 2026-04-08T23:54:32.535Z