SQLite authority provider¶
SQLite is an opt-in local conformance candidate, behind the existing
TypeScript AuthorityStore interface. File remains the canonical-provider
fallback when no selector is present; this does not make every new Goal
canonical or migrate an existing legacy Goal. This slice
does not promote a goal, run a live cutover, enable cross-host writes, or
qualify ten elapsed days of operation. It does provide the explicit
version-1 to version-2 database migration described below.
Short-term default decision and matched experiment¶
The September 27 decision is to target SQLite for the next qualified local new-Goal default, rather than first defaulting to File and moving again. This is an implementation direction, not default activation or completed D2 qualification. File remains an explicit provider, a conformance reference and an export/recovery destination. Existing Goals retain their selected authority; a rejected SQLite runtime must never silently open File instead.
The decision compares current File checkpoint/delta storage, not its retired
full-projection-per-commit layout. The final matched experiment ran sequentially
on Linux x86_64 (16 vCPUs), Node 22.22.3 / SQLite 3.51.3, with baseline
96ce9efb3 and candidate 758db221e. For each workload the baseline SQLite
arm ran immediately before its candidate arm; candidate File arms followed.
The same runner and qualified runtime served every arm. Each uses 20 warm
read samples, the final 100 writes and five fresh-process head reads. Values
below are p95 milliseconds. Cold process includes module loading and does not
clear the OS page cache. These are bounded observations, not population
estimates or formal capacity/soak qualification.
| Projection / commits | SQLite receipt before | Receipt after | SQLite scan 100 before | Scan after |
|---|---|---|---|---|
| Mixed 20 KiB / 128 | 67.69 | 16.04 | 184.69 | 71.37 |
| Mixed 20 KiB / 512 | 75.70 | 17.18 | 187.22 | 67.61 |
| 464 Todos, 64 leases, 220 KiB / 128 | 786.01 | 91.52 | 1964.03 | 623.49 |
| Changing 1 MiB / 128 | 456.97 | 288.76 | 1190.10 | 1114.94 |
| Fixed 64 KiB / 128 | 62.96 | 9.57 | 170.34 | 56.36 |
The many-field receipt/scan costs fall by 88%/68%, but still exceed the 50/250 ms targets on this host. Changing-1-MiB scan improves only about 6%: returning 100 complete large states remains expensive. Mixed and fixed-state reads also improve; the optimization is no longer limited to large strings. Write costs remain close to baseline because this is primarily a replay change. No frozen qualification threshold has been increased.
The candidate-provider comparison retains the tradeoffs instead of declaring one provider faster for every operation:
| Projection / commits | SQLite write / warm head / cold head | File write / warm head / cold head | File receipt / scan 100 |
|---|---|---|---|
| Mixed 20 KiB / 128 | 10.26 / 2.11 / 146.14 | 21.01 / 4.81 / 296.79 | 3.28 / 97.72 |
| Mixed 20 KiB / 512 | 10.56 / 2.59 / 143.09 | 51.87 / 6.08 / 702.25 | 5.55 / 78.13 |
| 464 Todos, 64 leases, 220 KiB / 128 | 64.24 / 16.44 / 152.50 | 72.75 / 7.69 / 1494.86 | 3.48 / 939.29 |
| Changing 1 MiB / 128 | 97.50 / 19.51 / 164.87 | 761.97 / 115.46 / 1892.69 | 154.34 / 441.47 |
| Fixed 64 KiB / 128 | 12.27 / 2.84 / 144.58 | 14.85 / 3.19 / 329.43 | 3.22 / 85.83 |
SQLite has lower write and restart costs in every measured shape. File has cheaper warm receipts and, for the many-field fixture, a cheaper warm head. SQLite now scans ordinary/many-field history faster, while File wins the changing-1-MiB scan. This supports SQLite as the implementation target for mutation/restart-heavy local operation, not a universal read-performance claim.
The 1-MiB SQLite post-fill RSS rises from about 118 to 160 MiB; other measured SQLite shapes remain near their baseline. This includes fixture and verification allocations and is not a steady-state or leak measurement. Sustained-memory qualification remains open. Baseline/candidate SQLite final store bytes match in all five shapes; no persisted format is changed. Earlier macOS measurements motivated the initial target, but a new local paired run encountered load above 40 and unstable unchanged-baseline timings. Its correctness checks were kept; its timing samples are not used to claim improvement or qualification.
These tests change a deterministic observation field in a retained full state;
they exercise storage, not the complete CLI/Turn workflow. In changing-1m,
padding is resized to retain exactly 1 MiB, so the large string itself can
change. It must not be reported as a stable-payload cache benchmark.
Reproduce each arm from the same checkout and qualified Node runtime:
node --experimental-sqlite --experimental-strip-types \
examples/coordination/local-provider-comparison.ts \
--provider sqlite --workload mixed --commits 128 --samples 20
# Repeat with --provider file. Workloads: mixed, full, fixed-64k, changing-1m.
# Use --commits 512 to cross more checkpoint windows; --output writes JSON.
The runner creates and removes its own temporary store, checks complete
projections (including Todo metadata), events, original receipts and reopened
state, and records the source revision, runtime, runner hash and tracked source
diff hash. After timing, it verifies that an exact historical retry returns the
original applied revision/cursor, even after later commits; projection-, event-
or receipt-only drift must conflict. The later head, original receipt and entire
paged history must remain unchanged. historical_replay_and_conflict_checks
is reported only after these checks pass; a failed check prevents a successful
report. These are storage guarantees, not Goal-instance isolation or permission
to repeat external effects. It does not open a selected live Goal. RSS includes fixture/checking
allocations; File publication bytes are application bytes, not physical disk
writes. Use the existing SQLite capacity runner for WAL traffic and D2 history
sizes. Keep performance experiments separate from concurrent test suites.
Before changing release defaults, the existing owners must close these gaps:
- L6 / D2: rerun the unchanged reference capacity profiles after read-path optimization; qualify many-field/changing-state history, crash/restore, consumer lag, supported runtimes/platforms and the >=10-day elapsed soak. PR #4931 contributes read-proof optimization, not a D2 pass.
- L8 / D3: qualify the integrated Goal command/projection and migration path. Reuse merged reviewed migration/retained audit (#5173), managed-host protection (#5144) and obsolete Todo-source retirement (#5054), rather than count them as new work. A storage benchmark does not certify long-running execution or authorize migration of existing Goals.
- L9: make new-Goal creation/onboarding choose that qualified profile, including installed runtime admission, settings/readback and packaged entry points. Keep explicit provider selection and reviewed backup/rollback.
The L9 implementation candidate now resolves an absent goal_storage namespace
to canonical SQLite/hard-lease creation through the existing configuration and
TS initialization owners. Explicit v0 selectors, v1 opt-out and existing Goals
keep their recorded route. Settings preserves v0 opt-out during guided/JSON
conversion; removing a setting restores the new default. See new-Goal creation
and compatibility.
This candidate does not certify the gates above or activate an installed release.
Both current local providers require Node >=22.22.3. SQLite uses built-in
node:sqlite: it adds no database service or external SQLite package. Its
actual embedded SQLite/finalization probe remains required, and a pre-existing
managed runtime must be restarted on the qualified executable as documented
below. PostgreSQL deployment is independent of this local default decision.
Placement and persistence¶
The provider belongs to the existing shared-coordination authority boundary
(loopx/control_plane/coordination). It is bundled with LoopX, not a new
capability or extension. Legal transitions, actor/lease checks, operation
digests and replay decisions remain with the existing typed transaction
executors. Python only admits the corresponding sqlite_v0 source receipt.
Each goal has a separate database under the runtime's authority/sqlite-v0
directory. Metadata binds the goal, schema version and random database
incarnation. Provider revisions combine that incarnation with a monotonic
integer sequence; they are not authority revisions or lease epochs.
The version-2 schema contains:
| Table | Contract |
|---|---|
metadata |
Version and database/goal identity |
head |
The live committed projection, its state digest and one cursor |
commits |
Unique operation ID, canonical commit digest, ordered cursor, original receipts and events, one exact state delta, its state digest and parent state digest |
checkpoints |
One full projection and its digest per bounded window |
commits also serves as the durable projection outbox used by
scanCommitted. There is no independent ACK or second receipt authority.
Existing consumers resume by cursor. A unique operation index makes receipt
lookup and cursor paging indexed.
Retention is bounded by window instead of by history: every commit keeps one
exact delta, and one full projection is retained per checkpoint window
(authority_state_log.ts, 64 commits per window). A live read resolves the head
from the head row, its retained transaction and the cursor bounds; a historical
read rebuilds at most one window from the covering checkpoint. Retained deltas
are therefore the only part that still grows with history, and their size is
proportional to what each commit changed. Original receipts and events are
retained without pruning, so fixed live state with large receipts still grows
with history. Growing application projections require separate
retention/compaction work.
The rehearsal profile measures this profile directly: at 1,000 commits with a 64 KiB projection, 16 checkpoints retain 1,048,576 projection bytes and 126,714 delta bytes, against 65,536,000 bytes for one full copy per retained commit. Formal 10k/100k evidence still requires the separately authorized matched profile.
Writes use BEGIN IMMEDIATE, a five-second busy timeout, WAL and
synchronous=FULL. The head, receipt, events and outbox row commit together.
Before-COMMIT failures roll back; a COMMIT error reports an ambiguous outcome
for receipt reconciliation. Readers use committed snapshots; no history rewrite
is needed for a new transaction. SQLite storage durability still depends on the
local filesystem and hardware honoring synchronization.
Schema changes are explicit: unknown user_version, foreign tables or a
different goal/incarnation fail closed. No automatic migration, identity
rotation, corruption repair, or network-filesystem sharing is supported.
Cursor covering index¶
New version-2 databases include a non-unique index over commits(cursor).
The existing explicit V1-to-V2 migration uses the same table builder and creates
its index inside the migration transaction. The narrower index reduces the
pages SQLite visits for the exact continuity count; the count remains dependent
on retained history. Head, parent, digest, checkpoint and original-receipt proofs
are unchanged. The index stores no new authority or operation receipt.
Ordinary reads and writes do not add the index to existing version-2 databases.
Those databases retain their current query plan and remain supported without a
rebuild. This additive physical index leaves user_version=2, metadata and all
logical rows unchanged; earlier V2 readers can read indexed databases, and the
current reader accepts databases without the index. Reverting the binary does
not require deleting it. Any existing-Goal rebuild still requires the existing
explicit migration/recovery route and its authorization, rather than an implicit
upgrade during a command.
The index adds storage pages and write maintenance. Measure those costs with matched reads and writes, page/WAL traffic and complete CLI calls under the original declared profile. Keep unmeasured payload, headroom and elapsed rows explicit; a faster count alone does not qualify all of D2. Provider selection and migration permission do not change with this index.
Read integrity¶
Direct retries follow the authority operation replay contract: matching full intent returns its verified original position without a new write.
Authority reads share one SQLite snapshot, and writes run the same live proof inside their transaction before publishing a new commit row. The proof is layered so that each layer pays only for what it returns:
| Layer | Proves | Cost |
|---|---|---|
Live head (loadAuthority, commitAuthority) |
Head row digest over the live projection, the retained transaction at that cursor reproducing its exact commit digest, parent linkage, min=1/count=max=head cursor continuity, and the presence of the checkpoint that covers the head |
One head row, one retained row and one parent digest; no projection replay. The indexed continuity count still depends on retained cursor count |
Materialized history (scanCommitted, readReceipt) |
Every row from the covering checkpoint through the requested span, including each delta, state digest and parent lineage; paged scans also prove the lookahead row used for has_more |
At most one checkpoint window plus the requested span |
Archive audit (verifyAuthorityHistory) |
The complete delta chain from the empty root, every checkpoint against retained history, and the final state against the head | Linear in retained history; qualification and recovery only |
Historical reconstruction uses the shared TS AuthorityStateReplay owner.
It validates and privately copies the initial state and each delta, copies only
changed object paths, and reuses exact canonical encodings for unchanged
subtrees. Both proofs still hash the complete original v0 byte sequence; this
is not a new Merkle proof or a persisted digest format. Cached subtree keys are
weak, and encoded long-string buffers are capped separately; no cache survives
the provider read/audit call. A receipt query verifies its entire covering span
without materializing projections it does not return. Scans return independent
JSON objects, so editing one row cannot alter another row or a later read.
The same owner now reconstructs provider-neutral archives. Previously a consumer could edit a yielded transaction's projection and change the decoder's next replay basis, causing a later digest or Goal-identity failure. The decoder now retains private state; callers receive detached projections. Existing File/SQLite/PostgreSQL archives and terminal seals keep the same bytes. Rejected delta batches never advance the replay frontier, and sparse protocol arrays are rejected rather than bypassing validation of their absent elements.
A missing head, rolled-back head, internal cursor gap, rewritten receipt/event,
orphaned parent digest or mismatched state digest is rejected as
provider_protocol_violation before returning authority or accepting a write.
Preparing a commit also re-applies its own delta and requires byte equality with
the committed projection before anything is written.
Two shapes are deliberately outside the live proof because the live head never reads them: the delta of the newest retained row, and the projection of the checkpoint the live head resumes from. Both are refused by every read that materializes their span and by the archive audit, and neither can change the authority value a live read returns. The commit digest is unchanged from v0 (operation ID, projection, events, receipts, expected predecessor revision), so cursors, provider revisions and stored digests stay comparable.
This is integrity validation of the current and accessed evidence, not a full cryptographic audit of every historical payload on each call. Unaccessed older row digests are checked when those rows are read. Checksums detect inconsistent data; they do not authenticate an administrator who can rewrite both the data and its digest. Restoration of an older, internally consistent database remains outside this slice's qualification boundary.
Explicit selection¶
Use an isolated qualification runtime and an empty, unpromoted goal. Set
RUNTIME_ROOT to that runtime's absolute directory. The SQLite qualification
reference is Node 22.22.3 with SQLite 3.51.3. The provider checks the actual
embedded SQLite version and synchronous prepared-statement finalization before
creating or opening an authority database. Record both sqlite_version() and
sqlite_source_id(); the Node version alone is insufficient.
SQLite 3.51.3 and later 3.x releases contain the WAL-reset concurrency fix. The fixed 3.44.x (3.44.6+) and 3.50.x (3.50.7+) backport lines are also admitted when their driver finalizes statements on close. Unknown version strings or unverified vendor backports fail closed. Passing these prerequisites does not qualify the complete D2 profile.
This intentionally rejects SQLite runtimes previously accepted by the statement-only probe, including vulnerable drivers shipped with older Node 22 releases. The public Node minimum is now 22.22.3 for both File and optional SQLite use; SQLite also checks the actual embedded driver. Provider selection does not change, and an explicitly selected SQLite runtime never falls back to File when rejected. The optional driver is still opened only after opt-in. The serving runtime loads it in-process once at startup for the read-only identity probe described below; that probe opens no database file and creates no authority state, and the File path keeps working when the driver is unavailable.
From the repository checkout, preview selection:
node --experimental-sqlite --experimental-strip-types \
loopx/control_plane/coordination/local_authority_provider.ts \
--runtime-root "$RUNTIME_ROOT" --goal-id example
Apply the same command with --execute. It creates the empty database and a
durable per-goal selector bound to its incarnation. Repeating it is idempotent.
The command rejects an existing canonical file head or a writer fence. It does
not bootstrap or promote authority. Existing qualification/promotion gates
still govern the first canonical state, with the chosen provider used as the
destination. Do not bypass those gates to enable a live goal.
The process starting the managed Effect runtime must use the qualified Node runtime too. Stop a previously running managed runtime normally before changing its Node executable. Adding an experimental flag to an older Node 22 release does not fix its statement lifecycle.
The managed Effect runtime is reused per user and source revision, so the
Node/SQLite pair serving a goal is not necessarily the one the calling process
resolves from PATH. Its info file records a runtime_identity
(node_version, sqlite_version, sqlite_authority_qualified), and
loopx doctor reports that identity plus a restart recommendation when the
serving runtime is not qualified. Installing the qualified Node alone does not
repair a runtime that is already running: restart it with
so the next control-plane request starts a new runtime from the current PATH. Waiting for the runtime's idle shutdown has the same effect. A direct CLI invocation that is not the reused runtime reports the same qualification failure without a restart step, because rerunning it on the qualified PATH is already sufficient there.
After separately admitted canonical initialization, ordinary loopx todo
commands use the persisted selector. For example:
loopx --registry "$REGISTRY_PATH" --runtime-root "$RUNTIME_ROOT" \
--format json todo list --goal-id example
An unavailable database, malformed selector, changed incarnation or lost selector fails closed; none silently falls back to file authority. Deleting the generated Markdown does not delete the canonical Todo state.
Provider-open errors retain selection identity separately from request errors.
A validated SQLite selector produces source_authority=sqlite_v0 even when its
database is missing or unreadable. An invalid, unavailable or missing selector
reports source_authority=null because selection is unresolved; it never guesses
file authority. Typed local_authority_selector_* and
local_authority_provider_* reason codes identify that boundary, with a
provider_reason_code when the store returned a more specific diagnostic.
These failures set both decision_read_from_provider=false and
legacy_fallback_used=false. Successful responses and unrelated request/domain
errors retain their existing contracts.
Promotion failure evidence¶
legacy_writer_fenced reports whether this promotion invocation verified the
exact persisted fence against the request. Provider opening precedes that
verification so opening errors retain their selected-provider diagnostics.
Such early failures report false, even if a fence exists but was not read and
matched. This is not proof that legacy writes are allowed; callers must consult
the durable writer guard. After successful fence verification, later failures
retain true. Request fields alone never establish fencing evidence.
The failure-path regression matrix covers:
| Boundary | Evidence checked |
|---|---|
| Selector/database open | List, exact read, mutation, create, claim, native/planning update, compatibility edit, terminal, monitor poll, archive, ACK and promotion retain typed source/reason, no fallback and unchanged authority bytes. |
| Fence readback | Missing, malformed and mismatched fences do not establish verified fencing; an open failure cannot infer it from an existing marker. |
| After verified fence | Missing/invalid shadow and rejected qualification preserve verified fencing without canonical writes. |
| Existing promotion readback | Exact receipt/first-commit lineage permits replay; missing receipts or mismatched lineage reject without modifying authority. |
The matrix is typed against every exported runtime entrypoint so adding a new entrypoint requires an explicit failure fixture.
These tests use disposable file/SQLite stores and the production runtime entrypoints. They preserve the current qualification gate: mirrored file shadow health alone does not authorize a new canonical cutover. Shared store conformance separately covers transactional CAS, commit ambiguity, receipt reconciliation and projection replay. No active Goal is needed for this validation.
Stop and recovery boundary¶
To stop using the candidate, stop the owning goal/host runtime and retain its
database and selector. For a managed goal, loopx configure-goal --goal-id
example --quota-compute 0 --execute pauses automatic turns; separately stop
any active host process before taking an offline backup. Pausing does not
cancel a transaction already running.
Return to File through a reviewed migration from the current SQLite head
using the steps below. Do not delete the selector to disable the provider.
Preserve the database together
with any -wal/-shm files when recovering an interrupted runtime; use SQLite
backup facilities or a fully stopped database for a coherent backup. Restoring
an older snapshot as concurrent live authority is not supported. Disposable
qualification runtimes may be retired as a whole after their processes stop.
Selection grants local storage use only. It grants no actor/lease ownership, external service access, cross-host synchronization or promotion authority.
Reversible developer trial / 可逆开发者试用¶
Use the existing at-most-20-person opt-in stage; qualify each entry profile separately. A new canonical Goal, an existing canonical File Goal and a cold Markdown Goal have different entry requirements. The cold source must complete reviewed import; it cannot enter by creating an empty database. Existing canonical migration uses Goal data storage. Do not make every participant wait for unrelated profiles or all formal D2 axes.
Before admission, retain the exact candidate commit/package digest, actual Python/Node/embedded SQLite versions, platform and selected Goal/provider/policy. Compare the wheel's product Python/TypeScript inventory with that source; retired modules must be absent after both clean and repeated builds. Stale build output is not compatibility and cannot certify physical retirement. Verify its installed CLI and packaged App entry, ordinary Todo metadata/source readback, restart/original-operation recovery and a current-write-preserving exit in an isolated synthetic Goal. Keep passed, failed and untested cases separate. This operation sheet is preparation, not a trial invitation or a certificate for a released default.
Bind these checks and later operations to the reviewed installation, not the
loopx currently found on PATH. Two installations can report the same version
while carrying different source. Set TRIAL_LOOPX_BIN to the candidate's
installed console command or release launcher; resolve it once before the first
read so a later release-symlink switch cannot retarget the trial. Record the
installation doctor's runtime identity (release/source, or package fingerprint
for a normal wheel) and Python executable alongside the candidate digest/source,
and check that they identify the selected installation.
The PATH-discovered command in that report is only discovery context. An
inherited PYTHONPATH must not substitute a checkout for the selected package.
Record the selected Host separately from storage readiness. In Chat, a built-in
CLI endpoint's available flag checks executable discovery; it does not prove
authentication, model access, a completed Turn or restart recovery. For Codex,
codex --version and codex login status inspect the existing installation and
login without sending a model request. Do not copy credentials or another
session's history to qualify a profile. An actual adapter thread/start response
also establishes only startup: an empty upstream session can still lack a
recoverable rollout after its process exits. Keep a refused original-session
resume visible; neither a fresh replacement nor successful startup proves
original-operation recovery.
Use turn plan below to inspect the selected Todo's native decision without
starting a model, writing state, acknowledging a scheduler or spending quota.
outer_controller describes the hypothetical headless caller; this read-only
command does not install or activate that controller. A ready route remains a
plan, and the returned required reads, lease and execution boundaries still
apply to any later authorized execution. Qualify the cohort's chosen Host and
workload; unavailable unrelated Hosts do not block an independently qualified
profile. Keep successful first-Turn execution and its original-operation restart
readback separate from these non-model checks.
For an already canonical trial Goal, set TRIAL_REGISTRY, TRIAL_RUNTIME and
TRIAL_GOAL to its reviewed routes; choose TRIAL_EXIT_PLAN outside the live
store. Keep the plan and returned plan_sha256 privately. This POSIX-shell
example retains one resolved candidate command for all reads and operations:
: "${TRIAL_LOOPX_BIN:?Set the installed command for the reviewed candidate}"
TRIAL_LOOPX_BIN=$(python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$TRIAL_LOOPX_BIN")
trial_loopx() {
: "${TRIAL_REGISTRY:?Set the reviewed registry}" "${TRIAL_RUNTIME:?Set the reviewed runtime}"
: "${TRIAL_GOAL:?Set the reviewed Goal}"
env -u PYTHONPATH "$TRIAL_LOOPX_BIN" --registry "$TRIAL_REGISTRY" \
--runtime-root "$TRIAL_RUNTIME" --format json "$@"
}
trial_loopx doctor --installation-only
trial_loopx todo list --goal-id "$TRIAL_GOAL"
: "${TRIAL_AGENT:?Choose the registered trial Agent}" "${TRIAL_TODO:?Choose its eligible Todo}"
# Use the selected Todo's authorized workspace; repository writes require an independent worktree.
trial_loopx turn plan --goal-id "$TRIAL_GOAL" --agent-id "$TRIAL_AGENT" \
--todo-id "$TRIAL_TODO" --host codex-cli --iteration-context fresh \
--execution-mode isolated-headless --scheduler-owner outer_controller \
--available-capability filesystem_read
# These inspection commands do not execute the first Turn or grant a lease.
trial_loopx todo list --goal-id "$TRIAL_GOAL"
trial_loopx configure-goal --goal-id "$TRIAL_GOAL" --quota-compute 0 --execute
# Separately stop owning Hosts/writers and settle active leases/captures.
: "${TRIAL_EXIT_PLAN:?Choose a private plan path outside the live store}"
trial_loopx authority-archive plan-migration --goal-id "$TRIAL_GOAL" \
--provider file --plan "$TRIAL_EXIT_PLAN"
# Review the frozen current source and backup; copy its returned plan_sha256.
: "${TRIAL_EXIT_DIGEST:?Copy the reviewed plan_sha256}"
trial_loopx authority-archive migrate --goal-id "$TRIAL_GOAL" \
--plan "$TRIAL_EXIT_PLAN" --plan-sha256 "$TRIAL_EXIT_DIGEST"
# Confirm only that unchanged reviewed plan.
trial_loopx authority-archive migrate --goal-id "$TRIAL_GOAL" \
--plan "$TRIAL_EXIT_PLAN" --plan-sha256 "$TRIAL_EXIT_DIGEST" --execute
trial_loopx todo list --goal-id "$TRIAL_GOAL"
Preview is read-only. Active/expired unsettled leases, active capture and source
drift remain refusals, not reasons to edit state manually. After an ambiguous
apply, keep and retry the original plan/digest; do not generate a second apply
or restore an old pre-trial backup over later writes. Readback must report
authority_read.source_authority=file_v0, legacy_fallback_used=false and the
same full Todo records. Historical completion cannot reselect a superseded
provider. Keep automatic turns paused until the exit/readback succeeds; restore
only the reviewed prior quota configuration after owner confirmation.
During the trial, follow each native guard's required reads and admission; after validated writeback consume its original refresh/spend continuation. Retry the same operation/Turn identity after response loss. Collect actual daily tasks, latency/resource/history growth, failures, human interventions and successful recovery; an accelerated loop is not elapsed-time evidence. Stop the affected lane for data loss, duplicate effects, identity/settlement/fence errors or failed recovery. Report the candidate/profile, step, original operation identifiers, expected/observed result and whether later writes survive through the project's normal issue/review route; redact private text, paths and raw evidence. Storage activation grants no Host execution, lease ownership or external access.
Run the plan from the selected Todo's authorized workspace. A repository-writing
peer Todo inspected outside its independent worktree can correctly return
agent_workspace_repair, even though the installation and storage are healthy.
Move to the existing authorized worktree and repeat the same read-only plan;
do not change the Goal route or weaken its workspace guard. A successful process
exit or ok=true alone does not mean a ready route: inspect route.kind,
route.effective_action, the envelope obligations and effects. Plan-only
host_invocation_allowed=false remains expected even for ready_for_host.
新 canonical Goal、已有 canonical File Goal 和冷 Markdown Goal 按各自入组路径 验收。复用既有不超过 20 人的可逆 opt-in 阶段,冷源先完成审核导入;不能通过创建 空 SQLite 库替代导入,也不让无关 profile 或全部 D2 轴成为每个人的前置。 入组前记录精确候选包/提交、实际 runtime/平台、Goal/provider/policy。 核对 wheel 中产品 Python/TypeScript 文件与当前源码一致;冷构建和重复构建都不得 夹带已退役模块。陈旧 build 产物不是兼容性,也不能作为物理退役证据。在隔离 合成 Goal 验证安装 CLI、打包 App、完整 Todo/来源、重启原操作恢复及保留新写入的 退出。上面的命令是操作准备,不代表已经邀请试用或发布默认资格通过。
把后续检查和操作绑定到已审核的安装,不直接使用 PATH 上碰巧命中的 loopx。
两个安装可以版本相同而源码不同。TRIAL_LOOPX_BIN 指向候选安装的 console
命令或 release launcher,在首次读取前解析一次实际路径,避免后续 release 符号
链接切换悄悄改变试用来源。doctor 的 runtime identity(release/source,正常 wheel
为 package fingerprint)和 Python executable 应与候选包/源码一起记录并核对;
报告中的 PATH 命令只是发现信息。上述 wrapper
移除继承的 PYTHONPATH,防止另一个 checkout 替代候选包;不会更新全局安装。
Host 与存储分别验收。Chat 内置 CLI endpoint 的 available 仅检查可执行文件
能否找到;不是登录、模型访问、Turn 完成或重启恢复证明。Codex 可先用
codex --version、codex login status 读取已有安装与登录,不发送模型请求,
不复制凭据或其他 Session 的历史。真实 thread/start 返回也只证明启动;空
Session 的进程退出后可能尚无可恢复的 rollout。原 Session 恢复拒绝须保留,
不能用另建 Session 或启动成功证明原操作恢复。
上面的 turn plan 只读选定 Todo 的原生决策,不启动模型、写状态、ACK 调度或
扣额度。outer_controller 描述假定的 headless 调用方,并不安装或启动控制器;
ready 路由仍是计划,后续授权执行须遵守返回的 required reads、lease 和权限边界。
按试用所选 Host 和负载验收,不让无关 Host 不可用阻塞独立合格的 profile。
首个真实 Turn 完成及原操作重启读回仍须分别证明,不能从上述非模型检查推断。
从选定 Todo 的授权工作目录读取 plan。带仓库写入的 peer Todo 在独立 worktree
之外可以合法返回 agent_workspace_repair,并不表示安装或存储故障。切换到既有
授权 worktree 重读同一只读计划,不修改 Goal 路由或弱化 workspace guard。
进程成功或 ok=true 本身不表示路由 ready;读回 route.kind、route.effective_action、
envelope 义务和 effects。即使 ready_for_host,plan-only 的
host_invocation_allowed=false 也仍是预期结果。
退出先暂停自动 Turn,再分别停止 Host/writer、结算 active lease/capture;从当前 SQLite head 预览 File 迁移,审核冻结来源与备份,再明确应用同一 plan/digest。 预览不写;过期 active lease、active capture 或来源变化仍拒绝,不能手改状态绕过。 失响应重试原计划;不恢复试用前旧备份覆盖后来写入。完整 Todo 与来源读回通过后, 再经 owner 确认恢复原配额。历史 completed 不重新选择已被替代的 provider。 日常工作遵守本次原生 guard/read/refresh/spend,同 Turn 只结算一次;记录实际使用、 失败、恢复、资源和人工干预,不把加速循环当自然时间。数据、重复效果、身份、结算、 fence 或恢复错误停止受影响 lane;通过项目既有 issue/review 路径报告已脱敏的候选/ profile、步骤、原操作、预期/实际和新增写入保留情况。存储启用不授予 Host、lease 或外部访问权。正式默认与剩余退役仍按原 RFC 分开验收。
Reproduce validation¶
Use the qualified Node executable on PATH, including the Python CLI's managed Effect runtime. The runner records the actual Node/SQLite/source identity.
npm ci --ignore-scripts
npm run typecheck:control-plane
node --no-warnings --experimental-sqlite --experimental-strip-types --test \
tests/control_plane_ts/sqlite_authority_store.test.ts \
tests/control_plane_ts/sqlite_authority_bounded_profile.test.ts \
tests/control_plane_ts/sqlite_authority_migration.test.ts \
tests/control_plane_ts/authority_state_log.test.ts \
tests/control_plane_ts/authority_provider_parity.test.ts \
tests/control_plane_ts/local_authority_provider.test.ts \
tests/control_plane_ts/sqlite_runtime_admission.test.ts \
tests/control_plane_ts/sqlite_capacity.test.ts
python -m pytest -q tests/control_plane/test_sqlite_authority_cli.py
node -e "require('node:fs').mkdirSync('.local', {recursive:true})"
node --no-warnings --experimental-sqlite --experimental-strip-types \
examples/coordination/sqlite-capacity.ts --profile rehearsal --cli \
--output .local/sqlite-rehearsal.json
node --no-warnings --experimental-sqlite --experimental-strip-types \
examples/coordination/sqlite-capacity.ts --profile matched-64k --cli \
--output .local/sqlite-matched-64k.json
node --no-warnings --experimental-sqlite --experimental-strip-types \
examples/coordination/sqlite-capacity.ts --profile matched-1m --cli \
--output .local/sqlite-matched-1m.json
node --no-warnings --experimental-sqlite --experimental-strip-types \
examples/coordination/sqlite-capacity.ts --profile headroom-64k --cli \
--output .local/sqlite-headroom-64k.json
For an already promoted Goal, canonical lease renewal uses the selected provider's CAS and original receipt. It is a command-coverage slice, not D2 qualification or a provider-default change.
The no-argument default intentionally replaces the former 4 KiB/100k run with
a small rehearsal; full capacity now requires an explicit profile. The default
rehearsal creates 100/1,000 commits and checks runner execution,
independent invariants and cleanup; it cannot satisfy formal performance
budgets. The explicit matched-64k profile creates separate 10k/100k databases,
serially, with exactly 64 KiB native synthetic projection JSON and at most 4 KiB
of new event/receipt JSON per commit. Each fill write is followed by three head
reads and two indexed historical receipt reads. Both formal groups sample the
last 1,000 commits and their corresponding reads, plus 200 scan-100 samples.
This one-Todo storage axis isolates history growth; it is not the complete
multi-agent/lease/capture workload.
Two dedicated headroom profiles extend the same workload to RFC 7.2's other
axes with an unchanged budget matrix: matched-1m runs the identical 10k/100k
shape with a 1 MiB live projection (rows prefixed one_mib_), and
headroom-64k runs 100k/300k databases at 64 KiB (rows prefixed
headroom_300k_, cumulative-growth budgets evaluated against the 3x depth
ratio). The per-axis fill wall budget is an operational guard — floored at
2,400 s and scaled by commits and payload bytes beyond the 64 KiB 100k
workload — never a qualification budget; the p95 and growth budgets do not
move with it. A dedicated axis clears its coverage row only after its measured
payload and both exact commit depths match the selected profile. Other axes
remain explicit missing rows (payload_one_mib, headroom_300k, burst_60s).
--cli adds 20 formal samples (three in rehearsal) for complete CLI mutation,
status and quota, using fresh Python processes and a newly started managed
Effect runtime for each sample. Shutdown occurs outside the timed interval in
the isolated fixture. --python chooses the Python executable. These figures
include process startup but do not drop the OS file cache. Cold Node-only load
and warm actual-provider calls are separate. The provider's normal per-call
connection open/close remains inside warm timing. CLI mutations happen after
the fixed-history measurement; CLI, traffic-window and lock-probe commits all
extend past the fill target and are counted separately, so target-state rows
keep their meaning.
Status and quota CLI samples use the operational, scan-deferred entrypoints.
They no longer accept --scan-root. The capacity runner removes its scan-root
option and the input probe that only supported that retired argument. Its
cold_cli_scan stays null, so the existing ledger leaves the historical
scan-inclusive status budget missing; faster scan-free observations cannot
qualify that frozen workload. Todo mutation and independent store metrics keep
their existing criteria. Source/runtime, fixture, command mix and host
conditions still belong in every comparison. Historical reports and failures
are unchanged. Synthetic CLI processes disable usage telemetry and preserve
normal execution-authority checks. Check outgoing content separately with
loopx check.
Status 与 quota 的 CLI 样本走推迟扫描的运行入口,不再接受 --scan-root。
容量工具同步删除该参数及仅为它服务的输入探针,cold_cli_scan 保持 null,
因此既有账本继续将历史“包含扫描”的状态性能资格标为 missing,不能用更快的
无扫描观测通过冻结的旧工作负载。Todo 变更和存储指标的既有标准保持,比较仍需
报告源码、运行时、fixture、命令组合和宿主条件;历史报告及失败结果不变。
外发内容另用 loopx check 检查。
Reports carry p50/p95/p99 and counts, parent-process RSS, application request
JSON bytes and separate DB/WAL/SHM sizes at the target history. Resource-usage
peak RSS is process-lifetime across both groups; sampled axis RSS is separate,
and CLI child RSS is not measured. Application bytes, final files, SQLite
logical writes, cumulative WAL traffic and physical device writes are different
metrics and are never substituted for one another. Logical write volume is
measured from the filled database as the serialized bytes each commit hands to
SQLite (commits row plus the full-projection head rewrite plus amortized
checkpoint rows); page, index and compaction overhead belong to the other
columns. Cumulative WAL traffic is measured over one bounded commit window per
axis: read marks pinned by two observer connections make every WAL reset
impossible, so frame growth over the window is exact, and the per-commit
traffic at both depths carries the <=15x cumulative-growth budget. Lock wait is
app-observed: a probe process holds the write lock for a controlled interval
and the end-to-end store commit wait is reported against the uncontended
baseline. Whole-run WAL totals, pure busy-handler time and physical device
writes remain missing; a final WAL size of zero still proves no
cumulative-write bound.
Each axis reserves 5 GiB free space, caps its database at 16 GiB and checks a
2,400-second fill budget. All data are generated in a new temporary directory;
there is no flag to select an existing Goal/runtime for writes. Keep generated
reports in ignored local storage. Failure results survive in the report and
exit nonzero; omitted or incomplete evidence never becomes a pass. An exit
zero with status=incomplete means the requested measurements ran, not that
D2 qualified. Formal budget failures must remain visible without changing the
workload or thresholds to obtain a green report.
The real-process regressions exercise SIGKILL before and after business COMMIT,
lost-response receipt readback, exact head/event/receipt/scan equivalence and
SQLite max_page_count exhaustion. These are small disposable-database tests,
not power-loss, operating-system ENOSPC or large-history recovery qualification.
Retention deletion, restore-incarnation change and cross-host sharing are still
absent from this slice.
Version-1 migration¶
The shipped version-1 database keeps one full projection per retained row, so it
cannot be read by the version-2 provider. sqlite_authority_migration.ts
migrates one Goal database in place: it reads the frozen version-1 rows, proves
every stored commit digest, writes the checkpoint/delta log, proves that each
written delta reconstructs its projection, reads the not-yet-swapped tables back
and replays them through the store's own delta decoder, requires the commit
count to match, swaps tables and updates the schema version inside a single
BEGIN IMMEDIATE transaction. Any failure rolls back and leaves version 1
untouched; a second run reports already_current; a rewritten proof, a
mismatched goal/incarnation or an existing swap target fails closed. Cursors,
operation IDs, commit digests, provider revisions, receipts, events and scan
pages are byte-identical after the migration.
Retained projections keep every JSON object key the version-1 provider accepted,
including an empty key and a __proto__ key: a database the previous provider
could read must not become one the version-2 provider cannot. The replay proof
is what keeps that promise honest, because identical identity and digest columns
alone would not show that a migrated state log is unreadable.
A version-1 database that published only its schema and metadata — the state a goal leaves behind when it selected the provider and never committed — migrates to an equally empty version-2 database instead of failing, so the operator is never left with a database that neither provider accepts.
Run it from the repository checkout, with --execute omitted for a safe plan:
node --no-warnings --experimental-sqlite --experimental-strip-types \
examples/coordination/sqlite-authority-migration.ts \
--directory "$RUNTIME_ROOT/authority/sqlite-v0" --goal-id example
Add --execute (optionally with --expected-identity, which refuses a database
whose stored incarnation is not the one the operator names) to migrate. The
entry point rewrites only that Goal's database; it does not change provider
selection, promote a goal, or enable cross-host writes. Keep the pre-migration
database copy until the promoted Goal has been validated. A production cutover
command, migration manifest and reverse export remain separate deliverables.
Qualification holds / 资格保留项¶
The report's passed rows apply only to their named axis and sample counts.
failed measurements remain failed. The split storage-write rows —
logical_write_growth, wal_traffic_growth and lock_wait_observed, prefixed
one_mib_/headroom_300k_ in the dedicated headroom profiles — carry
the <=15x cumulative-growth budget as per-commit traffic measured at both
depths, and an invalidated window or missing probe is missing evidence, never a
pass from the surviving columns. missing rows still include whole-run WAL
totals, pure busy-handler time, physical device writes, steady-state RSS proof,
the full domain profile, the headroom axes a report did not itself run
(payload_one_mib, headroom_300k, burst_60s), 24-hour consumer lag,
large-history recovery, fenced backup/restore, supported upgrades/rollback,
OS/runtime coverage and a real >=10-day soak. Those holds still block profile
promotion. Accelerated volume never substitutes for elapsed time, and running
this command starts no soak.
Retained state is measured where the formal profile runs: an axis reports its
checkpoint count, replay budget, recovery tail and retained projection/delta
bytes, and bounded_retained_state compares them against one full copy per
retained commit. That row stays missing for a rehearsal, exactly like every
other formal budget.
SQLite 资格参考使用 Node 22.22.3/SQLite 3.51.3;打开前同时检查实际 WAL 修复版本 和 statement 关闭行为。公开 Node 最低版本已升至 22.22.3,同时适用于默认 File 和可选 SQLite;SQLite 仍检查实际内嵌驱动。显式 SQLite 选择遇到不合格 runtime 会拒绝,不会改默认 provider 或静默回退。
托管 Effect runtime 按用户与源码修订复用,因此真正服务某个 Goal 的 Node/SQLite
不一定等于调用进程从 PATH 解析到的那一份。runtime info 文件记录
runtime_identity(node_version、sqlite_version、sqlite_authority_qualified),
loopx doctor 会报告该身份;当正在服务的 runtime 不合格时,它还给出重启建议。
只安装合格 Node 不足以修复一个已经在运行的 runtime:用
结束后,下一次 control-plane 请求会按当前 PATH 启动新 runtime;等待其 idle 自动退出等效。非复用 runtime 的直接 CLI 调用只报告同一资格失败、不给重启步骤, 因为直接换成合格 PATH 重跑一次即可。
默认无参数命令从旧的 4 KiB/100k 改为小型 rehearsal,只验证工具和不变量;
正式 64 KiB、10k/100k 对照必须显式选择
matched-64k。--cli 分开记录完整 CLI 冷启动与 warm store,返回分位数、样本数、
RSS 和文件大小;没有量到的累计 WAL/逻辑写入和纯锁等待保持 missing。
应用 JSON 字节不能替代底层写入量,WAL 最终归零不能证明没有写入放大。
进程中断与 SQLite 容量注入在一次性合成数据库上运行,不等于断电、真实文件系统 耗尽、长期 consumer backlog 或完整恢复验证。首批测量允许保留 failed/missing;
=10 天自然时间 soak、迁移和晋升分别评审与授权。本入口不改变持久格式、Todo 语义、默认 provider 或任何活跃 Goal。
版本 2 把“每个提交都保留一份完整投影”改成“每个窗口一个检查点 + 每提交一条精确
delta”:活跃头读取只用自己的行、对应提交和游标连续性自证,历史读取最多重建一个
窗口,完整归档由 verifyAuthorityHistory 线性审计。版本 1 数据库需要显式迁移
(examples/coordination/sqlite-authority-migration.ts,默认只做 plan,--execute
才写入,--expected-identity 可拒绝并非操作者所指的 incarnation,失败保持 v1
原样)。只发布过 schema 与 metadata、从未提交的 v1 库会迁移成同样为空的 v2 库,
不会让操作者落在两个 provider 都不接受的状态。迁移不改 cursor、operation id、
commit digest、provider revision、receipt、event 或 scan 页面字节。迁移在提交前
还会把刚写入的表读回来、用 store 自己的 delta 解码器重放一遍:只核对搬过去的
标识与摘要无法证明新的状态日志可读。v1 能接受的 JSON key(包括空字符串和
__proto__)在 v2 中保持同样的数据语义,迁移不会把原本可读的库变成读不出来的
状态。