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, Catalog

Arrows are dependency direction. tacet-mcp sits beside tacet-web as the second crate permitted a socket.

What each crate is for

CrateResponsibility
tacet-kernelThe contract: Tool, ArgSchema, ToolOutcome, DataStore, Catalog, and since 0.1.5 the constraint contract too. Depends on nothing.
tacet-grammarTurns 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-engineThe contracts an engine must satisfy, plus FakeEngine and CandleEngine
tacet-toolsThe concrete tools, the executor, and the router with its 48 KiB classifier
tacet-zipZip, deflate and CRC32 by hand, which is what makes the OOXML writing possible
tacet-webSearch, page fetch, the SSRF gate and the addon registry. Has a socket.
tacet-mcpThe MCP client. JSON-RPC 2.0 over Streamable HTTP with SSE, OAuth, elicitation and task polling. Has a socket.
tacet-skillsTrigger matching, and fencing one skill into one turn
tacet-memoryNotes on disk and the relevance test that decides when to inject one
tacet-evalCases, scoring, baselines and reports
tacet-cliThe 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.

What "the full list" honestly means

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.

Every crate now ships the MIT text as a file rather than only the field.
CrateWasNow
tacet-kernel0.1.40.1.5
tacet-zip0.1.00.1.1
tacet-grammar0.1.20.2.0
tacet-web0.1.50.1.6
tacet-mcp0.2.00.2.1
tacet-engine0.1.90.1.11
tacet-skills0.1.00.1.1
tacet-memory0.1.10.1.2
tacet-tools0.1.130.1.14
tacet-eval0.1.90.1.10
tacet-cli0.1.250.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.