Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
132 commits
Select commit Hold shift + click to select a range
73a6585
docs(plans): ADR-007, ADR-008, ADR-009 — the cache redesign, as one set
paddymul Sep 20, 2026
077e308
docs(plans): fold design-session answers 7-10 into the cache redesign…
paddymul Sep 20, 2026
20d330d
docs(plans): fold the PR #184 review into the cache redesign ADRs
paddymul Sep 20, 2026
f777d06
docs(plans): fold the second PR #184 review into the cache redesign ADRs
paddymul Sep 21, 2026
f9bb94b
test(cache): failing tests for ADR-007, ADR-008 and ADR-009
paddymul Sep 21, 2026
5105def
feat(core): re-entrant project lock, manifest fields, reset leaves co…
paddymul Sep 21, 2026
4f544ff
feat(xorq): tallyman-owned materialization, row order and content digest
paddymul Sep 21, 2026
74372b1
feat(companion): hand Buckaroo files that exist, page by __row_order,…
paddymul Sep 21, 2026
b5856bb
feat(app): show pinned and orphan snapshots on the Cache page
paddymul Sep 21, 2026
82652b4
docs(contract): system contract for tallyman-owned materialization
paddymul Sep 21, 2026
2dce104
fix(xorq): an entry with no manifest still serves its page
paddymul Sep 21, 2026
58a1ecb
test: update existing tests for the two deliberate build rules
paddymul Sep 21, 2026
04d68a0
docs(mcp): staleness sweep reports absent snapshots and writes nothing
paddymul Sep 21, 2026
95454bc
docs: compute_cache and bullpen docstrings match ADR-007 D14
paddymul Sep 21, 2026
1ddf83e
docs: describe the implemented cache design
paddymul Sep 21, 2026
e1b8ff7
docs(plans): ADR-004 to ADR-009 status lines and implementation notes
paddymul Sep 21, 2026
283c483
test: rework the tests of mechanisms the redesign retires
paddymul Sep 21, 2026
d7466bb
test: an ordered copy is made again for an entry whose manifest does …
paddymul Sep 21, 2026
8bbd36b
fix: recreate an ordered copy from any manifest that records it; repa…
paddymul Sep 21, 2026
119c3ce
test: the page-load profiler runs against a corpus this branch builds
paddymul Sep 21, 2026
c594898
docs(plans): ADR-010 immutable result store, every entry materialized…
paddymul Sep 21, 2026
7e6bdf8
docs(plans): ADR-010 resolves the row hash, Buckaroo's route, the sto…
paddymul Sep 21, 2026
34f7138
docs(plans): ADR-010 rejected
paddymul Sep 22, 2026
605a677
ci: run the test workflow for PRs into feat/** branches
paddymul Sep 22, 2026
bb28f80
docs(plans): ADR-011 a raw input is an alias, files enter only by imp…
paddymul Sep 22, 2026
c72d8bf
test: a file enters the catalog only by an explicit import (ADR-011 s…
paddymul Sep 22, 2026
ebfb971
feat: a file enters the catalog only by an explicit import (ADR-011 s…
paddymul Sep 22, 2026
c492b20
docs(plans): ADR-011 status — accepted, stage 1 implemented
paddymul Sep 22, 2026
1621c95
test: pyarrow writes a source snapshot, and a deleted one comes back …
paddymul Sep 22, 2026
d65222b
test: the two writer tests read the snapshot back with pyarrow (ADR-0…
paddymul Sep 22, 2026
1d40db0
test: a CSV source snapshot comes back under its recorded reader (ADR…
paddymul Sep 22, 2026
2b86727
feat: pyarrow writes a source snapshot, in the pinned layout (ADR-011…
paddymul Sep 22, 2026
532f0f1
feat: a source snapshot is cache, re-created from the clone (ADR-011 D1)
paddymul Sep 22, 2026
ebe0702
docs(plans): ADR-011 D1 — the snapshot is pyarrow's, and it is cache
paddymul Sep 22, 2026
2d01e6f
test: staleness has one axis, identity has one mode (ADR-011 D6, D8)
paddymul Sep 23, 2026
2f8350a
test: an import names its own project, whichever one is active
paddymul Sep 23, 2026
ec4f86e
feat: one staleness axis, one identity mode, no raw file reads (ADR-0…
paddymul Sep 23, 2026
502c7a5
fix: an import names its own project, whichever one is active (ADR-011)
paddymul Sep 23, 2026
a803ba7
test: the last four files author imports, not raw reads (ADR-011 D2)
paddymul Sep 23, 2026
75381c5
docs(plans): ADR-002, ADR-005 and ADR-008 amended for ADR-011
paddymul Sep 23, 2026
e028e15
test: two projects importing one file share nothing (ADR-011)
paddymul Sep 24, 2026
d8b3c69
test: identical bytes under a second alias are refused (ADR-011)
paddymul Sep 24, 2026
c7a0339
fix: importing bytes another alias already holds is an error (ADR-011)
paddymul Sep 24, 2026
57e3881
test: an alias's kind matches the entries it points at (ADR-011)
paddymul Sep 24, 2026
048a051
test: a source version is named by the alias that holds it now (ADR-011)
paddymul Sep 24, 2026
da53ca7
fix: an alias's kind matches the entries it points at (ADR-011)
paddymul Sep 24, 2026
6aefd0f
fix: a source version is named by the alias that holds it now (ADR-011)
paddymul Sep 24, 2026
45c177a
Merge remote-tracking branch 'origin/fix/adr-011-alias-kind-matches-e…
paddymul Sep 24, 2026
f48ccc8
Merge remote-tracking branch 'origin/fix/adr-011-source-names-follow-…
paddymul Sep 24, 2026
dbde141
test: a repair import heals, CSV advice carries its reader, companion…
paddymul Sep 24, 2026
37567b2
test: the append-only refusal does not advise a second alias (ADR-011)
paddymul Sep 24, 2026
d3ef749
fix: a repair import heals, advice carries the reader, companion rout…
paddymul Sep 24, 2026
9f46fc8
docs: ADR-011 and the MCP reference describe the #219 fixes
paddymul Sep 24, 2026
968d502
Merge pull request #219 from buckaroo-data/fix/adr-011-duplicate-import
paddymul Sep 24, 2026
10a0c72
Merge pull request #218 from buckaroo-data/feat/adr-011-stage-2
paddymul Sep 24, 2026
8530876
Merge pull request #217 from buckaroo-data/feat/adr-011-sources-are-a…
paddymul Sep 24, 2026
5b64ac9
test: a failed build keeps the snapshot already on disk for its hash
paddymul Sep 24, 2026
8f600c3
test: a pin survives a reset and a dismissed error banner (#194, #195…
paddymul Sep 24, 2026
09894e5
fix: a reset keeps the live entry dir over an older parked copy (#194)
paddymul Sep 24, 2026
9b4ca11
fix: skip a line of errors.jsonl that is not a JSON object (#196)
paddymul Sep 24, 2026
03f2b69
fix: the pin is a fact of the manifest, and a retired entry's file ke…
paddymul Sep 24, 2026
a3795e6
fix(xorq): a failed build keeps the snapshot already on disk for its …
paddymul Sep 24, 2026
9452001
Merge pull request #222 from buckaroo-data/fix/193-failed-build-keeps…
paddymul Sep 24, 2026
6829da0
Merge branch 'feat/adr-007-009-cache-redesign' into fix/194-196-pins-…
paddymul Sep 24, 2026
1f8cb02
Merge pull request #223 from buckaroo-data/fix/194-196-pins-adr011
paddymul Sep 24, 2026
375388c
docs: describe the system as built with ADR-007, ADR-008 and ADR-009
paddymul Sep 22, 2026
2e061c0
docs(plans): ADR-007 to ADR-009 accepted; supersession notes on ADR-0…
paddymul Sep 22, 2026
44761c3
docs(plans): status notes where a plan describes behaviour that has c…
paddymul Sep 22, 2026
5b6c163
docs: describe the system as built with ADR-011 (sources are aliases)
paddymul Sep 24, 2026
2650674
docs(plans): status notes for ADR-011 on the plans this PR annotated
paddymul Sep 24, 2026
357f0a2
docs: #193 to #196 are fixed; describe the staged publish and the man…
paddymul Sep 24, 2026
f98bd07
docs(plans): ADR headers state the design as built, without merge his…
paddymul Sep 24, 2026
a61943b
docs: coherence pass — contradictions with the code, leftover mechani…
paddymul Sep 24, 2026
71f9264
docs: architecture-new.md, the system as built after ADR-011
paddymul Sep 24, 2026
cf446c6
docs: architecture-new.md names two more defects with no issue
paddymul Sep 24, 2026
5ce41b7
docs: open bug list for 2026-09-24, and known defects cite #224-#240
paddymul Sep 24, 2026
e6f33b7
test(#118): failing tests — executions on the shared backend must be …
paddymul Sep 24, 2026
d125d5a
test: a diff leaves out __row_order and buckets rows by side, not by …
paddymul Sep 24, 2026
9243c9b
test: a zone on offset-less CSV text is attached, not converted (#231)
paddymul Sep 24, 2026
d45bee8
test: a directory with no manifest is refused by every read, not serv…
paddymul Sep 24, 2026
49a9c75
fix(#118): executions on the shared backend run one at a time per pro…
paddymul Sep 24, 2026
b284b6d
test: what a failed import says, records and leaves behind
paddymul Sep 24, 2026
c3d7f8b
fix(cache): a read refuses a directory with no manifest instead of gu…
paddymul Sep 24, 2026
71a93c2
fix(csv): a zone on offset-less CSV text is attached, not converted (…
paddymul Sep 24, 2026
4441c68
fix(diff): a diff leaves out __row_order and buckets rows by side mar…
paddymul Sep 24, 2026
0f06cbc
fix(#118): only executions on the shared default backend hold the lock
paddymul Sep 24, 2026
e6447b9
fix(import): refuse a parquet column xorq has no type for, naming it,…
paddymul Sep 24, 2026
b8acc06
fix(import): a failed import is recorded, names the user's file and l…
paddymul Sep 24, 2026
e41fb32
test(#118): failing tests — the key search's budget counts only its o…
paddymul Sep 24, 2026
21ccbba
fix(#118): the key search's budget counts only its own queries' run time
paddymul Sep 24, 2026
7093520
Merge pull request #242 from buckaroo-data/fix/118-execution-lock
paddymul Sep 24, 2026
667557d
test: the import leaves out a column xorq has no type for, whatever t…
paddymul Sep 24, 2026
4d38c3e
test: a zoned column imports beside a `row` header and under kept emp…
paddymul Sep 24, 2026
026148f
test: the append-only refusal does not advise resetting the whole cat…
paddymul Sep 24, 2026
e0e91b9
fix(import): leave out a column xorq has no type for, and name it
paddymul Sep 24, 2026
67648c0
test: reading an entry with no manifest is one plain error, with no a…
paddymul Sep 24, 2026
e061241
fix(csv): a `row` header and kept empty strings no longer break a zon…
paddymul Sep 24, 2026
e7feef7
Merge remote-tracking branch 'origin/feat/adr-007-009-cache-redesign'…
paddymul Sep 24, 2026
97576b1
Merge pull request #244 from buckaroo-data/fix/231-tz-offsetless-csv
paddymul Sep 24, 2026
7ebeaf3
fix(cache): reading an entry with no manifest raises one plain error,…
paddymul Sep 24, 2026
7bdbd15
Merge pull request #245 from buckaroo-data/fix/204-worthiness-without…
paddymul Sep 26, 2026
4d9efd8
test(#183): failing tests — one tallyman server per data dir
paddymul Sep 24, 2026
677b47d
fix(#183): one tallyman server per data dir, and each data dir's clie…
paddymul Sep 24, 2026
e1949a0
test: no test reaches a companion it did not start
paddymul Sep 24, 2026
afa4657
test(#183): failing tests — review of #241
paddymul Sep 24, 2026
d68a7b9
fix(#183): no default companion port; refuse another data dir's proje…
paddymul Sep 24, 2026
9033ef4
docs(#183): the claim outlives the port; a restart waits for the proc…
paddymul Sep 24, 2026
d41d8e0
refactor(#183): drop the redundant close_fds and claim_fd
paddymul Sep 24, 2026
a0e8804
test(#183): failing tests — run with no active project, and a server …
paddymul Sep 25, 2026
3da0432
fix(#183): run names the projects to pass when none is active; a serv…
paddymul Sep 25, 2026
002a85b
test(#183): failing tests — a server on :: serves IPv4 too, and is re…
paddymul Sep 25, 2026
44f41cd
test(#183): failing tests — the port check for :: sees an IPv4 listener
paddymul Sep 25, 2026
8da25e3
fix(#183): a server on :: serves IPv4 too, and every local URL for it…
paddymul Sep 25, 2026
c35cc05
test(#183): failing tests — review of #241, round 2
paddymul Sep 26, 2026
9db1e0c
fix(#183): run holds the data dir until Buckaroo has stopped, serves …
paddymul Sep 26, 2026
63bcdd6
Merge pull request #241 from buckaroo-data/fix/183-one-server-per-dat…
paddymul Sep 26, 2026
3871687
Merge branch 'feat/adr-007-009-cache-redesign' into fix/200-13-diff-r…
paddymul Sep 26, 2026
5c4e608
Merge branch 'feat/adr-007-009-cache-redesign' into fix/224-227-impor…
paddymul Sep 26, 2026
664972c
Merge branch 'feat/adr-007-009-cache-redesign' into docs/adr-007-009-…
paddymul Sep 26, 2026
9efffe3
docs: #118, #183, #204 and #231 are fixed on this branch; describe th…
paddymul Sep 26, 2026
2c64e5a
Merge pull request #216 from buckaroo-data/docs/adr-007-009-implemented
paddymul Sep 26, 2026
2597de8
test: the #234 guard catches a stale tool name spelled like a variable
paddymul Sep 28, 2026
1a15939
test: a failure after an import skips nothing after it, and is not a …
paddymul Sep 28, 2026
e1c8891
fix(import): each step after an import runs, and a failure there is n…
paddymul Sep 28, 2026
42a64d4
fix(test): the #234 guard counts only modules, functions and classes …
paddymul Sep 28, 2026
87669e8
Merge pull request #243 from buckaroo-data/fix/200-13-diff-row-order-…
paddymul Sep 28, 2026
7d14081
Merge pull request #247 from buckaroo-data/fix/224-227-import-hardening
paddymul Sep 28, 2026
0826cb6
test: the companion's own builds survive a forked git that dies with …
paddymul Sep 28, 2026
f6d9940
fix(companion): the companion installs the git-state guard when it is…
paddymul Sep 28, 2026
06772db
chore: license tallyman under the GNU AGPL, version 3 only
paddymul Sep 28, 2026
b5aecdc
Merge pull request #284 from buckaroo-data/fix/companion-git-state-guard
paddymul Sep 28, 2026
b09a2c0
Merge pull request #285 from buckaroo-data/chore/license-agpl-3.0
paddymul Sep 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ on:
push:
branches: [main, "spike/**"]
pull_request:
branches: [main]
# feat/** too: fix PRs stacked on a feature branch (e.g. PRs into #189's branch) need CI for the red/green cycle.
branches: [main, "feat/**"]

# CI runs three sequential jobs: lint → fast tests → integration tests.
#
Expand Down
3 changes: 1 addition & 2 deletions .mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
"args": ["run", "tallyman", "mcp"],
"cwd": "/Users/paddy/tallyman",
"env": {
"TALLYMAN_PROJECT": "spike",
"TALLYMAN_COMPANION_URL": "http://127.0.0.1:7860"
"TALLYMAN_PROJECT": "spike"
}
}
}
Expand Down
661 changes: 661 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

126 changes: 82 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tallyman — a data science environment designed for coding agents. Stop squinti

Two windows: Claude Code in one, a browser in the other. You work by defining a set of named results that can depend on each other, then making sure those results live up to their name. You describe them; the agent writes the queries. You tell it what `likely_customers` should mean, and a moment later the answer is a table in the browser at full size, sortable and searchable, with statistics over every column. Your prompt sits above the query the agent wrote, so you read your intent, the code that came out of it, and the result together. You notice it's catching people who already churned, so you sharpen the sentence and the agent revises the query. Everything built on top of that name updates to match, fast enough that you don't lose your place. How big the data is, what has already been computed, and what needs recomputing never enter into it.

Those queries are expressions: self-contained programs that declare the raw files and other results they read. Writing the expression is the agent's entire job. An expression is declarative and can be introspected, so tallyman reads its dependencies straight off it and builds a directed graph of the project's aliased expressions. When a parent updates, its children are recomputed; a raw file changing on disk counts as an update too. Tallyman executes each expression once and writes the result and its summary statistics to disk. Reading it back is out of core: scrolling, sorting, and searching pull only the pieces of data needed to fill the screen, so nothing has to fit in memory and four million rows opens like four thousand.
Those queries are expressions: self-contained programs that name, by alias, the imported data and the other results they read. Writing the expression is the agent's entire job. An expression is declarative and can be introspected, so tallyman reads its dependencies straight off it and builds a directed graph of the project's aliased expressions. When a parent updates, its children are recomputed; importing a new version of a data file counts as an update too. Tallyman executes each expression once and writes the result and its summary statistics to disk. Reading it back is out of core: scrolling, sorting, and searching pull only the pieces of data needed to fill the screen, so nothing has to fit in memory and four million rows opens like four thousand.


You aren't typing `df.sort_values('lifetime_sales')` just to see which customers are at the top, you aren't waiting for the LLM to print a 10 row table like it's coming out of a 1200 baud modem. You aren't running out of memory in the middle of a session. You aren't building the ad-hoc cache that every long notebook grows, the pickle in /tmp behind an if not exists guard that you never quite trust. You aren't nursing a kernel along for days because one cell takes five minutes to rerun, or bracing yourself before you close the window. None of that is in your head while you work. What's in your head is the data and what it means.
Expand Down Expand Up @@ -50,58 +50,77 @@ index of all the docs — start with [docs/architecture.md](docs/architecture.md

## V0 scope

End-to-end: a Claude Code MCP tool that compiles a xorq expression, materializes
a result to a content-hashed catalog entry on disk, and pushes a live update
to a browser companion via SSE.
End-to-end: a Claude Code MCP tool that compiles a xorq expression into a
content-hashed catalog entry on disk (running it, and writing its result to a
file when the entry does expensive work), and pushes a live update to a browser
companion via SSE.

What's working:

- **MCP tools** (FastMCP over stdio):
- Catalog: `catalog_run`, `catalog_load_parquet`, `catalog_create`,
- **MCP tools** (FastMCP over stdio, 31 tools):
- Catalog: `catalog_import_source`, `catalog_run`, `catalog_create`,
`catalog_revise`, `catalog_alias`, `catalog_rename`, `catalog_unalias`,
`catalog_list`, `catalog_diff`, `catalog_chart`, `catalog_recalc`, plus the
summary-stat / post-processing / display-klass authoring tools.
`catalog_list`, `catalog_diff`, `catalog_promote_diff`, `catalog_chart`,
`catalog_chart_errors`, `catalog_scan_staleness`, `catalog_recalc`,
`catalog_export_marimo`, plus the summary-stat / post-processing /
display-klass authoring tools.
- Notebook: `notebook_reorder`, `notebook_remove`, `notebook_edit_markdown`.
- Project: `project_list`, `project_new`, `project_switch`.

See [docs/architecture.md](docs/architecture.md) for the full tool surface.
See [docs/mcp-server.md](docs/mcp-server.md) for every tool and its side
effects.
- **Companion** (FastAPI on `:7860`) — serves the React SPA
(`packages/app/dist`) as a catch-all and exposes a JSON API + SSE under
`/{project}/api/*`:
- SPA tabs: **Catalog** (entry list + detail with V_n chips and forensic
history), **Notebook** (curated narrative anchored on aliases, drag-reorder,
inline markdown editor, × remove), **Diff** (code diff, schema diff,
per-column stats, key-joined side-by-side, head() side-by-side), **Cache**
(per-entry cache footprint), and **Log** (linear, filterable activity view).
- JSON: `/{project}/api/{entries,entry/<hash>,aliases,notebook,errors,log,
data/<hash>,diff_data/...,disk_usage,result_cache,staleness}`, plus the
mutation routes (`PATCH notebook`, `PUT code/<alias>`,
- SPA pages: **Catalog** (entry list + detail with V_n chips, forensic
history, and a metadata tab with the entry's disk footprint, sources,
parents and children), **Notebook** (curated narrative anchored on aliases,
drag-reorder, inline markdown editor, × remove), **Diff** (code diff, schema
diff, per-column stats, key-joined side-by-side, head() side-by-side, and a
promote button), **Cache** (the result snapshots on disk, with a delete
button; pinned snapshots cannot be deleted, and a snapshot whose entry a
reset retired is labelled as such), **Log** (linear, filterable
activity view), and the project list.
- JSON: `/{project}/api/{entries,entry/<hash>,entry_cache/<hash>,
session/<hash>,aliases,notebook,notebook_full,errors,error/<id>,log,
data/<hash>,diff_data/...,disk_usage,result_cache,staleness,telemetry}`,
plus the mutation routes (`PATCH notebook`, `PUT code/<alias>`,
`PUT markdown/<cell_id>`, `POST reset`, `POST recalc`,
`POST promote_diff/...`).
- `/{project}/api/sse` — live updates (`new_entry`, `build_failed`,
`alias_changed`, `notebook_changed`, `recalc`, `summary_stat_changed`).
`POST promote_diff/...`, `DELETE result_cache/<hash>`, `DELETE errors`).
- `/{project}/api/sse` — live updates. The SPA listens for `new_entry`,
`build_failed`, `notebook_changed`, `chart_attached`,
`post_processing_changed`, `summary_stat_changed`, `recalc` and
`project_switched`.
- `/internal/notify` — the MCP server's notification hook; fans out to SSE.
- **Buckaroo subprocess** — `tallyman run` spawns `python -m buckaroo.server`
on `:8700` (falls back to a random port if busy), watches for the
`BUCKAROO_PORT=...` handshake, and lazily creates per-entry sessions on
first view by POSTing the entry's `xorq_build/` dir to Buckaroo's
`/load_expr` endpoint (PR 776) — sort/search push down to the xorq
backend rather than paging over a materialised parquet. The build dir is
`BUCKAROO_PORT=...` handshake, and opens a session for an entry by POSTing a
build dir to Buckaroo's `/load_expr` endpoint (PR 776), after making sure
every file the entry reads exists. It does this when an entry's catalog page
opens, and for every cell each time the notebook page loads. A *worthy*
entry (one whose query tallyman materialized to a result file when the entry
was created) is handed a view build, a build that is one read of that file
(`.xorq_view_build/`). A *cheap* entry (a filter, selection or computed column
over one file, which keeps no file of its own) is handed its own build,
expanded into a stable per-entry path (`.xorq_build_expanded/`, gated by a
`.complete` marker) so `${TALLYMAN_PROJECT_ROOT}` placeholders are resolved
before xorq's loader sees them. Sessions are persisted in a global
`~/.tallyman-notebooks/buckaroo_sessions.json` (keyed by content hash, shared
across projects) and invalidated by start-time when Buckaroo restarts.
Tear-down rides along with the companion. Disable with `--no-buckaroo`.
before xorq's loader sees them. Buckaroo's sorting, search and summary stats
run as queries over that build. A session's id is derived from the project and
the content hash, so tallyman keeps no session file. Tear-down rides along with
the companion. Disable with `--no-buckaroo`.
- **Build artifacts are portable.** xorq's absolute filesystem paths are
rewritten to `${TALLYMAN_PROJECT_ROOT}` on write and expanded back on load.
rewritten to `${TALLYMAN_PROJECT_ROOT}` on write and expanded back on load
(with one known gap for copied projects, #209).
- **`tallyman serve <project_dir>`** — read-only companion against a project
directory that may live anywhere on disk. Mutation routes return 403.
directory that may live anywhere on disk. Mutation routes return 403, and no
Buckaroo subprocess runs, so entry grids do not load.

What's NOT yet implemented:

1. Column-level lineage (xorq has the data; there is no lineage view today).
2. ML training pipeline (storyboard beats 7-8).
2. A dedicated ML training tool, `catalog_train` (storyboard beats 7-8, #2).
Models can already be fitted as catalog entries with `xorq.ml`, as
`catalog_run`'s tool description shows.

## Running the spike

Expand All @@ -125,7 +144,8 @@ In another terminal, launch Claude Code from this directory; it picks up

Recommended prompts:

> Use catalog_load_parquet to load `orders.parquet`.
> Use catalog_import_source to import
> `~/.tallyman-notebooks/projects/spike/data/orders.parquet` as `orders`.
>
> Now use catalog_create to make a named entry `shoe_sales` that groups orders
> by region and totals the price.
Expand All @@ -147,22 +167,36 @@ tar xzf my-project.tgz -C ~/projects/
uv run tallyman serve ~/projects/spike
```

The companion runs read-only: same catalog, same forensic history, no edit
affordances. Mutation routes return 403.
The companion runs read-only: same catalog, same forensic history, same charts.
The edit controls still show, but mutation routes return 403, and there is no
Buckaroo grid.
The archive includes `compute_cache/`, and a known defect (#209) makes a copied
project's cheap entries read from the original location; see
[docs/installing.md](docs/installing.md#sharing-a-project).

## Conventions worth knowing

- xorq 0.3.x reads use `xo.deferred_read_parquet` (NOT `xo.read_parquet` — that
resolves through ibis's backend loader and fails). Use
`import xorq.api as xo` and `import xorq.vendor.ibis as ibis`. Do NOT
`import ibis` directly.
- Prefer `from tallyman_xorq.io import read_project_file; t = read_project_file("name.parquet")`
over absolute paths — the catalog records project-relative intent and the
build is portable across machines/users.
- A file enters the catalog only through `catalog_import_source(path, alias)`,
which copies its bytes into the project and makes each version of it an entry
under a **source alias**; the path can be anywhere and is never read again.
Import it again to bring in new data: different bytes mint the next version,
and the entries downstream are recalculated. A CSV takes its `schema` and
reader options in the import call, and they are fixed there.
- Recipes read entries by alias, never files:
`tracked_expr_from_alias("orders")` follows an alias and
`pinned_expr_from_alias("orders-v2")` pins one version. `read_project_file`,
`tallyman_read_csv` and `xo.deferred_read_csv` are build errors that name the
import to use, and so is `xo.deferred_read_parquet` of any file outside the
project's `compute_cache/`. `xo.read_parquet` resolves through ibis's backend
loader and fails. Use `import xorq.api as xo` and `import xorq.vendor.ibis as
ibis`. Do NOT `import ibis` directly.
- Content hash is xorq's build hash — same code + same inputs → same hash → same
entry dir (idempotent).
- All catalog state lives on disk. The MCP server holds no in-memory state; the
companion only holds the SSE subscriber list.
entry dir (idempotent). A source entry's hash is instead an md5 of the
imported bytes and the reader options.
- All catalog state lives on disk. The MCP server and the companion keep only
in-memory caches of things that never change (loaded builds and reads, keyed
by content hash), plus the MCP session's active project and the companion's
SSE subscribers and diff sessions.
- `TALLYMAN_PROJECT_PATH` overrides project_dir() resolution for the active project.
Used by `tallyman serve` to point at a project directory anywhere on disk.

Expand All @@ -172,3 +206,7 @@ affordances. Mutation routes return 403.
uv run pytest # full suite
uv run pytest tests/test_pack.py # the pack / portability proof
```

## License

Tallyman is licensed under the GNU Affero General Public License, version 3 only (`AGPL-3.0-only`). The full text is in [LICENSE](LICENSE).
14 changes: 7 additions & 7 deletions demo/datasets.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,19 @@ df['tip_pct'] = (df['tip_amount'] / df['fare_amount'].replace(0, float('nan')))
Type these into Claude Code with the tallyman MCP server running:

```
Use catalog_load_parquet to load /tmp/nyc_taxi/yellow_tripdata_2024-01.parquet,
name it yellow_jan_2024. Prompt: "NYC yellow cab trips January 2024".
Use catalog_import_source to import /tmp/nyc_taxi/yellow_tripdata_2024-01.parquet
under the alias yellow_jan_2024. Prompt: "NYC yellow cab trips January 2024".
```

```
Create a named entry trips_by_zone that joins yellow_jan_2024 with the taxi zone
lookup at /tmp/nyc_taxi/taxi_zone_lookup.csv on PULocationID, groups by Zone,
and counts trips with mean fare. Name it trips_by_zone.
Import /tmp/nyc_taxi/taxi_zone_lookup.csv under the alias taxi_zones, then create
a named entry trips_by_zone that joins yellow_jan_2024 with taxi_zones on
PULocationID, groups by Zone, and counts trips with mean fare.
```

```
Use catalog_load_parquet to load /tmp/citibike/citibike_h1_2024.parquet,
name it citibike_h1. Prompt: "Citibike trips January–June 2024".
Use catalog_import_source to import /tmp/citibike/citibike_h1_2024.parquet
under the alias citibike_h1. Prompt: "Citibike trips January–June 2024".
```

```
Expand Down
24 changes: 17 additions & 7 deletions demo/storyboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
"project": "spike",
"steps": [
{
"tool": "catalog_load_parquet",
"args": {"rel_path": "orders.parquet", "name": "orders", "prompt": "raw orders dataset"},
"narration": "beat 1: load the orders parquet and name it"
"tool": "catalog_import_source",
"args": {
"outside_path": "${TALLYMAN_PROJECT_ROOT}/data/orders.parquet",
"alias": "orders",
"prompt": "raw orders dataset"
},
"narration": "beat 1: import the orders parquet under a source alias"
},
{
"tool": "catalog_create",
Expand All @@ -21,7 +25,7 @@
"code": "from tallyman_xorq.io import tracked_expr_from_alias\nt = tracked_expr_from_alias('shoe_sales')\nexpr = t.order_by('total')\n",
"prompt": "verify shoe_sales totals are reasonable"
},
"narration": "beat 4: scratch verification — sort the result"
"narration": "beat 4: scratch verification \u2014 sort the result"
},
{
"tool": "catalog_revise",
Expand All @@ -39,8 +43,14 @@
"vega_spec": {
"mark": "bar",
"encoding": {
"x": {"field": "region", "type": "nominal"},
"y": {"field": "total", "type": "quantitative"}
"x": {
"field": "region",
"type": "nominal"
},
"y": {
"field": "total",
"type": "quantitative"
}
}
}
},
Expand All @@ -52,7 +62,7 @@
"cell_id": "PLACEHOLDER",
"markdown": "## Shoe sales by region\n\nBroader bucket: boots, sneakers, sandals."
},
"narration": "beat 10: rewrite the markdown for the audience (skipped — needs cell_id resolution)",
"narration": "beat 10: rewrite the markdown for the audience (skipped \u2014 needs cell_id resolution)",
"skip": true
}
]
Expand Down
Loading
Loading