How it works
What you author, what the engine enforces, what an agent receives, where a person stands in the way, and how the whole thing is put together underneath.
1 · Design time
A workflow owns two sibling things: the shapes of the items that flow through it, and the map they flow over. Fields, routes and rules hang off the shape; queues and junctions hang off the workflow and are shared by every shape in it.
Workflow
├── WorkItemType the "shape" of an item
│ ├── Tab one per stage, usually
│ ├── Field 15 types + list values
│ ├── Route this type's path over the queues
│ ├── Rule ─ RuleAction the enforcer
│ └── Scenario ─ Criterionwhen a rule fires
│
├── Queue ┐ workflow-level:
├── Junction ─ JunctionPath │ SHARED by every
├── ComponentLink │ type in the workflow
└── PermissionGroup ─ Grant ┘
Queues are keyed to the workflow; routes are keyed to the type. So two item types — say
Code Change and Project — can share one set of queues and travel through them
along completely different paths.
What a work item carries
Half of it is defined by the item's type; the other half is universal and behaves identically in every workflow you will ever author.
| Captured | When | What it is for |
|---|---|---|
| Field values | At creation, then edited at any stage that shows the field | The stage's artifact. A rule decides whether it is required, visible, read-only, or human-only at that queue. |
| The outcome field | At the end of a stage, before the item advances | The one enumerated value the stage exists to produce. It selects the route — the engine moves the item, not the worker. |
| Attachments | Any time; also on ingest from an external source | PDFs, Office files, images, .eml/.msg — 50 MB cap, extension allow-list, stored via the blob store. |
| Notes | Any time, by anyone working the item | Free narrative. Rules can add automatic notes on entry, exit, or field change. |
| History / events | Automatically, on every save, route, finish, lock and field change | The audit trail. Each entry carries actor and provenance (human / ai / app) stamped server-side from the auth path — never client-supplied. |
| Typed links | When one item realizes, verifies, covers or supersedes another | Traceability edges across items — including across workflows. |
| Estimated duration | At intake or any time after | A system attribute, not a form field, so every item is schedulable regardless of workflow. Feeds the project critical path. |
| Time entries | While the item is worked | Actual effort, against estimate. |
| Dependencies | Inside a project | Finish-to-start and friends, with lag, across workflows. The only place cross-item gating exists. |
| Cost telemetry | When an agent session ends | Tokens, model, duration and dollars per stage, so cost-per-outcome is a measured number rather than a hope. |
2 · Run time
The same eight moves whether the worker is a person in a browser or a model on the other end of an MCP connection. See what steps 3 to 7 look like on screen →
Created in the UI, imported from CSV, filed by another workflow, or posted to the ingestion port by a source — a folder watcher, a mail webhook, a cloud event, your own application. Ingestion is idempotent on a dedupe key, so re-delivery is safe, and the file rides along as an attachment.
A worker pulls the next item from a queue it is permitted to work. The claim is atomic — a competing pull skips a locked row rather than blocking — and it leases the item so two workers can never hold the same one. Ordering respects project dependencies first, then the earlier project due date, then arrival.
One call returns the item, its field values, the stage playbook bound to that queue, the reference documents that stage needs, and the queue contract. Context is scoped to the queue on purpose: the narrower the brief, the cheaper and more accurate the work.
Fields are written, notes added, attachments uploaded, time logged. Every save runs the rules for this queue: required becomes required, invisible stays invisible, a human-only field refuses an agent's write before the engine even evaluates it, and each write is stamped with actor and provenance.
The stage's one enumerated outcome field is filled — Pass, Fail,
NeedsHuman, whatever this stage declares. That value is the routing decision. A
session that ends without setting it did not complete the stage, and the item goes to the queue the
contract nominates for exactly that case.
BeforeWorkItemRouted rules evaluate. Missing evidence cancels the transition with a message.
Because the rules see the whole item, a gate can require something set five stages earlier — which is what
makes “this item followed the process” a checkable claim rather than an assumption.
At a queue flagged as a human checkpoint, an agent trying to advance the item out is refused with a 403 — route, finish, or batch, it fails closed. The item waits somewhere staffed and visible. Human-only fields record the decision, and the audit trail names who made it.
The engine performs the transition, not the worker; two mechanisms moving one item is how work lands in the wrong queue. On the way out, anything a future worker would want is contributed to the knowledge base, where it merges into the existing record on that topic instead of becoming a second copy of it.
3 · The interface between a stage and its worker
One call answers the only question a worker actually has: what do I set here, and where does each value send the item? A route that the contract cannot explain is a decision with no basis — so it should not exist.
{
"outcomeField": "TestResult",
"deliverables": ["TestPlan", "TestEvidence"],
"outcomes": [
{
"value": "Pass",
"destination": "Verify",
"whenToUse": "Everything the TestPlan requires
passed; evidence recorded."
},
{
"value": "Fail",
"destination": "Development",
"whenToUse": "Any planned case failed; quote the
failing cases in TestEvidence."
}
],
"noOutcome": "Management",
"engineRoutes": true
}
| Declaration | Meaning |
|---|---|
| outcomeField | The one field this stage exists to produce. Enumerated, never free text. |
| deliverables | The evidence the stage owes besides the outcome. Advancing without it is visible to supervision. |
| outcomes | For each value: the destination queue and one sentence saying when to use it. This is the routing table. |
| noOutcome | Where the item goes when a session ends with the outcome unset — a staffed queue, not a quarantine flag nobody looks at. |
| engineRoutes | States that the engine moves the item. The worker sets the outcome and stops. |
The deliverables live on the workflow itself, not in an agent's instructions. A field name written into shared code is a workflow-specific literal in a platform — the moment a second workflow arrives, that code is wrong for one of them.
4 · The enforcer
A rule is: a trigger, a set of scenarios (all true or any true), and one or more actions. It is data on the work-item type — so changing what your process guarantees is an authoring change, and it takes effect without a deployment.
Where in the item's life the rule gets its say.
BeforeWorkItemCreated AfterWorkItemCreated
BeforeWorkItemSave AfterWorkItemSave
OnFieldUpdate OnWorkItemOpen
OnButtonClick
BeforeWorkItemRouted AfterWorkItemRouted
BeforeWorkItemFinished AfterWorkItemFinished
What the rule does when its scenarios hold.
Worth stating plainly
An automatic route is put through the same gate rules, destination validation and human checkpoint as a hand-made one. Auto-routing stops a move being forgotten; it never grants permission for a move that would have been refused.
5 · The workers
Everything that acts on WorkflowHub — a person's browser session, an agent, an ingestion source, an external worker — authenticates as an ordinary principal with ordinary permissions. There is no separate, weaker machine door.
An API key is configured and the agent works queues end to end: claim, read context, do the work, write evidence, set the outcome, stop. Unattended, but never past a human gate.
No key: a person drives the model interactively through the MCP tools, step by step. Same API, same permissions, same audit trail — a human is simply in the loop for each move.
Work that needs no judgement — move a file, run an on-prem query, read a blob only your network can reach — goes to the leased task fabric instead: register, lease, heartbeat, complete or fail, with retries and a dead-letter.
6 · Memory
Retrieval quality is not a storage detail — it decides whether a worker trusts the archive or rebuilds the answer from scratch at full price. So the corpus is treated as a maintained artifact with its own workflow.
Ingestion, curation and retrieval are all just workflows and API calls — there is no separate, unaudited pipeline.
Verified, asserted, or unknown — and “nobody knows who wrote this” is stated honestly rather than rounded up to trusted. Trust starts from that state and moves with evidence.
A record found wrong is contradicted through a channel that raises a case, not quietly rewritten. A contradicted record stops being served.
What a stage delivers to its worker is the context window that matters. Binding decides what each queue receives, and an over-budget manifest is a defect — not untidiness.
7 · Underneath
C# on .NET 10, PostgreSQL 16 with pgvector, a React + TypeScript single-page app, and an MCP server that is a façade over the same API every other client uses.
Deployed on Cloud Run against Cloud SQL, or self-hosted with Docker Compose. The same image and the same schema either way.
Because the alternative is two places that decide who may do what. Every authorisation check, every rule evaluation, every audit stamp lives on one path. The SPA holds no database credential; neither does an agent. MCP is a façade over the API, not a second door into the data.
Every entity carries a fast internal identifier and a stable public UUID, and every foreign key is doubled. That is what lets a workflow definition be exported from one environment and imported into another with its identities intact — the basis of test environments, tenant portability, and shareable workflow bundles.
8 · Security
Identity is external; the application owns authorisation. No layer assumes the one above it did its job.
| Layer | What it enforces |
|---|---|
| Identity | OIDC single sign-on — Microsoft Entra ID, Google, Okta, Auth0. MFA and conditional access stay the identity provider's job. The caller is anchored to the provider's immutable subject, not an email address that changes, and a first-time user is provisioned from the token. |
| Principals | API keys are first-class principals stored hash-only, each carrying a fixed acts-as identity. A break-glass administrator key exists deliberately, so a misconfigured SSO cannot lock you out of your own system — without reintroducing a local password store. |
| Authorisation | 25 permission types, granted to groups over components (a queue, or a whole workflow), scoped to the workflow. Membership is explicit or resolved from an SSO group claim. Because groups belong to the workflow, they travel with its definition. |
| Agency | Being an agent is a claim established at authentication. Human checkpoints, human-only fields and direction overrides are checked against it server-side, so no client — and no prompt — can route around them. |
| Tenancy | The organisation is the tenant and the trust boundary: it owns members, workflows, groups and secrets. Cross-organisation sharing is an explicit, mutual, non-transitive trust link per shared resource — A trusting B and B trusting C never implies A trusts C. |
| Data | The database runs under a least-privilege login that cannot alter schema. Secrets are envelope-encrypted and never committed to a file that can be pushed. Attachments are validated by size and extension before anything is stored. |
| Audit | Every state change writes an event with actor, provenance and timestamp, stamped server-side from the authentication path. The audit trail is not a feature bolted on for compliance — it is the same data the reports and the supervision workflow read. |
9 · Scale
Isolation, billing and placement all follow the organisation. Every user has at least a personal org, so there is no separate solo-user path to maintain.
An organisation lives wholly inside one environment. A control plane provisions environments, moves organisations between them, and transports workflow definitions.
Agents and external workers execute; the platform brokers. That keeps the scaling problem a matter of partitioning organisations rather than sizing a compute farm.
Stuck items, looping items and contradicted knowledge raise cases into a supervision pipeline — so operational drift becomes tracked work instead of something a person happens to notice.
The engine is what makes autonomous work trustworthy: evidence before advance, separate eyes on review, and a person at every decision that has consequences.