Skip to content

AA10a: Save gate: proposal status, stored-proposal resolution, failure copy - #857

Merged
LinuxDevil merged 1 commit into
mainfrom
fb-aa10a-save-gate
Nov 3, 2026
Merged

LinuxDevil merged 1 commit into
mainfrom
fb-aa10a-save-gate

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

What changed

Pure save-gate logic for the Agent Author in apps/agent-forge/server/author/proposal/ — the structural rule that no proposal reaches disk until the user has seen and answered its card:

  • gate.ts — pure functions only, no I/O:
    • transcriptProposalStatus(messages, identity) scans the stored transcript for an identity's newest card: an ok proposeAgent* part opens it, a matching saveAgent* part or the user's Apply uiEvent saves it, a matching discardPendingChanges clears it. Failed (ok: false) propose parts — including bounced reviews — never count as cards, and update tools are filtered per agentId so a card for agent A never moves agent B's gate.
    • saveBlockingError(turn, identity) decides whether a model save may run: UNANSWERED when the card was proposed this turn (the stored transcript cannot know about it), NO_PROPOSAL when discarded this turn or absent in the stored transcript, ALREADY_SAVED when saved either way — one confirmation buys exactly one save.
    • resolveStoredProposal(messages, identity, proposalId) resolves the stored card a save would write, refusing AMBIGUOUS_PROPOSAL (id on more than one tool call), UNKNOWN_PROPOSAL, WRONG_AGENT, STALE_PROPOSAL (carries latestProposalId), ALREADY_SAVED and NO_PROPOSAL.
    • identityOfProposal (which gate a bare proposalId belongs to, for the Apply route) and openProposalCards (open cards for the turn-context block, newest last, named from each card's spec.name).
  • failures.ts — one builder per refusal code; error/next are model-facing (name tools and ids), userMessage is plain words with no tool names or ids.
  • wiring.ts — the runner's turnContextInput now lists real open proposal cards via openProposalCards(session.transcript) instead of [].

The save tools, writes and HTTP Apply route are out of scope (AA10b); this PR lands the enforcement they call into.

How tested

  • gate.test.ts: all 24 rows of the scenario table (stored transcript × this-turn state), 24/24 pass.
  • failures.test.ts: every builder returns non-empty error/userMessage/next; a scan rejects tool names and ids in user-facing copy.
  • Studio: typecheck:server clean; test:server 1055 pass; npm test -- --run 956 pass; typecheck and lint clean (warnings-only, pre-existing).
  • Root: tsc --noEmit, lint, test:coverage (480 files, 0 failures), fallow, docs:ratchet, api:check (20/20 up to date), docs:llms:check, size-check all pass.

Acceptance criteria

  • All 24 rows pass.
  • No function in this file does I/O.
  • Every failure has non-empty error, userMessage and next; no userMessage contains a tool name or an id.

Generated with Devin

…ution, failure copy

Pure functions over the stored transcript and the turn's ProposalTurnState
that decide whether a save may run and which card it writes - the
structural "ask before you write" rule that holds even when the model
ignores the prompt. transcriptProposalStatus/saveBlockingError gate the
model save path (UNANSWERED same-turn cards, NO_PROPOSAL, ALREADY_SAVED);
resolveStoredProposal refuses ambiguous, unknown, wrong-agent, stale,
saved and discarded ids; identityOfProposal and openProposalCards serve
the Apply route and the turn-context block. Every refusal is an
AuthorToolFailure with model and plain-words copy; nothing writes to disk.

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@LinuxDevil
LinuxDevil merged commit fc24d15 into main Nov 3, 2026
4 checks passed
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