feat(apps): add an AgentCore Observatory built-in app - #8463
Conversation
Amazon Bedrock AgentCore spreads one deployment across several console surfaces, so answering "is this runtime healthy, and is online evaluation actually on" means visiting each of them. Nothing in the dashboard reads that estate. Adds a default-disabled built-in app that lists AgentCore resources for one AWS profile name and region. The resource surface is data, not code: catalog.py holds all 27 control-plane types with the verb, response key and identifier fields each one needs, so a type is a row rather than a branch. Only the 17 root-listable types appear in the rail; the rest load from a parent row, because their list verb requires a parent id. Reads are lazy per type. Listing all 27 up front is 27 sequential aws CLI subprocesses, and they are deliberately not parallelised because concurrent invocations race the same SSO token file. A section that cannot be read says so. An authorization failure renders as a denial and a truncated page as partial, never as an account with nothing deployed -- a live run surfaced an SSL failure against `memories` and reported it as a failure rather than as absence, which is the distinction the page exists to keep. Configuration is a profile NAME and a region only. No credential is read, written or cached; the aws CLI resolves it, and the cloud chokepoint already refuses these verbs from an agent session. Adds AgentCore to the do-not-translate glossary. Without it the app title tripped the untranslated-script gate in six locales, which forced a translated product name where every sibling app keeps its own -- the same mechanism that lets `AWS Control` stay English in zh-CN.
d13cd30 to
cdbc271
Compare
First Principles Review (Fable 5, fork) — 🔴 BLOCKPremise-level review of All facts verified. The review is complete. First-Principles-Verdict: BLOCK The repo's own blocking rule closes the built-in set — a new app ships through the KiroCrewApps registry, and this PR ships one as a built-in anyway. What this change shipsIntent: let an operator see whether an AgentCore deployment is healthy and evaluated, from the dashboard — an ADDITION.
Blockers
Subtractions
[FIRST-PRINCIPLES-REVIEWED] cdbc271 |
Design Review (Fable 5, fork) — 🔴 BLOCK (blocking)Design-level review of I have everything I need. The design is internally well-built (catalog-driven, chokepoint reuse, error-vs-empty distinction), but it ships as a new built-in app, which the repo's governance explicitly closes off — and I verified the external-app path supports the same shape (UI page + backend). Design-Verdict: BLOCK A new built-in app is a closed door here: this ships as an external KiroCrewApps registry app, not under Blockers
Watch
[DESIGN-REVIEWED] cdbc271 |
GPT 5.6 Review (fork) — 🔴 changes requested (blocking)Reviewed 2 of 4 blocking finding(s) are security-class and were withheld from adjudication, so the blocking verdict stands. BLOCKING -- src/kiro_crew/apps/builtins/agentcore_observatory/app.json:2 -- New built-in violates the closed app set BLOCKING -- src/kiro_crew/apps/builtins/agentcore_observatory/backend/routes.py:61 -- AWS inventory routes omit owner authorization (origin: validation) BLOCKING -- src/kiro_crew/apps/builtins/agentcore_observatory/backend/routes.py:241 -- Query keys inject AWS global options BLOCKING -- website/src/apps/agentcore-observatory/AgentcoreObservatoryPage.tsx:73 -- Failures bypass ErrorNotice FINDING -- src/kiro_crew/apps/builtins/agentcore_observatory/backend/routes.py:141, src/kiro_crew/apps/builtins/agentcore_observatory/tests/test_manifest.py:99 -- Function-local [BLOCK-MERGE] cdbc271 Adjudication (Opus 4.8) — is blocking on each finding proportionate?Both findings are confirmed against the code and anchored to AUTOSDE rules carrying F1 — New F4 — |
Opus 4.8 Review (fork) — 🔴 changes requested (blocking)Reviewed Two merge-blockers: a new app added under BLOCKING — src/kiro_crew/apps/builtins/agentcore_observatory/app.json:1 (+ src/kiro_crew/apps/builtins/init.py:4) BLOCKING — src/kiro_crew/apps/builtins/agentcore_observatory/backend/routes.py:241 FINDING — src/kiro_crew/apps/builtins/agentcore_observatory/backend/agentcore.py:138 — |
UX Review (Fable 5, fork) — 🟡 CONCERNSUX-level review of All evidence is in. The change is a new opt-in "AgentCore Observatory" app: a grouped resource rail, lazy per-type lists, a connect form, with honest empty/denied/partial states. Two genuine UX risks survive scrutiny: the hand-rolled error surface renders raw machine codes/CLI stderr (diverging from the product's UX-Verdict: CONCERNS Errors reach the user as raw machine codes and CLI stderr, and the App Store showcase screenshot ships with grey redaction bars over its content. Watch
Suggestions
[UX-REVIEWED] cdbc271 |
|
@warren830 Thanks for this. Nothing on What blocks it is the shape, not the idea. Three defects need fixing either way:
Also drop the zero-consumer Posted from the 2026-09-08 open-PR relationship audit (read-only, one auditor per PR); reply here if any of this is wrong. |
Problem / Motivation
Amazon Bedrock AgentCore spreads one deployment across several console surfaces.
Answering the two questions an operator actually has — is this runtime healthy,
and is online evaluation actually switched on — means visiting each of them and
correlating by hand. Nothing in the Kiro Crew dashboard reads that estate, so an
AgentCore deployment is invisible from the surface where the rest of the work
happens.
Why it matters
The failure this addresses is not "no dashboard". It is that the answer people
reach for is a count, and a count cannot tell an empty region from a denied
call. Both render as zero. An operator who reads "0 runtimes" and concludes
nothing is deployed, when the truth is that their profile lacks
bedrock-agentcore:ListAgentRuntimes, has been actively misled.What changed (motivation → approach → change)
Goal: make an AgentCore estate legible from the dashboard without becoming a
second console, and without ever being able to spend money or mutate a resource.
Approach, and what it was chosen over. The obvious shape is a page of summary
cards. I rejected it: a bare
Evaluators 32is exactly the count-versus-denialambiguity above, and it answers nothing an operator can act on. The next option
was a hand-written page per resource type — 27 near-identical components, each a
place for the response-key handling to drift.
What is here instead is catalog-driven:
backend/catalog.pyholds all 27control-plane types as data — list verb, response key, get verb, identifier
field, and parent linkage — and one query function reads that row to build the
argv. Adding a type is a row, not a branch. The response keys in particular
cannot be inferred and are not guessed:
credentialProvidersis shared by threetypes,
itemsby gateways and gateway targets, andagentRuntimesby bothruntimes and runtime versions. Each was read off the live API.
Only 17 of the 27 types are root-listable. The other 10 require a parent
identifier (and
policy-generation-assetsrequires two), so they are not in therail at all; they load from an expanded parent row, which is also the only place
their parent id exists.
Reads are lazy per type, and deliberately not parallel. Listing everything up
front is 27 sequential
awsCLI subprocesses. Parallelising them is worse thanslow: concurrent invocations race the same SSO token file.
A section that cannot be read says so. A per-section failure returns HTTP 200
with
ok:falseso one card degrades instead of the page, and the UI renders anauthorization failure as a denial and a capped list as partial — never as an
account with nothing deployed. This is not theoretical: during live verification
memorieshit an SSLUNEXPECTED_EOF_WHILE_READINGand the page reported afailure rather than absence, which is the distinction the app exists to keep.
Configuration is a profile NAME and a region, nothing else. No credential is
read, written or cached — the
awsCLI resolves it. Agent sessions cannot reachthese verbs at all:
cloud/aws.py's chokepoint already refuses non-allowlistedverbs when a session key is set, and this change does not widen that allowlist.
One shared-file change:
AgentCoreis added to the do-not-translate glossary.Without it the app title tripped the
untranslated-scriptgate in the sixnon-Latin locales, which forced a translated product name where all 19 sibling
apps with proper-noun names keep theirs. It is the same mechanism that lets
AWS Controlstay English inzh-CN: the DNT term is stripped before the gatejudges the value. Verified to add zero new findings to
glossary.test.ts'sbaseline — the alternative of adding the whole phrase
AgentCore Observatorywould have introduced 21.
Tests
Backend, 140 tests over the app (100% on catalog/query/config, 99% on routes):
test_catalog.py— internal consistency of all 27 rows against the verifiedAPI facts: every type has a list verb or is the documented get-only singleton,
parent params and parent fields are positionally paired, ids are unique.
test_agentcore.py— the argv each listable type builds; error-versus-empty,truncation, and the unconfigured and malformed-response branches.
test_config.py— profile/region validation and every documented corruptionmode of the stored config.
test_routes.py— all six routes, the enablement gate on each, path-identifierrefusal, and that
/catalogperforms no AWS call.test_manifest.py— the manifest invariants the reference apps pin(default-disabled, no agents, one UI page, declared assets exist on disk).
Frontend,
website/src/apps/agentcore-observatory/test/labels.test.ts, 9 testspinning the two defects found by driving the real page:
list-agent-runtime-versionsreturns thesame
agentRuntimeNameand the sameagentRuntimeArnfor every version —the API reference documents a
:versionsuffix on that ARN that the servicedoes not send — so keying on either collapsed thirteen rows onto one identity
and expanding one expanded all of them. The test fails if the key stops
including the index.
v13, not13, so it cannot be read as a count.Manual verification
Driven against a real AWS account in an isolated pod, since no unit test proves a
CLI-backed read works end to end:
agent-runtimesreturned five READYruntimes, and expanding one loaded its 13 versions and its DEFAULT endpoint.
evaluatorsandonline-evaluation-configsreturned real data.runtimesin an empty region returnedok=true, items=[]and rendered as"None in this region." rather than as an error.
memoriesfailed with an SSL error and rendered as a failure, not as absence.Screenshots / video
Populated state: the grouped rail, one expanded runtime, its 13 versions each
distinguishable by version badge, and one row expanded to raw JSON.
The grey bars are redactions applied before committing: the account id, runtime
ARNs, runtime ids and the runtime name are covered because this is a real
deployment in a public PR.
agentRuntimeVersionandstatusare left readableon purpose — they are the fields the shot exists to demonstrate.
The same image is committed twice by design:
temp-screenshots/is the PRevidence copy this repo's convention asks for, and
website/public/app-assets/agentcore-observatory/screenshot-main.pngis thepackaged app-store asset the manifest declares and
test_every_discovered_builtin_declares_a_real_screenshotrequires.Related Issues
Fixes #8420
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)