-
Qwen3 VL 235B (
qwen.qwen3-vl-235b-a22b,$0.53/$2.66) — the first In-Region-only model. A vision-capable model reached over Converse onbedrock-runtime, but with nous.*/global.*cross-Region inference profile: its model card lists Geo and Global as Not supported. It is therefore the first model to exercise thecross_regionfield that5.0.1added but left unused. The registry key is the bare foundation-model IDqwen.qwen3-vl-235b-a22bwith"cross_region": false, and its application inference profile wraps the foundation model directly in the deploy Region rather than aus.*system profile, so cost attribution is preserved. Providerqwenis added toVALID_PROVIDERS, aqwenbranch toget_model_family, and the family joins the drop-thinking set in_thinking_fields(the card documents no reasoning parameter); profiles, the SSM map, IAM grants, and the wizard dropdown derive automatically.grant_invoke_to_roleomits the system-profile statement for it (none exists) and covers it through the foundation-model grant. In-Region only —us-west-2is supported. Verified live over Converse for both text and image input; the application-profile-over-foundation-model wrapper is synth-verified and takes effect on deploy. -
Targeted region inspection. The new
inspect_region_toolgives the vision model a close, blind reread of localized regions that a specialist could not resolve. Specialists can flag aMISS, an alternate reading, or an audit-modehuman_review_flagwith a bounding box. The agent batches up to 12 flagged regions per page, calls the inspector after the specialist pass and before correlation, and passes its S3 artifact into correlation. Reports retain validated crop PNGs and expose them in the Page Reader's Inspections tab with reading confidence, scale, dimensions, and provenance. Pages without uncertainty flags are not inspected.
Since [5.0.0]. Three vision-capable models added, a second inference transport alongside
Converse, and direct image upload. pyproject.toml reads 5.0.1.
-
Three vision models (eleven active total). Kimi K3 (
us.moonshotai.kimi-k3,$3.30/$16.50) and Pixtral Large (us.mistral.pixtral-large-2502-v1:0,$2.00/$6.00) join on the existing Converse path — registry entries plus their providers (moonshotai,mistral) added toVALID_PROVIDERSand families toget_model_family; profiles, IAM, and the wizard dropdown derive automatically with no other code change. Gemma 4 31B (google.gemma-4-31b,$0.14/$0.40) joins on the new mantle transport below. -
A second transport:
mantle. Gemma 4 31B is served only on the OpenAI-compatiblebedrock-mantleendpoint, not Converse.bedrock_client.pygains a transport dispatch in_invoke_single_modeland an_invoke_mantlepath: an OpenAI Chat Completions request (/openai/v1/chat/completions) signed with SigV4 under thebedrock-mantleservice and POSTed over HTTP, its response normalized to the same envelope Converse returns.VALID_TRANSPORTSgainsmantle;get_transport()resolves it by model ID because the registry is not shipped to the Lambda layer. -
cross_regionregistry field. Marks an In-Region-only model whose key is the bare foundation-model ID and whose application profile wraps the foundation model directly rather than aus.*system profile. Present and validated, but currently unused — no such model is registered. -
Direct image upload. A document can now be a single image (PNG, JPEG, TIFF, WebP, or GIF), not only a PDF. The image is uploaded to the source bucket like a PDF and runs the same pipeline — classification, enhancement, specialists, correlation, report — with no PDF-specific steps.
pdf_to_images_converterroutes on the fetched bytes:%PDF-rasterizes via Poppler as before; anything else is opened with Pillow and normalized (first frame only, RGB, transparency flattened onto white, downscaled to 2048px, JPEG-compressed to the size cap) into a singlepage_001.b64. It emits the same artifact in the output bucket, so classify/specialists/correlator/report are unchanged and Poppler never runs on an image.
-
Mantle models have no application inference profile. They are excluded from profile creation, the SSM
model-profilesmap, and the two inference-profile IAM statements, and are instead granted the foundation model plusbedrock:InvokeModelonproject/default(the OpenAI default-project grant now also covers mantle). Cost is attributed to the Bedrock default project, not a per-model profile. -
The wizard offers mantle models without a profile.
GET /api/modelsnow includes a model when it isactiveand either has a profile in the SSM map or uses themantletransport. -
Upload gates accept images. Both the browser (
Chat.jsx) and the server (/api/upload) now accept a PDF or an image. The server sniffs magic bytes rather than trusting the declared MIME type, and caps image uploads at 20 MB — PDFs keep the 50 MB limit — because image bytes are validated un-compressed by the foundationImageProcessor(20 MB limit), whereas PDF pages are pre-compressed. -
pdf_to_images_converterparameter renamedpdf_path→document_path(schema and handler), reflecting that it now accepts a PDF or an image. -
remediation_specialistrejects image inputs. It tags an existing PDF's structure tree, so there is nothing to remediate for an image. It now fails fast with a clear message asking for a PDF, and its tool description tells the agent not to call it for image sources. -
deploy.shstep 4 prompts to rebuild a stale foundation layer. Before the container build and the Lambda deploy it checkslambdas/layer.zipagainst its sources and offers to runbuild_foundation_layer.sh, instead of letting the synth-time staleness guard surface as a CDK traceback. HonoursBADGERS_ALLOW_STALE_LAYER=1(skip) andBADGERS_ASSUME_YES=1(auto-rebuild). Step 6 is unaffected — the Runtime image copiesfoundationsource and installs its own requirements, so it never ships the pre-built layer.
Everything since [4.0.0] (2026-08-07). pyproject.toml reads 5.0.0. This is the first
tagged release of the repository; earlier versions exist only as CHANGELOG entries.
Warning
5.0 is a fresh-install release. There is no upgrade path from 4.x.
Every model 4.x used is retired, and no manifest, agent config, or custom specialist that
names one will work. Every bucket and the jobs table carry RemovalPolicy.DESTROY, so the
supported path is to destroy the 4.x deployment and deploy 5.0 clean. Custom specialists
created through the wizard under deployment/custom_specialists/ are not rewritten by
anything and must be re-pointed at a current model by hand.
Three further items are breaking for anyone scripting the deploy entry points, referencing the UI env file, or reading per-model profile environment variables; all are marked BREAKING below.
-
A model registry as the single source of truth.
deployment/s3_files/config/model_registry.jsonholds nineactivemodels with display name, provider, transport, prices (including long-context bands), thinking support, prompt caching, and astatusfield.deployment/stacks/model_registry.pyreads and validates it at synth, so a malformed entry fails the build rather than a Lambda.This replaces roughly seven hand-maintained model lists across CDK, the Lambda layer, and the UI, several of which failed silently when missed and had already drifted — Claude Sonnet 4.6 had a profile but was absent from all three IAM statements, so it had never been invocable. Adding or retiring a model is now one registry edit plus a deploy.
statusisactive,retiring, ordisabled.disabledprovisions nothing at all — intended for a model an account declines to adopt on price, terms, or region footprint. -
GET /api/models. Joins the deployed SSM profile map against the registry and returns only models that are bothactiveand actually provisioned, so the wizard cannot offer a model this deployment has no profile for. 60-second cache; returns 503 rather than falling back to a stale hardcoded list. -
Two deploy preflights, wired into
deploy.sh. Model access and a live invocation check. Both already existed incommon.shbut were never called from any entry point; both now take their model list from the registry and run before anything is created. -
Layer staleness guard.
lambda_stack._assert_layer_not_stale()compareslayer.zipagainst the 19 files the build script copies and fails at synth.deploy.shbuilds the layer as step 1, so a normal deploy was never stale; this covers the two paths that skip it (DEPLOY_RESUME_MODE=1, and callingcdkdirectly), where a deploy would otherwise ship the previous layer silently. -
handwriting_math_specialistis a first-class specialist, disabled by default. It has had a handler, a manifest, a schema, and ten prompt files since 2026-07, but never an entry indeployment_config.json— and the Lambda stack deploys only names present there, so no flag existed to flip. It now has one ("enabled": false, Text & Language), aspecialist_defaultsrow inpricing_config.json(prompt size measured at ~12,700 tokens; its dictionary prompt makes it the largest specialist prompt), ahandwritten_mathroute in the agent prompt pointing atanalyze_handwriting_math_tool, a place in both enhancement-eligible lists to match its manifest'senhancement_eligible: true, and a classifier guidance line so the classifier names handwritten mathematics explicitly. Verified through the CDK filter: disabled, 12 functions and no target; flipped on, 13 functions and ahandwriting-math-specialistGateway target.
-
BREAKING: all eight models are new. Retired: Claude Sonnet 4.5, Claude Sonnet 4, Claude Haiku 4.5, Claude Opus 4.5, Amazon Nova Premier. Added: Claude Opus 5, Claude Opus 4.6, Claude Sonnet 4.6, Amazon Nova 2 Lite, GPT-6 Astra, GPT-5.6 Sol, GPT-5.6 Terra, GPT-5.6 Luna. Two behaviours arrive with them: Opus 5's adaptive thinking is on by default, so the runtime pins its temperature to 1 on every call; and the OpenAI models document no reasoning parameter and additionally require
bedrock:InvokeModelonarn:aws:bedrock:{region}:{account}:project/default.Data retention: none of the eight models requires an account-level retention opt-in, and none is subject to human review. Opus 5 is not on Bedrock's abuse-detection retention list. The four OpenAI models do retain classifier-flagged traffic for up to 30 days for automated abuse detection under the default retention mode; no opt-in is needed and no human review occurs, and ZDR is available through your AWS account team.
Assignments: Sonnet 4.6 is the specialist workhorse (23 manifests), Opus 4.6 handles heavy reasoning and the agent runtime, GPT-5.6 Terra is fallback 1 and Nova 2 Lite fallback 2. Haiku 4.5 was replaced by GPT-5.6 Luna and Nova Premier by Sonnet 4.6 — capability matches, not price matches.
-
BREAKING: InvokeModel replaced by Converse on the specialist path. Converse is the only API all eight models support; the four OpenAI models have no Invoke support at all. One request builder for every provider, and
_normalize_responseno longer branches by model family — reasoning arrives asreasoningContentfor both Claude and Nova 2. Deleted 193 lines of now-unreachable code, including the Nova 1 payload converter, which emitted aschemaVersionNova 2 does not accept. -
BREAKING: per-model
*_PROFILE_ARNenvironment variables are gone. Consumers receive one variable,MODEL_PROFILES_PARAM, and read the SSM parameter/badgers-{deployment_id}/model-profiles. This removes fiveCfnOutputexports, fiveFn.import_valuecalls inapp.py, six named properties onInferenceProfilesStack, and four hand-maintained env-wiring sites.MODEL_TO_PROFILE_ENV_MAP— an exact-string dict whose misses silently lost cost attribution — is deleted. -
One model-selection parser. There were two, drifted in opposite directions: one never read
adaptive_thinkingoreffort, the other read them for the primary model but flattenedfallback_listto bare strings. Between them no manifest could express "fall back to a different model and keep thinking on."foundation/model_selection.pyis now the only parser. It returns a dict rather than a tuple, rejects an unknowneffort, and no longer silently defaults a missingmodel_idto Opus. -
The UI model list is served, not checked in. The
modelsblock was removed fromui/config/pricing_config.json;/api/pricing-configoverlays live models from/api/models. The file keeps onlyingestion,presets, andspecialist_defaults. Prices can no longer drift from the registry — the checked-in copy understated every Claude price by 10%, because it recorded Global CRIS prices while the code invokes geo profiles. -
toModelIdno longer passes unknown labels through. It wrote an unrecognised label into the manifest verbatim as a model ID, producing a specialist that saved and deployed cleanly then failed on first call — and the fallback chain correctly refuses to retryAccessDenied, so nothing rescued it. It now throws and lists the known models.
- HTML document reports. A new deterministic specialist,
html_report_specialist, runs once per document after every page correlation and produces a durable report. It makes no model call and has no prompt files.- Tool
generate_html_report_tool; new workflow step 6 (REPORT) inagent_system_prompt.xml, exempt from the per-page tool budget. - New S3 layout under the output bucket:
reports/{ownerKey}/{reportId}/holdingreport.html,manifest.json, andpages/page-{n}.jpg/.xmlper page.ownerKeyislocalin local development, otherwise the first 24 hex characters ofsha256(user_id). - Five endpoints:
GET /api/reports,/api/reports/:id/manifest,/api/reports/:id/pages/:n/image,/api/reports/:id/pages/:n/xml, and/api/reports/:id/download. - New
owner-indexGSI on the jobs table (PKowner_sub, SKstarted_at, INCLUDE projection). Deliberately sparse:owner_subis written only on the job-levelorchestratorrow, so subtask rows never appear. - New job-row attributes:
owner_sub,user_name, and — once a report exists —report_id,report_title,report_created_at,report_page_count. - Identity now propagates end to end. The UI sends the caller's Cognito sub as
actor_idand email as a newuser_namepayload field, replacing the hardcodedlocal_testing_user;JobTrackingHookstampsuser_id/user_nameinto any tool whose schema declares them. - New 📚 Reports tab with three views — Overview, Page Reader (image beside the correlated spine, with Rendered Spine / Raw XML / Specialists / Audit tabs and ← → keyboard navigation), and Audit Trail — plus a single-file HTML download that inlines the CSS, JS, and every page image as base64 so it works offline.
- Tool
- The agent prompt's
<available_tools>{{TOOLS_LIST}}</available_tools>element is removed. Nothing ever substituted it — not the runtime, which prepends the operating environment and appends the session id and otherwise sends the file verbatim, and not Strands — so the model received the literal placeholder. Tools arrive as MCP tool definitions from the Gateway; the prompt never needed a list. - The specialist creation wizard works end to end. Its three server endpoints were
stubs returning
{prompts:{}},{}, and{output:'Not yet wired'}; they are now implemented in a newui/server/routes/wizard.js, mounted bymountWizardRoutes.POST /api/wizard/generatestreams SSE, not JSON. Six sequential Bedrock calls (gestalt,job_role,context,rules,tasks,format) take minutes, which no plain POST survives behind the load balancer. Thestartframe ships the whole section list so the client keeps no second copy of it.POST /api/wizard/saveis new — it did not previously exist in any form. It writes every artifact underdeployment/custom_specialists/, whichCustomSpecialistsStackalready reads. The built-in specialists indeployment/s3_files/are never touched.POST /api/wizard/deployalso changed from JSON to SSE, streamingdeploy_custom_specialists.shinto the shared log panel.- A failed section becomes an
<!-- ERROR ... -->stub and is reported inwarnings, so one bad generation does not lose the other five. All six failing is a hard error instead: that means credentials, region, or model access is broken, and six comment stubs would read as content. - Bedrock is reached by hand-signing an HTTPS Converse request with SigV4 rather than
through
@aws-sdk/client-bedrock-runtime, which is not a dependency ofui/. No new npm dependency and no container-build change. - Save and Deploy are separate buttons; Deploy stays disabled until a save succeeds.
- Gateway observability, which previously produced nothing. The Gateway had the IAM
permissions and claimed full observability but had no delivery configured — the console
showed "Log delivery (0)" and "Tracing: Not enabled". A new
deployment/stacks/log_delivery.pyprovidesshared_delivery_policy,deliver_to_log_group, anddeliver_traces_to_xray. The Gateway stack now creates log group/aws/bedrock-agentcore/gateways/{deployment_id}/app(TWO_YEARS retention,RETAIN), deliversAPPLICATION_LOGSto it, and deliversTRACESto X-Ray. - Deploy preflights.
step_infranow refuses to proceed unless the account/region is CDK-bootstrapped and service quotas have headroom:preflight_bootstrapandpreflight_service_quotas(VPCs, internet gateways, S3 buckets, Cognito pools, DynamoDB tables, ECR repositories, 26 Lambda functions, 5 IAM roles, KMS keys). Steps 6 and 7 additionally check QEMU binfmt for cross-architecture container builds viapreflight_docker_cross_platform, installingqemu-user-staticon Linux if absent. - A log-delivery preflight before the Gateway and Runtime steps. A CloudWatch
DeliverySourceis unique per(resourceArn, logType), not per name, and CloudFormation creates new resources before deleting removed ones — so both spellings coexist during an update, collide, and fail the deploy withAlreadyExists. Renaming cannot fix it.preflight_log_deliveryreports what occupies the slot, explains that removing it deletes delivery configuration only, and clears it on confirmation. - Region is resolved rather than assumed.
ensure_regionwalks env (AWS_REGION,AWS_DEFAULT_REGION) →aws configure get region→ prompt →us-west-2with a confirmation. An invalid region name is a hard failure. - Enhanced-image provenance in HTML reports. Reports previously showed the page as
scanned, because the correlation artifact names the original as its source — while
most specialists had read the enhanced copy.
html_report_specialistnow persists the enhanced image to{report_prefix}/pages/page-{token}-enhanced.jpgand recordsenhanced_image_keyper page; the Page Reader offers Original/Enhanced tabs when a second image exists. New endpointGET /api/reports/:reportId/pages/:pageNumber/enhanced-image, returning 404 rather than an error when absent. - A
levelsenhancement operation — the 13th. A Photoshop-style levels adjustment that auto-computes black and white points from the histogram by percentile, applies gamma, and blends back by intensity. Unlikethresholdit preserves continuous tonal gradation, so ink stroke weight survives. It returns percentile analysis to the agent so intensity can be retuned on retry, and comes with a new Pipeline F, "Historical Manuscript Restoration" (desaturate@1.0 → levels@0.5 → remove_stains@0.4). - Enhancement document-type context is now operator-editable. The enhancer reads
s3://{CONFIG_BUCKET}/config/document_type_contexts.json, falling back to an in-code map, so enhancement behaviour can be tuned per document type without a container rebuild. Contexts went from a terse phrase to a paragraph of guidance each, and five types were added:historical_manuscript,photograph,map,legal,newspaper. Newdeploy.shstep-3 submenu option6) Runtime Configuploads it. - Live analyzer status in Chat. A new right-hand "🧩 Analyzers" panel polls
/api/jobs/:jobIdevery 2.5s while a turn runs and shows one pill per specialist withcomplete/totalpages. It exists because the stream reports a tool starting but never finishing or failing, so real COMPLETE/FAILED can only come from the job rows. Pills report the status needing attention (FAILED before RUNNING before PENDING before COMPLETE) and carry the first failure's error in the tooltip. - Live activity indicator in Chat, driven by the server's
statusevents, which the UI previously discarded. - Copy buttons on chat messages, extracted to a shared
CopyButtoncomponent, plus Shiki syntax highlighting in the Reports Raw XML tab and the Evaluations Result Output pane. The Evaluations pane picks its grammar from the artifact's file extension rather than assuming XML. - Attachment chips in the composer with a status line, and deferred upload — nothing reaches S3 until Send, so a removed or unsent attachment leaves no object behind.
- BREAKING —
deploy.shmenu keys and option 9 semantics. Resume moved from12tor/R/resume, and./deploy.sh 12is now rejected. Option9(Full Deployment) no longer re-runs completed steps or prompts on each one; it delegates to the samerun_remainingpath as resume, so it behaves like resume. Anyone who used option 9 to force a redeploy needs11(Reset Deployment State) first. - BREAKING — the UI env file moved and
update_frontend_env.shwas deleted.ui/config/.env→ui/.env, anddeployment/update_frontend_env.shis gone with its job folded intodeployment/scripts/generate_ui_env.sh. An existingui/config/.envis now silently ignored: the UI comes up with unset buckets and table name, and/api/reportsreturns 503, untilgenerate_ui_env.shis re-run. - Deploy behaviour:
step_gatewayandstep_runtimenow have an interactive gate. Both callpreflight_log_delivery, which prompts before deleting anything and stops the deploy on decline. It also stops when it cannot determine the answer, on the reasoning that "could not determine" is not "clear to proceed". An unattended or CI invocation that previously ran clean can now block on the prompt. New escape hatchBADGERS_SKIP_LOG_DELIVERY_PREFLIGHT=1; noteBADGERS_ASSUME_YES=1answers the prompt too and therefore deletes the conflicting sources without asking. destroy.shnow deletes local deploy state (.deploy-state/{id}.jsonand the outputs cache) and resolves bucket names from stack outputs instead of hardcoding them. It also deletes the KMS alias after scheduling key deletion, so a redeploy with the sameDEPLOYMENT_IDno longer waits outKMS_WAIT_DAYS. With nothing to destroy it now waits for a keypress rather than exiting, which will hang a non-interactive run.- Specialist Lambda memory raised 2048 MB → 6144 MB via a new
SPECIALIST_MEMORY_MBconstant, applied to zip, container, and wizard-generated specialists. Lambda scales CPU with memory and specialists hold source plus processed page images. Faster pages, higher per-invocation cost. full_text+elementsare now mandatory on every page, including visual-only and handwriting-only pages, so every page can be correlated into a canonical spine. More model invocations and more cost per document than before.- Default theme is now light, and the AWS Purple theme was removed. A stored
badgers-themeofpurpleis rewritten tolighton load. - Runtime images now carry a unique tag per deploy (
websocket-{UTC timestamp}fromruntime_image_tag, exposed asRUNTIME_IMAGE_TAG) alongside the floatingwebsocketalias. See Fixed for why. - The chat SSE stream sends a
: pingcomment every 15s. The load balancer closes any connection idle in either direction for 60s (idle_timeout.timeout_seconds, the AWS default, whichCfnExpressGatewayServicedoes not expose) and this stream writes nothing for the whole of a tool call —full_texthas taken 115s on a dense page. deploy_specialist.shno longer requires aprompts/directory; it reads the manifest and only uploads prompts whenspecialist.prompt_filesis non-empty. This is what lets a promptless specialist such ashtml_report_specialistdeploy.- The text an attachment sends to the agent changed from
Uploaded file: <s3 uri>toProcess: <s3 uri>. The agent parses this, so it is a contract between UI and prompt. foundation/__init__.pyresolves submodules lazily (PEP 562) instead of eagerly re-exporting them. Source-compatible for every documented name, but anything relying onimport foundationpre-importingfoundation.image_processoras a side effect breaks.- The
awsandpython3CLI calls incommon.share now shell-function wrappers that strip carriage returns, for Windows VDI / Git Bash hosts where captured values silently gained a trailing\r. @assistant-ui/react^0.12.21→^0.15.8, adding@assistant-ui/core,store, andtap. The hookuseMessage()was replaced byuseAuiState().markitdownand its dependency tree (magika,onnxruntime,mammoth,openpyxl,pdfminer-six, Azure Document Intelligence clients) added to the lock file.pypdf6.14.2→6.15.0;pillow>=12.3.0added to thedevdependency group.versionwas4.0.0across these commits and is now5.0.0.
-
Claude Sonnet 4.6 had a profile but no IAM grant. All three statements in
grant_invoke_to_roleomitted it, so it had never been invocable. It was also the only model inpricing_config.jsonwith no profile at all. Found by generating the statements from one list instead of maintaining three. -
agent_model_config.json'seffortandadaptive_thinkingreached nothing. The agent forwarded only{"thinking": ...}. Both are now sent, as siblings — nestingeffortinsidethinkingraisesValidationException; it belongs in a separateoutput_configobject. -
The image enhancer's model was pinned in code. It read a hardcoded model ID rather than the
VISION_MODELenvironment variable the stack already set. -
Converse requires raw image bytes where Invoke accepted a base64 string.
ImageSource.bytesis a blob and boto3 encodes it, so forwarding the existing base64 string would have double-encoded every page image. Now decoded, with invalid base64 rejected loudly. -
fallback_modelsin the agent config was dead. Defined and read nowhere; its two entries named retired models. Deleted rather than migrated. Strands already retriesModelThrottledExceptionsix times with exponential backoff, so the gap it appeared to fill did not exist. -
Every cdk-nag
appliesTostring was matching nothing. Two independent bugs. The model lists were hand-written and stale — 5 entries against a registry of 9, three naming models that no longer exist. And every entry wrote<AWS::AccountId>, with several hardcodingus-east-1, while cdk-nag matches the resolved template — wherecommon.shhas exported a concrete account and Region. Model lists are now generated from the registry, andstacks/nag_arn_renderings.pyemits both ARN forms.Also suppressed for the first time: the
Action::wildcards CDK'sgrant_read/grant_read_writeL2 grants emit, andResource::*on actions that take no resource (ecr:GetAuthorizationToken, X-Ray sampling reads,cloudwatch:PutMetricData,aws-marketplace:*).custom_specialists_stack.pyhad noNagSuppressionsimport at all, so all 20 of its findings were unsuppressed — including L1 and IAM4 cases already justified elsewhere.agentcore_gateway_stack.pyhad the same drift, having never gainedhtml_report_specialist.Result: 0 cdk-nag findings across every stack that synthesizes, from 29.
-
Two documentation claims were the inverse of the truth.
README.mdand a comment ininference_profiles_stack.pyboth stated thatus.*inference profiles "avoid cross-region routing." They do not —us.*is geo cross-Region inference; the prefix bounds which Regions may be used, not whether Regions are crossed. The SCP troubleshooting section built on that premise also quoted the global foundation-model ARN form, a shape aus.*profile cannot produce, so its diagnosis pointed away from the real cause. -
The pricing calculator's fallback model was a retired one. With no
default_modelfor a specialist it fell back to the literal'Claude Sonnet 4.5', which then failed the lookup and priced against whichever model happened to be first in the list. Now derived from the served model list. -
The Converse rewrite dropped the temperature rule for extended thinking. The old client forced
temperature = 1for both extended and adaptive thinking; the first Converse draft carried over only adaptive. Claude rejects any other temperature when thinking is on, so the correlation specialist's extended-thinking fallback would have failed with a ValidationException that the fallback chain correctly refuses to retry. Caught by executing the request builder before any live run. Opus 5, whose thinking is on by default, gets the same treatment via a newthinking_default_on()check — a wizard-created Opus 5 specialist would otherwise have inherited the 0.1 default and failed every call. -
preflight_model_invocationcould report success on a failed call. It discarded the exit code and grepped for four exception names, so an expired token, a throttle, or an AWS CLI too old to knowbedrock-runtime converseall printed a tick. Now judged by exit code. -
deploy.shstep 3 marked a failed category sync as complete. Options 1–6 called_sync_config_categorywithout checking its status, so a failedaws s3 syncfell through tomark_complete. Option 6 is how the model registry reaches S3; a silent miss there leftGET /api/modelsreturning 503 while the state file said uploaded. Every arm now propagates failure. -
Resume rebuilt both Docker images every time. Steps 6 and 7 had no
check_completedguard — every other step did — sorun_remainingalways rebuilt and pushed the runtime and UI images even when both were recorded complete. It also made step 8'sinvalidate_state("ui_image_pushed")inert, since step 7 re-ran regardless. Step 6 now checks bothruntime_image_pushedandruntime_deployed, which is the case the previous guard got wrong before it was removed. -
Four step failure paths called
exit 1instead ofreturn 1, killing the interactive menu session and bypassing the "Resume stopped at" message. All step failures now return. -
destroy.shverified only its hardcoded stack list. A stack matchingBADGERS-*-{id}-{suffix}in CloudFormation but absent from the list survived under a "Teardown complete" banner. The final check now also queries CloudFormation for the name pattern and reports anything unexpected. -
The UI task role could not call Bedrock, so the deployed wizard could not generate.
POST /api/wizard/generateinvokes Claude Sonnet 4.6 from the ECS container, and the task role had nobedrock:InvokeModelstatement at all. Every section failed withAccessDeniedExceptionin a deployed environment while the same code worked locally on the operator's credentials.ECSStacknow takesinference_profiles_stackand callsgrant_invoke_to_role(task_role, models=[WIZARD_GENERATOR_MODEL_ID])— a newmodelsfilter on the same registry-generated grant the agent role uses, narrowed to the one model the container invokes (no OpenAI default-project statement, since none is needed). The same constant is exported to the container asWIZARD_GENERATOR_MODEL_ID, whichwizard.jsnow reads ahead of its local-development literal, so the grant and the call cannot name different models.invoke_nag_applies_totakes the same filter, so the suppression lists exactly the ARNs that statement produces. -
preflight_model_accessasked the wrong question. It checked whether each base model appeared in the Region'slist-foundation-modelscatalog. Every BADGERS model is invoked through aus.*geo profile and none supports In-Region inference, so a CRIS-only model can be fully usable from a Region whose catalog does not list it — a false block. It now checksget-inference-profileon the geo ID forACTIVEfirst, which answers "is this a valid source Region", and falls back to the catalog (ACTIVEorLEGACY) only when that lookup fails for an unrelated reason such as a missingbedrock:GetInferenceProfile. -
GET /api/modelsread the wrong SSM parameter in local development. The profile map lives at/badgers-{id}-{suffix}/model-profiles. The ECS task definition setsDEPLOYMENT_IDto the composite already;ui/.envsets the bare id andSTACK_SUFFIXseparately, so the same code missed every parameter locally and the wizard dropdown was empty. AresourceId()helper appends the suffix only when it is not already present. -
destroy.shcould not tear down a deployment whilelambdas/layer.zipwas stale.cdk destroysynthesizes the app first, and the stale-layer guard added in this release aborted that synth — so the run ended on a traceback with every stack still standing, before a single DeleteStack was issued. Observed tearing down a 4.x deployment from a 5.0 working tree. The guard exists to stop a deploy from shipping the previous layer; a destroy ships nothing, socdk_destroyincommon.shnow setsBADGERS_ALLOW_STALE_LAYER=1itself. Deploys are unaffected — step 1 rebuilds the layer before the guard runs.
-
The Gateway stack no longer uses
@aws-cdk/aws-bedrock-agentcore-alpha. The Gateway L2s graduated intoaws-cdk-libasaws_cdk.aws_bedrockagentcore, which the Memory and Runtime stacks already imported; the alpha package is deprecated wholesale and emitted 27 warnings on every synth. Every symbol the stack uses exists in the stable module under the same name with the same keyword parameters. The synthesized Gateway template is identical before and after — same 24 logical IDs, same properties — so an in-place update does not replace the Gateway. The dependency is removed frompyproject.toml. -
Stack.add_dependency→add_stack_dependencyat all 24 call sites inapp.py; the former is deprecated in aws-cdk-lib 2.263. Construct-levelnode.add_dependencyis a different API and is unchanged. A full synth now emits zero deprecation warnings. -
Menu option
mis now real. It prints the model registry as a table — status, provider, thinking mode, prices, flags — and states which steps a registry edit requires and why: 2 (profiles, IAM, SSM map, preflights), 3 option 6 (registry to S3 for/api/models), 6 (agent role grants live in the Runtime stack). Previously it said "not yet implemented" and described models as living in the CDK stacks. -
destroy.shnow names what it leaves behind. Bedrock model-access subscriptions are account-level; teardown never touches them, and now says so.
- Three manifests referenced prompt files that do not exist.
classify_pdf_contentlistedclassification_tools.xmlandclassification_tool_rules.xml, which live only under the legacyprompts/pdf_processor/directory;decision_tree_specialistlisteddecision_tree_notes.xmlandtable_specialistlistedtable_notes.xml, which exist nowhere.PromptLoaderlogs a warning per missing file and carries on, so nothing failed, but every cold start of an enabled specialist logged it and the manifests claimed prompt content the model never saw. The four references are removed; runtime behaviour is unchanged. Every manifest'sprompt_filesnow resolves on disk. - The legacy
pdf_processorspecialist is removed. It had a manifest and a prompts directory (the only home of those twoclassification_tool*.xmlfiles) but no handler, no schema, and nodeployment_config.jsonentry, so it had never deployed. Its remaining footprints are gone with it: thespecialist_defaultsrow inpricing_config.json, the "PDF Processor" entry in the Editor tab's category map, its membership in the pricing calculator's default selection, and_validate_pdf_processor_configinconfiguration_manager.py, whose four checks applied to keys only that manifest carried. 26 built-in manifests remain. - Job tracking was silently disabled in every deployed environment.
foundation/__init__.pyeagerly re-exported every submodule, sofrom foundation import job_statein the orchestrator dragged inimage_processor, which imports Pillow at module scope — and Pillow is deliberately absent from the agent container. The caller catchesImportErrorto degrade gracefully, so nojob_idwas ever minted and every specialist requiring one, includinghtml_report_specialist, failed. The import error is now captured and named in the warning rather than blamed on the build script. - Rebuilt agent images never went live. AgentCore runtime versions are immutable and
CfnRuntimeonly cuts a new version whencontainer_urichanges; with a fixed:websockettag, re-pushing left the CloudFormation property identical, so no update happened and the runtime kept serving the previously pinned digest. No module named 'config'cold-start failure in container Lambdas.build_container_lambdas.shskipped staginglayer/python/configinto theremediation_specialistbuild context, and neither it norimage_enhancercopied it in their Dockerfile.- The quota preflight aborted the deploy in any account large enough to paginate. The
AWS CLI applies a
--query length(...)per page when auto-paginating, solambda list-functionsin an account with 81 functions returned"50\n31". Bash arithmetic on a multi-line value is a syntax error, and underset -euo pipefailwith anERRtrap that aborted the whole run with a bare arithmetic error rather than any quota message. Counts are now summed withawk. Operators in large accounts who never saw quota checks will now see them — and may be legitimately blocked by real headroom failures the crash was masking. /api/chatcrashed withEACCESin the container. The server is copied to/app/serverwith noui/prefix, soPROJECT_ROOTresolved to/and the default log path landed on the read-only filesystem root. NewCHAT_LOGS_DIRoverride.- A new chat session threw
ENOENT—realpathSyncran on the log file, which does not exist yet for a new session. It now resolves the directory, which exists aftermkdir. - Assistant messages stopped rendering after the assistant-ui upgrade:
useAuiState()was called without a selector, so it returned the whole state rather than the message. NowuseAuiState((s) => s.message). - Gateway emitted no logs and no traces at all — see Added.
- Markdown tables rendered as one run-together line of pipes. react-markdown is
CommonMark-only, where tables do not exist;
remark-gfmis now wired in, bringing strikethrough and autolinks with it. - Attachment-only messages rendered as an empty bubble and sent an empty string. The
composer keeps typed text and attachment parts in separate arrays and never merges
them; a new
collectUserTextmerges both for rendering and for the request body. - Upload failures surfaced as an unhelpful JSON parse error when a proxy or crash
returned non-JSON. Non-2xx and missing-
s3Uricases are now reported distinctly. - The activity indicator could stick on after an abort or a thrown error; it is now
cleared in a
finally. - "New Session" in Chat did not reset the session. It swapped the session id in
place, leaving the thread, the composer's attachments, the activity indicator, the
analyzer pills, and
S3AttachmentAdapter.lastDocIdintact.lastDocIdis the one that mattered: carrying it into a new session attributed the next job to the previous document — thedoc_idmismatch that breaks report generation. The button now remounts the component, sinceuseLocalRuntimeowns the message store and no single call clears all of it. job_state.get_job_recordsnow reads withConsistentRead=True; the report Lambda reads job rows immediately after the specialists wrote them, and an eventually consistent read could miss records and produce an incomplete report.job_state.set_reportis guarded byattribute_exists(job_id)so anupdate_itemupsert cannot fabricate an ownerless job row, and is called only after bothreport.htmlandmanifest.jsonare durable — a pointer to a half-written report would list and then 404 on open.loadReportManifest's prefix-confinement check pushedundefinedforenhanced_image_key, which would have failed manifest validation on every pre-existing report. It is now confined only when present.image_enhancersetsContentType: image/jpegon upload. The report side additionally toleratesbinary/octet-streamandapplication/octet-stream— the S3 default when an uploader omits the header — and verifies JPEG identity by the SOI marker rather than trusting metadata, which had been silently dropping enhanced images.- The resource-policy preflight both under-counted BADGERS' own policies and misattributed one of them to an unknown third party, which is the opposite of useful in a report whose purpose is answering "which of these can I remove?".
step_ui_deploygained the completion guard it was missing, andmark_completemoved from before the rollout poll to after it — the step previously recorded success before the service was confirmed healthy.
/api/envdisclosed deployment configuration to unauthenticated callers. It was the load balancer's health check path and therefore necessarily unauthenticated, and the service defaults to public subnets and an internet-facing balancer — yet it returned the region, gateway id, and both bucket names. Bucket names occupy a global namespace and the deployment id embedded in them derives every other resource name in the deployment. It is replaced byGET /api/healthcheck, which returns a constant{"status":"ok"}. The ECS stack'shealth_check_pathwas updated to match./api/mewas removed. Identity now comes from claims in the ID token the OIDC authorization-code + PKCE flow already produced andoidc-client-tsalready validated (signature, issuer, audience, nonce). Asking the server to echo back claims the browser holds added a request and an endpoint without adding information. The client-side role still decides only which tabs render; every admin route enforcesrequireAdminserver-side, so a client claimingadminstill gets 403.- Branding moved to build-time config bundled by a Vite plugin, removing a synchronous file read that ran on every request to the one route reachable without a token.
- The Home "Environment" panel that displayed region, gateway id, and bucket names was removed. Every field was rendered as text and none was used by the browser, which never talks to S3, DynamoDB, or AgentCore directly.
- ASH (
--mode local, MEDIUM threshold) passes on every scanner: 0 actionable findings, from 7.multer2.2.0 → 2.4.0 closes GHSA-qfvm-cv95-jqjf, GHSA-wc9g-mqfw-jrwm, and GHSA-535w-7cp7-47q4 (all high) plus one low; the floor inui/package.jsonis raised to^2.3.0so a satisfied version cannot regress.qs6.15.3 → 6.16.0, transitive via express, closes GHSA-x5fp-wj9c-mxmx and GHSA-4mjr-xmp4-gh2g.npm audithad reported 0 on the same lock file; the advisories are in GitHub's database, which grype reads.html_report_specialistparses correlation XML throughdefusedxmlwithforbid_dtdinstead ofxml.etree(bandit B314); the existing pre-check that rejects<!DOCTYPEand<!ENTITY>stays, so the parser enforces what the string check already refused.defusedxmlis added to the foundation layer'srequirements.txt. The remaining finding was in.archive/, gitignored scratch; the local ASH config (.ash/, also gitignored) now excludes it.
- 28 Python dependencies nothing imports or invokes. An AST scan of every
.pyfile outside virtual environments, plus a grep of scripts, Dockerfiles, and docs for CLI use, found no consumer forgradio,gradio_pdf/gradio-pdf,uvicorn,itsdangerous,pyjwt,python-dotenv,websockets,requests,pypdf,pdfreader,reportlab,markdown2docx,lxml,xmlschema,xmltoxsd,jpype1,pandas,pyvis,networkx,strands-agents-tools,aws-cdk-aws-bedrock-alpha,aws-cdk-mixins-preview,pip-audit,nbstripout,playwright,types-requests, atomlientry whosepython_version < '3.11'marker can never match underrequires-python >= 3.12, orsetuptools, which after the prune nothing in the tree required. Most date from the pre-2.5.0 Gradio application; the[tool.setuptools.packages.find]table that listed its packages (tools*,config*,server*,main*— none exist) is gone with them. Four duplicate entries (aws-cdk-lib,pymupdf,gradio-pdf,strands-agents) are collapsed to one each.pydanticandurllib3stay as documented transitive floors from 4.0.0. The lock file goes from 226 packages to 132; every import the Lambda, container, runtime, and CDK code makes still resolves, and a fullCDK_NAG=1synth is unchanged.pip-auditandnbstripoutare tools, not dependencies:uvx pip-auditanduv tool install nbstripoutprovide them without entering the project tree. deployment/requirements.txt. A second, stale dependency list — floors from aws-cdk-lib 2.150 and the deprecatedaws-cdk.aws-bedrock-agentcore-alphapackage — referenced only by the Manual Deployment step inDEPLOYMENT_README.md, which now saysuv sync. Each Lambda, container, and runtime target keeps its own requirements file.
- Wizard-created specialists still skip job tracking, but no longer because the
generator is a stub. The generated schema declares only
session_id, where built-in schemas also declarejob_idanddoc_id— which is what the foundation layer stamps records from. This supersedes the stub-related entry under[2.5.0]. - Jobs recorded before
owner_subpropagation have no owner and are absent from the sparseowner-index, so their reports never list. This is the intended fail-closed outcome — a row whose owner cannot be established is not attributed to anybody — but it means historical runs surface no reports. - The enhanced copy counts against the same 150 MiB
_MAX_AGGREGATE_BYTESceiling, so a second image per page effectively halves the page budget on enhanced runs. Exceeding it is caught and logged, and the page simply carries no enhanced key. Resolved by the model migration — the two model preflights are wired intopreflight_model_access,preflight_model_invocation, andpreflight_ecs_slrare defined incommon.shbut never called from any entry point.BADGERS_DEFAULT_MODELSalso namesus.anthropic.claude-sonnet-4-5-20250514-v1:0, a date that appears nowhere else in the repository.deploy.shand take their list from the registry.preflight_ecs_slrremains uncalled.- Inference does not stay in the deployment Region. All eight models are invoked through
US geo cross-Region inference profiles, and In-Region inference is unsupported for every one
of them on
bedrock-runtime— so this is not a configuration choice. The Lambda role therefore grantsbedrock:InvokeModelwith a wildcarded Region field. Enumerating the destinations instead is possible but needs a deploy-timeGetInferenceProfilecall, because only 3 of the 9 model cards publish a destination-Region table. Seedeployment/DEPLOYMENT_README.md→ Inference Profiles and Regions. - The Gateway execution role's Logs and X-Ray statements were removed. Both were
Resource: "*"and neither had a caller — the Gateway's logs and traces are vended deliveries written bydelivery.logs.amazonaws.com, not by the role. If a Gateway code path does turn out to use the role for X-Ray, traces stop arriving silently; confirm a trace appears in X-Ray Transaction Search after the first chat message on a fresh deploy. Resolved — synthesized with live credentials on 2026-09-16: 8 rule evaluations, 7 compliant, 1 suppressed (BADGERS-Vpchas no measured cdk-nag state.vpc_stack.py'smax_azs=2resolves availability zones through a context lookup, which fails without live credentials, so cdk-nag never evaluates the stack.AwsSolutions-EC23on the endpoint security group, whose ingress rule uses the VPCCidrBlockintrinsic the rule cannot evaluate; the reason is on the resource). All 14 stacks now report 0 findings.- On resume,
step_runtime's guard tests onlyruntime_image_pushed, so a deployment that pushed the runtime image but never deployed the runtime is skipped rather than re-entered. The previous resume table checked both keys. - Editing
config/document_type_contexts.jsonin S3 does not take effect on warm Lambda containers, because the map is cached in a module global for the life of the execution environment. watermarkedis a validdocument_typein both the enhancer schema and manifest but has no entry indocument_type_contexts.jsonor the in-code fallback, so it silently resolves to empty context.
- Version numbering is realigned across the repository.
pyproject.tomlhad drifted to1.2.0while this file had reached2.5.0, and the two moved independently after that — the branch carriedpyproject2.0.0against a changelog claiming3.1.0. Both now read4.0.0. The 2.x range was consumed between February and April 2026 and[2.0.0]already denotes the February remediation container release, so this release moves forward to a free major rather than reusing a number.3.0.0and3.1.0were never merged tomainline; they ship for the first time alongside this entry and are left intact, since they document the specialist rename, job tracking, Cognito OIDC/PKCE, and the ECS Express Gateway migration in detail.
cdk-nagis capped below 3.x. 3.x changed the Aspect API and fails at synth against aws-cdk-lib 2.x withaspectApplication.aspect.visit is not a function.- Raised
mcp>=1.28.1,opencv-python>=4.13.0.92, andpydantic>=2.13.4.
- Merged the outstanding dependabot backlog from
mainline. pypdf 6.14.2, bedrock-agentcore 1.18.1, setuptools 83.0.0, pillow 12.3.0, cryptography 50.0.0, mcp 1.29.0, and the npm advisories covering vite, postcss, ip-address, body-parser, shell-quote, and concurrently.pip-auditandnpm auditboth report no known vulnerabilities. - The
torchadvisory is resolved by removing the dependency rather than pinning it.torch,torchvision,realesrgan,basicsr,spandrel, andsuper-imageare dropped along with the upscaling stack they supported. - npm fixes that landed on
mainlineunderlocal_testing/are carried intoui/. That directory was consolidated intoui/in 3.0.0, so the upstream edits applied to files this branch no longer has. Resolving the conflict as a deletion alone would have discarded the fixes; the equivalent floors were raised inui/package.jsoninstead, so a satisfied version cannot regress.
- Duplicate
constructsandjpype1entries in thepyproject.tomldependency list.
- BREAKING: stack names now include the
DEPLOYMENT_ID.BADGERS-{Name}-{suffix}becomesBADGERS-{Name}-{DEPLOYMENT_ID}-{suffix}, e.g.BADGERS-S3-a1b→BADGERS-S3-dev-a1b. Stacks deployed under the previous naming cannot be adopted or updated in place and must be torn down and redeployed.- The suffix alone was enough for uniqueness but not for identity: with suffix-only
names a mistyped
DEPLOYMENT_IDstill resolved real stacks, while every resource name derived from it pointed at something that did not exist. In one case that combination scheduled a live deployment's in-use KMS key for deletion while reporting success. - Tooling now reads a deployment's identity off the stack name, which removed the need to recover it by parsing a bucket name out of a stack output.
- The suffix alone was enough for uniqueness but not for identity: with suffix-only
names a mistyped
deploy.shno longer readsDEPLOYMENT_IDfrom the environment. It unsets the variable and always chooses interactively, listing every deployment in.deploy-state/— complete and in progress — newest activity first, with an option to start a new one. A value left exported in the shell could otherwise target another deployment silently. New ids are validated against^[a-z][a-z0-9-]{0,15}$, the rule S3 bucket naming imposes.destroy.shchooses its target interactively, discovered from CloudFormation rather than from.deploy-state/, because a state file can be deleted while the stacks are still live. An explicitly passedDEPLOYMENT_IDis validated and checked against what exists, and refused if nothing matches.
- Option 12, Resume — runs only outstanding steps, skipping completed ones before calling them so their "already complete, re-run?" prompts never fire. A step counts as complete only when every state key it writes is set, so a partially finished step is re-entered rather than skipped.
- A network-exposure prompt in step 8. ECS Express Mode derives the load balancer
scheme from the subnets it is given, and the first Express service in a VPC fixes that
scheme for the VPC. The prompt is asked up front for options 9 and 12 so the rest of the
run is unattended.
UI_PUBLIC_ACCESSanswers it without prompting; default is public. Previously the UI was always placed on private subnets, which produced an internal load balancer whose public URL resolved but never answered. UI_PUBLIC_ACCESSandvpc_stack.public_subnet_ids, matching the media-contracts toggle. Defaults totrue.- A full manual console teardown procedure in the deployment guide: the two resources
that must be removed before any stack, the 14-stack deletion order with per-stack notes,
why parallel deletion fails,
DELETE_FAILEDremediation, and the resources no teardown removes.
- Step 8 now forces the image rollout. The ECS stack pins a static image tag, so
pushing a new image to that tag left the template unchanged,
cdk deployreported no changes, and the service kept serving the old image. Step 8 now callsupdate-express-gateway-servicewith the container spec after thecdk deploy, as media-contracts does, then pollsrolloutState. - The UI task role was missing
bedrock-agentcore:InvokeAgentRuntimeWithWebSocketStream. The presigned WebSocket path authorizes against a different action thanInvokeAgentRuntime, so chat failed with a 403 at the handshake — before reaching the container, which is why nothing appeared in its logs. The permission was inherited from media-contracts, which invokes over HTTP. /api/envis exempt from the/api/auth middleware. It is the ALB health check path and the SPA reads it for branding before login, so a blanketrequireAuthmount made every health check 401 and the task was killed for failing them. Media-contracts applies auth per route and leaves this endpoint open; BADGERS took the health check path without the exemption.destroy.shuseddelete-serviceon an Express Gateway Service, which rejects it outright. It now usesdelete-express-gateway-service, which also removes the load balancer the service created.destroy.shprinted a success banner over a failed teardown. It now re-verifies after the VPC auto-fix and, if stacks remain, lists them, states that the KMS key was left alone, and exits non-zero.- KMS key deletion is gated on the teardown succeeding. It was previously scheduled
even when
cdk destroyhad failed and every stack was still standing. - The X-Ray decision is resolved before every
cdk deploy, not just in step 2.RuntimeWebSocketdepends on the XRay stack andcdk deployincludes a stack's dependencies, so deploying the Runtime or ECS stack directly reintroduced the stack and attempted a CloudWatch Logs resource policy against a non-adjustable quota of 10.preflight_xrayalso no longer resets an explicitly setBADGERS_SKIP_XRAY. build_container_lambdas.shwas called without its required argument in step 4, and needed the composite{id}-{suffix}to derive the right ECR repository.- The menu reported
exit 0for failed steps, having read$?after an intervening command. It now captures the step's status directly. - Step 8 marked itself complete on a rollout timeout. The poll loop could exhaust its
window without ever reaching
COMPLETEDand still record success. - A completed deployment was unreachable from the deploy menu. The chooser filtered to unfinished deployments, and starting a "new" one refuses an id that already has state, so there was no way to re-run a single step against a finished deployment.
- BREAKING: stack and resource names now follow the media-contracts (MC) convention.
Existing
badgers-*deployments cannot be adopted or updated in place — they must be torn down and redeployed under aDEPLOYMENT_ID.- Stack names are
BADGERS-{Name}-{suffix}, e.g.badgers-s3→BADGERS-S3-a1b - Resource names carry
{DEPLOYMENT_ID}-{suffix}, e.g.badgers-config-dev-a1b, and SSM parameters move under/badgers-dev-a1b/ app.pynow requiresDEPLOYMENT_IDandSTACK_SUFFIX(env or CDK context) and fails fast with usage rather than generating a random id, which previously made it easy to deploy a second copy of everything by accident- Because both parts are unique per deployment, several deployments can now coexist in one account and region
- The Gateway stack takes the Cognito stack name as a parameter instead of hardcoding
badgers-cognito-UserPoolId/-UserPoolClientId - AgentCore runtime and memory names normalise the composite id to underscores, since
their names must match
[a-zA-Z][a-zA-Z0-9_]{0,47}and cannot contain hyphens
- Stack names are
- Deployment is now driven by
./deploy.shand./destroy.shat the repository root, replacingdeploy_from_scratch.sh,resume_deploy.shanddeployment/destroy.shdeploy.shis an interactive menu with eight steps, also runnable non-interactively as./deploy.sh N. Step completion and timestamps are recorded in.deploy-state/{DEPLOYMENT_ID}.json, so a re-run after a failure resumes instead of starting over — this replaces the separate resume script, which took a step number and had no record of what had actually completedBADGERS_ASSUME_YES=1answers every confirmation, for use without a terminaldestroy.shrequires typing theDEPLOYMENT_ID, empties the buckets, and deletes the ECS service and AgentCore runtime before the VPC, polling until their ENIs release. CloudFormation cannot delete a VPC while an ENI is attached, and both release theirs asynchronously after reporting gone — the previous script did neither and would strand the VPC. It also schedules the KMS key for deletion on a 7-day window so the alias is freed for redeployment, verifies deletion, auto-fixes aDELETE_FAILEDVPC via--retain-resources, and offers--vpc-cleanup-onlyfor a stranded VPC- New
deployment/scripts/common.shholds the shared logging, state, stack-name and CDK helpers that every script sources
- The admin Stacks tab listed a
frontendstack that no longer exists, omitteddynamodb,ecsandcustom-specialists, and described the VPC as serving an ALB and Fargate. It is now suffix-aware, receivingDEPLOYMENT_IDandSTACK_SUFFIXfrom the ECS container environment. - The UI's Deploy All button called
/api/deploy-test, an endpoint that never existed, so it always returned 404. It now calls/api/deploy-all. /api/stacks/:stackId/outputsreturned a 404 message that interpolated a function rather than the stack name, and the deploy/destroy routes accepted arbitrary stack ids. Both now validate against the known stack list.s3_stackpublished a global/badgers/config-bucket-nameSSM parameter for the agent runtime to discover. With per-deployment naming a second deployment would overwrite it and point the first deployment's agent at the wrong bucket. The parameter is gone and the runtime receivesCONFIG_BUCKET_NAMEdirectly as a container environment variable.sync_custom_specialists.shread../frontend/.env, a path removed in 2.5.0.
- BREAKING: "analyzer" is now "specialist" across the entire codebase. Aligns BADGERS
with the media-contracts (MC) reference implementation. Case-aware rename across 189
tracked text files plus 306 path renames:
- 22 Lambda specialist directories under
deployment/lambdas/code/ - The remediation container directory and its utils
- Prompt, manifest, and schema directories and files under
s3_files/ - The
custom_specialiststree, includingspecialist_registry.json - Manifest key
analyzer→specialist;metadata.analyzer_type→metadata.specialist_type - Env var
ANALYZER_NAME→SPECIALIST_NAME AnalyzerFoundation→SpecialistFoundation(analyzer_foundation.py→specialist_foundation.py)- Route
/api/analyzers→/api/specialists - Scripts
deploy_analyzer.sh→deploy_specialist.sh,deploy_custom_analyzers.sh→deploy_custom_specialists.sh - Stack
custom_analyzers_stack.py→custom_specialists_stack.py - Doc
LAMBDA_ANALYZERS.md→LAMBDA_SPECIALISTS.md
- 22 Lambda specialist directories under
- UI moved from
local_testing/anddeployment/ui/into a singleui/directory, serving both the testing workbench and the deployment console from one Express server (port 7860, Vite dev server on 5175) - UI infrastructure adopts the MC pattern: the self-managed ALB + Fargate + ACM + Route 53
badgers-frontendstack is replaced bybadgers-ecs, an ECS Express Gateway service that provisions and manages its own load balancer and HTTPS endpoint. No hosted zone, domain, or ACM certificate is required. - All UI container configuration now comes from SSM Parameter Store via
valueFromreferences — no plaintext configuration in the service definition or deploy scripts - Specialists now fail loudly when an analysis result cannot be persisted, instead of returning a result the caller cannot retrieve
- Fixed a latent suffix-strip bug surfaced by the rename: the Gateway target builder
stripped a hardcoded 9 characters (
_analyzer), which would have truncated names once the suffix became the 11-character_specialist
- Job tracking — a
doc_id→job_id→subtask_idhierarchy recorded in DynamoDB- New
badgers-dynamodbstack: single jobs table, on-demand billing, 30-day TTL, point-in-time recovery, plusstatus-indexanddoc-indexGSIs - New
foundation/job_state.py, the single writer for job records. Never raises, and no-ops entirely whenJOBS_TABLE_NAMEis unset, so tracking cannot break an analysis and stays opt-in per deployment - All 26 specialist Lambdas record their own subtask state:
PENDING → RUNNING → COMPLETE | FAILED, with the S3 output key on success and the failure reason on error - Subtask keys are
{specialist}#{image_identifier}, deterministic so the per-page fan-out stays unique and retries are idempotent job_idanddoc_iddeclared as optional parameters on all 26 tool schemas- The Runtime agent mints
job_idon the first specialist call of a turn and stampsjob_id/doc_idinto tool inputs via a StrandsBeforeToolCallEventhook, rather than asking the model to carry identifiers doc_idminted per upload and threaded from the browser through/api/chatto the agentGET /api/jobs/:jobIdandGET /api/jobs?doc_id=— job status is computed at read time by aggregating subtasks, so a run with 9 of 10 pages succeeding reportsPARTIAL
- New
- Cognito OIDC + PKCE authentication for the UI, replacing ALB-injected
x-amzn-oidc-dataheaders, which the ECS Express Gateway service does not provide- One user pool with two app clients: a public UI client (
openid email profile) and the existing machine-to-machine Gateway client - Managed Login v2 with explicit branding, plus
adminandtestergroups - Tokens verified against the user pool JWKS in
ui/server/auth.js; every/api/*route sits behindrequireAuth deployment/scripts/generate_ui_env.shwrites the build-timeVITE_*values
- One user pool with two app clients: a public UI client (
- New
badgers-vpcstack: private subnets, VPC Flow Logs, and S3/DynamoDB gateway plus Bedrock/SSM/Secrets Manager interface endpoints for the UI service - New
badgers-xraystack enabling X-Ray Transaction Search for AgentCore tracing
- Documentation updated for the rename, the new infrastructure, and job tracking. Removes
the stale
badgers-frontend/frontend_config.jsondeployment instructions, corrects the stack list from 10 to 13 (plus 1 optional), drops the nonexistentaccessibility_specialistfrom the README specialist table, adds the missinghandwriting_math_specialist, corrects the tool count from 25 to 26, and documents the teardown order the UI's VPC exports require
deploy_frontend.sh,destroy_frontend.sh, andfrontend_config.example.json— leftovers from the previous self-managed ALB + Fargate design, all referencing the deletedbadgers-frontendstack.deploy_frontend.shfailed outright;destroy_frontend.shwas worse, skipping the missing stack and then failing to deletebadgers-vpc(whose exportsbadgers-ecsconsumes) while swallowing the error and reporting success. Deploy and tear downbadgers-vpc/badgers-ecsdirectly.- The
deployment/frontend_config.jsongitignore entry, now that nothing reads it
update_frontend_env.shwrote to../frontend/config/.env, a directory removed back in 2.5.0. Withset -eit exited 1 on every run, and bothdeploy_from_scratch.shandresume_deploy.shswallowed the failure with a warning. Now targetsui/config/.envand also writesJOBS_TABLE_NAME, without which the local/api/jobsendpoints return 503.
- The specialist creation wizard (
/api/wizard/generate) is a stub and does not emitjob_id/doc_idon generated schemas, so custom specialists skip job tracking. build_container_lambdas.shcopies the foundation module from the generatedlayer/directory, sobuild_foundation_layer.shmust run first or container images ship stale foundation code.deploy_from_scratch.shanddestroy.shdo not namebadgers-vpc,badgers-ecs, orbadgers-dynamodb. Deploy resolves the jobs table automatically as a dependency ofbadgers-iam, butdestroy.shuses--exclusivelyand leaves all three standing.
- Local Testing UI (
local_testing/) — React + Express app replacing the Gradio frontend- 8-tab interface: Home, Chat, Edit Analyzer, Create Analyzer, Evaluations, Pricing, Observability, Chat Log
- WebSocket proxy to AgentCore Runtime for interactive chat
- SSE streaming for long-running operations
- Vite dev server (port 5174) + Express API (port 3457)
- Deployment UI (
deployment/ui/) — React + Express app for CDK stack management- 4-tab interface: Stacks, Analyzers, S3 Configs, Deploy Tags
- Deploy/destroy individual CDK stacks with streaming log output
- S3 config file editor for manifests, prompts, and schemas
- Vite dev server (port 5173) + Express API (port 3456)
ui/UI_README.md— documentation for the BADGERS UI
- Gradio-based frontend (
frontend/) replaced bylocal_testing/anddeployment/ui/
- Updated navigation bars across all 10 README files to replace
frontend/links with Local Testing and Deployment UI links - Updated inline references to Analyzer Creation Wizard in deployment and Lambda analyzer docs
- Updated project structure section in main README to reflect
local_testing/replacingfrontend/
- Increased all timeout configurations from 300s to 900s to support 10+ minute agent runs (#42)
- Lambda function timeouts (Duration.seconds), BEDROCK_READ_TIMEOUT env var, MCP server timeout
- Bedrock client connect_timeout raised from 10s to 30s
- WebSocket ping_timeout raised to 90s, close_timeout to 30s for long-running stability
- Frontend AGENTCORE_READ_TIMEOUT default raised to 900s
- Prompt generator boto3 read_timeout raised to 900s
- Added operating environment configuration value for agent context (#47)
- Poppler-qpdf Lambda layer with fontconfig for improved PDF text extraction (#40)
- PDF syntax repair pre-processing step in remediation pipeline (#25)
- Configurable
RESOLVER_MAX_TOKENSenv var for remediation analyzer - New pricing models and analyzer defaults in pricing calculator
- Path injection vulnerabilities in chat download functions (CodeQL py/path-injection)
- Remediated CodeQL alerts #11, #12, #18, #19 — path traversal in
agent_chat_websocket.py
- Bumped requests from 2.32.5 to 2.33.0
- Bumped pypdf from 6.8.0 to 6.9.2
- Bumped pymupdf from 1.26.6 to 1.26.7
- Bumped pyjwt from 2.10.1 to 2.12.0
- Dynamic token estimation based on image complexity (#15)
- Complexity scorer using text ratio, entropy, edge density, color std
- Token usage vs budget logging for calibration (#15)
- Dynamic token toggle checkbox in Gradio chat UI
- Sonnet 4.6 application inference profile for image enhancer
- Dynamic token estimation docs in README and Lambda Analyzers docs
- Container stack adjustments
- 21 Lambda handlers updated to support dynamic tokens env var from request payload
- Bumped pypdf from 6.7.1 to 6.7.5
- Bumped gradio from 6.3.0 to 6.7.0
- Cell grid resolver v3 for remediation analyzer with improved table detection
- Diagnostic visualizer for remediation analyzer output inspection
ENABLE_DIAGNOSTICSenvironment variable for remediation analyzer Lambda- Claude Opus 4.6 inference profile support
- Remediation analyzer README moved to
REMEDIATION_README.md - Updated README analyzer count from 29 to 25 (accurate Lambda function count)
- Updated remediation analyzer description to reflect container architecture and new capabilities
- Increased font size in remediation analyzer for improved analysis
- CDK IAM policies and manifest schema for remediation analyzer
- Remediation analyzer credential threading, image sizing, and CJK font encoding
- Acrobat accessibility report and screen reader video for remediation analyzer
- Updated README to v2.1
- Image enhancement tool updates
- Remediation analyzer v2.0 with container + layer architecture (moved from code-based to ECR container)
- PDF accessibility auditor, tagger, and models modules
- Container build script and Dockerfile for remediation analyzer
- Remediation analyzer container missing required Python modules and dependencies (#9)
- Hard coded klayers and Pillow ARN regions now uses
Stack.of(self).region(#8)
- PDF remediation adjustments
- Initial codebase clean-up
- Bumped Pillow from 11.3.0 to 12.1.1
- Initial commit with 25 Lambda analyzer functions (23 code-based + 2 container-based)
- Strands Agent with AgentCore Runtime and Gateway
- CDK deployment (10 CloudFormation stacks)
- Multi-page Gradio frontend with chat, wizard, editor
- Foundation layer shared across all analyzers
- Modular XML prompting system
- Inference profiles for cost tracking