Agentic Workforce ME Developer PortalDocs 1.1 · Widget 0.1.0

Backend integration

How the demo portals do it

Meridian (accounts payable) and Dubai Police (complaint intake, escalation, gated case work) as reference implementations of the pattern.

The Agentic Workforce ME repository ships several demo portals that are built exactly the way this guide describes: a real application with its own database and UI, whose work is done by platform agents through the public API only. They are the reference architecture behind every page in this section. Two are worth reading closely.

Meridian Supplies — accounts payable

A small accounts-payable tool for a fictional trading company. Invoices arrive, are validated against purchase orders, wait for a manager’s approval, then get paid. Every one of those steps is performed by an agent; the portal has no local validation path at all (without a platform connection it refuses intake with 503 HIVE_NOT_CONFIGURED).

The loop

  1. Intake. A clerk submits an invoice. The server signs the JSON with the trigger secret (x-hive-signature: sha256=…) and POSTs it to /hooks/:triggerId; the platform answers 202 with the run_id, which the portal stores on the invoice. (Triggers)
  2. Validation. The invoice-intake workflow runs the ap-validator agent, which reads the invoice, PO and vendor through the portal’s own MCP server (invoice.get, po.get, vendor.get) and writes its verdict back with invoice.set_validation or invoice.flag_exception — a visible state change in the portal’s database, attributed to hive:agent. (MCP)
  3. Human decision. The workflow parks on a wait_event node. When the AP manager approves or rejects in the portal, the server delivers the event with POST /v1/workflows/runs/:runId/events and the run resumes.
  4. Payment. On run.completed for an approved invoice, the portal starts the ap-payment-clerk agent directly (POST /v1/agents/:id/runs). Its payment.record tool is gated by a high-risk approval policy: the run pauses until a Finance Controller decides in the console. The clerk is a direct run rather than a node in the intake workflow because a workflow agent node cannot be resumed after an approval pause. (Approvals)
  5. Closing the loop. The webhook receiver at /api/hive/webhook verifies Hive-Signature with the SDK’s verifyWebhookSignature, de-duplicates on the envelope id, acknowledges immediately and applies run.completed, run.failed and approval.requested to the invoice timeline asynchronously. (Webhooks)

What to copy

  • Two files. Outbound (server/hive/client.ts: trigger fire, SSE relay, event delivery, direct agent run) and inbound (server/hive/webhook.ts: verify, dedupe, apply). Everything platform-related is readable in one sitting.
  • The SDK is the only platform dependency. The portal may import @hive/sdk and nothing else from the monorepo — the same position a real customer is in.
  • Evidence tables. Every outbound call and every inbound delivery is recorded (without secrets) and rendered on an “Integration” page that shows the signed payloads, the verification result and the dedupe hits. Build one; it turns debugging into reading.
  • Honest failure. No silent fallback when the platform is unreachable: the UI says the processing is unavailable. Your users can cope with that; they cannot cope with fake results.
  • Failure must not be fatal. Every route that calls the platform goes through an async wrapper and an error middleware, so an unreachable API becomes a 502 PLATFORM_UNAVAILABLE response, not a crashed process — Express 4 does not catch a rejected promise from an async handler, and Node exits on an unhandled rejection. The example’s asyncHandler in server.mjs is the whole pattern.

Dubai Police demonstration portal — intake, escalation, gates

A bilingual services portal for a fictional police tenant: citizens file reports, pay fines, object to fines and renew registrations; officers dispatch units and decide cases. Larger than Meridian, but the integration is the same shape with three additions worth studying.

PatternHow the portal does it
Multi-step workflow with a parked human stepreport-intake: classify → enrich → propose a dispatch → wait_event dispatcher.decision. The dispatcher approves on the portal’s board; the server delivers the event and the run continues. Same for fine-dispute with committee.decision.
Gated write after the human stepThe actual mutation (dispatch.commit, objection.decide, court.file_referral) is done by a separate direct agent run whose tool is covered by a high-risk approval policy — so the irreversible action always passes a platform-side approval, whatever the workflow decided.
Approvals decided inside the portalPOST /api/demo/approvals/:id/decision: the portal server checks its own session (command persona only), then calls POST /v1/approvals/:id/decision with its server-held service key and a feedback naming the persona. The key is an admin API key and the policies use approver_role: admin, so the decision is authorised and audited like a console decision. The SPA only ever sees the approval id.
Escalation from inside a toolWhen the incident.classify MCP tool computes the “major crime” lane (from the agent’s indicators or a deterministic keyword floor the model cannot dodge), the portal itself moves the report to escalated, opens a linked case and fires a second signed trigger (dpf-major-case) — state changes on the portal’s own rules mid-run, never on model prose and never by waiting for a webhook. The receiving agent then reads the case through MCP (case.get, records.check, …) rather than trusting the prompt.
Webhooks as a timelineapproval.requested becomes a timeline entry (“awaiting the duty officer’s approval — nothing has moved yet”); run.completed for a record that did not change means the gate was rejected — the portal shows that honestly rather than assuming success.

What to copy

  • Separate deciding (a wait_event your UI resumes) from committing (a gated tool under an approval policy). Your UI owns the business decision; the platform guarantees a human saw the exact action before it ran.
  • Proxy approvals only for roles that should decide, and record who clicked in your own audit as well as in feedback.
  • Keep the local loop reachable: the portal’s public URL must be visible from the platform’s worker (host.docker.internal plus the SSRF allow-list for a local stack; a tunnel otherwise).

The pattern, extracted

StepMeridianDubai PoliceGuide page
Business event → runSigned trigger (invoice submitted)Signed trigger (report / objection filed)Triggers
Agent acts on your dataMCP: invoice.*, po.get, vendor.get, payment.recordMCP: incident.*, dispatch.*, fines.*, objection.*, case.*MCP
Human step mid-workflowwait_event (manager approval)wait_event (dispatcher / committee)Workflows
Privileged actionpayment.record under a high-risk policydispatch.commit, objection.decide, court.file_referralApprovals
Decide from your UIConsolePortal proxy with a server-held keyProxy
Back to your systemSigned webhooks, deduped, applied asyncSameWebhooks
Provisioningscripts/setup-hive.ts, slug-keyed, idempotentSame, plus a reset scriptProvisioning

The runnable example in this portal (backend-integration-node) is a deliberately small version of the same shape: one Express process with the four surfaces and a provisioning script, so you can read all of it in ten minutes before opening the demos.

In the repository: demo/portal/ (Meridian), demo/dpf/ (Dubai Police), demo/mocd/ (a citizen-cases variant of the same loop), and the runbook docs/runbooks/dubai-police-demo.md.