Skip to content

Release/1.7.1#675

Merged
philmerrell merged 1661 commits into
mainfrom
release/1.7.1
Jul 17, 2026
Merged

Release/1.7.1#675
philmerrell merged 1661 commits into
mainfrom
release/1.7.1

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

Patch release. Cut from develop, targets main (squash-merge). Backmerge into develop follows once this merges.

What's in it

🐛 Fixed — Word-document PutObject PermanentRedirect (#674)
The user-files S3 client pinned its endpoint to https://s3.{AWS_REGION}.amazonaws.com, but the AgentCore Runtime's AWS_REGION doesn't reliably match the bucket region and the explicit endpoint_url disabled botocore's automatic region redirect — so Word-document saves failed with PermanentRedirect. The client now resolves the bucket's real region via HeadBucket (x-amz-bucket-region, backed by the s3:ListBucket the runtime role already has) and drops the hardcoded endpoint_url. Fixes both the save and the presigned download URL.

✨ Improved — static-sort-key write path for session metadata (issue #175 Phase 1b) (#673)
New sessions are born at a static sort key (S#{session_id} + SessionRecencyIndex keys) behind a real attribute_not_exists(PK) conditional put; legacy rows self-migrate in place on their next write. Rows no longer rotate on every message, so the ghost-row race behind the "Failed to parse session item" warnings is structurally eliminated for migrated rows, and the deterministic SK closes the first-turn duplicate-row race. delete_session resolves the raw SK via the GSI and soft-deletes in place.

Release mechanics

  • VERSION 1.7.0 → 1.7.1; sync-version.sh --check passes (manifests + lockfiles regenerated).
  • CHANGELOG.md + RELEASE_NOTES.md updated, new entry on top, previous entries untouched.

Deployment

Backend-only — ship through backend.yml. No CDK deploy, no data migration (rows self-migrate; readers already tolerate both schemes as of 1.7.0). No breaking changes.

🤖 Generated with Claude Code

philmerrell and others added 30 commits July 4, 2026 14:03
…-and-user-memory-specs

docs: add tool-search token-bloat + per-user markdown memory specs
A reauth pause was opaque — "credentials need re-consent" gave no hint
why. The vault token is keyed by (workload identity, userId), so the
overwhelmingly common cause is that the token was vaulted under a
DIFFERENT workload identity than the worker queries: e.g. a consent done
through local dev (AGENTCORE_RUNTIME_WORKLOAD_NAME=local_dev_inference)
can't be read by the deployed worker (platform-workload), and vice
versa. That exact mismatch cost hours to diagnose.

Surface the workload name, userId, and provider the lookup used in the
pause warning so the mismatch is obvious in one log line.

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

feat(kb-sync): log workload identity + userId on a reauth pause
KB sync ships dark: the EventBridge rule is created disabled and the
KB_SYNC_ENABLED kill switch is false unless CDK synths with
CDK_KB_SYNC_ENABLED=true (config.ts). platform.yml never forwarded that
var, so a CDK deploy always synthed the feature off — the only way to
enable it was an out-of-band CLI flip that reverts on the next deploy.

Forward it as an environment-scoped `${{ vars.CDK_KB_SYNC_ENABLED }}`
like every other CDK_ var. The development environment variable is set
to "true" (enables the rule + kill switch in dev-ai durably); production
leaves it unset, so it stays dark until validated there.

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

chore(kb-sync): plumb CDK_KB_SYNC_ENABLED into the platform deploy
KB sync shipped opt-in (default off), a holdover from ship-dark
incremental development. The feature is complete and guarded, so a
deployer/cloner of the public stack should get it working by default —
a hidden default-off flag is bad DX. Invert to opt-out: enabled unless
CDK_KB_SYNC_ENABLED=false (or a `kbSync.enabled: false` cdk.json context).

The workflow forwards `${{ vars.CDK_KB_SYNC_ENABLED }}`, which is an empty
string when unset — a plain `?? false` fallback wouldn't flip the default,
so config.ts treats empty/unset as "use the default (on)" and only the
literal "false" as the kill switch. Adds config-resolution tests covering
unset / empty / "true" / "false" / context-disable.

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

chore(kb-sync): default the feature ON with a kill switch
The knowledge-base sync control was a bare "Manual only" dropdown that
never explained what syncing does, and its status line only appeared
while a schedule was active — a manual-only file showed nothing.

- Lead the control with an "Auto-sync from <source>" label (with a
  descriptive title tooltip) so every row states what it is and where it
  pulls from.
- Rename the options to self-describing verbs: "Don't auto-sync",
  "Sync daily/weekly/monthly".
- Always surface a "Last synced" line, including manual-only sources, via
  a new lastSyncedAt input fed from Document.lastSyncedAt.
- Pair a relative age with an absolute date/time ("Synced 2h ago ·
  Jul 3, 2:14 PM") for both "how long ago" and "exactly when".

Display-only; the timestamps already exist in the data model.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aligns the knowledge-base sync control with the approved mockup and fixes
a bug that rendered the status line as a bare "Synced · next sync".

Layout:
- Move the "Auto-sync from <source>" context out of the control's button
  row (where it forced "Pause" to wrap) and into the file's meta line.
- Compact the control to just the schedule select + actions.
- Add a status-line dot (green healthy / amber attention / grey idle).
- Top-align the status icon and download/trash actions with the filename
  (items-start) instead of floating against the taller row.

Timestamp bug: several generators built ISO strings as
`datetime.isoformat() + "Z"`, yielding "…+00:00Z" (offset AND Z). That is
invalid ISO 8601 and parses to Invalid Date in strict engines (Safari),
so last-sync / next-sync rendered blank. Normalize to a single trailing Z
in service, dispatcher, and worker; harden the dispatcher parser to always
return a UTC-aware datetime; and harden the SPA to tolerate the legacy
"+00:00Z" so already-persisted policies still render.

Download and delete controls on documents are preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The web-source (crawl) row shared the sync control with documents but was
missing the parity bindings the document row got: no source-context line
and no persistent last-synced timestamp, so a manual web source rendered
as a bare dropdown while an identical document showed full context.

- Add the meta-line context "Auto-sync from the web" (guarded by
  isCrawlSyncable), mirroring the document's "Auto-sync from <provider>".
- Feed the control lastSyncedAt from crawl.completedAt so a manual web
  source still shows when it last refreshed (the component already prefers
  the policy's lastSyncAt when a schedule is active).
- Make the crawl meta line wrap-safe (flex-wrap), matching the doc row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Web sources list had no loading flag, so it popped into existence
after its network round-trip, while Uploaded Documents showed a lone
centered spinner — two async lists with inconsistent, jarring loads.

Introduce one initial-load gate for both lists:
- Add isLoadingCrawls, set from loadSyncData (cleared on the viewer
  early-return and in finally), mirroring isLoadingDocuments.
- isLoadingKnowledge computed gates both lists so they reveal together;
  raise both flags synchronously in ngOnInit so the first paint is the
  skeleton, not an empty flash.
- Replace the documents spinner with a skeleton list (pulsing icon + two
  text bars, varied widths) that mirrors the final row shape — no layout
  shift, and consistent with the existing connector-button skeleton.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Changing the schedule (e.g. Don't auto-sync → Sync weekly) held the busy
state for the whole create/update/delete round-trip but only disabled the
controls — there was no positive sign anything was happening, and the
select eagerly reverts to its old value until the mutation confirms, so it
read as "nothing happened".

While busy, the status slot now shows a spinner + "Saving…" (role=status),
taking precedence over the sync status line — including for a manual-only
source being enabled, where no policy or status exists yet.

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

fix(kb-sync): clarify the auto-sync control and repair blank sync timestamps
UserSyncService built timestamps as `datetime.isoformat() + "Z"`, yielding
"…+00:00Z" — both an offset AND a Z. That is invalid ISO 8601 and parses to
Invalid Date in strict engines (Safari), so the admin user-list "Last login"
and user-detail "Created"/"Last login" dates (rendered via `new Date()`)
showed "Never".

Same class of bug just fixed across KB-sync (service/dispatcher/worker);
this applies the identical normalization to the users domain.

- Write path: add `_iso()` helper, normalize `created_at`/`last_login_at`.
- Read path: add `_heal_iso()` and apply in `_item_to_profile` /
  `_item_to_list_item` so legacy "+00:00Z" rows render correctly. Necessary
  because `created_at` is preserved across logins forever (never rewritten),
  so pre-fix users would otherwise show "Never" in Safari permanently.
- Regression tests for the write-path invariant and read-path heal.

`last_login_at` also backs GSI2SK/GSI3SK (sort-by-last-login); the suffix
change is lexicographically safe — ordering is dominated by the microsecond
datetime prefix and a transient mix of "+00:00Z"/"Z" rows does not corrupt it.

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

fix(users): normalize sync timestamps to strict ISO 8601 (single Z)
…panel

Wrap the Knowledge base section on the Assistant editor in a rounded,
bordered inset with a subtle gray-100/60 fill instead of the flat
border-t divider shared by other sections. Against the gray-50 form
column this reads as a mildly contrasted group, and the white inner
lists (Web sources / Uploaded Documents) now float on the tint to
reinforce the grouping. Drops the border-t/pt-8 divider since the card
provides its own separation; the form's space-y-8 preserves the top gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Drive, Docs, Gmail, and Calendar icon assets under public/logos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Google connector logo SVGs (Drive, Docs, Gmail, Calendar).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… + spike brief

Reframe the proactive-agent effort from a single "Oliver" feature into a
primitive-enablement plan, mapped onto the Harness (headless run entrypoint)
and Registry (catalog + governance) explorations.

- agentic-platform-primitives.md: primitive maturity + gap ledger, the six
  fundamentals (F1 headless run entrypoint .. F6 registry/governance),
  phased plan, and Harness/Registry overlap. Oliver demoted to one
  validation use case among several.
- scheduled-agent-runs.md: detailed design for F1+F2+F3 (renamed from the
  earlier Oliver draft; generalized so any config/prompt/cadence works).
- harness-entrypoint-spike-brief.md: tight spike brief for the F1 keystone
  (unattended-as-user auth + server-side SSE), to run in dev-ai.

Resolved decisions: F1 = minimal internal run_agent_headless(), A2A-ready;
governance floor (F6a) pulled forward into Phase A, Registry discovery (F6b)
deferred. Open: KB decoupling appetite, floor depth, sequencing.

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

docs(specs): agentic platform primitives plan (+ scheduled-runs design + F1 spike brief)
…ev-ai

run_agent_headless in apis/shared/harness: per-owner Cognito bearer mint
(workload-token + SigV4 front-door paths proven dead at the runtime
gateway), server-side SSE drain pinned to live wire shapes, F6a audit
records + guardrails/classification seams, delivery via the runtime's own
session materialization + title override. Driver script reproduces the
dev-ai proof and the negative auth probes. Findings + Phase A design in
docs/specs/harness-entrypoint-spike-findings.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The headless-run entrypoint spike (Fable 5) is GO — proven end-to-end in
dev-ai. Bring its findings doc onto develop as the decision record and lock
the four gate decisions into the plan of record.

- Add harness-entrypoint-spike-findings.md (design deliverables + Phase A
  punch list; full spike code lives on branch spike/harness-headless-entrypoint).
- agentic-platform-primitives.md §6: resolve act-as-user auth policy (Cognito
  per-owner token behind an explicit headless-grant record), F6a floor depth
  (audit fail-closed now; PII checkpoint required before unattended schedules),
  KB decoupling (defer), sequencing (proactive-spine-first confirmed).
- scheduled-agent-runs.md §8: mark unattended-auth resolved with the chosen path.

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

Replace the spike's BFF-table Scan with an explicit headless-grant record
(apis/shared/harness/grants.py): create-on-enable from an attended session,
per-owner lookup via the sparse HeadlessGrantUserIndex GSI, revocation that
deletes the stored credential, and a documented "must have logged in within
30 days" policy (TTL anchored to the login that issued the pinned refresh
token, matching the Cognito refresh-token validity).

CognitoRefreshBearerAuth now mints from the grant (rotation-aware: a rotated
refresh token is persisted back before the mint returns — punch-list #5).
Dedupe build_invocations_url into the harness as the single canonical
resolver; the chat proxy imports it (punch-list #4). Document the
enabled_tools=None = all-RBAC-allowed semantic and the schedule-snapshot
rule (punch-list #7). Governance docstrings updated to the locked F6a
decision: audit-only fail-closed + wired no-op guardrail/classification
seams, implementations gated to the scheduled phase. Add an
_build_http_client seam and tests covering the grant lifecycle,
grant-backed minting, audit fail-closed ordering, and stream outcomes
against a MockTransport runtime.

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

POST /runs/now executes one agent turn through the exact unattended path a
scheduled run will use (create-on-enable grant -> per-owner Cognito mint ->
runtime /invocations -> server-side SSE drain -> governance floor -> session
materialization) — the PR-1 validation surface from
docs/specs/scheduled-agent-runs.md §7. GET/DELETE /runs/grant expose grant
status and total revocation.

Gating is two independent controls (spec §6): the SCHEDULED_RUNS_ENABLED
kill switch (default ON; only the literal "false" disables — empty workflow
vars can't dark-stop prod) and a new `scheduled-runs` RBAC capability
resolved through the mature tools grant axis
(apis/shared/rbac/capabilities.py) — GA = grant the id to the default role.
Auth is the standard SPA cookie dependency per the CLAUDE.md app-api rule;
mint failures surface as 409, never 401, so the SPA is not bounced through
the login redirect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Thread the scheduled-runs kill switch through CDK: scheduledRuns.enabled in
config.ts (the CDK_KB_SYNC_ENABLED empty-string-safe ternary, copied
exactly) -> SCHEDULED_RUNS_ENABLED on the app-api container ->
CDK_SCHEDULED_RUNS_ENABLED forwarded by platform.yml. Default ON with a
kill switch; nightly inherits the default.

Add the sparse HeadlessGrantUserIndex GSI (grant_user_id / created_at) to
the BFF sessions table backing apis/shared/harness/grants.py — only
HEADLESS-GRANT# items carry the partition attribute, so session rows never
project into it. App-api's existing table grant already covers index/*, so
no IAM change. Tests mirror the kbSync flag matrix and pin the GSI shape.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ke-findings-and-decisions

docs(specs): F1 spike findings + locked decision-gate calls
…phase-a

feat(harness): Phase A — headless run entrypoint + Run now (PR-1)
…isite

Records the work breakdown, model tiering, and the one design fork gating
scheduled delivery: the F6a classify_output checkpoint must run in-loop (or
as a post-hoc scrub), not pre-delivery, because the runtime turn persists
the session during the turn. B1 (inert schedule CRUD) is safe to start now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…0 PII gate

A headless run executes as the owner with the owner's RBAC and delivers
only to the owner's own session list, so it crosses no new access boundary
and introduces no new recipient. Governance = RBAC (run-as-user) + grant
lifecycle + quota + fail-closed audit — all already built. The content
classification pass adds nothing for deliver-to-self, so B0 collapses and
B2 delivery is unblocked. Revises primitives §6-4 and the Phase B brief §1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ScheduledPrompt model + ScheduledPromptService (apis/shared, mirroring
sync_policies) and app-api CRUD under /schedules — create/list/get/update/
pause/resume/delete, gated by SCHEDULED_RUNS_ENABLED + the scheduled-runs
RBAC capability. Cadence (daily/weekday/weekly) -> next_run_at is computed
timezone-aware in the service so the future dispatcher stays a dumb "who's
due" query. enabled_tools is snapshotted at creation (Phase A punch #7).

Storage rides the existing sessions-metadata table (PK=USER#{user_id},
SK=SCHEDPROMPT#{schedule_id}), with a new sparse DueScheduleIndex GSI
(GSI3_PK/GSI3_SK, distinct from SessionLookupIndex's GSI_PK/GSI_SK to avoid
attribute collision) projected only while state=="active". app-api already
holds CRUD+index/* IAM grants on this table, so no IAM changes were needed.

Deliberately inert: nothing fires yet. The dispatcher/worker that reads
DueScheduleIndex and calls run_agent_headless is B2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
philmerrell and others added 26 commits July 16, 2026 15:44
Resume turns (interrupt_responses set — OAuth-gated MCP consent or
tool-approval) crashed with `NameError: cannot access free variable
'effective_enabled_tools'`. The variable is referenced unconditionally by
the `stream_with_quota_warning` streaming closure (attachment guidance +
tabular inventory) but was only assigned in the non-resume branch. On
resume the closure raised before its first yield, the inference-api
container returned 500, and the AgentCore Runtime data plane translated
that into a 424 Failed Dependency to app-api and the SPA.

This broke every interrupt-resume turn since the agent-designer
tool-binding refactor (0b9b039) — most visibly "connect to Gmail for
employees", which completes via an OAuth-consent resume. Bind
effective_enabled_tools from the paused-turn snapshot on the resume
branch (the same source the resume get_agent call uses).

Adds a resume-path regression test to tests/routes/test_inference.py that
drives /invocations with interrupt_responses and asserts a 200 stream;
without the fix it fails with the NameError.

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

fix(agents): resolve model provider for agent-bound invocations
…tive-enabled-tools-nameerror

fix(inference): bind effective_enabled_tools on resume path (prod 424 on OAuth-gated MCP connect)
…nto-develop-1.6.1

Backmerge: main into develop (1.6.1)
…2026-07-10

chore(kaizen): weekly research scan 2026-07-10
Generated by the kaizen-research skill. Top 5 ideas appended to
docs/kaizen/review-queue.md for the kaizen-review-prep run later this morning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root-cause spec for the SessionMetadata parse-failure warnings: the session
row's sort key encodes lastMessageAt, forcing a delete+put row move every turn.
Concurrent writers race that move and upsert bare ghost rows. Also drives the
first-turn duplicate-row race.

Fix: static SK (S#{session_id}) + sparse SessionRecencyIndex GSI for recency
listing. Covers the expand -> migrate -> backfill -> contract migration,
downstream/forked-deployment safety (marker gate + graceful GSI-missing
fallback), pagination-token compatibility, and the test matrix.

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

Sparse recency index (GSI4_PK=USER#{id}, GSI4_SK={lastMessageAt}#{session_id},
projection ALL) for newest-first active-session listing once the base sort key
becomes static. Phase 0 of the static-sort-key migration: adding the index is a
no-op until rows populate GSI4 keys, so it deploys safely ahead of any code
change. IAM already covers it via the SessionsMetadataAccess /index/* wildcard.

Update tables-detailed test to assert all four GSIs (the "2 GSIs" title was
already stale after DueScheduleIndex) and the new index's key schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…metadata-static-sk-phase0

feat(infra): SessionRecencyIndex GSI on sessions-metadata (issue #175 Phase 0)
…Phase 1a)

Expand-read step of the static-sort-key migration. list_user_sessions now reads
the UNION of two disjoint sources so a session is visible whether or not its base
sort key has been migrated to the static S#{session_id} form:

- legacy (un-migrated): base table, SK begins_with 'S#ACTIVE#'
- migrated: SessionRecencyIndex GSI (GSI4_PK=USER#{id}, GSI4_SK={lastMessageAt}#{id})

Pagination switches to a value cursor ({lastMessageAt}#{session_id}) so each page
is derived independently from the last returned position, with no cross-page
buffering; fetching limit+1 valid rows per source is provably enough to detect a
next page. The cursor decoder is tolerant — legacy/undecodable tokens fall back to
first page (a harmless reset across the deploy boundary).

Degrades to legacy-only if SessionRecencyIndex doesn't exist yet (code ahead of the
CDK GSI): the GSI query's ResourceNotFoundException is caught.

No writes change and no row migrates in this phase — this only teaches every reader
to cope with both schemes, which must be fully rolled out before Phase 1b turns on
self-migrating writes.

Tests: union ordering, cross-union pagination (no dupes/gaps), migrated-only via
GSI, ghost/preview skip, and graceful fallback when the index is absent. conftest
sessions_metadata_table fixture gains the SessionRecencyIndex GSI to match prod.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…metadata-static-sk-phase1a

feat(sessions): dual-scheme union read for session listing (issue #175 Phase 1a)
Auto prompt caching (CacheConfig strategy=auto) appended its cachePoint
after the last user message's content, so any turn attaching a non-PDF
document (txt/docx/csv/...) sent [text, document, cachePoint] and
Bedrock's Anthropic adapter rejected it with "ValidationException ...
messages.N.content.M.type: Field required", surfacing to users as
"Agent force-stopped" (prod incidents Jul 14-16, e.g. session
dd1a647a on a .txt transcript upload).

strands 1.48.0 places the cache point before the first non-PDF document
block instead (upstream issue #1966); every placement it produces was
verified live against global.anthropic.claude-sonnet-4-6 ConverseStream.
Also corrects the model_config comment that credited PR #1438/1.39.0
with this fix - #1438 was the auto-caching feature itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ce-stop-validation-791e6f

fix(deps): bump strands-agents to 1.48.0 for cachePoint-attachment fix
…2026-07-17

chore(kaizen): weekly research scan 2026-07-17
… missing GSI (issue #175)

The Phase 1a dual-scheme read (PR #667) catches a missing SessionRecencyIndex to
fall back to legacy-only listing, but only handled ResourceNotFoundException — what
moto raises. Real DynamoDB raises ValidationException ("The table does not have the
specified index") for a missing GSI (verified against the prod table). So if the
1a backend deployed to an environment before the CDK GSI existed, list_user_sessions
would 503 instead of degrading.

Broaden the catch to also handle ValidationException (scoped by the "specified index"
message so genuinely malformed queries still surface). This restores the intended
order-independence: 1a is safe whether or not SessionRecencyIndex exists yet, which
matters for prod deploy ordering (backend.yml vs platform.yml) and for forked
deployments.

Add a test that reproduces the real ValidationException on the index query (moto
masks it), asserting fallback to legacy-only results.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Provision a full Word (.docx) toolset behind the single create_word_document capability toggle. Each tool runs python-docx in Bedrock Code Interpreter and uses the existing user-files store (S3 + DynamoDB) for persistence and delivery. - create/modify/list/read tools in agents/builtin_tools/word_document_tool.py, injected per-request via _build_word_document_tools (inference_api/chat/routes.py). - Frontend inline-visual 'word_document' renderer with an accessible download button (Tailwind utilities, no scoped CSS). - Restore-time content-block sanitizer in TurnBasedSessionManager: drops empty/typeless blocks from restored history that caused Bedrock ConverseStream 'messages.N.content.M.type: Field required'. - Seed create_word_document in bootstrap DEFAULT_TOOLS ('Word Documents') + updated seed tests.
…ument-tools

Add Word document tools (create/modify/list/read)
…-missing-index-fallback

fix(sessions): degrade to legacy-only on real ValidationException for missing GSI (issue #175)
…nto-develop-1.7.0

Backmerge: main into develop (1.7.0)
…rotation (issue #175 Phase 1b)

Turns on the write side of the static-sort-key migration. Sessions stop encoding
lastMessageAt in the sort key, so the row never moves and the ghost-row race that
produced "Failed to parse session item" warnings is structurally eliminated for
every migrated row.

Changed (all resolve the row via GSI, which is SK-scheme-agnostic):
- ensure_session_metadata_exists: new sessions born at static SK S#{id} + GSI4
  keys, with a real attribute_not_exists(PK) conditional put. The deterministic SK
  makes the guard meaningful, closing the first-turn duplicate-row race the old
  timestamped SK made impossible to gate.
- update_session_activity: drops the per-turn Phase-B rotation. Static rows update
  in place (SET GSI4_SK re-positions the sparse recency index — no row move); a
  still-legacy row does its one-time final rotation to the static SK, carrying any
  concurrent write.
- _store_session_metadata_cloud: static SK; migrate legacy->static on move; SET
  GSI4 for active, REMOVE for deleted; never un-migrates a static row.
- session_service.delete_session: resolves the raw SK via _get_session_by_gsi
  instead of reconstructing S#ACTIVE#{lastMessageAt}#{id} (which misses migrated
  rows). Non-rotating soft-delete: SET status=deleted + REMOVE GSI4 in place, or
  migrate a legacy row to a static tombstone. Drops the S#DELETED# prefix (nothing
  reads it).

The ~10 other writers resolve-then-update-in-place on the current SK and need no
change — they already work on a static SK and never rotate.

Tests: TestWriteSideMigration (born-static, one-time migrate, no rotation,
soft-delete in-place/legacy, end-to-end create->activity->list->delete) plus the
real ConditionalCheckFailedException contract (moto raises it for a failed
conditional put). Updated three tests that encoded the old rotation contract. Full
shared+routes suites: 1689 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…metadata-static-sk-phase1b

feat(sessions): static-SK write path — born static, self-migrate, no rotation (issue #175 Phase 1b)
The user-files S3 client pinned its endpoint to https://s3.{AWS_REGION}.amazonaws.com. In the AgentCore Runtime AWS_REGION does not reliably match the bucket region, and the explicit endpoint_url disables botocore's automatic S3 region redirect, so PutObject failed with PermanentRedirect. Resolve the bucket's real region via HeadBucket (x-amz-bucket-region header; maps to s3:ListBucket, which the runtime role already has — GetBucketLocation is not granted) and pin the client to it, dropping the hardcoded endpoint_url. Fixes both the save and the presigned download URL region.
…t-s3-region

Fix S3 PutObject PermanentRedirect in Word document tools
Patch release fixing a Word-document save failure in the AgentCore Runtime and
advancing the session-metadata static-sort-key migration (issue #175) to its
write side.

- Fix: Word-document PutObject no longer fails with PermanentRedirect — the
  user-files S3 client resolves the bucket's real region via HeadBucket instead
  of pinning to AWS_REGION, and drops the hardcoded endpoint_url (#674)
- Improved: static-sort-key write path for session metadata — new sessions born
  static, legacy rows self-migrate in place, no per-turn rotation; eliminates the
  ghost-row race and closes the first-turn duplicate-row race (issue #175 Phase 1b) (#673)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@philmerrell
philmerrell requested a review from a team July 17, 2026 22:19
@philmerrell
philmerrell merged commit a6ddf68 into main Jul 17, 2026
11 checks passed
@philmerrell
philmerrell deleted the release/1.7.1 branch July 17, 2026 22:49
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.

5 participants