AAG Agent Action Gate

A small library for agent side effects

Review the exact action. Then run it once.

An agent prepares an action with a stable idempotency key. A separate reviewer accepts its input hash. The library claims the approved action in SQLite before calling the external operation.

Read setup and API
PROPOSAL / EXAMPLE pending review
actor
marketing-agent
action
pause-campaign
input
{ campaignId: "example-123" }
input hash
sha256: 8c2…illustrative
idempotency key
conversation-42/tool-call-1

The review surface shows the saved input. Approval must come from outside the agent.

The approval applies to bytes, not a vague intention.

The proposal stores canonical JSON and its hash. If the input changes, the old approval cannot authorize the new action.

01

Prepare

prepare() saves actor, action, input, expiry, and idempotency key. Repeating the same key and input returns the existing proposal.

pending
02

Review outside the agent

The application authenticates a reviewer and calls approve(id, reviewer, inputHash). The library does not authenticate people.

approved
03

Claim, then dispatch

execute() atomically moves one row from approved to executing. Only the process that wins that claim calls the operation.

executing
04

Record the result

A returned value becomes succeeded. The state change and the audit event share one SQLite transaction.

succeeded

The host keeps the approval method away from the agent.

The agent may prepare a proposal and later execute an approved one. Its tool list must not expose the review command.

Put the reviewer call behind your own authenticated UI or process. The operation callback still owns provider authorization, rate limits, and result verification.

const proposal = gate.prepare({
  actor: 'marketing-agent',
  action: 'pause-campaign',
  input: { campaignId: 'example-123' },
  idempotencyKey: 'conversation-42/tool-call-1',
});

// In a trusted review process:
gate.approve(proposal.id, reviewerId, proposal.inputHash);

// Back in the agent host:
const result = await gate.execute(proposal.id, operation);

A timeout is not permission to try again.

The provider may have completed the action before the connection broke. A thrown operation becomes uncertain; this proposal cannot dispatch again.

executing uncertain inspect provider reconcile

If the process dies while executing, an operator checks the provider, marks the interrupted action uncertain, and records the verified outcome. The library does not guess what happened remotely.

Built for a trusted host that needs an audit trail around agent actions. Local Bun SQLite, no distributed workflow engine.

Architecture ↗ Source ↗