| Claim | Mechanism | Automated evidence |
|---|---|---|
| Limina owns execution | API lifespan recovers active projects; supervisor selects and owns SDK sessions | supervisor ownership and lifecycle tests |
| Codex and Claude Code are selectable | immutable runtime column plus validated public runtime field |
service, API, CLI, migration tests |
| Limina owns Codex login state | private CODEX_HOME, API-key/access-token materialization, and administrator-driven ChatGPT device login |
auth lifecycle, permission, concurrency, and fresh-volume live tests |
| Users do not manage provider machinery | public CLI/OpenAPI expose project concepts; serializers remove private state | public-surface and sanitized-status tests |
| Agents use the same public boundary | authenticated MCP tools/resources call the shared project operation layer | MCP discovery, cross-transport, attribution, and resource tests |
| Team identity and roles are enforced | OIDC/JWT claims create principals; project membership gates every shared operation | signed-token, anti-spoofing, membership, and role tests |
| Browsers can attach safely | short-lived one-time live tickets carry project identity and role | ticket consumption, replay rejection, and read-only viewer tests |
| Synchronous steering works | Codex steers in-turn; Claude interrupts then redirects the same session | adapter, supervisor, and WebSocket tests |
| Asynchronous steering survives disconnects | inbox write precedes live delivery and remains pending until checkpoint | durable inbox/checkpoint tests |
| A new process can resume | provider continuation is persisted before/while managed work starts | supervisor and both adapter tests |
| Several viewers share one history | persisted event sequence is independent of WebSocket connections | event-stream ordering tests |
| Private writes are scoped | short-lived capability maps to one project; internal routes are absent from OpenAPI | capability authorization tests |
| Control-plane credentials stay private | adapters build allow-listed child environments | provider environment-boundary tests |
| Secrets are write-only | encryption at rest plus public and runtime redaction | service, API, and adapter redaction tests |
| Resource changes reach active work safely | controlled restart discards the stale turn and rematerializes its environment | resource-refresh supervisor test |
| H → E → F is enforced | experiment requires H; finding requires completed E | invariant tests |
| Parallel KB writes are safe | atomic IDs, per-experiment leases, append-only observations, CAS transitions | concurrency and stale-write tests |
| Knowledge is queryable and visualizable | PostgreSQL FTS, paginated filters, graph relations, tags, comments, revisions, and saved views | UI-readiness API and migration tests |
| Runtime work is observable | every managed turn has a correlated run record with timing, events, errors, tools, and available usage | supervisor run, run detail, and analytics tests |
| Usage and cost are not conflated | per-turn provider usage and separate provider/operator-rate provenance | resumed-thread usage, pricing, migration, API, and live Codex tests |
| Transient provider failures recover durably | typed retry classification, attempt-per-run records, persisted wake time, and bounded backoff | retry/recovery tests |
| Public diagnostics stay product-level | run events use the same sanitizer as activity/live views; receipts are scoped to signed subjects | internal-field redaction, duplicate-display-name, and cross-project replay tests |
| Export remains portable | deterministic snapshot passes the existing KB validator | export validation test |
| Schema upgrades preserve Codex projects | migration defaults old projects to Codex and renames private continuity state | upgrade/downgrade and preservation tests |
uv sync --extra runtimes
uv run ruff format --check src migrations tests/test_cloud_*.py
uv run ruff check src migrations tests/test_cloud_*.py
uv run python -m unittest discover -s tests
LIMINA_DATABASE_URL=postgresql+psycopg://limina:limina@localhost/limina \
uv run alembic upgrade head --sql >/dev/null
python3 scripts/kb_validate.py
OPENAI_API_KEY=test ANTHROPIC_API_KEY=test LIMINA_API_TOKEN=acceptance-test-token \
docker compose config
OPENAI_API_KEY=test ANTHROPIC_API_KEY=test LIMINA_API_TOKEN=acceptance-test-token \
docker compose -f compose.cloud.yaml configmake runtime-check runs those mechanical checks. Building Dockerfile.cloud is a separate
acceptance step when a Docker daemon is available.
An independent API/security review initially found three defects: a cross-project comment replay, raw internal fields in run-detail events, and possible last-owner demotion. The fixes now have regression tests. A second independent pass verified those fixes, signed-subject receipt scoping, secret-redacted failure storage, source-credential rejection, and concurrent owner locking, and reported no remaining P0/P1 issue.
The current production image was rebuilt in an isolated Compose project and returned an authenticated health response advertising Limina runtime ownership, both engine adapters, REST, and MCP. The smoke volume and network were removed after the check.
Dockerfile.cloud was rebuilt with the direct mcp dependency and started as an unprivileged
container on a clean SQLite store. The published server reported:
{
"ok": true,
"runtime_owner": "limina",
"runtimes": ["codex", "claude-code"],
"interfaces": {"rest": "/v1", "mcp": "/mcp/"}
}An authenticated MCP initialize negotiated protocol 2025-06-18. A subsequent
limina_create_project tool call, attributed with X-Limina-Actor, returned structured project
content with isError=false. A REST read then returned the same mcp-smoke project and durable
timestamps from the canonical store. This proves the packaged process, mounted Streamable HTTP
transport, authentication middleware, MCP tool dispatch, shared operation layer, and REST/MCP
state convergence without invoking either paid provider runtime.
The adapter uses openai-codex for:
- starting and resuming threads;
- JSON-schema turn output;
- consuming notification streams;
- steering an active turn;
- interrupting active work.
These behaviors correspond to the official Codex SDK and app-server turn lifecycle documentation. The streaming result collector is currently a private SDK helper, isolated inside the adapter and called out as a compatibility risk.
The official SDK was exercised against an actual Limina server:
-
A structured turn started SDK thread
019f5b54-d13f-7202-9ffe-1f587b3c180aand returnedstatus=COMPLETE. -
A deliberately failed project was resumed through
limina resume runtime-smoke, exercising recovery rather than a user-managed session. -
The managed turn reached the capability-scoped private API from its workspace sandbox:
GET /internal/v1/projects/runtime-smoke/status HTTP/1.1 200 OK GET /internal/v1/projects/runtime-smoke/artifacts HTTP/1.1 200 OK -
Steering submitted during the turn returned:
{"delivery":"LIVE"} -
The supervisor committed:
{ "status": "COMPLETE", "next_step": "Accept this checkpoint and close the runtime smoke test.", "blocker": "None", "knowledge": {}, "pending_guidance": 0 }
The first attempt found an environment-isolation defect: blanking Codex origin metadata prevented SDK initialization. The allow-list now retains required, non-secret Codex metadata while blanking database, admin-token, and unrelated values. A regression test covers that boundary.
[[E002]] rebuilt the current image and started an isolated Compose project with a fresh named
volume. The previously supplied API key entered the test through no-echo process input, was never a
command argument, and was removed with the container and volume after the run. Only public Limina
CLI commands created and started the project; there was no directory repair or manual codex login.
The first managed turn completed in 51,192 ms with gpt-5.4, 24 tool calls, no retry, and a durable
H→E→F chain. The provider reported 19,824 input, 18,816 cached-input, 217 output, 90 reasoning-output,
and 20,041 total tokens. usage_source=provider; cost and cost_source were null because no operator
price was configured. This is the intended honest state rather than an inferred provider price.
Post-run checks found no credential pattern in durable project events, found the Codex state
directory at mode 0700 and auth.json at 0600, and found no residual test container or volume.
The complete finding matrix and raw summary are under kb/research/data/E002/.
The same acceptance run exercised the automated suite:
Ran 77 tests in 4.720s
OK
KB validation passed.
ChatGPT account login is available through limina runtime codex login --method chatgpt and the
instance-administrator REST flow. Device start/poll/cancel and auth/turn exclusion are covered by
the pinned SDK contract tests. Completing the browser code was intentionally not part of the
API-key trial; cached ChatGPT credentials remain single-runtime-node state and serialize Codex
turns, as documented in the deployment contract.
The adapter uses the official Claude Agent SDK for Python for:
- connecting a persistent client and resuming a session ID;
- the bundled Claude Code tool and system-prompt preset;
- JSON-schema structured output;
- sandbox and tool-permission controls;
- receiving assistant, tool, task, and result messages;
- interrupting active responses;
- redirecting a live session with newly committed steering.
Contract tests replace the SDK module with a deterministic fake and verify option construction, workspace and environment isolation, session resume/persistence, event mapping, structured checkpoint parsing, disconnect, and interrupt-then-redirect steering. These tests exercise the adapter protocol without spending provider credits.
No live Anthropic API smoke has yet been recorded. That remains an explicit release-evidence gap,
not an implied pass. A live smoke should create a claude-code project, perform one private
knowledge command, steer while it is active, restart the server, and prove the same session resumes.
The default docker compose up --build --detach path built the earlier Codex-only image, created
one limina-data volume, applied migrations, and started one healthy server. limina doctor
reported runtime_owner=limina; a visible DATASET_URI and write-only SERVICE_TOKEN produced:
[
{"name":"DATASET_URI","type":"VARIABLE","value":"s3://example/eval.parquet"},
{"name":"SERVICE_TOKEN","type":"SECRET","configured":true}
]The secret was absent from CLI output and a byte scan of persistent storage. The encryption key was
owned by limina:limina with mode 0600. Project and secret metadata survived a container restart.
Automated tests additionally reopen the key, decrypt with project/name binding, rotate, redact
runtime output, and verify logical value wiping on removal.
That historical run does not establish that the newly added Claude SDK packages correctly; the current two-runtime result is recorded separately below.
Dockerfile.cloud built successfully for Linux arm64 with both openai-codex and
claude-agent-sdk, plus Claude Code's bubblewrap and socat sandbox dependencies. A container
check loaded both SDKs, executed a basic unprivileged bubblewrap boundary, and rendered packaged
limina project help. A fresh one-command Compose instance then:
- applied the runtime migrations and became healthy;
- advertised
runtimes=["codex", "claude-code"]; - accepted one
runtime=codexproject and oneruntime=claude-codeproject; - returned each immutable runtime through the public project projection;
- shut down cleanly, with the isolated smoke volume removed.
This establishes image packaging, migration startup, health discovery, and runtime selection. It does not replace the live Anthropic execution smoke described above.
- API restarts: lifespan recovery recreates loops from canonical project state.
- Runtime dies before a final checkpoint: the continuation pointer and accepted knowledge are already durable; the lease eventually permits takeover.
- Long turn: a heartbeat renews the coordinator lease; lease loss interrupts the turn.
- Pause/stop during a turn: provider interruption is requested and lifecycle state wins over a proposed checkpoint.
- Two replicas recover one project: only one obtains the coordinator lease.
- Lost HTTP response: idempotency receipts return the original database result.
- MCP retry: callers may supply the same durable idempotency key; no provider session is exposed.
- Lost WebSocket: guidance and events remain durable; reconnect replays them.
- Missing live turn: feedback remains queued.
- Concurrent artifacts: per-kind atomic counters allocate unique IDs.
- Concurrent experiments: leases are scoped to experiment IDs, not the project.
- Stale decision: compare-and-swap rejects it without acknowledging unrelated guidance.
- Export failure: canonical state remains valid and export can be retried.
- Unknown runtime value: validation rejects it; the factory also fails closed.
- Old database: migration assigns existing projects to Codex and preserves their continuation.
- Workspaces are durable directories, not isolated project containers or microVMs.
- Bounded uploads live in the project workspace; large evidence has external URIs but no object-store lifecycle manager or malware-scanning pipeline.
- Live guidance is durable-at-least-once; exact boundary failures may replay a message.
- The Codex Python SDK is beta and one private result collector remains a compatibility risk.
- Claude Code continuity includes provider-managed local transcript files; backup and restore of the Limina volume must preserve those files with the database.
- A live Claude Code provider smoke and restart-resume proof are still required.
- There is no transactional notification outbox, quota system, billing, or admin audit export.
- Compose demonstrates one runtime replica; multi-replica failover still needs a target-platform test against managed PostgreSQL.
- The generated local encryption key favors one-command operation. Replicas need a shared key from a secrets manager, and backups need separate key escrow.
- MCP intentionally omits secret-value writes; automation that provisions secrets must use the trusted CLI or REST API. Remote hostnames must be added to the MCP DNS-rebinding allowlist.
These limits do not change runtime ownership. A scheduler, sandbox manager, or notification service must remain an internal Limina concern rather than exposing provider session management to users.