Govern the agents you already run on Temporal. Polyflow installs as a worker plugin, and your workflow code stays byte-identical. There are four levels, each one change to your code, and you can stop at any of them.
git clone -b platform/temporal-governance https://github.com/cognitive-fab/polyflow cd polyflow/platform && npm install
Everything on this page runs against Temporal's local dev server. Run the tests once so you know your environment is complete; the Temporal suite downloads a dev server on first run.
Every check below is a consistency check, not a proof. It is exhaustive over the finite domains a contract declares, and nothing more.
temporal server start-dev (cd packages/kernel && npm test) (cd packages/temporal && node scripts/test-each.mjs)
On Windows, some sample paths are long: clone with git -c core.longpaths=true clone … if checkout reports "Filename too long".
Add the plugin to a worker, last in the list: it must see the final input every other plugin produced. From then on, every activity, child workflow, signal and Nexus operation a workflow orders becomes a proposal, a verdict, an effect and an observation on a hash-chained ledger.
The events ride on the headers of the activities the workflow already schedules, so Temporal's history alone can rebuild the record. A sink that is down loses nothing.
plugins: [new PolyflowPlugin({ sink: fileSink('./ledger'), signingKey })]npx polyflow verify ./ledger/default/<workflow-id>/<run-id>.jsonl --trust ./keys/trust.json
The verifier is Apache-2.0, so an auditor or a security team can check a record with no licence conversation.
A policy names your activities as effects, each with a consequence class, and states rules over their sequence: requires-prior, at-most, budgets, rates, and approvals bound to one effect. Admission refuses a policy that guards something no effect declares.
A call that would break a rule fails before it is scheduled, with a witness the agent can re-plan from. Nothing in the agent's prompt needs to know the rules.
npx polyflow policy policy.json --out admitted.json
Then set level: 'guard' on the plugin. An activity the policy does not name is denied unless you say otherwise: a guard that allows what it does not know fails open.
Upstream, "no payment without approval" is one if in the workflow, and the approval signal names nobody. In the port, the control flow is a small state machine that admission checks over every path, and activities.ts is byte-identical to Temporal's.
Delete the approval step and admission refuses the machine, naming the guarantee it breaks. With a trust store on the worker, a machine whose bytes differ from its certificate is refused at start.
cd examples/temporal_samples/expense npm run server # the sample's expense server on :3000 npm run start # the worker npm run workflow-approve
npx polyflow admit ./machine
In CI, --key ci.key.json signs the certificate; a worker with a trust store refuses anything unsigned.
patched()Temporal's answer to changing a workflow with runs in flight is patched(): both branches stay in the code until the old runs drain. With a certified machine the question becomes checkable: for every state a live run holds, can the new version take it?
vet answers per run: auto-upgrade, migrate, or pin. The gate promotes the new version under Worker Versioning, and a step in flight finishes where it started. The saga port adds a v2 this way, with no patched() in the code.
npx polyflow vet --old ./machines/v1 --new ./machines/v2 \ --fleet fleet.json --trust ./keys/trust.json
cd examples/temporal_samples/saga npm run start # v1 worker npm run workflow # account opened npm run start.v2 # v2 worker
A run that started under v1 gets v2's behaviour. That is a migration the operator accepts on vet's report, not an equivalence, and the record stays one chain across the hand-over.
The Python plugin covers G0 and G1, and its kernel is pinned byte for byte to the TypeScript one by a shared conformance corpus. The SDK routes every MCP tool through one activity per server, so the policy classifies by the tool the call carries.
In Temporal's customer_service sample, a seat change before a lookup is refused with a witness. The agent looks the booking up, then changes the seat, once. The ledger verifies under the TypeScript CLI.
A LangGraph binding covers the same two levels without Temporal: it records and guards an unmodified LangGraph agent's tool calls with the same ledger and guard.