Loom is an agent builder playground that simplifies the lifecycle of building, testing, integrating, deploying, and operating AI agents built on Amazon Bedrock AgentCore Runtime and AWS Strands Agents. The platform consists of:
- A FastAPI backend that encapsulates all AWS interactions and business logic.
- A React/TypeScript frontend (Vite, shadcn, Tailwind CSS) that interacts exclusively through the backend API.
- A relational database (via SQLAlchemy) — SQLite for local development or PostgreSQL for cloud deployments — for persisting agent metadata, session history, security configurations, and credential management.
The platform tracks session liveness using a local idle timeout heuristic, providing cold-start indicators so users know whether their next invocation will incur agent startup latency.
The frontend is organized around persona-based workflows, accessible via a sidebar:
- Platform Catalog (default) — Browse and manage agents, memory resources, and other platform resources. Includes sections for MCP Servers and A2A Agents.
- Agents — Deploy new agents or import existing ones. Includes agent listing with card/table view toggle.
- Security Admin — Manage IAM roles, authorizer configurations, credentials, and permission requests.
- Memory — Create new AgentCore Memory resources with configurable strategies or import existing ones.
- Tagging — Manage tag policies (platform + custom) and tag profiles. Accessible to all scopes; write operations require
*:write. - Settings — Manage display preferences (theme, timezone). Accessible to all scopes.
- MCP Servers — Register and manage MCP servers, view available tools, and control persona access.
- A2A Agents — Register and manage A2A (Agent-to-Agent) protocol integrations, view Agent Cards, and control persona access to skills.
- Registry (opt-in) — Browse and manage AWS Agent Registry records for governance and discovery. When enabled, agents are auto-registered on deployment and must be approved before end-users can access them. MCP servers and A2A agents must also be approved before they can be used in agent deployments. Supports full record lifecycle (create, submit, approve, reject, delete) and semantic search.
- Admin Dashboard — Platform usage analytics for super-admins. Tracks user logins, user actions, and page navigation at the browser session level. Includes summary cards, charts, and per-session drill-down.
loom/
├── agents/ # Agent blueprint source code
│ └── strands_agent/ # Strands Agent blueprint
│ ├── handler.py # Agent handler / entry point (trace_invocation wrapped)
│ ├── config.py # Agent configuration
│ ├── integrations/ # Tool and service integrations
│ │ ├── mcp_client.py # MCP tool client vending
│ │ ├── a2a_client.py # A2A agent client vending
│ │ └── memory.py # AgentCore Memory hooks (MemoryHook)
│ └── telemetry.py # OTEL setup, ADOT auto-instrumentation, TelemetryHook
├── backend/ # Backend API (see backend/SPECIFICATIONS.md)
│ ├── app/
│ │ ├── main.py # FastAPI app (docs at /api/docs, /api/redoc, /api/openapi.json)
│ │ ├── db.py
│ │ ├── models/
│ │ ├── dependencies/
│ │ ├── routers/
│ │ └── services/
│ ├── scripts/
│ ├── tests/
│ ├── etc/ # Backend environment config (app + ECS backend service)
│ │ ├── environment.sh # Sources account-specific file + shared outputs
│ │ ├── models.json # Supported model catalog (model_id, display_name, group, pricing)
│ │ └── runtime_pricing.json # AgentCore Runtime pricing constants
│ ├── iac/ # Backend infrastructure
│ │ ├── rds.yaml # RDS PostgreSQL with optional RDS Proxy
│ │ ├── ec2.yaml # EC2 bastion for SSM tunnel to RDS
│ │ └── ecs.yaml # Backend ECS Fargate service (task def, service, auto-scaling)
│ ├── Dockerfile # Backend container image (Python 3.13 slim + uvicorn)
│ ├── makefile
│ └── SPECIFICATIONS.md
├── frontend/ # Frontend UI (see frontend/SPECIFICATIONS.md)
│ ├── src/
│ ├── etc/ # Frontend environment config (ECS frontend service)
│ │ └── environment.sh # Sources account-specific file + shared outputs
│ ├── iac/
│ │ └── ecs.yaml # Frontend ECS Fargate service (task def, service)
│ ├── Dockerfile # Frontend container image (multi-stage Node + nginx)
│ ├── nginx.conf # nginx SPA config with gzip and cache headers
│ ├── makefile
│ └── SPECIFICATIONS.md
├── shared/ # Shared IaC, deployment, and security
│ ├── iac/
│ │ ├── role.yaml # SAM template for IAM roles
│ │ ├── cognito.yaml # SAM template for Cognito pools, groups, users, scopes
│ │ ├── dns.yaml # Route 53 hosted zone for subdomain delegation
│ │ ├── infra.yaml # Shared infra: S3, ECR, ACM, ALB, security groups, Route 53
│ │ └── ecs.yaml # ECS Fargate cluster (shared by frontend and backend)
│ ├── etc/
│ │ ├── environment.sh # Sources account-specific config
│ │ └── outputs_<profile>.sh # Centralized stack outputs (auto-generated)
│ ├── scripts/
│ │ └── capture_outputs.py # Query all stacks, write outputs + frontend/.env
│ └── makefile # Cognito, infra, ECS, podman build/push, deploy
├── CLAUDE.md
├── README.md
└── SPECIFICATIONS.md # This file (project-level specification)
Detailed specifications for each component are maintained in their respective directories:
- Backend:
backend/SPECIFICATIONS.md— API endpoints, database schema, service modules, streaming architecture, latency measurement flow, security management, memory resource management, tag policies and profiles. - Frontend:
frontend/SPECIFICATIONS.md— Technology stack, persona-based navigation, Platform Catalog/Agents/Security Admin/Memory/Settings workflows, streaming behavior.
- No credentials, tokens, or secrets are committed to git.
etc/environment.shand.envfiles are listed in.gitignore.- The backend uses the standard boto3 credential chain (environment variables, AWS profile, instance metadata) — no hardcoded credentials.
- All AWS API calls follow least-privilege IAM.
- CORS is configured to allow
localhost:{FRONTEND_PORT}in development. When deployed, theLOOM_ALLOWED_ORIGINSenvironment variable adds additional allowed origins (e.g., the ALB domain). - Cognito client secrets are stored in AWS Secrets Manager, never in the local database.
- The backend retrieves secrets at invocation time with in-memory caching (5-minute TTL).
- Secrets are cleaned up from Secrets Manager when authorizer credentials or agents are deleted.
- Security administration (roles, authorizers, credentials, permissions) is managed through a dedicated persona workflow.
- Human-in-the-loop (HITL) approval policies enforce human oversight for sensitive tool calls. Four HITL methods are supported: agentic loop hooks (custom agents), tool context interrupts (custom agents), MCP elicitation (custom agents with MCP), and harness inline functions (managed agents).
- On-behalf-of (OBO) token exchange (RFC 8693) enables agents to access downstream OAuth2 resources with the invoking user's scoped permissions. Configurable per MCP server and A2A agent via
delegation_mode(m2m or obo).
- Users authenticate via an AWS Cognito User Pool using the
USER_PASSWORD_AUTHflow. - The frontend stores tokens (id, access, refresh) in React state only — never in localStorage or cookies.
- The backend validates user JWTs against the Cognito JWKS endpoint (keys cached for 1 hour).
- The
GET /api/auth/configendpoint exposes only the pool ID and region — never client IDs or secrets. - The user client ID is configured on the frontend via the
VITE_COGNITO_USER_CLIENT_IDenvironment variable (Vite.envfile). The user client hasGenerateSecret: falsesince browser-based apps cannot safely store client secrets. - When a user is authenticated, their access token is forwarded to OAuth-protected AgentCore agents. The backend auto-includes the user app client ID in the agent's
allowedClientson deploy. M2M credentials remain available for service-to-service integrations. - Unauthenticated requests are allowed to pass through with a warning (no breaking change to existing flows).
- The
NEW_PASSWORD_REQUIREDCognito challenge is handled on first login for admin-created users. - Access tokens are automatically refreshed before expiry using the refresh token.
- On 401 responses, the frontend automatically refreshes the access token and retries the failed request.
- Token persistence across browser refreshes is out of scope — users must re-login after page reload.
The Cognito User Pool is managed via CloudFormation (shared/iac/cognito.yaml) and includes:
- Password policy: Minimum 12 characters, uppercase, lowercase, numbers required; symbols not required.
- Resource server scopes (21 total):
invoke,catalog:read,catalog:write,agent:read,agent:write,memory:read,memory:write,security:read,security:write,settings:read,settings:write,tagging:read,tagging:write,costs:read,costs:write,mcp:read,mcp:write,a2a:read,a2a:write,registry:read,registry:write. - Two-dimensional group architecture:
- Type groups (UI view):
t-admin(admin UI with all pages),t-user(user UI with Catalog, Agents, Memory, Costs only) - Admin groups (t-admin users, single group):
g-admins-super(all scopes),g-admins-demo(read/write to most pages including MCP and A2A + demo group resources),g-admins-security(security:read/write, settings:read, tagging:read/write),g-admins-memory(memory:read/write, settings:read, tagging:read/write),g-admins-mcp(mcp:read/write, settings:read, tagging:read/write),g-admins-a2a(a2a:read/write, settings:read, tagging:read/write),g-admins-registry(mcp:read, a2a:read, registry:read/write, settings:read/write, tagging:read) - User groups (t-user users, can have multiple):
g-users-demo,g-users-test,g-users-strategics(each grants: catalog:read, agent:read, memory:read, costs:read, costs:write, invoke)
- Type groups (UI view):
- Users:
admin(t-admin + g-admins-super),demo-admin(t-admin + g-admins-demo),security-admin(t-admin + g-admins-security),integration-admin(t-admin + g-admins-memory + g-admins-mcp + g-admins-a2a),registry-admin(t-admin + g-admins-registry),demo-user(t-user + g-users-demo) — each assigned to both type and group viaUserPoolUserToGroupAttachment. - Clients:
- M2MClient —
client_credentialsflow with secret, scoped toinvoke. - UserClient —
USER_PASSWORD_AUTH+REFRESH_TOKEN_AUTHflows without secret, scoped to all custom scopes plusopenid,email,profile.
- M2MClient —
- User passwords are set via
make cognito.set-passwordsin theshared/directory.
The frontend enforces scope-based access control derived from Cognito group membership:
| Group | Scopes | Sidebar Access | Write Access |
|---|---|---|---|
g-admins-super |
All 21 scopes | All pages (including Admin Dashboard) | All actions |
g-admins-demo |
catalog:read, agent:read, agent:write, memory:read, memory:write, security:read, settings:read, settings:write, tagging:read, costs:read, costs:write, mcp:read, mcp:write, a2a:read, a2a:write, invoke | All admin pages | Read/write restricted to demo group resources only |
g-admins-security |
security:read, security:write, settings:read, tagging:read, tagging:write | Security, Settings, Tagging | Security + tag policy/profile management |
g-admins-memory |
memory:read, memory:write, settings:read, tagging:read, tagging:write | Memory, Settings, Tagging | Memory + tag policy/profile management |
g-admins-mcp |
mcp:read, mcp:write, settings:read, tagging:read, tagging:write | MCP Servers, Settings, Tagging | MCP + tag policy/profile management |
g-admins-a2a |
a2a:read, a2a:write, settings:read, tagging:read, tagging:write | A2A Agents, Settings, Tagging | A2A + tag policy/profile management |
g-admins-registry |
mcp:read, a2a:read, registry:read, registry:write, settings:read, settings:write, tagging:read | Registry, Settings, Tagging | Registry governance + settings management |
g-users-* |
catalog:read, agent:read, memory:read, costs:read, costs:write, invoke | Catalog, Agents, Memory, Costs | No write access; costs:write for cost settings only |
- Sidebar visibility: Each sidebar item is shown only when the user has the corresponding
*:reador*:writescope. The Platform Catalog is always visible. Type groups determine the overall UI view (t-admin sees all admin pages, t-user sees only Catalog/Agents/Memory/Costs). - Write protection: Components receive a
readOnlyprop that disables or hides add, edit, and delete buttons when the user lacks*:writescopes. Demo-admins see delete buttons only for resources tagged withloom:group=demo. - Resource filtering: Admins (t-admin) see all resources including untagged. Users (t-user) see only resources matching their group tags (union semantics for multiple groups).
- Tag profile management: Only super-admins can edit/delete custom tag policies. Demo-admins can only edit/delete tag profiles with
loom:group=demo. - Bypass mode: When authentication is not configured (no Cognito pool ID or client ID), all scopes are granted and all features are accessible.
Model metadata (display names, groups, pricing) and AgentCore Runtime pricing are maintained in external JSON configuration files (backend/etc/models.json and backend/etc/runtime_pricing.json), loaded at backend startup. Models are organized by vendor group and sorted alphabetically in the UI via the groupModels() utility.
Anthropic:
- Claude Opus 4.7 (
anthropic.claude-opus-4-7) - Claude Opus 4.6 (
us.anthropic.claude-opus-4-6-v1) - Claude Sonnet 4.6 (
us.anthropic.claude-sonnet-4-6) - Claude Opus 4.5 (
us.anthropic.claude-opus-4-5-20251101-v1:0) - Claude Sonnet 4.5 (
us.anthropic.claude-sonnet-4-5-20250929-v1:0) - Claude Haiku 4.5 (
us.anthropic.claude-haiku-4-5-20251001-v1:0)
Amazon:
- Nova 2 Lite (
us.amazon.nova-2-lite-v1:0) - Nova Pro (
us.amazon.nova-pro-v1:0) - Nova Lite (
us.amazon.nova-lite-v1:0) - Nova Micro (
us.amazon.nova-micro-v1:0)
DeepSeek:
- DeepSeek v3.2 (
deepseek.v3.2) - DeepSeek-R1 (
deepseek.r1-v1:0)
Google:
- Gemma 3 12B IT (
google.gemma-3-12b-it) - Gemma 3 27B PT (
google.gemma-3-27b-it) - Gemma 3 4B IT (
google.gemma-3-4b-it)
Meta:
- Llama 4 Maverick 17B Instruct (
meta.llama4-maverick-17b-instruct-v1:0) - Llama 4 Scout 17B Instruct (
meta.llama4-scout-17b-instruct-v1:0) - Llama 3.3 70B Instruct (
meta.llama3-3-70b-instruct-v1:0)
MiniMax:
- MiniMax M2.5 (
minimax.minimax-m2.5) - MiniMax M2.1 (
minimax.minimax-m2.1) - MiniMax M2 (
minimax.minimax-m2)
Moonshot AI:
- Kimi K2.5 (
moonshotai.kimi-k2.5) - Kimi K2 Thinking (
moonshot.kimi-k2-thinking)
Model selectors in the UI are searchable by both display name and model ID, with grouped sections sorted alphabetically by vendor. No default is pre-selected — the user must explicitly choose a model.
Administrators can restrict which models are available for agent deployment and runtime selection via the Settings page ("Enabled Models" section). The enabled_model_ids site setting stores a JSON array of allowed model IDs. When the list is empty (default), all models are available. The GET /api/agents/models endpoint filters the full model catalog by this setting before returning results. The GET /api/settings/models and PUT /api/settings/models endpoints manage the configuration (requires settings:read / settings:write scopes).
The list above is Bedrock's curated catalog. When a self-hosted LiteLLM proxy connection is configured (Settings → Models → LiteLLM), agents can instead be deployed with provider="litellm", and the model catalog is extended with whatever models the proxy itself reports live (GET /api/agents/models/litellm) — no Loom code changes needed to add a new model, since the proxy owns that catalog. See Phase 32 below and backend/SPECIFICATIONS.md § 16 for the full design (provider registry, per-agent virtual key vending, dynamic catalog merging, IAM/IaC).
Agents support runtime model selection, allowing users to choose from a set of allowed models at invoke time rather than being locked to a single model:
allowed_model_ids— A JSON array stored on theagentstable specifying which models the agent can use. Defaults to[model_id](the deploy/register model) when not explicitly set.- Deploy form — An "Allowed Models (runtime selection)" checkbox section appears after selecting the default model. The deploy model is always included and cannot be unchecked.
- Agent detail / Deployment panel — Displays allowed models grouped by vendor with an inline edit mode (pencil icon) for updating the allowed list and default model post-deployment.
- Invoke panel — A model dropdown appears when the agent has multiple allowed models. The dropdown is disabled when only one model is available. Selecting a non-default model passes
model_idin the invoke request. - ChatPage — A model picker button appears in the input area footer when the agent has multiple allowed models. Click to open a dropdown; selecting a model applies it to subsequent invocations.
- Backend validation —
POST /api/agents/{id}/invokevalidates the optionalmodel_idparameter against the agent'sallowed_model_ids. Returns HTTP 400 if the model is not in the allowed list. The validated model overrides the agent's default for that invocation. - PATCH endpoint —
PATCH /api/agents/{id}acceptsmodel_idandallowed_model_idsfields for updating model configuration. Description changes are propagated to AgentCore viaupdate_runtime.
- Backend: Agent registration, metadata retrieval, SSE invocation with real-time streaming, CloudWatch log retrieval (stream browsing + session-filtered with pagination), integrated cold-start latency calculation, SQLite persistence with session/invocation separation, session liveness tracking via idle timeout heuristic, active session count per agent.
- CLI: Streaming invocation client (
scripts/stream.py) and comprehensivemakefiletargets for manual testing. - Frontend: Build tab (ARN registration), Test tab (invocation + streaming + latency display), Operate tab (basic dashboard), active session count display on agent cards, session live status indicators.
- Refactored
tmp/latency/into reusable service modules.
- Agent deployment to AgentCore Runtime from the Strands Agent blueprint.
- Auto-build artifact pipeline (pip cross-compile for ARM64, S3 upload, zip packaging).
- Configurable deploy form: model selection (grouped, searchable), protocol (HTTP; MCP/A2A coming soon), network mode (PUBLIC; VPC coming soon), IAM role (searchable select or auto-create), authorizer (Cognito JWT with auto-populated discovery URL, or custom OIDC provider), lifecycle timeouts, integrations (coming soon).
- Cognito OAuth2 token retrieval for authenticated agent invocations (client credentials grant).
- Secret management via AWS Secrets Manager for Cognito client secrets.
- Agent deletion with optional AgentCore cleanup (runtime + endpoint removal).
- Account ID extraction from runtime ARN on deploy and refresh.
- Persona-based frontend navigation: Platform Catalog, Agents, Security Admin, Memory, MCP Servers, A2A Agents.
- Platform Catalog page with sections for agents, memory resources, MCP servers, and A2A agents. Card/table view toggle with cards as default.
- Agents page (formerly Builder) with agent listing (card/table view toggle), Add Agent button with Deploy/Import tabs.
- Security Admin page for managing IAM roles, authorizer configs, authorizer credentials, and permission requests.
- Model selector on both register and deploy forms with no default selection.
- Model ID tracked on agent responses for display on invoke page.
- Credential-based invocation: select a credential from an authorizer config to generate an OAuth token at invoke time.
- Token indicator on invoke responses (
has_token,token_sourcein SSE session_start). - Configurable session defaults via
LOOM_SESSION_IDLE_TIMEOUT_SECONDSandLOOM_SESSION_MAX_LIFETIME_SECONDSenvironment variables, exposed via/api/agents/defaults.
- Backend API for creating, managing, and deleting AgentCore Memory resources.
- Memory strategies: semantic, summary, user_preference, episodic, and custom — mapped to AWS tagged union format.
- Local SQLite persistence for memory resource metadata with status tracking.
- Refresh endpoint to poll AWS for latest memory status.
- AWS error mapping: ValidationException→400, ConflictException→409, ResourceNotFoundException→404, AccessDeniedException→403, ThrottledException/ServiceQuotaExceededException→429.
- Makefile curl targets for manual testing of all memory endpoints.
- Frontend Memory persona: memory card and table views with card/table toggle (cards default), create form with strategy configuration, status badges, refresh and delete actions, toast notifications for all operations.
- Memory import endpoint (POST /api/memories/import) for importing existing AgentCore Memory resources.
- Async deletion flow: backend returns DELETING status, frontend polls for updates, detects 404 when resource is fully deleted, then purges locally.
- "Also delete in AgentCore" checkbox on memory deletion for optional upstream cleanup.
- Timer persistence across navigation using server timestamps.
- Purge endpoint (DELETE /api/memories/{id}/purge) for local database cleanup after confirmed deletion.
- View mode (card/table) state lifted to App.tsx and persisted per-page across persona switches.
- Deployment entry point wrapped with
opentelemetry-instrumentCLI for ADOT auto-instrumentation of boto3, HTTP clients, and other libraries at process startup. TelemetryHookon the Strands Agent that creates OTEL spans for tool calls and model invocations as children of the invocation span.trace_invocation()wraps each handler invocation with a root span carryingagent.session_idandagent.invocation_idattributes.- Noop mode when running locally without the
opentelemetry-instrumentwrapper — no errors, no performance overhead. OTEL_SERVICE_NAMEis automatically set to the agent name at deploy time.AGENT_OBSERVABILITY_ENABLEDis set totrueat deploy time, which activates theaws-opentelemetry-distroexport pipeline (OTEL traces exported to CloudWatch logs/metrics).- Console script shebang fix: the build pipeline rewrites
opentelemetry-instrument(andopentelemetry-bootstrap) scripts with a portable#!/usr/bin/env python3shebang so they execute correctly on the Linux-based AgentCore Runtime container. - Unit tests for telemetry setup idempotency, span creation, hook lifecycle, shebang fix, and noop operation.
- AgentCore Memory Integration:
MemoryHookis a StrandsHookProviderthat registersBeforeInvocationEventandAfterInvocationEventcallbacks for automatic memory operations. Before invocation: retrieves memory records viaretrieve_memory_recordsusing the last user message as search query. After invocation: creates events in memory for each message in the conversation viacreate_event. EmitsLOOM_MEMORY_TELEMETRY: retrievals=N, events_sent=Mstructured log line for cost tracking (always emitted, even when counters are 0). All operations logged at INFO level for visibility. Graceful degradation: AccessDeniedException and other errors are caught and logged without interrupting the agent invocation.
- Cognito-based user authentication with
USER_PASSWORD_AUTHflow. - Login page with
NEW_PASSWORD_REQUIREDchallenge handling for admin-created users. AuthContextprovider with login, logout, and automatic token refresh.- User indicator (username) and logout button in the sidebar.
- JWT validation middleware on the backend (JWKS caching, token claim extraction).
- User access token forwarded to AgentCore for authenticated invocations (priority over M2M flow).
- Graceful fallback to existing M2M client credentials flow when no user token is present.
GET /api/auth/configendpoint returns pool ID and region; user client ID is configured on the frontend viaVITE_COGNITO_USER_CLIENT_ID.- Tokens stored in memory only (not localStorage); authentication does not persist across page reloads.
- Cognito User Pool IaC: resource server with custom scopes (15 initial scopes, later expanded to 21:
invoke,catalog:read/write,agent:read/write,memory:read/write,security:read/write,settings:read/write,tagging:read/write,costs:read/write,mcp:read/write,a2a:read/write,registry:read/write), user groups, users with group assignments, password policy (12+ chars, no symbols required). - Security makefile with
cognito.set-passwordstarget for setting permanent user passwords. - Scope-based frontend authorization:
AuthContextextractscognito:groupsfrom the ID token, maps groups to scopes, and exposeshasScope(). Sidebar items are conditionally rendered based on user scopes. Write operations (add, edit, delete buttons) are disabled or hidden via areadOnlyprop when the user lacks*:writescopes. When auth is not configured, all scopes are granted.
- Configurable tag policy system:
TagPolicymodel with key, default_value, required, and show_on_card fields. Two-tier designation:platform:required(keys starting withloom:) andcustom:optional(all others). Designation is computed from the key, not stored. - Tag policy CRUD API under
/api/settings/tagswith default seed data (loom:application, loom:group, loom:owner). - Tag profile system:
TagProfilemodel for named sets of tag values. CRUD API under/api/settings/tag-profiles. Profiles satisfy required tag policies and are applied to all deployed resources. ResourceTagFieldsshared component: fetches tag policies and profiles, renders a profile dropdown withsessionStoragepersistence (loom:selectedTagProfileId), resolves tags from the selected profile + policy defaults, and passes resolved tags to the parent form viaonChange. Used by both the agent deploy form and memory create form.- Unified tag resolution: for each policy, use user-supplied value → fall back to
default_value→ error if required and missing. Required tag validation before deployment — missing tags return HTTP 400. - All AWS resources that support tags (AgentCore runtimes, runtime endpoints, IAM execution roles, managed roles, memory resources) receive the resolved tags.
- Memory resources:
tagscolumn added to thememoriestable. Tags are resolved from tag policies + selected profile on creation, passed to AWScreate_memory, and stored locally. Imported memories fetch existing tags from AWS vialist_tags_for_resourceand enforce tag policies (missing required tags default to "missing"). - Registered agents fetch existing tags from AWS via
list_tags_for_resourceand enforce tag policies (missing required tags default to "missing"). - Resolved tags stored on Agent and Memory records as JSON columns, included in API responses.
- Agent and memory cards display tag badges (
variant="secondary") for tags withshow_on_card=true. - All listing pages (Platform Catalog, Agents, Memory) provide tag-based filtering with multi-select dropdowns (checkbox-based), AND logic, clear button, and item count display.
- Settings persona: new sidebar entry accessible to all scopes.
SettingsPageprovides tag profile CRUD.*:writescopes can create, edit, and delete profiles;*:readscopes can only view. Tag value inputs enforce a 128-character maximum length. - 29 backend tests covering tag policy CRUD, tag designation, tag resolution, validation, and agent tag storage.
- Theme system:
ThemeContextwith 10 themes — 5 light (Ayu Light, Catppuccin Latte, Everforest Light, Rosé Pine Dawn, Solarized Light) and 5 dark (Ayu Dark, Catppuccin Mocha, Dracula, Nord, Tokyo Night). Theme selector on Settings page with Light/Dark grouping. CSS variables per theme inindex.css. Latte is the default (no class); other themes use class selectors.localStoragepersistence. - Theme accessibility: darkened
foreground/muted-foreground/borderfor all light themes for better readability. Brightenedforeground/muted-foreground/borderfor all dark themes. Badgeborder-borderadded to default and secondary badge variants for visibility. - Settings page: moved theme and timezone selectors from sidebar to Settings preferences section. Description updated to "Manage settings and tag profiles."
- Drag-to-reorder cards:
@dnd-kitfor card reordering in grid sections (SortableCardGridcomponent), order persisted tolocalStorage. - Admin role view switching: sidebar dropdown (Eye icon) to test other role experiences while retaining admin access.
effectiveHasScopeoverrideshasScopefor UI rendering. - Deploy flow: fire-and-forget pattern — form collapses immediately, shows agent card with creating status. Background API call with error toast on failure. No 45-second blocking.
- Agent card two-phase creation: shows deploying → completing deployment → finalizing endpoint status with spinner and timer. Timer format: spinner (Ns) message. Timer uses
registered_atto avoid reset on phase transition. Two-row header layout. - Memory card: matching two-row header layout with spinner/timer for creating/deleting states.
- Polling stability:
initialLoadDoneref prevents skeleton flash on refetches.watchIds-based polling effect dependency to prevent interval teardown on state updates. Removed redundant polling fromAgentListPage. - JSON paste: handles
model,role,authorizer, andnetwork_modefields in addition toname/description/persona/instructions/behavior. - Credential suggestion on errors:
friendlyInvokeErroraccepts optionalauthorizerName, suggests correct authorizer on 401/403 errors. - Catalog page: removed unnecessary refresh button from Memory Resources section.
- Documentation:
frontend/.env.exampletemplate, README title updated.
- Dedicated Tagging page: tag profile management extracted from Settings into a new
TaggingPagecomponent with its own sidebar entry (Tags icon, visible to all scopes). Drag-to-reorder viaSortableCardGridfor both tag policies and tag profiles. - Custom tag policy management:
platform:requiredtags shown as read-only cards with lock icon (top-right) and designation badge;custom:optionaltags are editable (pencil icon) and deletable (Trash2 icon, top-right) with designation badge. "Add Custom Tag" form with key, default value, and show-on-card toggle. Custom tags are alwaysrequired=false. - Tag profile form with two sections: Platform (Required) with mandatory input fields for all
platform:requiredtags, and Custom (Optional) with checkbox-to-enable pattern per custom tag (checking reveals a value input, unchecking removes from profile). - Simplified tag model: removed
source(build-time/deploy-time) distinction. Tag resolution: for each policy, use user-supplied value → fall back todefault_value(only for required policies) → error if required and missing. Custom/optional tags only appear when the profile explicitly sets them. - Progressive disclosure tag filtering: on Catalog, Agents, and Memory pages, required tag filters are always shown; custom tag filters are hidden until added via a custom
AddFilterDropdowncomponent. Filter bar layout: required filters → eyeball toggle → activated custom filters → "custom filters" Add dropdown → Clear filters → count. All label rows use fixedh-4height for visual alignment. Filter state (tagFiltersandactiveCustomFilterKeys) persisted tolocalStorageper page and survives navigation. - Custom tag show/hide toggle: Eye/EyeOff button on filter bars (positioned left of custom filter dropdown) toggles visibility of custom tags on agent and memory cards. Preference persisted to
localStorage(loom:showCustomTags). - Card layout consistency: Trash2 icon for delete across all cards (agents, memory, roles, authorizers, tags). Edit/delete icons positioned top-right as lightweight
<button>elements. Delete confirmation right-aligned at card bottom. - Table consistency: all tables use
table-fixedwith matching percentage-based column widths (30%/12%/14%/14%/14%/16%). Action columns removed from all tables — delete/refresh operations are card-view only. - Security card consistency: RoleManagementPanel and AuthorizerManagementPanel cards use the same top-right icon pattern (pencil + trash for authorizers, trash for roles). Tags aligned with content via
ml-6offset. - Backend:
tagscolumn added tomanaged_rolesandauthorizer_configstables. IAM role import fetches tags vialist_role_tags.environment.sh.examplefiles added for backend and security directories. - Pydantic error handling:
apiFetchhandles array-styledetailresponses (Pydantic validation errors) by joiningmsgfields. - Settings page simplified to display preferences only (theme + timezone).
- Two-dimensional group architecture: Type groups (
t-admin,t-user) define UI view; Group membership (g-admins-*,g-users-*) grants scopes. Users belong to both a type group and one or more resource groups. - Expanded scope model from 15 to 19 scopes: added
tagging:read/writefor tag policy/profile management,costs:read/writefor cost dashboard access. Scopes at this phase:invoke,catalog:read/write,agent:read/write,memory:read/write,security:read/write,settings:read/write,tagging:read/write,costs:read/write,mcp:read/write,a2a:read/write. (Later expanded to 21 in Phase 22 withregistry:read/write.) - Updated Cognito group structure:
- Type groups:
t-admin(admin UI),t-user(user UI) - Admin groups:
g-admins-super(all scopes),g-admins-demo(read/write to most pages including MCP/A2A + demo group resources),g-admins-security,g-admins-memory,g-admins-mcp,g-admins-a2a - User groups:
g-users-demo,g-users-test,g-users-strategics(each grants catalog:read, agent:read, memory:read, costs:read, costs:write, invoke)
- Type groups:
- Updated user assignments:
admin→ t-admin + g-admins-super,demo-admin→ t-admin + g-admins-demo,security-admin→ t-admin + g-admins-security,integration-admin→ t-admin + g-admins-memory + g-admins-mcp + g-admins-a2a,demo-user→ t-user + g-users-demo - Backend OAuth2 enforcement: every router endpoint guarded by
require_scopes()dependency with OpenAPI scope annotations viaSecurity().get_current_uservalidates JWT, extractscognito:groups, and derives scopes viaGROUP_SCOPESmapping. Returns 401 for missing/invalid tokens, 403 for insufficient scopes. - Group-based invoke restriction:
g-admins-supercan invoke any agent; other admins and users can only invoke agents whoseloom:grouptag matches their group (stripsg-admins-org-users-prefix for comparison). - Resource filtering: Admins (t-admin) see all resources including untagged. Users (t-user) see only resources matching their group tags (union semantics for multiple groups).
- Demo-admin write restrictions:
g-admins-demousers can only create/delete agents, memory resources, MCP servers, and A2A agents tagged withloom:group=demo. Delete buttons hidden on cards for resources outside their group. - Tag policy management:
list_tag_policies()andlist_tag_profiles()require authentication but no specific scope (needed for card display). Onlyg-admins-supercan edit/delete custom tag policies. Demo-admins can edit/delete tag profiles only withloom:group=demo. - Token forwarding for agent invocation: user's login token is forwarded to OAuth-protected agents (shared Cognito pool). Token priority: manual bearer token > M2M credential > user login token > agent config M2M > SigV4 (no token).
- Auto-include user app client ID (
LOOM_COGNITO_USER_CLIENT_ID) in agent authorizerallowedClientson deploy, so user login tokens are accepted by the agent runtime. - Credential dropdown shows context-aware options: OAuth agents show user's token (default), M2M credentials, and manual token; non-OAuth agents show "No credentials (SigV4)" only.
- Auto-select newly created session in the session dropdown after an invocation.
- Automatic 401 token refresh:
apiFetchintercepts 401 responses, refreshes the Cognito access token, and retries the request transparently. - Re-fetch agent list after authentication to prevent empty state on hard refresh.
- Frontend
AuthContextandApp.tsxupdated with matchingGROUP_SCOPESmapping. View As selector changed from group-based to user-based (admin, demo-admin, security-admin, etc.) for more realistic role simulation. - Default page on login: users are routed to their first accessible page based on available scopes (e.g., security-admin → Security page, demo-user → Catalog page).
- Group restriction on
ResourceTagFields: demo-admins only see tag profiles matching their group, andloom:grouptag is forced to their group value. Tag profile selector: "None" option removed, users must select a profile. - Sidebar overflow fix: sidebar and main content use proper scroll containment.
- Bypass mode preserved: when
LOOM_COGNITO_USER_POOL_IDis not set, all scopes are granted (local development). - Comprehensive test updates for new group structure in
test_scopes.pyandtest_costs.py.
- Shared
JsonConfigSectioncomponent for collapsible JSON import/export on forms. Encapsulates toggle, textarea, and Apply/Export/Cancel buttons. - Agent deploy form (
AgentRegistrationForm): refactored to useJsonConfigSection. Export serializes form state to JSON with human-readable names (model ID, role name, authorizer name, tag profile name). Import mapsname,description,persona,instructions,behavior,model,role,network_mode,authorizer,tags. - Memory create form (
MemoryManagementPanel): addedJsonConfigSectionwith import/export. Import mapsname,description,event_expiry_duration(validated 3-364),tags(tag profile name lookup),strategies(array with strategy_type validation against semantic/summary/user_preference/episodic/custom). Export serializes current form state; empty/default fields omitted. Memory namespace changed from array with TagInput to singular string textbox for simpler UX and import compatibility. - Round-trip capable: exported JSON is valid input for import, reproducing the same form state.
- Consistent visual behavior across both forms: same collapse/expand toggle, textarea styling, and button layout.
- Agent async deletion polling: agent DELETE endpoint returns
AgentResponsewith DELETING status (instead of 204) whencleanup_aws=trueand agent has a runtime. FrontenduseAgentshook polls DELETING agents at 5-second intervals; on 404, calls the new purge endpoint (DELETE /api/agents/{id}/purge) to clean up locally. Agent cards show spinner and elapsed timer during deletion, matching the memory deletion pattern. NewdeleteStartTimesstate tracks deletion initiation timestamps for accurate timer display.
- Default alphabetical sorting (case-insensitive, A-Z) for all card grids on initial load when no persisted custom order exists.
- Standalone
SortButtoncomponent (A-Z / Z-A toggle) placed inline with section headers, next to "Add" buttons. Sort preference persisted to localStorage per grid (loom-sort-${storageKey}). - After drag-to-reorder, custom order takes precedence and sort direction is cleared.
- New items not in persisted order are sorted alphabetically among themselves and appended after persisted items.
SortableCardGriduses controlledsortDirectionprop withonSortDirectionChangecallback. Exported helpers:loadSortDirection(),saveSortDirection(),toggleSortDirection(),SortButton,SortDirection.SortableTableHeadcomponent for clickable sortable table column headers with arrow indicators (ArrowUp/ArrowDown).sortRows()helper for generic multi-column sorting (string and numeric).- Table view column sorting: pages with table views (CatalogPage, AgentListPage, MemoryManagementPanel) support click-to-sort on any column header.
- Security admin panels (RoleManagementPanel, AuthorizerManagementPanel, PermissionRequestsPanel) converted from stacked
<div className="space-y-2">layouts toSortableCardGridwith drag-to-reorder and alphabetical sort controls. AuthorizerManagementPanel and PermissionRequestsPanel use responsive grid (md:grid-cols-2 lg:grid-cols-3); RoleManagementPanel uses full-width single-column layout since role cards contain long ARNs and expandable policy documents. - All existing card grid consumers updated: CatalogPage (agents, memories), AgentListPage (agents), MemoryManagementPanel (memories), TaggingPage (policies, profiles).
- Backend:
McpServer,McpTool,McpServerAccessORM models with full CRUD API under/api/mcp/servers. MCP server registration with name, endpoint URL, and transport type (SSE or Streamable HTTP). OAuth2 authentication configuration with well-known URL, client ID, client secret (write-only), and scopes. Conditional validation: OAuth2 fields required when auth_type isoauth2. Client secrets never returned in GET responses (has_oauth2_secretflag instead). - Tool discovery:
GET /api/mcp/servers/{id}/toolsreturns cached tools,POST /api/mcp/servers/{id}/tools/refreshfetches from server (stub implementation). Each tool stores name, description, and input schema (JSON). - Access control:
GET/PUT /api/mcp/servers/{id}/accessmanages per-persona access rules. Access levels:all_tools(any tool including future ones) orselected_tools(specific tool names). Disabled by default — no rules means all agents have access. When rules exist, only listed agents have access. Auto-grant: when deploying an agent with MCP/A2A associations, if access rules already exist, the new agent is automatically added withall_tools/all_skillsaccess. - Connection test:
POST /api/mcp/servers/{id}/test-connectionvalidates OAuth2 configuration (stub for actual MCP connectivity). - Frontend:
McpServersPagewith card/table view toggle, sortable columns, server detail view with Tools/Access tabs.McpServerFormwith progressive OAuth2 field disclosure.McpToolListwith refresh, collapsible input schema display.McpAccessControlwith per-agent toggle, all_tools/selected_tools radio, individual tool checkboxes. - MCP Servers sidebar item activated (no longer disabled/coming soon). Scope-gated by
mcp:read/mcp:write. - Agent deployment with MCP server selection: multi-select dropdown on deploy form allows selecting MCP servers from catalog. Selected servers attached to agent during deployment.
- OAuth2 credential provider creation: for OAuth2-enabled MCP servers, backend calls AgentCore
create_oauth2_credential_providerAPI usingCustomOauth2vendor withdiscoveryUrlfrom server configuration. Credential providers auto-named{agent_name}-mcp-{server_name}. Credential provider creation uses exponential backoff retry (4 retries, delays 2s/4s/8s/16s). Deployment fails with credential_creation_failed status if all retries are exhausted. - Background deployment with progressive status updates: deploy endpoint returns immediately with
creating_credentialsstatus. Background task progresses throughcreating_role,building_artifact,deployingphases with DB updates. Frontend polls at 2-second intervals. - Frontend progressive deployment status display: agent cards show human-readable status messages (Creating credential provider, Creating IAM role, Building artifact, Deploying runtime, Completing deployment, Finalizing endpoint) with spinner and elapsed timer.
- Smart polling optimization: frontend skips AWS API calls during
creating_credentials,creating_role, andbuilding_artifactphases (local operations only), reducing unnecessary backend load. - Credential provider cascade delete: when agents are deleted, associated OAuth2 credential providers are automatically cleaned up via AgentCore API.
- Agent runtime deferred MCP client initialization: OAuth2 MCP clients are initialized at invocation time (not handler startup) since workload tokens are only available during active requests.
- Agent runtime
_OAuth2Authhttpx handler: exchanges ephemeral workload token for downstream OAuth2 access token via AgentCore Identity service M2M flow. Workload token retrieved fromAWS_CONTAINER_AUTHORIZATION_TOKEN_FILEenvironment variable. - Agent deletion with background polling: delete endpoint returns
DELETINGstatus. Background task polls AgentCore until runtime deletion completes, then purges local DB record. Endpoint status badge hidden during deletion. - 25 backend tests covering all CRUD operations, validation, secret exclusion, OAuth2 conditional fields, tools, access rules, and cascade delete.
- Backend:
A2aAgent,A2aAgentSkill,A2aAgentAccessORM models with full CRUD API under/api/a2a/agents. A2A agent registration by base URL with automatic Agent Card fetching from<base_url>/.well-known/agent.json. Agent Card data cached locally: name, description, version, provider, capabilities, authentication schemes, input/output modes, and raw JSON. Skills parsed and stored in a separate table for queryability. - OAuth2 authentication configuration: well-known URL, client ID, client secret (write-only), and scopes. Conditional validation: OAuth2 fields required when auth_type is
oauth2. Client secrets never returned in GET responses (has_oauth2_secretflag instead). - Agent Card endpoints:
GET /api/a2a/agents/{id}/cardreturns cached raw Agent Card JSON.POST /api/a2a/agents/{id}/card/refreshre-fetches from remote agent, updates all cached fields and syncs skills. Failed refresh preserves existing cached data. - Skills endpoint:
GET /api/a2a/agents/{id}/skillsreturns skills parsed from the Agent Card. Skills synced on registration and on card refresh (add new, remove stale). - Access control:
GET/PUT /api/a2a/agents/{id}/accessmanages per-persona access rules. Access levels:all_skills(any skill including future ones) orselected_skills(specific skill IDs). Deny by default. - Connection test:
POST /api/a2a/agents/{id}/test-connectionacquires OAuth2 token if configured and fetches the Agent Card. - Agent deployment with A2A integration: multi-select dropdown on deploy form allows selecting A2A agents from catalog. Selected agents attached to agent during deployment. OAuth2-enabled A2A agents get credential providers created with exponential backoff retry. Credential provider names follow pattern
loom-{agent_name}-a2a-{a2a_name}. Deployment fails withcredential_creation_failedstatus if credential provider creation exhausts retries. - Agent deployment with memory integration: multi-select dropdown on deploy form allows selecting memory resources from catalog. Selected memory IDs and names passed in
AGENT_CONFIG_JSONunderintegrations.memory.resources. - A2A runtime client (
agents/strands_agent/src/integrations/a2a_client.py):_AuthenticatedA2AAgentsubclass of the Strands SDKA2AAgentfor OAuth2-protected A2A endpoints. Injects OAuth2 Bearer tokens via AgentCore Identity service into both agent card fetches and message sending. Handles both SSE (text/event-stream) and plain JSON responses. Falls back frommessage/streamtomessage/sendon "Method not found". BuffersMessageevents and yields them afterTaskevents sostream_asyncpicks the content-bearingMessageaslast_complete_event. Each enabled A2A agent in the configuration is wrapped as a@toolfunction that the orchestrating agent can invoke during conversation. - Agent deletion cascade: credential providers for both MCP and A2A integrations are cleaned up. Explicit session/invocation deletion in all delete paths (immediate, background, purge) as safety net alongside ORM cascades.
- Frontend:
A2aAgentsPagewith card/table view toggle, sortable columns, agent detail view with Agent Card/Access tabs.A2aAgentFormwith base URL input and progressive OAuth2 field disclosure.A2aAgentCardViewwith structured display of capabilities (enabled/disabled badges), authentication schemes, input/output modes, and skills list.A2aSkillListwith expandable skill cards showing tags, examples, and mode overrides.A2aAccessControlwith per-persona toggle, all_skills/selected_skills radio, individual skill checkboxes with descriptions. - A2A Agents sidebar item activated (no longer disabled/coming soon). Scope-gated by
a2a:read/a2a:write. - Frontend state management: clearing stale session/invocation state on agent selection and deletion.
useSessionshook clears sessions immediately when agent changes before fetching new data. - Frontend status display:
credential_creation_faileddeployment status mapped to destructive badge variant. - JSON import/export: agent deploy form supports
a2a_agentsandmemoriesarrays (names) in JSON configuration alongsidemcp_servers. - 26 backend tests covering CRUD operations, Agent Card fetching, skill sync, card refresh, secret exclusion, OAuth2 validation, access rules, and cascade delete.
- Backend schema:
input_tokens,output_tokens,estimated_cost,compute_cost,compute_cpu_cost,compute_memory_cost,idle_timeout_cost,idle_cpu_cost,idle_memory_cost,memory_retrievals,memory_events_sent,memory_estimated_cost,stm_cost,ltm_cost,cost_sourcecolumns on theinvocationstable via SQLAlchemy migration (_migrate_add_columns). - Token estimation: 4 characters per token heuristic used since AgentCore doesn't expose token counts directly. Applied to both prompt and response text.
- Cost calculation:
(input_tokens / 1000 * input_price_per_1k_tokens) + (output_tokens / 1000 * output_price_per_1k_tokens)using per-model pricing data. - Model pricing metadata:
SUPPORTED_MODELSextended withinput_price_per_1k_tokens,output_price_per_1k_tokens, andpricing_as_offields for all models (Anthropic and Amazon). - AgentCore Runtime pricing constant:
AGENTCORE_RUNTIME_PRICINGtracks CPU ($0.0895/vCPU-hour), Memory ($0.00945/GB-hour), default vCPU (1), default memory (0.5 GB), and default idle timeout (900 seconds). - View-time cost recomputation: Runtime CPU and memory costs are recomputed from
client_duration_msat view time using current pricing defaults (1 vCPU, 0.5 GB), so changing defaults retroactively affects all historical data._apply_view_time_costs()applies the I/O wait discount to CPU costs._backfill_idle_costs()always recomputes idle costs from session gaps to correct stale values. - CPU I/O Wait Discount: Single configurable site setting (
cpu_io_wait_discount, default 75%) applied universally to runtime CPU costs across both estimates and actuals. Configurable on the Settings page. Stored as integer percentage (0-99). - Cost estimation formulas:
Runtime CPU = hours × 1 vCPU × $0.0895 × (1 − I/O wait%),Runtime Mem = hours × 0.5 GB × $0.00945,Idle Mem = idle_seconds × 0.5 GB × $0.00945 / 3600. - New endpoints:
GET /api/agents/models/pricingreturns models with pricing metadata;GET /api/dashboard/costsprovides estimated cost aggregation with group filtering, time-range filtering (7d/30d/90d/all), and per-agent breakdown;POST /api/dashboard/costs/actualspulls actual runtime and memory costs from CloudWatch logs. - CloudWatch log retrieval strategies: (1) stream-name matching for session-specific streams (fetches all events), (2) filterPattern fallback for shared streams. Both use nextToken pagination for complete data retrieval.
- Vended log sources: log viewer dropdown includes runtime APPLICATION_LOGS, runtime USAGE_LOGS, and memory APPLICATION_LOGS as selectable vended log sources with display labels and last event timestamps. Stream timestamps respect the user's timezone preference.
- Cost dashboard sections: Estimated Costs table with per-agent breakdown (Model Tokens, AgentCore Runtime CPU+Mem, AgentCore Memory STM+LTM, Per Invoke, Total) with inline sub-details. Actual Costs split into Runtime and Memory sub-sections. Runtime: collapsible agent groups with per-session detail rows, subtotals per agent, sortable at agent level. Memory: consolidated per-resource table with columns Log Events, Extractions, Consolidations, LTM Retrievals, Records Stored, Total. Actuals are cached in module-level state to persist across page navigation.
- Runtime actuals session filtering: Only sessions tracked in Loom's
invocation_sessionstable are shown, filtering out external invocations against the same runtime. - Runtime actuals aggregation: CloudWatch usage log events (1-second granularity) are aggregated by
(agent_name, session_id)tuple fromattributes.agent.nameandattributes.session.id. Timestamps normalized from epoch milliseconds or ISO strings to UTC ISO 8601. Delivery of usage logs can be delayed up to 15 minutes. - Memory actuals: Parsed from
BedrockAgentCoreMemory_ApplicationLogsvended log group. Memory pipeline session IDs are internal to AgentCore and do NOT correlate with runtime session IDs — they represent asynchronous extraction/consolidation/storage pipeline runs.parse_memory_log_events()mapsbody.logmessages to pricing operations. - Cost summary in responses:
AgentResponseincludescost_summaryfield withtotal_input_tokens,total_output_tokens,total_model_cost,total_runtime_cost,total_memory_cost,total_cost, andtotal_invocations. - SSE streaming:
session_endevent includes token counts and estimated cost for immediate display after invocation completes. - Settings page: CPU I/O Wait Discount input with description and save-on-blur behavior.
- Database integrity:
PRAGMA foreign_keys=ONadded to all test engines for proper SQLite FK enforcement. Explicit cascade delete for invocations before sessions to prevent FK constraint violations. - Frontend invocation metrics: InvocationTable expanded to 7 columns — Client Invoke, Agent Start, Cold Start, Duration, Input Tokens, Output Tokens, Est. Cost — all displayed in a single row.
- Frontend agent cards: cost badge displayed when
total_estimated_cost > 0. READY status badge hidden to reduce clutter. Memory cards hide ACTIVE status badge. - Frontend cost dashboard: new
CostDashboardPagewith time-range selector (7d/30d/90d/All), summary cards (Total Cost, Model Tokens, Runtime, Memory), estimated costs table with sortable columns and methodology formulas, actual costs section with separate Runtime (collapsible agent groups) and Memory (consolidated per-resource) sub-sections, Pull Actuals button with loading timer. Platform catalog page includes estimates disclaimer. - Costs sidebar item: new navigation entry (above Settings) visible to users with
catalog:readscope.
- Backend OTEL log parsing service (
services/otel.py): fetches OTEL log records from the CloudWatchotel-rt-logsstream viafilter_log_events. Parses JSON log bodies containingtraceId,spanId,observedTimeUnixNano,body(string or dict with input/output),attributes(withsession.id), andscope.name. Bodies with bothinputandoutputkeys are split into two separate events for accurate event counting. Groups events by trace ID for list view and by span ID for detail view. - Backend traces router (
routers/traces.py):GET /api/agents/{id}/sessions/{sid}/tracesfetches all OTEL events from the log stream (single fetch, no filter), then filters bysession.idin Python. Returns trace summaries with trace ID, start/end time, duration, span count, and event count.GET /api/agents/{id}/traces/{tid}fetches events filtered by trace ID and returns full trace detail with per-span event lists, scopes, and timing. - Frontend Logs/Traces tabbed layout on
SessionDetailPageusing shadcn Tabs. Logs tab preserves existing log viewer unchanged. Traces tab shows trace list (Trace ID, Start Time, End Time, Duration, Spans, Events) with lazy loading on first tab activation. Description text prompts users to click a trace ID for detail. - Interactive
TraceGraphcomponent: CSS-based waterfall timeline with colored horizontal bars showing span durations relative to trace start. 8-color palette for span differentiation. Hover over spans reveals a persistent detail panel (Span ID, Scope, Duration, Events, Start/End times). Click-to-select spans with event detail expansion. Left panel shows span list with divide styling; right panel shows events with expand/collapse all toggle. Event detail lines show timestamp, span ID link, and scope/source. InvocationDetailPageextended with Traces tab showing traces scoped to that session.- 12 backend tests covering trace router endpoints (7) and OTEL log parsing (5), including string body handling and input/output splitting.
- Admin Dashboard global user filter: Multi-select dropdown in the dashboard header allowing super-admins to selectively include/exclude specific users from all reporting. When a filter is active, summary cards (Total Logins, Total Page Views, Total Actions, Total Duration, Most Active Page), charts (Logins Over Time, Actions Over Time, Page Views), and all tab tables (Sessions, Actions, Page Views) are recomputed client-side from the filtered data. When no users are selected, all data is shown as before.
- Table column consistency: Agent and memory tables updated to reduce Status column to 10% and add an Estimated Cost column at 12%. MCP Server and A2A Agent tables aligned to a consistent 5-column structure: Name (18%), Endpoint/URL (46%), Transport/Version (10%), Auth (10%), Created (16%). A2A tables removed the Provider and Status columns to match the MCP structure. All column widths sum to 100% across Catalog, standalone MCP Servers, and A2A Agents pages.
- Tag visibility at CREATING status: Agent tags are now applied to the DB record immediately after initial record creation (before the background deployment task begins). This ensures tag-based resource filtering (e.g., demo users filtered by
loom:group) can see agents from the moment they appear in CREATING status. - Logout sessionStorage cleanup: On logout, all
loom:invokePrompt:*keys are cleared fromsessionStorageso per-agent prompt drafts do not persist across sessions. - Deploy flow: Reverted to fire-and-forget pattern where the form collapses immediately and the real agent card appears once the DB record is created. Removed the ephemeral pending-card approach in favor of the direct DB-record polling flow.
- End-user routing:
App.tsxdetects the type group of the authenticated user (or admin in "View as" mode). Users int-user(withoutt-admin) are routed toChatPageinstead of the admin layout. Admins can preview the end-user experience by selecting anydemo-user-*ortest-userin the "View as" dropdown. - ChatPage layout: Two-column layout — narrow left sidebar and main chat area, with an optional right memory panel. The sidebar contains: logo, agent picker (shown when multiple agents exist), "New Conversation" button, conversation history list, "My Memory" button (when agent has memory), and user info/logout.
- Agent filtering: Agents are filtered by
loom:grouptag vs. the user'sg-users-*group names. Agents with no group tag are visible to all users. A single accessible agent is auto-selected. - Chat interface: Alternating user (right, primary) and assistant (left, muted) message bubbles. In-flight messages (user prompt + streaming assistant response) displayed during streaming. Animated cursor while streaming. Markdown rendering with
react-markdown+remark-gfm: paragraphs, headings, lists, tables, blockquotes, inline code, fenced code blocks, bold, and links. JSON code blocks rendered as collapsibleCollapsibleJsonBlockcomponents. Error display inline in the chat area. Enter to send, Shift+Enter for newline. Max-width content centering on wide screens. - Streaming: Reuses
useInvokehook withqualifier: "DEFAULT"and no credential selection. Streaming indicator (isCurrentlyStreaming) is scoped to the active session — it isfalsewhen viewing a different conversation while a stream is in progress, preventing the thinking/spinner from appearing in unrelated conversations. - Session tab created immediately: The conversation sidebar entry is created as soon as the
session_startSSE event fires (not aftersession_end). The new session is also auto-selected and highlighted in the sidebar at that point. This means the conversation tab appears the moment the agent acknowledges the invocation, rather than waiting for the full response. - Session management: New conversations start without a session ID; the first invocation creates a new
InvocationSession. Subsequent messages reuse the session ID fromsessionEnd. Resuming a past session rebuilds message history frominvocation.prompt_text/invocation.response_textpairs. OnsessionEnd, messages are loaded authoritatively fromgetSession()rather than assembled from in-memory streaming state, preventing content loss on finalization.setPendingPrompt(null)is deferred until aftersetMessages()completes so the in-flight bubbles remain visible during the backend fetch. - Conversation removal: Removing a conversation calls
hideSession()then records aremove_conversationaudit action viatrackAction. If the removed session is currently active, the chat area is cleared. - User isolation: Session list filtered to the authenticated user's sessions (
user_idmatch). Admin details (session IDs, qualifiers, credentials, bearer tokens) are not exposed in the end-user interface. - Memory panel: Accessible via "My Memory" button when the agent has memory resources. Displays Session Memory (current exchange count + custom strategy names/descriptions) and "What I Remember About You" (long-term strategies: semantic, summary, user_preference, episodic). No memory IDs, ARNs, namespaces, strategy types, or configuration objects shown.
- View-as banner: When an admin is previewing the end-user experience via "View as", a non-destructive banner indicates the preview mode with an "Exit preview" button.
useInvokesubscription stability:clearInvokeState()resets the module-level store toEMPTYand notifies all subscribers, but deliberately does NOT remove the subscriber set for the agent. This keeps the React component subscribed across "New Conversation" resets so that subsequent invocations correctly propagate streaming state updates to the UI.
- RDS PostgreSQL support: SAM template (
iac/rds.yaml) for RDS PostgreSQL with optional RDS Proxy, multi-AZ, IAM database authentication, and Secrets Manager integration (secrets encrypted with a dedicated KMS CMK managed in the same stack). Dialect-aware engine configuration indb.py(SQLite vs PostgreSQL)._migrate_add_columnshelper handles both dialects. Migration script (scripts/migrate_sqlite_to_postgres.py) with topological sort for foreign-key dependency order. Sequence auto-repair script (scripts/fix_sequences.py) for PostgreSQL after migration. Database reset script (scripts/reset_db.py). - Shared infrastructure stack: SAM template (
iac/infra.yaml) for long-lived resources: S3 artifact bucket (versioning, AES256 encryption, public access block, access logging to a pre-existing logging bucket), ECR repositories (frontend + backend, KMS CMK-encrypted, with lifecycle policies), ACM certificate (DNS validation via Route 53), ALB (internet-facing, HTTPS-only with TLS 1.3, path-based routing), security groups (ALB + ECS), target groups (frontend port 80, backend port 8000), and Route 53 A-record alias for the ALB. - EC2 SSM tunnel: SAM template (
iac/ec2.yaml) for an EC2 bastion instance in a private subnet with no public IP, using SSM Session Manager (via VPC endpoints) for tunneling to RDS. Makefiletunneltarget configures port forwarding viaaws ssm start-session. - Multi-account environment configuration:
backend/etc/environment.shsources account-specific files (e.g.,environment_9582.sh,environment_1527.sh) containing AWS profile, account ID, VPC/subnet IDs, RDS parameters, and stack names. Enables managing multiple AWS accounts from the same codebase. - CountTokens API integration:
services/tokens.pyuses the Bedrockcount_tokensAPI for accurate token counting. Provider guard restricts API calls to supported providers (Anthropic, Meta); all other models fall back to the 4 chars/token heuristic. - Background usage poller:
services/usage_poller.pyruns every 10 minutes, finds invocations withcost_source="estimated", polls USAGE_LOGS from CloudWatch, matches events to invocations by timestamp (within 5 seconds), and updates costs from estimated to actual (cost_source="usage_logs"). - Observability service:
services/observability.pyenables USAGE_LOGS and APPLICATION_LOGS delivery for agent runtimes via CloudWatch vended log delivery APIs (put_delivery_source,put_delivery_destination,create_delivery). - AgentCore credential management: Makefile targets for listing and bulk-deleting AgentCore OAuth2 credential providers.
- Infrastructure makefile targets: Shared makefile (
shared/makefile):infra,ecs,docker.*,deploytargets for cross-cutting infrastructure and container deployment. Backend makefile:rds,ec2,tunneltargets for database infrastructure.migrate-db,fix-sequences,reset-dbtargets for database operations. - Pull Actuals session ID fix: Removed
tracked_session_idsfilter frompull_cost_actualsincosts.py. USAGE_LOGS use internal AgentCore session IDs that do not match Loom'sruntimeSessionId, so session-based filtering dropped all events. Events are now scoped by time window and runtime ID only. - Tagging UX improvements: Collapsible tag profile groups in
TaggingPage(platform required vs custom optional sections). Form-fill import from tag policies (auto-populates profile form with policy defaults). Tag policy and profile sorting. Sidebar entry renamed from "Tagging" to "Tags". - Admin dashboard fixes: Fixed
page_views_by_pagetype mismatch that caused dashboard crash. Custom tooltips on all recharts charts for consistent styling. Theme picker moved from Settings page to admin sidebar for easier access.
- Containerization:
backend/Dockerfile(Python 3.13-slim, non-root user, uvicorn on port 8000) andfrontend/Dockerfile(multi-stage: Node 20 build + nginx Alpine serving on port 80).frontend/nginx.confwith SPA fallback routing, gzip compression, and immutable asset caching..dockerignorefiles exclude.env,node_modules,dist, and development artifacts. - Dynamic API base URL:
frontend/src/api/client.tsreadsVITE_API_BASE_URLfrom Vite build-time env using nullish coalescing (??) so empty string (same-origin) works in production while falling back tohttp://localhost:8000for local dev. Injected viaARG/ENVin the frontend Dockerfile. - Cognito client ID injection:
VITE_COGNITO_USER_CLIENT_IDis passed as a Docker build arg duringpodman.build.frontend(sourced fromO_COGNITO_USER_CLIENT_IDin the outputs file). Required because.dockerignoreexcludes.env. - FastAPI docs under /api:
docs_url="/api/docs",redoc_url="/api/redoc",openapi_url="/api/openapi.json"so API documentation is accessible through the ALB's/api/*path-based routing rule. - Multi-stack deployment architecture: 10 CloudFormation stacks across 3 directories:
shared/iac/dns.yaml— Route 53 hosted zone for subdomain delegationshared/iac/infra.yaml— S3, ECR, ACM, ALB, security groups, target groups, Route 53 A-recordshared/iac/cognito.yaml— Cognito User Pool, groups, scopes, usersshared/iac/role.yaml— IAM execution roles for agentsshared/iac/ecs.yaml— ECS Fargate cluster (shared by frontend and backend)frontend/iac/ecs.yaml— Frontend ECS service (task def, service, public subnets, port 80)backend/iac/ecs.yaml— Backend ECS service (task def, task role, service, auto-scaling, private subnets, port 8000)backend/iac/rds.yaml— RDS PostgreSQL with optional RDS Proxybackend/iac/ec2.yaml— EC2 bastion for SSM tunneling
- Centralized stack outputs:
shared/scripts/capture_outputs.pyqueries all stacks and writesO_*variables toshared/etc/outputs_<profile>.sh. This single file is included by all environment files across shared, frontend, and backend directories. Also writesVITE_COGNITO_USER_CLIENT_IDtofrontend/.env. - Git SHA image tagging:
IMAGE_TAG := $(shell git rev-parse --short HEAD)used in all podman build, tag, and push commands. ECR URIs stored without tags in outputs (O_ECR_FRONTEND_URI,O_ECR_BACKEND_URI); tags appended dynamically at deploy time as$(O_ECR_*_URI):$(IMAGE_TAG). - Cross-platform container builds:
--platform linux/amd64on all podman build commands to ensure ECS Fargate compatibility when building on ARM64 Macs (M-series). - Split ECS services: Cluster is shared (
shared/iac/ecs.yaml); frontend and backend have independent ECS service stacks in their respectiveiac/directories. Each has its own environment config inetc/, includingecs.*makefile targets. - 4-phase deployment: Phase 0 (DNS + delegation + ECS and AgentCore service-linked roles) → Phase 1 (foundation stacks in parallel) → Phase 2 (capture outputs) → Phase 3 (container build + push + ECS deploy).
- Granular deployment targets:
shared/makefilesupportsdeploy(full),deploy.frontend, anddeploy.backendfor independent container build+push+deploy. Podman targets:podman.build.*,podman.push.*,podman.login. - ALB with HTTPS: HTTPS-only listener (port 443) with ACM certificate (DNS-validated via Route 53), TLS 1.3 policy. Port 80 is not exposed. Path-based routing:
/api/*and/healthto backend target group, default to frontend. Route 53 A-record alias for the ALB domain. Health check interval: 60 seconds. - ECR repositories: CloudFormation-managed ECR repos (frontend + backend) encrypted with a dedicated KMS CMK (
EcrKmsKeyininfra.yaml, with auto-rotation enabled), scan-on-push, and lifecycle policies (keep last 10 images). - ECS Fargate configuration: Fargate and Fargate Spot capacity providers on the cluster. Configurable task sizes via environment variables. Backend service includes auto-scaling (CPU target tracking at 70%).
- Security groups: ALB allows inbound HTTPS (443) from anywhere (suppressed intentionally — internet-facing ALB); ECS tasks allow inbound only from ALB security group on ports 80 (frontend) and 8000 (backend).
- IAM roles: Frontend: execution role (ECR pull + KMS decrypt for ECR CMK, CloudWatch Logs). Backend: execution role (ECR pull + KMS decrypt for ECR CMK, CloudWatch Logs, Secrets Manager + KMS decrypt for secrets CMK) + task role (Bedrock, Bedrock AgentCore, S3, CloudWatch Logs, IAM PassRole, Secrets Manager + KMS, Cognito, CloudFormation).
- RDS IAM authentication:
EnableIAMDatabaseAuthentication: trueon the RDS instance. Password-based authentication (via Secrets Manager) remains fully functional alongside IAM auth. - Dynamic CORS:
LOOM_ALLOWED_ORIGINSenv var (comma-separated) adds origins to the default localhost entries. Backend reads this for CORSMiddleware configuration. - Database URL injection:
LOOM_DATABASE_URLinjected via ECS Secrets (ValueFrom) referencing the RDS stack's Secrets Manager ARN with JSON key extraction. - Cross-account DNS delegation: DNS stack creates a Route 53 hosted zone; if the parent domain is in a different account, NS delegation records must be added in the parent account before deploying the infra stack.
- Scope expansion from 19 to 21: Added
registry:readandregistry:writescopes for Agent Registry governance. Registry endpoints usemcp:readfor GET andmcp:writefor POST/PUT/DELETE as the underlying scope enforcement. - New Cognito group:
g-admins-registrywith scopesmcp:read,a2a:read,registry:read,registry:write,settings:read,settings:write,tagging:read. Enables dedicated registry governance administrators. - Registry sidebar visibility gating: The Registry sidebar entry is visible only when the user has
registry:readorregistry:writescope, ensuring non-registry users do not see the governance UI. registryEnabledprop pattern: All frontend pages that display registry UI elements (RegistryStatusBadge, RegistryActions) fetchgetRegistryConfig()on mount and thread aregistryEnabledboolean through their components. When registry is disabled, all registry badges and action buttons are hidden — RegistryStatusBadge returns null whenregistryEnabled=false, and RegistryActions rendering is gated byregistryEnabled &&at all render sites.- Registry status badges across all pages: RegistryStatusBadge added to CatalogPage (agents, MCP servers, A2A agents sections), AgentListPage, McpServersPage, and A2aAgentsPage in both card and table views. All instances pass
registryEnabled={registryEnabled}to hide when disabled. - AgentCard
registryEnabledprop: AgentCard acceptsregistryEnabled(default true) and passes it to its embedded RegistryStatusBadge, allowing the catalog and agent list pages to control badge visibility. - Collapsible catalog sections: CatalogPage sections (Agents, Memory Resources, MCP Servers, A2A Agents) are collapsible via ChevronRight/ChevronDown toggles. Collapse state persisted to
localStorageunderloom:collapsedSections:catalog. - Backend registry status sync: When registry is re-enabled via the Settings page,
_sync_registry_statuses()validates all storedregistry_record_idvalues across Agent, McpServer, and A2aAgent models against the live registry. Records that no longer exist are cleared; status mismatches are updated. This prevents stale governance data after a disable/re-enable cycle.
- Model catalog externalization:
SUPPORTED_MODELSandAGENTCORE_RUNTIME_PRICINGextracted from inline Python constants to JSON configuration files (backend/etc/models.json,backend/etc/runtime_pricing.json), loaded at startup. Model catalog expanded from 10 to 22 models across 7 vendor groups (Anthropic, Amazon, DeepSeek, Google, Meta, MiniMax, Moonshot AI). - Admin-enabled models:
enabled_model_idssite setting (default[]= all models).GET /api/settings/modelsreturns the enabled list and full catalog.PUT /api/settings/modelsupdates the enabled set (validates againstSUPPORTED_MODELS).GET /api/agents/modelsfilters by the enabled set. Settings page "Enabled Models" section with per-vendor grouped checkboxes and Save button. - Per-agent allowed models:
allowed_model_idsTEXT column on theagentstable (JSON array).AgentORM model withget_allowed_model_ids()/set_allowed_model_ids()helpers. Deploy and register flows store the allowed list (defaults to[model_id]).AgentResponseincludesallowed_model_idsfield. - Runtime model override on invoke:
POST /api/agents/{id}/invokeaccepts optionalmodel_idparameter. Validated against the agent'sallowed_model_ids(HTTP 400 on mismatch). Passed through toinvoke_agent_stream()which overridesagent_model_idfor that invocation. - Agent PATCH endpoint extended:
PATCH /api/agents/{id}acceptsmodel_id(updatesAGENT_CONFIG_JSON) andallowed_model_ids(validates againstSUPPORTED_MODELS). Description changes propagated to AgentCore viaupdate_runtime(description=...). - Group-based invoke fix: Agents with no
loom:grouptag are now accessible to any authenticated user with invoke scope (previously blocked by empty-string comparison). - Streaming tool-call events: Strands agent handler detects
contentBlockStartevents withtoolUsedata and yields{"tool_use": {"name": ..., "id": ...}}structured events. Backendinvoke_agent_streamforwards these asevent: tool_useSSE events. FrontendStreamSegmenttype models interleaved text and tool_use segments.useInvokehook trackssegments,currentToolName, andtoolNamesarrays. - Inline tool-call indicators:
ToolUseBlockcomponent renders tool calls with Wrench icon, counter (N/M), elapsed timer, and tool names.formatToolName()strips MCP server prefixes (server___tool→tool). Tool names persist inChatMessage.toolNamesand are displayed in finalizedMessageBubblecomponents after stream ends. - 401 retry for SSE invoke:
invokeAgentStream()ininvocations.tsintercepts 401 responses, callstryRefreshToken()(exported fromclient.ts), and retries the SSE request with the refreshed token. Mirrors the existingapiFetch401 retry pattern. - Frontend model selection UI:
- Deploy form: "Default Model" label, "Allowed Models (runtime selection)" checkbox section grouped by vendor via
groupModels(). InvokePanel: model dropdown (Select) filtered byallowedModelIds, disabled when single model, passesmodel_idoverride toonInvoke.DeploymentPanel: "Allowed Models" section with inline edit mode (grouped checkboxes, default model toggle, Save/Cancel buttons).AgentDetailPage: Overview card consolidates description + deployment + model config.RegisteredAgentModelConfigcomponent for non-deployed agents.ChatPage: model picker button in input area footer with click-outside-to-close dropdown, auto-reset on agent switch.groupModels()utility infrontend/src/lib/models.ts: groupsModelOption[]by vendor, sorts groups alphabetically.
- Deploy form: "Default Model" label, "Allowed Models (runtime selection)" checkbox section grouped by vendor via
- AgentCard display updates: Endpoint qualifiers shown as comma-separated text (removed individual badges). Authorizer shown as a single outline badge with name/type fallback.
- Agent detail response pane refactor: Markdown rendering extracted to standalone
MarkdownBlockcomponent. Response pane renderssegmentsarray withToolUseBlockandMarkdownBlockblocks. Thinking indicator shown when streaming with no segments.StreamingBubblecomponent in ChatPage for segment-based rendering during active streams. - 12 backend tests in
test_model_selection.pycovering Agent model helpers, registration withallowed_model_ids, response inclusion, PATCH validation, and invoke model validation.
- API key authentication for MCP servers: New
api_keyauth type alongsidenoneandoauth2. Admin API keys stored in Loom-managed AWS Secrets Manager (loom/mcp/{name}/admin-api-key), never in the database. Per-user API keys stored atloom/mcp/{name}/api-key/{user_sub}. Backend resolves keys from Secrets Manager with 5-minute in-memory cache._ApiKeyAuthhttpx handler in agent runtime resolves key once at session init (not per-request) to avoid throttling. - Dynamic MCP connectors in ChatPage: "Connectors" button in the input area footer (left of model picker). Dropdown lists MCP servers available to the user with per-server toggle switches. Enabled connector state persisted to
localStorageper agent (loom:enabledConnectors:{agentId}). API key connectors prompt for key entry on first enable; disconnect action deletes the stored key. Connector IDs passed through invoke request to agent runtime asdynamic_mcp_servers. - Agent runtime model override:
AGENT_MODEL_IDenvironment variable andmodel_idfield in invoke payload. Agent handler cachesBedrockModelinstances per model ID and swapsagent.modelper invocation. Default model restored when no override specified. - Agent runtime dynamic MCP attachment: Handler accepts
dynamic_mcp_serversin invoke payload. Maintains connection pool keyed by(server_name, actor_id). Previously-connected servers are reused across invocations. Supportsapi_key,oauth2, and unauthenticated transports. - Salesforce Agentforce A2A integration: Salesforce uses
/v1/cardinstead of/.well-known/agent.jsonfor Agent Card endpoint._AuthenticatedA2AAgentdetects Salesforce URLs and trusts the card's declared RPC URL (does not override with endpoint). Agent cardcapabilities.streamingcheck — skipmessage/streamand go directly tomessage/sendfor agents that don't advertise streaming support. - A2A agent card UX improvements: Skill list restyled from Card-based layout to compact expandable rows matching MCP tool list style. Capabilities (streaming, push notifications, state history) and default I/O modes displayed inline with label prefixes. Empty default modes show "none" indicator.
- Registry lifecycle improvements: MCP server deletion cleans up associated registry records. Tool refresh propagates updated descriptors to the registry. Registry status badges shown across catalog and list views.
- Catalog navigation improvements: Clicking an item in the CatalogPage navigates to the corresponding admin page with the item pre-selected.
- AgentCard overflow fix:
overflow-hiddenon flex container prevents long agent names from overflowing card boundaries. - ChatPage model picker grouped by vendor: Model dropdown uses
groupModels()utility for alphabetical vendor grouping with section headers, matching the admin deploy form pattern. - SQLite absolute path default:
DATABASE_URLdefaults to an absolute path based on the backend directory to avoid data loss when CWD differs between invocations.
- New deployment type:
source="harness"alongside existingregisteranddeploytypes. Harness is a fully managed agent loop — no user-authored code, artifact build, or credential provider creation required. Agents are configured entirely through API parameters (model, system prompt, tools, iteration limits, timeouts). - Backend model:
harness_idcolumn added to theagentstable (VARCHAR, nullable). Stores the harness ID for managed agent deployments. Included inAgent.to_dict()serialization andAgentResponse. - Backend service module:
backend/app/services/harness.pywithcreate_harness(),get_harness(),delete_harness(), andinvoke_harness_stream()functions. Control plane operations use thebedrock-agentcore-controlclient; data plane invocation uses thebedrock-agentcoreclient.invoke_harness_stream()translates Converse API streaming events (messageStart,contentBlockStart,contentBlockDelta,contentBlockStop,messageStop,metadata) into the existing SSE format (text,structured/tool_use,metadata) so the frontend works without modification. - Harness tools: Three tool types supported —
remote_mcp(from MCP server catalog),agentcore_code_interpreter(built-in toggle), andagentcore_browser(built-in toggle). Custom tools can also be passed viaharness_toolsparameter. - Deploy-time vs invoke-time tool split: OAuth2-authenticated MCP tools are excluded from
create_harness(they fail to initialize without auth headers at deploy time). All MCP tools are stored in the config JSON underharness_config.toolsfor invocation-time injection; only non-OAuth2 tools go toharness_config.deploy_tools(whatcreate_harnessreceives). At invocation time, OAuth2 tools are injected with fresh M2M tokens asAuthorization: Bearerheaders in theremoteMcptool config. - OAuth2 credential provider creation: For harness agents with OAuth2 MCP servers, the backend creates AgentCore credential providers during deployment (same pattern as custom agents). Credential providers are cleaned up on agent deletion.
- M2M token injection: At invocation time, the backend looks up each OAuth2 MCP server's
client_id,client_secret, andwell_known_urlfrom the database, calls_get_oauth2_token()to obtain an M2M access token, and injectsAuthorization: Bearer <token>into theremoteMcptool'sheadersmap. This uses the MCP server's own OAuth2 credentials, not the Loom user's token. - JWT authorizer support: Harness invocations support JWT authorization. When a user access token is available, the data plane client is configured with
UNSIGNEDSigV4 and anAuthorization: Bearer <token>header injected via a boto3before-sendevent hook. - Observability: Harness agents automatically get USAGE_LOGS and APPLICATION_LOGS delivery enabled on their auto-provisioned runtime (same as custom agents). Observability is cleaned up on deletion.
- ARN field handling: The Harness API returns the ARN in the
"arn"field (not"harnessArn"). All code paths (deploy, status poll, manual refresh) useresponse.get("arn") or response.get("harnessArn", ""). - Deployment flow:
_deploy_harness()validates name, model_id, and role_arn. Creates Agent record withsource="harness",status="CREATING",deployment_status="initializing". Background task_deploy_harness_background()creates OAuth2 credential providers for MCP servers, callscreate_harnesswith non-OAuth2 tools only, sets harness_id, extracts auto-provisioned runtime, enables observability, and updates status todeployed. On failure, status is set toFAILED. - Status polling:
get_agent_status()detectssource="harness"agents and polls viaget_harness_api()instead of the runtime API. Extracts runtime from the harness environment and updates status. - Refresh:
refresh_agent()routes harness agents toget_harness_api()for status refresh. - Deletion:
delete_agent()routes harness agents todelete_harness_api()instead ofdelete_runtime(). Cleans up observability and credential providers. - Invocation:
invoke_agent_endpoint()dispatches toinvoke_harness_agent_stream()whenagent.source == "harness". Readsharness_config.toolsand MCP server auth types from stored config, resolves OAuth2 M2M tokens per server, and passes tools with auth headers asdynamic_tools. Token counts come from harness metadata events (falls back to CountTokens API if zero). - Frontend types:
AgentHarnessDeployRequestinterface with harness-specific fields (max_iterations, timeout_seconds, max_tokens, temperature, top_p, code_interpreter, browser).AgentResponse.sourceunion extended to include"harness".AgentResponse.harness_idfield added. - Frontend form:
AgentRegistrationFormadds a Deployment Type selector (Custom Agent vs Managed Agent radio buttons). Managed mode shows model parameters (max tokens, temperature, top_p), built-in tools (code interpreter, browser toggles), and iteration/timeout configuration. Harness Parameters section positioned between Authorizer and Lifecycle. Custom-only sections (protocol, authorizer, A2A agents, memory) are hidden in managed mode. - Frontend AgentCard: Deployment type label (
MANAGED/CUSTOM) and cost badge moved below the info box as outline badges, decluttering the card header row. Header row contains only: agent name, registry status, status badge, session count, refresh/trash buttons. - Frontend wiring:
useAgentshook includesdeployHarnessAgentfunction with harness-specific polling support.AgentListPageandApp.tsxwireonDeployHarnessprop through to the registration form. - 21 backend tests in
test_harness.pycovering deployment CRUD, validation, MCP server integration, built-in tools, model parameters, status polling, refresh, deletion, config storage, and service module functions (create, get, delete, invoke stream with text and tool_use events).
- Backend endpoint:
GET /api/agents/{id}/integrationreturns assembled integration details for READY agents: invocation URLs (per qualifier), protocol, network mode, authentication requirements, and example code snippets. - URL construction: Invocation URLs follow the
bedrock-agentcore.{region}.amazonaws.com/runtimes/{runtime_id}/endpoints/{qualifier}/invokepattern. Protocol-specific URLs for MCP (/mcp) and A2A (/.well-known/agent.json) are included. - Auth info (SigV4): For agents without an authorizer, displays required IAM action (
InvokeAgentRuntimefor custom,InvokeHarnessfor managed), resource ARN, example IAM policy, boto3 snippet, and AWS CLI snippet. - Auth info (OAuth2): For agents with an authorizer, displays authorizer type, OIDC discovery URL, token endpoint (derived from Cognito pool ID or custom discovery URL), allowed client IDs and scopes, example token request and invocation curl snippets. Client secrets are never returned.
- Frontend component:
ExternalIntegrationSectiondisplays endpoint info with copy-to-clipboard buttons, protocol badges, network mode indicators, and syntax-highlighted code blocks. Only shown for agents with status READY. - 10 backend tests in
test_integration_info.pycovering SigV4 custom/harness agents, OAuth2 Cognito/custom OIDC, MCP/A2A protocol URLs, multiple qualifiers, VPC network mode, and error cases.
- Backend
IdentityProvidermodel (backend/app/models/identity_provider.py): stores OIDC configuration (issuer, client ID, discovery URL, authorization/token/JWKS endpoints), group claim mapping (external IdP groups to Loom groups via configurable JSON mapping), and cached discovery metadata with TTL-based refresh. - CRUD endpoints at
/api/settings/identity-providersfor managing identity provider configurations, plusPOST .../discoverfor OIDC discovery andPOST .../test-discoveryfor validating provider connectivity. - OIDC discovery service (
backend/app/services/oidc.py): fetches.well-known/openid-configurationfrom any OIDC-compliant provider, extracts authorization, token, JWKS, and userinfo endpoints. - Generic JWT validation (
backend/app/services/jwt_validator.py): validates tokens against any JWKS endpoint (not just Cognito). Supports key rotation via cached JWKS with automatic refresh on key-not-found. - Group claim mapping: maps external IdP group claims (e.g., Microsoft Entra ID
groups, Oktagroups) to Loom's internal group model (g-admins-*,g-users-*) via a configurable per-provider mapping table. GET /api/auth/configextended to return the active identity provider configuration when an external IdP is active, backward-compatible with the existing Cognito-only response format.- Generic token service (
backend/app/services/token.py): client credentials grant against any OIDC-compliant token endpoint (not just Cognito). - Token endpoint in the security router supports both Cognito and generic OIDC authorizers for agent invocation.
- Supported providers: Microsoft Entra ID, Okta, Auth0, Generic OIDC. Cognito remains the default when no external IdP is configured.
- Frontend OIDC Authorization Code + PKCE flow:
AuthContextextended withstartOIDCLogin()andexchangeOIDCCode()infrontend/src/api/auth.tsfor standard browser-based OIDC login without client secrets. - LoginPage: shows provider-specific button (e.g., "Sign in with Microsoft Entra ID") when an external IdP is active, alongside the existing Cognito login form.
- Identity Provider management UI (
frontend/src/components/IdentityProviderPanel.tsx): CRUD for provider configurations, OIDC discovery test button, and group mapping table editor. - Security Admin page: new "Identity Providers" tab for managing external IdP configurations.
- API client at
frontend/src/api/identity_providers.tsfor all identity provider CRUD operations. - 22 backend tests in
backend/tests/test_identity_providers.pycovering CRUD, discovery, group mapping, token validation, and backward compatibility. - Per-user authorizer linking (
backend/app/services/authorizer_linking.py): OAuth popup flow for cross-IdP scenarios where the user's login IdP differs from the agent's authorizer. Refresh tokens stored in Secrets Manager atloom/authorizers/{auth_id}/user-tokens/{user_sub}. Access tokens resolved at invocation time (Priority 1.5) with in-memory caching. Four linking endpoints under/api/security/authorizers/{auth_id}/link. FrontendOAuthLinkCallbackPagehandles popup callback withwindow.opener.postMessage. - Same-IdP detection: When the user's login IdP matches the agent's authorizer (e.g., same Entra ID tenant), the frontend auto-detects this by comparing tenant IDs extracted from login issuer URL and agent authorizer discovery URL. Shows a green dot indicator and automatically selects the user's login token — no account linking required.
allowed_audiencefield: Added to theAuthorizerConfigmodel, DB migration, CRUD endpoints, deploy requests, and frontend forms. Maps to AgentCore'sallowedAudienceparameter (validates theaudJWT claim, separate fromallowedClientswhich validatesazp).- Entra ID
allowedClientsfix: Microsoft Entra ID v1.0 access tokens lack the standardazpclaim (they useappidinstead). AgentCore validatesallowedClientsagainstazp, causingUnrecognizedClientException(401). Deploy paths now omitallowedClientsforentra_idauthorizer type, relying onallowedAudiencealone. - Entra ID v1.0/v2.0 issuer handling: Backend auth dependency detects v2.0 issuer URLs and constructs the expected v1.0 issuer (
https://sts.windows.net/{tenant}/) for token validation, since Entra ID v2.0 token endpoints issue access tokens with v1.0 format issuers. - Credential filtering per authorizer: Frontend invoke panel filters M2M credentials to only show those from the agent's matching authorizer, preventing cross-authorizer credential selection.
- Harness IAM prefix fix: IAM role policy and CloudFormation role template updated to include
harness_prefix for workload identity resources and CloudWatch Logs permissions, supporting harness-deployed agents.
- OBO delegation mode:
delegation_modefield added toMcpServerandA2aAgentmodels (m2morobo). When set toobo, AgentCore credential providers are configured for on-behalf-of token exchange (RFC 8693) instead of machine-to-machine client credentials. Configurableobo_grant_typefield supportsTOKEN_EXCHANGE(RFC 8693, for Okta and others) andJWT_AUTHORIZATION_GRANT(RFC 7523, for Microsoft Entra ID). - Credential provider OBO configuration:
create_oauth2_credential_provideracceptsdelegation_modeandobo_grant_typeparameters. Whenobo, the provider is configured withonBehalfOfTokenExchangeConfigcontaining the appropriate grant type.TOKEN_EXCHANGEusesactorTokenContent: NONEwithCLIENT_SECRET_BASICauth method;JWT_AUTHORIZATION_GRANTusesCLIENT_SECRET_POST. - User token forwarding: The invoke endpoint extracts the user's access token from the
Authorizationheader and passes it asuser_access_tokenin the invocation payload. The harness service injects it as anX-Loom-User-Access-TokenHTTP header via a boto3before-sendevent hook. Custom agents receive it in the invoke payload. - Agent runtime OBO exchange: The
_OAuth2Authhttpx handler inmcp_client.pysupports both M2M and OBO flows. OBO usesoauth2Flow: ON_BEHALF_OF_TOKEN_EXCHANGEwith the user's access token. Workload tokens are captured eagerly at construction time to avoid ContextVar propagation issues in background threads. Token caching is keyed by(credential_provider_name, oauth2_flow, workload_token_prefix)with expiry-skew handling. - Token info inspection:
TokenInfoHook(StrandsHookProvider) extracts__TOKEN_INFO__markers from MCP tool results (server-initiated notifications cannot traverse the AgentCore proxy). Decoded JWT claims are emitted astoken_infoSSE events. The frontend renders aTokenInfoCardon the invoke page showing user token claims, OBO token claims with group mapping resolution, credential provider attribution, and claim annotations (issuer, audience, scopes, roles, expiry). client_typefield on identity providers:IdentityProvidermodel gains aclient_typecolumn (publicorconfidential). The frontendIdentityProviderPanelshows a toggle for selecting client type, which controls whether the client secret is required.- Harness update endpoint:
PUT /api/agents/{id}/redeploy-harnessuses theUpdateHarnessAPI to modify existing harness agents in-place without recreation. Background task handles credential provider lifecycle (create new, delete removed) and config JSON updates. - Session table pagination:
SessionTablecomponent adds pagination with page size controls and page navigation, improving usability for agents with many sessions. - Session ownership filtering: Admin invoke panel filters sessions to those owned by the current user via
user_idmatch, resolved authoritatively from a backend/api/auth/meendpoint. - Resource export/edit system:
AgentCardandMemoryCardreplace the refresh button with a pencil-to-edit button. Clicking edit on an agent navigates to the deploy form pre-filled with the agent's exported configuration. Memory cards open the create form pre-filled with the memory's exported configuration (viaGET /api/memories/{id}/export). JSON export from the form serializes the current state includingmemory_strategies. - Sidebar tooltip component: New
Tooltipshadcn component (frontend/src/components/ui/tooltip.tsx) using Radix Tooltip primitives with zero-delay appearance and animated content. Used for instant username display on sidebar hover. - HITL improvements: Approval dialog race condition fix — prevents duplicate decision submissions. Elicitation dialog improvements.
ToolProviderExceptionhandling in agent runtime yields user-friendly error messages instead of crashing the stream. - Graceful MCP failures:
attach_mcp_toolsskips servers that fail to connect (e.g., 401 Unauthorized) rather than failing the entire agent initialization. Retry on next invocation when attachment fails. Strands internal loggers suppressed to CRITICAL during MCP attachment to avoid noisy stack traces. oauth2_audiencefield on MCP servers: Supports token exchange audience parameter required by Okta custom authorization servers.WORKLOAD_IDENTITY_NAMEenv var: Automatically set toloom-{agent_name}at deploy time for credential provider discovery.- Agent list registry filtering: When registry is enabled,
t-userusers see onlyAPPROVEDagents (previously saw both approved and unregistered). When registry is disabled, the original behavior (show all non-draft) is preserved.
- Config:
CodeInterpreterConfigdataclass added toagents/strands_agent/src/config.pyunderIntegrationsConfig— fields:enabled(bool),region(string),identifier(string, optional custom interpreter ID). - Agent wiring:
build_agent()inagents/strands_agent/src/agent.pyinstantiatesAgentCoreCodeInterpreterfromstrands-agents-toolswhenconfig.integrations.code_interpreter.enabledisTrue. The.code_interpretertool is appended to the agent's tool list. - SDK dependency: Uses
strands-agents-tools(already inrequirements.txt) which providesAgentCoreCodeInterpreter— a Strands@tool-decorated class backed bybedrock_agentcore.tools.code_interpreter_client.CodeInterpreter. - Sandbox isolation: Code executes in an AWS-managed sandbox with no access to the host filesystem or network.
- Configuration parsing:
_parse_integrations()readscode_interpreterfrom the JSON config and populatesCodeInterpreterConfig. - Custom CI resource: When a Code Interpreter execution role is configured, the backend creates a custom
bedrock-agentcore-controlCode Interpreter resource in parallel with the runtime artifact build. The resource ID is stored inagents.code_interpreter_idand its identifier is injected intoAGENT_CONFIG_JSONbefore deployment. - IAM roles — two-role pattern: Two distinct IAM roles are used. The agent execution role (deployed via
shared/iac/role.yaml) requiresbedrock-agentcoreactions coveringcode-interpreter/*(system) andcode-interpreter-custom/*(customer-owned) resource ARNs. A separate CI execution role (deployed viashared/iac/code_interpreter_role.yaml, namedloom-ci-role-{sanitized-name}) is the sandbox identity used by the custom interpreter. - Deployment form: Registration form exposes Code Interpreter as a peer integration section alongside Memory, MCP, and A2A. Fields: enable toggle, network mode (SANDBOX/PUBLIC), region dropdown, and CI execution role selector (filtered to
role_type="code_interpreter"managed roles). - JSON manifest: CI config exported/imported under nested
code_interpreterkey:{"enabled": true, "region": "us-east-1", "network_mode": "SANDBOX", "role": "loom-ci-role-demo"}. - CI observability:
enable_code_interpreter_observability()wires USAGE_LOGS and APPLICATION_LOGS vended log delivery for the custom CI resource, mirroring the runtime observability pattern. Account ID is extracted from the CI ARN to ensure the log group ARN is always valid. - X-Ray tracing: Runtime deployments now include
OTEL_TRACES_EXPORTER=awsxrayandOTEL_PROPAGATORS=xrayenvironment variables, activating the ADOT auto-instrumentation pipeline already present in the agent package. - Lifecycle: Deleting an agent also deletes its associated CI resource. If active sessions are present, sessions are terminated first via
StopCodeInterpreterSessionbefore retrying the delete. - Role management:
ManagedRolemodel extended withrole_typecolumn ("agent"or"code_interpreter"). The Role Management panel groups roles by type with collapsible sections. - Status polling: CI status is surfaced via
code_interpreter_statusonAgentResponse. CI polling is skipped when the agent is inDELETINGstate.ResourceNotFoundExceptionduring CI poll is logged at DEBUG (expected post-deletion). - Deployment phase label:
creating_ci_resourcedisplays as "Building artifact & creating Code Interpreter" to reflect the parallel nature of the two operations.
- VPC configuration model:
VpcConfigORM model withname,vpc_id,subnet_ids(JSON array), andsecurity_group_ids(JSON array). Full CRUD API under/api/vpc-configs. Stored in thevpc_configstable; referenced by agents viavpc_config_id(INTEGER FK). - VPC egress for deploy-type agents:
AgentDeployRequestacceptsnetwork_mode(PUBLICorVPC) andvpc_config_id. Whennetwork_mode=VPC, the runtime is created withnetworkConfiguration.networkMode=VPCand the resolved subnet/SG IDs.vpc_config_idis persisted on theAgentrecord and included inAgentResponsefor export/edit round-trips. - VPC egress for harness agents:
AgentHarnessDeployRequestacceptsnetwork_modeandvpc_config_id.create_harness/update_harnessaccept optionalvpc_subnet_idsandvpc_security_group_idsparameters passed through to the AgentCore Harness API whennetwork_mode=VPC.vpc_config_idis persisted on theAgentrecord at harness creation time. - PrivateLink ingress IaC:
shared/iac/privatelink.yamlSAM template creates the NLB, VPC Endpoint Service, and required security groups for PrivateLink-based agent invocation. Makefile targets:privatelink,privatelink.delete,privatelink.describe. - Frontend VPC selector: Network mode radio (PUBLIC/VPC) in the deploy form expands to show a VPC Config dropdown when VPC is selected. On agent edit, the previously-selected VPC config is pre-populated using a
useRef-deferred effect that resolves the config name after thevpcConfigslist loads. - Harness refinements (same branch):
- All MCP tools (including OAuth2-authenticated) are included in the harness tool list; OAuth2 tokens are injected at invocation time via
remoteMcp.headers. actor_idsanitized with regex[^a-zA-Z0-9:_/\-] → _to handle Okta email-format subs that contain@and..- Code Interpreter
ConflictExceptionhandled by reusing existing CI resource by name (list_code_interpretersscan); orphaned CI resource deleted when harness is deleted. - Memory
retrievalConfigbuilt from active strategies (strategy.status == "ACTIVE") usingstrategyIdas key;get_memoryresponse unwrapped from nested"memory"key. bedrock-agentcore:ListEventsadded to the memory policy inshared/iac/role.yaml.- Harness
ConflictExceptionon create resolved by pre-deleting the existing harness with the same name before callingcreate_harness. - Registry auto-registration for harness agents: harness status polling now includes the same auto-registration block as custom deploy-type agents — when deployment reaches READY and no
registry_record_idexists, a DRAFT registry record is created.
- All MCP tools (including OAuth2-authenticated) are included in the harness tool list; OAuth2 tokens are injected at invocation time via
- Provider registry:
backend/etc/providers.jsonis the static provider registry (currentlybedrockandlitellm, each withrequires_api_key,requires_base_url,harness_supportedflags). Loaded at startup intoSUPPORTED_PROVIDERS/SUPPORTED_PROVIDER_IDSinrouters/agents.py.GET /api/agents/providersreturns the registry merged with a liveavailableflag per provider (LiteLLM isavailableonly when a proxy connection is configured and enabled). - Per-agent provider selection:
AgentDeployRequest,AgentHarnessDeployRequest, and the register/PATCH request models acceptprovider("bedrock"default or"litellm"),base_url, andapi_key. Non-Bedrock providers are validated againstSUPPORTED_PROVIDER_IDS; forlitellm, the effective base URL comes from the configured proxy connection rather than a per-agent field. - LiteLLM proxy connection service (
backend/app/services/litellm.py): resolves the proxy's master key and base URLs with a two-tier fallback — a Settings-page override (Secrets Manager +SiteSettingrows, gated by alitellm_enabledtoggle) always wins once saved; otherwise falls back to CFN-seeded env vars (LOOM_LITELLM_PROXY_BASE_URL,LOOM_LITELLM_DISCOVERY_BASE_URL,LOOM_LITELLM_PROXY_API_KEY) so a fresh deploy works without a Settings visit. Two base URLs are tracked because the caller differs:agent_base_urlis what deployed agents/harnesses reach at runtime (e.g. an internal ALB);discovery_base_urlis what the Loom backend itself uses for/model/info,/key/generate,/key/deletecalls, falling back toagent_base_urlwhen unset (useful when the backend reaches the proxy via a local SSM tunnel during dev while agents reach it through the real ALB). - Per-agent virtual key vending: the master key is never handed to an agent.
vend_virtual_key()mints a scoped LiteLLM virtual key via/key/generate(aliasedloom-agent-{agent_id}, restricted to the agent'sallowed_model_ids) andrevoke_virtual_key()deletes it via/key/deleteon redeploy/delete. Vending is idempotent — a deterministic key alias means a retried deploy revokes any stale key under the same alias before minting a new one. For custom (deploy-type) agents, the vended key is stored as a Secrets Manager secret (loom/agents/{name}-{id}/llm-provider-api-key) and referenced via theLLM_PROVIDER_API_KEY_SECRET_ARNconfig entry, resolved by the agent runtime at build time (see below). For harness agents, the key is instead registered as an AgentCore API key credential provider (create_api_key_credential_provider/delete_api_key_credential_providerinservices/credential.py) — the Harness API'sliteLlmModelConfig.apiKeyArnresolves it directly viabedrock-agentcore:GetResourceApiKeyat invocation time, a distinct mechanism from Secrets Manager. - Harness model config (
services/harness.py):_build_model_config()builds eitherbedrockModelConfig(default) orliteLlmModelConfig(modelId, optionalapiKeyArn,apiBase,maxTokens) forCreateHarness/UpdateHarness/InvokeHarness, selected by a newproviderparameter threaded throughcreate_harness(),update_harness(), andinvoke_harness_stream(). - Strands agent runtime provider support:
agents/strands_agent/src/config.py'sAgentConfiggainsprovider(default"bedrock"),base_url, andapi_key_secret_arnfields, parsed from the deploy-time JSON config.agents/strands_agent/src/agent.py's_build_model()instantiatesBedrockModel(IAM-authenticated, unchanged default), or foropenai/anthropic/litellmresolves the API key once viaagents/strands_agent/src/integrations/secrets.py::resolve_secret()and constructsAnthropicModel/OpenAIModel/LiteLLMModelrespectively, with a bounded request timeout (LOOM_MODEL_REQUEST_TIMEOUT_SECONDS, default 30s) so an unreachable proxy fails fast with a loggable exception instead of hanging until the caller's own read timeout. Forlitellm,client_args["use_litellm_proxy"] = Trueis set so a bare model ID is routed through the proxy'sbase_urlwith the vended virtual key, rather than being handed unprefixed to LiteLLM's own provider auto-detection (which would otherwise route straight at the real upstream provider using the virtual key as if it were a real provider key). - Dynamic model catalog (
backend/app/services/model_catalog.py): merges four sources — the curated staticmodels.json, live Bedrock availability/catalog discovery (list_foundation_models/list_inference_profiles, restricted to allow-listed labs), the live LiteLLM proxy's own catalog (/model/info, only when a proxy is configured), and LiteLLM's publicmodel_prices_and_context_window.jsonas a pricing fallback for curated/dynamic Bedrock entries.get_bedrock_models()(Bedrock-only, never touches the proxy — used for the model picker's eager page-load fetch) andget_litellm_models_live()(proxy-only, no public-catalog or placeholder fallback — returns models actually deployed on the proxy, or empty if unreachable) are cached independently with a shared TTL (default 900s,LOOM_MODEL_CATALOG_TTL_SECONDS) so selecting one provider never waits on the other.get_merged_models()concatenates both for callers needing the full valid-ID universe (settings validation, PATCH, pricing).clear_litellm_cache()andPOST /api/settings/litellm-proxy/refreshforce a live re-fetch bypassing the TTL, recovering from a stale/empty cache (e.g. cached while the proxy was unreachable) without a backend restart. - Settings page LiteLLM proxy endpoints:
GET/PUT /api/settings/litellm-proxymanageenabled,base_url(agent base URL),discovery_base_url, and a write-onlymaster_key(omitting it on PUT leaves the stored key untouched; responses includehas_master_keyinstead of the key itself).PUTpersists the URLs/toggle asSiteSettingrows and the master key to Secrets Manager, then clears the LiteLLM model-catalog cache so the next fetch reflects the new connection. - IAM/IaC (
backend/iac/ecs.yaml): new parameterspLitellmProxyBaseUrl,pLitellmDiscoveryBaseUrl,pLitellmProxyApiKeySecretArn,pLitellmProxyApiKeySecretKmsKeyArnseed the CFN-level env-var fallback; when the secret ARN is set, it's injected as theLOOM_LITELLM_PROXY_API_KEYECS task Secret and the task execution role is grantedsecretsmanager:GetSecretValueon it (pluskms:Decrypton its KMS key, if a customer-managed key is specified) — both conditioned on the parameter being non-empty so a deployment without LiteLLM configured grants nothing extra. Reaching this feature also required closing several unrelated AgentCore IAM gaps on the backend task role: fullCreateAgentRuntime/UpdateAgentRuntime/DeleteAgentRuntime-family actions (previously only invoke-time actions were granted),iam:CreateServiceLinkedRolescoped toAWSServiceRoleForBedrockAgentCoreNetwork(VPC-mode runtimes trigger AWS to lazily create this role on first use per account), and CloudWatch Logs delivery-pipeline permissions (PutDeliveryDestination/PutDeliverySource/CreateDelivery/DescribeDeliveriesand matchingDelete*) for vended runtime/Code Interpreter log routing. A dedicatedLogsKmsKeynow encrypts the backend's own ECS log group.bedrock:ListFoundationModels/ListInferenceProfiles(list-only, no resource-level scoping) were added formodel_catalog.py's live Bedrock discovery. - Frontend provider selection:
AgentRegistrationFormadds a provider selector (fetched viafetchProviders()); switching providers resets model/credential fields. Selectinglitellmlazily fetches its catalog on demand (fetchLitellmModels(), called only when the provider is selected or an imported/edited manifest references it) rather than eagerly alongside Bedrock's list, since it reflects exactly what the deployed proxy reports. A provider withoutharness_supportedforces the deployment type back to Custom Agent. JSON import/export accepts both a flatproviderstring (legacy) and a nested{id, base_url, api_key}object.InvokePanelandChatPagemergefetchModels()(Bedrock) withfetchLitellmModels()so an agent'sallowed_model_idsresolve correctly regardless of which provider they come from;groupModelsByProvider()inlib/models.tsgroups the merged picker options by provider (Bedrock first, then LiteLLM, then alphabetical) before the existing vendor grouping. Settings page gains a full LiteLLM panel (enabled toggle, Agent Base URL, Discovery Base URL, master key write-only field, live model list split by provider with per-provider enable/disable and a search filter, and a Refresh button wired toPOST /api/settings/litellm-proxy/refresh). - API key credential provider service:
create_api_key_credential_provider()/delete_api_key_credential_provider()added toservices/credential.py, distinct from the existing OAuth2 credential provider functions — creates/deletes AgentCore API key credential providers viabedrock-agentcore-control, with the same create-or-update-on-conflict handling as the OAuth2 path.
| # | Question | Notes |
|---|---|---|
| 1 | What Strands Agents templates will be supported beyond the initial blueprint? | To be defined as new agent patterns emerge. |
| 2 | Should the Operate tab aggregate metrics via a separate analytics store or compute on-the-fly from SQLite? | SQLite is sufficient for MVP; revisit at scale. |
| 3 | What is the CloudWatch log format for agents that do NOT emit the "Start time:" structured log? | Resolved. parse_agent_start_time first looks for the "Agent invoked - Start time:" pattern; if not found, it falls back to the earliest CloudWatch event timestamp as an approximation. |
| 4 | Will multi-region support be needed? | Region is extracted per-agent from the ARN. The backend can manage agents across multiple regions simultaneously. |
| 5 | Should the Agent PK be changed from integer to a natural key? | Decision: keep integer PK. Integer PKs provide the best ergonomics for CLI usage and fastest SQLite joins. |
| 6 | Can we query AWS for live session status? | No. The Bedrock AgentCore SDK does not expose session listing/querying APIs. Session liveness is computed locally using an idle timeout heuristic (LOOM_SESSION_IDLE_TIMEOUT_SECONDS, default 300). |
| 7 | How should Cognito client secrets be stored? | Resolved. AWS Secrets Manager with in-memory caching (5-minute TTL). Never stored in the local database. |
| 8 | Should agent deletion also clean up AWS resources? | Resolved. Optional checkbox "Also delete in AgentCore" shown when agent has a runtime_id. IAM roles are preserved. |
| 9 | Should session filtering be done client-side or server-side? | Resolved. Server-side via user_id query parameter on GET /api/agents/{id}/sessions. Client-side filtering caused a race condition with Cognito auth timing in AWS deployments — sessions would briefly appear then vanish when currentUserId resolved and triggered effect re-runs. The ALB idle timeout is set to 300s to support long-lived SSE connections. |