Release/1.8.0#694
Merged
Merged
Conversation
B1's update_scheduled_prompt skipped None, so a PATCH could never detach a schedule's assistant or reset its tool restriction — the SPA's clear checkboxes were inert (a bare null reads as 'leave unchanged'). Add an explicit clear contract: an UNSET sentinel in the service distinguishes 'omitted' (leave) from None (clear -> REMOVE the attribute). UpdateScheduleRequest gains clearAssistant/clearTools booleans (rejected if combined with a value). clearAssistant reverts to the default agent; clearTools re-snapshots the caller's current RBAC-allowed tools, mirroring creation so a schedule never stores an unresolved None. SPA sends the flags. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The admin GET /admin/bedrock/models endpoint calls the Bedrock control
plane's ListFoundationModels, but the App API Fargate task role only had
bedrock:InvokeModel. In deployed environments the call raised
AccessDeniedException, which the app-wide AWS-error handler maps to a
generic 502 ("Upstream service error."). It only worked locally because
local dev runs with the developer's broader AWS credentials.
Add a BedrockListFoundationModels statement granting
bedrock:ListFoundationModels and bedrock:GetFoundationModel. These are
account-level list/read actions that do not support resource-level
permissions, so they are granted on `*`.
Requires a platform.yml (CDK) deploy to take effect.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on-inset-grouping feat(assistant-editor): group knowledge base into a contrasted inset panel
…ock-list-models-iam fix(app-api): grant bedrock:ListFoundationModels to task role
…d-runs-pr5a-hardening feat(schedules): PR-5a hardening — lean-image guard + clearable schedule fields
…26-07-03 chore(kaizen): weekly review prep 2026-07-03
…ged-harness-spike docs(kaizen): managed-Harness build-vs-adopt spike brief
A scheduled (unattended) run delivers a session the user wasn't watching.
Surface it: the sidebar shows an unread dot until they open it.
- sessions/metadata.py: set_session_unread / mark_session_read — a targeted
single-attribute UpdateExpression on the row's current SK (GSI-resolved),
concurrent-safe with the title/activity writes; preview-session guarded;
best-effort (never raises).
- harness/runner.py: mark the delivered session unread only on a *completed*
run with trigger == "schedule" — attended "Run now" and failed/consent-
blocked runs never set the dot (the user is present / there's nothing to
read).
- app_api POST /sessions/{id}/read: clears the durable flag when the user
opens the session; idempotent, ownership-enforced via the GSI lookup.
- SPA: durable server-persisted unread on SessionMetadata (survives reload,
reaches other devices) ORed with ChatStateService's ephemeral in-tab
unread for interactive background completions; session-list renders the dot.
Backend 108 + frontend 25 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-unread-scheduled-runs feat(sessions): unread indicator for scheduled-run deliveries
Phil-initiated kaizen focus: scope a future bedrock-runtime (Converse) -> bedrock-mantle endpoint migration. Watchlist/Defer — strategic alignment with where Bedrock capability lands first, not near-term need; Claude-on- Mantle is Messages-API-only today and lacks cross-region + native CountTokens + Guardrails, so the primary chat path stays on bedrock-runtime. Interim low-risk value = finishing the already-scaffolded non-Claude OpenAI-compatible Mantle lane. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…le-watchlist docs(kaizen): Bedrock Mantle endpoint watchlist item
…wered Complete the #570 build-vs-adopt spike for the headless/scheduled lane. Answered from the now-GA AWS managed Harness docs cross-checked against our code and the proven F1 entrypoint spike: - Q1 RBAC -> allowedTools: qualified yes (per-invoke globs; we already snapshot the RBAC-narrowed set statically at the app-api boundary). Non-membership gates relocate (quota/cost -> dispatcher; approval -> exclude on headless; consent -> Identity outbound). - Q2 per-user tokens: yes on mechanism (OAuth-inbound customJWTAuthorizer == our authorizer; Gateway outbound == our USER_FEDERATION exchange). SigV4 cannot do per-user identity. One residual: customParameters vault-key pinning through the Gateway-managed exchange -> live probe. - Q3 lose MCP Apps + SSE on headless: yes (interactive-only affordances; SPA loads the delivered session, not the harness stream). Recommendation: green-light a narrow InvokeHarness probe to close the Q2 residual; interactive inference-api untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hide the "Scheduled Runs" menu option from non-admins while the feature is still maturing. Adds an isAdmin() check to the existing capability gate, mirroring the admin-dashboard nav pattern. isAdmin is already wired into the sidenav component from UserService; showSchedules keeps its accessibility-probe behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d-runs-system-admin-only feat(sidenav): gate Scheduled Runs nav entry to system_admin
…ness-spike-findings docs(kaizen): managed-Harness spike findings — 3 gating questions answered
Records the live dev-ai probe (2026-07-06) that closes the Q2 customParameters residual from the spike findings. Confirmed live (real CreateHarness/InvokeHarness via boto3): our exact customJWTAuthorizer is accepted (harness READY); outboundAuth.oauth customParameters is a first-class field persisted verbatim on GetHarness (so we CAN pin the same params the consent flow uses); OAuth-inbound runs as the owner (HTTP 200); the exchange calls the same GetResourceOauth2Token our get_token_for_user uses; a failed exchange surfaces legibly as a typed runtimeClientError stream event (maps to paused_reauth). Boundary found: the managed Gateway 3LO (AUTHORIZATION_CODE) exchange fails with "must provide a ResourceOauth2ReturnUrl" and does not source that URL from defaultReturnUrl / OAuth2CallbackUrl header / workload AllowedResourceOauth2ReturnUrl — a GA wiring gap. Cross-workload token visibility (platform vs harness-own workload identity) unreached past it. Decision: GO to adopt Harness on the headless lane, but keep customParameters-sensitive / all 3LO connectors on our own get_token_for_user until the return-URL wiring is resolved with AWS. Aside: managed memory is on by default per harness (relevant to F5). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a "Mark as read / Mark as unread" toggle to the session options menu
in both the top nav and the sidebar session list, backed by a new
POST /sessions/{id}/unread endpoint (mark_session_unread) that mirrors the
existing /read verb. The client surfaces the dot instantly via the
ChatStateService unread signal while the durable server flag lands async.
Fixes two sidebar-only bugs where the in-row options trigger lives inside
the row's group, so the CDK menu restoring focus to it on close tripped
group-focus-within:
- Bug 1: the ellipsis trigger stayed visible after a menu action. Switch
its reveal from :focus / group-focus-within to :focus-visible, so
mouse-restored focus no longer reveals it while keyboard nav still does.
- Bug 2: a freshly marked-unread dot didn't appear until the next browser
interaction. The dot was being hidden by group-focus-within (restored
trigger focus); scope that to group-has-[button:focus-visible] instead,
and kick a synchronous refreshSessions() in the mark-unread branch
(mirroring mark-read) so the OnPush row re-renders immediately.
The top-nav toggle was unaffected because its trigger sits outside the row.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…read-unread-toggle feat(sessions): mark-as-read/unread toggle + sidebar dot fixes
…asts Two additions to the Scheduled Runs feature: - Interval cadence: schedules can now run every N minutes/hours in addition to daily/weekly. Adds interval_value/interval_unit to the scheduled-prompt model and API, a MIN_INTERVAL_MINUTES floor enforced on create/update/ resume, and interval_to_minutes() feeding compute_next_run_at. The schedule form gains the interval option with matching validation. - "Run now": run a schedule's prompt headlessly on demand from the schedule form. RunNowService fires POST /runs/now (existing app-api endpoint) as fire-and-forget and reports progress through a new app-wide toast system — BackgroundTaskService (signal-backed task list) rendered by the BackgroundTaskToastsComponent mounted in app.html. On completion the run materializes as a real session; the list is refreshed and the toast offers a "View" affordance. Tests: backend test_schedules_routes / test_scheduled_prompts / test_harness_runner (100 passed); frontend schedule-form, run-api, run-now, background-task, and background-task-toasts specs (30 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d-runs-run-now feat(schedules): interval cadence + "Run now" with background-task toasts
…26-07-03 # Conflicts: # docs/kaizen/review-queue.md
…2026-07-03 chore(kaizen): weekly research scan 2026-07-03
…ness-q2-probe-result docs(kaizen): managed-Harness Q2 live-probe result — GO-with-boundary
…areable
Reframes the per-user markdown memory spec into the Memory Space primitive:
a named, first-class, bindable, templated, and shareable markdown wiki.
Oliver becomes a Chief-of-Staff template + a bound agent rather than a
special-cased feature.
- Adds the three-layer abstraction (Agent / Memory Space / declarative
binding) and the structural-config vs. semantic-MEMORY.md split.
- Space-keyed storage (SPACE#{id} + membership records + UserSpacesIndex
GSI) so sharing is expressible from day one.
- Entry types (entity / episodic / fact), Space Templates, and manifest-
indexed fields for relational/temporal queries ("who owes what").
- Sharing via owner/editor/viewer grants mirroring assistant collab-edit,
with run-as-user write attribution.
- Data governance is proportionate: same data class as sessions/artifacts
behind the same Entra JWT + RBAC — identity-based, no content-inspection
gate. Deletion-purge + inherited encryption are the only real items.
- 8-PR phasing; PR-1 (data layer) is the next buildout phase.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…paces-spec docs(memory): Memory Spaces — bindable, templated, shareable second-brain primitive (F5)
…rn-url-recovery docs(kaizen): recover resourceOauth2ReturnUrl section orphaned by #576 merge timing
Add a first-class "download the entire space as a .zip of raw markdown" capability (index + all entries, structure preserved, metadata.json), framed as the user-ownership / zero-lock-in property. Promotes the stubbed export endpoint into §9 with contents, access, streaming mechanics, and an import-friendly round-trip note; folds the zip export into PR-5. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…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>
Skills authored before PR-2 have neither the SKILL.md write-through
projection nor the standard bundle layout — v1 stored resources
content-addressed (skills/{id}/{sha256}) with no `kind`. Their S3 prefix is
therefore not a valid agentskills.io bundle: it can't be handed to a managed
Harness or exported as-is, which is the whole point of the projection.
The script fixes both per skill: copies each legacy object to its standard
path, rewrites the row's manifest to point there (adding `kind`), and writes
the SKILL.md generated from the row. It imports `generate_skill_md` and the
key helpers from the live write path, so a backfilled bundle is byte-identical
to one the app writes today.
Follows the backfill_session_static_sk conventions: dry-run by default,
idempotent, throttled, scopeable to one skill. Copies are non-destructive —
legacy objects survive unless --delete-legacy, and a manifest entry whose
bytes are missing is left untouched rather than repointed at nothing.
Applied to dev-ai/web_research: manifest now points at
references/extraction_tips.md, SKILL.md written, read path verified at 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backfilling dev's web_research row surfaced that the seeder itself still
emits v1 shapes, so every fresh environment reproduces exactly the state the
backfill just repaired:
- resources landed at the content-addressed key (skills/{id}/{sha256}) with
no `kind`, instead of skills/{id}/references/{filename}
- no SKILL.md was ever written, so the seeded prefix was not a valid
agentskills.io bundle and could not be handed to a managed Harness or
exported as-is
Both fixed. The slug and frontmatter rules are duplicated from
apis/shared/skills/bundle.py rather than imported, because seed.sh runs this
script standalone after infra deploy without the app package on the path —
the existing content-hash logic was duplicated for the same reason.
Tests now pin the standard layout and the projection, plus a guard that the
seed prose never again names the retired v1 meta-tools (skill_executor /
skill_dispatcher). That drift is what left dev's row instructing the model to
call tools deleted in PR-1/PR-2.
Note the seeders are skip-if-exists, so this repairs new environments only;
existing ones need backfill_skill_bundles.py (dev-ai: applied).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…2-user-tier feat(skills): user-authored skills tier (Skills v2 PR-3)
…R-4)
Wires the chat opt-in picker end-to-end and lands the §6 invoke-through
access predicate. Per spec docs/specs/skills-as-agent-primitive.md §8.
The picker's markup shipped in PR-1 but nothing ever sent its selection,
so the plain-chat skills path had never actually run. Turning it on
surfaced three latent bugs:
- Skill resolution was gated on agent_type == "skill", so the picker
could never have taken effect. Skills are now driven by the selection
on any turn; agent_type gates nothing ("skill" stays a ChatAgent alias
only so stale SPA sessions don't 422).
- The binding resolver gated on AppRoleService.can_access_skill, which
has no ownership clause — an author was blocked on their own authored
skill when invoking their own Agent — and whose "*" wildcard matched
any id at all, including another user's private skill. Clauses 1+2 now
route through resolve_accessible_skill_ids, which expands "*" over the
catalog only.
- Paused-turn resume and the construction snapshot both keyed skills off
agent_type == "skill", which would have orphaned the paused agent of
any plain-chat turn carrying skills. Both now key off the snapshot's
own enabled_skills.
Invoke-through (D7) is deliberately AGENT-scoped: it lives in the binding
resolver, not in resolve_accessible_skill_ids, because widening the
shared resolver would leak an Agent owner's private skills into every
invoker's plain-chat picker and bindable palette. The owner-match clause
blocks chain-sharing, and a system-owned Agent gets no invoke-through at
all so RBAC stays the sole gate on catalog skills.
D6 default flip: an absent or empty enabled_skills means no skills, on
both the runtime filter and the picker's untouched-preference default.
The two must agree or the UI would show skills as active that the turn
never loads. It also keeps skills free for turns that don't want them —
an absent selection short-circuits before any RBAC or skill-table read.
read_skill_file needed no per-call predicate: its record set IS the
turn's effective skill set, so there is no id the model can name to reach
a skill the invoker cannot use.
The Designer palette union needed no code — /agents/bindable already
delegates to resolve_accessible_skill_ids, which PR-3 widened.
Backend 4714 passed; SPA 1469 passed across 128 spec files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Light/dark Octocat marks alongside the existing google-* connector logos. Follows the repo's theme convention: -light is the black glyph (for light backgrounds), -dark the white one. Nothing references these yet — they're staged for a GitHub connector. Unrelated to the skills work in this branch; riding along rather than sitting untracked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…2-selection-surfaces feat(skills): selection surfaces + invoke-through access (skills v2 PR-4)
…2 PR-5) Flips SKILLS_ENABLED to default-ON with a kill switch and adds the infrastructure wiring it never had, closing out the Skills v2 epic (spec docs/specs/skills-as-agent-primitive.md §8). SKILLS_ENABLED had zero CDK/workflow plumbing, so "enable it per environment" was not previously expressible. Adds SkillsConfig to config.ts, threads SKILLS_ENABLED into both app-api and inference-api (they must stay in step — design-time refuses to bind a skill while the flag is off, so a mismatch would let an Agent be built with skills the runtime then blocks), and forwards CDK_SKILLS_ENABLED in platform.yml with the empty-string-safe ternary an unset GitHub variable requires. Feature existence and audience are two independent controls. The flag says the feature exists in an environment; the new `skills` RBAC capability says who sees the user-facing surfaces. system_admin holds it implicitly via its "*" tools grant, so the picker and My Skills stay admin-only during rollout; GA is one grant of `skills` to the `default` role, no redeploy. The gate raises 404, not 403. The SPA hides the My Skills nav entry by riding the list call, so a 404 hides the surface while a 403 surfaces an error toast — the failure mode that got the scheduled-runs capability gate reverted in prod. It also deliberately does not gate the runtime: an Agent shared to an ordinary user must still resolve its bound skills (invoke-through, §6/D7), and a capability check there would break exactly that path. Verified live end to end against a real agentskills.io bundle (Anthropic's docx) uploaded as a user skill and bound to an Agent: L1 8,075 -> L2 9,835 (skills tool, SKILL.md body) -> L3 10,737 (read_skill_file on references/LICENSE.txt). Invoke-through confirmed with a second non-admin account — the grant resolves through the shared Agent while the same skill stays absent from that user's own picker and /skills/mine. The "session auth, not Bearer" assertion now walks the transitive dependency tree rather than each route's direct dependencies, since the routes hang off the capability gate which in turn depends on the session. Pins the invariant that actually matters instead of the shape. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…2-enable-and-dogfood feat(skills): enable Skills v2 + admin-only capability gate (skills v2 PR-5)
Skills v2 moved skill authorization to apis/shared/skills/access.py (resolve_accessible_skill_ids = catalog ∪ own, and resolve_invocable_skill_ids which adds the Agent-owner invoke-through clause). AppRoleService.can_access_skill has had zero production callers since PR-4 and is wrong on two axes for anything user-tier: no ownership clause, and its "*" wildcard matches ANY skill id including another user's private authored skill. - Delete the method and its three tests. test_can_access_skill_with_wildcard asserted can_access_skill(user, "any_skill") is True — it enshrined the wildcard over-expansion bug as expected behavior. - Fix two stale docstring/comment references in agent_designer's binding_validation.py that still named it as the live run-time mechanism; since PR-4 that is resolve_invocable_skill_ids. - Reword the intentional "deliberately NOT can_access_skill" rationale in skills/access.py and agent_binding_resolver.py to past tense so they no longer imply the function still exists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Skills v2 moved skill authorization to apis/shared/skills/access.py (resolve_accessible_skill_ids / resolve_invocable_skill_ids). SkillAccessService was left behind with zero live callers — not exported from admin/services/__init__.py, not DI-wired, and reached by no dynamic import. Its can_access_skill carried the same "*"-wildcard over-expansion flaw that got AppRoleService.can_access_skill deleted in #686. Also updates the stale comment in admin/skills/routes.py that named the service as the consumer of the all-skill-ids snapshot; that snapshot is still live, but its reader is now skills.access. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ac-can-access-skill refactor(rbac): remove dead AppRoleService.can_access_skill
…ad-skill-access-service refactor(skills): delete dead SkillAccessService
A browse-and-discover surface for published Agents: a directory page and a detail page modeled on the ChatGPT/Claude plugin-detail layout, built as a read-view over the Agent record rather than a new primitive. The central decision (D1) is that we do NOT introduce a "Plugin" noun. Both vendors need a bundle layer because their capabilities install into a workspace separately from any persona; our Agent's `bindings[]` already IS that bundle, attached to the persona. Every field a vendor plugin-detail page renders already exists on our record. Grounding findings that shaped the design: - `VisibilityStatusIndex` (GSI2) is live and populated, and PUBLIC *access* still resolves to "viewer" — only the *listing* was switched off in ad4437e when email sharing superseded a public index. The read path is mostly built. - Listing is nonetheless a new sparse GSI5, not a re-enable: `VISIBILITY#PUBLIC` is one hot partition and can't be filtered by category. GSI5 is the next free slot (GSI4 is DueSyncIndex), and DueSyncIndex on the same table is the precedent — unlisted agents have no key, so the query physically can't see them. - `listed` is deliberately separate from `visibility` (D3). Deriving listing from PUBLIC would retroactively publish every existing PUBLIC agent to the whole institution with no author consent; backfill is listed=false. - Publishing amplifies Skills v2 invoke-through from a typed email list to everyone, so the publish dialog must enumerate which authored skills it exposes, and memory_space bindings block publication outright (D5). - The Designer's block-on-missing rule (D5) strains under open browsing, so the detail page previews per-invoker runnability up front — which resolves the "per-invoker capability preview" open question parked in agent-designer.md. Also notes a behavior change needing a call: GET /agents/{id} currently returns `instructions` to any PUBLIC viewer, which is a much larger exposure once agents are broadly listed than it was under link-sharing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rectory-spec docs(agents): draft the Agent Directory spec
… toggle Artifacts shipped as two independent `protocol: local` catalog rows — `create_artifact` and `update_artifact` — so the tool picker listed them as two unrelated entries. The picker only groups children under a parent for MCP protocols (driven by `serverTools`), so there was nothing to nest them under; the flat listing was a data-model fact, not a template gap. Adopt the Word-documents idiom already used one entry below in the seed list: a single catalog row whose id is the gate key, with the runtime injecting the full toolset. `create_artifact` is now that key and provisions both the create and update tools. `seed_default_tools` is create-only, so a seed run does nothing to an environment seeded before this change. Add a backfill script that retitles the surviving row, promotes `update_artifact` to `create_artifact` everywhere a grant can hide — role TOOL_GRANT# items *and* the grantedTools/effectivePermissions.tools arrays on DEFINITION, user toolPreferences, assistant bindings — then deletes the retired row. Promote-before-delete, so an aborted run degrades to "both granted", never "neither". Schedule snapshots are left alone: a stale id there is an inert no-op since the runtime only reads `create_artifact`. Two judgement calls worth recording: - User prefs are a sparse override map, so only an explicit *enable* of the retired id carries over. Someone who switched update off while leaving create at its default-on never asked to lose artifacts, so an explicit disable just drops the key. - A role granting `*` gains no concrete grant — the wildcard already covers the keeper and narrowing it would be a silent scope change. Behavior change: anyone with create enabled but update disabled now gains update. That is inherent to collapsing the toggle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tifact-catalog-rows refactor(artifacts): collapse the two artifact catalog rows into one Artifacts toggle
The /my-skills route, page, service, and the whole /skills/mine backend surface stay fully functional — only the sidenav link is removed, until we decide how users should actually navigate to their skills. Drops the now-dead showMySkills computed, the MySkillService injection, and its loadSkills() accessibility probe (that call existed solely to decide whether to render the link). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adopts the authoring half of Anthropic's open-source skill-creator and scopes out the eval half, which assumes a filesystem, subagent spawning, and script execution — none of which exist here (scripts are inert by design). Evals hang off F1's headless lane instead. PR-1 ships the methodology as an admin-catalog skill with zero code, routing the handoff through the My Skills form's existing frontmatter parsing. PR-2..PR-4 add the missing primitive: agent tools that write the user's own skills, executing as the invoking user and re-checking ownership per call, mirroring memory_write. Depends on skill-bundle-import's nested paths and server-side SKILL.md parser rather than restating them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…skills-nav chore(sidenav): hide the My Skills nav entry
…eator-spec docs(skills): draft the Skill Creator spec
The user-facing skills surfaces (`GET /skills/`, `PUT /skills/preferences`, and all of `/skills/mine/*`) hung off `require_skills_capability`, which 404'd anyone not holding the `skills` RBAC capability. Its job was to keep skills admin-only during the v2 rollout, with GA framed as "one grant of `skills` to the `default` role, no redeploy." That GA path does not work, for two independent reasons: 1. `default` is a *fallback* role. `resolve_user_permissions` consults it only when a user matches zero AppRoles (service.py "Step 3"); it is not merged alongside a matched role. Prod's `default` carries no JWT mappings at all, so granting there would reach only unmapped users — never the faculty/staff/student cohorts. 2. A capability id cannot be granted from the admin roles UI regardless. That form builds `grantedTools` from the tool catalog, and a capability is not a tool, so there is no way to select it and no free-text entry. Net effect: an admin who granted a catalog skill to a role would find it silently invisible to that role's users, with no in-product way to fix it. Remove the gate. Skills are governed by `SKILLS_ENABLED` per environment and by a role's `grantedSkills` per cohort — a complete model that the admin UI can actually operate. Both surfaces are already self-limiting: `GET /skills/` returns only what `resolve_accessible_skill_ids` grants (no grants means an empty list and no rendered picker), and every `/skills/mine/*` route is owner-scoped inside `UserSkillService`. The route-coverage control is kept rather than dropped, retargeted from "every route is capability-gated" to "every route requires a session" — the invariant that still matters now that the session dependency is the only thing between these routes and an anonymous caller. A second test pins the removal so the gate cannot creep back without also making capabilities grantable from the roles UI. Also corrects the "GA = grant to `default`" claim where it appeared in capabilities.py, infrastructure/lib/config.ts, and platform.yml, and notes that `SCHEDULED_RUNS_CAPABILITY` is itself unused (that gate was dropped after 403ing in prod), which leaves capabilities.py with no consumers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rop-capability-gate feat(skills): drop the `skills` capability gate from user-facing routes
`apis/shared/rbac/capabilities.py` has no consumers left. It defined two capability ids granted through the `grantedTools` axis: * `SCHEDULED_RUNS_CAPABILITY` was orphaned when the RBAC gate on `/schedules` and `/runs` was dropped after 403ing in prod. * `SKILLS_CAPABILITY` was the last live caller, removed in the preceding commit along with `require_skills_capability` and its 12 route deps. Delete it rather than keeping it as a reference. The mechanism it documented is not one we want reached for again: a capability id cannot be granted from the admin roles UI (that form builds `grantedTools` from the tool catalog and offers no free-text entry), so any gate built on it is operable only by hand-writing DynamoDB items. Both live gates were removed for that reason. Its docstring carried two findings that cost real effort to establish, so they move to `AppRoleService.resolve_user_permissions` — the code they actually describe — rather than dying with the file: 1. `default` is a *fallback*, not a universal role. Step 3 substitutes it only when a user matched zero AppRoles, and prod's `default` carries no `jwtRoleMappings`, so granting there reaches only unmapped users. The "GA = one grant to `default`, no redeploy" framing that appeared in several comments was wrong. 2. The roles-UI limitation above, recorded where someone would see it before routing a new grant through this axis. The algorithm list in that docstring is renumbered to match the code's own step comments, so the "Step 3" reference is unambiguous. Also corrects both `feature_flags.py` docstrings, which still described the now-deleted capability as the companion "who may use it" control: `skills_enabled` points at a role's `grantedSkills`; `scheduled_runs_enabled` notes the flag is now the only control and the routes are deliberately ungated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d-capabilities-module refactor(rbac): delete the dead capabilities module
Feature release delivering Skills v2 — skills redesigned from tool-binding containers into pure, portable knowledge bundles that Agents load on demand, with a user-authored tier so any signed-in user can create their own. Skills ship enabled by default. Also completes the session-metadata static-sort-key migration (issue #175 Phases 2-3) and consolidates the two artifact tool-catalog rows into a single "Artifacts" toggle. - Skills v2 (#679-#685, #692): pure knowledge bundles in agentskills.io S3 format with a SKILL.md projection, progressive disclosure L1-L3 via the new read_skill_file tool, the Strands AgentSkills plugin replacing the bespoke SkillAgent, a user-authored tier at /skills/mine/*, a chat picker, and invoke-through so a shared Agent resolves its skills for the recipient. Skills no longer bind tools — allowedTools persists as advisory metadata and never grants. The `skills` capability gate was removed as inoperable: it could not be granted from the admin roles UI at all. - Session metadata (#677, #678): cold-tail backfill script plus a GSI-only read contraction gated on its completion marker, failing open to dual-read. - Artifacts (#689): two catalog rows collapsed to one, with a backfill that promotes grants before retiring the old id. Three backfill scripts must be run per environment; see RELEASE_NOTES.md. No new AWS resources and no dependency changes. Skills activate on the backend.yml deploy itself, since unset SKILLS_ENABLED now reads as enabled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| self._invalidate(skill_id) | ||
|
|
||
| logger.info( | ||
| f"User {user.email} created skill: {skill_id}", |
Comment on lines
+183
to
+187
| extra={ | ||
| "event": "user_skill_created", | ||
| "skill_id": skill_id, | ||
| "owner_user_id": user.user_id, | ||
| }, |
| self._invalidate(skill_id) | ||
|
|
||
| logger.info( | ||
| f"User {user.email} updated skill: {skill_id}", |
Comment on lines
+213
to
+218
| extra={ | ||
| "event": "user_skill_updated", | ||
| "skill_id": skill_id, | ||
| "owner_user_id": user.user_id, | ||
| "changes": list(updates.keys()), | ||
| }, |
| self._invalidate(skill_id) | ||
|
|
||
| logger.info( | ||
| f"User {user.email} deleted skill: {skill_id}", |
Comment on lines
+242
to
+246
| extra={ | ||
| "event": "user_skill_deleted", | ||
| "skill_id": skill_id, | ||
| "owner_user_id": user.user_id, | ||
| }, |
| import logging | ||
| import os | ||
| import time | ||
| from typing import Any, Dict, Optional, Tuple |
| try: | ||
| resp = table.get_item(Key={"PK": _MIGRATION_MARKER_PK, "SK": _MIGRATION_MARKER_SK}) | ||
| if (resp.get("Item") or {}).get("complete"): | ||
| _migration_complete_cache = True |
| from botocore.exceptions import ClientError | ||
|
|
||
| from .models import SkillDefinition, SkillStatus, UserSkillPreference | ||
| from .models import SYSTEM_OWNER_ID, SkillDefinition, SkillStatus, UserSkillPreference |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Feature release delivering Skills v2, completing the session-metadata static-sort-key migration, and consolidating the artifact tool-catalog rows.
Full detail: RELEASE_NOTES.md · CHANGELOG.md
Three backfill scripts must be run per environment, each dry-run first (all three are dry-run by default and idempotent), dev before prod:
backfill_artifact_tool_merge.pyupdate_artifactrow is deleted. Skipping it drops the tool for roles grantedupdate_artifactalone.backfill_skill_bundles.py--delete-legacy.backfill_session_static_sk.pySkills go live the moment
backend.ymlcompletes — before any CDK deploy — because deployed containers set noSKILLS_ENABLEDand unset now reads as enabled. To hold an environment back, setCDK_SKILLS_ENABLED=falseand deployplatform.ymlfirst.What's in it
Skills v2 (#679–#685, #692). Skills were containers that bound tools; they are now pure knowledge bundles — instructions plus reference files — loaded on demand via progressive disclosure (name/description →
SKILL.md→read_skill_file). Stored as portable agentskills.io S3 bundles with aSKILL.mdwrite-through projection. The runtime moved to the StrandsAgentSkillsplugin. Any signed-in user can now author skills at/my-skills, and a skill bound to a shared Agent resolves for whoever uses that Agent.Session-metadata migration, Phases 2–3 (#677, #678). A cold-tail backfill for rows that never get written again, plus a read contraction that skips the legacy query once the backfill's marker is set. Fails open to dual-read, so the deploy is order-independent.
Artifacts consolidated (#689). "Create Artifact" and "Update Artifact" become one Artifacts toggle that injects both tools.
Breaking
allowedTools, but those values stop granting. Anything relying on a skill to confer tool access must grant via RBAC instead.update_artifactcatalog row is retired — see the backfill above.Notes
No new AWS resources, no new IAM, no dependency changes. The only CDK delta is the
SKILLS_ENABLEDenv var on app-api and the inference-api Runtime.The "My Skills" sidenav entry is deliberately hidden pending a navigation decision — the route, page, and backend surface are live and reachable by direct URL.
🤖 Generated with Claude Code