Release/1.1.0#604
Merged
Merged
Conversation
Adds two related guards on the role-management surface:
1. apis/shared/rbac/role_constraints.py validates jwt_role_mappings on
both create_role and update_role:
- Every entry must match ^[A-Za-z0-9_-]{2,64}$ (well-shaped IdP group
identifiers only — no whitespace, HTML, control characters, etc.).
- On a protected role (system_admin), entries that would map every
authenticated user — 'default', '*', 'user', 'users', 'everyone',
'anyone', 'authenticated', 'all', 'public', etc. — are rejected.
Violations raise RoleConstraintError (a ValueError subclass) which
the existing route handler converts to HTTP 400.
2. apis/shared/rbac/version.py exposes a process-wide monotonic
roles-version counter. AppRoleAdminService bumps it after every
role mutation, and AppRoleCache.invalidate_all() bumps it too. The
user-profile cache in apis/shared/auth/dependencies tags each entry
with the current version and treats entries from older versions as
stale on read. This eliminates the post-revert window where users
continued to see permissions from a recently-removed role until the
5-minute TTL elapsed.
Tests:
- tests/rbac/test_role_mutation_constraints.py (23 tests) covers
ubiquitous-mapping rejection on protected roles, format validation
including a parametrized set of malformed inputs, and confirms the
rule does not apply to non-protected roles.
- tests/rbac/test_role_version_invalidation.py (6 tests) covers
monotonicity, thread-safety under concurrent bumps, profile-cache
refresh after a bump, and confirms cache.invalidate_all + role
mutations both bump the version.
Both generate_diagram_and_validate and analyze_spreadsheet take a
python_code parameter and forward it to AWS Bedrock Code Interpreter.
The sandbox is isolated from the host, but legitimate use of these
tools is narrow: matplotlib / numpy / pandas / seaborn for charts and
dataframe operations. Anything beyond that surface area — subprocess,
os, sys, socket, eval/exec/compile, dunder attribute walks — is an
abuse signal regardless of intent.
apis/shared/security/python_ast_policy.py adds validate_diagram_code,
which parses the source with the standard ast module and walks the
tree:
- Imports must root in an allowlist of plotting / data-analysis
packages (matplotlib, numpy, pandas, seaborn, scipy, math,
statistics, json, datetime, random, collections, itertools,
functools, re, decimal, fractions, csv, io). Submodules of allowed
roots pass; anything else is rejected.
- Bare names matching the host-process escape set (subprocess, os,
sys, socket, shutil, pickle, ctypes, importlib, urllib, http,
requests, httpx, asyncio, threading, multiprocessing, signal,
pathlib, tempfile, etc.) are rejected wherever they appear, even
with no import statement (defeats __import__('os') and aliasing).
- Dunder attribute access (__class__, __bases__, __subclasses__,
__builtins__, __dict__, ...) is rejected so the standard 'walk the
type hierarchy' obfuscation cannot reach builtins.
- Empty source, syntax errors, and sources over 32 KiB are rejected
generically.
Both tools call the validator at the top of their implementation,
before any sandbox lookup, file resolution, or AWS API call. The check
runs server-side after the LLM emits the tool call, so a user-supplied
system_prompt cannot disable it. Rejections return a generic message
('Diagram code rejected by policy.' / 'Analysis code rejected by
policy.') with no detail; the structural reason is logged at WARN.
Tests:
- 56 unit tests in tests/security/test_python_ast_policy.py covering
every forbidden import, every forbidden bare name, dunder access
via attribute chains, obfuscation patterns (alias assignment,
string concatenation), edge cases (empty / whitespace / oversize /
syntax-error / non-string), and four realistic chart-code samples.
- 5 integration tests for the diagram tool confirm the sandbox is not
invoked when policy fails, and that valid chart code passes the
policy gate.
- 3 integration tests for the spreadsheet tool confirm policy fires
before file resolution, and that legitimate pandas analysis code
passes.
…on (#419) (#450) Adds the protocol=mcp ("MCP Gateway (AgentCore)") config layer so an admin can register an externally deployed MCP server as a target on the centralized AgentCore Gateway. This is the backend model foundation (PR1 of 5); the live target lifecycle, infra grants, route orchestration, and form land in later PRs. - GatewayListingMode (default|dynamic), GatewayCredentialType (gateway_iam_role|oauth|api_key), and GatewayOAuthGrantType (authorization_code|client_credentials|token_exchange) enums, mirroring the bedrock-agentcore-control listingMode / credentialProviderType / grantType surfaces (uppercased at the AWS boundary by the forthcoming GatewayTargetService). - MCPGatewayConfig (mirrors MCPServerConfig) with target_name, endpoint_url, listing_mode, credential_type, credential_provider_arn, oauth_scopes, grant_type, custom_parameters, per-tool MCPToolEntry flags, and AWS-assigned target_id/gateway_arn (the catalog<->AWS link used by update/delete reconciliation). Reuses MCPToolEntry serialization. - OAuth grant_type defaults to authorization_code (3LO, on-behalf-of-user — the flow AWS's June-2026 Gateway auth-code blueprint documents); custom_parameters are carried because they are part of the AgentCore token-vault key and must match between target registration and token retrieval. - Co-gating validator: OAuth requires DEFAULT listing + a provider ARN; API key requires a provider ARN; gateway IAM role forbids one. Encodes the listing-mode gotcha (DYNAMIC disables 3LO + semantic search) at the model layer. - ToolDefinition.mcp_gateway_config + to_dynamo_item/from_dynamo_item round-trip under the mcpGatewayConfig key. - MCPGatewayConfigRequest/Response (+ to_model/from_model) wired into ToolCreateRequest, ToolUpdateRequest, and AdminToolResponse. - 17 model/serialization/validator tests; existing tool-model consumers green. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…M grants (#419) (#452) PR3 of 5 for admin-managed AgentCore Gateway target registration. Wires the infrastructure the app-api GatewayTargetService (PR #451) needs at runtime. - AgentCoreGatewayConstruct publishes /{prefix}/gateway/id as an SSM parameter (the construct header already claimed this but only emitted CFN outputs). app-api reads it at runtime via ssm:GetParameter — never at CFN deploy time — so there is no same-stack publish/consume ordering deadlock. - app-api-iam-grants adds: - SsmReadGatewayId: ssm:GetParameter on /{projectPrefix}/gateway/id. - AgentCoreGatewayTargetAccess: bedrock-agentcore:{Create,Get,Update,Delete, List}GatewayTarget scoped to the gateway resource type (arn:...:bedrock-agentcore:region:account:gateway/*). Target operations are authorized through the parent gateway ARN — there is no gateway-target resource type. - Action names verified against the control-plane API model and the IAM service-authorization reference (2026-06-05). Create/Get/Delete/List are in the reference; UpdateGatewayTarget is a real control-plane operation following the Update* IAM precedent in this file (UpdateOauth2CredentialProvider) but was not yet in the reference — included with a comment + fallback note (delete+recreate) in case any account rejects the forward-looking name. - Completes the placeholder gateway-SSM-params construct test. Gate: tsc --noEmit clean; jest constructs/security-policy/ssm-safety/platform-stack green (full stack synthesizes with the new statements; no Action:*/Resource:* violations). Pre-existing, unrelated config.test.ts failures and the bare `cdk synth` artifacts-cert lookup error reproduce on clean develop. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) Re-lands the GatewayTargetService that was stranded when #451 merged into its stacked base instead of develop, and adds the admin route + ToolCatalogService lifecycle for protocol=mcp Gateway target registration (create-AWS-first, update reconcile, hard/soft delete, 409/502 route mapping). Backend for #419 is now complete on develop; the admin form (Phase 5) remains. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Admin UI to register an externally deployed MCP server as an AgentCore Gateway target: protocol=mcp form section (target/endpoint/listing-mode/credential-type + conditional ARN/scopes/grant), per-tool approval rows with Discover-from-server, OAuth co-gating, and 502-vs-400 error handling. Completes the #419 feature on develop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /users/me/sync now derives the persisted roles list exclusively from current_user.roles (the validated JWT claims). Roles are already populated server-side by the BFF callback's _sync_user_from_id_token off the trusted ID-token decode, and per-request RBAC reads them from the access token's verified claims plus the Users table that the callback writes — the request body has no role to play in this path. UserProfileSyncRequest no longer declares a 'roles' field. Pydantic's extra='allow' setting keeps the endpoint backwards-compatible with clients that still send extra keys (no 4xx; the extras surface via model_extra and are dropped when building the persisted profile). The handler logs a WARNING when it sees a legacy 'roles' key so stale clients can be tracked down. Tests: - test_sync_persists_jwt_roles_not_body_roles: body roles do not influence the persisted record. - test_sync_with_no_jwt_roles_persists_empty_list: empty JWT roles cannot be filled in by the body. - test_sync_warns_when_legacy_roles_field_present: legacy field emits the WARN log. - Plus seven baseline tests covering email normalization, name and picture pass-through, validation errors, auth requirement, and the graceful no-op when the Users repository is disabled.
…iring & health UX (#419) (#457) * fix(tools): correct Gateway mcpServer credential config (None + IAM service) (#419) CreateGatewayTarget rejected our GATEWAY_IAM_ROLE targets with "IamCredentialProvider is required for mcpServer targets using IAM authentication": unlike OpenAPI/Lambda targets (which accept a bare GATEWAY_IAM_ROLE), an mcpServer (HTTP endpoint) target must supply an explicit iamCredentialProvider naming the AWS service to sign for. We were sending `[{credentialProviderType: GATEWAY_IAM_ROLE}]` with no nested provider. - New credential type NONE (public endpoint) — omits credentialProviderConfigurations entirely (the API parameter is optional). This is now the default (least-config path; the admin opts into IAM/OAuth/API-key). - GATEWAY_IAM_ROLE now carries aws_service (required) + optional aws_region, sent as credentialProvider.iamCredentialProvider{service, region?}. Model validator requires aws_service for IAM and the create/update calls omit the credential block for NONE. - Backend: MCPGatewayConfig + request/response models + GatewayTargetService. - Frontend: 'none' credential option (default), AWS service/region fields shown for the IAM case, build/restore wiring, and the discover auth mapping unchanged (gateway_iam_role → aws-iam, else none). Verified: full backend suite 3466 passed; tool-form gateway spec 6/6 (ng test). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(agents): point Gateway client at the infra-param gateway + expand #419 catalog tools (#419) The agent resolved the gateway from a hardcoded `/strands-agent-chatbot/dev/...` SSM param, while the admin form (#419) registered targets on the CDK gateway `/{PROJECT_PREFIX}/gateway/id` — two different gateways, so admin-registered tools never reached the agent. - New shared `gateway_identity.resolve_gateway_id`/`gateway_url_from_id`: one source of truth (explicit id → AGENTCORE_GATEWAY_ID override → SSM `/{PROJECT_PREFIX}/gateway/id`). The agent's `get_gateway_url_from_ssm` and GatewayTargetService both resolve through it, so they can't diverge. - Expand a `protocol=mcp` catalog tool (`gateway_class_search`) into the gateway's runtime per-tool ids (`gateway_<targetName>___<toolName>`) in base_agent, so the FilteredMCPClient matches them; raw RBAC gateway ids (already containing `___`) pass through unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(tools): self-service Gateway MCP registration — per-target IAM grant, health, discovery UX (#419) Make admins able to register any same-account Lambda-URL MCP server through the form with no infra change, and surface the failures that were previously silent. Per-target Lambda grant (replaces the gateway role's standing `mcp-*` wildcard): - app-api grants the gateway role `lambda:InvokeFunctionUrl` on exactly the registered function at registration (`AddPermission` naming the role as Principal — a role-specific resource grant authorizes it same-account, no identity grant) and revokes it on delete. `gateway_lambda_grant.py` + GatewayTargetService lifecycle; new `MCPGatewayConfig.lambda_function_name` and a conditional "Lambda function name" form field. - Cross-account validation: `GetFunctionUrlConfig` confirms same-account + that the endpoint is under the function URL; otherwise a 400 tells the admin to go public or use a credential provider. - CDK: gateway role loses its Lambda-invoke wildcard (logs only); app-api gains scoped `AddPermission`/`RemovePermission`/`GetFunctionUrlConfig` + `GetGateway`. Gateway target health: `GET /admin/tools/{id}/gateway-status` (+ statusReasons on GatewayTargetInfo) and a Ready/Syncing/Failed/Missing badge on the tools list, so a FAILED target sync is visible instead of only "the agent can't see the tool". Discovery + form polish: unwrap the upstream HTTP status from MCP discovery errors (403 stays 403 with an actionable message; 401→400 to avoid the SPA logout); auto-derive awsService/awsRegion from the endpoint and recommend IAM when an AWS host is left on None. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… page (#459) * docs(docs-site): add API Keys to Features and a new Admin section Adds an API Keys page to the Features menu, and registers a new "Admin" sidebar section (after Features) whose sub-menu mirrors the SPA admin console — Cost Analytics, Quotas, Fine-Tuning, Models, Tools, Connectors, Users, Roles, Auth Providers, User Menu Links, and Conversation Modes, plus an Admin Overview landing page. Pages follow the existing Draft-stub convention and are picked up via Starlight directory autogeneration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(docs-site): flesh out Tools page and shorten its menu label Shortens the Features menu link to "Tools" via sidebar.label while keeping the full "Tools and Multi-Protocol Architecture" page title. Replaces the Draft stub with content covering the four tool protocols (Direct, AWS SDK, MCP+SigV4, A2A), the intentionally-limited default tool set, and the extension points that make it flexible by design. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… plan (#460) Design plan for an admin feature to author Skills (instruction bundles that bind existing catalog tools) and make them available to user roles via RBAC, leveraging the existing SkillAgent/SkillRegistry progressive-disclosure runtime. Covers data model, persistence, admin API, RBAC extensions, cross-source tool binding, cache correctness, frontend, forward-compat for user-shared skills, and a 6-PR breakdown. Design only — no implementation. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundational, no-behavior-change PR-1 of the admin-managed Skills feature
(docs/specs/admin-skills-rbac-tool-binding.md §4/§5/§11). Adds the
DynamoDB-backed Skill catalog as a parallel of the Tools catalog; nothing
consumes it yet (RBAC, admin API, frontend, runtime wiring are PR-2..PR-6).
- apis/shared/skills/models.py — SkillDefinition (mirrors ToolDefinition):
skill_id (^[a-z][a-z0-9_]{2,49}$), display_name, description, instructions,
bound_tool_ids, compose, status (active/draft/disabled), category,
forward-compat owner_id/visibility (ADMIN-scope in v1), computed
allowed_app_roles, audit fields; snake_case→camelCase to/from_dynamo_item.
- apis/shared/skills/repository.py — SkillCatalogRepository (mirrors
ToolCatalogRepository): reuses the app-roles table (PK=SKILL#{id},
SK=METADATA); get/list/create/update/soft_delete/exists/batch_get.
- apis/shared/skills/freshness.py — 10s-TTL per-skill freshness hash +
all-skill-ids snapshot + invalidate(skill_id).
- apis/shared/skills/__init__.py — public exports.
- infra: add SkillOwnerIndex GSI (GSI4PK=OWNER#{owner_id},
GSI4SK=SKILL#{skill_id}, PAY_PER_REQUEST) to the app-roles table now to
avoid a Phase-2 migration; unused in v1.
- tests: model round-trip + skill_id regex, repository CRUD (moto), freshness
cache; infra tables-detailed asserts the new GSI; conftest roles_table
gains GSI4 + a skill_repository fixture.
Import-boundary clean (apis.shared imports neither app_api nor inference_api).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR-2 of the admin-managed Skills feature
(docs/specs/admin-skills-rbac-tool-binding.md §4.2/§5/§7/§11). Extends the
RBAC engine so skills are granted to AppRoles exactly like tools/models, and
adds the server-side SkillAccessService. No route/frontend/runtime wiring yet
(PR-3..PR-5), so no user-visible behavior change.
Mirrors the tool RBAC path throughout:
- rbac/models.py: EffectivePermissions.skills + AppRole.granted_skills +
UserEffectivePermissions.skills (trailing default); grantedSkills/skills on
the Create/Update/Response DTOs (defaulted → additive, non-breaking).
- rbac/admin_service.py: _compute_effective_permissions unions granted_skills
across inheritance; create_role threads it; new get_roles_granting_skill /
add_skill_to_role / remove_skill_from_role.
- rbac/service.py: _merge_permissions unions skills (with "*"); new
can_access_skill / get_accessible_skills.
- rbac/repository.py: write SKILL_GRANT#{id} reverse-lookup items reusing the
GSI2 keyspace with a disjoint SKILL# partition (no new GSI); get_roles_for_skill.
- rbac/seeder.py + scripts/seed_bootstrap_data.py: system_admin gets the skill
wildcard (granted_skills=["*"] + SKILL_GRANT#*); default stays gated ([]).
- app_api/admin/services/skill_access.py: SkillAccessService mirroring
ToolAccessService (no gateway_* passthrough — skills have no dynamic form).
Tests: skill resolution/wildcard/inheritance/reverse-lookup, access-service
filtering, repo round-trip + tool↔skill partition non-collision + stale-grant
cleanup, bootstrap SKILL_GRANT#* assertions, make_app_role factory extended.
Full suite: 3571 passed, 3 skipped. Import-boundary clean.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR-3 of the admin-managed Skills feature (docs/specs/admin-skills-rbac-tool-binding.md §6/§11). Adds the /admin/skills REST surface + SkillCatalogService, mirroring app_api/admin/tools/routes.py and app_api/tools/service.py. Net-new admin-only routes; no frontend (PR-4) or runtime (PR-5) consumer yet, so no behavior change to existing surfaces. - apis/shared/skills/models.py: admin request/response DTOs mirroring the tool equivalents (SkillCreate/UpdateRequest, SkillRoleAssignment, SkillRolesResponse, Set/AddRemoveSkillRolesRequest, AdminSkillResponse + from_skill_definition, AdminSkillListResponse). - apis/shared/skills/repository.py: add hard delete_skill (PR-1 had only soft_delete_skill; the ?hard= flag needs it). - apis/app_api/skills/service.py (new): SkillCatalogService — CRUD, _validate_bound_tools (batch-get vs the tool catalog; reject unknown/non-active), allowedAppRoles hydration, bidirectional role sync onto granted_skills. - apis/app_api/admin/skills/routes.py (new): /admin/skills CRUD + /roles endpoints; ValueError->400, missing->404, freshness invalidation on writes. - apis/app_api/admin/routes.py: register the subrouter under /admin. Tests (moto-backed): service CRUD + bound-tool validation + role sync + hydration; TestClient route tests for status codes, camelCase shape, bound-tool 400, and a /roles round-trip. One moto app-roles table backs SKILL#/TOOL#/ROLE#. Full suite: 3606 passed, 3 skipped. Import-boundary clean. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ts (#464) Adds a §0 revision (2026-06-09) to the admin-managed Skills plan capturing the direction agreed with the product owner: - Three drivers, reordered: tool-hiding UX (primary) > token efficiency > ecosystem-compatible authored expertise. Skills and assistants are distinct primitives (skill = bound tools for an outcome; assistant = a knowledge- grounded "Project"), not redundant. - New skill bundle shape: SKILL.md instructions + supporting reference files (read-only, progressive disclosure) + bound catalog tools. - Scripts + code execution DEFERRED (out of scope) — removes the code-exec risk; imports bring knowledge faithfully, behavior is re-expressed via bound catalog tools. - Import-prefill of a SKILL.md/folder/zip is now in scope (asset ingestion, not live discovery; tool bindings stay manual). - Re-sequenced remaining phasing: PR-4 reference-file data layer (S3-backed resource store) -> PR-5 frontend incl. import -> PR-6 runtime incl. a read-reference-file disclosure level + cross-source bind resolver -> PR-7 default flip. - Inline updates to non-goals, data model (resources manifest), persistence (S3 skill-resources bucket), phasing, and risks. PR-1..3 marked merged. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…R-4) (#465) Adds the data + API layer for a skill's supporting reference files (read-only markdown/resources for deep progressive disclosure). Bytes live in a new content-addressed S3 bucket; the DynamoDB SkillDefinition row carries only a lightweight `resources` manifest (400 KB item limit). No runtime or frontend consumes them yet — zero behavior change. - models: SkillResourceRef + resources manifest on SkillDefinition, round-tripped through to/from_dynamo_item (camelCase, backward-compatible) - shared: SkillResourceStore (content-hash keyed, dedupe, graceful degradation), mirroring the artifacts/MCP-apps persistence patterns - service: upload/list/read/delete resource CRUD with filename/size/count validation, atomic manifest update, orphaned-object GC - api: /admin/skills/{id}/resources endpoints (require_admin) - cdk: SkillResourcesConstruct bucket, threaded via PlatformComputeRefs; app-api read/write + inference-api read-only (PR-6 forward-compat) - tests: model round-trip, moto store, TestClient endpoints, infra jest Spec: docs/specs/admin-skills-rbac-tool-binding.md §0.5 (PR-4) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…PR-5) (#466) Adds the admin/skills/ Angular feature, mirroring admin/tools/: list page, create/edit form, bound-tool picker, role-grant dialog, and management of a skill's S3-backed reference files (PR-4 data layer). Import-prefill ingests a SKILL.md (frontmatter -> id/name/description, body -> instructions); tool bindings are picked manually and scripts are out of scope. - models: admin-skill.model.ts (AdminSkill, SkillResourceRef, request DTOs, SKILL_STATUSES/CATEGORIES) + skill-import.util.ts (parse SKILL.md + slugify) - service: AdminSkillService (resource()+signals CRUD, role sync, reference-file list/upload/read/delete) - pages: skill-list (filters, expandable rows, tool/ref-file/role badges, role dialog, soft delete) + skill-form (reactive form, tool picker, reference-file upload/view/delete + inline authoring, SKILL.md import) - components: skill-role-dialog (copy of tool-role-dialog) + tool-picker-dialog (multi-select active catalog tools) - routes: skills / skills/new / skills/edit/:skillId; "Skills" admin nav link - tests: skill-import.util.spec + admin-skill.service.spec (ng test) No backend changes. Spec: docs/specs/admin-skills-rbac-tool-binding.md §0.5 (PR-5) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…inding (PR-6a) (#467) Opt-in (agent_type="skill"); the default "chat" path is unchanged. First slice of the skills runtime wiring: admin-authored skills now load for the right users, fold their local tools behind the meta-tools, and cache correctly. Gateway/external MCP tool folding + the read-reference-file level + an example seed are deferred to PR-6b; the default flip to PR-7. - registry: load_records() DB source (inline instructions + bound_tool_ids + compose + resources), DB-aware load_instructions, all_bound_tool_ids, bind_catalog_tools (resolve bound catalog ids -> live objects). File scan unchanged. - skill_tools: make_skill_tools(registry) — per-agent (dispatcher, executor) closures, fixing a latent concurrency bug (module-global registry would cross-pollute concurrent per-user skills). Module-level fns kept for dev. - skill_agent: DB discovery + enabled_tools augmentation (skill-as-grant, all sources) before super().__init__; bind/fold local bound tools; degrade to ChatAgent at 0 skills; restore original enabled_tools in the construction snapshot for resume cache parity. - chat: route resolves accessible skills via shared AppRoleService (get_accessible_skills, "*" -> get_all_skill_ids), threads them into every get_agent call; get_agent adds skills_hash to _create_cache_key (digest of accessible ids + their updated_at). Empty for chat -> key unchanged. - tests: registry DB source/binding, make_skill_tools isolation, skills_hash cache separation + chat path unaffected, _fetch_skill_records filtering. Spec: docs/specs/admin-skills-rbac-tool-binding.md §0.5 (PR-6), §8 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…example seed (PR-6b) (#468) Runtime increment after PR-6a (#467). All opt-in via agent_type="skill"; the default "chat" path is unchanged, so zero default behavior change. 1. Fold gateway/external MCP tools behind the meta-tools. PR-6a folded only LOCAL callables; gateway/external tools materialize as *client objects* (one MCPClient exposing many tools), so they were available but not hidden. New mechanism: - mcp_tool_folding.py: a per-client fold set; both FilteredMCPClient and UICapableMCPClient drop folded names from list_tools_sync (the seam Strands uses to build the model tool list). Setting a fold also invalidates the client's _loaded_tools cache so Strands re-lists with the fold applied (external clients are pre-flighted before folds are known). - mcp_binding.py: resolve_mcp_bindings maps each non-local bound id to its concrete MCP tool(s) + owning client (gateway via expand_gateway_tool_ids from the catalog, no session; external by enumerating the live client), wrapping each as a FoldedMCPTool. The client object stays in the agent's tool list (Strands keeps its session alive); skill_executor runs folded tools through MCPClient.call_tool_sync. - SkillAgent._bind_mcp_tools wires it after the existing local binding. 2. Read-reference-file progressive-disclosure level. skill_dispatcher's previously-unused `reference` arg now serves a skill's supporting reference files from the PR-4 S3 SkillResourceStore on demand; the no-arg dispatch response lists available filenames so the model knows what it can read (mirrors how a real SKILL.md body names its files). 3. Bootstrap example-skill seed. seed_example_skills seeds one demonstrable bundle: instructions + a bound local tool (fetch_url_content) + an S3-uploaded reference file, granted to the default role. Mirrors seed_default_tools; the skill-resources bucket name is derived in seed.sh from the project prefix. Spec: docs/specs/admin-skills-rbac-tool-binding.md (§0.5, §8, §12). Full backend suite: 3711 passed, 3 skipped. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(tools): scoped tool-id helpers + agent runtime resolution
A bare catalog tool id still means the whole MCP server; a scoped `toolId::name` selects one tool of it. Adds apis/shared/tools/scoped_ids.py as the single source of truth and resolves scoped ids at the runtime seam: the tool-filter classifies by base id, expand_gateway_tool_ids emits only the chosen gateway_<target>___<name>, and external MCP clients pass the SDK-native tool_filters allow-list (matched on the raw mcp tool name).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(tools): validate and persist per-tool selections
Skill bound_tool_ids and user tool preferences accept scoped ids: the base catalog tool must exist + be active, and (when the server has a curated list) the named tool must be one it exposes. GET /tools/ now surfaces each MCP server's tools via UserToolAccess.serverTools, each with its effective enabled state (scoped pref -> server-level pref -> catalog default).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(tools): live discover-tools endpoints for saved MCP servers
Adds POST /admin/tools/{id}/discover (admin, skills picker) and POST /tools/{id}/discover (session auth + RBAC, model settings) so per-tool selection works for servers whose tools aren't enumerated in the catalog. External servers are listed live (OAuth-3LO falls back to the curated list); gateway targets return the tools recorded at registration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(admin): per-tool selection in the skills tool picker
The Bind Tools dialog now expands an MCP server into its individual tools with per-tool checkboxes (parent shows all/partial/none), emitting scoped ids in boundToolIds; servers with no curated list get a live Discover tools action. Bound-tool chips render as "Server · tool". Adds the frontend scoped-tool-id helper mirroring the backend.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(settings): per-tool toggles in model settings
An MCP server row in model settings expands into per-tool switches; toggling the server toggles all, toggling a tool switches the server to a subset. enabledToolIds emits scoped ids for a partial server and the bare id when all tools are on. Servers with no enumerated tools offer a live Discover tools action.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final increment of the admin-managed Skills feature. Stacked on PR-6b (#468). The server now defaults a user turn to the SkillAgent instead of ChatAgent (routes.DEFAULT_AGENT_TYPE = "skill"). A user with zero accessible skills gets a SkillAgent that degrades to plain ChatAgent behavior, so the flip is a no-op for them; a user whose roles grant a skill gets that skill's bound tools folded behind the two meta-tools (PR-6a/6b). Clients opt out per turn with agent_type="chat". Implementation: - routes.py: DEFAULT_AGENT_TYPE constant; the invocations() handler computes effective_agent_type = input_data.agent_type or DEFAULT_AGENT_TYPE and uses it for skill resolution + the three non-resume get_agent calls. - The resume get_agent call gates accessible_skill_ids on the snapshot's type (snapshot.agent_type == "skill"), so a turn explicitly built as "chat" that pauses on an interrupt rebuilds the SAME cache key on resume (empty skills_hash) instead of orphaning the paused agent. - service.get_agent keeps a conservative "chat" fallback for direct callers; the request-policy default lives in the route. SPA omits agent_type → gets "skill". toolTokens is measured post-deploy via the context-attribution contextBreakdown partition (folded bound tools drop out of the tools partition; the skill catalog moves into the system partition). Full backend suite: 3712 passed, 3 skipped (1 pre-existing flaky PBT test). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Uncomment the cache_config block in ModelConfig.to_bedrock_config() so caching_enabled=True now emits CacheConfig(strategy="auto"). Strands places cache points per-model and no-ops with a warning for models that don't support automatic caching, so this is safe to set unconditionally on the Bedrock path. The prior deferral was for the strands PR #1438 blocker (cachePoint blocks colliding with non-PDF document attachments), resolved in strands-agents 1.39.0 — we pin 1.40.0. Update the test that asserted the key was omitted to assert CacheConfig(strategy="auto") is present. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cross-referenced the env vars set by the CDK constructs (per tests/supply_chain/test_env_var_contract.py) against what the Python code reads, and added the gap to backend/src/.env.example with placeholder values and explanatory comments matching the existing style. Added (17 vars): - Core wiring: PROJECT_PREFIX, AGENTCORE_RUNTIME_WORKLOAD_NAME, INFERENCE_API_URL, LOG_LEVEL - AgentCore: MEMORY_ARN, AGENTCORE_MEMORY_TYPE, BROWSER_ID, AGENTCORE_LOCAL_OAUTH_CALLBACK_URL - DynamoDB: DYNAMODB_API_KEYS_TABLE_NAME, DYNAMODB_USER_MENU_LINKS_TABLE_NAME - New "Admin-managed Skills" section: S3_SKILL_RESOURCES_BUCKET_NAME - New "Fine-tuning" section: FINE_TUNING_ENABLED, DYNAMODB_FINE_TUNING_ACCESS_TABLE_NAME, DYNAMODB_FINE_TUNING_JOBS_TABLE_NAME, S3_FINE_TUNING_BUCKET_NAME, FINE_TUNING_DEFAULT_QUOTA_HOURS, SAGEMAKER_EXECUTION_ROLE_ARN, SAGEMAKER_SECURITY_GROUP_ID, SAGEMAKER_SUBNET_IDS Intentionally excluded: render-Lambda-internal aliases (ARTIFACTS_BUCKET, ARTIFACTS_TABLE, RENDER_TOKEN_SECRET_ARN, CSP_SCRIPT_SRC, FRAME_ANCESTOR_ORIGIN), the AWS-injected AWS_DEFAULT_REGION, and legacy local-only cruft that nothing reads. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…R-1) (#473) Adds the admin-managed chat-mode policy that the skills-mode feature (docs/specs/skills-mode.md) builds on: which agent mode (skill/chat) new conversations default to, and whether users may toggle between modes. - New apis/shared/platform_settings/ domain — ChatModeSettings stored as a SYSTEM_SETTINGS#chat-mode sentinel item in the auth-providers table (the existing first-boot convention; zero CDK changes since both app-api and the inference runtime already have the table env + IAM), with a 60s TTL-cached service for the per-turn inference read. - GET/PUT /admin/settings/chat (require_admin) with audit stamping. - GET /system/chat-settings — SPA-facing policy read (session auth). - Defaults reproduce current behavior (skill default, toggling allowed), so an unconfigured environment sees no change. Nothing consumes the policy yet — enforcement lands in PR-2. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…lls-mode PR-2) (#474) Backend half of skills mode (docs/specs/skills-mode.md): users get a listable, per-skill-toggleable view of their RBAC-granted skills, and the admin chat-mode policy from PR-1 (#473) is now enforced on /invocations. - New GET /skills/ + PUT /skills/preferences (session auth) — ACTIVE RBAC-accessible skills with per-user prefs merged, mirroring the tools endpoints. Prefs stored as USER#{id}/SKILL_PREFERENCES in the skills table (UserSkillPreference, mirrors UserToolPreference). - Shared resolver apis/shared/skills/access.py — single source of truth for "which skills does this user get" used by both app-api and the inference path (the routes helper stays as a thin test seam). - InvocationRequest.enabled_skills — None = all accessible (back-compat); a list is intersected server-side with the RBAC set (narrow, never grant); the effective set feeds SkillAgent AND the skills_hash cache key, so toggle changes can't cross-pollute cached agents. - Chat-mode policy enforcement: effective agent_type now resolves through the admin settings (TTL-cached). When toggling is disabled the client's skill/chat choice is overridden server-side; voice and other internal types bypass the policy. DEFAULT_AGENT_TYPE is now aliased to the policy model's compiled-in default so they can't drift. - PausedTurnSnapshot.enabled_skills — paused skill turns persist their effective skill set so resume rebuilds the same cache key even if the user toggles skills mid-pause; legacy snapshots fall back to request-time resolution. Full backend suite: 3808 passed, 3 skipped. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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 Sonnet 4.6 <noreply@anthropic.com>
…g (skills-mode PR-3) (#476) * feat(spa): skills mode UX — mode toggle, skills picker, request wiring (skills-mode PR-3) The user-facing half of skills mode (docs/specs/skills-mode.md), on top of the #474 backend: - New SkillService (mirrors ToolService): loads GET /skills/, per-skill optimistic toggles persisted via PUT /skills/preferences. - New ChatModeService: admin policy from GET /system/chat-settings (compiled-in default on failure), effective mode precedence = policy lock > local selection > user preferred mode > admin default. Toggling persists preferredAgentMode (user settings) and the session's agentType so a conversation reopens in the mode it was using. - Model-settings panel: Skills/Tools segmented control (hidden when the admin disallows toggling), Skills toggle section (visual twin of the Tools section, with empty state) in skills mode, Tools section in tools mode. - Chat request: sends agent_type each turn; skills mode sends enabled_skills and enabled_tools=[] (capabilities come from skills); assistant turns are excluded and keep pre-skills-mode behavior. - Session page hydrates the stored mode from session preferences, mirroring the lastModel pattern. - Backend (additive): SessionPreferences.agent_type (validated to skill|chat, merge-safe in the metadata PUT) and user settings preferredAgentMode. Frontend: 1215 tests green (ng test) + production build clean. Backend: full suite green (two pre-existing PBT tests flaked under load in an unrelated area — session-manager truncation — and pass clean in isolation and on file re-run). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(spa): silence the error dialog for the mode toggle's best-effort persist Toggling skills/tools mode persists preferredAgentMode via PUT /users/me/settings, which fails loud (503, #161 behavior) when user-settings storage isn't configured. The toggle already applied in-memory, so the background persist now opts out of the global error toast via the existing SUPPRESS_ERROR_TOAST context token; explicit settings-page saves stay loud. Failure still logs to console. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…MCP tools (#477) In skills mode a skill-bound external MCP tool executes through the skill_executor meta-tool, so OAuthConsentHook's provider_lookup (keyed on the selected tool being an MCPAgentTool) resolved nothing and the consent gate silently never fired: the tool ran tokenless, returned an auth error as text, and the model apologized instead of the turn pausing with an oauth_required interrupt. - OAuthConsentHook: optional tool_use_provider_lookup second-chance resolver (gate + 401-retry handler) consulted when provider_lookup returns None - mcp_binding: make_folded_tool_provider_lookup maps the executor's tool_use input (skill_name/tool_name) -> bound FoldedMCPTool -> owning client -> provider id; gateway clients resolve to None (SigV4, not user OAuth) - FoldedMCPTool.invoke now returns a ToolResult-shaped error on failure so the error status survives the fold and the consent hook's 401-retry heuristic (gated on status == "error") can fire for folded tools - SkillAgent wires the resolver over its registry via the new BaseAgent._build_tool_use_provider_lookup hook point (None for chat) The resume path is unchanged: the interrupt is raised on the skill_executor call itself, so consent -> interrupt_responses -> cache warm -> the client's lazy token provider picks up the token. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… prompt (#478) An admin's needs_approval=True flag was silently bypassed in skills mode: the flagged tool runs behind the skill_executor meta-tool, so the approval hook's selected_tool lookup resolved nothing and the tool_use name never matched. Mirror the OAuth consent fold fix — an optional tool_use-based second-chance lookup resolves the folded target via the SkillRegistry, checks it against the owning client's needs_approval set, and raises the same tool_approval_required interrupt with the inner tool's name + args so the dialog describes the real tool, not the executor. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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>
…se 3 PR-B) Shared, sync helper that resolves a memory_space binding's alwaysLoad specs into injectable text fragments — the read side of Workstream B. - resolve_always_load(): MEMORY.md → index; latest:<type>/<prefix> → most-recent matching manifest entry (defines that scheme, which had no resolver); bare slug → entry. Missing entries skipped (never fails a turn). Byte-budgeted with a truncation marker pointing at memory_read (MEMORY_INJECTION_MAX_BYTES, ~24KB) - render_memory_block(): delimited system-prompt block; empty for a fresh space - reads go through MemorySpaceService (re-checks viewer+ internally) — no leak - 11 unit tests against a fake service Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(Phase 3 PR-C) The Harness now resolves an Agent's memory_space binding against the invoking user and injects the space's alwaysLoad content (read-only) into the system prompt — the first half of the Workstream B / Oliver payoff. - agent_binding_resolver: _resolve_memory() checks the invoker's grant via MemorySpaceService.resolve_permission (D4); blocks (D5) when the flag is off, the space is gone, or a readwrite binding meets a below-editor invoker (no silent read-only downgrade). Returns ResolvedMemoryBinding (v1: first binding) - routes.py: after prompt assembly, hydrate via resolve_always_load (asyncio.to_ thread; MemorySpaceService re-checks viewer+) and append render_memory_block; best-effort — a memory-read hiccup never fails the turn - tests: memory grant matrix (none/flag-off/missing/read-viewer/readwrite-viewer- block/readwrite-editor/invoker-identity); 78 inference+compat tests green Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…signer-harness-resolution feat(agents): Agent Designer Phase 3 — harness resolution (model + read-only memory)
…el agents run-app-api.sh launches app-api with `cd src/apis/app_api && python main.py`, putting that directory on sys.path[0]. The new apis/app_api/agents/ package (Agent Designer surface, #591/#592) then shadowed the top-level `agents` package, so `admin/quota/routes.py`'s `from agents.main_agent...` resolved into it and crashed startup with `ModuleNotFoundError: No module named 'agents.main_agent'`. Tests never caught it — pytest runs from backend/ where `agents` resolves correctly. Production is unaffected (the container runs `uvicorn apis.app_api.main:app` from WORKDIR /app, so sys.path[0] is /app). Rename the package apis/app_api/agents → apis/app_api/agent_designer (and its test dir) so its name can't collide with the top-level `agents` package. Pure rename: the /agents URL surface, router, and behavior are unchanged. Verified: `import agents.main_agent.quota.repository` and the full app-api module now load from src/apis/app_api; 34 agent/boundary tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ge-name-collision fix(agents): rename app_api.agents package to unbreak local app-api startup
…(Phase 3) Completes the Workstream B write side: an Agent with a memory_space binding now gets memory_list / memory_read (always) and memory_write (readwrite bindings only) at invocation — Oliver can read AND write his space. - agents/builtin_tools/memory_spaces/: closure-scoped factories capturing the binding's space id + invoker identity, MemorySpaceService via asyncio.to_thread (artifact-tools pattern). Every call re-checks the grant inside the service (viewer+ read / editor+ write), so a revoked grant becomes an error tool-result mid-session, never a leak - routes.py _build_memory_tools(): appended to the extra_tools seam only when a memory binding resolved; write tool gated on access==readwrite. extra_tools agents are never cached → tools closed over user A can't be served to user B - not gated on enabled_tools: the governing capability is the Agent's binding, not the user's tool picker (same reasoning as artifact tools) - tests: tool success/permission-error/not-found matrix + seam counts (none=0, read=2, readwrite=3); 84 inference+tool tests green Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Memory Spaces is a complete feature (CRUD + SPA panel + agent binding), so it should ship enabled for every deployer/forker — opt-out, not opt-in — matching the kbSync / scheduledRuns convention. The table + bucket are already provisioned unconditionally in PlatformStack, so this only flips the runtime MEMORY_SPACES_ENABLED env var; no new infra footprint. - config.ts: memorySpaces.enabled default true (empty/unset workflow var = on, only literal "false" disables); interface + block docs updated - platform.yml: forward CDK_MEMORY_SPACES_ENABLED (kill switch) and CDK_AGENTS_API_ENABLED (per-env enable for the still-default-off /agents surface, so dev can turn it on for dogfooding without a code change) - app-api-environment.ts: comment reflects default-on - config.test.ts: 5 tests locking default-on + kill-switch + context override Agent Designer (AGENTS_API_ENABLED) stays default OFF until the Phase-4 Designer UI ships — a headless /agents surface helps no forker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mory-tools feat(agents): Agent Designer Phase 3 — memory_* tools (write side)
…paces-default-on chore(memory): default Memory Spaces ON with a kill switch
Ships the Agent Designer authoring surface (Phase 4) and its Phase-2
precursor, the bindable-primitives catalog. The pickers can't exist
without the catalog, so both land together.
Backend (Phase 2 catalog):
- GET /agents/bindable?kind=model|tool|skill|knowledge_base|memory_space
returns an RBAC-filtered palette, composing the 5 existing per-primitive
access services (D4); no new RBAC invented. Uniform BindableItem shape so
every picker consumes one contract. Route declared before /{agent_id} so
the literal path isn't captured. knowledge_base → empty (welded/synthesized);
skill/memory_space → empty when their feature flag is off. Behind
AGENTS_API_ENABLED.
- Fix binding_validation._validate_model: it resolved models via
get_managed_model() — a primary-key lookup on the internal UUID — but
modelConfig.modelId is the Bedrock model_id that the runtime resolver, RBAC
(permissions.models) and invocation all key on. A valid model would have
been rejected 400 on save the moment the picker set one. Now matches by
model_id, consistent with the whole chain.
Frontend (Phase 4 UI):
- New agents/ feature dir (separate from the Assistants editor): Agent +
Binding + BindableItem TS contracts, a thin AgentApiService, and an
AgentService signal facade with the accessible$ 404-probe idiom + a
per-kind bindable cache.
- Agents list page (model + binding-count badges) and an agent-form page:
persona/emoji/tags/starters, a required single-select model picker (D3),
tool/skill multi-select chips, and a memory-space picker with access
(read/read+write — write disabled unless editor+ on the space, per D5) and
an alwaysLoad MEMORY.md toggle. KB shown read-only. Sharing reuses the
assistants share dialog (agentId == assistantId).
- Routes agents / agents/new / agents/:id/edit, plus a sidenav "Agents"
entry gated on the accessible$ probe.
Tests: backend 1552 pass (9 new catalog + 4 new route + 3 updated
model-validation); SPA build + tsc clean, ng test 7 AgentService + 11
sidenav specs pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sidenav constructor now probes agent accessibility (void agentService.loadAgents()), but sidenav.spec.ts didn't provide a mock AgentService, so the real service fired an unstubbed HTTP GET /agents that rejected with status 0 — vitest fails the run on unhandled errors even though all assertions passed. Provide a mock AgentService (accessible$ signal + no-op loadAgents) mirroring the schedule/memory stubs, plus parity tests for the probe + showAgents gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…igner-phase-4-ui-666b5e feat(agent-designer): Phase 4 — Agent Designer UI + bindable catalog API
The catalog lists models via ModelAccessService.filter_accessible_models, but design-time write validation used can_access_model — and the two disagree. filter_accessible_models grants access whenever the model id is in the user's AppRole permissions.models; can_access_model only honors that membership when the model record ALSO carries a non-empty allowed_app_roles. So a model granted purely via the user's AppRole (empty allowed_app_roles) was listed by the picker but rejected on save with a 403 — and the runtime resolver (membership-based) would actually have allowed it. Validate the model with the same filter_accessible_models predicate the catalog uses, so 'if the palette offers it, the write accepts it' holds by construction. Adds a regression test for the empty-allowed_app_roles grant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…igner-phase-4-ui-666b5e fix(agent-designer): model write-check rejects models the picker lists (403)
…ew badges Match the Scheduled Runs treatment for the two other preview surfaces: Memory Spaces and Agents now also require the system_admin AppRole (showX() && isAdmin()) in addition to their accessibility probe. Adds a small amber 'Preview' badge to all three nav entries (Agents, Memory Spaces, Scheduled Runs) so their preview status is visible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…av-admin-gate feat(sidenav): gate Memory Spaces + Agents to system-admin, add Preview badges
…per-invoker RBAC) An Agent's `tool` bindings were stored by the Designer but inert at run time — the free-select tool picker fully drove the toolset regardless of what the Agent bound. This resolves them, mirroring the shipped `modelConfig` override: - Run-time (inference-api): `resolve_agent_invocation` now returns `plan.tools` (`ResolvedTools`). When an Agent binds tools they *replace* the request's `enabled_tools` for the turn; each bound tool is re-checked against the INVOKING user via `AppRoleService.can_access_tool` (the same AppRole gate the harness uses for model, R2) and a missing tool blocks the turn with a message (D5). No tool binding ⇒ `plan.tools is None` ⇒ the request drives the toolset exactly as today. Wired at the existing `extra_tools`/`get_agent` seam via `effective_enabled_tools` (also feeds the spreadsheet/artifact tool gates + attachment guidance/inventory). - Design-time (app-api): `tool` dropped from `_INERT_KINDS`; a bound tool must be in the author's palette (`ToolCatalogService.get_user_accessible_tools`, the same source the picker fetches — "if the palette offers it, the write accepts it", cf. the model check). The palette is resolved once per write. `skill` bindings stay inert here (their run-time fold interacts with agent_type/skill resolution — a follow-up slice). Tests: 6 resolver cases (override, dedupe, block-on-missing, per-invoker, none→passthrough) + 5 validation cases (accessible/inaccessible/empty-ref/fetch-once/lazy). Full backend suite green (4621 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the assistant/agent indicator out of the chat-input footer and into the top nav, beside the session title, so an attached assistant is visible throughout the conversation. - Add a compact 'variant' to app-assistant-indicator: a subtle name-only pill (emoji + name) that opens the same actions menu (New session / Edit / Share) on click. The full card style is preserved behind variant="card". - Add a menuPlacement input so the actions dropdown opens downward in the top nav instead of clipping off-screen. - Thread the assistant/owner/loading state and action outputs from the chat container into app-topnav; render the pill (with a loading shimmer) to the right of the title. - Remove the now-orphaned footer indicator and loading skeletons from the full-page chat container (embedded preview footer left intact). - Assistant card: move conversation starters into a collapsible accordion (expanded by default) to keep the card compact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ol-binding-resolution feat(topnav): surface active assistant in the top nav
Feature release adding four capabilities on shared, RBAC-governed primitives — Scheduled Runs (unattended/proactive agent runs), Memory Spaces (bindable, shareable markdown memory), the Agent Designer (primitive-binding authoring surface), and Knowledge Base Sync (scheduled re-index) — plus a chat/session UX overhaul (per-conversation streaming, interrupted-turn persistence, mid-stream session titles). All new surfaces are flag-gated; no breaking changes and no migration. - Bump VERSION to 1.1.0; sync manifests + lockfiles - CHANGELOG.md and RELEASE_NOTES.md 1.1.0 entries - Requires a platform (CDK) deploy for new resources (memory-spaces table/bucket, GSIs, kb-sync + scheduled-runs Lambda images, EventBridge rules)
| def test_owner_deletes(self, service, monkeypatch): | ||
| client = _client(service, monkeypatch) | ||
| sid = client.post("/memory/spaces", json={"name": "X"}).json()["spaceId"] | ||
| assert client.delete(f"/memory/spaces/{sid}").status_code == 204 |
| typed = client.get(f"/memory/spaces/{sid}/entries?type=fact").json()["entries"] | ||
| assert typed == [] | ||
|
|
||
| assert client.delete(f"/memory/spaces/{sid}/entries/jane-doe").status_code == 204 |
| # the list reports the member's actual grant, not a placeholder | ||
| assert listed[0]["role"] == "viewer" | ||
| # member DELETE = leave, not destroy | ||
| assert member_client.delete(f"/memory/spaces/{sid}").status_code == 204 |
Comment on lines
+283
to
+286
| assert ( | ||
| owner.delete(f"/memory/spaces/{sid}/shares/{STRANGER.email}").status_code | ||
| == 204 | ||
| ) |
| client, _, _ = _make_client(monkeypatch, flag="false") | ||
| assert client.post("/runs/now", json={"prompt": "hi"}).status_code == 404 | ||
| assert client.get("/runs/grant").status_code == 404 | ||
| assert client.delete("/runs/grant").status_code == 404 |
| def test_revoke_grant(self, monkeypatch): | ||
| client, grants, _ = _make_client(monkeypatch, grants=FakeGrantService(_grant())) | ||
|
|
||
| assert client.delete("/runs/grant").json() == {"revoked": True} |
|
|
||
| assert client.delete("/runs/grant").json() == {"revoked": True} | ||
| assert grants.revoke_calls == ["user-1"] | ||
| assert client.delete("/runs/grant").json() == {"revoked": False} |
| assert client.get("/schedules").status_code == 404 | ||
| assert client.post("/schedules", json={}).status_code == 404 | ||
| assert client.get(f"/schedules/{SCHEDULE_ID}").status_code == 404 | ||
| assert client.delete(f"/schedules/{SCHEDULE_ID}").status_code == 404 |
| client = _make_client(monkeypatch) | ||
| monkeypatch.setattr(schedules_routes, "get_scheduled_prompt", AsyncMock(return_value=None)) | ||
|
|
||
| assert client.delete(f"/schedules/{SCHEDULE_ID}").status_code == 404 |
| touching the runner. | ||
| """ | ||
|
|
||
| async def mint_bearer_for_user(self, user_id: str) -> str: ... |
# Conflicts: # CHANGELOG.md # README.md # RELEASE_NOTES.md # VERSION # backend/pyproject.toml # backend/uv.lock # frontend/ai.client/package-lock.json # frontend/ai.client/package.json # infrastructure/package-lock.json # infrastructure/package.json
| except ValueError as e: | ||
| raise HTTPException(status_code=400, detail=str(e)) | ||
| except Exception as e: | ||
| logger.error(f"Error listing bindable '{kind}': {e}", exc_info=True) |
| except MemorySpaceError as e: | ||
| raise _translate(e) | ||
| except MemorySpaceStoreError as e: | ||
| logger.error("memory-spaces: export failed for space=%s: %s", space_id, e) |
| except MemorySpaceError as e: | ||
| raise _translate(e) | ||
| except MemorySpaceStoreError as e: | ||
| logger.error("memory-spaces: consolidate failed for space=%s: %s", space_id, e) |
| if await bump_last_used_at(input_data.rag_assistant_id): | ||
| await resume_inactive_policies(input_data.rag_assistant_id) | ||
| except Exception as bump_err: | ||
| logger.warning(f"lastUsedAt bump failed for assistant {input_data.rag_assistant_id}: {bump_err}") |
| logger = logging.getLogger(__name__) | ||
| logger.setLevel(logging.INFO) | ||
|
|
||
| _NATIVE_PREFIX = "application/vnd.google-apps." |
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any, Dict, List, Optional |
| touching the runner. | ||
| """ | ||
|
|
||
| async def mint_bearer_for_user(self, user_id: str) -> str: ... |
| import logging | ||
| import os | ||
| from datetime import datetime, timezone | ||
| from typing import Any, Dict, Optional |
|
|
||
| DEFAULT_MAX_SCHEDULES_PER_USER = 20 | ||
|
|
||
| _WEEKDAY_CADENCES = {"weekday"} # Monday-Friday |
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.
see release notes