Skip to content

Release/1.10.0 - #715

Merged
philmerrell merged 1734 commits into
mainfrom
release/1.10.0
Jul 21, 2026
Merged

Release/1.10.0#715
philmerrell merged 1734 commits into
mainfrom
release/1.10.0

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

v1.10.0

Feature release adding Excel spreadsheets to chat and fixing the CSP gap that blocked every MCP App iframe on deployed environments. Full narrative in RELEASE_NOTES.md; factual log in CHANGELOG.md.

What ships

  • 🚀 Excel spreadsheet toolset (feat(office-tools): add Excel spreadsheet toolset #709) — create/modify/list/read_excel_spreadsheet build and edit .xlsx via openpyxl in the sandboxed Code Interpreter; files persist to the user-files bucket and appear in the chat Files panel. One catalog toggle ("Excel Spreadsheets", gate key create_excel_spreadsheet, off by default) provisions the set. Word tools refactored onto the new shared office/_storage.py; generic file-download renderer replaces the Word-specific one.
  • 🐛 MCP Apps CSP fix (fix(infra): allow the mcp-sandbox origin in the SPA frame-src CSP #714) — the SPA distribution's frame-src never included the mcp-sandbox.{domain} origin, so domained deploys CSP-blocked every MCP App iframe (localhost bypasses CloudFront headers, masking it). PlatformStack now threads the sandbox proxy origin into SpaDistributionConstruct, with a synth-time regression test.

Version bump

Minor (1.9.01.10.0) — new user-facing capability. VERSION + all manifests synced (sync-version.sh --check passes), lockfiles regenerated.

Deployment

  • platform.yml required — SPA CloudFront ResponseHeadersPolicy update (CSP change). Then backend.ymlfrontend-deploy.yml as usual.
  • Environments seeded before this release need the "Excel Spreadsheets" catalog entry added/enabled + RBAC-granted.
  • No data migration, no new AWS resources, no dependency changes.

After merge

Squash-merge, then backmerge maindevelop (merge commit, not squash).

🤖 Generated with Claude Code

philmerrell and others added 30 commits July 7, 2026 12:30
…pace-export-zip

docs(memory): full-ownership zip export of a Memory Space
Adds the F5 Memory Space primitive's data layer — no runtime wiring,
gated by MEMORY_SPACES_ENABLED (default off).

Backend (apis/shared/memory/):
- store.py: S3 content-addressed byte store (sibling of the skills store)
- models.py: MemorySpace / MemoryIndex / MemoryEntryRef / SpaceMember
- templates.py: Blank / Chief-of-Staff / Research-Notebook presets
- repository.py: dedicated memory-spaces table CRUD (META/INDEX/MEMBER
  rows, OwnerIndex + MemberIndex GSIs, Decimal handling)
- service.py: permission-gated lifecycle + sharing + entry/index I/O,
  resolve_permission chokepoint (viewer reads, editor writes, owner
  shares/deletes), content-addressed writes with GC-on-replace
- feature_flags.py: memory_spaces_enabled() (default off)
- 47 moto-backed tests; import boundaries clean

Infrastructure:
- MemorySpacesConstruct: S3 bucket + dedicated memory-spaces DynamoDB
  table (OwnerIndex/MemberIndex GSIs), a per-domain table matching the
  project's actual pattern
- Threaded via PlatformComputeRefs to both compute roles (readwrite S3 +
  DynamoDB); env vars S3_MEMORY_SPACES_BUCKET_NAME /
  DYNAMODB_MEMORY_SPACES_TABLE_NAME / MEMORY_SPACES_ENABLED
- config.ts flag default-off; resource-count assertions updated
- tsc + 429 jest tests pass

Spec updated to reflect the dedicated-table + two-GSI decisions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…paces-pr1-data-layer

feat(memory): Memory Spaces data layer (PR-1)
…orkstreams

Splits the plan into two workstreams to keep the Memory Space a clean
bindable primitive: Workstream A (this epic) delivers the primitive + the
user-facing "own your data" surface (data layer, app-api CRUD, export,
sharing, SPA panel, consolidation); Workstream B (Agent/Harness layer)
delivers agent-consumption — the memory_* tools, declarative binding, and
system-prompt index injection — so any run surface can bind the same
primitive rather than welding it to inference-api.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eslice-phasing

docs(memory): re-slice phasing — primitive vs. agent-consumption workstreams
Workstream A2 of the re-sliced memory epic: the user-facing "own your data"
surface over the Memory Space primitive. No agent-consumption (tools /
binding / prompt injection) — that's the Agent/Harness workstream.

app-api (apis/app_api/memory_spaces/):
- routes.py: /memory/spaces CRUD over MemorySpaceService — list (with
  templates + accurate per-space role), create-from-template, get (index +
  entry manifest), delete-or-leave, entry read/list/upsert/delete, index
  read/update. Sync handlers (FastAPI threadpools the sync boto3 service).
- Gated by require_memory_spaces_user: 404 while MEMORY_SPACES_ENABLED off
  (surface behaves as unmounted); cookie auth via get_current_user_from_session.
- Service errors translated NotFound->404, Permission->403, Error->400.
- models.py: camelCase request/response models.
- Mounted before the existing /memory (AgentCore Memory) router; paths are
  non-overlapping (/memory/spaces vs /memory/{record_id}).

shared service:
- leave_space(): a member drops their own grant (the shared-in forget-me
  case); owner cannot leave.
- list_spaces_for_user() now returns (space, role) so shared-in spaces carry
  the member's real viewer/editor grant (only consumer is the new route).

Tests: 12 route tests (moto-backed real service, flag gate, CRUD, 403/404,
member-leaves-via-delete) + leave_space service tests. Full memory suite +
import boundaries green (69 passing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…2-app-api-surface

feat(memory): Memory Spaces user surface — /memory/spaces CRUD (A2)
The "own your data" leg of Workstream A: a loss-free `.zip` download of a
space's raw markdown (§9). `MemorySpaceService.export_space` gathers the
corpus once (index + every entry's bytes) behind the viewer+ permission
gate, including the member grant list only for editor+ callers (mirrors
`list_members`). The app-api route builds the archive in a
`SpooledTemporaryFile` — spilling to disk beyond 8 MiB so a large space
never pins memory — and streams it back.

Zip mirrors the S3 layout (`{name}/MEMORY.md`, `entries/<type>/<slug>.md`,
`metadata.json`) so it is self-contained and re-importable later. Archive
path components are sanitized against zip-slip. Route tests cover layout,
verbatim frontmatter, owner-vs-viewer member disclosure, 403/404/flag-off,
and the hostile-slug case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…3-export-zip

feat(memory): Memory Space zip export — /memory/spaces/{id}/export (A3)
…(A4)

The sharing leg of Workstream A, plus the concurrency guarantee that makes
multi-editor spaces safe.

Sharing surface (app-api, over the existing service grant methods):
- GET    /memory/spaces/{id}/shares          list grants (editor+)
- POST   /memory/spaces/{id}/shares          grant viewer|editor (owner)
- PATCH  /memory/spaces/{id}/shares/{email}  change a grant's role (owner)
- DELETE /memory/spaces/{id}/shares/{email}  revoke (owner, idempotent)
New `MemorySpaceService.update_share` gives PATCH proper not-found semantics
and preserves the grant's original createdAt (distinct from share's upsert).

Optimistic manifest concurrency (the real design content):
- `MemorySpaceRepository.put_index(expected_version=…)` does a conditional
  DynamoDB write on the manifest `version`, raising the repository-local
  `OptimisticLockError` on a mismatch.
- `write_entry`/`delete_entry` route through a new `_mutate_index` helper: a
  bounded read-modify-conditional-write retry loop. Because an entry write
  touches a single slug, re-reading the fresh manifest and re-applying is safe;
  it converges on transient races and raises `MemorySpaceConcurrencyError`
  (→ 409) only on a sustained one. Behavior is unchanged for single-writer
  spaces.

Tests: 6 route tests (share CRUD, member gains access, non-owner 403, viewer
can't list, PATCH-unknown 404, owner-role 422) + 7 service tests (update_share
role/origin/owner-gate + version-increments, stale-write rejected, retry
converges, gives-up-after-max). 76 memory + import-boundary tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…4-sharing

feat(memory): Memory Space sharing + optimistic manifest concurrency (A4)
…rt (A5)

The user-facing "Memory" surface for the Memory Space primitive, under
frontend/ai.client/src/app/memory-spaces/. Makes A2–A4 visible to users.

- List page: owned + shared-in spaces as cards with role/template badges;
  per-card open, share (owner), download .zip, delete/leave. Empty, loading,
  error, and feature-unavailable states.
- Detail page: view/edit the MEMORY.md index (editor+) and the entry list;
  entries open in a dialog to view (viewer) or edit/create (editor+); delete
  per entry. Header carries share/download/delete-or-leave.
- Create-from-template dialog and a share dialog (add-by-email + per-row role
  + delta-on-save over the A4 /shares endpoints). Viewer access is read-only
  throughout; the share dialog fails soft for non-owners.
- Signal facade (MemorySpaceService) + thin API service mirror the
  assistants/schedules pattern. The nav entry rides a live accessible$ probe:
  a 404 (MEMORY_SPACES_ENABLED off) hides it, matching showSchedules.
- Routes memory-spaces + memory-spaces/:id (authGuard); redesign-tokens and
  @angular/cdk/dialog conventions throughout.

Facade spec (7 tests) green; dev build + tsc clean; sidenav specs still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rejection)

The A5 sidenav now injects MemorySpaceService and probes `loadSpaces()` in the
auth effect. The sidenav spec mocked ScheduleService but not the new service,
so the authenticated-probe test constructed the real MemorySpaceService, which
fired a real XHR to /memory/spaces (status 0, no backend); `loadSpaces` then
re-threw, surfacing as a Vitest "unhandled rejection" at suite level.

Mock MemorySpaceService exactly as ScheduleService is mocked, and add matching
coverage: probe fires once authenticated (not while unauthenticated) and the
`showMemorySpaces` nav gate resolves null→false, false→false, true→true.

Full suite: 1422 passed, 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…5-spa-panel

feat(memory): Memory Spaces SPA panel — list/detail/create/share/export (A5)
app-api owns the Memory Spaces CRUD surface (`/memory/spaces/*`) but its
container environment only set MEMORY_SPACES_ENABLED — never the table or
bucket names the service reads (DYNAMODB_MEMORY_SPACES_TABLE_NAME /
S3_MEMORY_SPACES_BUCKET_NAME). Without them the repository falls back to the
default "memory-spaces" table name, which doesn't exist, so every read throws
a boto3 ResourceNotFoundException that the centralized handler maps to a
502 "Upstream service error." (inference-api already sets the identical trio,
but per the service-boundary rule it isn't the one serving these routes.)

Thread `refs.memorySpacesTable`/`.memorySpacesBucket` through AppApiSsmParams
and emit both names next to the flag in buildAppApiEnvironment. Names are
always wired (read lazily); only MEMORY_SPACES_ENABLED gates route mounting,
so flipping the switch on later needs no env change. New unit test guards the
wiring; tsc + 431 infra jest tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fold the lesson from the app-api env-wiring fix into the cdk-infrastructure
skill so the next construct-author sees it while wiring, not after a 502.
Adds a "Cross-Construct References" subsection: set a resource's name env var
on every compute that reads it (one doesn't imply the other), the silent-502
failure mode (default-name fallback → ResourceNotFoundException → generic 502,
invisible to synth/CI), and the env-map test guard + service-boundary caveat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s-app-api-env-wiring

fix(memory): wire memory-spaces table/bucket names onto app-api
The safe, non-LLM slice of Workstream A6. `MemorySpaceService.consolidate`
(editor+) + `POST /memory/spaces/{id}/consolidate` → a `ConsolidationReport`.

Auto-fixes only storage hygiene: orphaned content-addressed objects — keys
under a space's prefix that no manifest entry or the index pointer references
(leaks from crashed/raced writes) — are GC'd (new `MemorySpaceStore.list_keys`
drives it). Everything that needs a judgment call is *reported, not mutated*:
- duplicate content across slugs (same content hash) — which slug survives is
  semantic, so it's flagged, never auto-merged;
- dead `[[slug]]` wikilinks in MEMORY.md — reported; opt-in `stripDeadLinks`
  unlinks them (they point nowhere) while preserving the surrounding prose;
- over-cap entry counts (`MEMORY_SPACE_INDEX_CAP`, default 200) — flagged, never
  auto-evicted.

This deliberately does not merge/evict/rewrite durable memory — that's deferred
to the LLM consolidation pass (Workstream B era), which extends this exact
`consolidate()` seam once agentic writes create real duplication/staleness to
act on. On-demand only for now; scheduler/threshold auto-run and SPA surfacing
are follow-ups.

Tests: 8 service (healthy report, orphan GC + skip, dup-report-no-merge,
dead-link report + strip-keeps-prose, over-cap flag, editor gate) + 4 route
(report shape, no-body, viewer 403, flag-off 404) + 3 store (list_keys prefix
scoping / empty / disabled). 104 memory + boundary tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…6-consolidation

feat(memory): deterministic consolidation health pass (A6)
Captures the strategy for the "Agent Designer" (Agent Harness Editor): a new
authoring surface that composes an Agent from RBAC-governed primitives
(instructions, model, KBs, tools, skills, Memory Spaces, + future), replacing
the term/feature "Assistant."

Locks the load-bearing decisions: own a primitive-agnostic Agent contract and
federate AgentCore Registry later rather than build on it (adopt-with-boundary
precedent); evolve the assistant store in place (no parallel table); a uniform
bindings[] model with the model as a governed single-select; RBAC = compose the
five existing per-primitive access checks (incl. ModelAccessService), not a new
system; design-time filter + run-time re-resolution per invoker with block-on-
missing v1; ship memory-consumption as a thin vertical slice before the full
Designer. Phasing 0–5 + later AWS federation; supersedes the memory spec's
"extend the Assistant" §B1 framing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…signer-spec

docs(agent): Agent Designer spec — unified primitive-binding surface
…dels

Phase 1 (PR-1) of the Agent Designer. Pure library, zero behavior change:
legacy Assistants read unchanged and no caller passes the new fields yet.

- AgentModelConfig (D3 governed single-select; field is model_settings/
  alias modelConfig to dodge pydantic's reserved model_config — R3)
- AgentBinding (open kind on read, KNOWN_BINDING_KINDS for request validation)
- optional model_settings + bindings on Assistant (additive)
- compat.effective_bindings/to_agent_view (D2): absent bindings synthesize a
  knowledge_base binding reffing the assistant id (KB's only stable identity,
  F4 deferred — R4); absent model maps to None, never fabricated (R1)
- Decimal-safe serialization for modelConfig.params floats

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1 (PR-2). The Agent fields now round-trip through the rag-assistants
store and are validated at write time by composing existing RBAC checks (D4).
Legacy clients are unaffected: the SPA sends none of the new fields and the
AssistantResponse surface is unchanged.

- service.create_assistant/update_assistant thread bindings + model_settings;
  to_ddb_safe on write / from_ddb on read so modelConfig.params floats survive
  DynamoDB (Decimal); explicit [] replaces bindings, absent leaves them untouched
- app_api/agents/services/binding_validation.py composes model access
  (ModelAccessService), memory resolve_permission (viewer+/editor+), the
  implicit-KB rejection, and inert shape-only checks for tool/skill (D4/D5)
- assistants POST/PUT validate then pass through; validation raises 4xx outside
  the create handler's generic except so it isn't masked as 500
- tests: validation matrix (incl. inert no-RBAC guarantee), persistence round
  trip, legacy no-field read

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…signer-phase1

feat(agents): Agent Designer Phase 1 — contract + binding persistence (foundation)
Phase 1 (PR-3). A governed Agent read/write surface over the evolved assistant
store: same shared service functions and identity-based access gates as
/assistants, but returning the Agent shape (compat.to_agent_view -> AgentResponse)
so callers see modelConfig + bindings. Legacy ids valid unchanged.

- feature_flags.agents_enabled(): AGENTS_API_ENABLED, default OFF (memory-spaces
  pattern) — surface 404s while off, ships incrementally, /assistants unaffected
- app_api/agents/routes.py: require_agents_enabled 404-gate; draft/create/list/
  get/update/delete + 4 shares endpoints, delegating to apis.shared.assistants
  service and reprojecting via to_agent_view; create/update run binding_validation
- AgentResponse/AgentsListResponse/AgentSharesResponse (agentId == assistantId)
- main.py mounts the router
- test-chat + document sub-routes deliberately excluded (would force a 2nd
  architecture import-boundary exception); list is owner+shared (public/pagination
  parity deferred to the Phase-4 Designer)
- tests: 404-gate, agentId/bindings projection, CRUD permission gating, shares

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…signer-agents-router

feat(agents): Agent Designer Phase 1 — /agents surface (dark)
Phase 1 (PR-4). Completes Phase 1 deployability: the /agents surface can now
be turned on per environment. No new AWS resources — the flag only gates
whether the routes 404 (the assistant store it reads is always present).

- config.ts: AgentsConfig { enabled }; CDK_AGENTS_API_ENABLED (default off,
  empty-string-safe) or an `agents.enabled` cdk.json context, mirroring
  memorySpaces exactly
- app-api-environment.ts: AGENTS_API_ENABLED env on app-api
- infra tests: default-off / opt-on assertion; mock-config default
- docs: agent-designer.md Phase-1 status (+ the two refinements and the
  Oliver-dogfood-gated-on-Phase-3 note); CHANGELOG [Unreleased]

The live Oliver dogfood (D6) is deliberately NOT included: it needs Phase 3
harness resolution + Memory Spaces deployed to the target env before a
memory_space binding resolves at invocation. Tracked as the Phase 3 payoff.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…signer-flag-plumbing

feat(agents): Agent Designer Phase 1 — AGENTS_API_ENABLED flag plumbing + docs
…se 3 PR-0)

Phase 3 harness resolution runs inside inference-api, so the runtime needs the
same flag the app-api surface got in #593. Default off, mirrors the app-api
wiring; without it the harness ignores Agent bindings entirely (today's behavior).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hase 3 PR-A)

The Harness now re-resolves an Agent's governed modelConfig against the INVOKING
user (D5) and applies it to model selection. Absent modelConfig ⇒ the model
resolves exactly as today; gated on AGENTS_API_ENABLED (off in all envs still).

- agent_binding_resolver.py: resolve_agent_invocation() checks the pinned model
  against AppRoleService.can_access_model for the invoker (R2 — same gate the
  harness uses elsewhere), returns a model_override or raises
  AgentBindingBlockedError. inference-api imports apis.shared only (boundary-safe)
- routes.py /invocations: resolve after assistant load, before the KB search;
  on block, stream a conversational stream_error via stream_conversational_message
  (D5 block-with-message, no silent downgrade). Override wins at model resolution;
  agent params sit beneath request params, still flowing through admin bounds/locks
- tests: allowed→override, denied→block (checked vs invoker), no-modelConfig→
  empty plan (no RBAC call); 57 existing inference/chat tests unchanged

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
philmerrell and others added 24 commits July 19, 2026 20:43
…ad the stable prefix

CacheConfig(strategy="auto") places exactly one message-level cachePoint;
when its lookup misses, nothing is read and the whole prefix re-writes at
the cache-write premium. One proven miss mode is structural: Anthropic's
cache lookback checks only ~20 content blocks behind the breakpoint, so a
wide parallel tool fan-out (18 parallel calls = ~38 new blocks) pushes the
previous checkpoint out of range — prod session aecd387d observed
cacheRead=0 / cacheWrite=134k mid-turn (~$0.34).

Now the request carries 3 of Bedrock's max-4 cachePoints:
- toolConfig tail via cache_tools="default"
- system tail via SystemContentBlock list with trailing cachePoint
  (built in AgentFactory; the cache_prompt config key is deprecated)
- last-user-message point via the existing auto strategy (which strips
  only message-level points, never system/tools ones)

Both new points are gated on ModelConfig.bedrock_cache_points_supported()
(mirrors Strands' _cache_strategy predicate) because unlike auto mode they
would be sent verbatim to non-Anthropic models and rejected.

Verified live on global.anthropic.claude-sonnet-5 via ConverseStream:
simulated message-level miss reads the 6.5k-token system+tools prefix from
cache (cacheRead=6497, cacheWrite=83) instead of re-writing it. CountTokens
accepts the cachePoint-bearing request, so context attribution is
unaffected. Position/budget test asserts exactly 3 cachePoints.

Upstream: strands-agents/harness-sdk#3348 proposes auto mode keep a
rolling pair of message cachePoints so fan-outs stay within the lookback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… call

A $1.60 prod conversation audit (session aecd387d) showed 75% of spend was
avoidable Bedrock prompt-cache re-writes, and diagnosing the causes took
hours of manual forensics against raw DynamoDB rows. This makes the whole
class measurable end to end:

- PrefixFingerprintHook (BeforeModelCallEvent) hashes the three cacheable
  prefix components per model call — toolConfig (order-sensitive canonical
  JSON), effective system prompt (captured after AgentSkills injection),
  and message history excluding the newest message. The stream coordinator
  persists entry N on the turn's Nth assistant-message cost row, so a miss
  is diagnosable with a column diff instead of row forensics.
- Write-time cacheStatus per cost row (first_write | hit | miss_ttl_expired
  | miss_avoidable | uncached) derived from the session's previous C# row
  (one GSI read), plus wastedUsd for avoidable misses priced at the
  cache-write premium over cache-read from the row's own pricingSnapshot.
  Turn rows now write sequentially (was parallel) so each call classifies
  against its true predecessor.
- Session-row rollups next to totalCost: totalCacheReadTokens,
  totalCacheWriteTokens, avoidableMissCount, wastedUsd — cache-efficiency
  ratio for lists/admin without scanning cost rows.
- Admin cost anatomy: GET /admin/costs/sessions/{sessionId}/calls
  (require_admin) returns chronological per-call rows with token splits,
  cost, cacheStatus, and fingerprints, plus session-level cache summary.
- CloudWatch EMF per call (CacheReadTokens / CacheWriteTokens /
  AvoidableMiss / WastedUsd) via a raw-JSON stdout logger — dashboard and
  alarm on fleet cache-write share with no SDK calls or extra IAM.
- CI determinism guard: builds the chat-agent surface twice from shuffled
  skill/role/tool record orders (RBAC merge -> skills runtime -> Agent ->
  AgentSkills injection) and asserts identical system-prompt and toolConfig
  fingerprints; verified to fail when the skill-ordering sort is removed.

Complements the sorting + byte-stability fixes from
feature/prompt-cache-stability (merged in).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PROMPT_CACHE_OBSERVABILITY_ENABLED=false disables the fingerprint hook,
per-call cacheStatus derivation (and its GSI read), and EMF emission —
default ON per house convention; empty string stays enabled. Raw
cacheRead/cacheWrite token rollups are unaffected (usage passthrough,
not derived). CLAUDE.md now encodes the determinism + byte-stability
contract and the fingerprint-based debugging recipe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ache-stability

fix: preserve Bedrock prompt-cache hits across turns (deterministic ordering + byte-stable compaction)
…extarea-sizing

fix(chat-input): make the textarea scrollable and reset it after submit
- CLAUDE.md: add "token cost effectiveness is a design tenet" bullet under
  Key Conventions — prefix determinism / bounded per-turn payloads / verify
  via the #697 observability, balanced so quality wins on genuine conflict.
- kaizen review-queue: queue the two deferred #697 follow-ups —
  track harness-sdk#3348 (rolling-pair cachePoints; local workaround gated
  on dashboard evidence) and the ContextOffloader S3 adoption spike
  (with its four known gotchas).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
 follow-up)

New cross-service construct area lib/constructs/observability/ with a
PromptCacheObservabilityConstruct composed into PlatformStack. Graphs
the dimension-less EMF metrics both APIs emit into
AgentCoreStack/PromptCache (cache read/write tokens, a cache-efficiency
MathExpression, AvoidableMiss, WastedUsd) plus a Logs Insights widget
over the runtime log group grouped by cacheStatus.

Console-only alarms on AvoidableMiss and WastedUsd Sums (stricter in
prod, NOT_BREACHING on missing data so the
PROMPT_CACHE_OBSERVABILITY_ENABLED kill switch stays quiet). No SNS —
alerting infra is deliberately out of scope, matching kb-sync and
scheduled-runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Consumes GET /admin/costs/sessions/{id}/calls (backend PR #697):
- SessionCostAnatomy / SessionCallRow / PrefixFingerprints models + CacheStatus union
- AdminCostHttpService.getSessionCostAnatomy with URL-encoded session id
- New /admin/costs/sessions/:id route (lazy, component input binding)
- Drill-down page: summary rollups (total cost, cache efficiency incl. null,
  avoidable misses, wasted USD, cache read/write tokens), chronological calls
  table with color-coded cacheStatus badges, and prefix-fingerprint diffing
  that flags which hash (tools/system/history) flipped vs the previous
  fingerprinted call — the cache-buster diagnosis on miss_avoidable rows.
  Expandable rows show full hashes + messageCount; 404 renders a
  no-cost-rows empty state.
- Session-id lookup form on the Cost Analytics dashboard as the entry point
- Vitest specs for the diff util, HTTP method, and page states

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he-followup-breadcrumbs

chore: PR #697 follow-up breadcrumbs + token-cost-effectiveness tenet
…ache-dashboard

feat(observability): prompt-cache CloudWatch dashboard + alarms
…st-anatomy-page

feat(admin-costs): per-session cost-anatomy drill-down page
…d calls as miss_avoidable

When every prior call in a session was uncached (prompt below the
model's minimum cacheable prefix, e.g. ~4096 tokens on Claude Haiku 4.5),
the first call that crosses the threshold does cacheWrite>0/cacheRead=0
and was classified miss_avoidable — inflating the AvoidableMiss and
WastedUsd EMF metrics and the admin Session Cost Anatomy page. Verified
live in dev-ai session 9a1f25b2 (calls 1-5 uncached at 3.5-4k tokens,
call 6 falsely flagged with write=4122/read=0).

classify_cache_status now takes the previous call's cached-prefix token
total: when the immediately preceding call had zero cache activity there
was no entry to read from, so the write is classified first_write (the
expected initial population) and excluded from waste pricing. Unknown
(None) keeps the previous behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…voidable-below-threshold

fix(observability): first cache write after below-threshold calls is first_write, not miss_avoidable
The AgentCore Runtime env block set DYNAMODB_USER_FILES_TABLE_NAME but not S3_USER_FILES_BUCKET_NAME, so the Word-document tools' _user_files_bucket() fell back to the literal 'user-files' default and PutObject failed with AccessDenied (and earlier PermanentRedirect against that unrelated bucket). The runtime role's UserFilesBucketAccess already grants Get/Put/Delete/List on the real bucket; this just points the runtime at it. tsc build passes.
…er-files-bucket-env

Set S3_USER_FILES_BUCKET_NAME on inference-api runtime
_user_files_bucket() previously defaulted to a literal 'user-files' bucket when S3_USER_FILES_BUCKET_NAME was unset, which surfaced a missing runtime env var as a confusing S3 PermanentRedirect/AccessDenied. It now raises _StorageNotConfiguredError, and create/modify/read short-circuit before the Code Interpreter run with a clear 'storage is not configured' message.
…storage-config

Fail loudly when Word doc storage bucket is unconfigured
…nto-develop-1.9.0

Backmerge main into develop after 1.9.0
- Add excel_spreadsheet_tool.py with create/modify/list/read tools for .xlsx files
- Create office/_storage.py module with shared Code Interpreter and S3 storage utilities for Word and Excel
- Implement Excel toolset integration in inference_api chat routes with tool injection
- Add "Excel Spreadsheets" catalog entry to DEFAULT_TOOLS in seed_bootstrap_data.py
- Replace word-document-renderer with generic file-download-renderer for all generated office documents
- Extend Word document tool to use shared office storage module
- Update test fixtures and chat routes to support Excel tool provisioning
- Generated Excel files are persisted to S3_USER_FILES_BUCKET_NAME and appear in chat Files panel
…dsheet-tools

feat(office-tools): add Excel spreadsheet toolset
The SPA distribution's CloudFront ResponseHeadersPolicy only ever opened
frame-src to 'self' and the artifacts origin, so every domained deploy
CSP-blocked the MCP App sandbox iframe (mcp-sandbox.{domain}) — the
sandbox side's frame-ancestors was locked to the SPA origin from the
start, but the SPA side was never extended. Localhost dev bypasses
CloudFront's response headers entirely, which masked the gap through
live verification.

Thread the already-computed mcpSandboxProxyOrigin from PlatformStack
into SpaDistributionConstruct and append it to frame-src. Add a
regression test that synths a domained PlatformStack and asserts both
iframe origins are present in the frontend headers policy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…c-mcp-sandbox

fix(infra): allow the mcp-sandbox origin in the SPA frame-src CSP
Feature release adding Excel spreadsheet creation and editing to chat, plus the CSP fix that unblocks MCP App iframes on deployed environments.

- Excel spreadsheet toolset (create/modify/list/read .xlsx via openpyxl in Code Interpreter) behind a single "Excel Spreadsheets" catalog toggle; files land in the chat Files panel (#709)
- Shared office/_storage.py module for Word + Excel; generic file-download renderer replaces the Word-specific one (#709)
- SPA CloudFront CSP frame-src now includes the mcp-sandbox origin, fixing MCP App UIs blocked on every domained deploy (#714)
- CDK deploy required: SPA ResponseHeadersPolicy update via platform.yml

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@philmerrell
philmerrell requested a review from a team July 21, 2026 18:48
return {"content": [{"text": text}], "status": "error"}


_NO_CI_MESSAGE = (
return {"content": [{"text": text}], "status": "error"}


_NO_CI_MESSAGE = (
@philmerrell
philmerrell merged commit a13a482 into main Jul 21, 2026
11 checks passed
@philmerrell
philmerrell deleted the release/1.10.0 branch July 21, 2026 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants