feat: reactor acts as a service account (owned, reviewable autonomous proposals) - #86
Merged
Conversation
…owned and visible An autonomous reactor run has no human caller, so its agent's API calls had no identity: under local trust they fell back to local-user, under authentication they were rejected (401). Neither yielded a reviewable owner. Introduce built-in service accounts (reactor, system) in @braidhq/schema. The reactor runs as the reactor service account by minting a short-lived session and threading it as the run's caller token, so its proposals are owned by 'reactor'. The auth middleware honours a valid Bearer session even under local trust (Studio sends none, so nothing is shadowed), and the accounts are seeded as admins so they clear the workspace-access gate via the existing admin-to-owner rule. Pending visibility now treats any service account owner as always-visible (isServiceAccount), generalising the prior owner === 'system' special-case, so a sole workspace owner can review autonomous proposals. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s only Autonomous (service-account) proposals and clarifications can only be applied by an owner, so restrict their pending visibility to owners too: the list routes pass includeServiceAccounts only when the viewer is an owner (or when there is no viewer context, an open in-memory composition). Also prune already-expired sessions on issue, so the reactor's per-cycle short-lived tokens do not accumulate in the session file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nomy Service-ness is now registry data (User.kind='service') snapshotted onto each proposal and ticket as ownerKind at submit time, via the user directory. The HITL filter checks ownerKind==='service' instead of the hardcoded SERVICE_ACCOUNTS set, so a new autonomous component needs no schema edit, it just seeds its own account with ensureServiceAccount. Drops schema/identity.ts and the read-time service-account id plumbing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An autonomous reactor run has no human caller, so the AI agent's API calls carried no identity: under local trust they fell back to
local-user, under authentication they were rejected (401). Neither produced a reviewable, correctly-attributed proposal owner.This gives the reactor a real, non-human identity so its proposals are owned and reviewable, in both auth modes, without hardcoding a mode branch.
Design: service accounts
@braidhq/schemagains a small, extensible registry of built-in service accounts (reactor, plus a genericsystem). Adding a future autonomous component is one entry here, seeding, auth, workspace access, and pending-visibility all read the set viaisServiceAccount, so none of them need to change.reactorservice account. It mints a short-lived session and threads it as the run'scallerToken, which propagates through the existing subprocess plumbing (BRAID_TOKENenv, MCP gateway Bearer) to the agent's API calls. So its proposals are owned byreactor.isServiceAccount(owner)), generalising the priorowner === 'system'special-case. A sole workspace owner can now review autonomous proposals.Why this over the alternatives
Re-owning proposals after the fact does not work in authenticated mode, the agent's submit is rejected before anything exists to re-own. A trusted internal credential is the only way the round-tripping agent can authenticate as a known non-human principal while an anonymous caller stays rejected.
Verification
pnpm typecheckgreen (15/15);pnpm lintclean.reactor,local-user, and another user), a logged-in workspace owner sees only thereactor-owned one in the personal Pending view, the other-user and non-viewer ones stay hidden. Confirms service-account visibility without breaking the personal filter.reactorandsystemare seeded into the user registry on boot.Supersedes the closed #85 (which removed a Studio toggle gate, the wrong layer). Relates to #83.
🤖 Generated with Claude Code