@draht/rlm 2026.7.12-1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `RlmSession` — the RLM root loop.\n *\n * root-LLM-produces-code -> REPL-executes -> truncated-stdout ->\n * history-append -> FINAL-check, per\n * .planning/phases/26-rlm-core-primitives/26-01-PLAN.md, Architecture\n * section 4. Talks to `../python/repl_driver.py` over the newline-delimited\n * JSON wire protocol documented in that file's module docstring (and\n * section 2-3 of the plan).\n */\n\nimport { type ChildProcess, execFile } from \"node:child_process\";\nimport { mkdtempSync, rmSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { spawnSandboxed } from \"./sandbox.js\";\nimport type { RlmHistoryEntry, RlmResult, RlmSessionOptions } from \"./types.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst DRIVER_PATH = join(__dirname, \"..\", \"python\", \"repl_driver.py\");\n\nconst DEFAULT_MAX_ITERATIONS = 24;\nconst DEFAULT_STDOUT_TRUNCATE_CHARS = 2000;\n\n// Resource-limit defaults (Phase 28, Architecture section 3). These are the\n// REAL enforcement mechanisms -- Node-side wall-clock + RSS polling, not any\n// Python-level RLIMIT/timer (see repl_driver.py's Linux-only RLIMIT_AS,\n// which is explicitly a backstop, not the primary mechanism, and isn't used\n// on macOS at all per the plan).\nconst DEFAULT_STEP_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_RSS_BYTES = 256 * 1024 * 1024;\nconst DEFAULT_RSS_POLL_INTERVAL_MS = 250;\n\n/**\n * Thrown internally when a step's Node-side wall-clock timeout fires and the\n * driver subprocess is hard-killed. Caught in `run()` and mapped to\n * `RlmResult.kind === \"timeout\"` -- never surfaced to callers directly.\n */\nclass RlmStepTimeoutError extends Error {}\n\n/**\n * Thrown internally when a `maxSubCalls`/`maxTotalCostUsd` pre-check fails\n * and the driver subprocess is hard-killed as a result. Caught in `run()`\n * and mapped to `RlmResult.kind === \"budget_exhausted\"` -- never surfaced to\n * callers directly.\n */\nclass RlmBudgetExhaustedError extends Error {}\n\n/**\n * Thrown internally when the OS-level sandbox's runtime startup self-test\n * (Phase 28 Architecture section 1) doesn't confirm the sandbox is actually\n * enforcing for this process -- either the driver never answered, answered\n * with something other than a well-formed `self_test_result`, or reported\n * that a network connect or an out-of-workdir file read *succeeded*. Caught\n * in `run()` and mapped to `RlmResult.kind === \"sandbox_violation\"`. This is\n * distinct from the synchronous `SandboxUnavailableError` thrown by\n * `spawnSandboxed` itself (missing wrapper binary/profile/interpreter) --\n * that one fails closed before any process exists; this one fails closed\n * after a process exists but before it's ever trusted with real\n * root-LLM-authored code.\n */\nclass RlmSandboxViolationError extends Error {}\n\n/**\n * Reads the driver subprocess's current resident-set size in bytes, or\n * `null` if it can't be determined right now (process already exited,\n * `ps`/`/proc` unavailable, a transient read race, etc.). `null` always\n * means \"skip this poll\" -- never treated as \"assume 0 bytes\" or \"assume\n * over the limit\", since either guess could produce a wrong kill/no-kill\n * decision.\n *\n * Linux: reads `/proc/<pid>/status`'s `VmRSS` line directly (no subprocess\n * spawn needed). Every other platform (macOS in practice): shells out to\n * `ps -o rss= -p <pid>` (BSD `ps` reports RSS in KiB) since there's no\n * `/proc` to read. See Phase 28 Architecture section 3.\n */\nfunction readRssBytes(pid: number): Promise<number | null> {\n\tif (process.platform === \"linux\") {\n\t\treturn readFile(`/proc/${pid}/status`, \"utf8\").then(\n\t\t\t(status) => {\n\t\t\t\tconst match = /^VmRSS:\\s*(\\d+)\\s*kB$/m.exec(status);\n\t\t\t\treturn match ? Number(match[1]) * 1024 : null;\n\t\t\t},\n\t\t\t() => null,\n\t\t);\n\t}\n\treturn new Promise((resolve) => {\n\t\texecFile(\"ps\", [\"-o\", \"rss=\", \"-p\", String(pid)], { encoding: \"utf8\" }, (err, stdout) => {\n\t\t\tif (err) {\n\t\t\t\tresolve(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst trimmed = stdout.trim().split(\"\\n\")[0]?.trim();\n\t\t\tif (!trimmed) {\n\t\t\t\tresolve(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst kb = Number(trimmed);\n\t\t\tresolve(Number.isFinite(kb) ? kb * 1024 : null);\n\t\t});\n\t});\n}\n\n/** One newline-delimited-JSON message in either direction of the wire protocol. */\ninterface DriverMessage {\n\ttype: string;\n\t[key: string]: unknown;\n}\n\n/** The `exec_result.final` payload shape (see repl_driver.py). */\ninterface DriverFinalPayload {\n\tkind: \"value\" | \"var\";\n\t/** For \"value\": `str(answer)`. For \"var\": `repr(globals_dict[name])`. */\n\tvalue: string;\n\tname?: string;\n}\n\ninterface DriverExecResult extends DriverMessage {\n\ttype: \"exec_result\";\n\tstdout: string;\n\terror: string | null;\n\tfinal: DriverFinalPayload | null;\n}\n\n/**\n * Extracts Python source from a root-LLM response.\n *\n * Per R26-RLM.4, the response is expected to contain a ```repl or ```python\n * fenced block (possibly surrounded by prose explaining the plan). Falls\n * back to the whole (trimmed) response when no fence is present, so mocked\n * `rootLlm` callbacks in tests can return raw code directly.\n */\nexport function extractPythonCode(response: string): string {\n\tconst match = /```(?:repl|python)[ \\t]*\\r?\\n([\\s\\S]*?)```/.exec(response);\n\tif (!match) return response.trim();\n\treturn match[1].replace(/\\r?\\n$/, \"\");\n}\n\n/**\n * Truncates captured stdout to `limit` characters, appending an explicit\n * marker for how many characters were cut — matching the `[truncated N\n * chars]` format R28-SBX.4 (Phase 28) will enforce as a hard cap. This phase\n * only applies it as a plain formatting rule, not a safety mechanism.\n */\nexport function truncateStdout(stdout: string, limit: number): string {\n\tif (stdout.length <= limit) return stdout;\n\tconst omitted = stdout.length - limit;\n\treturn `${stdout.slice(0, limit)}\\n[truncated ${omitted} chars]`;\n}\n\n/**\n * Restricted Python-literal parser used to turn a `FINAL_VAR`-produced\n * `repr()` string back into a real JS value.\n *\n * Handles the JSON-safe subset the plan calls out: `None`/`True`/`False`,\n * int/float, single- or double-quoted strings (with the common backslash\n * escapes), lists, tuples (mapped to arrays — JS has no tuple type), and\n * dicts (mapped to plain objects; keys are coerced with `String(...)`).\n * Anything it can't parse (custom object reprs like `<Foo object at\n * 0x...>`, sets, NaN/Infinity, etc.) falls back to the raw repr string\n * rather than throwing, so a result is never lost — see the module-level\n * note in this file's neighboring test for why raw-string fallback beats\n * failing the whole run.\n */\nexport function pythonReprToValue(repr: string): unknown {\n\ttry {\n\t\tconst parser = new PyReprParser(repr);\n\t\tconst value = parser.parseValue();\n\t\tparser.expectEnd();\n\t\treturn value;\n\t} catch {\n\t\treturn repr;\n\t}\n}\n\nclass PyReprParseError extends Error {}\n\n/** Minimal recursive-descent parser for a subset of Python literal syntax. */\nclass PyReprParser {\n\tprivate i = 0;\n\tconstructor(private readonly src: string) {}\n\n\tprivate skipWs(): void {\n\t\twhile (this.i < this.src.length && /\\s/.test(this.src[this.i])) this.i++;\n\t}\n\n\texpectEnd(): void {\n\t\tthis.skipWs();\n\t\tif (this.i !== this.src.length) {\n\t\t\tthrow new PyReprParseError(`unexpected trailing characters at ${this.i}`);\n\t\t}\n\t}\n\n\tparseValue(): unknown {\n\t\tthis.skipWs();\n\t\tconst c = this.src[this.i];\n\t\tif (c === undefined) throw new PyReprParseError(\"unexpected end of repr\");\n\t\tif (c === \"'\" || c === '\"') return this.parseString();\n\t\tif (c === \"[\") return this.parseSequence(\"[\", \"]\");\n\t\tif (c === \"(\") return this.parseSequence(\"(\", \")\");\n\t\tif (c === \"{\") return this.parseDictOrSet();\n\t\tif (this.src.startsWith(\"None\", this.i)) {\n\t\t\tthis.i += 4;\n\t\t\treturn null;\n\t\t}\n\t\tif (this.src.startsWith(\"True\", this.i)) {\n\t\t\tthis.i += 4;\n\t\t\treturn true;\n\t\t}\n\t\tif (this.src.startsWith(\"False\", this.i)) {\n\t\t\tthis.i += 5;\n\t\t\treturn false;\n\t\t}\n\t\treturn this.parseNumber();\n\t}\n\n\tprivate parseString(): string {\n\t\tconst quote = this.src[this.i];\n\t\tthis.i++;\n\t\tlet out = \"\";\n\t\twhile (this.i < this.src.length && this.src[this.i] !== quote) {\n\t\t\tconst ch = this.src[this.i];\n\t\t\tif (ch === \"\\\\\") {\n\t\t\t\tthis.i++;\n\t\t\t\tconst esc = this.src[this.i];\n\t\t\t\tswitch (esc) {\n\t\t\t\t\tcase \"n\":\n\t\t\t\t\t\tout += \"\\n\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"t\":\n\t\t\t\t\t\tout += \"\\t\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"r\":\n\t\t\t\t\t\tout += \"\\r\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"b\":\n\t\t\t\t\t\tout += \"\\b\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"f\":\n\t\t\t\t\t\tout += \"\\f\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"v\":\n\t\t\t\t\t\tout += \"\\v\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"0\":\n\t\t\t\t\t\tout += \"\\0\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"\\\\\":\n\t\t\t\t\t\tout += \"\\\\\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"'\":\n\t\t\t\t\t\tout += \"'\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase '\"':\n\t\t\t\t\t\tout += '\"';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"x\": {\n\t\t\t\t\t\tout += String.fromCharCode(Number.parseInt(this.src.slice(this.i + 1, this.i + 3), 16));\n\t\t\t\t\t\tthis.i += 2;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"u\": {\n\t\t\t\t\t\tout += String.fromCharCode(Number.parseInt(this.src.slice(this.i + 1, this.i + 5), 16));\n\t\t\t\t\t\tthis.i += 4;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"U\": {\n\t\t\t\t\t\tout += String.fromCodePoint(Number.parseInt(this.src.slice(this.i + 1, this.i + 9), 16));\n\t\t\t\t\t\tthis.i += 8;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tout += esc ?? \"\";\n\t\t\t\t}\n\t\t\t\tthis.i++;\n\t\t\t} else {\n\t\t\t\tout += ch;\n\t\t\t\tthis.i++;\n\t\t\t}\n\t\t}\n\t\tif (this.src[this.i] !== quote) throw new PyReprParseError(\"unterminated string literal\");\n\t\tthis.i++;\n\t\treturn out;\n\t}\n\n\tprivate parseNumber(): number {\n\t\tconst start = this.i;\n\t\tif (this.src[this.i] === \"-\" || this.src[this.i] === \"+\") this.i++;\n\t\twhile (this.i < this.src.length && /[0-9]/.test(this.src[this.i])) this.i++;\n\t\tif (this.src[this.i] === \".\") {\n\t\t\tthis.i++;\n\t\t\twhile (this.i < this.src.length && /[0-9]/.test(this.src[this.i])) this.i++;\n\t\t}\n\t\tif (this.src[this.i] === \"e\" || this.src[this.i] === \"E\") {\n\t\t\tthis.i++;\n\t\t\tif (this.src[this.i] === \"-\" || this.src[this.i] === \"+\") this.i++;\n\t\t\twhile (this.i < this.src.length && /[0-9]/.test(this.src[this.i])) this.i++;\n\t\t}\n\t\tconst numStr = this.src.slice(start, this.i);\n\t\tif (!/^[+-]?[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?$/.test(numStr)) {\n\t\t\tthrow new PyReprParseError(`invalid number literal at ${start}`);\n\t\t}\n\t\treturn Number(numStr);\n\t}\n\n\t/** Parses `[...]` (list) or `(...)` (tuple) into a JS array. */\n\tprivate parseSequence(_open: string, close: string): unknown[] {\n\t\tthis.i++; // opening bracket\n\t\tconst out: unknown[] = [];\n\t\tthis.skipWs();\n\t\tif (this.src[this.i] === close) {\n\t\t\tthis.i++;\n\t\t\treturn out;\n\t\t}\n\t\twhile (true) {\n\t\t\tout.push(this.parseValue());\n\t\t\tthis.skipWs();\n\t\t\tif (this.src[this.i] === \",\") {\n\t\t\t\tthis.i++;\n\t\t\t\tthis.skipWs();\n\t\t\t\tif (this.src[this.i] === close) {\n\t\t\t\t\tthis.i++;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (this.src[this.i] === close) {\n\t\t\t\tthis.i++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tthrow new PyReprParseError(`expected ',' or '${close}' at ${this.i}`);\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Parses `{...}` as a dict (if any `key: value` pair is found) or a set (mapped to an array). */\n\tprivate parseDictOrSet(): unknown {\n\t\tthis.i++; // {\n\t\tthis.skipWs();\n\t\tif (this.src[this.i] === \"}\") {\n\t\t\tthis.i++;\n\t\t\treturn {};\n\t\t}\n\t\tconst firstKeyOrValue = this.parseValue();\n\t\tthis.skipWs();\n\t\tif (this.src[this.i] === \":\") {\n\t\t\tthis.i++;\n\t\t\tconst out: Record<string, unknown> = {};\n\t\t\tout[String(firstKeyOrValue)] = this.parseValue();\n\t\t\tthis.skipWs();\n\t\t\twhile (this.src[this.i] === \",\") {\n\t\t\t\tthis.i++;\n\t\t\t\tthis.skipWs();\n\t\t\t\tif (this.src[this.i] === \"}\") break;\n\t\t\t\tconst key = this.parseValue();\n\t\t\t\tthis.skipWs();\n\t\t\t\tif (this.src[this.i] !== \":\") throw new PyReprParseError(`expected ':' at ${this.i}`);\n\t\t\t\tthis.i++;\n\t\t\t\tout[String(key)] = this.parseValue();\n\t\t\t\tthis.skipWs();\n\t\t\t}\n\t\t\tif (this.src[this.i] !== \"}\") throw new PyReprParseError(\"expected '}' to close dict\");\n\t\t\tthis.i++;\n\t\t\treturn out;\n\t\t}\n\t\tconst out: unknown[] = [firstKeyOrValue];\n\t\tthis.skipWs();\n\t\twhile (this.src[this.i] === \",\") {\n\t\t\tthis.i++;\n\t\t\tthis.skipWs();\n\t\t\tif (this.src[this.i] === \"}\") break;\n\t\t\tout.push(this.parseValue());\n\t\t\tthis.skipWs();\n\t\t}\n\t\tif (this.src[this.i] !== \"}\") throw new PyReprParseError(\"expected '}' to close set\");\n\t\tthis.i++;\n\t\treturn out;\n\t}\n}\n\ninterface PendingWaiter {\n\tresolve: (message: DriverMessage) => void;\n\treject: (error: Error) => void;\n}\n\nexport class RlmSession {\n\tprivate readonly child: ChildProcess;\n\tprivate readonly workdir: string;\n\tprivate readonly stdoutTruncateChars: number;\n\tprivate readonly maxIterations: number;\n\tprivate readonly stepTimeoutMs: number;\n\tprivate readonly maxRssBytes: number;\n\tprivate readonly rssPollIntervalMs: number;\n\tprivate readonly maxSubCalls: number | undefined;\n\tprivate readonly maxTotalCostUsd: number | undefined;\n\tprivate readonly getAccumulatedCostUsd: (() => number) | undefined;\n\tprivate readonly history: RlmHistoryEntry[] = [];\n\tprivate readonly ready: Promise<void>;\n\n\tprivate buffer = \"\";\n\tprivate messageQueue: DriverMessage[] = [];\n\tprivate waiters: PendingWaiter[] = [];\n\tprivate terminated = false;\n\tprivate terminationError: Error | null = null;\n\tprivate lastFinal: DriverFinalPayload | null = null;\n\tprivate subCallCount = 0;\n\tprivate rssPollTimer: NodeJS.Timeout | undefined;\n\tprivate rssPollInFlight = false;\n\n\tconstructor(private readonly opts: RlmSessionOptions) {\n\t\tthis.stdoutTruncateChars = opts.stdoutTruncateChars ?? DEFAULT_STDOUT_TRUNCATE_CHARS;\n\t\tthis.maxIterations = opts.maxIterations ?? DEFAULT_MAX_ITERATIONS;\n\t\tthis.stepTimeoutMs = opts.stepTimeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;\n\t\tthis.maxRssBytes = opts.maxRssBytes ?? DEFAULT_MAX_RSS_BYTES;\n\t\tthis.rssPollIntervalMs = opts.rssPollIntervalMs ?? DEFAULT_RSS_POLL_INTERVAL_MS;\n\t\tthis.maxSubCalls = opts.maxSubCalls;\n\t\tthis.maxTotalCostUsd = opts.maxTotalCostUsd;\n\t\tthis.getAccumulatedCostUsd = opts.getAccumulatedCostUsd;\n\n\t\t// The session's scratch workdir -- the only place the OS-sandboxed\n\t\t// driver process may read *and* write freely (see sandbox.ts /\n\t\t// ../sandbox/macos.sb). Created fresh per session and cleaned up in\n\t\t// dispose(). Not caller-configurable (yet): Phase 28 scopes the\n\t\t// sandbox to this session-owned directory, not an arbitrary\n\t\t// caller-supplied path.\n\t\tthis.workdir = mkdtempSync(join(tmpdir(), \"rlm-session-\"));\n\n\t\t// spawnSandboxed wraps python3/repl_driver.py in the platform's\n\t\t// OS-level sandbox (sandbox-exec on macOS, unshare/bwrap on Linux) --\n\t\t// see sandbox.ts's module docstring for why this, and not\n\t\t// repl_driver.py's Python-level guardrails, is the real security\n\t\t// boundary. It throws SandboxUnavailableError synchronously (never\n\t\t// returns a fallback unwrapped command) if the sandbox can't be\n\t\t// established, which is exactly what should happen here: this\n\t\t// constructor must fail closed, not silently spawn the driver\n\t\t// unsandboxed.\n\t\tthis.child = spawnSandboxed({ driverPath: DRIVER_PATH, workdir: this.workdir });\n\n\t\tthis.child.stdout?.setEncoding(\"utf8\");\n\t\tthis.child.stdout?.on(\"data\", (chunk: string) => this.onStdoutChunk(chunk));\n\n\t\tthis.child.stderr?.setEncoding(\"utf8\");\n\t\tthis.child.stderr?.on(\"data\", (chunk: string) => {\n\t\t\t// Driver-side crashes (as opposed to in-REPL exceptions, which are\n\t\t\t// reported via exec_result.error) surface here. Don't throw on\n\t\t\t// stray stderr output alone -- just make it visible for debugging.\n\t\t\tconsole.error(`[rlm repl_driver stderr] ${chunk}`);\n\t\t});\n\n\t\tthis.child.once(\"exit\", (code, signal) => {\n\t\t\tthis.markTerminated(new Error(`RlmSession: python3 driver exited (code=${code}, signal=${signal})`));\n\t\t});\n\t\tthis.child.once(\"error\", (err) => {\n\t\t\tthis.markTerminated(new Error(`RlmSession: failed to spawn python3 driver: ${err.message}`));\n\t\t});\n\n\t\t// Node-side RSS polling (Phase 28 Architecture section 3) -- the real\n\t\t// memory enforcement mechanism, running for the whole lifetime of the\n\t\t// child process, independent of any single step's wall-clock timeout.\n\t\tthis.startRssPolling();\n\n\t\t// Tell the driver how aggressively to cap captured stdout *before* any\n\t\t// real step runs (see repl_driver.py's `configure` handling / Phase 28\n\t\t// Architecture section 5) -- fire-and-forget: writes to child.stdin are\n\t\t// delivered strictly in order, so this is guaranteed to be processed\n\t\t// before the `self_test`/`exec` messages sent next.\n\t\tthis.send({ type: \"configure\", stdoutTruncateChars: this.stdoutTruncateChars });\n\n\t\t// Runtime startup self-test (Phase 28 Architecture section 1) -- runs\n\t\t// before `context` is even seeded, let alone before any root-LLM-\n\t\t// authored `exec`. If the OS sandbox isn't actually enforcing for this\n\t\t// process, `runSelfTest()` throws and `seedContext()` never runs, so no\n\t\t// step ever executes against an unconfined driver. See\n\t\t// `RlmSandboxViolationError`'s doc comment for how this differs from\n\t\t// `spawnSandboxed`'s synchronous fail-closed throw.\n\t\tthis.ready = this.runSelfTest().then(() => this.seedContext());\n\t\t// Attach a no-op rejection handler so Node doesn't report this as an\n\t\t// unhandled rejection when the failure path is exercised without every\n\t\t// caller immediately awaiting `run()`/`step()` -- `await this.ready`\n\t\t// elsewhere still observes the same rejection (a promise notifies every\n\t\t// attached reaction, not just the first).\n\t\tthis.ready.catch(() => {});\n\t}\n\n\tprivate onStdoutChunk(chunk: string): void {\n\t\tthis.buffer += chunk;\n\t\tlet newlineIndex = this.buffer.indexOf(\"\\n\");\n\t\twhile (newlineIndex !== -1) {\n\t\t\tconst line = this.buffer.slice(0, newlineIndex);\n\t\t\tthis.buffer = this.buffer.slice(newlineIndex + 1);\n\t\t\tnewlineIndex = this.buffer.indexOf(\"\\n\");\n\t\t\tif (!line.trim()) continue;\n\t\t\tlet message: DriverMessage;\n\t\t\ttry {\n\t\t\t\tmessage = JSON.parse(line) as DriverMessage;\n\t\t\t} catch {\n\t\t\t\tthis.markTerminated(new Error(`RlmSession: driver emitted non-JSON output: ${line}`));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst waiter = this.waiters.shift();\n\t\t\tif (waiter) waiter.resolve(message);\n\t\t\telse this.messageQueue.push(message);\n\t\t}\n\t}\n\n\tprivate markTerminated(error: Error): void {\n\t\tif (this.terminated) return;\n\t\tthis.terminated = true;\n\t\tthis.terminationError = error;\n\t\tif (this.rssPollTimer) {\n\t\t\tclearInterval(this.rssPollTimer);\n\t\t\tthis.rssPollTimer = undefined;\n\t\t}\n\t\tconst waiters = this.waiters;\n\t\tthis.waiters = [];\n\t\tfor (const waiter of waiters) waiter.reject(error);\n\t}\n\n\t/**\n\t * Hard-kills the driver subprocess and marks the session terminated with\n\t * `error` in one step. Used by every resource-limit enforcement path (RSS\n\t * ceiling, wall-clock timeout, budget exhaustion) -- these are Node-side\n\t * hard kills, not attempts at graceful in-process recovery (Phase 28\n\t * Architecture sections 3 and 6). `markTerminated`'s first-write-wins\n\t * guard means whichever of these fires first supplies the message callers\n\t * actually see, even though the child's own `exit` handler will also fire\n\t * shortly after and try to (harmlessly, redundantly) mark terminated too.\n\t */\n\tprivate killAndTerminate(error: Error): void {\n\t\tthis.markTerminated(error);\n\t\tthis.child.kill(\"SIGKILL\");\n\t}\n\n\t/**\n\t * Starts the Node-side RSS poll loop (Phase 28 Architecture section 3) --\n\t * the real memory enforcement mechanism, not any Python-level\n\t * `RLIMIT_AS`. Runs for the child process's whole lifetime; `unref()`d so\n\t * it never by itself keeps the host Node process alive past everything\n\t * else finishing. A `maxRssBytes <= 0` disables polling entirely.\n\t */\n\tprivate startRssPolling(): void {\n\t\tif (!(this.maxRssBytes > 0)) return;\n\t\tconst timer = setInterval(() => {\n\t\t\tvoid this.pollRss();\n\t\t}, this.rssPollIntervalMs);\n\t\ttimer.unref();\n\t\tthis.rssPollTimer = timer;\n\t}\n\n\tprivate async pollRss(): Promise<void> {\n\t\tif (this.terminated || this.rssPollInFlight) return;\n\t\tconst pid = this.child.pid;\n\t\tif (!pid) return;\n\t\tthis.rssPollInFlight = true;\n\t\ttry {\n\t\t\tconst rssBytes = await readRssBytes(pid);\n\t\t\tif (rssBytes === null || rssBytes <= this.maxRssBytes) return;\n\t\t\tthis.killAndTerminate(\n\t\t\t\tnew Error(\n\t\t\t\t\t`RlmSession: python3 driver exceeded the configured RSS ceiling of ${this.maxRssBytes} bytes ` +\n\t\t\t\t\t\t`(observed ${rssBytes} bytes) -- killed. This is a resource-limit kill (Phase 28 Architecture ` +\n\t\t\t\t\t\t\"section 3), not the OS sandbox itself failing.\",\n\t\t\t\t),\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.rssPollInFlight = false;\n\t\t}\n\t}\n\n\t/**\n\t * Pre-dispatch sub-call budget check (Phase 28 Architecture section 6,\n\t * R28-SBX.5): called *before* an `llm_query_request` is ever forwarded to\n\t * `opts.llmQuery`, so the (budget+1)th attempted sub-call never actually\n\t * dispatches -- the session is killed right here instead. Deliberately\n\t * NOT also checked before dispatching a whole step: a step that never\n\t * attempts another sub-call can still legitimately finish (e.g. call\n\t * FINAL immediately) even after the budget is otherwise exhausted, and\n\t * pre-emptively killing such a step here would be wrong.\n\t */\n\tprivate checkSubCallBudget(): void {\n\t\tif (this.maxSubCalls === undefined) return;\n\t\tif (this.subCallCount < this.maxSubCalls) return;\n\t\tconst error = new RlmBudgetExhaustedError(\n\t\t\t`RlmSession: llm_query sub-call budget exhausted (maxSubCalls=${this.maxSubCalls}) -- refusing to ` +\n\t\t\t\t`dispatch sub-call #${this.subCallCount + 1} and killing the driver subprocess.`,\n\t\t);\n\t\tthis.killAndTerminate(error);\n\t\tthrow error;\n\t}\n\n\t/**\n\t * Pre-dispatch cost budget check (Phase 28 Architecture section 6,\n\t * R28-SBX.5): called before dispatching a step's `rootLlm` turn and\n\t * before forwarding a sub-call to `opts.llmQuery`. No-ops unless both\n\t * `maxTotalCostUsd` and `getAccumulatedCostUsd` are supplied -- see\n\t * `RlmSessionOptions.maxTotalCostUsd`'s doc comment for why an unpaired\n\t * option is a no-op rather than a guess.\n\t */\n\tprivate checkCostBudget(): void {\n\t\tif (this.maxTotalCostUsd === undefined || !this.getAccumulatedCostUsd) return;\n\t\tconst accumulated = this.getAccumulatedCostUsd();\n\t\tif (accumulated < this.maxTotalCostUsd) return;\n\t\tconst error = new RlmBudgetExhaustedError(\n\t\t\t`RlmSession: cost budget exhausted (maxTotalCostUsd=$${this.maxTotalCostUsd}, accumulated=$${accumulated}) ` +\n\t\t\t\t\"-- killing the driver subprocess.\",\n\t\t);\n\t\tthis.killAndTerminate(error);\n\t\tthrow error;\n\t}\n\n\t/**\n\t * Races `promise` against the configured wall-clock step timeout\n\t * (Phase 28 Architecture section 3, R28-SBX.3) -- the real timeout\n\t * enforcement mechanism. On expiry, hard-kills the driver subprocess and\n\t * rejects with `RlmStepTimeoutError`, even if `promise` itself is stuck\n\t * awaiting something that `markTerminated`'s waiter-rejection can't reach\n\t * (e.g. a slow/hung `opts.llmQuery` callback, as opposed to a pending\n\t * `nextMessage()` wait) -- the explicit `reject` here is what actually\n\t * unblocks the caller in that case, not just a formality.\n\t */\n\tprivate withStepTimeout<T>(promise: Promise<T>): Promise<T> {\n\t\tif (!(this.stepTimeoutMs > 0)) return promise;\n\t\treturn new Promise<T>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tconst error = new RlmStepTimeoutError(\n\t\t\t\t\t`RlmSession: step exceeded the configured wall-clock timeout of ${this.stepTimeoutMs}ms -- killed.`,\n\t\t\t\t);\n\t\t\t\tthis.killAndTerminate(error);\n\t\t\t\treject(error);\n\t\t\t}, this.stepTimeoutMs);\n\t\t\ttimer.unref();\n\t\t\tpromise.then(\n\t\t\t\t(value) => {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tresolve(value);\n\t\t\t\t},\n\t\t\t\t(err) => {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\treject(err);\n\t\t\t\t},\n\t\t\t);\n\t\t});\n\t}\n\n\tprivate nextMessage(): Promise<DriverMessage> {\n\t\tconst queued = this.messageQueue.shift();\n\t\tif (queued) return Promise.resolve(queued);\n\t\tif (this.terminated) {\n\t\t\treturn Promise.reject(this.terminationError ?? new Error(\"RlmSession: driver process is no longer running\"));\n\t\t}\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.waiters.push({ resolve, reject });\n\t\t});\n\t}\n\n\tprivate send(message: Record<string, unknown>): void {\n\t\tif (this.terminated) {\n\t\t\tthrow this.terminationError ?? new Error(\"RlmSession: cannot send to a terminated driver process\");\n\t\t}\n\t\tthis.child.stdin?.write(`${JSON.stringify(message)}\\n`);\n\t}\n\n\t/**\n\t * Sends one `exec` message and handles any `llm_query_request` round-trips\n\t * until the matching `exec_result`, all under the wall-clock step timeout\n\t * (`withStepTimeout`, Phase 28 Architecture section 3).\n\t */\n\tprivate async runExec(code: string): Promise<DriverExecResult> {\n\t\tthis.send({ type: \"exec\", code });\n\t\treturn this.withStepTimeout(this.collectExecResult());\n\t}\n\n\t/** The message loop `runExec` races against the step timeout -- split out so `withStepTimeout` can wrap just this. */\n\tprivate async collectExecResult(): Promise<DriverExecResult> {\n\t\twhile (true) {\n\t\t\tconst message = await this.nextMessage();\n\t\t\tif (message.type === \"llm_query_request\") {\n\t\t\t\tconst id = message.id as string;\n\t\t\t\tif (!this.opts.llmQuery) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\"RlmSession: REPL code called llm_query(...) but no `llmQuery` callback was provided in RlmSessionOptions.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// Budget pre-checks (Phase 28 Architecture section 6, R28-SBX.5) --\n\t\t\t\t// before this sub-call is dispatched, not after: either check can\n\t\t\t\t// throw (and kill the driver), in which case opts.llmQuery is never\n\t\t\t\t// called for this attempt at all.\n\t\t\t\tthis.checkSubCallBudget();\n\t\t\t\tthis.checkCostBudget();\n\t\t\t\tthis.subCallCount++;\n\t\t\t\tconst text = await this.opts.llmQuery(message.prompt as string);\n\t\t\t\tthis.send({ type: \"llm_query_response\", id, text });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (message.type === \"exec_result\") {\n\t\t\t\treturn message as DriverExecResult;\n\t\t\t}\n\t\t\tthrow new Error(`RlmSession: unexpected message from driver: ${JSON.stringify(message)}`);\n\t\t}\n\t}\n\n\tprivate async seedContext(): Promise<void> {\n\t\t// JSON string escaping and Python double-quoted string literal escaping\n\t\t// agree on the escapes that matter here (\\\", \\\\, \\n, \\t, \\r, and\n\t\t// \\uXXXX), so JSON.stringify produces a valid Python string literal.\n\t\tawait this.runExec(`context = ${JSON.stringify(this.opts.prompt)}`);\n\t}\n\n\t/**\n\t * Runtime proof the OS-level sandbox is actually enforcing for *this*\n\t * process (Phase 28 Architecture section 1) -- sends `{\"type\":\n\t * \"self_test\"}` and requires the driver's `self_test_result` response to\n\t * report both a network connect attempt and an out-of-workdir file read\n\t * failing. Throws `RlmSandboxViolationError` (fail-closed, refusing to run\n\t * at all) if:\n\t * - the driver never responds (crash/hang -- guarded by the same\n\t * wall-clock timeout as a normal step, via `withStepTimeout`),\n\t * - the response isn't a well-formed `self_test_result`, or\n\t * - either check reports the sandbox did NOT block the attempt.\n\t *\n\t * Deliberately does not go through `runExec`/the guarded `exec()` wire\n\t * message: `repl_driver.py`'s `_run_self_test` handles `self_test`\n\t * on a route that bypasses its own Python-level guardrails (restricted\n\t * builtins, import allowlist, AST screen) entirely, because a self-test\n\t * *routed through* those guardrails would be a false positive -- e.g.\n\t * `import socket` failing on the guardrail import allowlist looks\n\t * identical to `import socket` failing because the OS sandbox denied the\n\t * connection, even with the OS sandbox completely disabled. See\n\t * `_run_self_test`'s docstring in repl_driver.py for the full reasoning.\n\t */\n\tprivate async runSelfTest(): Promise<void> {\n\t\tthis.send({ type: \"self_test\" });\n\t\tlet message: DriverMessage;\n\t\ttry {\n\t\t\tmessage = await this.withStepTimeout(this.nextMessage());\n\t\t} catch (err) {\n\t\t\tthrow new RlmSandboxViolationError(\n\t\t\t\t\"RlmSession: OS-level sandbox startup self-test did not complete \" +\n\t\t\t\t\t`(refusing to run -- fail-closed): ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t);\n\t\t}\n\t\tconst networkBlocked = message.type === \"self_test_result\" && message.networkBlocked === true;\n\t\tconst fileReadBlocked = message.type === \"self_test_result\" && message.fileReadBlocked === true;\n\t\tif (!networkBlocked || !fileReadBlocked) {\n\t\t\tconst error = new RlmSandboxViolationError(\n\t\t\t\t\"RlmSession: OS-level sandbox startup self-test failed -- the sandbox is not confining this \" +\n\t\t\t\t\t`process (networkBlocked=${networkBlocked}, fileReadBlocked=${fileReadBlocked}, ` +\n\t\t\t\t\t`response=${JSON.stringify(message)}). Refusing to run rather than executing unconfined ` +\n\t\t\t\t\t\"root-LLM-authored code (fail-closed).\",\n\t\t\t);\n\t\t\tthis.killAndTerminate(error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate resolveFinal(final: DriverFinalPayload): unknown {\n\t\tif (final.kind === \"value\") {\n\t\t\t// FINAL(answer) already sent `str(answer)` -- use as-is.\n\t\t\treturn final.value;\n\t\t}\n\t\t// FINAL_VAR(name) sent `repr(globals_dict[name])`. Parse it back into a\n\t\t// real JS value for JSON-safe types; fall back to the raw repr string\n\t\t// for anything else. See `pythonReprToValue`'s doc comment for why.\n\t\treturn pythonReprToValue(final.value);\n\t}\n\n\t/** One root-LLM turn: get code, exec it, append history, may resolve a final. */\n\tasync step(): Promise<RlmHistoryEntry> {\n\t\tawait this.ready;\n\t\t// Pre-dispatch cost budget check (Phase 28 Architecture section 6,\n\t\t// R28-SBX.5) -- before the root LLM is even asked for this step's code,\n\t\t// not just before a sub-call within it.\n\t\tthis.checkCostBudget();\n\t\tconst response = await this.opts.rootLlm(this.history.slice());\n\t\tconst code = extractPythonCode(response);\n\t\tconst result = await this.runExec(code);\n\t\tconst entry: RlmHistoryEntry = {\n\t\t\tstep: this.history.length + 1,\n\t\t\tcode,\n\t\t\t// Already capped+marked incrementally by the driver's\n\t\t\t// `_TruncatingStdout` (Phase 28 Architecture section 5) -- no\n\t\t\t// second Node-side truncation pass here (Phase 26's original\n\t\t\t// post-hoc `truncateStdout` call is exactly the OOM-via-print gap\n\t\t\t// this phase closes; re-applying it here on top of the driver's\n\t\t\t// own `[truncated N chars]` marker would risk mangling it).\n\t\t\ttruncatedStdout: result.stdout,\n\t\t\terror: result.error ?? null,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t};\n\t\tthis.history.push(entry);\n\t\tthis.lastFinal = result.final ?? null;\n\t\treturn entry;\n\t}\n\n\t/** Loops `step()` until a FINAL/FINAL_VAR, `maxIterations`, or an unrecoverable driver error. */\n\tasync run(): Promise<RlmResult> {\n\t\ttry {\n\t\t\twhile (this.history.length < this.maxIterations) {\n\t\t\t\tawait this.step();\n\t\t\t\tif (this.lastFinal) {\n\t\t\t\t\tconst final = this.lastFinal;\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkind: final.kind === \"var\" ? \"final_var\" : \"final\",\n\t\t\t\t\t\tvalue: this.resolveFinal(final),\n\t\t\t\t\t\tsteps: this.history.length,\n\t\t\t\t\t\thistory: this.history.slice(),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { kind: \"max_iterations\", steps: this.history.length, history: this.history.slice() };\n\t\t} catch (err) {\n\t\t\t// Typed stop reasons (Phase 28 Architecture section 6, R28-SBX.6):\n\t\t\t// a hard-killed step surfaces here as one of these internal error\n\t\t\t// classes; anything else (a genuine driver crash, a step\n\t\t\t// referencing llm_query with no callback configured, etc.) falls\n\t\t\t// through to the generic \"error\" kind, same as Phase 26.\n\t\t\tif (err instanceof RlmStepTimeoutError) {\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"timeout\",\n\t\t\t\t\tvalue: err.message,\n\t\t\t\t\tsteps: this.history.length,\n\t\t\t\t\thistory: this.history.slice(),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (err instanceof RlmBudgetExhaustedError) {\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"budget_exhausted\",\n\t\t\t\t\tvalue: err.message,\n\t\t\t\t\tsteps: this.history.length,\n\t\t\t\t\thistory: this.history.slice(),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (err instanceof RlmSandboxViolationError) {\n\t\t\t\treturn {\n\t\t\t\t\tkind: \"sandbox_violation\",\n\t\t\t\t\tvalue: err.message,\n\t\t\t\t\tsteps: this.history.length,\n\t\t\t\t\thistory: this.history.slice(),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tkind: \"error\",\n\t\t\t\tvalue: err instanceof Error ? err.message : String(err),\n\t\t\t\tsteps: this.history.length,\n\t\t\t\thistory: this.history.slice(),\n\t\t\t};\n\t\t}\n\t}\n\n\t/** Kills the underlying python3 subprocess and removes its scratch workdir. Safe to call more than once. */\n\tdispose(): void {\n\t\tthis.markTerminated(new Error(\"RlmSession: disposed\"));\n\t\tthis.child.stdin?.end();\n\t\tthis.child.kill();\n\t\ttry {\n\t\t\trmSync(this.workdir, { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best-effort cleanup only -- a leftover empty-ish tmp dir isn't\n\t\t\t// worth failing dispose() over.\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Trajectory JSONL logging for RLM sessions.
3
+ *
4
+ * Every `RlmSession` created via `createRouterBackedSession` (router-session.ts,
5
+ * Phase 27) already generates one `trajectoryId` (`randomUUID()`) shared with
6
+ * every cost entry it logs via `@draht/router`'s `estimateCost`/`logCost`.
7
+ * This module gives that same `trajectoryId` a second, complementary log: a
8
+ * newline-delimited JSON file under `.draht/rlm/<trajectoryId>.jsonl`
9
+ * recording one `TrajectoryStepEntry` per root-LLM step (plus the sub-calls
10
+ * that step triggered) and one terminal `TrajectoryFinalEntry` once the
11
+ * session resolves. Together with `readTrajectory`, this is what
12
+ * `draht rlm replay <trajectory-id>` (Phase 30, task 4) reconstructs an
13
+ * answer from, with zero LLM calls.
14
+ *
15
+ * See .planning/phases/30-eval-observability-docs/30-01-PLAN.md, Architecture
16
+ * section 1.
17
+ */
18
+ import type { RlmResultKind } from "./types.js";
19
+ /** One root-LLM step recorded in a trajectory's JSONL log. */
20
+ export interface TrajectoryStepEntry {
21
+ type: "step";
22
+ trajectoryId: string;
23
+ /** 1-indexed step number -- matches `RlmHistoryEntry.step`. */
24
+ step: number;
25
+ /** The Python source executed this step (post fence-extraction). */
26
+ code: string;
27
+ /** Captured stdout, already truncated -- matches `RlmHistoryEntry.truncatedStdout`. */
28
+ truncatedStdout: string;
29
+ /** Traceback/message if the executed code raised, else null. */
30
+ error: string | null;
31
+ /** Sub-LLM (`llm_query`) calls made *during* this step -- not attributed to any other step. */
32
+ subCalls: Array<{
33
+ costUsd: number;
34
+ provider: string;
35
+ model: string;
36
+ }>;
37
+ /** This step's own root-call cost plus the sum of its `subCalls`' costs. */
38
+ costUsd: number;
39
+ /** ISO 8601 timestamp -- matches `RlmHistoryEntry.timestamp`. */
40
+ timestamp: string;
41
+ }
42
+ /** The terminal entry appended once a trajectory's `RlmSession.run()` resolves. */
43
+ export interface TrajectoryFinalEntry {
44
+ type: "final";
45
+ trajectoryId: string;
46
+ kind: RlmResultKind;
47
+ value: unknown;
48
+ /** Sum of every step's `costUsd` in this trajectory. */
49
+ totalCostUsd: number;
50
+ totalSteps: number;
51
+ timestamp: string;
52
+ }
53
+ /**
54
+ * Appends one entry (step or final) to `trajectoryId`'s JSONL log, creating
55
+ * `logDir` (default `.draht/rlm/`) if it doesn't exist yet. Each call is one
56
+ * `appendFileSync` of a single JSON-encoded line -- callers are responsible
57
+ * for calling this once per step and once for the final entry, in order.
58
+ */
59
+ export declare function appendTrajectoryEntry(trajectoryId: string, entry: TrajectoryStepEntry | TrajectoryFinalEntry, logDir?: string): void;
60
+ /**
61
+ * Reads back `trajectoryId`'s JSONL log, splitting it into its step entries
62
+ * (in file order) and its final entry (`null` if the trajectory never
63
+ * resolved -- e.g. the process was killed mid-run). Throws a clear error
64
+ * (rather than a bare ENOENT) when no log file exists for `trajectoryId` at
65
+ * `logDir`, so callers like `draht rlm replay` can surface it directly.
66
+ */
67
+ export declare function readTrajectory(trajectoryId: string, logDir?: string): {
68
+ steps: TrajectoryStepEntry[];
69
+ final: TrajectoryFinalEntry | null;
70
+ };
71
+ //# sourceMappingURL=trajectory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trajectory.d.ts","sourceRoot":"","sources":["../src/trajectory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKhD,8DAA8D;AAC9D,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,eAAe,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,+FAA+F;IAC/F,QAAQ,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,mFAAmF;AACnF,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,aAAa,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,wDAAwD;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CAClB;AAOD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACpC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,mBAAmB,GAAG,oBAAoB,EACjD,MAAM,CAAC,EAAE,MAAM,GACb,IAAI,CAIN;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC7B,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,MAAM,GACb;IAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAAA;CAAE,CAkBtE"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Trajectory JSONL logging for RLM sessions.
3
+ *
4
+ * Every `RlmSession` created via `createRouterBackedSession` (router-session.ts,
5
+ * Phase 27) already generates one `trajectoryId` (`randomUUID()`) shared with
6
+ * every cost entry it logs via `@draht/router`'s `estimateCost`/`logCost`.
7
+ * This module gives that same `trajectoryId` a second, complementary log: a
8
+ * newline-delimited JSON file under `.draht/rlm/<trajectoryId>.jsonl`
9
+ * recording one `TrajectoryStepEntry` per root-LLM step (plus the sub-calls
10
+ * that step triggered) and one terminal `TrajectoryFinalEntry` once the
11
+ * session resolves. Together with `readTrajectory`, this is what
12
+ * `draht rlm replay <trajectory-id>` (Phase 30, task 4) reconstructs an
13
+ * answer from, with zero LLM calls.
14
+ *
15
+ * See .planning/phases/30-eval-observability-docs/30-01-PLAN.md, Architecture
16
+ * section 1.
17
+ */
18
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
19
+ import { join, resolve } from "node:path";
20
+ /** Default directory trajectory JSONL files are written under/read from, relative to `process.cwd()`. */
21
+ const DEFAULT_LOG_DIR = ".draht/rlm";
22
+ /** Resolves the on-disk path of a trajectory's JSONL log file. */
23
+ function trajectoryFilePath(trajectoryId, logDir) {
24
+ return join(resolve(logDir ?? DEFAULT_LOG_DIR), `${trajectoryId}.jsonl`);
25
+ }
26
+ /**
27
+ * Appends one entry (step or final) to `trajectoryId`'s JSONL log, creating
28
+ * `logDir` (default `.draht/rlm/`) if it doesn't exist yet. Each call is one
29
+ * `appendFileSync` of a single JSON-encoded line -- callers are responsible
30
+ * for calling this once per step and once for the final entry, in order.
31
+ */
32
+ export function appendTrajectoryEntry(trajectoryId, entry, logDir) {
33
+ const dir = resolve(logDir ?? DEFAULT_LOG_DIR);
34
+ mkdirSync(dir, { recursive: true });
35
+ appendFileSync(join(dir, `${trajectoryId}.jsonl`), `${JSON.stringify(entry)}\n`, "utf-8");
36
+ }
37
+ /**
38
+ * Reads back `trajectoryId`'s JSONL log, splitting it into its step entries
39
+ * (in file order) and its final entry (`null` if the trajectory never
40
+ * resolved -- e.g. the process was killed mid-run). Throws a clear error
41
+ * (rather than a bare ENOENT) when no log file exists for `trajectoryId` at
42
+ * `logDir`, so callers like `draht rlm replay` can surface it directly.
43
+ */
44
+ export function readTrajectory(trajectoryId, logDir) {
45
+ const filePath = trajectoryFilePath(trajectoryId, logDir);
46
+ if (!existsSync(filePath)) {
47
+ throw new Error(`readTrajectory: no trajectory log found for id "${trajectoryId}" at ${filePath}`);
48
+ }
49
+ const steps = [];
50
+ let final = null;
51
+ const content = readFileSync(filePath, "utf-8");
52
+ for (const line of content.split("\n")) {
53
+ if (!line.trim())
54
+ continue;
55
+ const entry = JSON.parse(line);
56
+ if (entry.type === "step") {
57
+ steps.push(entry);
58
+ }
59
+ else if (entry.type === "final") {
60
+ final = entry;
61
+ }
62
+ }
63
+ return { steps, final };
64
+ }
65
+ //# sourceMappingURL=trajectory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trajectory.js","sourceRoot":"","sources":["../src/trajectory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAG1C,yGAAyG;AACzG,MAAM,eAAe,GAAG,YAAY,CAAC;AAkCrC,kEAAkE;AAClE,SAAS,kBAAkB,CAAC,YAAoB,EAAE,MAA0B;IAC3E,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,eAAe,CAAC,EAAE,GAAG,YAAY,QAAQ,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACpC,YAAoB,EACpB,KAAiD,EACjD,MAAe;IAEf,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC;IAC/C,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,QAAQ,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC3F,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC7B,YAAoB,EACpB,MAAe;IAEf,MAAM,QAAQ,GAAG,kBAAkB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC1D,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,mDAAmD,YAAY,QAAQ,QAAQ,EAAE,CAAC,CAAC;IACpG,CAAC;IACD,MAAM,KAAK,GAA0B,EAAE,CAAC;IACxC,IAAI,KAAK,GAAgC,IAAI,CAAC;IAC9C,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,SAAS;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA+C,CAAC;QAC7E,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACnC,KAAK,GAAG,KAAK,CAAC;QACf,CAAC;IACF,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACzB,CAAC","sourcesContent":["/**\n * Trajectory JSONL logging for RLM sessions.\n *\n * Every `RlmSession` created via `createRouterBackedSession` (router-session.ts,\n * Phase 27) already generates one `trajectoryId` (`randomUUID()`) shared with\n * every cost entry it logs via `@draht/router`'s `estimateCost`/`logCost`.\n * This module gives that same `trajectoryId` a second, complementary log: a\n * newline-delimited JSON file under `.draht/rlm/<trajectoryId>.jsonl`\n * recording one `TrajectoryStepEntry` per root-LLM step (plus the sub-calls\n * that step triggered) and one terminal `TrajectoryFinalEntry` once the\n * session resolves. Together with `readTrajectory`, this is what\n * `draht rlm replay <trajectory-id>` (Phase 30, task 4) reconstructs an\n * answer from, with zero LLM calls.\n *\n * See .planning/phases/30-eval-observability-docs/30-01-PLAN.md, Architecture\n * section 1.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport type { RlmResultKind } from \"./types.js\";\n\n/** Default directory trajectory JSONL files are written under/read from, relative to `process.cwd()`. */\nconst DEFAULT_LOG_DIR = \".draht/rlm\";\n\n/** One root-LLM step recorded in a trajectory's JSONL log. */\nexport interface TrajectoryStepEntry {\n\ttype: \"step\";\n\ttrajectoryId: string;\n\t/** 1-indexed step number -- matches `RlmHistoryEntry.step`. */\n\tstep: number;\n\t/** The Python source executed this step (post fence-extraction). */\n\tcode: string;\n\t/** Captured stdout, already truncated -- matches `RlmHistoryEntry.truncatedStdout`. */\n\ttruncatedStdout: string;\n\t/** Traceback/message if the executed code raised, else null. */\n\terror: string | null;\n\t/** Sub-LLM (`llm_query`) calls made *during* this step -- not attributed to any other step. */\n\tsubCalls: Array<{ costUsd: number; provider: string; model: string }>;\n\t/** This step's own root-call cost plus the sum of its `subCalls`' costs. */\n\tcostUsd: number;\n\t/** ISO 8601 timestamp -- matches `RlmHistoryEntry.timestamp`. */\n\ttimestamp: string;\n}\n\n/** The terminal entry appended once a trajectory's `RlmSession.run()` resolves. */\nexport interface TrajectoryFinalEntry {\n\ttype: \"final\";\n\ttrajectoryId: string;\n\tkind: RlmResultKind;\n\tvalue: unknown;\n\t/** Sum of every step's `costUsd` in this trajectory. */\n\ttotalCostUsd: number;\n\ttotalSteps: number;\n\ttimestamp: string;\n}\n\n/** Resolves the on-disk path of a trajectory's JSONL log file. */\nfunction trajectoryFilePath(trajectoryId: string, logDir: string | undefined): string {\n\treturn join(resolve(logDir ?? DEFAULT_LOG_DIR), `${trajectoryId}.jsonl`);\n}\n\n/**\n * Appends one entry (step or final) to `trajectoryId`'s JSONL log, creating\n * `logDir` (default `.draht/rlm/`) if it doesn't exist yet. Each call is one\n * `appendFileSync` of a single JSON-encoded line -- callers are responsible\n * for calling this once per step and once for the final entry, in order.\n */\nexport function appendTrajectoryEntry(\n\ttrajectoryId: string,\n\tentry: TrajectoryStepEntry | TrajectoryFinalEntry,\n\tlogDir?: string,\n): void {\n\tconst dir = resolve(logDir ?? DEFAULT_LOG_DIR);\n\tmkdirSync(dir, { recursive: true });\n\tappendFileSync(join(dir, `${trajectoryId}.jsonl`), `${JSON.stringify(entry)}\\n`, \"utf-8\");\n}\n\n/**\n * Reads back `trajectoryId`'s JSONL log, splitting it into its step entries\n * (in file order) and its final entry (`null` if the trajectory never\n * resolved -- e.g. the process was killed mid-run). Throws a clear error\n * (rather than a bare ENOENT) when no log file exists for `trajectoryId` at\n * `logDir`, so callers like `draht rlm replay` can surface it directly.\n */\nexport function readTrajectory(\n\ttrajectoryId: string,\n\tlogDir?: string,\n): { steps: TrajectoryStepEntry[]; final: TrajectoryFinalEntry | null } {\n\tconst filePath = trajectoryFilePath(trajectoryId, logDir);\n\tif (!existsSync(filePath)) {\n\t\tthrow new Error(`readTrajectory: no trajectory log found for id \"${trajectoryId}\" at ${filePath}`);\n\t}\n\tconst steps: TrajectoryStepEntry[] = [];\n\tlet final: TrajectoryFinalEntry | null = null;\n\tconst content = readFileSync(filePath, \"utf-8\");\n\tfor (const line of content.split(\"\\n\")) {\n\t\tif (!line.trim()) continue;\n\t\tconst entry = JSON.parse(line) as TrajectoryStepEntry | TrajectoryFinalEntry;\n\t\tif (entry.type === \"step\") {\n\t\t\tsteps.push(entry);\n\t\t} else if (entry.type === \"final\") {\n\t\t\tfinal = entry;\n\t\t}\n\t}\n\treturn { steps, final };\n}\n"]}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Types for `@draht/rlm`'s root loop (`RlmSession`).
3
+ *
4
+ * See .planning/phases/26-rlm-core-primitives/26-01-PLAN.md, Architecture
5
+ * section 4, for the authoritative spec this mirrors.
6
+ */
7
+ /**
8
+ * One root-LLM turn recorded in the session's history.
9
+ *
10
+ * Deliberately constant-size (R26-RLM.7): the full `context` string is never
11
+ * appended here, only short metadata about what happened in this step. This
12
+ * is what gets fed back into `rootLlm` on the next turn, so it must stay
13
+ * small regardless of how large `context` or the REPL's real output is.
14
+ */
15
+ export interface RlmHistoryEntry {
16
+ /** 1-indexed step number. */
17
+ step: number;
18
+ /** The Python source that was executed this step (post fence-extraction). */
19
+ code: string;
20
+ /**
21
+ * Captured stdout, truncated to `stdoutTruncateChars` with a marker.
22
+ * Enforced incrementally on the Python side (`repl_driver.py`'s
23
+ * `_TruncatingStdout`, Phase 28 Architecture section 5) -- the value here
24
+ * already carries the cap and marker; `RlmSession` no longer re-truncates
25
+ * it Node-side (Phase 26's original design did, post-hoc, which is exactly
26
+ * the OOM-via-print gap Phase 28 closes).
27
+ */
28
+ truncatedStdout: string;
29
+ /** Traceback/message if the executed code raised, else null. */
30
+ error: string | null;
31
+ /** ISO 8601 timestamp of when this step's exec_result was received. */
32
+ timestamp: string;
33
+ }
34
+ /**
35
+ * Why `RlmSession.run()` stopped. Per R28-SBX.6:
36
+ * - `timeout`: the Node-side wall-clock per-step timeout fired and the
37
+ * driver subprocess was hard-killed (Phase 28 Architecture section 3).
38
+ * - `budget_exhausted`: a `maxSubCalls`/`maxTotalCostUsd` pre-check failed
39
+ * before dispatch (Phase 28 Architecture section 6).
40
+ * - `sandbox_violation`: the OS-level sandbox itself failed to establish or
41
+ * its runtime startup self-test didn't confirm both network and
42
+ * out-of-workdir file access are actually denied (Phase 28 Architecture
43
+ * section 1) -- this means "the sandbox itself intervened/failed", never
44
+ * "resource exhaustion" (see `error` below for that).
45
+ * - `error` also covers an RSS-ceiling kill (Phase 28 Architecture section
46
+ * 3) -- R28-SBX.6's enum is fixed and has no dedicated value for OOM, and
47
+ * it deliberately isn't folded into `sandbox_violation` -- see the `value`
48
+ * message for which ceiling was hit.
49
+ */
50
+ export type RlmResultKind = "final" | "final_var" | "max_iterations" | "budget_exhausted" | "timeout" | "sandbox_violation" | "error";
51
+ /** Outcome of a full `RlmSession.run()` loop. */
52
+ export interface RlmResult {
53
+ kind: RlmResultKind;
54
+ /**
55
+ * The resolved final value. Present for "final" and "final_var". See
56
+ * `pythonReprToValue` in `session.ts` for how a `FINAL_VAR` repr string is
57
+ * turned back into this.
58
+ */
59
+ value?: unknown;
60
+ /** Number of `step()` calls executed before stopping. */
61
+ steps: number;
62
+ /** Full step-by-step history accumulated during the run. */
63
+ history: RlmHistoryEntry[];
64
+ }
65
+ export interface RlmSessionOptions {
66
+ /** Becomes the `context` variable inside the REPL. */
67
+ prompt: string;
68
+ /**
69
+ * Called each `step()` with history-so-far; must return a response
70
+ * containing Python source, optionally fenced in ```repl or ```python.
71
+ */
72
+ rootLlm: (history: RlmHistoryEntry[]) => Promise<string>;
73
+ /**
74
+ * Answers `llm_query(prompt)` calls made from inside the REPL. Injectable
75
+ * so Phase 26 tests (and later, non-production callers) can mock it;
76
+ * production wiring through `@draht/router` lands in Phase 27.
77
+ */
78
+ llmQuery?: (prompt: string) => Promise<string>;
79
+ /**
80
+ * Soft cap on the number of `step()` iterations `run()` will perform
81
+ * before giving up with `{ kind: "max_iterations" }`. Not a hard safety
82
+ * limit — that's Phase 28's job.
83
+ */
84
+ maxIterations?: number;
85
+ /**
86
+ * Max chars of stdout kept per step before truncation. Default 2000.
87
+ * Sent to the driver as a `configure` message at session startup and
88
+ * enforced incrementally there (Phase 28 Architecture section 5) -- not
89
+ * applied a second time Node-side.
90
+ */
91
+ stdoutTruncateChars?: number;
92
+ /**
93
+ * Node-side wall-clock timeout (ms) for one full step round trip (from
94
+ * sending `exec` to receiving `exec_result`, including any `llm_query`
95
+ * sub-call round trips in between). This -- not any Python-level
96
+ * `RLIMIT_CPU`/timer -- is the real enforcement mechanism (Phase 28
97
+ * Architecture section 3, R28-SBX.3): exceeding it hard-kills the driver
98
+ * subprocess and `run()` resolves with `{ kind: "timeout" }`. The whole
99
+ * session ends -- there is no partial-step rollback for a hard kill (see
100
+ * `RlmHistoryEntry`/`repl_driver.py`'s rollback docs). Default 30_000.
101
+ */
102
+ stepTimeoutMs?: number;
103
+ /**
104
+ * RSS ceiling (bytes) for the driver subprocess, polled Node-side roughly
105
+ * every `rssPollIntervalMs` (Phase 28 Architecture section 3). Exceeding
106
+ * it hard-kills the subprocess; `run()` resolves with `{ kind: "error" }`
107
+ * (R28-SBX.6 has no dedicated enum value for OOM -- see `RlmResultKind`).
108
+ * Default 256 MiB (`256 * 1024 * 1024`).
109
+ */
110
+ maxRssBytes?: number;
111
+ /** How often (ms) to poll the driver subprocess's RSS. Default 250. */
112
+ rssPollIntervalMs?: number;
113
+ /**
114
+ * Hard cap on the total number of `llm_query` sub-calls this session will
115
+ * ever forward to `llmQuery`. Checked *before* each sub-call is
116
+ * dispatched (Phase 28 Architecture section 6, R28-SBX.5) -- the
117
+ * (budget+1)th attempted call never reaches `llmQuery`; the driver
118
+ * subprocess is killed instead and `run()` resolves with
119
+ * `{ kind: "budget_exhausted" }`. `undefined` (default) means unlimited.
120
+ */
121
+ maxSubCalls?: number;
122
+ /**
123
+ * Hard cap on accumulated cost (USD), compared against
124
+ * `getAccumulatedCostUsd()`'s return value before dispatching each step
125
+ * and before each sub-call (Phase 28 Architecture section 6). Has no
126
+ * effect unless `getAccumulatedCostUsd` is also supplied -- there being
127
+ * nothing to compare against, the check is skipped entirely rather than
128
+ * guessing "already exhausted" or "never exhausted".
129
+ */
130
+ maxTotalCostUsd?: number;
131
+ /**
132
+ * Returns the session's accumulated real-money cost so far (USD) -- e.g.
133
+ * `createRouterBackedSession` (Phase 27) can wire this to a running sum of
134
+ * its logged `CostEntry.estimatedCostUsd` values. `RlmSession` itself has
135
+ * no notion of "provider" or "cost"; it only ever compares this callback's
136
+ * return value against `maxTotalCostUsd`.
137
+ */
138
+ getAccumulatedCostUsd?: () => number;
139
+ }
140
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC/B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,aAAa,GACtB,OAAO,GACP,WAAW,GACX,gBAAgB,GAChB,kBAAkB,GAClB,SAAS,GACT,mBAAmB,GACnB,OAAO,CAAC;AAEX,iDAAiD;AACjD,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,OAAO,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IACjC,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,MAAM,MAAM,CAAC;CACrC"}
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Types for `@draht/rlm`'s root loop (`RlmSession`).
3
+ *
4
+ * See .planning/phases/26-rlm-core-primitives/26-01-PLAN.md, Architecture
5
+ * section 4, for the authoritative spec this mirrors.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * Types for `@draht/rlm`'s root loop (`RlmSession`).\n *\n * See .planning/phases/26-rlm-core-primitives/26-01-PLAN.md, Architecture\n * section 4, for the authoritative spec this mirrors.\n */\n\n/**\n * One root-LLM turn recorded in the session's history.\n *\n * Deliberately constant-size (R26-RLM.7): the full `context` string is never\n * appended here, only short metadata about what happened in this step. This\n * is what gets fed back into `rootLlm` on the next turn, so it must stay\n * small regardless of how large `context` or the REPL's real output is.\n */\nexport interface RlmHistoryEntry {\n\t/** 1-indexed step number. */\n\tstep: number;\n\t/** The Python source that was executed this step (post fence-extraction). */\n\tcode: string;\n\t/**\n\t * Captured stdout, truncated to `stdoutTruncateChars` with a marker.\n\t * Enforced incrementally on the Python side (`repl_driver.py`'s\n\t * `_TruncatingStdout`, Phase 28 Architecture section 5) -- the value here\n\t * already carries the cap and marker; `RlmSession` no longer re-truncates\n\t * it Node-side (Phase 26's original design did, post-hoc, which is exactly\n\t * the OOM-via-print gap Phase 28 closes).\n\t */\n\ttruncatedStdout: string;\n\t/** Traceback/message if the executed code raised, else null. */\n\terror: string | null;\n\t/** ISO 8601 timestamp of when this step's exec_result was received. */\n\ttimestamp: string;\n}\n\n/**\n * Why `RlmSession.run()` stopped. Per R28-SBX.6:\n * - `timeout`: the Node-side wall-clock per-step timeout fired and the\n * driver subprocess was hard-killed (Phase 28 Architecture section 3).\n * - `budget_exhausted`: a `maxSubCalls`/`maxTotalCostUsd` pre-check failed\n * before dispatch (Phase 28 Architecture section 6).\n * - `sandbox_violation`: the OS-level sandbox itself failed to establish or\n * its runtime startup self-test didn't confirm both network and\n * out-of-workdir file access are actually denied (Phase 28 Architecture\n * section 1) -- this means \"the sandbox itself intervened/failed\", never\n * \"resource exhaustion\" (see `error` below for that).\n * - `error` also covers an RSS-ceiling kill (Phase 28 Architecture section\n * 3) -- R28-SBX.6's enum is fixed and has no dedicated value for OOM, and\n * it deliberately isn't folded into `sandbox_violation` -- see the `value`\n * message for which ceiling was hit.\n */\nexport type RlmResultKind =\n\t| \"final\"\n\t| \"final_var\"\n\t| \"max_iterations\"\n\t| \"budget_exhausted\"\n\t| \"timeout\"\n\t| \"sandbox_violation\"\n\t| \"error\";\n\n/** Outcome of a full `RlmSession.run()` loop. */\nexport interface RlmResult {\n\tkind: RlmResultKind;\n\t/**\n\t * The resolved final value. Present for \"final\" and \"final_var\". See\n\t * `pythonReprToValue` in `session.ts` for how a `FINAL_VAR` repr string is\n\t * turned back into this.\n\t */\n\tvalue?: unknown;\n\t/** Number of `step()` calls executed before stopping. */\n\tsteps: number;\n\t/** Full step-by-step history accumulated during the run. */\n\thistory: RlmHistoryEntry[];\n}\n\nexport interface RlmSessionOptions {\n\t/** Becomes the `context` variable inside the REPL. */\n\tprompt: string;\n\t/**\n\t * Called each `step()` with history-so-far; must return a response\n\t * containing Python source, optionally fenced in ```repl or ```python.\n\t */\n\trootLlm: (history: RlmHistoryEntry[]) => Promise<string>;\n\t/**\n\t * Answers `llm_query(prompt)` calls made from inside the REPL. Injectable\n\t * so Phase 26 tests (and later, non-production callers) can mock it;\n\t * production wiring through `@draht/router` lands in Phase 27.\n\t */\n\tllmQuery?: (prompt: string) => Promise<string>;\n\t/**\n\t * Soft cap on the number of `step()` iterations `run()` will perform\n\t * before giving up with `{ kind: \"max_iterations\" }`. Not a hard safety\n\t * limit — that's Phase 28's job.\n\t */\n\tmaxIterations?: number;\n\t/**\n\t * Max chars of stdout kept per step before truncation. Default 2000.\n\t * Sent to the driver as a `configure` message at session startup and\n\t * enforced incrementally there (Phase 28 Architecture section 5) -- not\n\t * applied a second time Node-side.\n\t */\n\tstdoutTruncateChars?: number;\n\n\t/**\n\t * Node-side wall-clock timeout (ms) for one full step round trip (from\n\t * sending `exec` to receiving `exec_result`, including any `llm_query`\n\t * sub-call round trips in between). This -- not any Python-level\n\t * `RLIMIT_CPU`/timer -- is the real enforcement mechanism (Phase 28\n\t * Architecture section 3, R28-SBX.3): exceeding it hard-kills the driver\n\t * subprocess and `run()` resolves with `{ kind: \"timeout\" }`. The whole\n\t * session ends -- there is no partial-step rollback for a hard kill (see\n\t * `RlmHistoryEntry`/`repl_driver.py`'s rollback docs). Default 30_000.\n\t */\n\tstepTimeoutMs?: number;\n\t/**\n\t * RSS ceiling (bytes) for the driver subprocess, polled Node-side roughly\n\t * every `rssPollIntervalMs` (Phase 28 Architecture section 3). Exceeding\n\t * it hard-kills the subprocess; `run()` resolves with `{ kind: \"error\" }`\n\t * (R28-SBX.6 has no dedicated enum value for OOM -- see `RlmResultKind`).\n\t * Default 256 MiB (`256 * 1024 * 1024`).\n\t */\n\tmaxRssBytes?: number;\n\t/** How often (ms) to poll the driver subprocess's RSS. Default 250. */\n\trssPollIntervalMs?: number;\n\n\t/**\n\t * Hard cap on the total number of `llm_query` sub-calls this session will\n\t * ever forward to `llmQuery`. Checked *before* each sub-call is\n\t * dispatched (Phase 28 Architecture section 6, R28-SBX.5) -- the\n\t * (budget+1)th attempted call never reaches `llmQuery`; the driver\n\t * subprocess is killed instead and `run()` resolves with\n\t * `{ kind: \"budget_exhausted\" }`. `undefined` (default) means unlimited.\n\t */\n\tmaxSubCalls?: number;\n\t/**\n\t * Hard cap on accumulated cost (USD), compared against\n\t * `getAccumulatedCostUsd()`'s return value before dispatching each step\n\t * and before each sub-call (Phase 28 Architecture section 6). Has no\n\t * effect unless `getAccumulatedCostUsd` is also supplied -- there being\n\t * nothing to compare against, the check is skipped entirely rather than\n\t * guessing \"already exhausted\" or \"never exhausted\".\n\t */\n\tmaxTotalCostUsd?: number;\n\t/**\n\t * Returns the session's accumulated real-money cost so far (USD) -- e.g.\n\t * `createRouterBackedSession` (Phase 27) can wire this to a running sum of\n\t * its logged `CostEntry.estimatedCostUsd` values. `RlmSession` itself has\n\t * no notion of \"provider\" or \"cost\"; it only ever compares this callback's\n\t * return value against `maxTotalCostUsd`.\n\t */\n\tgetAccumulatedCostUsd?: () => number;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@draht/rlm",
3
+ "version": "2026.7.12-1",
4
+ "description": "Recursive Language Models — root-LLM-produces-code / REPL-executes / truncated-stdout scaffold",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "python",
11
+ "sandbox",
12
+ "README.md"
13
+ ],
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ }
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.build.json",
22
+ "test": "vitest run",
23
+ "clean": "rm -rf dist"
24
+ },
25
+ "dependencies": {
26
+ "@draht/ai": "2026.7.12-1",
27
+ "@draht/knowledge": "2026.7.12-1",
28
+ "@draht/router": "2026.7.12-1"
29
+ },
30
+ "devDependencies": {
31
+ "vitest": "^3.2.4",
32
+ "@types/node": "^22.10.5",
33
+ "typescript": "7.0.2"
34
+ }
35
+ }