laboratory/docs/NATIVE_AGENT_WORK_GRANTS_V1.md
Claudiu Schuster ac1427f895
All checks were successful
access-validation-contract / contract (push) Successful in 3m19s
ci / ci (push) Successful in 4m28s
Add native agent work grants to the MCP gateway (#149)
2026-10-22 22:50:18 +00:00

6.2 KiB

Native agent work grants — MCP adapter v1

Implementation source; live deployment and end-to-end acceptance are separate. This is the thin Laboratory adapter for the reviewed Core .forgejo/AGENT_WORK_GRANT_V1.md design and the visible delegation requirements. A matching Core must implement the native routes, browser session approval and durable transaction boundary. The adapter does not create that authority itself.

Authenticated tools and native routes

Tool Inputs Native operation
list_agent_work_grants optional positive page, optional state GET /api/v1/agent-work-grants outside /user
get_agent_work_grant owner, repo, positive native grant_id GET /api/v1/repos/{owner}/{repo}/agent-work-grants/{grant_id}
confirm_agent_work_grant context plus exact proposal triple POST .../{grant_id}/confirm
submit_agent_work_proposal context, proposal triple, durable request_key, title, body POST .../{grant_id}/issues
get_agent_work_submission context and the same request_key GET .../{grant_id}/submissions/{request_key}

The proposal triple is scalar: proposal_schema="agent_work_grant.v1", proposal_version=1 (JSON integer), and the native lowercase 64-hex proposal_digest. The client must repeat the read proposal exactly. It does not supply the server's mutable CAS state_revision. IDs are positive native signed 64-bit integers, not names, booleans or numeric strings. Request keys are canonical lowercase UUIDv4 strings, safely escaped as a complete path segment for readback. The adapter never generates or replaces one.

The only submission fields sent to Core are the triple plus request_key/title/body. Unknown inputs, including human authorization, caller-defined issuer/agent IDs, sponsorship, scope changes or a claimed approval, are refused before upstream work. The current request bearer takes precedence over process configuration; stdio retains its existing own-token or Basic-auth configuration. No human browser credential is forwarded.

Core rechecks the actual native bot, selected grant, current repository permissions and credential scope/resource reducer. Confirm/submission require read:repository and write:issue, including the bot's existing code/issue reads. No read:user scope is added by this adapter. Preparing a grant is not approval; bot confirmation is not human approval. Human preparation, review, approval and withdrawal remain native browser-session operations. Missing private access is an explicit collaboration prerequisite, never an implicit membership or credential grant.

Native results and uncertain delivery

Single-grant tools return the complete native grant plus native_status. Submit/readback return native {grant,submission} plus native_status. Core's native participant/repository/Issue IDs and submission status are validated for consistency; requested owner/repo or caller text never invents missing attribution. New submission is HTTP201; a matching committed replay is HTTP200. The adapter sends each POST once and has no write retry/fallback.

HTTP400/401/403/404/409 retain their native meaning in bounded MCP errors. No redirect is followed. Raw upstream error bodies, Location headers, malformed protocol bytes and reflected credentials are not exposed. Malformed/incomplete success responses do not publish a receipt. A lost response, unexpected status or malformed write response leaves delivery uncertain: read back the same owner/repo/grant/key with get_agent_work_submission. Do not infer failure, generate a new key or automatically submit again. For uncertain confirmation, read the grant before deciding whether the identical confirmation is needed. Local input/credential/budget refusal happens before a native request.

Native receipts retain historical Issue ID/index/URL with outcomes submitted, issue_deleted or issue_binding_changed. A deleted Issue does not make its key available again. Withdrawal prevents new grant operations while an authorized receipt read can still reconcile a prior commit. A receipt remains review_state="awaiting_review"; it is not human result acceptance. Native Issue/API/list verified attribution is Core-owned; ordinary API/MCP/Git activity does not gain an assignment marker.

Title/body and native task/summary text remain whole strict UTF-8, without a new gateway text cap, excerpt or replacement. Existing native validation, gateway budgets, the remote MCP 1MiB request-body limit and the existing native client socket timeout still apply. This adapter does not remove those transport bounds or promise that every large request is accepted.

Discovery and acceptance

List requests ask Core for limit=30 and one positive page. Omit state for the native nonrevoked default, or use awaiting_agent, awaiting_human, active, revoked or all. Continue with next_page until has_more=false. Native total/Link relations determine continuation even if Core clamps the width. Link addresses are never followed; each page makes a fresh fixed-origin authenticated request. Pages are current observations (snapshot=false). There is no cursor, grant cache or atomic-list claim.

test_agent_work_grants.py uses real loopback HTTP and MCP dispatch, normally and under -O: strict inputs, credential choice, native errors, redirect refusal, complete text, pagination, malformed/lost responses, same-key readback, no hidden repeated write, receipt consistency and unchanged public four tools.

The required full scripts/e2e-local.sh invokes test_agent_work_grants_native.py on a fresh isolated stack with the tested Core image. Actual native HTML forms and session nonces prepare/approve/withdraw; real native bots call authenticated HTTP MCP. Separate setup privilege creates fixtures/deletes the Issue and never enters the MCP child. The fixture exercises 31 pending grants across real pages, full Issue body/actor readback, wrong bot and scope, ordinary unassigned activity, same-key replay/deletion tombstone, withdrawal, independent grants and current access loss. Core's separate real browser and SQLite/PostgreSQL gates remain required. Tests and source do not constitute runtime admission. See the runbook.