No description
  • Rust 98.4%
  • Nix 1.6%
Find a file
2026-07-12 13:20:45 +08:00
docs/agents chore: initial commit w/ docs 2026-07-08 20:04:36 +08:00
src feat: let 'tatami destroy' remove more than one slot at once 2026-07-12 13:20:45 +08:00
tests feat: let 'tatami destroy' remove more than one slot at once 2026-07-12 13:20:45 +08:00
.gitignore chore: nix flake and clippy check 2026-07-08 20:10:40 +08:00
AGENTS.md chore: simplify AGENTS.md 2026-07-08 21:03:23 +08:00
Cargo.lock feat: add --toon output mode alongside --json 2026-07-09 00:04:54 +08:00
Cargo.toml feat: add --toon output mode alongside --json 2026-07-09 00:04:54 +08:00
CONTEXT.md chore: initial commit w/ docs 2026-07-08 20:04:36 +08:00
flake.lock chore: nix flake and clippy check 2026-07-08 20:10:40 +08:00
flake.nix chore: nix flake and clippy check 2026-07-08 20:10:40 +08:00
README.md feat: let 'tatami destroy' remove more than one slot at once 2026-07-12 13:20:45 +08:00
tatami.toml.example feat: add init command and docs 2026-07-08 20:04:36 +08:00

tatami

A pool manager for jujutsu workspaces. Tatami keeps a pool of isolated, reusable working copies ready for agents and developers to pick up instantly, with build caches kept warm between uses.

Starting a fresh workspace per agent session throws away installed dependencies and build caches every time. Tatami manages a pool of pre-warmed slots so each session gets its own isolated jj workspace instantly, and returns it to the pool warm when done.

It is a jj-native reimagining of treehouse: where treehouse pools git worktrees, tatami pools jj workspaces and adds bookmarks as durable work identity. Linux only.

Quick start

cd myproject                 # any jj repo
tatami                       # acquire an anonymous slot, drop into a subshell
# ... work inside the warm workspace ...
exit                         # subshell exit returns the slot to the pool

tatami my-feature        # acquire a slot bound to bookmark 'my-feature'
tatami status                # show the pool

Installation

Nix only.

# run without installing
nix run github:weiwen/tatami

# install into your profile
nix profile install github:weiwen/tatami

# hack on it
nix develop            # dev shell with the rust toolchain + jj

Requires jj on PATH at runtime (bundled in the dev shell).

How it works

  • jj workspaces are the isolation primitive (jj workspace add). Git is not supported.
  • Return resets, never deletes (jj new trunk()), preserving target/, node_modules/, and other build artifacts for the next user.
  • No dirty check: a jj working copy is always a commit, so any change is already in the bookmark's history. The "merged into trunk()" check covers reclamation safety.
  • No daemon: pool state is a small JSON file per pool, written atomically under a file lock. In-use detection scans /proc for processes whose cwd is inside a slot.
  • Self-healing: a crashed session's owner reservation is cleared automatically, and a corrupt state file is rebuilt from the slots on disk.

Vocabulary

  • Pool — the managed slots for one repository (one pool per repo).
  • Pool slot — an anonymous, reusable directory holding one jj workspace; working copy and build cache persist across uses.
  • Bookmark — your named line of work. tatami get my-feature binds bookmark my-feature in a slot; it persists after you return the slot and resumes in a fresh warm slot on the next tatami get my-feature.
  • Lease — a durable reservation that survives with no process running in the slot.
  • Trunk — jj's trunk() revset; the reset target when a slot returns to the pool.

See CONTEXT.md for the full domain model.

CLI reference

Command Description
tatami [get] [bookmark] Acquire a slot and open a subshell. get is implicit, so tatami feat is the same as tatami get feat. Pass a bookmark to bind durable work identity.
tatami enter <index|bookmark|path> Open a subshell in an existing slot without acquiring or resetting it (alias cd).
tatami return [path|index] Release a slot, terminate lingering processes, and return it to the pool. Defaults to the current workspace.
tatami status Show pool status.
tatami prune Reclaim stale slots whose work has landed on trunk (dry-run by default).
tatami destroy <path|index>... Deliberately remove one or more slots, or a whole pool (dry-run by default).
tatami init Write a starter tatami.toml in the repository root.

get

tatami [BOOKMARK] [--lease] [--lease-holder <LABEL>]

get is implicit: any first argument that is not a known subcommand is treated as a bookmark, so tatami feat is shorthand for tatami get feat.

  • --lease — durably lease the slot without a subshell; prints only the slot path to stdout, so it composes with scripts:
    path=$(tatami get my-feature --lease)
    # ... use "$path" as a durable home; no process needs to stay inside it ...
    tatami return "$path"
    
  • --lease-holder <LABEL> — label recorded as the lease holder (defaults to $TATAMI_LEASE_HOLDER).

prune

tatami prune [--all] [--yes] [--verbose]
  • --all (alias --global) — sweep every managed pool under the user-level tatami root.
  • --yes — delete the candidates instead of doing a dry run.
  • --verbose, -v — list each skipped slot with its reason.

destroy

tatami destroy <PATH|INDEX>... [--all] [--yes] [--include-unlanded] [--include-in-use] [--include-leased]

Both destroy and return accept a slot's index (the number shown by tatami status) in place of its path; a bare number is resolved against the current repository's pool. destroy accepts several slots at once (e.g. tatami destroy 1 3 5), all within one pool; --all still takes a single pool or repository.

  • --all — target every slot in the named pool (PATH is a pool path or repository).
  • --yes — execute the removal instead of doing a dry run.
  • --include-unlanded — also remove slots whose bookmark has not landed on trunk.
  • --include-in-use — also remove in-use slots (their processes are terminated first).
  • --include-leased — also remove a leased slot (only when naming the exact slot path).

Structured output

Every command accepts a global --json flag, or --toon for the same result encoded as TOON (a token-efficient JSON alternative for LLM prompts). The two are mutually exclusive:

tatami status --json
tatami get my-feature --lease --json
tatami prune --all --toon

The contract is strict so a consumer can parse stdout unconditionally:

  • On success, stdout carries exactly one document (the command's result).
  • On failure, stdout carries exactly one {"error": "..."} document and the exit code is non-zero.
  • All human progress and status chatter is suppressed; nothing but the document is written to either stream (hook output, being your own scripts', is left untouched).

tatami destroy in a structured mode reports a single-slot no-op through the skipped array (each entry lists the needs flags) and exits zero, rather than the non-zero exit the text mode uses. The interactive tatami get subshell emits its acquired-slot document, then hands stdout to the subshell; use get --lease --json (or --toon) for a non-interactive flow.

Configuration

Repo-level tatami.toml (see tatami.toml.example) holds only safe settings:

# Maximum number of slots in the pool.
max_slots = 16

# Pool root override. Empty uses $HOME/.tatami.
# Relative paths resolve from the repo root; a ".tatami" component is appended.
# root = "$HOME/.local/share/tatami/"

Lifecycle hooks are read only from the user-level ~/.config/tatami/config.toml, never from a repo-level file, so cloning a repository can never make tatami run someone else's commands:

[hooks]
post_create = ["./scripts/setup.sh"]
pre_destroy = ["./scripts/teardown.sh"]

The user-level config may also set max_slots and root; a repo-level tatami.toml overrides those but never contributes hooks. $VAR / ${VAR} references in root expand from the process environment.

Suggested jj config

Idle slots keep can clutter jj log. This revset hides idle tatami- workspaces (add it to your jj config, e.g. ~/.config/jj/config.toml):

[revsets]
log = "(present(@) | ancestors(immutable_heads().., 2) | trunk()) ~ bookmarks('tatami-')"

Development

cargo build
cargo test
cargo clippy --all-targets -- -D warnings