orbit-agent-runtime 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CHANGELOG.md +750 -0
  2. package/LICENSE +201 -0
  3. package/README.md +492 -0
  4. package/README.zh-CN.md +389 -0
  5. package/benchmarks/README.md +24 -0
  6. package/benchmarks/gateway.mjs +68 -0
  7. package/benchmarks/pae.mjs +90 -0
  8. package/benchmarks/replay.mjs +51 -0
  9. package/benchmarks/run-all.mjs +24 -0
  10. package/benchmarks/wal.mjs +50 -0
  11. package/bin/orbit.mjs +523 -0
  12. package/dist/.tsbuildinfo +1 -0
  13. package/dist/demo-deepseek.d.ts +2 -0
  14. package/dist/demo-deepseek.d.ts.map +1 -0
  15. package/dist/demo-deepseek.js +66 -0
  16. package/dist/demo-deepseek.js.map +1 -0
  17. package/dist/demo-host.d.ts +2 -0
  18. package/dist/demo-host.d.ts.map +1 -0
  19. package/dist/demo-host.js +98 -0
  20. package/dist/demo-host.js.map +1 -0
  21. package/dist/demo-replay.d.ts +2 -0
  22. package/dist/demo-replay.d.ts.map +1 -0
  23. package/dist/demo-replay.js +68 -0
  24. package/dist/demo-replay.js.map +1 -0
  25. package/dist/src/core/orbitRuntimeHost.d.ts +349 -0
  26. package/dist/src/core/orbitRuntimeHost.d.ts.map +1 -0
  27. package/dist/src/core/orbitRuntimeHost.js +706 -0
  28. package/dist/src/core/orbitRuntimeHost.js.map +1 -0
  29. package/dist/src/index.d.ts +92 -0
  30. package/dist/src/index.d.ts.map +1 -0
  31. package/dist/src/index.js +226 -0
  32. package/dist/src/index.js.map +1 -0
  33. package/dist/test/audit_chain.test.d.ts +2 -0
  34. package/dist/test/audit_chain.test.d.ts.map +1 -0
  35. package/dist/test/audit_chain.test.js +283 -0
  36. package/dist/test/audit_chain.test.js.map +1 -0
  37. package/dist/test/channel_hub.test.d.ts +2 -0
  38. package/dist/test/channel_hub.test.d.ts.map +1 -0
  39. package/dist/test/channel_hub.test.js +66 -0
  40. package/dist/test/channel_hub.test.js.map +1 -0
  41. package/dist/test/cordis_adapter.test.d.ts +2 -0
  42. package/dist/test/cordis_adapter.test.d.ts.map +1 -0
  43. package/dist/test/cordis_adapter.test.js +280 -0
  44. package/dist/test/cordis_adapter.test.js.map +1 -0
  45. package/dist/test/cost_routing.test.d.ts +2 -0
  46. package/dist/test/cost_routing.test.d.ts.map +1 -0
  47. package/dist/test/cost_routing.test.js +31 -0
  48. package/dist/test/cost_routing.test.js.map +1 -0
  49. package/dist/test/domain_transaction.test.d.ts +2 -0
  50. package/dist/test/domain_transaction.test.d.ts.map +1 -0
  51. package/dist/test/domain_transaction.test.js +351 -0
  52. package/dist/test/domain_transaction.test.js.map +1 -0
  53. package/dist/test/file_channel.test.d.ts +2 -0
  54. package/dist/test/file_channel.test.d.ts.map +1 -0
  55. package/dist/test/file_channel.test.js +179 -0
  56. package/dist/test/file_channel.test.js.map +1 -0
  57. package/dist/test/gateway.test.d.ts +2 -0
  58. package/dist/test/gateway.test.d.ts.map +1 -0
  59. package/dist/test/gateway.test.js +548 -0
  60. package/dist/test/gateway.test.js.map +1 -0
  61. package/dist/test/governance_profile.test.d.ts +2 -0
  62. package/dist/test/governance_profile.test.d.ts.map +1 -0
  63. package/dist/test/governance_profile.test.js +214 -0
  64. package/dist/test/governance_profile.test.js.map +1 -0
  65. package/dist/test/governance_schema.test.d.ts +2 -0
  66. package/dist/test/governance_schema.test.d.ts.map +1 -0
  67. package/dist/test/governance_schema.test.js +200 -0
  68. package/dist/test/governance_schema.test.js.map +1 -0
  69. package/dist/test/host_integration.test.d.ts +2 -0
  70. package/dist/test/host_integration.test.d.ts.map +1 -0
  71. package/dist/test/host_integration.test.js +104 -0
  72. package/dist/test/host_integration.test.js.map +1 -0
  73. package/dist/test/host_journal_persistence.test.d.ts +2 -0
  74. package/dist/test/host_journal_persistence.test.d.ts.map +1 -0
  75. package/dist/test/host_journal_persistence.test.js +367 -0
  76. package/dist/test/host_journal_persistence.test.js.map +1 -0
  77. package/dist/test/impact_domain.test.d.ts +2 -0
  78. package/dist/test/impact_domain.test.d.ts.map +1 -0
  79. package/dist/test/impact_domain.test.js +48 -0
  80. package/dist/test/impact_domain.test.js.map +1 -0
  81. package/dist/test/injected_clock.test.d.ts +13 -0
  82. package/dist/test/injected_clock.test.d.ts.map +1 -0
  83. package/dist/test/injected_clock.test.js +200 -0
  84. package/dist/test/injected_clock.test.js.map +1 -0
  85. package/dist/test/isolation_domain.test.d.ts +2 -0
  86. package/dist/test/isolation_domain.test.d.ts.map +1 -0
  87. package/dist/test/isolation_domain.test.js +302 -0
  88. package/dist/test/isolation_domain.test.js.map +1 -0
  89. package/dist/test/journal_wal.test.d.ts +2 -0
  90. package/dist/test/journal_wal.test.d.ts.map +1 -0
  91. package/dist/test/journal_wal.test.js +622 -0
  92. package/dist/test/journal_wal.test.js.map +1 -0
  93. package/dist/test/mcp_adapter.test.d.ts +2 -0
  94. package/dist/test/mcp_adapter.test.d.ts.map +1 -0
  95. package/dist/test/mcp_adapter.test.js +440 -0
  96. package/dist/test/mcp_adapter.test.js.map +1 -0
  97. package/dist/test/openai_compat_channel.test.d.ts +2 -0
  98. package/dist/test/openai_compat_channel.test.d.ts.map +1 -0
  99. package/dist/test/openai_compat_channel.test.js +325 -0
  100. package/dist/test/openai_compat_channel.test.js.map +1 -0
  101. package/dist/test/openapi_adapter.test.d.ts +2 -0
  102. package/dist/test/openapi_adapter.test.d.ts.map +1 -0
  103. package/dist/test/openapi_adapter.test.js +270 -0
  104. package/dist/test/openapi_adapter.test.js.map +1 -0
  105. package/dist/test/orbit_cli.test.d.ts +2 -0
  106. package/dist/test/orbit_cli.test.d.ts.map +1 -0
  107. package/dist/test/orbit_cli.test.js +137 -0
  108. package/dist/test/orbit_cli.test.js.map +1 -0
  109. package/dist/test/pact_verifier.test.d.ts +2 -0
  110. package/dist/test/pact_verifier.test.d.ts.map +1 -0
  111. package/dist/test/pact_verifier.test.js +41 -0
  112. package/dist/test/pact_verifier.test.js.map +1 -0
  113. package/dist/test/pae_adapter.test.d.ts +2 -0
  114. package/dist/test/pae_adapter.test.d.ts.map +1 -0
  115. package/dist/test/pae_adapter.test.js +469 -0
  116. package/dist/test/pae_adapter.test.js.map +1 -0
  117. package/dist/test/plugin_sandbox_guard.test.d.ts +2 -0
  118. package/dist/test/plugin_sandbox_guard.test.d.ts.map +1 -0
  119. package/dist/test/plugin_sandbox_guard.test.js +57 -0
  120. package/dist/test/plugin_sandbox_guard.test.js.map +1 -0
  121. package/dist/test/rate_limit_behavior.test.d.ts +2 -0
  122. package/dist/test/rate_limit_behavior.test.d.ts.map +1 -0
  123. package/dist/test/rate_limit_behavior.test.js +85 -0
  124. package/dist/test/rate_limit_behavior.test.js.map +1 -0
  125. package/dist/test/replay_compat.test.d.ts +2 -0
  126. package/dist/test/replay_compat.test.d.ts.map +1 -0
  127. package/dist/test/replay_compat.test.js +1122 -0
  128. package/dist/test/replay_compat.test.js.map +1 -0
  129. package/dist/test/replay_engine.test.d.ts +2 -0
  130. package/dist/test/replay_engine.test.d.ts.map +1 -0
  131. package/dist/test/replay_engine.test.js +85 -0
  132. package/dist/test/replay_engine.test.js.map +1 -0
  133. package/dist/test/shell_channel.test.d.ts +2 -0
  134. package/dist/test/shell_channel.test.d.ts.map +1 -0
  135. package/dist/test/shell_channel.test.js +173 -0
  136. package/dist/test/shell_channel.test.js.map +1 -0
  137. package/dist/test/teardown_isolation.test.d.ts +10 -0
  138. package/dist/test/teardown_isolation.test.d.ts.map +1 -0
  139. package/dist/test/teardown_isolation.test.js +279 -0
  140. package/dist/test/teardown_isolation.test.js.map +1 -0
  141. package/dist/test/token_budget.test.d.ts +2 -0
  142. package/dist/test/token_budget.test.d.ts.map +1 -0
  143. package/dist/test/token_budget.test.js +113 -0
  144. package/dist/test/token_budget.test.js.map +1 -0
  145. package/dist/test/trace_journal.test.d.ts +2 -0
  146. package/dist/test/trace_journal.test.d.ts.map +1 -0
  147. package/dist/test/trace_journal.test.js +43 -0
  148. package/dist/test/trace_journal.test.js.map +1 -0
  149. package/dist/test/trace_journal_persistence.test.d.ts +2 -0
  150. package/dist/test/trace_journal_persistence.test.d.ts.map +1 -0
  151. package/dist/test/trace_journal_persistence.test.js +175 -0
  152. package/dist/test/trace_journal_persistence.test.js.map +1 -0
  153. package/dist/test/trace_persistence.test.d.ts +2 -0
  154. package/dist/test/trace_persistence.test.d.ts.map +1 -0
  155. package/dist/test/trace_persistence.test.js +216 -0
  156. package/dist/test/trace_persistence.test.js.map +1 -0
  157. package/dist/test/trip_protector.test.d.ts +2 -0
  158. package/dist/test/trip_protector.test.d.ts.map +1 -0
  159. package/dist/test/trip_protector.test.js +42 -0
  160. package/dist/test/trip_protector.test.js.map +1 -0
  161. package/examples/README.md +34 -0
  162. package/examples/cli-record-replay.mjs +74 -0
  163. package/examples/custom-channel.mjs +102 -0
  164. package/examples/js-pae-plugin.mjs +128 -0
  165. package/examples/mcp-adapter.mjs +102 -0
  166. package/package.json +60 -0
  167. package/src/core/orbitRuntimeHost.ts +853 -0
  168. package/src/index.ts +256 -0
package/README.md ADDED
@@ -0,0 +1,492 @@
1
+ # Orbit Agent Runtime
2
+
3
+ > **Deterministic · Provable · Governable**
4
+ > A plugin-based agent runtime kernel with hot plugin registration, provable fault isolation, full-chain traceability and sandboxed execution.
5
+
6
+ **English** · [简体中文](./README.zh-CN.md)
7
+
8
+ **Project status:** `pre-alpha` · **License:** Apache-2.0 · **Track:** open-source product (see [roadmap](#roadmap))
9
+
10
+ > Orbit is developed as an **open-source product**, not a lab experiment. The kernel is
11
+ > production-track engineering: strict TypeScript, zero runtime dependencies, every
12
+ > mechanism backed by tests and an architecture charter
13
+ > ([docs/VISION.md](./docs/VISION.md)). Current phase: kernel mechanisms complete
14
+ > (M1–M4) → product hardening (real model adapters, persistence, CLI, npm publish).
15
+ > Contributions follow [CONTRIBUTING.md](./CONTRIBUTING.md).
16
+
17
+ Orbit Agent Runtime is a lightweight, dependency-free runtime host for plugin-based AI agents. All external capabilities (model access, storage, IO) are abstracted into **capability channels**; agents never call capabilities directly — everything goes through the channel hub. The kernel is layered with strict one-way dependencies, and every component is private with read-only copies exposed.
18
+
19
+ ## Highlights
20
+
21
+ - **Channel-first decoupling** — all capabilities (memory KV, LLM) are channels; plugins can override or extend them at runtime
22
+ - **Plugin pact validation** — mandatory field completeness, host-edition compatibility, capability declarations
23
+ - **Trip protection** — per-plugin fault state machine (NORMAL → TRIPPED → PROBE), a plugin failure never takes down the host
24
+ - **Trace journal** — full-chain records with snapshot & replay for audit and debugging
25
+ - **Sandbox pool** — per-agent sandbox with cycle limits (anti-infinite-loop) and per-round trace IDs
26
+ - **Deterministic replay (M2)** — record a run, replay it with zero model calls, byte-identical output, bank-style reconciliation (digest-chain verified)
27
+ - **`orbit` CLI (M6)** — `record` / `replay` / `diff` over the kernel; reproducibility in three commands, zero extra dependencies
28
+ - **Provable isolation (M3)** — plugin/channel/sandbox dependencies as a graph; failure impact = reverse reachability closure, with an isolation theorem
29
+ - **Cost-aware routing (M4)** — channels declare cost/latency/quality; agents run under per-cycle budgets
30
+ - **Unified gateway (W7)** — `capabilityInvoke` is the determinism boundary: every call's governance decision (trip / pact / budget / rate-limit / route / compression) is recorded and restored on replay, and config drift is reported distinctly from digest drift
31
+ - **Token budget + compression (W8)** — `TokenBudgetEngine` is a pure-function (no `Math.random`/`Date.now`) token estimator and deterministic head-trim compressor; budget/route decisions are now computed from the engine and channel registry, and its threshold config is hashed into the run fingerprint for drift detection
32
+ - **Payload-aware storage compression (W9)** — large recorded outputs are transparently `deflate`-compressed at rest via `packSnapshot` (zero external deps), while the consumer always receives the original value and replay reproduces it byte-for-byte; the `compression` decision (`level` / `applied` / `bytesSaved`) is recorded for audit
33
+ - **Rate limiting + behavior collector (W11)** — `RateLimiter` is a pure-function (no `Math.random`/`Date.now`) call-count budget; the `rateLimited` decision is recorded and replayed verbatim (the limiter is bypassed on replay). `BehaviorCollector` captures a structured `BehaviorNote` in three modes — `record` (persisted on the trace), `live` (proposal, not persisted), `replay` (bypass)
34
+ - **Three-way drift classification (W13)** — replay failures are reported as distinct errors: config drift (`RunFingerprintDriftError`, version/fingerprint), decision drift (`DecisionDriftError`, e.g. a revoked pact), and call drift (`ReplayDriftError`, data/signature). Reconciliation also reports `decisionDriftFields`
35
+ - **`replay_compat` determinism gate (W12)** — a 27-case CI gate proves the gateway boundary stays faithful under compression / rate-limit / collector / fingerprint-drift / decision-drift / PAE adapters / durable WAL windows: every decision is recorded and replayed byte-identically
36
+ - **Plugin Adaptation Engine (W15)** — foreign runtimes (in-process JS, and later MCP / OpenAPI / Cordis) are mapped onto the kernel capability contract through adapters that surface as a single capability channel; every foreign call is a gateway transaction, recorded and replayed byte-identically. Fidelity is negotiated honestly (`full | reduced | lossy`), and the adapter surface is hashed into the run fingerprint for drift detection
37
+ - **Isolation domains (W19)** — the impact graph allocates the physical layer: a unit whose failure closure exceeds the threshold gets its own L2 child process (`iso:<unit>`), the rest share deterministic chunks (`shared:<n>`). The sync is a diff, not a rebuild, and domains are published as one capability channel, so a domain call is recorded and replayed byte-identically
38
+ - **Cross-domain transactions (W20)** — every hop between domains is an atomic gateway transaction: `decision (assignment / isolation) + execution + result + audit`, settled in a ledger that reconciles by (source → target) pair. Orphans (a hop that crossed a boundary and never settled) and refusals are both detectable from the records alone; replay injects the frozen output without re-entering the domain
39
+ - **Durable journals (W27)** — the audit journal and the recording window each mirror to a crash-safe write-ahead log, so a restart does not erase the audit trail or a recorded run. One JSON line per entry means the only artifact a crash can leave is a partial final line: recovery drops exactly that and rejects any invalid *interior* line as a genuine fault. Recovered entries keep their original ids and ordering, so they are byte-identical and a window split across processes replays as one uninterrupted run
40
+ - **Trust assumption & contractification (W31)** — VISION §3.1's last two governance dimensions: `strict` caps foreign adapters at L1 (no out-of-process children) and demands a declared parameter contract (`schema`) on every plugin; `standard` validates a tool's arguments against a declared schema before the call executes; `sandbox` checks nothing. The four-tier model is now fully implemented — every dimension in the VISION table has a code path
41
+ - **Audit hash chain (W30)** — an append-only audit log is only as trustworthy as its file permissions; a hash chain makes it tamper-evident. With `new OrbitRuntimeHost({ auditSigningKey })` every audit entry carries HMAC-SHA256 `prevHash`/`chainHash` linkage, `host.verifyAuditChain()` proves integrity, `orbit audit <trace.wal> --key …` verifies from the CLI, and the `strict` tier refuses to boot on a broken chain. Editing any entry breaks the chain at that point and everything after it
42
+ - **Four-tier governance (W29)** — VISION's Sandbox / Standard / Strict tiers are switchable configuration, not a design goal: `new OrbitRuntimeHost({ governanceProfile: "strict" })` tunes rate limits, trip thresholds, compression, PAE admission and trace durability in one declaration. `standard` is the kernel's previous numbers verbatim; a non-default tier is hashed into the run fingerprint, so a trace recorded under one tier refuses to replay under another (config drift, not silent divergence)
43
+ - **Zero runtime dependencies** — pure TypeScript, strict mode, runs on Node.js ≥ 20
44
+
45
+ ## Architecture
46
+
47
+ ```
48
+ @orbit/infra-common domain contracts · version parsing · domain errors
49
+
50
+ @orbit/core-hub channel · gateway (capabilityInvoke) · replay · trace
51
+
52
+ @orbit/sandbox-runtime sandbox · impact graph · isolation domains
53
+ @orbit/pae-engine plugin adaptation engine (JS / MCP / OpenAPI / Cordis)
54
+
55
+ host (src/) assembly & facade (OrbitRuntimeHost)
56
+ ```
57
+
58
+ 📐 Detailed diagrams & design rationale: [docs/architecture.md](./docs/architecture.md) · [architecture.svg](./docs/architecture.svg)
59
+
60
+ 📜 Architecture charter (three axioms · governance profiles · kernel admission gate): [docs/VISION.md](./docs/VISION.md)
61
+ 🗓 Dev plan (three release waves: open-source launch → gateway determinism boundary → ecosystem): [docs/DEV_PLAN.md](./docs/DEV_PLAN.md)
62
+ 🛠 Upgrade plan & blocker resolutions: [docs/UPGRADE_PLAN.md](./docs/UPGRADE_PLAN.md)
63
+ 📈 Product plan: [docs/PRODUCT_PLAN.md](./docs/PRODUCT_PLAN.md)
64
+
65
+ ## Plugin Adaptation Engine (W15)
66
+
67
+ Foreign runtimes — in-process JS today, MCP / OpenAPI / Cordis next — plug into
68
+ the kernel through the **Plugin Adaptation Engine (PAE)**. The adaptation layer
69
+ is deliberately thin: an adapter owns only the connection to the foreign runtime
70
+ and translates its tools into the kernel capability contract. Two rules make the
71
+ rest of the kernel unaware that anything foreign is happening:
72
+
73
+ 1. **Adapters never call the kernel directly.** A registered adapter is surfaced
74
+ as one capability channel (`ChannelKind.PAE_TOOL`), so every foreign call goes
75
+ `capabilityInvoke → ChannelHub → registry → adapter` and lands in the
76
+ `RecordJournal`. Foreign tools get the *same* four-way governance check as
77
+ native channels — gating, budgeting, trip protection, replay.
78
+ 2. **Adapters add no nondeterminism.** Randomness and clocks are injected through
79
+ `PaeInvokeCtx` (`rng` / `clock`); a handler that reaches for `Math.random` /
80
+ `Date.now` breaks replay and is rejected.
81
+
82
+ Capability negotiation is explicit, not silent: an adapter that cannot map a
83
+ foreign tool losslessly must say so via `fidelity` (`full | reduced | lossy`),
84
+ and a caller may demand a minimum fidelity. Anything below `full` must carry a
85
+ `fidelityNote`. The registry also derives a `configHash` of the whole adapter
86
+ surface into the run fingerprint, so a changed tool set reports as
87
+ configuration drift rather than as a digest mismatch.
88
+
89
+ ```ts
90
+ import { OrbitRuntimeHost, JsPaeAdapter, ChannelKind } from "orbit-agent-runtime";
91
+
92
+ const host = new OrbitRuntimeHost();
93
+ await host.bootHost();
94
+
95
+ // Adapt a foreign in-process tool set as a kernel capability channel.
96
+ const adapter = new JsPaeAdapter({
97
+ adapterId: "echo-tools",
98
+ sourceEdition: "1.0.0",
99
+ tools: [{
100
+ name: "echo",
101
+ capability: "channel:write",
102
+ handler: async (args) => ({ echoed: (args[0] as { text: string }).text })
103
+ }]
104
+ });
105
+ host.registerPaeToolAdapter(adapter); // foreign surface → derived Pact, gated + recorded
106
+
107
+ // Inside an agent script, a foreign tool is just another channel call:
108
+ // const out = await ctx.call(ChannelKind.PAE_TOOL, "echo", [{ text: "hi" }]);
109
+ ```
110
+
111
+ ## Isolation domains & cross-domain transactions (W19–W20)
112
+
113
+ The physical layer is allocated from the graph, not from a static trust table:
114
+
115
+ ```ts
116
+ const host = new OrbitRuntimeHost();
117
+ await host.bootHost();
118
+
119
+ host.registerPlugin({
120
+ id: "p.worker",
121
+ displayName: "p.worker",
122
+ edition: "1.0.0",
123
+ requireHostMinEdition: "0.8.0",
124
+ allowCapabilities: ["channel:read", "channel:write"],
125
+ declareChannelDeps: [ChannelKind.LLM_ACCESS]
126
+ });
127
+
128
+ // Graph → plan: closure > threshold ⇒ own L2 process; the rest share chunks.
129
+ // Omit `transportFactory` to use the built-in pure-unit host (node -e shim).
130
+ const plan = await host.allocateIsolationDomains({ maxImpactClosure: 1 });
131
+ plan.domains; // [{ id: "iso:p.worker", isolation: "L2", units: ["p.worker"] }, ...]
132
+
133
+ // A hop into a domain is a gateway transaction — recorded, replayed, settled.
134
+ const out = await host.invokeDomainUnit("p.worker", "ping", [{ hello: "world" }], {
135
+ pluginUnitId: "p.worker"
136
+ });
137
+
138
+ host.domainLedger(); // decision → execution → result
139
+ host.reconcileDomainTransactions(); // { balanced, pairs, orphans, rejected, totals }
140
+ host.runFingerprint(); // gains `domainPlanHash` once domains exist
141
+ ```
142
+
143
+ Three properties worth stating explicitly:
144
+
145
+ - **No bare randomness, no bare clock.** Coordinates and delays are hardcoded or
146
+ derived from indices; latency is measured through an injected clock, so a
147
+ ledger hashes identically for identical runs.
148
+ - **Backward compatible fingerprints.** `domainPlanHash` is *omitted* (not empty)
149
+ while no plan exists, so traces from hosts that never allocate domains keep the
150
+ exact fingerprint they had before the physical layer existed.
151
+ - **Replay never re-enters a domain.** The frozen output is injected at the
152
+ gateway, so the child process is not touched and no transaction is opened —
153
+ axiom A1 expressed on the ledger.
154
+
155
+ ## Journal durability (W27)
156
+
157
+ Journals were in-memory only, so a restart erased the audit trail and any
158
+ recorded run. Both now carry a crash-safe write-ahead log — opt-in per path, and
159
+ omitting the paths keeps the previous purely in-memory behavior byte for byte.
160
+
161
+ ```ts
162
+ const host = new OrbitRuntimeHost({
163
+ traceJournalPath: ".orbit/trace.wal.jsonl", // audit / behavior journal
164
+ recordJournalPath: ".orbit/record.wal.jsonl", // recording window
165
+ auditRetention: 10_000 // keep the newest N entries
166
+ });
167
+
168
+ await host.bootHost(); // recover (and heal) first, then wire channels
169
+ // ... run the agent; a previous window is resumed, orderIndex continues
170
+ await host.shutdownHost(); // drain pending writes, then apply retention
171
+ ```
172
+
173
+ Design points worth knowing:
174
+
175
+ - **The crash model justifies the format.** A write appends one whole line, so
176
+ the only thing a crash can leave behind is a *partial final line*. Recovery is
177
+ therefore a strict dichotomy: drop that trailing line, and reject any corrupt
178
+ or structurally invalid **interior** line as `WalFileInvalidError` with its line
179
+ number — an interior line cannot have been truncated by a crash, so skipping it
180
+ silently would hide real corruption.
181
+ - **The in-memory journal stays the source of truth.** The WAL is a
182
+ fire-and-forget mirror, serialised through a write chain so lines never
183
+ interleave; `shutdownHost` awaits it, so a clean shutdown loses nothing.
184
+ - **Recovery is byte-identical.** `entryUid`, `occurredAt` and `orderIndex` are
185
+ preserved, so a resumed recording window continues its index instead of
186
+ restarting at 0 — a run split across processes replays as one sequence.
187
+ - **A truncated tail is healed before the first append.** Recovery tolerates it,
188
+ but the line is still on disk: once this run appends, it becomes an *interior*
189
+ invalid line, which is a hard fault. Left unhandled, one crash would make every
190
+ later boot fail. `healIfNeeded()` rewrites the file atomically from the
191
+ surviving prefix, and is a no-op on a healthy log.
192
+ - **Retention is explicit.** An append-only log that grows without limit
193
+ eventually fills the disk, and a full disk is an outage, so `auditRetention` is
194
+ an operator choice rather than an implicit default. `pruneAuditLog()` prunes a
195
+ long-running host on demand.
196
+
197
+ ## Web console
198
+
199
+ [Orbit Console](./web/README.md) — a zero-dependency management console that
200
+ drives a real kernel instance over HTTP: lifecycle, channels, plugins,
201
+ sandboxes, trace, replay studio, impact graph and cost routing.
202
+
203
+ ```bash
204
+ npm run build
205
+ node web/bridge-server.mjs # http://127.0.0.1:8899
206
+ ```
207
+
208
+ ## Examples & benchmarks
209
+
210
+ **Examples** (`./examples`) are runnable, assertion-gated walkthroughs — each
211
+ exits non-zero on any failed check, so they double as CI smoke tests:
212
+
213
+ ```bash
214
+ node examples/custom-channel.mjs # implement a channel → record → replay byte-identically
215
+ node examples/js-pae-plugin.mjs # foreign JS tools through a governed channel (PAE L0)
216
+ node examples/mcp-adapter.mjs # real MCP child process, replayed after the peer is dead (L2)
217
+ node examples/cli-record-replay.mjs # the orbit CLI record → replay → diff loop
218
+ ```
219
+
220
+ **Benchmarks** (`./benchmarks`, `npm run benchmark`) observe the hot paths
221
+ against the budgets in [docs/VISION.md](./docs/VISION.md):
222
+
223
+ | Suite | What it measures | Sample (Node 22) |
224
+ |---|---|---|
225
+ | `gateway` | governed `capabilityInvoke` end to end (record mode) | ~82k calls/s (~12 µs) |
226
+ | `replay` | journal fast-path injection | ~261k calls/s (~3.8 µs) |
227
+ | `wal` | durable append + flush (WAL mirror) | ~1.5k appends/s |
228
+ | `pae` | L0 in-process vs L2 stdio-child adapter latency | ~38 µs vs ~176 µs (4.6×) |
229
+
230
+ ## Repository layout (monorepo)
231
+
232
+ The kernel is organised as npm workspaces with TypeScript Project References, so
233
+ each layer builds, versions and tests in isolation while the public API at
234
+ `src/index.ts` stays unchanged.
235
+
236
+ | Package | Path | Responsibility | Depends on |
237
+ |---|---|---|---|
238
+ | `@orbit/infra-common` | `packages/infra-common` | Domain contracts, pure utils, error types | — |
239
+ | `@orbit/core-hub` | `packages/core-hub` | Channel / gateway / replay / trace / pact / safeguard / routing | infra-common |
240
+ | `@orbit/sandbox-runtime` | `packages/sandbox-runtime` | Agent sandboxes, impact-domain graph, isolation domains | infra-common, core-hub |
241
+ | `@orbit/pae-engine` | `packages/pae-engine` | Plugin Adaptation Engine (JS / MCP / OpenAPI / Cordis) | infra-common, core-hub |
242
+ | root host | `src/` (`core/orbitRuntimeHost.ts`, `index.ts`) | Component assembly & facade | all packages |
243
+ | `@orbit/admin-console` *(app)* | `web/` | Web admin console — drives a live kernel via the bridge server | root host (`dist/`) |
244
+
245
+ Build and test from the repo root — `npm install` wires the `@orbit/*` workspace
246
+ symlinks, then `tsc -b` builds bottom-up:
247
+
248
+ ```bash
249
+ npm install # dev deps + workspace links
250
+ npm run build # tsc -b across all packages and the root
251
+ npm test # build + kernel unit tests (node:test)
252
+ npm run test:console # web console unit tests (node:test)
253
+ ```
254
+
255
+ > **Package manager:** the monorepo currently uses **npm workspaces**; the
256
+ > roadmap's pnpm migration is deferred (this runtime has no `pnpm`/`corepack`),
257
+ > but the package layout is identical and the switch is a drop-in later.
258
+
259
+ ## Getting started — the deterministic-replay loop
260
+
261
+ The headline feature is *reproducibility*: record a real run, replay it with
262
+ **zero** model calls, and prove the two chains are byte-identical. You drive it
263
+ with the `orbit` CLI (zero extra dependencies — ships in `bin/`, runs on Node ≥ 20).
264
+
265
+ ```bash
266
+ npm install # dev deps only (typescript + @types/node)
267
+ npm run build # strict TypeScript compile → dist/ (also builds the CLI)
268
+
269
+ # 1) write a script — it receives a ctx with channel access
270
+ cat > agent.mjs <<'EOF'
271
+ export default async function (ctx) {
272
+ const reply = await ctx.llm.chat("summarize: the sky is blue");
273
+ const seen = await ctx.call(ctx.ChannelKind.MEM_KV_STORE, "readEntry", "last");
274
+ return { reply, seen };
275
+ }
276
+ EOF
277
+
278
+ # 2) record a live run → captures every channel call into a trace
279
+ node bin/orbit.mjs record agent.mjs --out trace.jsonl
280
+ # ✓ recorded 2 channel calls from agent.mjs
281
+ # trace : trace.jsonl (JSONL, atomic write)
282
+ # meta : trace.jsonl.meta.json (driving script + sanitized config)
283
+
284
+ # 3) replay with ZERO real calls → reconcile the digest chain
285
+ node bin/orbit.mjs replay trace.jsonl
286
+ # original calls : 2 replayed calls : 2
287
+ # result : ✓ VERIFIED — digest chain consistent
288
+
289
+ # 4) diff two traces — locate the first divergence
290
+ node bin/orbit.mjs diff trace.jsonl trace.jsonl
291
+ # result: ✓ identical call chains
292
+ ```
293
+
294
+ That is the whole product in four commands: a stranger can be reproducible in
295
+ under ten minutes. Every command accepts `--json` for machine-readable output.
296
+
297
+ ### Lower-level API & demos
298
+
299
+ ```bash
300
+ npm test # build + run kernel unit tests (node:test) — 348 cases
301
+ npm run test:console # web console unit tests (node:test) — 89 cases
302
+ npm run demo # build + run demo-host.ts (full lifecycle demo)
303
+ npm run demo:replay # deterministic replay: ~1s real run replayed in ~2ms
304
+ ```
305
+
306
+ Expected demo output highlights:
307
+
308
+ ```
309
+ [cap] plugin -> LLM channel (channel:read): allowed
310
+ [cap] plugin -> KV write (undeclared channel:write): rejected
311
+ [sandbox] round 3 rejected (budget spent): agent sandbox box.demo-1 reached cycle limit 2
312
+ [guard] plugin crash isolated and journaled (host keeps running)
313
+ [trace] 5 entries: AGENT_SINGLE_CYCLE_EXEC / AGENT_CYCLE_LIMIT_HIT / PLUGIN_UNIT_EXCEPTION ...
314
+ ```
315
+
316
+ ## orbit CLI
317
+
318
+ Three commands form the deterministic-replay loop. The CLI loads the compiled
319
+ kernel via `createRequire` and uses only Node built-ins.
320
+
321
+ ```bash
322
+ orbit record <script.js> [--out trace.jsonl] [--config orbit.config.json]
323
+ orbit replay <trace.jsonl> [--via script.js] [--config orbit.config.json]
324
+ orbit diff <a.jsonl> <b.jsonl>
325
+ ```
326
+
327
+ | Command | What it does | Exit code |
328
+ |---|---|---|
329
+ | `record` | Run `<script>` against a live kernel; capture every channel call into a JSONL trace + a `.meta.json` (driving script, sanitized config, orbit/node versions). | 0 on success |
330
+ | `replay` | Re-run the recorded script with **zero** real channel calls; reconcile the replayed chain against the original (bank-style digest check). | 0 verified · 1 drift |
331
+ | `diff` | Compare two traces record-by-record; report the first breakpoint (`channelKind` / `funcName` / `inputDigest` / `outputSnapshot`). | 0 identical · 1 divergent |
332
+
333
+ **Script contract** — default-export an async function receiving `ctx`:
334
+
335
+ ```js
336
+ export default async function (ctx) {
337
+ const reply = await ctx.llm.chat("hello"); // sugar over LLM_ACCESS.chatRound
338
+ const prev = await ctx.call(ctx.ChannelKind.MEM_KV_STORE, "readEntry", "k");
339
+ return { reply, prev };
340
+ }
341
+ ```
342
+
343
+ **Configuration** — `orbit.config.json` (all keys optional) selects real
344
+ capabilities; env vars override for quick experiments:
345
+
346
+ ```json
347
+ {
348
+ "llm": { "kind": "mock" | "openai-compat", "baseUrl": "…", "model": "…" },
349
+ "file": { "enabled": true, "rootDir": "./agent-workspace" },
350
+ "shell": { "enabled": true, "allowedCommands": ["git","node"], "envAllowlist": ["PATH"] }
351
+ }
352
+ ```
353
+
354
+ Env overrides: `ORBIT_LLM_BASE_URL` / `ORBIT_LLM_API_KEY` / `ORBIT_LLM_MODEL`,
355
+ `ORBIT_FILE_ROOT`, `ORBIT_SHELL_ALLOW` (comma list) / `ORBIT_SHELL_ENV` (comma list).
356
+
357
+ > A recorded trace replays on a machine that has **none** of the real channels
358
+ > installed — the replay fast path serves from the journal and never needs
359
+ > providers, credentials or tools. See [docs/guide.md](./docs/guide.md) to
360
+ > write your own replayable channel.
361
+
362
+ ## Admin console workspace
363
+
364
+ The kernel is split into npm workspaces — see
365
+ [Repository layout (monorepo)](#repository-layout-monorepo) above for the package
366
+ map. The web admin console lives at `web/` as the private `@orbit/admin-console`
367
+ app workspace, run with `npm run start:web`.
368
+
369
+ ## Core concepts
370
+
371
+ | Concept | Role |
372
+ |---|---|
373
+ | Channel | Unified abstraction of an external capability; plugins can override built-in channels (plugin-first, built-in fallback) |
374
+ | Pact | Plugin manifest: id, edition, host min edition, declared capabilities |
375
+ | TripProtector | Per-plugin fault state machine: consecutive failures trip, cooldown then probe, single success recovers |
376
+ | TraceJournal | Append-only behavior journal with snapshot capture/restore and filters by trace / sandbox |
377
+ | AgentSandbox | Per-agent execution sandbox: cycle counter, per-round trace ID, channel-based model calls |
378
+ | SandboxPool | Lifecycle management of sandboxes: spawn, pick, drop, release |
379
+
380
+ ## Roadmap
381
+
382
+ | Milestone | Content | Status |
383
+ |---|---|---|
384
+ | M1 | Engineering skeleton: tsconfig, package, tests, demo entry, capability gate closed-loop | ✅ Done |
385
+ | M2 | **Deterministic replay** — record non-determinism, replay with zero model calls, digest-chain reconciliation | ✅ Done |
386
+ | M3 | **Impact domain graph** — fault isolation as reverse reachability closure with an isolation theorem, static capability-closure verification | ✅ Done |
387
+ | M4 | **Cost-aware routing** — channel cost/latency/quality profiles, per-cycle sandbox budgets | ✅ Done |
388
+ | M5 | Product hardening: benchmarks, plugin examples, CI, npm publish | ✅ Done |
389
+ | M6 | **Open-source launch** — `orbit` CLI (`record`/`replay`/`diff`) | ✅ Done (CLI + audit shipped) |
390
+ | M6b | **Open-source launch** — docs site / landing page, first public npm release | Pending (npm publish is one command away; `prepublishOnly` gates it) |
391
+
392
+ > M5/M6 track the `P0` milestones in [docs/PRODUCT_PLAN.md](./docs/PRODUCT_PLAN.md)
393
+ > (P0.1 real capabilities → P0.2 CLI release → P0.3 open-source launch).
394
+
395
+ ## Design stance vs. existing frameworks
396
+
397
+ - **DeepSeek Harness** pursues "everything is a plugin" on a 50+-package monorepo. Orbit keeps a **fixed kernel + channel-level plugins** model — fewer degrees of freedom, but each of the six mechanisms can be fully explained and independently verified.
398
+ - **Circuit breaker libraries** (opossum/cockatiel) protect a single call site statistically. Orbit isolates at **plugin granularity** and binds every state transition to the trace journal.
399
+ - **MCP** standardizes tool discovery; Orbit's channel hub is a lighter, in-process equivalent with timeout, fallback and capability gating.
400
+
401
+ ## License
402
+
403
+ [Apache License 2.0](./LICENSE)
404
+
405
+ ## Connect a real model (DeepSeek)
406
+
407
+ The built-in LLM channel is a mock for tests; swap in the real DeepSeek
408
+ provider (OpenAI-compatible, zero extra deps) at runtime — plugin channels
409
+ take precedence over built-ins:
410
+
411
+ ```ts
412
+ import { OrbitRuntimeHost, DeepSeekChannel, ChannelKind } from "orbit-agent-runtime";
413
+
414
+ const host = new OrbitRuntimeHost();
415
+ await host.bootHost();
416
+ host.channelHub.registerPluginExtChannel(
417
+ ChannelKind.LLM_ACCESS,
418
+ new DeepSeekChannel({ apiKey: process.env.DEEPSEEK_API_KEY, model: "deepseek-chat" })
419
+ );
420
+ ```
421
+
422
+ Deterministic replay works unchanged: record a live run, then replay with
423
+ zero API calls and byte-identical output.
424
+
425
+ ```bash
426
+ DEEPSEEK_API_KEY=sk-xxx npm run demo:deepseek
427
+ ```
428
+
429
+ ### Any OpenAI-compatible model
430
+
431
+ `OpenAICompatChannel` works against **any** OpenAI-compatible endpoint —
432
+ just point `baseUrl` at it:
433
+
434
+ ```ts
435
+ import { OpenAICompatChannel } from "orbit-agent-runtime";
436
+
437
+ // DeepSeek / OpenAI / Qwen / Kimi / GLM / Ollama / vLLM ...
438
+ host.channelHub.registerPluginExtChannel(ChannelKind.LLM_ACCESS, new OpenAICompatChannel({
439
+ apiKey: process.env.LLM_API_KEY,
440
+ baseUrl: "https://dashscope.aliyuncs.com/compatible-mode", // example: Qwen
441
+ model: "qwen-plus"
442
+ }));
443
+ ```
444
+
445
+ Non-OpenAI protocols (Anthropic Claude, Google Gemini) each need a small
446
+ `IChannelProvider` adapter (~20 lines); record/replay/isolation/routing are
447
+ protocol-agnostic.
448
+
449
+ Production hardening is built in: faults are classified
450
+ (`LlmChannelFaultError` — timeout / network / rate_limited / server_error /
451
+ auth / bad_request / not_found / no_content / invalid_response), retryable
452
+ faults are retried with a **deterministic** exponential backoff (no
453
+ `Math.random`, `Retry-After` honored), and internal retries never leak into
454
+ the record journal.
455
+
456
+ ### Real tool channels (File / Shell)
457
+
458
+ ```ts
459
+ import { FileChannel, ShellChannel } from "orbit-agent-runtime";
460
+
461
+ // Filesystem access jailed to a root directory (path escapes are rejected).
462
+ host.channelHub.registerPluginExtChannel(ChannelKind.FILE_SYSTEM, new FileChannel({
463
+ rootDir: "./agent-workspace"
464
+ }));
465
+
466
+ // Command execution behind an exact-match whitelist; argv arrays only
467
+ // (no shell string → no injection surface), empty child env by default,
468
+ // hard timeout and output caps.
469
+ host.channelHub.registerPluginExtChannel(ChannelKind.SHELL_EXEC, new ShellChannel({
470
+ allowedCommands: ["git", "node", process.execPath],
471
+ workDir: "./agent-workspace",
472
+ envAllowlist: ["PATH"]
473
+ }));
474
+ ```
475
+
476
+ Both are `IO_BOUND` channels: record a run, then replay it with zero disk
477
+ access and zero process spawns. The capability gate maps
478
+ read/list/stat → `channel:read` and write/append/remove/mkdir/exec →
479
+ `channel:write`.
480
+
481
+ ### Trace persistence (JSONL)
482
+
483
+ ```ts
484
+ import { saveRecordJournal, loadRecordJournal } from "orbit-agent-runtime";
485
+
486
+ await saveRecordJournal(journal, "trace.jsonl"); // atomic write (tmp + rename)
487
+ const restored = await loadRecordJournal("trace.jsonl"); // validated load
488
+ ```
489
+
490
+ Replay works on a fresh host **without the real channels installed** — the
491
+ replay fast path serves from the journal and never requires providers,
492
+ credentials or tools on the replaying machine.