The eleven crates
A Rust workspace of eleven crates with no circular dependencies, arranged so the contract never bends under pressure from an implementation.
The shape
tacet-cli ──────► terminal shell; drives the turn loop
│
├── tacet-eval ──► cases, scoring, reports
│
├── tacet-grammar► ArgSchema → constrained-generation grammar (PDA + token mask)
│ │
│ ▼
├── tacet-engine ► contracts: Prompt, EngineProvider, Constrainer, TokenCounter
│ FakeEngine (default) / CandleEngine (--features candle)
│
├── tacet-tools ─► concrete tools + ToolExecutor + Router
│ ├── tacet-zip ──► hand-written zip/deflate/crc32 → OOXML
│ └── tacet-web ──► one of the two crates with a socket:
│ search, page fetch, the SSRF gate, the addon registry
│
├── tacet-skills ► trigger-matched guidance, fenced into one turn
├── tacet-memory ► notes on disk, injected only when relevant
│
└── tacet-kernel► the CONTRACT: Tool, ArgSchema, ToolOutcome, DataStore, CatalogArrows are dependency direction. tacet-mcp sits beside tacet-web as the second crate permitted a socket.
What each crate is for
| Crate | Responsibility |
|---|---|
| tacet-kernel | The contract: Tool, ArgSchema, ToolOutcome, DataStore, Catalog, and since 0.1.5 the constraint contract too. Depends on nothing. |
| tacet-grammar | Turns an ArgSchema into a grammar: a pushdown automaton and the token mask it produces. Since 0.2.0 it does not depend on tacet-engine. |
| tacet-engine | The contracts an engine must satisfy, plus FakeEngine and CandleEngine |
| tacet-tools | The concrete tools, the executor, and the router with its 48 KiB classifier |
| tacet-zip | Zip, deflate and CRC32 by hand, which is what makes the OOXML writing possible |
| tacet-web | Search, page fetch, the SSRF gate and the addon registry. Has a socket. |
| tacet-mcp | The MCP client. JSON-RPC 2.0 over Streamable HTTP with SSE, OAuth, elicitation and task polling. Has a socket. |
| tacet-skills | Trigger matching, and fencing one skill into one turn |
| tacet-memory | Notes on disk and the relevance test that decides when to inject one |
| tacet-eval | Cases, scoring, baselines and reports |
| tacet-cli | The shell, the turn loop, the subcommands. The only crate that knows crossterm or clap. |
Two boundaries that are deliberate
tacet-kernel depends on nothing, so the contract cannot be bent to suit an implementation that finds it inconvenient. The constraint contract — Constrainer, ConstraintSession, ConstraintError, three signatures over &mut [f32] and u32 — now lives there too. It used to live in tacet-engine, which meant that taking the guarantee meant taking a crate full of GGUF loading and prompt budgeting to get it.
The result is that tacet-grammar no longer depends on tacet-engine at all. That is the whole point of the crate and it was not true before. Neither tacet-engine nor tacet-kernel may pick an async executor either, which is why there is no tokio in the workspace.
The dependency list is short on purpose
The direct list is serde, serde_json, thiserror, clap, crossterm and ureq. Behind the off-by-default candle feature there are three more: candle-core, candle-transformers and tokenizers. Adding a line to that list is an architectural decision documented at the top of the manifest, not a convenience.
Those are the direct dependencies, and they are the ones a reader can audit. Two of them pull a C build in transitively. ureq brings rustls, which brings ring, and ring compiles C — that is what sets the build prerequisite on Windows: not the MSVC linker it looks like, but a C compiler. And tokenizers brings onig and esaxx-rs, a second C build, on the candle feature. This page disclosed the first and not the second for a while, which made an off-by-default feature look cheaper than it is.
All eleven, published together
On 6 September 2026 every crate in the workspace was published at once, which had not happened before. Four of them had changed source under an unchanged published version — verified by downloading the tarballs and diffing them, not by reading the log. That state is worse than a version gap: the next publish of that number is rejected outright, and until then nobody can tell they are missing the fixes.
| Crate | Was | Now |
|---|---|---|
| tacet-kernel | 0.1.4 | 0.1.5 |
| tacet-zip | 0.1.0 | 0.1.1 |
| tacet-grammar | 0.1.2 | 0.2.0 |
| tacet-web | 0.1.5 | 0.1.6 |
| tacet-mcp | 0.2.0 | 0.2.1 |
| tacet-engine | 0.1.9 | 0.1.11 |
| tacet-skills | 0.1.0 | 0.1.1 |
| tacet-memory | 0.1.1 | 0.1.2 |
| tacet-tools | 0.1.13 | 0.1.14 |
| tacet-eval | 0.1.9 | 0.1.10 |
| tacet-cli | 0.1.25 | 0.1.27 |
Until that day cargo install tacet-cli gave you 0.1.25: no bench subcommand, and pinned one release below the floor for constrained generation on Metal, so a Mac user following the instructions installed the crash. If you installed before September, reinstall.
Version floors, not preferences
Most internal version numbers in the workspace manifest are minimums, and each was put there by a fix that a ^0.1.0 install would have resolved away. cargo install picks the newest match, but a machine with an older copy already in its registry does not have to move, so a version carrying a gate has to be the floor or the install looks identical while missing the thing it was published for. That has happened here once already. Each floor carries a comment naming the failure it closes.