Mnemose Architecture
Overview
Mnemose (Mnemosyne OS Environment) is a cloud-native SaaS platform where an AI agent acts as the primary cloud-infrastructure operator. The platform runs entirely on Cloudflare’s edge network — no GCP, no Docker containers, no Node.js runtime dependencies in Worker code.
Repository: Mnemose-ai/mnemose
Stack: TypeScript monorepo (pnpm workspaces), Cloudflare Workers/D1/R2/KV/Queues/Durable Objects/Pages/Vectorize/AI
Console: React 18 SPA on Cloudflare Pages at console.mnemose.ai
API: GraphQL endpoint at api.mnemose.ai/graphql
IaC: Pulumi (Cloudflare provider)
CQRS + Queues
Mnemose uses Command-Query Responsibility Segregation driven by Cloudflare Queues:
GraphQL mutation → commands Queue → handler → events Queue → read-model projectionFlow
- Client sends GraphQL mutation to Gateway Worker
- Worker validates auth (see Authentication), extracts tenant ID
- Worker publishes command to
mnemose-commandsQueue - Queue consumer dispatches command to appropriate handler
- Handler executes business logic, returns domain events
- Events appended to
domain_eventstable in D1 (append-only) - Events published to
mnemose-eventsQueue - Event projection consumer updates read models in D1
Authentication
The gateway Worker (services/gateway/src/auth/cf-access.ts, wired from
context.ts and REST /v1 routes) verifies identity and derives the tenant
exclusively from verified claims (never from client headers, except the gated
dev paths below). Auth failures are raised as UNAUTHENTICATED GraphQL / REST
errors. The Worker’s maskError pass-through preserves that code, and both
GraphQL and REST attach a WWW-Authenticate challenge so the console can
trigger token refresh flows.
Token sources, in order: Cf-Access-Jwt-Assertion, Authorization: Bearer,
then the CF_Authorization cookie.
Two verification paths:
- Cloudflare Access JWT (production path). The token is verified with
jose(RS256) against the Cloudflare Access JWKS athttps://<team-domain>.cloudflareaccess.com/cdn-cgi/access/certs. Tenant comes from themnemose_tenant_idclaim stamped by the Access IdP mapping. Pulumi resources live ininfra/access.tsand are enabled withmnemose:provisionAccess. Setup: Cloudflare Access. - Dev shared-secret bypass. When
NODE_ENVis not"production"and theDEV_AUTH_TOKENWorker secret matches the presented token (orDEV_AUTH_BYPASSis enabled and the token still matches), the request is accepted as the fixed dev actor (mnemose-dev-user) with the tenant taken from the schema-validatedx-tenant-idheader. Production ignores both flags. The console build mirrors this withVITE_DEV_AUTH_BYPASS/VITE_DEV_AUTH_TOKEN(set in the CI console deploy step from theMNEPOSE_DEV_AUTH_TOKENsecret).
Dev-environment first-run seeding lives in scripts/seed-dev-tenant.sh:
it calls the registerSelf mutation (creates the “Mnemose System” tenant
and the platform’s own cloud project) and then inserts a platform_admin
IAM binding for the dev actor so myTenants populates.
Sandbox tenant authorization
Sandbox mutations are tenant-verified at both layers:
- GraphQL layer. Every sandbox mutation targeting an existing sandbox
id (
destroySandbox,execInSandbox,destroyExecSandbox, theupdateSandbox*facet mutations,bind/unbindSandboxService,grant/revokeSandboxFacet) resolves the sandbox row first viaassertSandboxTenantand rejects with a non-leakingSandbox not founderror when the row is missing or owned by another tenant. Ephemeral exec sandboxes (which may have no D1 row) are verified withSandboxDO.verifyCallerinstead. - Durable Object layer (defense-in-depth).
SandboxDO.verifyCallercompares the caller’s tenant against the tenant stored in DO state and is invoked by every mutating RPC (terminate,executeCommand,suspend,resume, and all facet/data-source/deception-policy mutators). A leaked or guessed sandbox id cannot operate another tenant’s sandbox.
Key components
- Commands — Zod schemas in
packages/domain/src/commands/. 14 command types. - Handlers — Importable functions in
services/commands/*/. Each receives a command + Env + DbClient, returns domain events. - Events — Zod schemas in
packages/domain/src/events/. Appended to D1domain_eventstable. - Read models — Drizzle-defined SQLite tables in
packages/db/src/schema/. Updated by event projections.
Cloudflare Topology
Gateway Worker (mnemose-gateway-dev)
Entry point: services/gateway/src/worker.ts
- HTTP handler — Hono app with routes:
POST /graphql— GraphQL Yoga endpointGET /health— Health checkALL /agent/*— Proxy to AgentRuntimeDOALL /api/kernels/*— Proxy to KernelBrokerDOGET /assets/:key— R2 asset proxyGET /reports/:key— R2 report proxy
- Queue handler — Consumes commands and events from Queues
- Scheduled handler — Cron triggers for inventory sync, report builder, agent sweep, kernel sweep
CORS middleware allows console.mnemose.ai, mnemose-console.pages.dev, localhost:3200 with credentials.
Durable Objects
- AgentRuntimeDO (
services/gateway/src/do/agent-runtime-do.ts) — Agent execution runtime with hibernation support. SQLite-backed state. One DO per agent ID. - KernelBrokerDO (
services/gateway/src/do/kernel-broker-do.ts) — Kernel registry and heartbeat management. One DO per tenant ID.
Agent Harness
Evaluation targets accept targetType “harness”, resolved tenant-scoped and executed via AgentRuntimeDO /run with harnessId.
D1 Database (mnemose-dev)
SQLite database. 41 tables. 0.65 MB initial size.
- Event store:
domain_events(append-only) - Read models:
tenants,users,orgs,workspaces,projects,groups,sandboxes,cloud_projects,bootstrap_states,resource_inventory,scheduled_reports,agent_state, LLM provider config, etc. (the seven SEO read-model tables were dropped in migration0013_drop_seo_tables; SEO now ships as theseo-rank-trackerapp-store artifact) - Migrations:
packages/db/src/migrations/0000_initial_sqlite_schema.sql - ORM: Drizzle (
@mnemose/dbpackage,drizzle-orm/d1driver)
R2 Buckets
mnemose-assets-dev— Uploaded assets (images, files)mnemose-reports-dev— Generated reports (JSON, PDF)
KV Namespaces
mnemose-config-dev(ID:8e28cad8dbee460a811f1590ab0bacaa) — Runtime configurationmnemose-sessions-dev(ID:0c7e8098d02448fcb9a4813b53d4df37) — Session state
Queues
mnemose-commands-dev— Command bus (with DLQmnemose-commands-dlq-dev)mnemose-events-dev— Event bus (with DLQmnemose-events-dlq-dev)
DNS + Routes
| Record | Type | Target | Proxied |
|---|---|---|---|
mnemose.ai | A | 192.0.2.1 | Yes |
api.mnemose.ai | CNAME | mnemose.ai | Yes |
console.mnemose.ai | CNAME | mnemose-console.pages.dev | Yes |
Workers Routes:
api.mnemose.ai/*→mnemose-gateway-devmnemose.ai/*→mnemose-gateway-dev
Cloudflare AI + Vectorize
AIbinding — Workers AI for LLM inferenceVECTORIZE_INDEX— Vectorize indexmnemose-memory-index-devfor memory search (future)
Console
React 18 SPA on Cloudflare Pages.
- URL:
console.mnemose.ai(custom domain on Pages projectmnemose-console) - Auth: Cloudflare Access JWT via
CF_Authorizationcookie. Adapter inapps/console/src/providers/cf-access-adapter.ts. See Authentication for the current dev-environment fallback. - GraphQL client: urql with
fetchExchange+subscriptionExchange. Endpoint:https://api.mnemose.ai/graphql(production) //graphql(dev, proxied by Vite). - Routing: TanStack Router
- Styling: Tailwind CSS v4
- Telemetry: OpenTelemetry (sends
traceparentheader — CORS-allowed)
UX ethic: in-camera creation
Anywhere the console lets a user select an entity from another domain
(provider, model, project, harness, …), the UI must also offer a discrete
in-context creation path — typically a small (~0.8em) right-aligned “Add”
link in the field’s title row opening a create/edit modal dialog. The user
must never be forced to abandon their current page to create a missing
referenced entity. Reusable dialogs live in
apps/console/src/components/inline-create/ (ProviderCreateDialog,
ModelCreateDialog); prefer extracting shared ones there over re-implementing
per view. Selection fields fed by dynamic domains must repopulate their
options when the parent selection changes (e.g. model options follow the
selected provider) and support type-to-search when option lists are large.
Cloud Adapters
Port interfaces in packages/cloud-adapters/src/ports/:
| Port | CF Implementation |
|---|---|
| EventBus | cf/event-bus.ts (Cloudflare Queues) |
| BlobStorage | cf/blob-storage.ts (R2) |
| Secrets | cf/secrets.ts (KV + Workers Secrets) |
| Scheduler | cf/scheduler.ts (Cron Triggers) |
| CloudCredentials | cf/cloud-credentials.ts (WIF for customer clouds) |
| IdentityProvider | cf/identity-provider.ts (CF Access JWT) |
| Workflow | cf/workflow.ts (Durable Objects) |
| IaaS | cf/iaas.ts (Cloud API proxy) |
| Kms | cf/kms.ts (Workers Secrets) |
GCP and AWS implementations have been removed. The createCloudflarePlatform() function in packages/cloud-adapters/src/cf/platform.ts constructs a CloudPlatform from Worker Env bindings.
Deploy Profiles
Defined in infra/profiles.ts via resolveProfile():
| Profile | Plan | Cost | Limits |
|---|---|---|---|
solo (default) | Free | $0/mo at rest | 100k req/day, 500MB D1, 10GB R2, 10ms CPU |
prod | Workers Paid | ~$5/mo base | Higher limits, PITR, VPC, min instances |
Configured via pulumi config set mnemose:profile solo|prod.
Bootstrap Runtime
Shared across CLI, gateway, and command handlers via @mnemose/bootstrap.
Workers-compatible dist (packages/bootstrap/dist/index.js) exports:
coreManifestForEnv(env)— deployment topology constantsassessCoreHealth(manifest, kernelRows)— pure health assessmentpersistBootstrapState(db, input)— D1 bootstrap state upsert
GCP-specific bootstrap functions (organization/project provisioning) are not available on Workers.
Observability
- Worker logs:
console.logoutput visible in Cloudflare dashboard - OTEL traces:
traceparentheader propagated from console to Worker (CORS-allowed) - Cron monitoring: Scheduled handler logs visible in CF dashboard
- Model-call telemetry: every real model call is persisted per-request in D1
(
token_spend_metrics) with real provider-reported token usage (falling back to the character-heuristic estimate only when a provider omits usage), plus latency, status, provider, route id, and usage-source detail. Exactly one row is written per model call: caller-owned recording —AgentRuntimeDOrecords the agent-loop path (with session id and R2 payload key) and passesskipSpendRecordingso the model router does not double-record; the router records for its other callers (REST/v1/chat/completions, evaluation model targets). Themodelcolumn carries the actually-executed model (route/fallback resolved), not the requested name when they diverge. Full request/response payloads are captured to the R2 bucketmnemose-llm-calls-{env}undercalls/<tenant>/<date>/<callId>.json; capture failure never fails the LLM call. The capture pipeline stores payloads unredacted today (redaction insertable later); retention class defaults todefault(30 days) with per-tenant overrides via theretention_policiestable. - Retention policies API: tenants manage retention through GraphQL —
retentionPolicies(scope),upsertRetentionPolicy(scope, class, durationDays),deleteRetentionPolicy(scope, class), andmodelCall(id)drill-down (row detail plus the R2 payload fromLLM_CALLS, null-safe without the binding). Resolution order for an effective window is: exact class policy → tenant default (defaultclass) policy → 30-day global default;duration_daysNULL = retain forever. Enforcement is lazy:D1ObservabilityStoreprunestoken_spend_metricsrows with a per-row cutoff derived from theirretention_class, and sweeps expired R2 payloads undercalls/by key date prefix vs the class cutoff (best-effort, never fails the persist/query path). - Future: CF Observability + Tail Workers for structured logging
Error Handling & Masking
Yoga maskedErrors in worker.ts passes through safe codes (UNAUTHENTICATED, FORBIDDEN, BAD_USER_INPUT, GRAPHQL_VALIDATION_FAILED) and masks everything else as "Unexpected error." (INTERNAL_SERVER_ERROR).
BAD_USER_INPUTis reserved for operator-actionable errors (e.g. unconfigured providers, missing API secrets, vendor API rejections).- Resolvers use the
userError(message)helper (services/gateway/src/schema/errors.ts) to throw actionable errors. - Internal invariants and platform exceptions throw plain
Errorso they stay masked from clients. - Upstream vendor response bodies are never passed into error messages (status code only) to prevent leaking upstream secrets or internal topologies.
Models
Provider configuration and model catalog are decoupled.
- Provider row (
llm_provider_config): one per tenant +providerKind. HoldsbaseUrl,secretRef, optionaloauth(client-credentials:tokenUrl/clientId/clientSecretRef/scopes), orcloudCredentialRef.modelis optional leftover, not the catalog. - Model rows (
llm_provider_model): many per tenant + kind.sourceisdiscoveredormanual. Generation params live on the model entry (temperature,topP,topK,maxTokens,frequencyPenalty,presencePenalty,stopSequences,extra). - Auth modes: GraphQL
authModeis derived —oauthwhenoauthis set,cloudwhencloudCredentialRefis set, otherwisetoken. - Discovery:
syncLlmProviderModelsfetches vendor/models(OpenAI-compat Bearer, Anthropicx-api-key, Cloudflare account models). Azure / Vertex / Bedrock are manual-only. Sync deletes discovered rows not in the vendor payload, then upserts the set. - Write path: Direct synchronous writes via Drizzle on
ctx.dbin GraphQL resolvers (per ADR 0010), retiring the CQRS queue round-trip for tenant CRUD. Transactions are used for multi-table operations. Provider secrets are AES-GCM in theCONFIGKV namespace (enc-secret:<id>), keyed by Worker secretSECRETS_MASTER_KEY. - Routing: ModelRouter resolves candidates by consulting routes in strict order: exact alias match on enabled
llm_routesentries (match_kind = 'alias'), resolving candidate provider/model targets based on the route strategy (pinneduses the first available candidate,cascadetries candidates in sequence, whileautois reserved for wave M-4 and throwsBAD_USER_INPUT). When no route matches the requested model alias, ModelRouter falls back to the zero-config implicit cascade built from enabled provider models and default providers. Entry parameters are merged into the payload and client-credentials tokens are resolved for OAuth-configured providers.
No built-in fallback model: all inference routes through ModelRouter against tenant-configured providers. There is no edge/binding fallback and no implicit system default model: an unconfigured tenant lists zero models and cannot run inference — routing fails loud with No LLM providers configured for tenant <id>. The agent runtime resolves its default model the same way (resolveDefaultModel: default-kind provider models first, then any enabled model row; throws when none exist).
Agent Harness
An Agent Harness is a first-class aggregate: a named, tenant-scoped bundle of provider, model, generation params, autonomy mode, HITL tier, step budget, and tool bindings that Studio and the agent runtime treat as one subject.
- D1 tables:
agent_harnessesandagent_harness_tools(migration0010). - Commands:
CreateAgentHarness,UpdateAgentHarness, andDeleteAgentHarnessonmnemose.commands; projected into D1 byqueue-handler.ts(projectEventOntoDb). Create carries a caller-mintedharnessIdso the returned id matches the projected row. Temperature is stored ×100 (INTEGER) and converted at the GraphQL boundary (0..1). - Gateway: GraphQL module
services/gateway/src/schema/harnesses.ts(agentHarness/agentHarnesses, create/update/delete). - Runtime resolution:
AgentRuntimeDO/runacceptsharnessId. Lookup is tenant-scoped (id AND tenant_id); unknown or foreign ids return"Harness not found"(400). When present, the harness model overridesoptions.model(precedence: harness > model param)./agent/*routes require the same authentication as all other gateway routes. - Studio: Agent workbench save/load persists and rehydrates harnesses through the GraphQL harness operations; runs forward the selected
harnessId. - Vector store: attach point exists on the harness (
vectorStoreRefs) but remains deferred pending Cloudflare Vectorize free-tier verification. - Known gap: evaluation scorecards persist harness-subject runs as target type
"agent"(@mnemose/evaluationsschema only models model/agent targets).
Marketplace Harness Artifacts (M-3-A)
Distinct from agent harnesses above, marketplace harness artifacts
(kind = 'harness', ADR 0012) are importable JSON configuration presets
(HarnessSpecSchema in @mnemose/marketplace) sharing one validation layer
across three ingress paths: marketplace install, raw JSON import, and console
create (UI lands in a later stripe). Persisted in the existing
marketplace_artifacts table (spec in manifest_json; no new tables).
GraphQL: direct-write module services/gateway/src/schema/harness.ts
(createHarness / importHarness / listHarnesses / getHarness /
deleteHarness). See packages/marketplace/docs/architecture.md.
Installable App Lifecycle + Vendored Mounting (ADR 0013)
App-kind artifacts execute end-to-end on the Workers runtime:
- Lifecycle commands —
InstallApp,UninstallApp,EnableApp,DisableApp,UpdateAppConfigpublish tomnemose.commandsfrom the gatewayappsmutations;services/agent/src/command-subscriber.tsdispatches them to@mnemose/cmd-app-lifecycle(Workers-edge). Install fetches + validates the manifest from the catalog repo over HTTPS, replays the artifact’sdbSchemaModuleSQL idempotently (singledb.batch()), persists tomarketplace_artifacts, and emitsAppInstalled/AppConfigUpdatedetc. - Gateway mounting — vendored app gateway modules
(
services/gateway/src/app-modules.ts) register their queries/mutations on the shared Pothos builder beforetoSchema(); resolvers run through a per-requestSeoDb-style port built from the request’s D1 binding (ctx.seoDb), never importing cloud internals directly. - Console mounting —
/apps/:appId/*(InstalledAppShell) lazy-loads the vendored artifact’s default view when installed; the artifact’s stand-in GraphQL/page-shell adapters are overridden at bundle time by the real console modules (Vite exact-file aliases). A dynamic “Applications” sidebar section lists installed vendored apps. - Health —
assessAppHealthtakes host-supplied facts (mounted module ids, vendored console ids, deployed jobs, built images); manifest-declared surfaces require confirmation, undeclared surfaces pass. - Catalog index —
availableAppsis generated at build time from theapp-storesubmodule byscripts/generate-app-catalog.mjsintogenerated-app-catalog.ts(Workers cannot read the filesystem at boot). Each card surfaces the manifest’ssource(repo/ref/path) assourceRepo/sourceRef/sourcePathso the console install dialog can pre-populate source fields for registered apps.
Reference artifact: seo-rank-tracker (see docs/seo-rank-tracking.md).
Adding a new app: one static import + one registry entry per host
(services/gateway/src/app-modules.ts, apps/console/src/apps/registry.ts)
plus @app-store/* alias entries. See ADR 0013.
Key Files
| File | Purpose |
|---|---|
services/gateway/src/worker.ts | Worker entry (fetch/queue/scheduled) |
services/gateway/src/context.ts | GraphQL context factory (CF Access JWT + dev-token bypass auth) |
services/gateway/src/env.ts | Env interface (D1, R2, KV, Queues, DOs, AI) |
services/gateway/src/do/ | Durable Object classes |
services/gateway/src/queue-handler.ts | Queue consumer dispatcher |
services/gateway/src/event-listener.ts | Event projection processor |
packages/cloud-adapters/src/cf/ | CF adapter implementations |
packages/cloud-adapters/src/ports/ | Port interfaces |
packages/db/src/ | Drizzle SQLite schema + migrations |
packages/domain/src/ | Commands, events, personas, marketplace |
infra/index.ts | Pulumi IaC (D1, R2, KV, Queues, DNS, Routes) |
infra/profiles.ts | Deploy profile resolver |
wrangler.toml | Worker bindings configuration |
apps/console/ | React SPA source |
.github/workflows/ci.yml | CI pipeline |
Console wiring status (post Wave F-1)
Derived from the verdict matrix in functional-state-audit.md
(baseline e6df91e, ten pillar views mocked). Wave F-0 persisted the store,
evaluation, observability, and honeypot tables. Wave F-1 wired every mocked
view to a live API. The anti-mock ESLint rule
(mnemose/no-console-mock-theater) fails CI if SAMPLE_* / MOCK_*
constants or multi-line object-array useState seeds return under
apps/console/src/views (*.test.* / *.spec.* files are exempt).
Legend: REAL = view issues a live GraphQL/REST/DO call and renders only returned data. PARTIAL = live call, but the product surface is incomplete (read-only, missing fields, or a known proxy bug).
Legacy surface (unchanged since the audit)
| Area | View | State | API surface |
|---|---|---|---|
| Dashboard | dashboard.tsx | REAL | GraphQL DASHBOARD_QUERY |
| Fleet / Kernels | Fleet/* | REAL | GraphQL queries/mutations + subscriptions |
| Tenants / Users / IAM | tenants, users, iam-bindings, iam-escalations | REAL | GraphQL, D1-backed |
| Cloud projects / Ownership | cloud-projects, ownership | REAL | GraphQL |
| Provisioning / Remediation / Reports / Resources | provisioning, remediation, reports, resources | REAL | GraphQL |
| Encryption keys | encryption-keys.tsx | REAL | GraphQL |
| SEO suite | — | removed | Returns as the seo-rank-tracker marketplace app (S-3) |
| Login / Settings | login, settings | REAL | Auth flow |
| Sandboxes — list/table/policy | sandboxes/index, SandboxTable, NetworkPolicyInspector | REAL | GraphQL SANDBOXES_QUERY + DO mutations |
| Studio — session list | studio/index | REAL | GraphQL chatSessions / chatSession |
Wave F-1 pillar views (flipped MOCK → REAL)
| Area | View | State | API surface |
|---|---|---|---|
| Store — browse | store/StoreBrowseView | REAL | GraphQL availableApps, listTools, searchTools, installApp |
| Store — detail | store/StoreAppDetailView | REAL | GraphQL app, appHealth |
| Store — installed | store/InstalledAppsView | REAL | GraphQL installedApps, enableApp, disableApp, uninstallApp |
| Store — permissions | store/ToolPermissionMatrix | REAL | GraphQL getToolPermissions, setToolPermission, deleteToolPermission, evaluateToolPermission |
| Models — router | models/ModelRouterManager | REAL | GraphQL llmProviderConfigs (+ models, authMode), llmProviderKindMetadata, setLlmProviderConfig, model add/update/remove/sync |
| Models — fallback | models/FallbackTierList | PARTIAL | GraphQL llmProviderConfigs (read-only implicit cascade; see limitations) |
| Models — health | models/ProviderStatusGrid | REAL | GraphQL llmProviderStatus |
| Models — spend | models/SpendTelemetryPanel | REAL | REST GET /v1/observability/metrics/tokens |
| Evaluations — launcher | evaluations/EvaluationRunLauncher | REAL | GraphQL runEvaluation |
| Evaluations — runs | evaluations/BenchmarkRunsTable | REAL | GraphQL evaluationRuns (polls 5s while in flight) |
| Evaluations — leaderboard | evaluations/ModelLeaderboard | REAL | GraphQL evaluationRuns (aggregates completed scorecards) |
| Evaluations — scorecard | evaluations/ScorecardAccuracyTable | REAL | GraphQL evaluationRuns + evaluationRun(id) |
| Evaluations — diff | evaluations/RunComparisonDiff | REAL | GraphQL evaluationRuns + evaluationRun(id).caseResults |
| Evaluations — cases | evaluations/LlmAsJudgeResults | PARTIAL | GraphQL evaluationRun(id).caseResults (no judge/transcript fields; see limitations) |
| Observability — traces | observability/OtelTraceTimeline | REAL | REST GET /v1/observability/traces |
| Observability — tokens | observability/TokenUsageCharts | REAL | REST GET /v1/observability/metrics/tokens |
| Observability — honeypot | observability/CanaryHoneypotAlerts | REAL | GraphQL honeypotAlerts |
| Sandboxes — terminal | sandboxes/SandboxTerminal | REAL | GraphQL executeCommand (EXEC_IN_SANDBOX_MUTATION) |
| Sandboxes — files/artifacts | sandboxes/FilesystemArtifactExplorer | REAL | REST /v1/sandboxes/:id/files, /artifacts, artifact-content download |
| Studio — workbench | studio/AgentWorkbench + studio/index | REAL | GraphQL createChatSession / appendChatMessage + DO POST /agent/:id/run |
| Studio — live steps | studio/LiveStepExecution | REAL | DO GET /agent/:id/status + /transcript (polled) |
| Studio — transcript | studio/TranscriptInspector | REAL | DO transcript, fallback GraphQL chatSession.messages |
| Studio — HITL | studio/HitlApprovalBanner | PARTIAL | DO GET /status.pendingApproval + POST /approval/resume (singular; see limitations) |
Honest limitations
- FallbackTierList is read-only.
llmProviderConfigshas no persistable tier field. The view sorts default-first thenproviderKind; reorder is not available until the schema grows a tier column. - Studio HITL is a single pending approval. AgentRuntimeDO exposes
pendingApprovalonGET /statusand has no approval-list endpoint. The banner can approve/deny the current item only. - Eval case results lack judge/transcript fields.
EvaluationCaseResultcarries pass/score/latency/tokens/cost/model/rawResponse/failureReason. There is no judge rationale or per-case transcript to render. /api/kernels/*proxy prefix bug is still open. Gateway strips/agent/:idand/sandbox/:idbefore forwarding to the DOs, but/api/kernels/*forwards the raw request. KernelBrokerDO expects/kernels. Vite’s/kernelsproxy targets the local broker, so local Fleet may work while the deployed/api/kernelspath does not.