Governance: a review gate in front of agent writes

A structural review gate for AI agent writes: work pauses before it runs, a human reviews the actual draft, and every approval lands in an exportable audit trail. Here's the mechanism behind the human-in-the-loop approval gate, not just the promise.

Jul 3, 202617 min read

Emerjent governs AI agents with a structural gate. Gated work pauses before it runs and files a write intent. A reviewer sees the actual draft, edits it in place, or sends it back with feedback. Only approval releases the work, and every decision lands in an append-only record you can export, strictly scoped to your own workspace. Approval is review, not a rubber stamp, and the rest of this article is how each guarantee is actually enforced in code.

The problem: autonomy without a paper trail

Agencies want agents doing real work: drafting the monthly portal report, assembling the proposal, writing the client update. The blocker usually isn't capability. It's that client-facing work goes out under your name, and "the AI did it" is not an answer you can give a client when something ships wrong.

The major governance frameworks agree on the shape of the answer. NIST's AI Risk Management Framework makes governance a cross-cutting function: accountability structures, defined policies, and oversight that spans the whole AI lifecycle rather than bolting on at the end. The EU AI Act goes further for high-risk systems, requiring in Article 14 that they be designed so people can effectively oversee them, with oversight measures matched to the system's risk, its level of autonomy, and its context of use. Both describe what human-in-the-loop oversight has to achieve. Neither tells you how to build it into a working agent platform.

Emerjent's position is that trust in agent work needs three things built into the machinery itself: a gate the work can't route around, a record of who approved what, and a way to say "almost, fix this" that costs less than starting over. This piece walks each one down to the mechanism.

Two surfaces, one evaluator

Diagram showing Surface A (governance_requests, governance_request_approvals) and Surface B (agents.write_intents, agents.intent_approvals) both feeding into the single evaluateApprovalProgress function, which returns stage state and who can decide next. One evaluator, called by both governance surfaces, so approved means the same thing everywhere.

The first thing worth knowing is that "who reviews what" splits into two structurally different surfaces, and they were built to share exactly one decision-evaluation code path so their behavior can't drift.

Surface A governs human-requested actions. When a person (or an agent acting under a role that isn't allowed to publish unilaterally) triggers a client-facing action, a governance check gates it before it executes. The governed action types are the client-facing ones: publishing an update to a client portal, generating a proposal, posting a case study, sending a client message, publishing a card from a Space. Each pending action becomes a governance_requests row, and each decision on it is a row in a separate ledger, governance_request_approvals.

Surface B governs agent write intents. When an agent inside a workflow reaches a step that wants to write, or an in-app copilot proposes a change, that proposal parks as an agents.write_intents row, with each decision recorded in agents.intent_approvals. This is the surface behind the write-intent queue you approve from in chat.

The two surfaces have different origins and different schemas, but a review that means one thing on the portal-publish path and something subtly different on the agent path is a bug waiting to happen. So the actual decision logic lives in one pure function, evaluateApprovalProgress. Given the policy's stages and the decisions recorded so far, it returns the current state (pending, satisfied, or rejected), which stage is open, how many approvals that stage has versus how many it needs, and who is eligible to decide next. It performs no I/O, which means it is unit-testable in isolation and, more to the point, it is the single source of truth both surfaces call. Surface A calls it when a human decides a request. Surface B calls it when someone decides a write intent. There is one place where "is this approved yet" is answered, not two that have to be kept in sync by hand.

Write intents: the agent proposes, the human disposes

Pause before the work runs

Diagram of a gated workflow step being instantiated, stamped with approval_gate true, then paused before the atomic claim so a write intent is filed and a human decision releases it or re-parks it. The pause sits strictly before the atomic claim, which is what makes the gate structural rather than advisory.

An Emerjent workflow is a multi-step agent pipeline. Any step can be marked as requiring approval. The gate is enforced at two moments, and the second one is why it can't be forced open.

When a workflow is instantiated, a step configured with requires_approval gets approval_gate: true stamped onto its context. Then, at execution time, the runtime checks for that flag and holds the step in a paused state before the atomic claim that would let it run, filing a write intent that carries a single requested write: release this chain step. The step does not proceed after a grace period. It cannot be reached by the rest of the workflow. A human decision is the only thing that releases it.

That "before the atomic claim" detail is the load-bearing part. An earlier version of this gate had a subtler failure: the step's requires_approval value was read but never persisted at instantiation, so the flag was a no-op on every path and gated steps quietly ran anyway. The fix made the gate structural rather than advisory. The pause happens ahead of the claim, so even a forced resume re-pauses the step instead of running it. Enforcement lives at execution time, not just at scheduling time, which is what makes "the work can't route around the gate" a property rather than a hope.

The ordering is the point. A review surface that shows you what an agent already did is a notification; by the time you read it, the client may have too. Pausing before execution means the question is still open when the human arrives.

Review the draft, not a checkbox

The intent carries the actual upstream output the gated step will act on. Concretely, the draft you review is the output of the step or steps the gated step depends on. If the gated step publishes a report to a client portal, the reviewer reads the report itself, pulled from the step that wrote it. Approving means a person read the thing that's about to go out, not that they cleared an item from a queue.

This too came from a real gap. The first version of the approvals queue rendered a gated step as an opaque "1 write operation" card with nothing attached to review. Approving it proved nothing about the work. The gap got caught while reviewing a live client reporting chain, and closing it is most of what this section describes: the card now carries the draft, an inline editor, and the refine path, all hanging off whether the intent actually has draft content attached.

Approve, edit, or refine

Binary approve-or-reject is too coarse for real review. Most drafts are close: a wrong emphasis, a paragraph that should lead, a number to correct. The reviewer gets three moves, and each has different mechanics.

Edit at the gate

The reviewer can edit the draft directly in the review surface. On approval, the edited text is merged onto the released step's context as approved content, and when the step runs, its instruction builder injects that text verbatim under a header marking it as human-reviewed and approved at the gate, then skips the usual predecessor lookup entirely. The released step acts on exactly the approved words, not on a regenerated approximation of them. A last-mile fix doesn't cost a re-run, and there is no gap between what the human approved and what the system does.

One honest limitation lives here. The editable field is prefilled from a preview of the draft that is capped at roughly 8,000 characters, so editing a draft longer than that would approve a truncated version. Portal narratives run well under the cap, and an untouched approval is unaffected because it uses the full original upstream output. The cap is a known edge, not a hidden one.

Outbound email intents get their own edit affordances for the same reason: an approver can correct the recipient, the subject, or the body before a held send dispatches, with the corrected recipient also redirecting the audit row for that send. An approver can even save those edits onto the held intent without sending, so the edits become the new baseline and a later untouched approval still sends them. Edit, confirm the text took, then approve as a separate step.

The refine loop

When the draft needs more than a touch-up, the reviewer sends it back with written feedback instead of rejecting it. Refine re-runs the full ancestor subgraph of the gate, meaning every upstream step the gated step depends on, with the feedback injected, then files a fresh intent at the gate carrying the regenerated draft. The earlier intent is marked superseded rather than deleted, so the trail keeps both drafts and the feedback that connects them.

Refine re-runs upstream work, and upstream work costs model spend. The review surface says so before you click. This was a deliberate design choice: an immediate-draft-only alternative that regenerated less was considered and declined, on the logic that when a reviewer asks for a rewrite, the expectation is that the whole reasoning chain reconsiders, not just the final paragraph. If token cost ever hurts, the decision is revisitable.

One interaction is worth knowing: a refine restarts the fresh intent at the first stage of any approval chain. The draft changed, so everyone reviews it again. That is deliberate, and it is enforced by the same evaluator that runs every other decision.

Policies: who can approve what

Gates are half of governance. The other half is who's allowed to open them. Emerjent attaches approval policies to client-facing actions, set per action type. A policy names which roles or specific people can approve at each stage, whether someone can approve work they requested themselves, and how owner approval interacts with the stages.

Self-approval defaults are sized to the org, and the default is computed rather than guessed. A solo workspace defaults allow_self_approval to true; there's nobody else, and the gate's value there is the forced read-through before anything client-facing moves. A multi-member workspace defaults it the other way: the requester doesn't count toward approving their own request, at any stage.

Owner bypass is explicit, not accidental, and it is a single policy flag. By default owner_bypass is on, and an owner's approval instantly satisfies every remaining stage, because the owner is the backstop that keeps work from getting stuck. An org that configured staged review deliberately can turn the flag off, after which an owner is still exempt from the role and self-approval checks but their approval counts toward the current stage only, the same as anyone else's. The bypass is applied at decision time and marked on the just-written decision, never backfilled onto historical ledger rows, so the audit trail records what actually happened rather than a retroactive rewrite.

More than one set of eyes

Diagram of a two stage approval policy: a quorum stage requiring two approvers, then a sequential admin stage, backed by a decision ledger where a unique constraint on request, stage, and approver makes a duplicate approval attempt fail to insert. Quorum and sequence are policy configuration, but the guarantee is a uniqueness constraint on the ledger itself.

Some decisions shouldn't rest on one person, and regulation already recognizes this: the EU AI Act requires that certain high-risk biometric identifications be separately verified by at least two people before anyone acts on them. Two sets of eyes is an old control. Emerjent builds it into approval policies as a small stage model and one database constraint that does the real work.

A policy's stages are stored as a small structure: an ordered list where each stage names its eligible approver roles or specific users and a required count. A single stage with a required count above one is a quorum. Multiple stages are a sequence. Multiple stages that each carry their own required count are both at once. A policy with no stages configured is normalized at read time into a synthetic single stage, so legacy policies keep behaving exactly as before and there is still only one evaluation path.

Quorum

A review stage can require more than one approval before it's satisfied. The correctness guarantee is not in application logic that could be raced; it is a uniqueness constraint on the decision ledger over the combination of request, stage, and approver. One person physically cannot record two approvals at the same stage, because the second insert conflicts and is dropped. Two required approvals means two different humans actually decided. The same constraint exists on the agent surface's ledger for the same reason.

Sequence

Stages open in order. Stage two doesn't accept decisions until stage one is satisfied, so a PM review genuinely precedes the admin sign-off instead of racing it. Turn on require_distinct_approvers and anyone who decided at an earlier stage becomes ineligible at later ones, checked against the full ledger. That is segregation of duties in the classic internal-controls sense: the GAO's Green Book, the internal-control standard for U.S. federal agencies, treats dividing key duties among different people as a primary defense against error and against management override. The same logic holds when the transaction is an agent publishing client work, and the owner-bypass flag above is exactly the switch that keeps override a configured choice rather than a quiet default.

The concurrency argument in one paragraph

Two approvers acting at the same instant is the case that breaks naive gates, so it is worth stating how it is handled. Duplicate-person races are caught by the ledger's uniqueness constraint. Distinct-person races on a single-approval stage are caught by a status-guarded update: the stage advance and the terminal transition only apply when the row is still pending at the stage the decider thought it was on. If another approver advanced it first, zero rows update, and the caller re-reads and returns the current state instead of double-advancing. No explicit locks, no lost decisions. On the agent surface, current stage is derived from the ledger on every decision rather than stored, so even a benign extra approval recorded against an already-satisfied stage just counts forward harmlessly.

Approval that doesn't stall politely

Sequential review has a known failure mode: nothing is wrong, everyone intends to approve, and the work sits. Emerjent surfaces "waiting on you" per item, driven by a pure predicate that is true when the item is pending, the caller hasn't already decided at the current stage, and the caller is eligible to decide it now. That predicate feeds the approval queue and a cross-surface "your turn" view that returns pending human requests and pending agent intents together, filtered to items awaiting your decision. It also feeds the morning briefing, which carries a waiting-on-approval section that skips itself when the queue is empty, showing what's at your stage, how far along it is, and how long it has been your turn.

The turn clock is sourced carefully. On the human surface, "how long has this been your turn" comes from the stage-advance events that fire whenever a stage completes. The agent surface writes no such events, so rather than bolt on an event stream, the turn start there is derived from the decision trail itself: the first stage's turn opens when the intent is created, and a later stage's turn opens at the most recent approval of the stage before it. Same answer, no extra schema.

Guardrails on automation itself

Review gates govern what agents produce. A second set of guardrails governs when automation is allowed to start itself, because a gate on the output does nothing if a runaway trigger is firing the workflow a thousand times.

Emerjent workflows can fire from events: all onboarding steps complete, so the kickoff workflow starts. This is the third way a workflow can start, alongside cron schedules and inbound webhooks, and it ships locked down. A new event trigger is created observe-only by default. In that mode the evaluation path runs and records what it would have fired, and fires nothing. Promoting it to live is one deliberate flag, and promotion backfills the trigger's dedup ledger with the current backlog first, so the first live tick fires only newly-satisfying subjects instead of stampeding through everything that already qualified.

Once live, every trigger runs inside bounds. There is a per-trigger fire-rate ceiling, defaulting to sixty live fires per rolling hour; breach it and the trigger auto-deactivates (reversibly) and raises a runaway alert rather than continuing. There is a loop-depth cap so workflows can't trigger each other into a spiral. There is a per-subject cooldown, defaulting to fire-once-ever per trigger and subject, configurable to a windowed re-fire. And there are two kill switches above all of it: an org-level switch that disables live trigger firing for the whole workspace, and a global environment switch that disables it platform-wide. Deactivating a trigger is the reversible pause; the scheduler simply skips inactive triggers.

Cost gets its own watchdog, tunable per org. Configurable canaries alert on burn anomalies: a per-agent burn signal (defaulting to alert above roughly $0.75 for a single agent in a thirty-minute window), an org-wide burn signal (defaulting to about $1.50 in the same window), an agent-domination signal that fires when one agent is taking an outsized share of runs (default above 80 percent, over a minimum run count so a quiet period can't trip it), a quota-projection signal that warns when you're on track to exhaust a plan within a set number of days, and an opt-in hard budget ceiling for total spend in the current week or month. Alerts deliver through the org's own configured notification channel. The canary's job is to make the anomaly loud early, while a human decides what to do about it. Automatic pausing on a canary trip is deliberately not shipped yet; the current contract is alert-and-decide, not act-without-asking.

Everything leaves a record

Every decision described above lands somewhere permanent. Approvals write to a per-decision ledger, one row per person per stage, with the decision and the reason, including refine feedback recorded verbatim. The human-action surface also keeps an append-only event log, so stage advances and decisions exist as events with actors and timestamps, each stage-advance event carrying which stage it moved from, which it moved to, the total stage count, and who is eligible next. Reconstructing who approved this, at which stage, and what they were looking at is a query, not an investigation.

Deletion doesn't erase the trail either. When a knowledge record is hard-deleted, the content-bearing fields on its change-history rows are redacted to a tombstone, but the skeleton survives: identifiers, timestamps, and the fact that a change happened and was reviewed. The change history is deliberately decoupled from the record itself at the schema level, so it outlives the thing it describes. Even a GDPR erasure, which additionally drops the record's name from the final audit row, leaves the shape of the history intact.

Hand the trail to a client's compliance team

A record you can read is useful. A record you can hand over is a deliverable. An owner or admin can export the full approval decision-chain for a date range, a client, a deal, or an action type, and the export assembles both governance surfaces at once: the human-requested actions with their ledger, their stage-advance events, and the policy that was in force, and the agent write intents with their decision ledger, whose refine decisions are the revision loops. Each entry carries who requested it, every stage and every approver, the reasons they gave, per-stage turn durations, any refine loops, best-effort diffs of edited content, and the final disposition. It comes out as JSON for a system to ingest and as CSV for a person to open. PDF is a follow-on rendered outside the export path, kept off the server's boot dependencies on purpose.

That scoping is the load-bearing part. An audit export that leaked across tenants would hand one client's compliance trail to another, which is the single most damaging leak this system could have. So the organization scope is taken from the caller's authenticated context at the point the data is read, and any organization identifier passed in the request is ignored outright. The export tool is reachable by owners and admins only, and it sits on the denylist that agents can never call, so no agent can ever export the governance trail. The isolation wasn't assumed; it was proven three ways before it shipped: a boundary test confirming the input schema strips a passed organization id, a cross-organization impersonation test confirming an out-of-org caller gets nothing, and a proof against the live production system.

The export is itself audited. Every export call writes a row recording who pulled the org's audit data, with what filters, in what format, and how many rows came back, and that log table carries the full row-level-security treatment for the same reason everything else does. A compliance reviewer can see the export history. It is the audit of the audit, and it closes the loop.

When a client's compliance team asks how an agent-assisted deliverable was reviewed and signed off, the answer is a file you generate, not a story you reconstruct.

The same gate fronts what the platform learns

Emerjent learns your preferences from the edits reviewers make at these gates, and that learning is governed by the same machinery it came from. Every non-trivial gate edit is captured as a structured event with a line-level diff and an edit-distance measure, and only edits past a size threshold count as non-trivial, so a one-character fix doesn't teach anything. A nightly miner clusters recurring corrections per agent and task type, and once enough corroborating edits accumulate for a cluster, it distills a single candidate house rule.

The rule does not activate itself. It arrives as a write intent on the same approvals surface as any other proposal, and a human approves it, optionally editing the rule text at the gate first, before it ever shapes a future draft. Rejecting it archives it and suppresses near-duplicates for a set window. The miner never activates anything on its own, and there is a safety layer on top: a weekly job watches whether an approved rule coincided with reviewers suddenly editing more, and if a rule looks like it made outputs worse, it is automatically flagged back to review rather than left running. Nothing the platform infers about how you like work done goes live without passing the same gate as the work itself.

Built and run on a real practice

These gates weren't designed in the abstract. Emerjent is built and run on a real consulting practice, and the same workflows, gates, and policies described above sit in front of the operator's own client deliverables. That is also why the two failures mentioned earlier, the gate that read its flag but never persisted it and the approvals card that had nothing to review, were caught on live client reporting chains rather than in a test environment. The specificity in this article is the residue of using the thing.

Where this lives in the product

Governance is part of Emerjent's agent layer. How workflows are built, how steps depend on each other, and where gates sit in a pipeline are covered on the agents page; the wider architecture is at /technology. Elsewhere in this series, Agency Operations covers the workflows these gates govern, Memory covers the knowledge base agents draw on, and Learning covers how reviewer edits become governed house rules.

Common questions

Questions people ask about this.

What is an AI agent write intent?

A write intent is a paused, pending action an agent files instead of executing directly. When a workflow step is gated, the runtime holds it before the atomic claim that would let it run, attaching the draft the step would act on, and only a human decision releases, edits, or sends it back for revision.

How does human-in-the-loop approval work for AI agent writes?

A gated step pauses before execution and files a write intent carrying the actual draft output. A reviewer reads that draft, then approves it as-is, edits it directly and approves the edited version, or sends it back with feedback for a full re-run. Only approval releases the work.

What happens when a reviewer sends an agent's draft back for revision?

The refine path re-runs the full upstream chain the gated step depends on, with the reviewer's feedback injected, and files a new write intent with the regenerated draft. The prior draft is marked superseded rather than deleted, so the audit trail keeps both versions plus the feedback that connects them.

Can someone approve their own AI agent's action?

It depends on workspace size and policy. A solo workspace can default to allowing self-approval, since the gate's value there is the forced read-through before anything ships. A multi-member workspace typically excludes the requester from approving their own request at any stage.

How does an AI agent audit trail support compliance review?

Every approval decision writes to a per-decision ledger with the reviewer, the stage, and the reason, alongside an append-only event log of stage advances. An owner or admin can export the full decision chain for a date range, client, or action type as JSON or CSV, scoped strictly to their own workspace, to hand directly to a compliance team.

See the mechanism on your own practice.

Request access and walk through how this runs on a real engagement.