Skip to content

feat: human-in-the-loop approval gate (step 7) - #3

Merged
prashar32 merged 1 commit into
mainfrom
feat/step-7-hitl-approval
May 30, 2026
Merged

feat: human-in-the-loop approval gate (step 7)#3
prashar32 merged 1 commit into
mainfrom
feat/step-7-hitl-approval

Conversation

@prashar32

Copy link
Copy Markdown
Owner

What & why

Implements the human-in-the-loop approval gate (§8 step 7). A side-effecting tool call that policy gates pauses until a human approves or denies it — the LLM proposes, deterministic code disposes. No LLM is in the approval decision.

Highlights

  • internal/approval — deterministic Policy (exact-tool match or side-effect glob like *write*, plus a fail-closed default-safe mode) and a Gate that persists a pending approval, notifies push channels, and blocks the calling goroutine until resolved or the run's context is cancelled.
  • Storageapprovals table (forward migration 00003) as an auditable trail; idempotent resolve.
  • APIPOST /v1/runs/{id}/approve, GET /v1/approvals, and GET /v1/runs/{id} (surfaces pendingApproval + waiting_approval).
  • Three channels — CLI (riskkernel approvals list/approve/deny), a local embedded admin page (/admin/approvals), and an optional webhook.
  • ConfigRISKKERNEL_APPROVAL_DEFAULT_SAFE (default true), RISKKERNEL_APPROVAL_WEBHOOK.

The gate's live producers are the MCP gateway (step 9) and the Python SDK (step 8); this PR lands the mechanism + channels + contract, fully tested.

Verification

  • go test -race ./... green; vet + gofmt clean.
  • Tests: policy match/glob/default-safe; gate block→approve/deny, context-cancel, resolve-unknown; storage CRUD + double-resolve; HTTP approve flow unblocks a blocked call; no-pending → 409.
  • Live: migration v3 applies, /admin/approvals serves, CLI reaches the daemon.

Notes

  • SECURITY.md updated: the approval webhook is the third user-configured egress point (carries approval metadata, never secrets).
  • api/v1: additive GET /v1/approvals; ApprovalRequest gains resolution fields.

A side-effecting tool call that policy gates pauses until a human approves or
denies it. Deterministic — no LLM decides what needs approval.

- internal/approval: Policy (exact-tool or side-effect-glob match, plus a
  fail-closed default-safe mode) and a Gate that persists a pending approval,
  notifies push channels, and BLOCKS the calling goroutine until resolved or
  the run's context is cancelled. Webhook notifier (user-configured egress).
- internal/storage: approvals table (forward migration 00003) + CRUD; resolve
  is idempotent (double-resolve -> ErrNotFound). Auditable record of who
  allowed which side effect, when, why.
- API: POST /v1/runs/{id}/approve (resolves the named or single pending
  approval, returns the run), GET /v1/approvals, and GET /v1/runs/{id} now
  surfaces pendingApproval + waiting_approval status.
- Three channels: CLI (`approvals list/approve/deny`, via the daemon API so it
  wakes the blocked waiter), a local embedded admin page (/admin/approvals),
  and the optional webhook.
- config: RISKKERNEL_APPROVAL_DEFAULT_SAFE (default true) + _WEBHOOK.
- SECURITY.md: document the webhook as the third user-configured egress point.
- api/v1: additive GET /v1/approvals; ApprovalRequest gains resolution fields.
- Tests: policy match/glob/default-safe; gate block->approve/deny, ctx-cancel,
  resolve-unknown; storage CRUD + double-resolve; HTTP approve flow unblocks a
  blocked call, no-pending 409. Verified live: v3 migration, endpoints, admin
  page, CLI. Full suite green with -race.
@prashar32
prashar32 merged commit 11e5964 into main May 30, 2026
2 checks passed
@prashar32
prashar32 deleted the feat/step-7-hitl-approval branch May 30, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant