Repository navigation
feat(provider): add IBM Bob, read from its database - #29
Open
gillesdandrea wants to merge 4 commits into
Open
gillesdandrea wants to merge 4 commits into
gillesdandrea wants to merge 4 commits into
Conversation
IBM Bob keeps every session as rows of one SQLite database rather than files: a `tasks` row per session and per child it spawns, a `messages` row per turn with the turn in a JSON blob. This provider reads those rows as JSON lines, one per row (`wire.rs`), so a session exported that way is a file the stream reads like any other transcript, the conformance fixture (`assets/bob/shell-2.0.3.jsonl`, a probe run on Bob Shell 2.0.3, harness text elided) is such a file, and the browser opens one. What the format needs the provider to know: a child's row is born at `created_at` and ended by its `status` at `updated_at`, presented as two observations (`seen`) so the fold never learns an ending at a birth; the join from a spawning call to its child is the child's id on the call's result (`_meta.subagentId`), with the child's `title`, which is the call's prompt, as the fallback for a result that lacks it, and an errored spawn joins nothing; a spawn's result carries the child's whole transcript, stated as the child's turns whose tokens the child's row already totals; a turn's time is `_meta.timestamp` inside its blob, never the row's `created_at`, which Bob restamps when it rewrites a session's rows; a `/skill` launch stores the skill's text as content and what was typed in `_meta.mask`; a tool result the harness marks `hide` is an error it keeps from the user, and still answers its call. The stream is checked against forty interleavings of the fixture's task rows with its turns, and the harness's snapshot now compares tokens, labels and agent types, not only structure.
The row feeder DISCOVERY.md §7 predicted for an agent that keeps a database. A root read as `ReadMode::Rows` names the database and the task; `provider/bob/db.rs` (native, rusqlite bundled, read-only opens) presents the task tree's rows as the provider's lines, and `tailer/rows.rs` polls it in place of the byte tailer. The reader keys messages by id and task rows by their state, since Bob deletes and re-inserts a session's rows when the task ends or is reopened (rowids reused, `created_at` restamped, order changed); it reads each poll in one transaction, detects change cheaply before fetching, walks the task tree to any depth, and lifts the transcript a spawn's result carries out as the child's own lines, each at its own time, so a replay shows a child working when it worked. `sweep` lists stored sessions through a new `stored_sessions` primitive, so `zoe <id>` and `zoe <dir>` find Bob sessions beside the others; a followed directory attaches to a Bob task that starts later, and re-attach and auto-switch go by id, since every stored session shares the database's path. `inspect` folds a stored session the same way. The portable core does not see rusqlite.
`zoe export <id|dir>` writes a Bob session's rows as the lines its provider reads, to stdout: the file the browser opens, and the shape the fixture has. A directory means its newest stored session. A session kept as files needs no export and is refused with its files listed.
README lists Bob among the supported agents and the export command; DISCOVERY.md moves the database class of §7 from out of scope to supported, with Bob's answers in the primitives table and the `stored_sessions` primitive; ARCHITECTURE.md §0 covers the row feeder and §7 records two soft spots it exposed (an ending cannot be retracted when Bob reopens a task; a tool end read before its start stays pending); DESIGN.md's module map and CLI cover the new files and the subcommand.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds IBM Bob (the IDE and the
bobshell) as a third provider.Bob is the first agent here that keeps no files: every session is rows of one SQLite database (
~/.bob/db/bob.db, WAL mode), atasksrow per session and per child it spawns, amessagesrow per turn. DISCOVERY.md §7 scoped that class out as "a different kind of feeder reading rows, not a widerSessionFile". This PR takes exactly that path.Shape
provider/bob/reads JSON lines, one per row (wire.rs), through the sameStream::pushas the others. An export of a session is a file the stream reads like any transcript, the conformance fixture (assets/bob/shell-2.0.3.jsonl, with its two goldens) is such a file, and the browser opens one.ReadMode::Rowsnames the database and the task.provider/bob/db.rs(native only) presents the task tree's rows as the provider's lines;tailer/rows.rspolls it in place of the byte tailer.sweeplists stored sessions through one new primitive,stored_sessions, sozoe <id>andzoe <dir>find Bob sessions beside Claude's and Codex's, a followed directory attaches to a Bob task that starts later, and re-attach and auto-switch go by id.zoe export <id|dir>writes a stored session as those lines: the file the browser reads.rusqlitewithbundled, optional behind thenativefeature; the portable core does not see it. Bundled rather than the system library so nothing is needed on the machine and the crate builds the same everywhere; opens are read-only, one read transaction per poll.What the format needed the provider to know
Verified against a real database (586 sessions) and three live probes, all recorded in the module docs:
created_atrestamped, order changed. So the reader keys messages byid, task rows by their state, and a turn's time is_meta.timestampinside its blob.created_atand ended bystatusatupdated_at, presented as two observations so the fold never learns an ending at a birth (§1.2). The spawn's result names the child (_meta.subagentId) and carries its whole transcript; the reader lifts that out as the child's own lines, each at its own time./skilllaunch stores the skill's text as the user turn and what was typed in_meta.mask; a tool result the harness markshideis an error kept from the user, and still answers its call.Checks
CI on the fork is green on every job, Windows and Linux included: gillesdandrea#1. Beyond
cargo fmt,clippy,test, the wasm frontend lint and the MSRV build, the stream is folded under forty interleavings of the fixture's task rows with its turns, andharness::snapshotnow compares tokens, labels and agent types.Not in this PR
The web site copy, a demo recording, and the Herdr plugin (Bob runs inside its IDE, not a pane). Two soft spots the format exposed are recorded in ARCHITECTURE.md §7 rather than fixed: an ending cannot be retracted when Bob reopens a task, and a tool end read before its start stays pending.
This touches the same seams as #25 and #26 (
Providerarms, the spawn mark, usage text); happy to rebase over whichever lands first.