summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-06-10 10:06:07 -0600
committert <t@tjp.lol>2026-06-10 12:17:27 -0600
commitbb85e867bc938138f29fb45230169af1ba60761c (patch)
tree5ad1cf50274c61ccfffab7048b4a4e28003d82b5 /docs
parentd26890bbc52e9f0050db40f208763a7f2b714ddd (diff)
Add addUserText helper; update all call sites
Conversation.addUserMessage now takes a []ContentBlock (symmetric with addAssistantMessage). Introduce a thin addUserText wrapper in agent.zig for the plain-text case and update every call site in agent.zig and anthropic_messages_json.zig accordingly.
Diffstat (limited to 'docs')
-rw-r--r--docs/step19-cli-panto-module-plan.md144
1 files changed, 144 insertions, 0 deletions
diff --git a/docs/step19-cli-panto-module-plan.md b/docs/step19-cli-panto-module-plan.md
new file mode 100644
index 0000000..f162403
--- /dev/null
+++ b/docs/step19-cli-panto-module-plan.md
@@ -0,0 +1,144 @@
+# Step 19 plan: CLI consumes `libpanto-lua` as a Lua module (Option A)
+
+Goal: inside the panto CLI's embedded Lua VM, `require('panto')` returns the
+**native** `libpanto-lua` agent/stream table **plus** `panto.ext`. The CLI
+obtains that table only through Lua's normal module machinery (`require`) — it
+never calls `luaopen_panto` directly and knows nothing of the module's
+internals. The single invariant relied upon: `luaopen_panto` returns a fresh
+table (already true).
+
+## Mechanism
+
+1. **Embed `panto.so` in the CLI binary and stage it at bootstrap** into the
+ versioned rocks tree's C-module dir (`<tree>/lib/lua/5.4/panto.so`), which
+ is already on `package.cpath` (see `configurePackagePaths`). The staged
+ `.so` leaves `lua_*` undefined and resolves them against the host binary at
+ `dlopen` time — identical to how `luv.so` already works (`exe.rdynamic =
+ true`).
+
+2. **After luarocks bootstrap configures cpath**, the CLI:
+ ```lua
+ local panto = require('panto') -- finds staged panto.so
+ panto.ext = <ext table built in Zig> -- augment host's fresh copy
+ package.preload['panto'] = function() return panto end -- any later require gets ext too
+ ```
+ `require` caches the augmented table in `package.loaded['panto']`, and the
+ preload entry (searcher slot 1) makes any *future* `require('panto')` — e.g.
+ from an extension — return the same augmented table without re-hitting cpath.
+
+## File-by-file changes
+
+### `build.zig` (CLI)
+- Add `libpanto-lua` as a path dependency (`build.zig.zon`).
+- Get its compiled shared object via `dep.artifact("panto").getEmittedBin()`.
+- New codegen step `gen_panto_so_embed.zig`: emit a Zig module that
+ `@embedFile`s the `.so` bytes (binary embed is fine). Mirror
+ `generateLuaHeadersEmbed`. Materialize the `.so` next to the generated file
+ via `addCopyFile` so `@embedFile` can reach it.
+- Add the embed module import to `exe_mod` and `test_mod` as
+ `embedded_panto_so`.
+
+### new `src/embedded_panto_so` import
+- A single `pub const bytes: []const u8 = @embedFile("panto.so");` style module
+ (generated). Name: `embedded_panto_so`.
+
+### `src/luarocks_runtime.zig`
+- New step `stagePantoModule`: write `embedded_panto_so.bytes` to
+ `<lib_lua_dir>/panto.so` via `writeIfDifferent`. Call it from `bootstrap`
+ after the tree dirs exist (alongside `stageLuaHeaders`).
+- `lib_lua_dir` must exist (create if missing) before writing.
+
+### `src/lua_bridge.zig`
+- **Stop building the native `panto` table and stop installing the preload
+ loader.** `install()` becomes "create the registrations tables + build the
+ `ext` table, stash `ext` in a registry slot" — it no longer fabricates a
+ `panto` table or touches `package.preload`.
+- Keep all `ext` thunks (`register_tool`/`register_command`/`on`/`emit`) and
+ the registrations-table machinery unchanged.
+- `pushPantoTable` is repurposed (or replaced by `pushExtTable`): internal Zig
+ code that today adds members to `panto` (`installEmit`, `_record_result`,
+ wrapper closure) must target the **augmented native** table after it is
+ obtained. Decision below.
+
+### `src/lua_runtime.zig`
+- `create()` no longer relies on a Zig-built `panto` table. Split startup:
+ - `create()` opens the state, `openlibs`, builds the `ext` table + regs
+ tables (via the slimmed `lua_bridge.install`), creates the EventBridge.
+ **Defer** `installEmit` until the native table exists.
+ - New `installPantoModule(self)`: run the `require('panto')` + attach-`ext` +
+ set-`preload` sequence in Zig (a `luaL_dostring`-style snippet plus stack
+ surgery), then stash the augmented table in the registry slot so
+ `pushPantoTable` keeps working, then `installEmit`.
+- `installScheduler` already runs after bootstrap; `installPantoModule` runs
+ between bootstrap and `installScheduler` (it needs cpath configured; the
+ scheduler's `_record_result`/wrapper need the table present).
+
+### `src/main.zig`
+- After `luarocks_runtime.bootstrap(...)` returns (cpath configured, `panto.so`
+ staged) and before `rt.installScheduler()`, call `try rt.installPantoModule()`.
+
+## Sequencing (the hazard)
+
+Today, ordering is: `create` (builds table + preload + installEmit) →
+bootstrap → installScheduler (adds `_record_result`, wrapper, caches uv.run).
+
+New ordering:
+1. `create` — state, libs, `ext` + regs tables, EventBridge. **No table, no
+ preload, no emit yet.**
+2. `bootstrap` — stages `panto.so`, configures cpath/path, installs luv.
+3. `installPantoModule` — `require('panto')`, attach `ext`, set preload, stash
+ in registry slot, `installEmit`.
+4. `installScheduler` — `_record_result` + wrapper closure (both reach the now-
+ augmented native table via the registry slot) + cache uv.run.
+5. extension loading — `require('panto')` returns the augmented table.
+
+Everything that previously assumed "the `panto` table exists from `create`"
+moves to step 3. The only consumers are `installEmit` and the scheduler, both
+already post-bootstrap-or-movable.
+
+## Open decision: registry slot semantics
+
+`lua_bridge.panto_table_key` currently holds the Zig-built table. Two choices:
+
+- **(chosen)** Keep the slot; in `installPantoModule`, after building the
+ augmented table, `lua_rawsetp` it into `panto_table_key`. `pushPantoTable`
+ is unchanged and now yields the native+ext table. `installEmit` /
+ `_record_result` / wrapper all keep using `pushPantoTable`. Minimal churn.
+- Alternative: split into `pushExtTable` (for `ext` members) vs `pushPantoTable`
+ (whole module). More precise but more edits. Not needed — `ext` lives on the
+ module table, so one slot pointing at the module suffices.
+
+## Tests to update / add
+
+- `lua_bridge.zig` tests assume `install` builds a `panto` table reachable via
+ `require('panto')`/`pushPantoTable`. These must change: either keep a tiny
+ test-only table builder, or rewrite the tests to drive `ext` directly. Since
+ `ext` is what those tests actually exercise (register_tool/command/on), point
+ them at the `ext` table via a test helper that fabricates a minimal module
+ table `{ ext = <ext> }` and stashes it — preserving coverage without the
+ native `.so`.
+- `lua_runtime.zig` tests call `LuaRuntime.create` and `loadExtension` without
+ bootstrap, so `require('panto')` would fail there (no staged `.so`, no
+ preload). Add a test-only `installTestPantoTable(self)` that fabricates the
+ `{ ext = <ext> }` module table and registers the preload loader, mirroring
+ what the old `create` did — so the runtime tests keep passing without a real
+ native module. `writeTempScript` already prepends `require('panto')`.
+- New integration check (best-effort, may be manual): in a built CLI,
+ `require('panto')` yields a table with both `agent` (native) and `ext`.
+
+## Risks
+
+- **Binary size**: embedding `panto.so` (~5.6 MB debug) bloats the CLI binary.
+ ReleaseFast/strip will shrink it; acceptable (luv and the agent tree are also
+ embedded). Confirm size is tolerable; consider stripping the `.so` in the
+ module's ReleaseSafe/Fast build.
+- **Two Lua builds must be ABI-identical**: the CLI's statically-linked Lua
+ (5.4.7) and the headers `panto.so` was compiled against (5.4.7, same pin).
+ Both come from the same `lua_src` tarball hash — consistent by construction.
+- **`dep.artifact("panto")`**: `libpanto-lua/build.zig` currently emits the
+ `.so` through `addLibrary` + a manual install-file rename. `artifact("panto")`
+ returns the `addLibrary` Compile step (named `panto`), whose `getEmittedBin()`
+ is `libpanto.so`/`libpanto.dylib`. Embedding by bytes is name-agnostic; we
+ stage it as `panto.so`. Verify the dependency exposes the artifact (it must be
+ `b.installArtifact`'d or at least addressable; may need a small tweak to
+ `libpanto-lua/build.zig` to expose it cleanly to dependents).