← All articles

LoopX 2.0 · Engineering

LoopX 2.0: Two Months of TypeScript Control-Plane Engineering

From an incremental migration proposal to complete transactions, shared authority, semantic checks, and retirement of old paths. How real callers and counterexamples changed the design.

LoopX 2.0 is out. This is an account of the history and technical details behind its TypeScript refactor.

The formal migration RFC merged on August 15. By the concentrated retirement of ordinary legacy writers in mid-October, the program had spanned roughly two months. Several tracks overlapped: transaction ownership, caller integration, shared authority, storage and history costs, Semantics, compatibility, and retirement.

The final relay by three developers and their agents in #6396 depended on that earlier work. Earlier choices determined what could be deleted and what still had to recover afterward.

Our new maintainer, Song (@songoow), has also been advancing the Semantics RFC, vocabulary convergence, production evidence, and checker fixes since mid-September. That track belongs alongside the transaction and storage story.

1. Reconstruct the history: the migration plan changed too

LoopX retains goals, tasks, evidence, and execution boundaries for long-running agents. Models propose routes. The control plane protects versions, execution authority, and state commits, then finds a valid recovery position after interruption.

Much of the original implementation was Python. TypeScript hosts, Pi integration, and the dashboard already existed. A Python call to make a decision, followed by a TS host consuming the result, was a natural starting point. As the system grew, defaults, state interpretation, ordering, and persistence could become distributed across both runtimes.

We wanted clear typed ownership of control-plane rules and contracts shared by the CLI, hosts, and App. The history developed through overlapping stages.

Period Main change Representative evidence
August 15 Formal proposal for incremental migration over a process boundary; initially favors reads and projections #3226
August 21–22 Effect Program cutover, followed by a pivot from leaf rules to complete transactions #3416, #3447
Late August–early September Settlement, Todo, quota, lease, Vision, and monitor transactions migrate; shared authority develops in parallel #3464, #3798
Early–mid September Integrate real callers, converge planning/read/provider-open rules, add optional SQLite #4178, #4317, #4121
From mid-September Song advances Semantics; storage work tackles checkpoints, deltas, writes, and replay cost #4433, #4408
Late September Retire an unused Python authority prototype and experimental Todo event overlay, preserving real compatibility boundaries #5105, #5054
Early–mid October Update new-Goal defaults, isolate ordinary old writers, converge production consumer routes #5805, #6104, #6280
Mid-October Concentrated retirement, followed by recovery, producer, and integration fixes #6396
Migration history: overlapping transaction, authority/storage, and Semantics tracks
Migration history: overlapping transaction, authority/storage, and Semantics tracks Scroll on narrow screens; open the image for full size.

The original August proposal considered a read-first sequence and staged dual reads and writes. The accepted approach evolved toward Effect Programs first, then replacing complete transactions and deleting the old rules, with one production semantic owner. An early proposal's staging ideas should not be presented as the runtime architecture of 2.0.

Plans can change usefully. The important record is why the route changed, which slices cut over, and which remained references or candidates. The maintained TS RFC retains those checkpoints.

2. Core control-plane statistics across the full period

The reproducible scope here is loopx/control_plane/**. App, benchmark, documentation, and test-suite directories outside it do not enter the core code counts. Internal testing/support tools inside this directory remain included.

The baseline is d1fe059, immediately before the formal TS RFC. The endpoint is the October 16 snapshot 488e443. The formal RFC to that endpoint spans roughly 62 days, covering 63 calendar dates; 62 of those dates contain a core mainline commit. The source comparison fixes both versions.

Engineering activity over the whole period

Metric Core-directory cohort
Mainline landing commits touching the directory, first-parent chain 1,144
Verifiably associated merged PRs, deduplicated by PR number 1,121
PR-author accounts in that cohort 56
Review records in the API snapshot 2,329
Conversation comments in the API snapshot, excluding inline review comments 1,631
Associated PRs with review records 1,031 / 1,121
Median PR creation-to-merge elapsed time 5.0 hours
P90 / P95 creation-to-merge elapsed time 25.4 / 45.0 hours

The scope includes migration, new capabilities, fixes, and maintenance during the period. The 56 accounts include automation and shared accounts; they do not establish 56 people or agents. Review totals can include self-review, repeated reviews, and post-merge discussion. Merge elapsed time includes waiting and is not active engineering time.

New maintainer Song is associated with 53 PRs in this narrow directory cohort. His work in loopx/semantics/ and other directories is not fully counted here. Concrete attribution still follows the contribution explanations and original PRs.

Source structure and cumulative churn

Physical line counts include comments and blank lines. File counts below include source files in the named language.

Core-directory source Baseline Evidence snapshot Cumulative additions / deletions
Python 256 files; 80,473 lines 471 files; 142,371 lines +120,899 / −59,001 lines
TypeScript 0 files; 0 lines 323 files; 87,268 lines +104,769 / −17,501 lines
Python + TypeScript 80,473 lines 229,639 lines +225,668 / −76,502 lines

TS represents 38.0% of the endpoint's Python-plus-TS physical lines. That describes the directory's language structure. It does not establish that 38% of migration is complete: some old entrypoints were outside this directory, and the period includes code moving in, new capabilities, and testing support.

Python and TypeScript source stocks in the core control-plane directory
Python and TypeScript source stocks in the core control-plane directory Scroll on narrow screens; open the image for full size.

Cumulative churn sums each mainline landing diff, so repeatedly editing a line counts repeatedly. Endpoint stocks describe only the two snapshots. The cumulative net, endpoint diff, and source-stock change were reconciled by language. The roughly 302,000 line edits record engineering activity; they do not measure unique output, quality, or model productivity.

How the work continued over time

Beijing dates, weekly buckets Core mainline commits Associated merged PRs PR-author accounts that week
08-15—08-21 52 48 10
08-22—08-28 87 86 14
08-29—09-04 61 61 16
09-05—09-11 92 92 14
09-12—09-18 133 133 14
09-19—09-25 150 147 10
09-26—10-02 161 161 17
10-03—10-09 216 214 12
10-10—10-16 192 179 14
Weekly associated merged PRs touching the core directory throughout the refactor period
Weekly associated merged PRs touching the core directory throughout the refactor period Scroll on narrow screens; open the image for full size.

The first and last weeks cover only time inside the selected endpoints. PRs use merge time; mainline commits use commit time. These can differ, so the two columns need not correspond one-for-one within a week.

By title prefix, the cohort contains 658 fix, 208 feat, 137 refactor, and 28 perf PRs. The rest include validation, documentation, and titles without those prefixes. These are title categories, not a partition of pure migration effort.

PR title categories in the same core-directory cohort
PR title categories in the same core-directory cohort Scroll on narrow screens; open the image for full size.

These numbers support explaining the mechanisms by stage. Moving a rule was followed by caller integration, semantic fixes, recovery validation, and retirement. The final #6396 is one concentrated slice within that process.

Reproduce the inventory

Public-safe PR records, core mainline commits, and metric JSON accompany the draft. The PR cohort joins mainline commits touching the directory to GitHub merge metadata, counting each PR once. Two mainline commits have no verifiable merged-PR association; their commits and churn remain included.

The research enumerated public PRs, then checked against mainline Git history. Direct reads supplemented 14 PR metadata records missed by search indexing. Source snapshots and processing scripts remain in the research materials. This inventory does not estimate tokens, model cost, CI compute, engineer-hours, or benchmark gains.

3. Start with the Effect Program: agree on what happened

The first bounded cutover was #3416 on August 21. TypeScript took ownership of the Effect Program, settlement algebra, Turn-journal interpretation, and a durable checkpoint effect.

These boundaries fit together because recovery asks one connected question: which effects belong to this operation, which have committed, and what may happen next?

A checkpoint involves more than writing JSON to a file. Operation identity, same-key exclusion, matching the previous content, atomic replacement, and durability ordering determine whether a retry can recognize the original result. Splitting the rules across runtimes makes the next recovery fix harder to locate.

The cutover therefore moved internal effects and their rules into TS. Python retained compatibility calls and callback/provider boundaries. A managed local runtime starts on demand and exits when idle, with a private token and source fingerprint for lifecycle handling. Callers do not need to manage a separate daemon manually.

The design also had a cost. The PR reported these measurements for the same historical checkpoint workload:

Path p50 p95
Previous checkpoint 0.755 ms 2.020 ms
New durable checkpoint path 12.597 ms 18.610 ms

The new path added RPC and durability work. This records the cost of that first slice. It does not isolate language performance or describe today's complete CLI latency. Acknowledging the cost made it possible to assess whether later complete transactions removed more round trips and repeated interpretation.

4. Migrate a complete transaction: two calls can be right

On August 22, #3447 explicitly stopped flat leaf-helper migration. Adding a facade, DTO, and parity scaffolding for each function could make the production path longer while Python still owned composition.

The new unit was an observable transaction. Turn settlement connects decoding, identity, the journal, pending effects, reduction, and closeout. Todo completion similarly connects admission, state, leases, continuation, and a commit receipt.

The responsibility boundary between a complete transaction and named external providers
The responsibility boundary between a complete transaction and named external providers Scroll on narrow screens; open the image for full size.

#3464 replaced seven leaf runtime calls with the coarse turn.settlement.reduce contract. In that delivered slice:

New provider work: TS preflight → Python provider / checkpoint → TS final reduction
Existing replay:   TS reduction / original-result recovery

Two calls and one call describe new provider work and replay respectively. The responsibility diagram does not count every call in the complete CLI.

Review caught an attractive but unsafe optimization: retaining only the final reduction. That saved a call, but malformed identity or journal state could be rejected only after an external provider had executed. The final design kept preflight. TS authorizes the exact pending effects; Python executes and checkpoints those effects before final reduction.

A correct check in the wrong position can fail to protect the user. Its order relative to an effect matters.

Later lease, Vision, monitor-poll, spend, and void slices followed the complete-transaction approach. Their migration economics must name the old rules removed, bridges added, happy and recovery crossings, scaffolding exit conditions, and correctness and latency through the full entrypoint. A net increase in code can be justified, but the extra code needs a purpose.

5. Shared authority: separate domain rules from storage primitives

In parallel, shared-authority work progressed from wchwawa's coordination core, aggregate/provider foundations, and recoverable references to the TS AuthorityStore in #3798.

Two responsibilities need distinct owners:

  1. The domain owner decides whether a transition is legal and who may execute it.
  2. The store provides conditional commit, durability, and proof readback.

The current AuthorityStore exposes operations including storeIdentity, loadAuthority, commitAuthority, readReceipt, and scanCommitted. The store atomically protects the head, events, and receipts. A database adapter must not quietly become an independent owner of domain transitions.

Several identities protect different boundaries:

Identity Boundary it protects
provider_revision A conditional provider commit; its representation can be opaque to domain callers
authority_revision A version of domain authority state
lease_epoch The generation of an execution grant, fencing an old executor
Store identity / lineage Whether a read still belongs to the same authority history
Operation identity / receipt Whether this is the same committed operation and what it originally returned

For example, a PostgreSQL connection may fail after COMMIT. The write may already be durable. A provider must distinguish missing, unavailable, failed, and ambiguous durability. A missing success response does not establish a missing commit. Reading the original operation's receipt is part of recovery. The PostgreSQL candidate also retained its separate deployment and adoption boundaries.

An earlier Python executor served reference and coverage purposes. The unused prototype was later deleted, while actual native File and SQLite paths became the corresponding qualification targets. Transitional references need an exit condition; they must not become a second production authority by accident.

6. After SQLite: retained history needs a cost model

LIHUA919's #4121 connected an optional SQLite provider to the existing TS authority contract. A transaction atomically commits the head, receipts, events, and outbox. Domain claim and lease semantics stay with their owner.

Adding the database left three distinct engineering problems.

First, reading the current head should not scale with total retained history. #4408 replaced a full retained projection per transaction with verified periodic checkpoints and committed deltas. It converged codec ownership and added transactional, idempotent migration of the old format. Current reads, continuity recovery, and a full audit have different read scopes.

Second, measure writes correctly. #4778 separately observed logical write volume, cumulative WAL traffic within a window, and application-observed lock wait. Final database size cannot substitute for write-amplification measurement. A rehearsal also does not establish formal capacity or sustained-operation acceptance.

Third, object ownership during replay affects correctness. #4931 reduced copying, sorting, and encoding of unchanged subtrees, and stopped materializing unused full projections for receipt lookup. It also prevented a caller's mutation of a returned projection from poisoning the next replay basis. Persisted proof bytes remained unchanged.

Some paths improved substantially. Large-state latency, scan cost, and memory still had gaps, and the PR retained the unmet targets. Those distinctions are more useful than a single performance-improvement label for every path.

In October, #5805 changed unconfigured new-Goal defaults to canonical SQLite and hard leases. Existing Goals, explicit choices, and old operation retries retained their own contracts. A new default, migration of existing data, and retirement of old writers happened at different stages.

7. Song's Semantics: connect definition, production, and interpretation

Our new maintainer, Song (@songoow), initiated and implemented the Semantics RFC, registry, and commit-path checks in #4433, merged on September 15. He continued advancing convergence afterward. This track governs how concepts grow as the software changes.

Cross-language migration readily creates duplicate knowledge. The same state can appear in a Python Enum, Literal, TS as const, a presentation layer, and a conditional branch. Even if the strings match today, one location may gain a value or change its interpretation tomorrow.

The Semantics RFC checks traceable relationships: definition owner, value set, generated binding, producer, and interpreter or projection. Runtime rules remain with typed owners. The registry constrains and checks them without becoming another production rule source.

Four kinds of information in one field

#4494 addressed a mixed effective_action field containing quota decisions, frontier actions, replay verdicts, and diagnostic error codes. Callers could not clearly identify the domain to interpret.

The converged contract puts verdicts in decision, diagnostics in error_code, and restricts the root action to the registered, disjoint union of quota and frontier domains. Cross-runtime bindings are generated from an owner. Old v0 signed plans retain their historical meaning and representation, rather than being rewritten in a way that breaks original identity verification.

The same work caught skip versus quota_skip. A consumer branch is not evidence that a producer emits the value. Checks must trace actual assignments and returns at supported sites, rather than counting string comparisons as production.

Counterexamples for the checker itself

#4687 retained useful review counterexamples and fixed three confidently wrong answers: missed global rebinding, mutation after dictionary spread treated as the old initializer, and a shadowed String mistaken for the built-in conversion.

False alarms and missed values have different consequences. A conservative candidate set may report an extra problem. Missing a real produced value can let an unregistered output through. Unsupported dynamic forms must preserve an unknown.

The checks also need scan-coverage floors, so scanning fewer files cannot make the result green. Retiring an expanding committed inventory in favor of on-demand full-tree computation did not remove that obligation. Gaps outside supported forms remain explicit, and the tracking issue remains part of the work.

Generation and checking reduce synchronization mistakes. Their assumptions, coverage, and error direction still require review.

8. Callers and defaults determine whether migration reaches users

Complete transactions need real producers. In September, #4178 converged nonterminal planning updates around one authority snapshot and a TS transaction. It removed a Python route that synthesized Markdown, parsed it again, and performed target reads before the transaction.

Omission, null, an empty value, and false each carry intent here. Dry-run must model that intent accurately. An unavailable selected provider must not trigger fallback elsewhere. #4279 later converged read classification and projection-delivery semantics. Some Python read ownership was deliberately retained. Clear ownership matters more than creating a second interpretation to pursue language purity.

Defaults expose similar distinctions. Current priority regressions protect these cases:

Input or operation Expected behavior
Create a new task, omit priority Apply the new-task P1 default
Read an old task with no historical priority Preserve its historical meaning
Replay the original creation operation Return the original result
Edit only the text Preserve the existing priority
Explicitly clear priority Clear it
Structured declaration conflicts with an old text prefix Refuse with actionable context

The File and SQLite regressions protect these cases. Applying new defaults after original-receipt recovery prevents an upgrade from making new decisions for history.

The closing review found another caller gap. The TS core passed direct tests for metadata-only completion repair. The normal Python producer automatically attached defaults such as completion policy and auto-acquire. The core interpreted them as fresh work mixed into a historical repair. The actual File and SQLite entrypoints failed in both dry-run and execution. The public counterexample records this.

That recovery gap also existed in the baseline, so it cannot all be attributed to the final contributor. The repair had to reach the normal producer while continuing to reject real changes to text, continuation, ownership, or validation. A correctly constructed typed request proves only that the inner boundary can handle it.

9. Recover the original result while protecting current authority

Recovery can confuse two points in time: whether the original operation was valid, and whether today's executor may continue changing state.

Suppose a team-plan committed but its response was lost. After an upgrade, the caller retries the same operation ID under stricter admission rules. Rejecting it as fresh work first can hide the original success. A closing recovery review caught that problem.

Separating original-receipt recovery from current execution authority
Separating original-receipt recovery from current execution authority Scroll on narrow screens; open the image for full size.

The current path decodes and locates the correct authority, then checks original identity, intent, and receipt. If it is the same committed operation, it recovers the original result. Work without a matching original result proceeds through current admission. The selected provider must still be available and its proof valid; failure cannot quietly redirect writes to Markdown.

Returning the original result does not renew a lease, overwrite later commits, or revive a retired writer. Lease transfers, epoch changes, and subsequent state have their own protections.

Types express the distinction too. Historical decoding still recognizes legacy; current fresh-execution handoff configuration accepts soft_claim and hard_lease. Reading an old record, recovering an original operation, and authorizing current execution each have independent conditions.

An internal receipt also has a limited scope. If a remote external effect lacks a verifiable idempotent identity and readback, settling once in the control plane cannot establish that the remote effect executed exactly once. Uncertainty belongs at the boundary where it actually occurs.

10. CLI output budgets: protect decision semantics before compression

CLI language participates in an agent's decisions. This engineering constraint predates the final writer retirement and remained a validation boundary during migration.

The interface budget contract examines:

  • Actually printed stdout, alongside the in-memory payload.
  • Distinct character, UTF-8 byte, line, and JSON-structure costs.
  • Base and head on the same fixture, plus growth as Todos and history accumulate.
  • Required fields, action signatures, semantic anchors, and explicit detail paths.

Presentation reduction belongs after complete planning. Information omitted from display differs from information never read. Projection envelopes likewise separate observation time, service time, source coverage, and completeness. Serving a cached result again must not make its facts newer.

Natural language requires clause-level review: obligations, ordering, evidence provenance, authority qualifiers, scope, continuation, and stop conditions. Removing a phrase such as “only when the original proof is valid” can change behavior even when every field remains.

One bounded example removed JSON indentation and separator whitespace. On the same historical fixture of 36 Todos and 12 runs, output fell from 48,418 to 39,726 characters, about 18%, with the complete decoded payload unchanged. This establishes a presentation-size change. Tokens, cost, and agent outcomes need separate measurements.

The budget protects an interface the consumer can use. Valuable guidance may justify increasing it with matched evidence. Dropping decision conditions to make a size test green damages the interface.

11. Final retirement: preserve credit and account for the deletions

Late-September work had already removed an unused authority prototype and the experimental Todo event overlay, dual-writer, and backfill. Old sources containing data received explicit preservation or refusal handling. This did not remove all event logging or the new authority's atomic events and receipts.

October work further isolated ordinary Markdown writers and validated real subsequent writes without the old modules. Concentrated deletion became possible after consumer routes and replacement paths were connected.

Duang777 led #6396. Song and maintainer-side agents continued recovery, producer, and integration work along the original branch, preserving contribution history. This three-developer-and-agent relay was the closing phase of the two-month program.

The final merge against its first parent gives the following diff, for this PR alone, including implementation and tests:

File group Added Deleted Net change
Python 10,938 25,235 −14,297
TypeScript 2,547 5,116 −2,569
Other 1,944 3,013 −1,069
Total 15,429 33,364 −17,935

Many removed lines were old tests. Deleted test lines do not establish product capability; the important question is whether current entrypoints and independent counterexamples still protect the intended invariants. These counts describe the closing scope. They do not validate the whole migration.

12. Community contributions and the next step

Multiple contributors advanced this program. Representative work is listed by its concrete boundary so readers can follow the history.

These are representative contributions along the relevant tracks. Later Semantics convergence and the changed-diff advisory also include other community work; they should not all be attributed to one person.

The acceptance questions I care about are practical: can a rule change be located in a clear owner? Can a real caller express the intended operation? Do checks precede effects? Does recovery preserve the original result and subsequent state? Have replaced rules left production? Can costs be measured at the real entrypoint?

2.0 does not close every RFC goal. Retained Python transports and specialized providers have specific purposes. Broader native CLI coverage and sustained-operation qualification continue along their own validation paths. We also want capability and efficiency comparisons on harder general long-horizon benchmarks such as EdgeBench and LHTB, especially in a single session.

I hope this refactor leaves a system that is easier to change, validate, and revert—and a working practice that keeps responding to counterexamples.