# Born In-Tree: The Great Extraction from OpenClaw Core

> lossless-claw was not built as a standalone plugin — it was extracted from OpenClaw's own in-tree src/plugins/lcm/ directory. The extraction-plan.md spec reveals the code was simply copied with all imports still pointing at OpenClaw core internals, and the entire extraction was a post-hoc dependency-injection refactor. The plugin also quietly depends on three @earendil-works/pi-* packages whose source is not public in this repo.

- Repository: Martian-Engineering/lossless-claw
- GitHub: https://github.com/Martian-Engineering/lossless-claw
- Human wiki: https://grok-wiki.com/public/wiki/martian-engineering-lossless-claw-a94e8135853e
- Complete Markdown: https://grok-wiki.com/public/wiki/martian-engineering-lossless-claw-a94e8135853e/llms-full.txt

## Source Files

- `specs/extraction-plan.md`
- `src/types.ts`
- `src/openclaw-bridge.ts`
- `package.json`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [specs/extraction-plan.md](specs/extraction-plan.md)
- [specs/lossless-claw-rename-spec.md](specs/lossless-claw-rename-spec.md)
- [src/types.ts](src/types.ts)
- [src/openclaw-bridge.ts](src/openclaw-bridge.ts)
- [src/plugin/index.ts](src/plugin/index.ts)
- [src/plugin/shared-init.ts](src/plugin/shared-init.ts)
- [src/engine.ts](src/engine.ts)
- [package.json](package.json)
- [index.ts](index.ts)
</details>

# Born In-Tree: The Great Extraction from OpenClaw Core

`@martian-engineering/lossless-claw` was never designed as a standalone plugin from day one. It was born inside OpenClaw's own codebase as `src/plugins/lcm/` — a first-party feature living cheek-by-jowl with the runtime it consumed. The repo you're looking at is the *extracted* artifact, and several layers of its history, naming decisions, and dependency tricks tell that story in surprisingly candid detail.

This page surfaces the hidden paper trail: the copy-paste origin, the two name changes, the post-hoc dependency-injection surgery, and the three `@earendil-works/pi-*` packages whose source lives nowhere public in this repo.

---

## The Copy-Paste Genesis

The extraction plan is unusually frank about how this started:

> "The source code has already been copied into `src/` and `test/` directories but all imports still reference OpenClaw core internals. The main task is to refactor imports using dependency injection."

Sources: [specs/extraction-plan.md:1-6]()

This means the initial commit of the repo was not a fresh design. It was a verbatim dump of `src/plugins/lcm/` from the OpenClaw monorepo, with every `import` still pointing at paths like `../../context-engine/types.js`, `../../routing/session-key.js`, and `../../../memory/sqlite.js`. The extraction plan itself is a *to-do list written after the code already landed*, not a design document written before it.

---

## The Three Names (A Brief Identity Crisis)

By the time the extraction happened, this plugin had already been renamed once internally. The rename spec reveals a layered history:

| Era | npm package name | Plugin/engine id | Repo name |
|-----|-----------------|-----------------|-----------|
| Earliest | `@martian-engineering/open-lcm` | `open-lcm` | `openclaw-lcm` |
| Middle | `@martian-engineering/openclaw-lcm` | `openclaw-lcm` | `openclaw-lcm` |
| Current | `@martian-engineering/lossless-claw` | `lossless-claw` | `lossless-claw` |

Sources: [specs/lossless-claw-rename-spec.md:1-10, 45-50, 181-183]()

The `.pebbles/events.jsonl` issue-tracker history (mentioned in the rename spec) still carries IDs like `open-lcm-8a9` and `open-lcm-6fc` — the append-only event log that cannot be rewritten. The extraction plan itself references the intermediate name `@martian-engineering/open-lcm` as the original target package name before the final rename landed.

---

## The Import Rewrite Surgery

The `extraction-plan.md` doubles as a forensic map of every coupling that existed between LCM and OpenClaw core. Eight distinct import paths needed replacing:

```text
../../context-engine/types.js          → openclaw/plugin-sdk
../../context-engine/registry.js       → openclaw/plugin-sdk
../../config/config.js                 → openclaw/plugin-sdk
@mariozechner/pi-ai (completeSimple)   → LcmDependencies.complete (injected)
../../agents/pi-embedded-runner/model  → LcmDependencies.resolveModel
../../agents/agent-paths.js            → LcmDependencies.resolveAgentDir
../../routing/session-key.js           → LcmDependencies.parseAgentSessionKey et al.
../../gateway/call.js                  → LcmDependencies.callGateway
../../../memory/sqlite.js              → better-sqlite3 (direct)
```

Sources: [specs/extraction-plan.md:49-128]()

The strategy was dependency injection through a single `LcmDependencies` interface, constructed entirely in `src/plugin/index.ts` from the `OpenClawPluginApi` at registration time. Every function that previously reached into OpenClaw's guts now receives a closure.

---

## The `LcmDependencies` Contract: All the Secrets in One Interface

The `LcmDependencies` interface in `src/types.ts` is essentially a full confession of what LCM once imported directly. Its fifteen-plus fields read like a negative space drawing of OpenClaw's internal API surface:

```typescript
export interface LcmDependencies {
  config: LcmConfig;
  complete: CompleteFn;                            // was: @mariozechner/pi-ai completeSimple
  callGateway: CallGatewayFn;                      // was: ../../gateway/call.js
  resolveModel: ResolveModelFn;                    // was: ../../agents/.../model.js
  parseAgentSessionKey: ParseAgentSessionKeyFn;    // was: ../../routing/session-key.js
  isSubagentSessionKey: IsSubagentSessionKeyFn;
  normalizeAgentId: (id?: string) => string;
  buildSubagentSystemPrompt: (...) => string;
  readLatestAssistantReply: (...) => string | undefined;
  resolveAgentDir: () => string;                   // was: ../../agents/agent-paths.js
  resolveSessionIdFromSessionKey: ...;
  resolveSessionTranscriptFile: ...;
  listStartupSessionFileCandidates?: ...;
  agentLaneSubagent: string;
  log: { info, warn, error, debug };
}
```

Sources: [src/types.ts:115-178]()

One field even has an inline tombstone in the live source: `sanitizeToolUseResultPairing` was removed from `LcmDependencies` and its comment says "now imported directly in assembler from transcript-repair.ts" — the refactor was still in flux when this file settled.

---

## The `openclaw-bridge.ts` Admission

`src/openclaw-bridge.ts` exists because the `openclaw/plugin-sdk` package doesn't export the newer context-engine type symbols yet. Rather than waiting for the SDK to catch up, the plugin ships its own local redefinitions of `ContextEngine`, `AssembleResult`, `BootstrapResult`, and friends. The file comment says it plainly:

> "This module intentionally keeps the context-engine contract local because older OpenClaw SDK packages do not publish these newer type symbols yet."

Sources: [src/openclaw-bridge.ts:7-9]()

The `OpenClawPluginApi` type defined there is deliberately permissive (`[key: string]: any`), a defensive stance against an evolving host API.

---

## The `@earendil-works/pi-*` Packages: Private Lineage, Public Dependency

The three runtime dependencies are not hosted in this repository:

| Package | Role | Version range |
|---------|------|---------------|
| `@earendil-works/pi-agent-core` | Core agent message types | `>=0.74 <1` |
| `@earendil-works/pi-ai` | LLM completion primitives | `>=0.74 <1` |
| `@earendil-works/pi-coding-agent` | `SessionManager` used in `engine.ts` | `>=0.74 <1` |

Sources: [package.json:36-39]()

The extraction plan's **original** import map lists these as `@mariozechner/pi-agent-core` and `@mariozechner/pi-ai` — a personal npm scope for [Mario Zechner](https://github.com/badlogic). By the time the extraction landed in this repo, the packages had been published under the `@earendil-works` org scope, but the extraction plan spec still refers to `@mariozechner/pi-ai` as the old import that needed to be replaced.

Sources: [specs/extraction-plan.md:86-90, 132-133]()

The `engine.ts` file imports `SessionManager` directly from `@earendil-works/pi-coding-agent` with no local shim — this is the one coupling that was *kept* as a direct peer dependency rather than injected through `LcmDependencies`.

Sources: [src/engine.ts:8]()

---

## The Singleton Anti-DB-Lock-Storm Pattern

When OpenClaw v2026.4.5+ started calling `register()` per-agent-context (main agent, subagents, cron lanes), each call would have opened a fresh SQLite connection and run migrations on the same `~/.openclaw/lcm.db`. The result: lock storms on large databases.

The fix is a `globalThis`-keyed singleton map using `Symbol.for()`:

```typescript
const SHARED_KEY = Symbol.for("@martian-engineering/lossless-claw/shared-init");
```

Sources: [src/plugin/shared-init.ts:30-31]()

The first `register()` call wins and stores its `waitForEngine`/`waitForDatabase` closures in the global map under the DB path key. Every subsequent call reuses those closures without opening a new connection.

---

## The Build: Bundled, Minified, External-Punched

The esbuild command in `package.json` is worth reading closely:

```
esbuild index.ts --bundle --platform=node --target=node22 --format=esm
  --outfile=dist/index.js
  --external:openclaw
  --external:"@earendil-works/*"
  --minify-whitespace
```

Sources: [package.json:19]()

`openclaw` and the entire `@earendil-works/*` namespace are marked external — they are *not* bundled into `dist/index.js`. OpenClaw provides `openclaw` at runtime; the `@earendil-works` packages come from the host's `node_modules`. Everything else (TypeScript source, `better-sqlite3`, `@sinclair/typebox`) is bundled in. This means a consumer who installs the plugin with `--ignore-scripts` may fail to build the native `better-sqlite3` addon — a known risk the extraction plan explicitly flags.

Sources: [specs/extraction-plan.md:168-172]()

---

## Diagram: Before and After the Extraction

```text
BEFORE (in-tree)                     AFTER (standalone plugin)
─────────────────────────────────    ──────────────────────────────────────
OpenClaw monorepo                    @martian-engineering/lossless-claw
  src/
    plugins/
      lcm/  ←── LCM code            src/
        engine.ts  ─────── directly    engine.ts  ──── via LcmDependencies
        summarize.ts ──── imports       summarize.ts     (injected at register)
        tools/ ─────────── from         tools/
    context-engine/                  openclaw/plugin-sdk  (external)
    routing/session-key.js           @earendil-works/pi-*  (external peers)
    gateway/call.js                  better-sqlite3  (bundled direct dep)
    memory/sqlite.js
```

---

## Summary

`lossless-claw` is a post-hoc extraction of OpenClaw's in-tree LCM feature, dressed up as a standalone plugin through a systematic dependency-injection refactor. The extraction plan spec is still in the repo as `specs/extraction-plan.md` — a candid to-do list written *after* the code was already copied, not before. The package carries two ghost names (`open-lcm`, `openclaw-lcm`) in its rename spec and issue-tracker history, relies on three `@earendil-works/pi-*` packages whose source is not public here, and includes a compatibility shim (`src/openclaw-bridge.ts`) for SDK symbols the host hasn't published yet. The whole thing is held together by a single `LcmDependencies` interface that is, in effect, a type-safe record of everything LCM once stole directly from OpenClaw's internals.

Sources: [specs/extraction-plan.md:1-6](), [specs/lossless-claw-rename-spec.md:181-209]()
