PRD: Non-blocking supervise (async approval + proposal polling) #413

Merged
didericis merged 1 commits from prd-nonblocking-supervise into main 2026-07-18 21:05:52 -04:00
Collaborator

Closes #412.

PRD: 25155a1d5a/docs/prds/prd-new-nonblocking-supervise.md

Summary

Makes the per-bottle supervise MCP flow non-blocking, so an approval can happen out-of-band (a human review console, minutes-to-hours) without holding an HTTP request open or wedging the agent.

  • The pending timeout response now carries the proposal_id and points the agent at check-proposal.
  • New check-proposal MCP tool: non-blocking status lookup by proposal id — pending | approved | modified | rejected | unknown — so the agent resumes after a late decision instead of re-proposing (a duplicate).
  • The short synchronous grace window (SUPERVISE_RESPONSE_TIMEOUT_SECONDS, default 30s) is kept for the "operator is right there" fast path.

Enforcement is unchanged — the MCP tools only propose policy; the egress proxy and git-gate still enforce — so returning early opens no hole.

Behavior / design

check-proposal is attributed to the calling bottle by source IP exactly like the proposal tools, reads the queue only (never creates a Proposal, so it stays out of TOOLS), and archives a decided proposal exactly like the synchronous path. A proposal that times out to pending stays visible to the operator until it is both decided and polled.

Tests

TestNonBlockingSupervise: pending-carries-id, grace-window timeout stays queued, check-proposal approved/rejected/pending/unknown, arg validation, and a full pending -> decide -> poll round trip. Full unit suite green, pyright clean on the touched files

Follow-ups (in the PRD, not this PR)

  • git-gate pre-receive reject-fast + re-push (synchronous, cannot poll)
  • Per-bottle in-flight-proposal backpressure cap
  • MCP server->client notifications; web-console review flow (RBAC, audit)
Closes #412. PRD: https://gitea.dideric.is/didericis/bot-bottle/src/commit/25155a1d5ac9fd05d22ed61f5c28b0d52d405cb5/docs/prds/prd-new-nonblocking-supervise.md ## Summary Makes the per-bottle supervise MCP flow non-blocking, so an approval can happen out-of-band (a human review console, minutes-to-hours) without holding an HTTP request open or wedging the agent. - The `pending` timeout response now carries the `proposal_id` and points the agent at `check-proposal`. - New `check-proposal` MCP tool: non-blocking status lookup by proposal id — `pending` | `approved` | `modified` | `rejected` | `unknown` — so the agent resumes after a late decision instead of re-proposing (a duplicate). - The short synchronous grace window (`SUPERVISE_RESPONSE_TIMEOUT_SECONDS`, default 30s) is kept for the "operator is right there" fast path. Enforcement is unchanged — the MCP tools only *propose* policy; the egress proxy and git-gate still enforce — so returning early opens no hole. ## Behavior / design `check-proposal` is attributed to the calling bottle by source IP exactly like the proposal tools, reads the queue only (never creates a Proposal, so it stays out of `TOOLS`), and archives a decided proposal exactly like the synchronous path. A proposal that times out to `pending` stays visible to the operator until it is both decided and polled. ## Tests `TestNonBlockingSupervise`: pending-carries-id, grace-window timeout stays queued, `check-proposal` approved/rejected/pending/unknown, arg validation, and a full pending -> decide -> poll round trip. Full unit suite green, pyright clean on the touched files ## Follow-ups (in the PRD, not this PR) - git-gate `pre-receive` reject-fast + re-push (synchronous, cannot poll) - Per-bottle in-flight-proposal backpressure cap - MCP server->client notifications; web-console review flow (RBAC, audit)
didericis force-pushed prd-nonblocking-supervise from 25155a1d5a to 5ca3209b41 2026-07-18 14:28:00 -04:00 Compare
didericis force-pushed prd-nonblocking-supervise from 5ca3209b41 to ae9a395162 2026-07-18 14:32:13 -04:00 Compare
didericis force-pushed prd-nonblocking-supervise from ae9a395162 to 143281705d 2026-07-18 14:44:37 -04:00 Compare
didericis added 1 commit 2026-07-18 17:06:09 -04:00
feat(supervise): non-blocking MCP — pending carries proposal id + check-proposal poll tool
test / integration (pull_request) Successful in 10s
tracker-policy-pr / check-pr (pull_request) Successful in 11s
test / coverage (pull_request) Successful in 39s
test / unit (pull_request) Successful in 1m30s
prd-number / assign-numbers (push) Failing after 10s
test / integration (push) Successful in 7s
test / unit (push) Successful in 30s
lint / lint (push) Successful in 42s
test / coverage (push) Successful in 35s
Update Quality Badges / update-badges (push) Successful in 34s
034f774529
Closes #412.

The supervise MCP server blocked the agent's tool call polling for the
operator's decision, and on timeout returned `status: pending` with no
proposal id and no way to poll a specific proposal — so the only way to
learn a late decision was to re-propose (a duplicate).

- `handle_tools_call` pending timeout now returns the `proposal_id` and
  points the agent at `check-proposal`.
- New `check-proposal` MCP tool: non-blocking status lookup by proposal id
  (pending | approved | modified | rejected | unknown). Reuses the queue's
  FileNotFoundError semantics; archives a decided proposal exactly like the
  synchronous path, so a pending proposal stays visible to the operator
  until it's both decided and polled.
- `TOOL_CHECK_PROPOSAL` constant, re-exported from supervise; kept out of
  TOOLS since it never becomes a Proposal.tool.

Enforcement is unchanged — the tools only propose policy; the egress proxy
and git-gate still enforce — so returning early opens no hole. Follow-ups
(git-gate reject-requeue, backpressure, notifications, web console) are in
the PRD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBCHap11yGAKuKfsehNPaD
didericis force-pushed prd-nonblocking-supervise from 143281705d to 034f774529 2026-07-18 17:06:09 -04:00 Compare
didericis-codex approved these changes 2026-07-18 20:54:31 -04:00
didericis-codex left a comment
Collaborator

No actionable findings. The pending response now carries the proposal ID, check-proposal preserves bottle attribution and avoids creating duplicate proposals, and terminal decisions follow the existing archive/error semantics. I also ran python3 -m unittest tests.unit.test_supervise_server: 63 tests passed.

No actionable findings. The pending response now carries the proposal ID, `check-proposal` preserves bottle attribution and avoids creating duplicate proposals, and terminal decisions follow the existing archive/error semantics. I also ran `python3 -m unittest tests.unit.test_supervise_server`: 63 tests passed.
didericis merged commit 034f774529 into main 2026-07-18 21:05:52 -04:00
didericis deleted branch prd-nonblocking-supervise 2026-07-18 21:05:53 -04:00
Sign in to join this conversation.