Skip to content

Releases: Boise-State-Development/agentcore-public-stack

Release 1.0.4

Choose a tag to compare

@github-actions github-actions released this 01 Jul 18:59
d4f06dd

Release Date: July 1, 2026
Previous Release: v1.0.3 (June 30, 2026)


⚠️ Upgrading from a beta? 1.0.4 is an in-place upgrade from any 1.0.x with no migration. Moving from a pre-1.0.0 beta is still the destructive backup → teardown → redeploy → restore migration described in the 1.0.0 notes. Brand-new deployments need none of this.


Highlights

v1.0.4 is a one-line-per-role IAM hotfix that restores AgentCore Memory functionality. Both the App API task role and the AgentCore Runtime execution role granted every memory data-plane action except bedrock-agentcore:GetMemory — the action the SDK's get_memory_strategies() call requires to resolve a memory's strategy IDs. Without it, strategy discovery failed silently: the Settings → memories/preferences page came back empty (the App API returned empty lists with a 200), and the agent stopped recalling long-term memories (the runtime kept writing conversation events but ran with retrieval disabled). Granting GetMemory on both roles fixes both symptoms. This is an infrastructure (IAM) change, so it deploys via the platform (CDK) pipeline.

🐛 Bug fixes

  • Memory dashboard showed no memories/preferences. GET /memory calls get_memory_strategies()bedrock-agentcore:GetMemory, which the App API task role didn't allow. The call AccessDenied, strategy discovery returned no IDs, and the endpoint returned empty facts/preferences with a 200 — so the page rendered blank even though records existed. (app-api-iam-grants.ts)
  • Agent didn't recall long-term memories. At session creation the runtime's _discover_strategy_ids() makes the same GetMemory call to build its retrieval namespaces; the runtime execution role also lacked the action, so retrieval was silently disabled ("long-term memory retrieval disabled") while event writes (CreateEvent) continued to succeed. (inference-api-iam-roles.ts)

🏗️ Infrastructure

  • Added bedrock-agentcore:GetMemory to the AgentCoreMemoryAccess policy statement on both roles — the App API Fargate task role (scoped to the memory ARN) and the AgentCore Runtime execution role (scoped to memory/*). No other action names changed. Verified against the AWS Service Authorization Reference (GetMemory is a Read action on the memory resource type).

🚀 Deployment notes

This is an IAM change on PlatformStack, so it ships through the platform (CDK) pipeline, not the API-only backend path. After deploy, no data migration is needed and existing memories become visible immediately. Note the App API caches strategy discovery per process (functools.lru_cache) — a normal deploy rolls the ECS tasks and AgentCore Runtime, so the cache starts fresh; no manual restart required.


Release 1.0.3

Choose a tag to compare

@github-actions github-actions released this 30 Jun 22:57
dd7aaf4

Release Date: June 30, 2026
Previous Release: v1.0.2 (June 29, 2026)


⚠️ Upgrading from a beta? 1.0.3 is an in-place upgrade from 1.0.0/1.0.1/1.0.2 with no migration. Moving from any pre-1.0.0 beta is still the destructive backup → teardown → redeploy → restore migration described in the 1.0.0 notes. Brand-new deployments need none of this.


Highlights

v1.0.3 is a maintenance patch — no application code or user-facing behavior changes. It's almost entirely CI/CD pipeline work: platform and backend deploys are now serialized through a shared concurrency group so they can't race each other onto the same ECS service / AgentCore Runtime / Lambda, push-triggered (path-scoped) auto-deploys are back on for the platform/backend/frontend workflows, and the duplicated test gates are consolidated into one reusable workflow. Rounding it out is a small dependency + CodeQL sweep (a joserfc CVE patch and a couple of unused-import removals). Operators on 1.0.x upgrade in place with no migration.

🔒 Security & 📦 Dependencies

  • joserfc 1.6.3 → 1.7.2 (backend) and 1.6.5 → 1.7.2 (backup-data tooling), remediating Dependabot GHSA-wphv-vfrh-23q5 / CVE-2026-48990. (#526)
  • Removed unused imports flagged by CodeQL — Optional in agents/main_agent/agent_types.py, ssm in app-api/app-api-environment.ts. (#526)

🔧 CI/CD

  • Serialized deploys. platform.yml and backend.yml now share one repo-global concurrency group (deploy-<ref>), so a CloudFormation deploy and the API-driven backend code deploys queue instead of running at once and stomping the same ECS service / AgentCore Runtime / Lambda. Frontend stays independent; cancel-in-progress stays false. (#525)
  • Auto-deploy restored. Push-triggered, path-scoped deploys are re-enabled for platform/backend/frontend (develop → development, main → production) after being manual-dispatch-only since v1.0.0. Each trigger is scoped to its own surface so unrelated changes don't redeploy. (#524)
  • Reusable test gates. Duplicated test jobs are extracted into a shared tests.yml consumed by ci, platform, backend, frontend-deploy, and nightly-deploy-pipeline; skipped single-suite callers now render correct job labels instead of raw ${{ }} expressions. (#524, #526)
  • Pipeline cleanup. Pruned dead nightly tracks (AI coverage analysis, merge-validation) and orphaned scripts; docs-deploy now publishes from main (was develop), and docs-deploy/release are fork-gated so forks syncing main don't auto-publish or auto-release. (#524)

🚀 Deployment notes

In-place patch on the single-stack PlatformStack — no new infrastructure, env vars, or migration. The only operator-visible change is to CI/CD behavior: pushes to develop/main once again auto-deploy (path-scoped), and platform vs. backend deploys now queue rather than run concurrently.


Release 1.0.2

Choose a tag to compare

@github-actions github-actions released this 30 Jun 18:01
fc70839

Release Date: June 29, 2026
Previous Release: v1.0.1 (June 26, 2026)


⚠️ Coming from a pre-1.0.0 (beta) deployment? Read the 1.0.0 release notes first. There is no special upgrade path for 1.0.2 itself — if you're already on 1.0.0 or 1.0.1 you upgrade in place with no migration. But 1.0.0 was the single-stack consolidation, and upgrading from any beta to 1.0.0 (and therefore to 1.0.2) is a destructive backup → teardown → redeploy → restore migration, not an in-place cdk deploy. If you haven't already worked through it, do that before deploying 1.0.2: see Upgrading an existing deployment (1.0.0 notes) below, or the published guide at https://boise-state-development.github.io/agentcore-public-stack/deployment/upgrade/. Brand-new deployments need none of this.


Highlights

v1.0.2 is a small, security-focused patch on the 1.0.0 single-stack architecture with one notable behavior change. The headline is that assistants can use tools again: 1.0.0 had locked assistant chats to a knowledge-base-only, tool-free mode (#382), and this release reverts that so an assistant can once more leverage the user's selected MCP and built-in tools. Alongside it, this release lands a CodeQL security-hardening sweep (two HIGH findings around URL/host validation, a log-injection pass across 24 call sites, and a hardened CI checkout), remediates 6 Dependabot alerts (Astro XSS/SSRF, esbuild dev-server file read, pydantic-settings path traversal), and fixes the nightly coverage pipeline that broke when the single-stack refactor moved its scripts. There is no migration — operators on 1.0.0 or 1.0.1 upgrade in place.


Assistants can use tools again

1.0.0 introduced a deliberate restriction: assistant ("RAG") chats ran knowledge-base-grounded with zero external tools — the inference API forced enabled_tools=[] on assistant turns and the system prompt told the model it had no external tools (#382). That made assistants safe and predictable but also meant they couldn't search the web, hit an MCP server, or run code even when the user had those tools enabled. v1.0.2 reverts the restriction so assistants behave like a normal chat with the assistant's knowledge and instructions layered on top.

What stays the same: knowledge-base context is still pre-stuffed into the user message, and the assistant's custom instructions still apply. What changes: the user's tool-picker selection now flows through to the agent on assistant turns, and assistant chats once again emit tool-use and MCP-App events.

Backend

  • inference_api/chat/routes.py — dropped the enabled_tools=[] override in the rag_assistant_id branch so the client's tool selection reaches the agent, and removed the "Knowledge Base Grounding / no external tools" directive from both the with-instructions and no-instructions system-prompt paths (restoring the pre-#382 prompt composition).

Frontend

  • chat-request.service.ts — no longer force-sends enabled_tools=[] on assistant turns; the user's tool-picker selection rides along (skills mode stays gated on non-assistant turns).
  • preview-chat.service.ts — the editor preview now forwards the owner's enabled tools instead of [], and builds the streaming assistant message as ordered content blocks (text interleaved with tool use) wired to onToolUse/onToolResult, so the shared message-list renders tool cards in the preview exactly like a consumer chat.

Test coverage

Specs updated to assert tools are forwarded on both assistant and preview turns (chat-request.service.spec.ts, preview-chat.service.spec.ts).

🐛 Bug fixes

  • Nightly coverage pipeline was failing. The single-stack refactor removed scripts/stack-app-api/ and scripts/stack-frontend/, but nightly.yml's test-backend, test-frontend, and install-frontend jobs still called them, so they died with "No such file or directory." The three scripts were ported to the sanctioned post-refactor layout — scripts/backend/test.sh, scripts/frontend/install.sh, and scripts/frontend/test.sh — with behavior preserved 1:1 (uv install/sync + pytest --cov; npm ci for frontend and infra; ng test --no-watch --coverage). (#518)

🔒 Security

This release closes a CodeQL sweep (#521) and 6 Dependabot alerts (#520), each with regression tests where applicable.

CodeQL code findings (#521):

  • HIGH py/incomplete-url-substring-sanitizationexternal_mcp_client previously substring-checked the whole URL for an AWS marker before deciding to SigV4-sign. A crafted URL with the marker in a path, query, or userinfo segment could trick it into attaching IAM credentials to a non-AWS host. It now parses the host with urlparse and matches an anchored suffix. Covered by TestAwsUrlHostSanitization (adversarial URLs).
  • HIGH js/regex/missing-regexp-anchoradmin-tool.model now parses the host via new URL and anchors the AWS-endpoint regexes ($) so a spoofed host can't satisfy the match. Covered by admin-tool.model.spec.ts (13 cases including spoofed hosts).
  • MEDIUM py/log-injection (24 sites across 16 files) — a new apis.shared.security.scrub_log() helper neutralizes CR/LF and control characters; every flagged user-controlled log value is now wrapped. Covered by test_log_sanitize.py.
  • MEDIUM actions/untrusted-checkout — all inputs.ref checkouts in nightly-deploy-pipeline.yml now set persist-credentials: false.
  • WARNING py/regex/duplicate-in-character-class — removed a stray [ from a re.VERBOSE comment that the regex parser misread as a character class.

Dependency CVE remediation (#520):

Component Package From To Fix
docs-site astro 6.3.1 6.4.8 Reflected XSS via slot name, host-header SSRF in prerendered error page, spread-attribute XSS
docs-site esbuild 0.28.1 (override) Dev-server arbitrary file read (GHSA-g7r4-m6w7-qqqr)
frontend esbuild 0.28.1 (override) Dev-server arbitrary file read (transitive via @angular/build 21.2.16)
backend pydantic-settings 2.13.1 2.14.2 NestedSecretsSettingsSource symlink traversal / local file read (GHSA-4xgf-cpjx-pc3j)

🚀 Deployment notes

v1.0.2 is a patch on the single-stack PlatformStack architecture. Operators on 1.0.0 or 1.0.1 upgrade in place — no migration, no new infrastructure, no new env vars.

  • Behavior change to be aware of: after deploying, assistant chats will once again use whatever tools the user has enabled (web search, MCP servers, code interpreter, etc.) rather than running knowledge-base-only. If your deployment relied on assistants being tool-free, note that this 1.0.0 restriction has been intentionally reverted.
  • The security and dependency fixes require no operator action beyond deploying the new images/SPA build.

Release 1.0.1

Choose a tag to compare

@github-actions github-actions released this 26 Jun 19:34
9b77d9e

Release Date: June 26, 2026
Previous Release: v1.0.0 (June 24, 2026)


⚠️ Coming from a pre-1.0.0 (beta) deployment? Read the 1.0.0 release notes first. 1.0.1 lands just two days after 1.0.0, so most operators haven't deployed 1.0.0 yet. There is no special upgrade path for 1.0.1 itself — if you're already on 1.0.0 you upgrade in place with no migration. But 1.0.0 was the single-stack consolidation, and upgrading from any beta to 1.0.0 (and therefore to 1.0.1) is a destructive backup → teardown → redeploy → restore migration, not an in-place cdk deploy. If you haven't already worked through it, do that before deploying 1.0.1: see Upgrading an existing deployment (1.0.0 notes) below, or the published guide at https://boise-state-development.github.io/agentcore-public-stack/deployment/upgrade/. Brand-new deployments need none of this.


Highlights

v1.0.1 is the first patch on top of the 1.0.0 general-availability release, and it ships two operator- and user-facing additions. Save conversations to connected apps ("Save to…") extends the existing connector/adapter pattern in the write direction: a user can push a full conversation transcript out to an app they've connected — Google Drive is the reference destination — as a native Google Doc, Markdown, or plain-text file, reusing the same OAuth consent, RBAC visibility, and AgentCore Identity token flow as document import. External (cross-account) Route53 hosted zones lets deployments whose DNS zone lives in a different AWS account (or is managed out-of-band) stand up the full platform without the deploy failing on an in-account hosted-zone lookup. No action is required for existing 1.0.0 deployments; both features are additive.


Save conversations to connected apps ("Save to…")

The platform already lets a user connect an app and pull documents into an assistant's knowledge base. v1.0.1 adds the opposite direction: save a conversation transcript out to a connected app. The architectural move is to split the existing connector pattern into a direction-agnostic auth layer (OAuthProvider + AgentCore Identity + consent UX, reused as-is) and a direction-specific capability layer — a new ExportTargetAdapter registry mirroring the read-side FileSourceAdapter registry. Google Drive is the first export target; adding OneDrive / SharePoint / Dropbox later is "write one adapter class, register it, and have an admin map a connector." (#507, #508, #509, #510, #511)

Backend

  • New apis.app_api.export_targets package: an ExportTargetAdapter contract (adapter.py), a code-shipped registry, the reference adapters/google_drive.py (creates a Drive file via the user's own token), a render.py transcript renderer (native Google Doc / Markdown / plain text), models.py (ExportFormat, ExportInclude, ExportTargetError), and service.py (connector resolution, RBAC visibility gate, AgentCore Identity token mint with consent/503 handling).
  • User-facing routes (export_targets/routes.py, on app-api — the inference-API boundary only proxies /invocations + /ping): GET /export-targets returns the catalog the "Save to…" dialog reads (per-connector connected state, supportedFormats, and a browsable flag for the folder picker), and POST /sessions/{id}/export renders the full transcript (paged, with a runaway guard) and creates the document via the resolved adapter. A 409 signals the user must complete OAuth consent; a 503 signals workload/callback misconfiguration.
  • Admin mapping: new GET /admin/export-target-adapters plus an OAuthProvider.export_target_adapter_id field — a connector becomes an export target only once an admin maps it to a shipped adapter. Export receipts are persisted to session metadata (ExportReceipt on sessions/models.py, written best-effort by add_export_receipt) so the SPA can reflect "saved" state.

Frontend

  • New ExportDialogComponent (the "Save to…" dialog: connector picker, format picker driven by supportedFormats, optional destination-folder picker reusing the file-source browse dialog) and an ExportService (session/services/export/). A "Save to…" action is added to the conversation list (session-list). Admin connector form gains an export-target-adapter mapping dropdown.

Test coverage

1,400+ lines of new tests: test_export_routes.py, test_export_target_service.py, test_export_google_drive.py, test_export_render.py, and test_export_target_adapters_admin.py on the backend; export-dialog.component.spec.ts and export.service.spec.ts on the frontend.

External (cross-account) Route53 hosted zones

Deployments where the Route53 hosted zone for domainName lives in a different AWS account — or is otherwise managed out-of-band — previously failed: the stack's in-account HostedZone.fromLookup + ALIAS/A record creation cannot reach a zone it doesn't own. v1.0.1 makes DNS record management optional so these deployments succeed, with the platform emitting the records an operator needs to create by hand. (#512)

Infrastructure

  • New manageDnsRecords config flag (env CDK_MANAGE_DNS_RECORDS, context manageDnsRecords; defaults to true, so existing single-account deployments are unaffected). Loaded in infrastructure/lib/config.ts and threaded into the four custom-domain origins.
  • When manageDnsRecords=false, the SPA, ALB, artifacts, and mcp-sandbox constructs still attach the custom domain + ACM certificate to each origin but skip the in-account HostedZone.fromLookup and ALIAS/A record creation. Instead each origin emits CfnOutputs with the record name and alias target (e.g. AlbDnsRecordName/AlbDnsAliasTarget, FrontendDnsRecordName, ArtifactsDnsRecordName/ArtifactsDnsAliasTarget, McpSandboxDnsRecordName/McpSandboxDnsAliasTarget) so an operator can create the records manually in the owning account.

CI/CD

  • CDK_MANAGE_DNS_RECORDS plumbed end-to-end: exported and passed as a --context flag in scripts/common/load-env.sh, and added to the job-level env: of the platform.yml, teardown.yml, and nightly-deploy-pipeline.yml workflows. The nightly smoke test reads it as well.

Docs

  • Deployment docs updated for the cross-account workflow — docs-site (environments, platform-cdk, troubleshooting) and .github/docs/deploy/ (GitHub config, troubleshooting) plus .github/ACTIONS-REFERENCE.md.

🚀 Deployment notes

v1.0.1 is a patch release on the single-stack PlatformStack architecture introduced in 1.0.0. Operators already on 1.0.0 upgrade in place — there is no migration. Both features are additive and off by default until configured.

  • Save to… (export targets): opt-in. An admin maps an existing connector (e.g. the Google Drive connector) to the google-drive export-target adapter from the admin connector form; until a connector is mapped, the "Save to…" dialog shows no destinations. No new infrastructure or env vars.
  • Cross-account DNS: if your hosted zone is in the same AWS account as the deployment (the default), no action is required — manageDnsRecords defaults to true and behavior is unchanged. If your zone lives in a different account (or you manage DNS out-of-band), set CDK_MANAGE_DNS_RECORDS=false (GitHub Actions Variable), then after the deploy read the *DnsRecordName / *DnsAliasTarget CloudFormation outputs and create the matching ALIAS/A records in the owning account for the SPA, ALB, artifacts, and mcp-sandbox origins.

Release 1.0.0

Choose a tag to compare

@github-actions github-actions released this 24 Jun 18:14
815de11

Release Date: June 24, 2026
Previous Release: v1.0.0-beta.27 (May 20, 2026)


Highlights

This is 1.0.0 — the first general-availability release. After 27 betas, the platform is stable, and the headline of this release is as much about the foundation as the features built on it: the entire CDK app collapses from nine CloudFormation stacks into a single PlatformStack with a platform-as-bootstrap code-deploy model, so day-to-day code changes ship in ~2 minutes via AWS APIs and cdk deploy runs only when infrastructure actually changes.

On top of that foundation, 1.0.0 lands a large slate of product work: Conversation Modes (admin-curated system prompts users opt into), file-source connectors and website crawling that turn external systems and the open web into assistant knowledge bases, self-service AgentCore Gateway MCP targets, a curated model catalog with a new Amazon Bedrock Mantle provider, per-turn context attribution, and a public Starlight documentation site. It also delivers a complete backup/restore disaster-recovery toolchain, a coordinated security-hardening sweep, and remediation of all 22 HIGH Dependabot findings.

Action required for operators with an existing deployment. Because 1.0.0 consolidates the old nine-stack architecture into a single PlatformStack, upgrading any prior (beta) deployment is a destructive backup → teardown → redeploy → restore migration — not an in-place cdk deploy. We've written step-by-step instructions to make it as painless as possible. Do not deploy over an existing environment without reading the Upgrading an existing deployment section below first. Brand-new deployments need no special steps.


Upgrading an existing deployment

Read this before you deploy 1.0.0 over any existing environment.

1.0.0 replaces the previous nine-stack CloudFormation layout with a single PlatformStack. There is no in-place upgrade path from a beta deployment — the old stacks must be torn down and replaced. Your data is preserved through a backup/restore cycle, but the steps are destructive and must run in order. We've written and tested detailed, click-by-click instructions so you can work through it confidently.

Start here — the full step-by-step guides:

The migration at a glance (≈45–75 min total — see the guide for the exact inputs for each workflow):

  1. Back up — run the Backup Data (Pre-Migration) workflow. This is the most critical step; confirm summary.failed is zero and note the {prefix}-backup-{timestamp} bucket name. Do not proceed on a failed backup.
  2. Tear down — run the Teardown All Infrastructure workflow (confirm: DESTROY) to delete the old stacks. If your environment retained stateful resources (CDK_RETAIN_DATA_ON_DELETE=true), clear them — at minimum delete the legacy /{prefix}/{app-api,inference-api}/image-tag SSM parameters, which otherwise fail the first new deploy.
  3. Redeploy — run Platform StackBackend DeployFrontend DeploySeed Bootstrap Data.
  4. Restore — run the Restore Data workflow against your backup bucket with dry_run: true first, then dry_run: false.
  5. Verify — confirm login, chat history, file uploads, the admin dashboard, and RAG assistants.

⚠️ Two things to know going in: the backup bucket is immutable and survives every teardown (it's your safety net and rollback source), and Cognito passwords do not transfer — native-password users must use "Forgot Password" on first login, while federated (OIDC/SAML) users are unaffected.

Brand-new deployments skip all of this — see Deployment notes.


Single-stack platform-as-bootstrap architecture

The biggest structural change in the project's history: the CDK app that used to be nine CloudFormation stacks is now one PlatformStack.

Why this overhaul. The multi-stack layout treated the platform like a fleet of independently deployable microservices — but the application is, by definition, a monolith: one cohesive product whose pieces are released together, version-locked, and only ever deployed as a unit. Splitting it across nine stacks bought none of the benefits of microservices and all of their operational cost. Cross-stack Fn::ImportValue references created brittle deploy-ordering requirements; a change in one stack routinely forced careful, manual sequencing of the others; and the seams between stacks were a constant source of deployment issues and gotchas — exported-value locks that blocked updates, drift between stacks that had to be reconciled by hand, and first-deploy chicken-and-egg problems. Consolidating into a single PlatformStack removes that entire class of failure: there are no cross-stack references to order, no inter-stack drift to reconcile, and one cdk deploy either succeeds or rolls back as a whole. Treating the monolith as a monolith from a DevOps standpoint is simpler to reason about, faster to deploy, and dramatically less error-prone.

Infrastructure

  • infrastructure/lib/platform-stack.ts composes ~39 single-responsibility constructs under lib/constructs/ (network, identity, data, rag, artifacts, mcp-sandbox, agentcore, inference-api, app-api, fine-tuning, spa, zones). It is built in two phases — the constructor (data + edge + Cognito + AgentCore Memory/Code-Interpreter/Browser/Gateway) and wireCompute() (Inference Runtime + SageMaker + App API Fargate) — which eliminates every cross-stack Fn::ImportValue and all deploy-ordering between stacks. npx cdk list now returns exactly ${prefix}-PlatformStack.
  • Platform-as-bootstrap. CDK ships small, byte-stable placeholder assets from infrastructure/bootstrap-assets/{app-api,inference-api,rag-ingestion,artifact-render}/ (stdlib HTTP servers / 503 handlers). The real code ships out-of-band via AWS control-plane APIs: aws ecs register-task-definition + update-service (app-api Fargate), aws bedrock-agentcore-control update-agent-runtime (inference-api Runtime), and aws lambda update-function-code (rag-ingestion image Lambda + artifact-render zip Lambda). Because CFN tracks each Code/image property from its own constant model, subsequent Platform deploys leave the out-of-band-deployed real code untouched.
  • All per-component CDK feature flags were removed (CDK_FRONTEND_ENABLED, CDK_APP_API_ENABLED, CDK_INFERENCE_API_ENABLED, CDK_GATEWAY_ENABLED, CDK_FILE_UPLOAD_ENABLED, CDK_ASSISTANTS_ENABLED, CDK_RAG_ENABLED, CDK_FINE_TUNING_ENABLED, CDK_ARTIFACTS_ENABLED, CDK_MCP_SANDBOX_ENABLED). The platform now deploys everything, always.

CI/CD

  • New platform.yml (CDK), backend.yml (build → API-driven code deploy), and frontend-deploy.yml workflows replace the legacy per-stack workflows, which were deleted along with their scripts and tests. A content-hash Docker build pipeline under scripts/build/ skips a rebuild when the computed hash already exists as an ECR tag. Day-to-day backend code deploys in ~2 minutes without touching CloudFormation; cdk deploy runs only on real infrastructure changes.

Test coverage

Carried forward from the refactor's stabilization: 7 policy-level assertions in infrastructure/test/security-policy.test.ts (Action:* + Resource:* prohibition, BFF-cookie-key Decrypt-only, every bucket SSE + public-access-block + enforceSSL, every DDB table SSE), 5 in compute-image-resolution.test.ts (SSM-resolved image shape), 2 in ssm-safety.test.ts (same-stack valueForStringParameter deadlock at synth), and a tests/supply_chain/test_env_var_contract.py reflection test that fails on any orphan CDK env var.


Conversation Modes

Shipped enabled. Admins curate a catalog of custom system prompts ("Guided Learning", "Concise", and so on) that users opt into per conversation.

Backend

  • New apis.shared.system_prompts module (models / repository / service) with optimistic-concurrency updates so a concurrent delete+edit can't resurrect a deleted prompt. Admin CRUD /admin/system-prompts (full prompt_text) and a user read /system-prompts (name + description only — prompt text stays server-side). Inference resolves the active prompt via chat/system_prompt_resolver.py and appends it to the base prompt; gating skips resume, continuation, preview, and assistant-attached turns. Selection precedence is request-body-first (so the first turn of a new session works without a metadata round-trip), with session preferences as fallback.

Infrastructure

  • New SystemPromptsTable DynamoDB construct (env DYNAMODB_SYSTEM_PROMPTS_TABLE_NAME; app-api CRUD, inference-api GetItem only); name + ARN published to SSM.

Frontend

  • Lazy SystemPromptsService, admin list/form pages, and a per-conversation chip + radio group in the settings panel.

Knowledge bases: file-source connectors and website crawling

Two complementary ways to fill an assistant's knowledge base from outside a manual upload.

File-source connectors

A four-PR arc turns OAuth connectors into RAG document sources. A provider-agnostic backend (FileSourceAdapter ABC + shipped-code-only registry, normalized FileEntry/BrowseResult/SourceRoot/DownloadedFile contract) ships with a GoogleDriveAdapter (Drive v3 browse/search/download including native-doc export). The Document model gains provenance (sourceConnectorId/sourceAdapterKey/sourceFileId/sourceEtag/importedByUserId). Admins opt a connector in by mapping it ...

Read more

Release 1.0.0-beta.27

Release 1.0.0-beta.27 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 20 May 22:09
9b7cad7

Release Date: May 20, 2026
Previous Release: v1.0.0-beta.26 (May 13, 2026)


Highlights

The largest release since the BFF cutover. Beta.27 lands two new user-visible surfaces, both built on top of brand-new CDK stacks, plus a major admin redesign and a handful of inference-API correctness fixes.

  • Artifacts — the agent can now produce versioned, iframe-isolated HTML, Markdown, and code artifacts that render in a docked side panel beside the chat. Backed by a new ArtifactsStack (S3 + DynamoDB + render Lambda + CloudFront on artifacts.{domain}) and short-lived JWT render tokens minted by app-api.
  • MCP Apps host renderer — third-party MCP servers can ship UI alongside their tools. The agent advertises a UI extension on initialize, fetches ui_resource payloads via resources/read, and the SPA frames them in a sandboxed <mcp-app-frame> over a strict CSP, with an app-initiated tools/call proxy and explicit user consent. Backed by a new McpSandboxStack (CloudFront origin on mcp-sandbox.{domain} with dynamic per-resource CSP via a CloudFront Function). Default-on this release.
  • Admin shell redesign — the 15-card admin grid is replaced with a persistent grouped sidebar, and dense list redesigns for models and tools turn cards into compact expandable rows. Quotas and Fine-Tuning collapse from seven sibling routes into two tabbed pages.
  • Recoverable max_tokens truncation — what used to be a leaky, infinite-looping MaxTokensReachedException is now an inline "Response length limit reached" notice with a Continue button that resumes the truncated turn instead of resending the prompt. Survives a page refresh.
  • Model-aware adaptive thinking — Opus 4.7's 400 on thinking.type=enabled is fixed: Opus 4.6/4.7, Sonnet 4.6, and Mythos now emit {type: adaptive, display: summarized} and depth is governed by a new admin- and user-configurable effort knob. Older models keep the legacy enabled shape.
  • /ping reaper fix — fixes silent mid-stream microVM reaping by emitting the integer time_of_last_update field AgentCore's idle reaper requires. Workaround for bedrock-agentcore-sdk-python#471 until async-task busy tracking lands.
  • Pre-migration backup toolscripts/backup-data/ produces a complete, restore-friendly snapshot of all DynamoDB tables, user-content S3 buckets, and Cognito (config + users + groups + IdPs + plaintext app-client secrets) for a given CDK_PROJECT_PREFIX. Workflow-dispatch wired.
  • Dependency upgradesbedrock-agentcore 1.6.4 → 1.9.1 (with coupled boto3 1.42.96 → 1.43.9) and strands-agents 1.39.0 → 1.40.0.

This release adds two new CDK stacks (ArtifactsStack, McpSandboxStack) and one new DynamoDB table (user-menu-links). Both new stacks are gated by config flags. Deploy order matters — see "Deployment notes" below.


Artifacts

The agent can now author versioned standalone documents — HTML pages, charts, Markdown reports — that render in a sandboxed iframe alongside the chat. Artifacts solve two problems the existing create_visualization and Code Interpreter outputs couldn't: persistence (the user can re-open and download), and isolation (HTML/JS runs in a cross-origin sandbox so it can't read cookies or the SPA DOM).

Architecture

A new leaf stack, ArtifactsStack, owns the rendering pipeline:

  • DynamoDB user-artifacts table — version log + HEAD pointer per artifact. PK USER#{user_id}, SK ARTIFACT#{aid}#V#{version:05d} for versions and ARTIFACT#{aid}#HEAD for the latest pointer. GSI1 indexes by SESSION#{session_id} so the SPA can list artifacts produced in the current chat.
  • S3 artifacts-content bucket — private, no CORS. Layout {user_id}/{aid}/v{n}/index.html. Versions are immutable: there's no s3:DeleteObject grant on the inference-api role, so an update_artifact writes a new version and re-points HEAD instead of mutating.
  • Render Lambda — validates a render-token JWT scoped to one (artifact_id, version), fetches the blob from S3, and returns it with a strict per-origin CSP that allows inline <style> / <script> plus scripts from cdn.tailwindcss.com, esm.sh, cdn.jsdelivr.net, and unpkg.com. connect-src 'none' — artifacts cannot make outbound network calls.
  • CloudFront distribution on artifacts.{domain} — terminates TLS, attaches the security-headers policy. The artifact origin is intentionally a different cookie-jar host from the SPA so a script in an artifact can't read __Host-bff_session.
  • HMAC signing key — the render-token signing secret lives in Secrets Manager in InfrastructureStack (not ArtifactsStack), so app-api and the render Lambda can both read it without ArtifactsStack becoming a stack-dependency root. App-api mints short-lived JWTs that the SPA embeds as the iframe src.

Agent tools

Two new built-in tools, registered as default public tools so the feature is usable on first deploy without an admin opting them in per role:

  • create_artifact(title, content, content_type="text/html; charset=utf-8") — writes v1. HTML mode requires a complete standalone document (<!doctype html> + full <html>); Markdown mode (content_type="text/markdown") takes raw GFM and the writer wraps it in a self-contained HTML render harness server-side.
  • update_artifact(artifact_id, content, ...) — writes a new version and re-points HEAD; the render-token mints against the latest version when the panel updates.

The system prompt documents the dual authoring contract and the CSP allowlist (Chart.js auto-registering build, import Chart from "https://esm.sh/chart.js@4/auto" etc.) so the model produces output that actually renders.

SSE + SPA

A new artifact SSE event streams from the inference-api each time the agent creates or updates an artifact. The frontend has:

  • ArtifactStateService + ArtifactHttpService + ArtifactDownloadService — signal-backed state, render-token fetch, blob download.
  • A docked, resizable artifact panel beside the chat that auto-opens on first creation, shows a skeleton while loading, and on update jumps to the latest version. Per-version history cards in the panel let the user step backwards through revisions.
  • An inline artifact card anchored to the producing message, with a preview/code toggle (syntax-highlighted source view) and a download button on both the card and the panel.
  • Full-width inline cards, scoped isolation: isolate z-indexing so a focused artifact card doesn't escape its message row, and live tool-output streaming into the tool rail while the artifact is being authored.

Configuration

Artifacts is opt-in at deploy time via CDK_ARTIFACTS_ENABLED=true. When enabled, CDK_HOSTED_ZONE_DOMAIN and CDK_ARTIFACTS_CERTIFICATE_ARN become required. Validation runs on every stack synth, so all five consumer GitHub workflows now thread these env vars through the OIDC composite action — a missing var on a non-ArtifactsStack workflow would otherwise fail synth.


MCP Apps Host-Renderer

A scoping document landed early in the cycle (docs/kaizen/scoping/mcp-apps-host-renderer.md) and the implementation followed a deliberate seven-PR sequence (#339 PR #0 → #349 PR #7). The result: third-party MCP servers can ship a small interactive UI alongside their tools, and that UI renders in a sandboxed iframe with the same isolation guarantees as artifacts.

Architecture

A new leaf stack, McpSandboxStack, mirrors the artifacts pattern:

  • CloudFront distribution on mcp-sandbox.{domain} — fronts an S3 origin that serves a tiny "basic-host" mount page. App URLs land at mcp-sandbox.{domain}/<resource-encoded-path>, the mount page reads the encoded resource URL from the path and frames the actual MCP App content in an inner blob iframe with allow-same-origin matching the basic-host reference.
  • Dynamic per-resource CSP — a CloudFront Function on the viewer-response decodes a ?csp= query param (URL-encoded frame-ancestors source list scoped to that one resource) and emits a per-request Content-Security-Policy header. The function source is loaded from assets/mcp-sandbox/csp-function.js and the frame-ancestors allowlist is JSON-injected at synth — the substitution asserts the placeholder marker is present exactly once so a future refactor that loses it fails loudly at synth, not at edge runtime.
  • Outer frame-ancestors allowlist — configurable via mcpSandbox.extraFrameAncestors so a deploy can permit framing from custom origins (preview environments, alternate SPA hosts) without rebuilding the function asset.

MCP protocol surface

The agent now advertises an experimental.ui extension during MCP initialize so a server knows whether the host can render UI. Tools whose only output is a ui_resource are filtered out for non-capable clients (the existing API-key path, scripted callers).

When a tool result references a UI resource, the agent fetches it via the standard MCP resources/read flow and emits a ui_resource SSE event with uri, permissions, and a sandboxOrigin that points at the deployed mcp-sandbox host (sourced from SSM, so the value is correct per environment). Two app-initiated message types complete the protocol:

  • ui/message — the App pushes structured data into the chat input as a tool-input draft (acts like a smart form).
  • ui/update-model-context — the App contributes context the agent should consider on the next turn.
  • tools/call proxy — the App can invoke other tools on the same MCP server. The frontend brokers these through app-api over an event broker rather than letting an iframe call the Bedrock runtime directly.

Frontend

  • <mcp-app-frame> Angular custom element + a postMessage bridge that enforces the allowed message types and rejects unknown origins.
  • A consent prompt rendered as an inline message component — the user explicitly approves an App before it gets framed. Consent d...
Read more

Release 1.0.0-beta.26

Release 1.0.0-beta.26 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 14 May 03:49
289e601

Release Date: May 13, 2026
Previous Release: v1.0.0-beta.25 (May 11, 2026)


Highlights

A small, focused release that lands two operator-facing fixes and one user-facing feature on top of the beta.25 production hardening. The big ones: multi-sheet XLSX support in the spreadsheet analysis tool with defensive caps so a pathological workbook can't blow up latency or context, and an async refactor of the spreadsheet file-lookup path that closes a regression where concurrent chat load could block the event loop. Also shipping a user default model preference applied at chat time, a green nightly E2E pipeline after a multi-attempt fix, and upstream contribution governance — PRs are now restricted to approved collaborators (GitHub "Collaborators only") and Dependabot version-update PRs are disabled in favor of manual weekly upgrades.

This release has no schema or infrastructure changes. Deploy in any order.


Multi-Sheet XLSX Support in Spreadsheet Analysis

The spreadsheet analysis tool from beta.25 only handled the first sheet of an XLSX file, which silently misled the agent on multi-tabbed workbooks (financial models, fine-tuning datasets, anything from a real BI export). Beta.26 expands the tool to convert every sheet into its own predictable CSV, with sane defaults that protect the latency budget and the model's context window from pathological inputs.

Backend

  • backend/src/agents/builtin_tools/spreadsheet_analysis/analyze_tool.py — adds two environment-configurable caps (MAX_SHEETS_TO_CONVERT, MAX_ROWS_PER_SHEET) so a workbook with thousands of small sheets can't blow out the Code Interpreter sandbox. New helpers:
    • _sanitize_sheet_name() produces filesystem-safe deterministic CSV filenames (stem.sheetname.csv) so the model's downstream code paths are predictable
    • _parse_sheet_inventory() extracts structured sheet metadata from the bootstrap stdout without eval/literal_eval on untrusted output
    • _safe_int() parses bootstrap integers defensively
    • _format_sheet_note() generates a markdown footer documenting which sheets converted, which were truncated, and the per-sheet CSV paths — surfacing caps to the model with actionable warnings rather than silently wrong results
  • Tool docstring documents the dual contract: single-sheet workbooks keep the legacy stem.csv fast path; multi-sheet workbooks get per-sheet CSVs plus a primary alias for the first sheet
  • backend/src/agents/main_agent/core/system_prompt_builder.py — system-prompt guidance updated so the model handles per-sheet filenames correctly on retries

Test Coverage

2,800+ lines of new tests across backend/tests/agents/builtin_tools/spreadsheet_analysis/:

  • test_analyze_tool_integration.py (779 lines) — multi-sheet XLSX and CSV workflows end-to-end
  • test_sheet_inventory.py (307 lines) — parser robustness against malformed bootstrap output
  • test_build_preview_code.py (127 lines) — filename escaping for quotes and special characters via repr() indirection (closes a code-generation injection edge case)
  • test_clean_stderr.py (202 lines) — MAX_ERROR_CHARS budget is now respected strictly, accounting for ellipsis length
  • test_helpers.py, test_find_file.py, test_list_spreadsheets.py, test_strip_first_row.py — coverage for the smaller utilities

A small robustness fix landed alongside the tests: code generation now stashes the filename as a _FNAME variable inside the generated snippet to prevent f-string interpolation conflicts when filenames contain quotes or braces.


Async Spreadsheet File Lookups

The analyze_spreadsheet and list_spreadsheets tools shipped in beta.25 ran synchronous DynamoDB queries on the event loop (_find_file, _get_kb_files, _get_session_files), and the inference-api _build_tabular_inventory chat-route helper used a nested asyncio.run + thread pool executor pattern that could block under concurrent chat load. This release converts the entire path to native async: tool entry points are async def, every DynamoDB query is offloaded via asyncio.to_thread, and the inference-api helper awaits directly. This fixes a regression introduced in #260 where high-concurrency chat traffic could stall the event loop during file lookups — the same class of bug the BFF middleware fix in beta.25 addressed for session resolution.

Backend

  • backend/src/agents/builtin_tools/spreadsheet_analysis/analyze_tool.py and list_spreadsheets_tool.pyanalyze_spreadsheet, list_spreadsheets, _find_file, _get_kb_files, _get_session_files are all async def; DynamoDB calls offload via asyncio.to_thread
  • backend/src/apis/inference_api/chat/routes.py_build_tabular_inventory is now async and awaits the file-operation calls directly. Replaces the nested asyncio.run + thread pool executor pattern that could deadlock under load

User Default Model Preference

User-saved default model preferences (set in Settings → Chat Preferences) are now actually applied when the chat starts. Previously the persisted defaultModelId was ignored and chat fell back to the hardcoded factory default — closes issue #161.

Backend

  • backend/src/apis/app_api/chat/routes.py and backend/src/apis/inference_api/chat/routes.py — new _resolve_user_default_model() helper looks up the persisted defaultModelId from user settings. Applied in chat_agent_stream and the invocations endpoint when the request does not specify a model_id
  • RBAC re-checks the resolved default at chat time, so a user whose access to the previously-saved default has been revoked falls back gracefully rather than getting a permission error mid-stream
  • A missing user-settings table now surfaces as 503 Service Unavailable instead of silently dropping the user choice
  • backend/src/apis/app_api/user_settings/routes.py — defaults endpoint adjustments

Frontend

  • frontend/ai.client/src/app/session/services/model/model.service.ts — supports persisted default model resolution
  • frontend/ai.client/src/app/settings/pages/chat-preferences/chat-preferences-settings.page.ts — Chat Preferences page now wires the default model picker to the persisted setting

Test Coverage

  • model.service.spec.ts — 56 lines covering the default-model resolution flow
  • chat-preferences-settings.page.spec.ts — 101 lines covering the settings UI

Nightly E2E Pipeline Restored

The nightly E2E pipeline had been red since the multi-stack deployment hit a series of cookie/JWT validation issues against the dynamic CloudFront URL. This release lands the fixes that turn the pipeline green:

  • CloudFront URL handling for cookie auth in the test environment
  • CDK certificate ARN wiring through the nightly job
  • Increased agent test time limits (the multi-tool turns were tripping default timeouts)
  • Switched the nightly suite from global Bedrock model IDs to US-region IDs to avoid cross-region routing flakes
  • Rebased fix branch on develop to pick up the release-notes strategy update from #248

Upstream Contribution Governance

A non-code change worth flagging because it changes how external contributors interact with this repository.

  • CONTRIBUTING.md — pull requests are now restricted to approved collaborators only (GitHub "Collaborators only" setting). The repository remains source-available under PolyForm Noncommercial 1.0.0; issues stay open to everyone for bug reports and proposed changes, and a maintainer triages each one. The contributing guide explains the path: open an issue → maintainer triages → maintainer either implements upstream or coordinates next steps with the reporter.
  • .github/dependabot.ymlopen-pull-requests-limit: 0 across all four ecosystems (pip, frontend npm, infrastructure npm, github-actions). Scheduled version-update PRs are off; we handle dependency upgrades manually on a weekly cadence. Dependabot security updates are unaffected — when a CVE is published against a dependency, you'll still see a PR.

The full schedules, groups, and labels are retained in the config so flipping the limit back to a positive number restores the previous behavior with a one-line change.


Documentation

  • backend/src/.env.example — BFF cookie encryption architecture documentation updated to reflect the beta.25 shift from direct KMS cookie encryption to Secrets Manager-mediated approach. Clarifies that the BFFCookieSigningKey CMK now encrypts the Secrets Manager secret at rest (not the cookie directly), documents the new BFF_COOKIE_DATA_KEY_SECRET_ARN variable, explains the cross-task SHA-256 derivation, and adds the SSM parameter path for locating the secret ARN with an example ARN format

📦 Dependencies

No dependency upgrades in this release. Dependabot version-update PRs are disabled going forward; the next deps refresh will land as a manually curated batch.


🏗️ Infrastructure

No infrastructure changes. No new resources, no IAM changes, no SSM parameter changes.


🔧 CI/CD

  • Nightly E2E pipeline fixes (#290) — CloudFront URL handling, CDK certificate ARN, agent test timeouts, US-region Bedrock model IDs

🚀 Deployment notes

  • Deploy in any order. No schema, infrastructure, or IAM changes.
  • After deployment, set the MAX_SHEETS_TO_CONVERT and MAX_ROWS_PER_SHEET env vars on the Inference API task definition if you want non-default caps for the spreadsheet analysis tool. Reasonable defaults are baked into the code; only set these if your workbooks routinely need higher limits.
  • Manual follow-up (not deploy-blocking): in the GitHub repo settings, flip Settings → General → Pull Requests → Collaborators only to actually enforce the contribution policy documented in CONTRIBUTING.md. Verify Settings → Code security → Dependabot security updates is still enabled — we explicitly want CVE-driven PRs to keep flowing even with version-update PRs disabled.

...

Read more

Release 1.0.0-beta.25

Release 1.0.0-beta.25 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 11 May 16:26
4fdaea0

Release Date: May 11, 2026
Previous Release: v1.0.0-beta.24 (May 6, 2026)


Highlights

This release is the production-readiness fix for the BFF Token Handler shipped in v1.0.0-beta.24. Beta.24 rewrote the SPA's auth surface onto cookie-based sessions but left three production-breaking bugs that only surfaced under real traffic: the SessionRefreshMiddleware ran synchronous boto3 on the uvicorn event loop so Angular's ~8-endpoint page-load fan-out produced ~16 serialized blocking AWS calls per user per minute (504s, 80s /files/quota tails, 15.6s p-max on a 0.7% CPU task); the CookieCodec minted a fresh random AES-256 key per process, so as soon as we raised desiredCount for concurrency slack every cookie started failing as bad seal on ~50% of requests; and the per-session refresh lock only coalesced in-process, so two tasks could still race cognito-idp:initiate_auth with the same refresh token and Cognito's rotation would silently log out the loser. This release lands the event-loop offload + single-flight resolve, a cross-task shared AES key via Secrets Manager, and a DDB conditional-write refresh lock that elects exactly one leader fleet-wide.

Also shipping: server-rendered PDF page-1 thumbnails on attachment cards, rich iMessage-style image mosaics with a full-screen lightbox and inline markdown preview for .md files in user messages, spreadsheet analysis tools (list_spreadsheets, analyze_spreadsheet) that run CSV/XLSX analysis inside the Code Interpreter sandbox, centralized 401 handling with proactive session-loss detection on tab refocus, and a SKIP_AUTH=true local-dev bypass gated by a CORS-origin allowlist and a CI guard workflow. Token accounting was corrected across the board — per-message cost no longer double-counts tool-use turns and the context-% badge reflects current context occupancy rather than Strands' summed-across-calls value.

Heads-up on beta.24

If you deployed beta.24 to a multi-replica environment, you saw some or all of: 401 storms on /auth/session, page-load latency tails in the tens of seconds, and users silently logged out after tab refocus. Beta.25 is the fix. The CookieCodec and refresh-lock changes require redeploying the Infrastructure and App API stacks in order — see 🚀 Deployment notes at the bottom.


BFF Middleware Event-Loop Blocking & Fan-Out Amplification

The middleware introduced in beta.24 ran three independent classes of work on the uvicorn event loop that weren't safe to run there: synchronous boto3 for DynamoDB + Cognito, an inline-awaited sliding-session write on the response path, and a refresh-coalescing lock that only wrapped the Cognito exchange instead of the full resolve path. Under Angular's ~8-endpoint page-load fan-out with a cold SessionCache window, a single cookie-bearing user produced ~16 serialized blocking AWS round-trips on one uvicorn worker running in a single ECS task — every slow call stalled every concurrent request on the same task. The observable symptoms were ALB 504s, TargetResponseTime p-max of 15.6s at 0.7% CPU, /files/quota outliers reaching ~80s, and endpoint p95s climbing into the hundreds of ms under trivial load. (#264)

How it works now

SessionRepository.{get,put,update_tokens,touch_last_seen,delete} and CognitoRefreshClient.refresh now offload every boto3 call via asyncio.to_thread, so the event loop keeps scheduling other coroutines for the full AWS round-trip duration. A new per-session single-flight primitive (apis/shared/sessions_bff/single_flight.py) wraps the whole cache.get → repository.get → needs_refresh → (maybe refresh) block in SessionRefreshMiddleware._resolve_session — the first caller per session_id runs the loader; N concurrent followers await a shared asyncio.Future and consume the leader's result. The existing get_session_lock(session_id) around the Cognito exchange is preserved end-to-end as defense in depth. _maybe_slide no longer awaits touch_last_seen inline — the DDB write dispatches as a detached asyncio.Task and the response returns the fresh Max-Age synchronously. The cache/throttle boundary alignment that forced a single request to pay both get_item and update_item on the cache-miss boundary has been de-aligned: _DEFAULT_SLIDING_RENEWAL_THROTTLE_SECONDS is now a strict multiple of _DEFAULT_REFRESH_LEEWAY_SECONDS (300s vs 60s).

Backend

  • apis/shared/sessions_bff/repository.py — every boto3 call now wrapped in a nested sync helper invoked via await asyncio.to_thread(helper, ...); method signatures, return types, and exception branches unchanged
  • apis/shared/sessions_bff/refresh.pyrefresh is now async def, calling await asyncio.to_thread(self._refresh_sync, ...); CognitoRefreshError contract and RefreshResult shape preserved verbatim
  • apis/shared/sessions_bff/single_flight.py — new module. async def resolve_once(session_id, loader_coro_factory) -> tuple[Optional[SessionRecord], bool]. Leader registers an asyncio.Future under a thread-lock-guarded dict, runs the loader, sets the result/exception on the Future, removes the registry entry in a finally block. Followers await the existing Future. Distinct session_ids never share a Future
  • apis/shared/middleware/session_refresh.py_resolve_session wraps the cache/repo/refresh block in resolve_once(session_id, _loader). _maybe_slide updates the local cache synchronously and dispatches touch_last_seen via asyncio.create_task, keeping the task on self._slide_tasks with an add_done_callback(self._slide_tasks.discard) — Python's asyncio docs explicitly warn that unreferenced tasks can be GC'd mid-flight, and our initial fix landed this footgun (caught by CI on Python 3.12)
  • apis/shared/sessions_bff/config.py_DEFAULT_SLIDING_RENEWAL_THROTTLE_SECONDS raised 60s → 300s. Strict multiple of the 60s leeway guarantees cache-miss and slide-throttle boundaries never coincide

Infrastructure

  • infrastructure/cdk.context.jsonappApi.desiredCount raised 1 → 2 for concurrency slack. A single blocked event loop on one task can no longer halt all ingress

Test Coverage

~900 lines of new property-based tests. test_session_refresh_bug_condition.py encodes each of the seven sub-conditions as a hypothesis property that fails on unfixed code and passes on fixed code (Property 1 / Expected Behavior from the bugfix spec). test_session_refresh_preservation.py locks in the 11 preservation invariants that must stay unchanged for non-buggy inputs — dormant pass-through, no-cookie pass-through, unrecoverable-cookie clearing, Max-Age re-emit contract, refresh-storm coalescing, codec + client-secret singletons, CSRF decision unchanged, absolute-lifetime cap, fail-closed rotation, uniform CookieDecodeError handling. test_single_flight.py covers the primitive itself: concurrent callers share one loader invocation, exceptions propagate to every waiter, registry entries clean up after failure, distinct sessions are independent.


BFF Cross-Task Cookie & Refresh Correctness

The desiredCount: 1 → 2 bump in the event-loop fix immediately exposed two latent defects in beta.24's BFF design that were hidden when only one task existed. Both had to be fixed before the deployment was actually safe to run with more than one replica. (#273, #274, #275)

Shared AES-256 data key via Secrets Manager

CookieCodec in beta.24 called kms:GenerateDataKey on first use per process and cached the resulting plaintext AES-256 key in memory. The code's own docstring predicted what would happen with more than one task: "two codecs in one process can never decrypt each other's output." And that's what happened — Task A sealed a cookie with Key-A, the ALB routed the follow-up to Task B which had its own Key-B, unseal hit InvalidTagCookieDecodeErrorDiscarding unrecoverable BFF cookie (bad seal) → 401. CloudWatch confirmed: three app-api streams each independently logged "BFF cookie codec initialized (KMS data key fetched)" and every subsequent /auth/session returned 401.

The fix moves the data key out of per-process state and into a single Secrets Manager secret, encrypted at rest by the existing BFFCookieSigningKey CMK:

  • CDK creates BFFCookieDataKeySecret with generateSecretString (44-char alphanumeric, ~261 bits of entropy). On every deploy the secret already exists so the value is stable — cookies survive redeploys
  • CookieCodec._ensure_cipher reads the secret string and applies SHA-256 to derive the 32-byte AES-256 key. Single-shot SHA-256 of a ≥256-bit-entropy random input is a sound KDF for AES-256 usage
  • Every app-api task decrypts the same secret and derives the same key → all codecs round-trip each other's seals. The kms:GenerateDataKey permission dropped from the runtime task role (least privilege); kms:Decrypt stays because Secrets Manager invokes it on the caller's behalf when reading a CMK-encrypted secret

A previous attempt at this bootstrap (#273's initial chained AwsCustomResource flow with kms:GenerateDataKey → secretsmanager:PutSecretValue) failed stack create with Response object is too long. Root cause: the AwsCustomResource framework Lambda JSON-stringifies the AWS-SDK response before applying outputPaths, and KMS returns CiphertextBlob as a Uint8Array that serializes as {"0":233,"1":18,...} — ~1.5 KB for a 200-byte ciphertext, past CloudFormation's 4 KB response-object limit. The Secrets-Manager-native generateSecretString path in #274 removes the chained custom resources entirely (-153 lines net), no per-cold-start kms:Decrypt call, simpler runtime IAM surface.

Cross-task refresh lock via DDB conditional-write

The in-process single-flight and the existing get_session_lock only coalesce same-session callers within one Python process. Once the cookie-codec fix lands and both tasks can share cookies ...

Read more

Release 1.0.0-beta.24

Release 1.0.0-beta.24 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 06 May 22:55
258193d

Release Date: May 6, 2026
Previous Release: v1.0.0-beta.23 (April 29, 2026)


Highlights

This release lands the BFF Token Handler — a ground-up rewrite of the SPA's auth surface. localStorage Bearer tokens are replaced with server-side Cognito session storage keyed by an opaque session id in a KMS-sealed AES-GCM cookie, the public PKCE Cognito client is decommissioned in favor of a confidential client whose secret never leaves the server, and same-origin /api/* routing via CloudFront enables __Host- cookies, double-submit CSRF, and eliminates the CORS preflight from every chat turn. Voice mode returns via a WebSocket-ticket proxy on app-api. The chat view gains a per-conversation cost + context-window badge with write-time aggregation, and context compaction events now surface inline with refresh-survival. Anthropic extended thinking is wired end-to-end via per-model inference parameters. The backend finishes its architecture cleanup: cost, tools, storage, and API-keys modules now live under apis.shared with AST-enforced import boundaries.


BFF Token Handler — Cookie-Based Auth

The SPA's entire auth surface has been rewritten. Bearer tokens in localStorage are out; an opaque session id in a __Host-bff_session httpOnly cookie is in. The public PKCE Cognito client is decommissioned in favor of a confidential BFF client whose secret never leaves the server. Chat streams and voice WebSockets now transit same-origin /api/* through CloudFront, with app-api proxying to inference-api server-side. This closes the window where an XSS could exfiltrate a long-lived Cognito access token, removes the CORS preflight from every chat turn, and sets the foundation for the voice re-enablement below.

How authentication works now

A successful login goes: SPA → GET /auth/login → Cognito Hosted UI (with PKCE) → GET /auth/callback on app-api. The callback exchanges the code server-side using the confidential client secret, writes the Cognito access/refresh/ID tokens to BFFSessionsTable keyed by an opaque session id, and seals that id into an AES-GCM cookie whose data key is wrapped by KMS. The browser never sees a JWT. Subsequent requests carry only the cookie; SessionRefreshMiddleware unseals it, looks up the session row, silently refreshes the Cognito token when it's near expiry, and forwards the request. Unsafe methods require a double-submit CSRF header matching the __Host-bff_csrf cookie.

What shipped

Backend (apis/shared/sessions_bff/). CookieCodec (AES-GCM with version-byte associated data, promoted to a process-wide singleton so the /auth/callback seal and middleware unseal share the same KMS-derived key), BFFSessionRepository with conditional TTL writes, SessionRefreshMiddleware and CSRFMiddleware on app-api, per-session asyncio.Lock so multi-tab refresh storms drive exactly one Cognito exchange, and a Cognito refresh-token client that retries rotation writes three times before failing closed (an old refresh token dies the instant Cognito rotates it, so a silently-failed write would log users out on the next request).

BFF auth routes.

  • GET /auth/login — Cognito authorize with PKCE, optional identity_provider for federated one-click SSO, optional return_to for deep-link preservation. _sanitized_return_to rejects all C0 control bytes (U+0000..U+001F), not just CR/LF, so browser URL-parser strip tricks like /\t/evil.com can't pivot through the // check.
  • GET /auth/callback — server-side code exchange, cookie seal, upsert of the Users row directly from ID-token claims (email, name, picture, custom:roles / cognito:groups); previously the per-request sync ran off the access token, which carries no email, so first-login users had email=None and the Cognito provider-group string in roles instead of the IdP-mapped values.
  • GET /auth/session — returns the session payload the SPA uses to bootstrap.
  • POST /auth/logout — clears cookies, invalidates the DDB row, returns {post_logout_url} pointing at {cognito_domain}/logout so the browser bounces through Cognito Hosted UI to clear the upstream session. Without this, Cognito silently re-issued a code on the next login without a credential prompt.

Sliding session lifetime. The cookie's Max-Age and the DDB row's TTL bump on every successful resolution, capped at created_at + BFF_SESSION_ABSOLUTE_LIFETIME_SECONDS (default 30 d) and throttled by BFF_SESSION_SLIDING_RENEWAL_THROTTLE_SECONDS. Without this, active users were getting logged out after 1 hour even though their refresh token was valid for 30 days.

Chat SSE proxy. POST /chat/stream on app-api is the cookie-authenticated proxy to {INFERENCE_API_URL}/invocations. It owns its httpx.AsyncClient lifecycle and closes it in the streaming generator's finally block — using async with would drain the upstream during __aexit__ and buffer the entire stream before headers flush. Forwards the SPA's OAuth2CallbackUrl header so AgentCoreContextMiddleware can scope tool-side OAuth consent landing URLs to the SPA origin. The AgentCore Runtime data-plane URL is built by _build_upstream_url(), which percent-encodes the ARN as a single path segment and appends ?qualifier=DEFAULT — without this the ARN's literal / split the path and AWS returned 404. Sets X-Accel-Buffering: no and Cache-Control: no-cache so late SSE events (notably oauth_required after message_stop) reach the browser. The same lifecycle fix was mirrored onto the API-key-authenticated /chat/api-converse proxy.

Frontend (SessionService). Bootstraps from GET {appApiUrl}/auth/session in a chained APP_INITIALIZER (migrated to Angular 19+ provideAppInitializer). On 401, navigates to the SPA's /auth/login page with returnUrl — not Cognito Hosted UI directly — so the user can pick a provider. The bootstrap promise hangs on the 401 path so APP_INITIALIZER stays pending until the browser tears the page down (previously the router could render / in the brief window before navigation landed). A new csrfInterceptor mirrors the CSRF header onto unsafe-method requests; a new withCredentialsInterceptor flips withCredentials: true on every HttpClient call to appApiUrl (local dev runs cross-origin; production is same-origin via CloudFront so the flag is a no-op, but without it cross-origin dev 401'd on every call after login). ChatHttpService and PreviewChatService target ${appApiUrl}/chat/stream with credentials: 'include' instead of hitting inference-api directly.

Legacy AuthService retired. auth.service.ts, auth.interceptor.ts, the SPA's /auth/callback page + callback.service.ts, and their specs are deleted. UserService.currentUser is derived from SessionService.user(). authGuard and adminGuard gate on SessionService.isAuthenticated(). The SPA /auth/callback route is gone — the BFF callback at ${appApiUrl}/auth/callback is the only OAuth landing.

Infrastructure. BFFSessionsTable (DynamoDB, TTL attribute), BFFCookieSigningKey (KMS), CognitoBFFAppClient (confidential, secret in Secrets Manager). CloudFront /api/* behavior on the frontend distribution forwards to the app-api ALB with a viewer-request Function that strips the /api prefix. Caching disabled, all-viewer-except-host-header policy, no compression (SSE must not be re-gzipped), readTimeout capped at CloudFront's 60 s default max. SPA fallback moved off distribution-wide errorResponses (which was rewriting /api/* 4xx into 200 + index.html, choking HttpClient JSON parsing) onto a viewer-request Function scoped to the S3 behavior. CognitoConfig.supportedIdentityProviders (env CDK_COGNITO_SUPPORTED_IDPS) wires federated IdPs onto the BFF client; previously only the now-deleted public client had them.

Public PKCE client decommissioned. The SPA-public appClient is gone, along with SSM parameters /auth/cognito/app-client-id and /oauth/callback-url. InferenceApiStack's runtime authorizer repoints to /auth/cognito/bff-app-client-id. AppApiStack's COGNITO_APP_CLIENT_ID also repoints to the BFF client, which keeps /chat/agent-stream Bearer validation alive for API-key and scripted callers.

/config.json retired. appApiUrl is baked into the bundle via Angular fileReplacements (dev → http://localhost:8000, prod → /api). version is generated from the monorepo root VERSION file by a scripts/gen-version.js prebuild hook. cognitoDomainUrl is fetched on demand from a new GET /admin/auth-providers/cognito-redirect-uri admin endpoint. ConfigService collapses to a thin signal accessor over environment.appApiUrl; APP_INITIALIZER drops the chained loadConfig step.

Breaking changes

  • Authorization: Bearer is no longer accepted on SPA-facing routes. Cookie auth is required. External callers must migrate to the BFF session flow or hit /chat/agent-stream (Bearer-only) instead.
  • POST /chat/stream is now the cookie-authenticated proxy. The legacy in-process agent loop moved to POST /chat/agent-stream for API-key and scripted callers.
  • SPA /auth/callback route removed. Third-party tools that deep-linked there must use ${appApiUrl}/auth/callback.
  • SSM parameters deleted: /auth/cognito/app-client-id and /oauth/callback-url. Consumers must migrate to /auth/cognito/bff-app-client-id and register a per-system callback URL.

Voice Mode via WebSocket-Ticket Proxy

Voice returns on top of the new cookie flow. The SPA no longer holds a Cognito access token, so it can't authenticate the WebSocket upgrade against the AgentCore Runtime's customJwtAuthorizer directly. Instead the SPA mints a single-use HMAC ticket, opens a same-origin WS to /api/voice/stream, and app-api opens the upstream WS using the BFF-stored Cognito token (#211, #233).

How it works

  • POST /voice/ticket (cookie + CSRF au...
Read more

Release 1.0.0-beta.23

Release 1.0.0-beta.23 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 29 Apr 22:31
1976764

Release Date: April 29, 2026
Previous Release: v1.0.0-beta.22 (April 8, 2026)


Highlights

This release introduces WebSocket voice streaming with Nova Sonic bidirectional audio, a multi-agent architecture with pluggable agent types (Chat, Skill, Voice), external MCP connectors via AgentCore Identity replacing the bespoke OAuth token vault, per-tool approval gates for dangerous operations, and a full Playwright E2E testing suite. The agent layer has been refactored into a BaseAgent → ChatAgent hierarchy with a factory registry, enabling runtime agent-type selection. The legacy in-house OAuth flow (token vault, PKCE service, encryption layer) has been retired in favor of AgentCore Identity's managed credential providers. 252 files changed across 23,000+ lines of new code.


Voice Mode — Bidirectional Audio Streaming

Full-stack voice interaction using Amazon Nova Sonic 2 via the Strands BidiAgent. Users can speak to the agent and receive spoken responses in real time, with voice-text continuity that carries context from prior text conversations into voice sessions.

Backend

  • VoiceAgent(BaseAgent) wraps BidiAgent with BidiNovaSonicModel for configurable voice, sample rate, and model selection
  • Voice-text continuity via _load_text_history() — loads the text session's message history so the voice agent has full conversational context
  • Separate agent_id ("voice") prevents session state conflicts between text and voice turns
  • Voice-optimized system prompt with conversational guidelines
  • WebSocket endpoint at /voice/stream (inference API) with JWT auth from query params
  • Bidirectional protocol: audio/text input from client, agent event streaming back
  • Accept-first WebSocket pattern aligned with the sample-strands-agent-with-agentcore reference architecture — AgentCore validates auth at the proxy layer
  • Config message supplements missing params in cloud mode; /voice/stream for local dev, /ws alias for AgentCore Runtime
  • Debug endpoints: GET /voice/sessions, DELETE /voice/sessions/{id}
  • CancelledError handling in VoiceAgent.stop() for clean teardown of Nova Sonic streams
  • Real-time cost calculation and token usage metadata for voice turns
  • Log injection prevention via _sanitize_log() for all user-provided values in voice routes

Frontend

Three-layer voice architecture in session/services/voice/:

  • pcm-utils.ts: Pure PCM encoding/decoding (Float32 ↔ Int16 ↔ base64)
  • AudioRecorderService: Mic capture via Web Audio API → 16kHz PCM chunks using an AudioWorklet (pcm-capture.worklet.js)
  • AudioPlayerService: Gapless base64 PCM playback with interruption support
  • VoiceChatService: WebSocket orchestration + state machine (idle → connecting → listening → speaking)
  • VoiceOverlayComponent: Full-screen voice UI with visualizer orb and status badges
  • Chat input gains a voice toggle button with animated state indicators (pulsing red = listening, bouncing green = speaking, spinner = connecting)
  • Live transcript overlay during voice mode
  • MessageMapService.addVoiceMessage() persists finalized voice transcripts to the message list

Infrastructure

  • strands-agents[bidi] optional dependency group added to pyproject.toml
  • Inference API Dockerfile updated with bidi dependency in uv sync commands
  • InferenceApiStack gains HTTP protocol configuration for WebSocket support
  • Voice router registered in inference API main.py

Test Coverage

16 new VoiceAgent unit tests, 14 voice route tests covering WebSocket auth, bidirectional streaming, and teardown.


Multi-Agent Architecture

The monolithic MainAgent has been decomposed into a pluggable agent hierarchy with a factory registry, enabling runtime selection of agent behavior without redeployment.

Agent Hierarchy

  • BaseAgent (ABC): Shared initialization for model config, tools, session management, streaming, and approval hooks
  • ChatAgent(BaseAgent): Strands Agent creation and text streaming — the standard conversational agent
  • MainAgent(ChatAgent): Backward-compatible alias so all existing callers work unchanged
  • SkillAgent(ChatAgent): Progressive skill disclosure (see below)
  • VoiceAgent(BaseAgent): Bidirectional audio via BidiAgent (see Voice Mode above)

Agent Type Registry

agent_types.py provides a pluggable registry pattern:

  • create_agent(agent_type, **kwargs)BaseAgent subclass
  • register_agent_type(name, cls) for dynamic registration
  • ChatAgent registered as "chat", SkillAgent as "skill", VoiceAgent as "voice" (conditional on strands-agents[bidi])

Factory Routing

The inference API now routes chat turns through create_agent(agent_type, ...) instead of hard-coding MainAgent. InvocationRequest gains an optional agent_type field, folded into the LRU cache key so chat/skill agents for the same session don't collide. PausedTurnSnapshot persists the resolved agent type so OAuth-paused turns rebuild on the correct factory variant after cache eviction.

Configuration Centralization

All environment variables and magic strings consolidated into agents/main_agent/config/constants.py with EnvVars, Defaults, and Prefixes classes. 13 modules updated to import from the centralized constants instead of inline os.getenv() with hardcoded strings.

Test Coverage

9 factory tests, 38 skill tests, 16 voice tests, plus existing 543 tests passing with zero behavior change.


Progressive Skill Disclosure

A three-level skill architecture adapted from the sample-strands-agent reference, allowing the agent to discover and load tool capabilities on demand rather than loading everything upfront.

How It Works

  • Level 1: Lightweight skill catalog injected into the system prompt — the agent sees what skills exist without loading their full instructions
  • Level 2: skill_dispatcher loads the full SKILL.md instructions on demand when the agent decides to use a skill
  • Level 3: skill_executor runs the actual tool functions bound to the skill

New Modules

  • skills/skill_registry.py: Discovers SKILL.md files, binds tools, serves the catalog
  • skills/skill_tools.py: skill_dispatcher + skill_executor as Strands @tool functions
  • skills/decorators.py: @skill() decorator and register_skill() for tool tagging
  • skill_agent.py: SkillAgent(ChatAgent) with progressive disclosure override

Skill Definitions

  • web-search/SKILL.md: Example skill definition for web search tools
  • canvas-morning-check/SKILL.md: Educator-facing morning course health check that surfaces submission rates, struggling students, and upcoming deadlines via the Canvas MCP server, with FERPA-aware anonymization guidance

External MCP Connectors via AgentCore Identity

The bespoke OAuth token vault (per-user DynamoDB encryption, KMS, Secrets Manager client credentials, manual refresh) has been replaced with AgentCore Identity's managed token vault and credential providers. This is a full-stack rewrite of how external MCP tools authenticate with third-party services.

AgentCore Identity Integration

  • AgentCoreContextMiddleware copies Runtime headers (WorkloadAccessToken, OAuth2CallbackUrl, session ID, request ID) into BedrockAgentCoreContext on every invocation — required because the Inference API is a plain FastAPI app, not a BedrockAgentCoreApp
  • AgentCoreIdentityClient wraps IdentityClient.get_token() with a narrower surface for USER_FEDERATION (3LO) flows, surfacing "user consent required" as a structured TokenResult(authorization_url=...) rather than an exception
  • AgentCoreCredentialProviderRegistrar wraps bedrock-agentcore-control for admin-side OAuth2 credential provider CRUD with vendor mapping (Google/Microsoft/GitHub to native vendors; Canvas/Custom via OIDC discovery URL)

OAuth Consent Flow

When an external MCP tool needs OAuth consent, the authorization URL flows through the SSE stream as an oauth_required event:

  • OAuthConsentService orchestrates popup opening + postMessage receipt
  • OAuthConsentBanner renders a "Connect" button inline in the chat
  • /oauth-complete landing page handles the AgentCore callback redirect and signals consent completion to the opener tab
  • PendingInterrupt gains an oauth_consent variant so the consent prompt rehydrates after a page refresh

Legacy OAuth Retirement

Deleted: OAuthService, OAuthTokenRepository, token_cache.py, encryption layer, user-facing /oauth/* routes, OAuthToolService, settings/connections page, settings/oauth-callback page. The admin UI has been rebranded from "OAuth Providers" to "Connectors" (admin/connectors/), with the form rewritten for the AgentCore-owned shape — credential rotation requires clientId + clientSecret together (AgentCore's update API is not partial), and the success screen displays the AgentCore callback URL with a copy button.

Shared Workload Identity

A CfnWorkloadIdentity (<projectPrefix>-platform-workload) is provisioned in InfrastructureStack and shared between inference-api and app-api. Both services mint user-scoped workload tokens against it via GetWorkloadAccessTokenForUserId, ensuring the OAuth token vault is keyed consistently — a user consents once and both code paths find the token. The runtime's auto-created identity stays in place but is no longer used for vault calls.

Infrastructure

  • InfrastructureStack: New CfnWorkloadIdentity + SSM exports
  • AppApiStack: IAM grants for Secrets Manager lifecycle (create/update/delete/get) on bedrock-agentcore-identity!default/oauth2/*, plus bedrock-agentcore:GetResourceOauth2Token
  • InferenceApiStack: Runtime workload identity lookup via AwsCustomResource (SDK GetAgentRuntime call) replacing the broken Fn::GetAtt on nested attribute paths; IAM grants for OAuth secret read
  • CloudFront added to API CORS origins

Test ...

Read more