Skip to content

fix(agentcore): read checkpoint answers given in the gate UI - #587

Merged
JWThewes merged 2 commits into
aws-samples:mainfrom
POLAARK:fix/checkpoint-selected-option
Oct 20, 2026
Merged

JWThewes merged 2 commits into
aws-samples:mainfrom
POLAARK:fix/checkpoint-selected-option

Conversation

@POLAARK

@POLAARK POLAARK commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Clicking Looks correct on a checkpoint gate (confirm_summary / request_plan_approval) in the UI never authorizes anything. The agent re-raises the checkpoint after every click, up to CHECKPOINT_MAX_ROUNDS.

Cause. The UI renders checkpoint gates with the generic QuestionEditor, which stores option indices:

{ "answers": [{ "selectedOptions": [0] }] }

chosenLabel (lambda/agentcore/mcp/process-bridge.js) only read perQuestion[0].answer, freeText, decision or a bare string, so it returned "" for every UI answer:

  • The resume message (formatResumeAnswer) told the agent The human answered the summary-confirmation checkpoint: "" — nothing is authorized yet … call confirm_summary again.
  • On the resume leg, rehydrateAuthorizations never minted the receipt.
  • A UI Request changes never recorded the withdrawal of an earlier approval.
  • The inline path returned re-ask.

Observed on a deployed 2.11 stack: four answered rounds, each stored as {answers:[{selectedOptions:[0]}]} with option 0 = "Looks correct", and the agent kept saying the confirmation "came back empty".

Fix.

  • chosenLabel(answer, questions) resolves a single well-formed index against the gate's own questions[0].options, which raiseCheckpoint writes, so the labels stay owned by the platform. All five call sites pass the gate's questions.
  • Only that exact label authorizes. A multi-select, out-of-range, negative or non-integer index, or a typed note with no option, names no label and gets a re-ask.
  • answerFreeText passes the reviewer's note (answers[0].freeText) to the agent as the requested-changes feedback.
  • Existing answer shapes behave as before.

Tests

New tests use the real UI shape:

  • inline approve and reject with a note
  • resume-leg receipt minting
  • resume-leg withdrawal: the existing test now runs for both the perQuestion and UI shapes
  • formatResumeAnswer approve and reject
  • malformed UI answers create no authority

The UI-shape tests fail on main and pass with this change.

  • vitest run --project=agentcore: everything passes except spawn-shutdown.test.js (5 s timeout under full-suite load). It passes 9/9 when run alone, repeatedly, and does not touch this code.
  • npm run lint: 0 errors. The 3 warnings in the touched files are pre-existing.
  • npm run format:check and npm run secretlint: clean.

Not in this PR

changeControlChoice (run-stage.js) parses the strict change-control gate the same way, so it probably has the same blind spot for UI answers. I left it out to keep this PR focused; it can be a follow-up that shares the index-to-label helper.

The gate UI answers a checkpoint (confirm_summary / request_plan_approval)
with the generic QuestionEditor, which writes option indices:
{ answers: [{ selectedOptions: [i], freeText? }] }. chosenLabel only read
perQuestion, freeText, decision or a bare string, so it returned '' for
every UI answer. The agent was told "nothing is authorized yet" after the
human clicked "Looks correct", re-raised the checkpoint, and looped; on the
resume leg no receipt was minted and a UI "Request changes" never recorded
the withdrawal.

chosenLabel now resolves a single well-formed index against the gate's own
questions[0].options at every call site (inline answer, resume-leg
rehydration, resume message). Only that exact label can authorize: a
multi-select, out-of-range or non-integer index, or a typed note, names no
label and re-asks. answerFreeText carries the UI note to the agent as
feedback.
JWThewes
JWThewes previously approved these changes Oct 20, 2026

@JWThewes JWThewes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good finding. Thanks for this one. Looks good to me.

Review follow-up: chosenLabel takes the gate row so the answer and the
options it indexes always come from the same row, and rehydration reads the
latest gate's label once. Drop dead guards (the array branch for questions,
which is always a JSON string; the negative-index check; the string branches
in answerFreeText and resolveCheckpointAnswer), fix the misleading comments,
and parametrise the existing tests over both answer shapes instead of copying
them.
@POLAARK

POLAARK commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Sorry I had a review agent run a little cleaning. Should be good right now @JWThewes

@POLAARK
POLAARK requested a review from JWThewes October 20, 2026 17:23
@JWThewes
JWThewes merged commit e9a3d18 into aws-samples:main Oct 20, 2026
3 checks passed
@eipasteur

eipasteur commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

TEST PURPOSES ONLY — DO NOT TAKE INTO ACCOUNT.

Automatically computed PR review signals (deterministic)

Cognitive complexity and branch coverage help focus review on functions and their tests; these signals are advisory.
2 changed JS/TS files analyzed · 1 flagged function · 1 improvement.
Coverage: PR measurements from incomplete tests; no comparable base, so no coverage delta.

Complexity and coverage — 2 functions
File / function Complexity Δ Branch coverage Δ Uncovered branches Depending files
🟠 lambda/agentcore/mcp/process-bridge.js:506 — createProcessBridge / rehydrateAuthorizations 41 → 43 +2 — → 78.0%* — — → 11 2
🟢 lambda/agentcore/commands/run-stage.js:1308 — formatResumeAnswer 26 → 24 -2 — → 68.0%* — — → 16 1

🟢 Improving · 🟠 Needs attention · 🔴 High risk · 🟣 Highest risk

— = unavailable · n/a = not applicable · * = incomplete tests


Isobar map

PR impact map

Fixed repository map: red marks changed files; green marks static name matches or import links across components. These links indicate possible reach, not proven runtime impact or a risk grade.

Isobar by r3al1tym.

Signals
  • A: agentcore/mcp/process-bridge.js:506 — function CC 41→43 (+2); 2 direct production importers; 17 file-level import-link targets.
  • B: agentcore/commands/run-stage.js — 1 direct production importer; 13 file-level import-link targets.
  • Total static reach: 2 unchanged production files; 15 unchanged test files.
  • Revision: fac3645; updated 2026-10-27 17:28 UTC.
Legend
  • ○ modified · ◇ added · × deleted. Blue markers: tests; gray markers: documentation.
  • Solid green: import-linked name matches. Dotted green: broader file-level import fallback. Both are static evidence, not proven execution.

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.

3 participants