
Tigerfs
- 5 installs
- 678 repo stars
- Updated July 25, 2026
- timescale/tigerfs
Helps with ai & agent building tasks.
About
tigerfs is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- tigerfs
- AI & Agent Building
- AI-coding skill
Tigerfs by the numbers
- 5 all-time installs (skills.sh)
- +1 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #13,065 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/timescale/tigerfs --skill tigerfsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 678 |
| Last updated | July 25, 2026 |
| Repository | timescale/tigerfs ↗ |
What it does
Helps with ai & agent building tasks.
Files
Using TigerFS
TigerFS mounts PostgreSQL databases as directories. You interact with data using Read, Write, Glob, and Grep -- no SQL needed. File-first mode gives you a transactional, shareable filesystem backed by a database. Data-first mode lets you explore and manipulate an existing database using file tools. Most work uses file-first.
Which Mode?
Each directory in a TigerFS mount is either file-first or data-first:
- File-first: Contains
.mdor.txtfiles. Read and write files normally. See files.md. - Data-first: Contains
.info/directory. Access rows as files or directories. See data.md.
Directory Structure
mount/
├── notes/ # File-first (markdown workspace)
│ ├── hello.md
│ ├── tutorials/ # Subdirectories: ls shows only real files; .history/<file>/ remains path-accessible
│ ├── .history/ # Versioned history (root level only)
│ ├── .log/ # Operation log (root level only; pipeline hidden from ls)
│ ├── .savepoint/ # Named bookmarks for undo (root level only)
│ └── .undo/ # Preview and apply undo operations (root level only)
├── snippets/ # File-first (plain text workspace)
│ └── bash-loop.txt
├── .tables/ # Backing tables in tigerfs schema
│ └── notes/ # Data-first access to notes backing table
├── users/ # Data-first (standalone table)
│ ├── .info/
│ ├── .by/
│ └── 1/ 2/ 3/ ...
├── .info/ # Mount-level metadata (user identity)
└── .build/ # Create new workspacesFile-First
A transactional, shareable filesystem backed by a database. Multiple users and agents can read and write concurrently. Create workspaces with .build/:
Bash "echo 'markdown' > mount/.build/notes"
Bash "echo 'markdown,history' > mount/.build/notes" # with versioned history
Bash "echo 'plaintext' > mount/.build/snippets" # body-only, no frontmatterSee files.md for full details on schemas, column roles, directories, and history.
What you can build
Because the filesystem is transactional and shared, it can implement collaborative workflows. Follow the recipe exactly:
| If asked to... | Follow |
|---|---|
| Create a task list, kanban, todo, or project tracker | recipes.md Recipe 4. Core rule: directories = states (`todo/`, `doing/`, `done/`); `mv` = transitions; do NOT use `status` frontmatter. |
| Create a knowledge base, wiki, or documentation store | recipes.md Recipe 5 |
| Save or resume session context | recipes.md Recipe 6 |
| Keep a log of what you do | recipes.md Recipe 7 |
| Revert, roll back, or undo changes | See Safe Editing below and files.md |
Safe Editing with Savepoints
Before making multiple or risky edits, always create a savepoint:
Bash "echo '{\"description\":\"Before investigating bug #42\"}' > mount/workspace/.savepoint/before-investigation.json"When to create a savepoint: investigating a bug, debugging, refactoring, multi-file edits, or any uncertain operation.
When to undo: user asks to revert, agent realizes the approach isn't working, or changes were made to wrong files.
Before undoing: always preview first and get user confirmation. Undo is destructive.
Undo is undoable: undo operations are logged (type='undo'), so you can undo an undo. Create a savepoint before a major undo for extra safety -- if the result isn't what was expected, undo back to that savepoint.
Common Workflows
"Create a savepoint"
Bash "echo '{\"description\":\"<why>\"}' > mount/workspace/.savepoint/<name>.json""What changed since the savepoint?" 1. Read the summary: Read "mount/workspace/.undo/to-savepoint/<name>/.info/summary" 2. If <= 5 files affected: for each file, read its before and current state, summarize cumulative changes in English 3. If > 5 files: present the summary table (type, filename, user, timestamp) 4. If user wants raw diffs: Bash "cd mount/workspace && diff -ru .undo/to-savepoint/<name> . -x '.*'"
"What changed in this file?" 1. Get the file's stable UUID: Read "mount/workspace/<dir>/.history/<file>/.id" (for a root-level file: Read "mount/workspace/.history/<file>/.id") 2. Find recent edits: Read "mount/workspace/.log/.by/file_id/<uuid>/.last/5/.export/json" 3. For each entry, read before and after, compare them, summarize in English. 4. Present with log_ids so the user can pick one to undo. Note: a single logical edit by an agent may produce multiple log entries (see How agent writes appear in the log).
"Show me the diff"
- Savepoint:
Bash "cd mount/workspace && diff -ru .undo/to-savepoint/<name> . -x '.*'" - Single file:
Bash "diff -u --color mount/workspace/.log/<id>/before mount/workspace/.log/<id>/after"
"Undo to the savepoint" 1. Read the summary: Read "mount/workspace/.undo/to-savepoint/<name>/.info/summary" 2. If <= 5 files: summarize cumulative changes per file in English 3. Present to user: "This will undo N changes: [summary]. Go ahead?" 4. Only if confirmed: Bash "touch mount/workspace/.undo/to-savepoint/<name>/.apply"
"Undo this one change" 1. Read the log entries around the change. If multiple entries belong to the same logical write (same file_id, adjacent timestamps), each must be undone — see How agent writes appear in the log. 2. Read the log entry summary: Read "mount/workspace/.undo/id/<log_id>/.info/summary" 3. Show the diff: Bash "diff -u --color mount/workspace/.log/<id>/before mount/workspace/.log/<id>/after" 4. Present to user: "This will revert [description]. Go ahead?" 5. Only if confirmed: Bash "touch mount/workspace/.undo/id/<log_id>/.apply". For a multi-entry group, apply each entry's undo (newest log_id first) (or touch .undo/to-id/<id-before-the-group>/.apply to reverse the whole group atomically).
How agent writes appear in the log
Different write methods produce different numbers of log entries:
- Atomic-rename writes (Claude's
Write/Edit, many editors): the operation produces multiple adjacent log entries. Typically: acreateof a temp file like<file>.tmp.<pid>.<hash>, then for an overwrite adeleteof the existing target, then arenameof the temp to the final name. That's 2 entries for a new file, 3 for an overwrite. The create and rename share the temp file'sfile_id; the delete (when present) carries the original target'sfile_id. - Direct writes (shell redirects like
echo "..." > file, in-place editors): a singlecreate(new file) oredit(existing file) log entry. No temp file, no rename.
To revert a logical operation, find its group of entries, then undo them:
1. Read the log newest-first: Read "mount/workspace/.log/.last/8/.export/json". 2. Anchor on the rename to your target filename -- its file_id is the new file. The paired create shares that same `file_id` (filename <basename>.tmp.<pid>.<hash>); the delete (overwrites only) is the adjacent entry with the target filename but a different file_id. Checks: create+rename share a file_id; count is 2 (new file) or 3 (overwrite). filename is not a .by/-indexed column, so don't filter by it -- to get the id without scanning, read .history/<file>/.id, then query .log/.by/file_id/<id>/. 3. Undo each entry touch .undo/id/<log_id>/.apply, newest first -- order is mandatory; each step sets up the next. Out of order is destructive: RPC struct is bad on the stale .tmp.*, or silent deletion of the live file.
Shortcut: when the group is the latest change, touch .undo/to-id/<id-just-before-it>/.apply reverses it atomically in order. Never revert by re-typing content with Write/Edit -- that adds another rename group and loses the audit trail.
For advanced cases (filtered undo, per-user undo, pipeline queries), construct paths directly from files.md. See recipes.md Recipes 1-3 for complete workflow patterns.
Data-First
Direct access to database rows as files and directories. Use when you need column-level access, index lookups, bulk export, or structured data processing.
Read "mount/users/.info/count" # Row count
Read "mount/users/1.json" # Row as JSON
Read "mount/users/1/email" # Single column
Glob "mount/users/.by/email/alice@example.com/*" # Index lookup
Read "mount/users/.by/status/active/.export/json" # Filtered exportAccess Strategy by Table Size
| Size | Strategy |
|---|---|
| ~100 rows or less | Glob patterns and row-as-directory access are fine |
| 100 - 1,000 rows | Prefer .export/ over reading individual rows to avoid 1 query per row. Use .by/, .filter/, .first/, .last/, .sample/ for selective access when possible |
| 1,000+ rows | Large tables are limited to 1,000 rows by default; use .all/ if you actually need all rows. Strongly prefer .export/ over reading individual rows. Use .by/, .filter/, .first/, .last/, .sample/ for selective access whenever possible |
Always check .info/count first to choose the right strategy.
See data.md for the full reference.
Quick Reference
| Goal | Tool Call |
|---|---|
| File-First | |
| List files | Glob "mount/workspace/*.md" or Glob "mount/workspace/**/*.md" (recursive) |
| Read file | Read "mount/workspace/file.md" |
| Write file | Write "mount/workspace/file.md" with content |
| Delete file | Bash "rm mount/workspace/file.md" |
| Search | Grep pattern="term" path="mount/workspace/" |
| History versions | Glob "mount/workspace/.history/file.md/*" |
| Read old version | Read "mount/workspace/.history/file.md/<timestamp>" |
| Savepoints & Undo | |
| Create savepoint | Bash "echo '{\"description\":\"Before refactoring\"}' > mount/workspace/.savepoint/name.json" |
| Diff all changes since savepoint | Bash "cd mount/workspace && diff -ru .undo/to-savepoint/name . -x '.*'" |
| File drift since a change | Bash "diff -u --color mount/workspace/.log/<id>/before mount/workspace/.log/<id>/current" |
| View recent log | Read "mount/workspace/.log/.last/10/.export/json" |
| Data-First | |
| Row count | Read "mount/t/.info/count" |
| Schema / columns | Read "mount/t/.info/schema" or .info/columns |
| Read row | Read "mount/t/pk.json" or Read "mount/t/pk/column" |
| Read multiple rows (small) | Glob "mount/t/*.json" then read individually |
| Read multiple rows (medium/large) | Read "mount/t/.export/tsv" (also .export/json, .csv, .yaml) |
| Navigate rows | Glob "mount/t/.first/N/*", .last/N/*, or .sample/N/* |
| Index lookup | Glob "mount/t/.by/col/val/*" |
| Filtered export | Read "mount/t/.by/col/val/.export/json" |
| Update | Write "mount/t/pk/col" or Write "mount/t/pk.json" (PATCH) |
| Insert row | Write "mount/t/new.json" with JSON |
| Delete row | Bash "rm mount/t/pk" |
Directory Scanning Safety
Virtual directory layout: .history/, .log/, .savepoint/, .undo/ only appear in ls at the workspace root level, not inside subdirectories. Subdirectories' ls shows only real files; .history/<file>/ and .history/<file>/.id remain path-accessible from subdirectories.
Pipeline capabilities (`.by/`, `.filter/`, `.order/`, `.export/`, etc.) behave differently depending on where they appear:
- Inside `.log/` or `.savepoint/`: accessible by explicit path but hidden from
ls(prevents recursive scanner blowup on exponential pipeline branching). - At a file-first workspace root or in a workspace subdirectory: rejected with
ErrInvalidPathand a hint pointing atmount/.tables/<workspace>/— that's the entry point for data-first access to a workspace's backing table. - On a standalone data-first table (e.g.,
mount/users/) or undermount/.tables/<workspace>/: fully accessible.
Never recursively scan these directories -- always use targeted access patterns from the Quick Reference above:
.log/-- pipeline capabilities hidden from listing, use.log/.last/10/.export/json.savepoint/-- same as.log/, use explicit paths.history/-- one entry per file version; grows with every edit.undo/-- preview trees that mirror the affected file hierarchy.by/<column>/-- lists every distinct value; each expands to filtered rows.filter/<column>/-- same as.by/with higher limit.export/-- reading files triggers full table/query dumps.import/-- write interface; scanning could trigger unintended operations
Safe to scan:
- Regular files and subdirectories in a workspace (e.g.,
Glob "mount/workspace/**/*.md") .info/-- small, fixed set of metadata files
Anti-Patterns
| Don't | Do Instead |
|---|---|
| File-First | |
Put status: in frontmatter to track state | Use directories as states (todo/, doing/, done/), mv to transition |
Use Write or Edit to restore prior file content -- even for a single file. The tool's write-temp-then-rename pattern creates 2-3 new log entries instead of reverting the originals. | Use TigerFS's undo machinery: .undo/to-savepoint/<name>/.apply for groups of changes, .undo/id/<log_id>/.apply for one operation. See How agent writes appear in the log for grouping multi-entry writes. |
| Undo without previewing first | Always read .info/summary and get user confirmation before applying |
| Data-First | |
| Read individual rows in a loop for large tables | Use .export/json or .export/csv for bulk access |
| Write full row JSON expecting replace semantics | JSON/CSV/TSV writes are PATCH -- only specified keys update |
Grep across all rows of a large table | Use .by/ index lookups for indexed columns |
| Glob a data-first directory without checking size | Read .info/count first, choose strategy based on table size |
Database Management
When asked to create, mount, fork, or manage a database or filesystem, see ops.md for tigerfs CLI commands.
Detailed References
- files.md -- File-first: workspaces with files and directories, and history
- data.md -- Data-first: row-as-file, row-as-directory, metadata, indexes, pipeline queries
- recipes.md -- Recipes: kanban boards, knowledge bases, session context, snippets, safe exploration, compare approaches, multi-agent undo
- ops.md -- Operations: mount, create, fork, status, unmount
Data-First Reference
Explore and manipulate database tables using file tools. Each table is a directory, each row is a file, each column is accessible individually.
Path Hierarchy
mount/
├── table_name/ # One directory per table
│ ├── .info/ # Metadata (read-only)
│ │ ├── count # Total row count
│ │ ├── schema # CREATE TABLE DDL
│ │ ├── ddl # Extended DDL with indexes
│ │ ├── columns # Column names, one per line
│ │ └── indexes # Index information
│ ├── .by/ # Index-based lookups
│ │ └── column/value/ # Rows matching index value
│ ├── .first/N/ # First N rows (ascending PK)
│ ├── .last/N/ # Last N rows (descending PK)
│ ├── .sample/N/ # Random N rows
│ ├── .filter/column/value/ # Filter by any column (may scan)
│ ├── .order/column/ # Sort results
│ ├── .columns/col1,col2/ # Column projection
│ ├── .export/json|csv|tsv # Bulk export
│ ├── .import/json|csv|tsv # Bulk import
│ ├── .all/ # Access all rows (hidden from ls; bypasses 1,000 row limit)
│ ├── .indexes/ # Index management (DDL)
│ ├── .modify/ # Table modification (DDL)
│ ├── .delete/ # Table deletion (DDL)
│ ├── pk/ # Row as directory
│ │ ├── column1 # Individual column value
│ │ └── column2
│ ├── pk.json # Row as JSON
│ ├── pk.csv # Row as CSV
│ └── pk.tsv # Row as TSV (also accessible without extension)
├── .build/ # Create file-first apps
├── .create/ # Create new tables, views, schemas (DDL)
└── .schemas/ # Access non-public schemasMetadata (.info/)
Read-only files describing the table. Check `.info/count` first to choose the right access strategy (see SKILL.md).
| File | Content | Example |
|---|---|---|
count | Total row count | 1000 |
schema | CREATE TABLE statement | CREATE TABLE users (id SERIAL PRIMARY KEY, ...) |
ddl | Extended DDL with indexes | Full DDL including CREATE INDEX statements |
columns | Column names, one per line | id\nname\nemail\nage |
indexes | Index descriptions | PRIMARY KEY: id\nUNIQUE: email |
Mount-Level Metadata (mount/.info/)
| File | Content | Read/Write |
|---|---|---|
user | Current user identity for log entries | Read/Write |
Read "mount/.info/user" # Current identity
Bash "echo 'agent-7' > mount/.info/user" # Change identity at runtimeUUIDv7 Display Format
Log entries, history versions, and other UUIDs use a human-readable display format: 2026-04-07T143000.123Z-zzz0063hd8e5r42 (UTC timestamp + base36 suffix). These are filesystem-safe, case-insensitive, and sort chronologically.
Data Formats
All rows can be read and written in four formats by using the corresponding file extension:
| Extension | Format | Best For |
|---|---|---|
.json | JSON | Structured data, when you need field names with values |
.yaml | YAML | Human-readable structured data, multi-document bulk operations |
.csv | CSV | Tabular processing, data pipelines |
.tsv / none | TSV | Quick inspection, simple text (default) |
These extensions work on individual rows (pk.json), exports (.export/json), and imports (.import/json).
Reading Data
Individual Rows
Read "mount/users/1.json" # Row as JSON (see Data Formats for other extensions)
Read "mount/users/1/email" # Single column value (raw text)Navigating Tables
Directory listings are limited to 1,000 rows by default. Use .all/ to bypass this limit. Note: .all/ does not appear in ls output (to prevent infinite recursion for recursive scanners), but works when accessed directly. Pagination directories (.first/, .last/, .sample/) appear in ls but show empty contents -- navigate directly with a number (e.g., .first/50/).
Glob "mount/users/*.json" # List rows (small tables only)
Glob "mount/orders/.first/20/*" # First 20 PKs (ascending)
Glob "mount/orders/.last/10/*" # Last 10 PKs (descending, most recent)
Glob "mount/orders/.sample/50/*" # 50 random PKs
Glob "mount/big_table/.all/*" # All PKs (bypasses 1,000 row limit)After getting PKs, read individual rows: Read "mount/orders/1.json"
Index Lookups (.by/)
Use .by/ for efficient lookups on indexed columns. Discover available indexes with Read "mount/users/.info/indexes" or Glob "mount/users/.by/*".
Glob "mount/users/.by/email/alice@example.com/*" # Single-column lookup
Glob "mount/users/.by/last_name.first_name/Smith.John/*" # Composite index
Glob "mount/orders/.by/status/pending/.first/50/*" # Index + paginationPipeline Queries
Chain capabilities to build complex queries. Each path segment maps to a SQL clause, executed as a single query:
.by/customer_id/123/.by/status/pending/.order/created_at/.last/10/.export/json
└─ WHERE └─ AND └─ ORDER BY └─ LIMIT └─ formatPrefer .by/ over .filter/ -- .by/ uses indexes and is fast, .filter/ scans the table and is slow on large tables.
Glob "mount/orders/.by/customer_id/123/.by/status/pending/.last/10/*"
Read "mount/orders/.by/status/pending/.columns/id,total/.export/json"
Read "mount/users/.by/status/active/.export/csv"Multiple filters are AND-combined: .by/status/active/.by/tier/premium/ becomes status='active' AND tier='premium'. Note: .by/ and .filter/ support equality only, not range queries.
When listing .filter/column/ values on a large unindexed column, TigerFS returns .table-too-large instead of scanning. You can still access rows directly by specifying the value: .filter/column/value/.
Chaining rules:
| Capability | Can Follow |
|---|---|
.by/col/val/ | .by/, .filter/, .order/, .first/, .last/, .sample/, .columns/, .export/ |
.filter/col/val/ | .by/, .filter/, .order/, .first/, .last/, .sample/, .columns/, .export/ |
.order/col/ | .first/, .last/, .sample/, .columns/, .export/ |
.first/N/ | .by/, .filter/, .order/, .last/, .sample/, .columns/, .export/ |
.last/N/ | .by/, .filter/, .order/, .first/, .sample/, .columns/, .export/ |
.columns/col1,col2/ | .export/ only |
.columns/ selects specific columns before export. Can only be used once.
For CSV/TSV exports, add .with-headers/ to include a header row: Read "mount/t/.export/.with-headers/csv". For imports without a header row, add .no-headers/: Write "mount/t/.import/.append/.no-headers/csv".
Writing Data
All format writes use PATCH semantics -- only specified columns are updated.
Write "mount/users/1/email" with content "new@example.com" # Update single column
Write "mount/users/1.json" with content '{"name":"Alice Smith"}' # Update via JSON (PATCH)
Write "mount/products/new.json" with content '{"name":"Widget","price":9.99}' # Insert new row
Bash "rm mount/users/999" # Delete rowBulk Import
Three import modes control how incoming data interacts with existing rows:
| Mode | Path | Behavior |
|---|---|---|
| Append | .import/.append/json | Insert new rows only. Fails on PK conflicts. |
| Sync | .import/.sync/json | Upsert by primary key. Updates existing rows, inserts new ones. |
| Overwrite | .import/.overwrite/json | Replace all rows. Deletes existing data, then inserts. |
Write "mount/users/.import/.append/json" with content '[{"name":"Bob","email":"bob@ex.com"}]'
Write "mount/users/.import/.sync/csv" with content (CSV with header row)
Write "mount/users/.import/.overwrite/json" with content (full dataset)DDL Operations (.create/, .modify/, .delete/, .indexes/)
Schema changes use a staging workflow:
Bash "mkdir mount/.create/products" # Start a CREATE session
Write "mount/.create/products/sql" with DDL content # Write the SQL
Bash "touch mount/.create/products/.test" # Dry-run (check for errors)
Bash "touch mount/.create/products/.commit" # Apply the change| Path | Purpose |
|---|---|
mount/.create/<name>/ | Create new tables, views, or schemas |
mount/<table>/.modify/<name>/ | ALTER existing table |
mount/<table>/.delete/<name>/ | DROP table |
mount/<table>/.indexes/<name>/ | Create or drop indexes |
Each session has: sql (the DDL statement), .test (dry-run), .commit (apply), .abort (cancel). After touching .test, read test.log for dry-run results and any errors.
Searching
Use Grep for text search across rows. For large tables, prefer .by/ or .filter/ over Grep -- index lookups are O(1), Grep scans every row. Limit scope with glob="*/column" to search specific columns instead of whole rows.
File-First Reference
Reference for file-first mode -- reading and writing markdown and plain text files backed by a database.
Creating Workspaces
Create a workspace when you need a new shared directory of files backed by a database.
Bash "echo 'markdown' > mount/.build/notes" # Markdown with frontmatter
Bash "echo 'markdown,history' > mount/.build/notes" # With versioned history
Bash "echo 'plaintext' > mount/.build/snippets" # Plain text, no frontmatter
Bash "echo 'history' > mount/.build/notes" # Add history to existing workspaceEach workspace creates a directory (mount/notes/) backed by a table in the tigerfs schema. Access the backing table via mount/.tables/notes/. To add file-first access to an existing data-first table: echo 'markdown' > mount/posts/.format/markdown
File Structure
Markdown
Each .md file has YAML frontmatter (from columns) and a body (from the body column):
---
title: Getting Started
author: alice
tags:
- tutorial
- intro
draft: false
---
# Getting Started
This is the body content stored in the text column...Plain Text
Plain text files have body content only, no frontmatter:
This is the entire file content.
No YAML frontmatter is parsed or generated.How Frontmatter Works
Frontmatter fields map to database columns. The write behavior depends on the column type:
- Known columns (e.g.,
title,author): Omitting a key from frontmatter keeps the old value. To clear a field, set it explicitly:title: "" - Headers JSONB (e.g.,
tags,draft-- keys with no dedicated column): Full-replace on each write. Omitting a key removes it from the database. - Body: Always replaced with what you write.
- Timestamps (
created_at,modified_at): File times only -- they don't appear in frontmatter and can't be set via writes.
To see which columns a table has, use Read "mount/.tables/appname/.info/columns".
Writing Files
Standard file operations work as expected: Read, Write, Glob, Grep, mv, rm, mkdir. Key TigerFS-specific behaviors:
Write Example
Write "mount/notes/new-post.md" with content:
---
title: New Post
author: bob
tags: [update]
---
# New Post
Content goes here...See How Frontmatter Works for write semantics (known columns, headers JSONB, body).
Auto-Parent Directories
Writing mount/notes/a/b/file.md auto-creates a/ and a/b/. No need to mkdir first.
Atomic Directory Rename
Bash "mv mount/notes/tutorials mount/notes/guides" -- renames all files within atomically.
Backing Table
Every workspace has a backing table in the tigerfs schema. For data-first access -- indexed lookups, bulk export/import, DDL on the backing table, full row-as-directory navigation -- use mount/.tables/<workspace>/. The file-first workspace path (mount/<workspace>/) presents files; data-first capability dirs (.by/, .filter/, .order/, .columns/, .first/, .last/, .sample/, .export/, .import/, .indexes/, .modify/, .delete/) at the workspace path return ErrInvalidPath with a hint pointing at the .tables/ route.
Read "mount/.tables/notes/.info/schema" # Table schema
Read "mount/.tables/notes/.info/count" # Row count
Read "mount/.tables/notes/.info/columns" # Column names
Glob "mount/.tables/notes/.by/author/alice/*" # Index lookup
Read "mount/.tables/notes/.export/json" # Bulk exportSee data.md for the full data-first reference.
Versioned History
Every update and delete is captured as a read-only timestamped snapshot in .history/. Requires the history feature (see Creating Workspaces).
Each directory has its own .history/ scoped to files in that directory:
Glob "mount/notes/.history/*" # History for root-level files
Glob "mount/notes/tutorials/.history/*" # History for tutorial files only
Glob "mount/notes/.history/hello.md/*" # Versions of a specific file (newest first)
Read "mount/notes/.history/hello.md/2026-02-12T013000Z" # Read a past versionTimestamps are formatted as 2006-01-02T150405Z (filesystem-safe, no colons).
History Across Renames and Moves
Each file has a stable UUID that persists across renames and directory moves. If you rename hello.md to intro.md or move it to archive/, the UUID stays the same and all history follows it.
Read "mount/notes/archive/.history/intro.md/.id" # Get the file's UUID (after rename + move)
Glob "mount/notes/.history/.by/<uuid>/*" # All versions by UUID, including before renameUUID browsing (.history/.by/<file_id>/) is addressable from .history/ at every level (root and subdirectories), and always returns the same rows -- the lookup is keyed only on file_id, so the surrounding directory does not scope the result.
Comparing and Recovering
1. List versions: Glob "mount/notes/.history/hello.md/*" 2. Read the version(s) you need: Read "mount/notes/.history/hello.md/<timestamp>" 3. Read the current file: Read "mount/notes/hello.md" 4. Compare and report differences.
For single-file recovery, look up the file's stable UUID via Read "<dir>/.history/<file>/.id", then find recent edits with Read ".log/.by/file_id/<uuid>/.last/5/.export/json" and undo a specific entry via touch .undo/id/<log_id>/.apply. The file_id route is rename-invariant and indexed; it works for nested files where filename-based lookup cannot (the log's filename column stores /-bearing full paths, and / is the path separator -- a value containing it can't be expressed as a single directory entry). For multi-file rollback to a known state, use touch .undo/to-savepoint/<name>/.apply which handles all affected files atomically. .history/ is best for reading and comparing old versions; use .undo/ for restoring. See SKILL.md "Common Workflows" for the full multi-step agent behavior.
User Identity
Each mount has an optional user identity used for log entries, savepoint auto-injection, and per-user undo filtering. The identity lives at the mount root .info/ (not the workspace-level .info/, which holds backing-table metadata like count/schema):
Read "mount/.info/user" # Read current identity (empty when --user-id not set)
Bash "echo 'agent-7' > mount/.info/user" # Set identity at runtimeSet at mount time: --user-id agent-7 or TIGERFS_USER_ID=agent-7. See ops.md.
Operation Log
Every create, edit, rename, and delete on a history-enabled workspace is recorded in .log/. Each entry has a stable log_id (UUIDv7), the operation type, affected file, and the user who performed it.
Glob "mount/notes/.log/.last/10/*" # Recent entries
Read "mount/notes/.log/.last/10/.export/json" # Recent entries as JSON
Glob "mount/notes/.log/.by/user_id/agent-7/.last/5/*" # By user (indexed)
Glob "mount/notes/.log/.by/type/edit/.last/10/*" # By type (indexed)
Read "mount/notes/.log/.by/file_id/<uuid>/.last/5/.export/json" # By file (indexed)The indexed columns for .log/.by/<col>/<val>/ queries are file_id, user_id, and type (composite indexes paired with log_id ASC). Use these for fast lookups. The filename column is explicitly blocked because it stores /-bearing full paths, and / is the path separator -- such values can't be expressed as a single directory entry. Use the file_id route after Read "<dir>/.history/<file>/.id" for filename-based queries. Other log columns are technically path-addressable but unindexed; prefer the indexed columns above.
Diff Symlinks
Each log entry directory contains before, after, and current symlinks for diffing:
Bash "diff -u --color mount/notes/.log/<id>/before mount/notes/.log/<id>/after" # What this edit changed
Bash "diff -u --color mount/notes/.log/<id>/before mount/notes/.log/<id>/current" # Drift since this editHistory paths are per-directory: tutorials/.history/getting-started.md/ (not .history/tutorials/getting-started.md/).
Log entry IDs use UUIDv7 display format: 2026-04-07T143000.123Z-zzz0063hd8e5r42 (timestamp + base36 suffix, filesystem-safe).
Savepoints
Named bookmarks for undo-to-savepoint operations. Create one before risky edits.
Bash "echo '{\"description\":\"Before investigating bug\"}' > mount/notes/.savepoint/before-investigation.json"Savepoint creation requires a format suffix (.json, .tsv, .csv, .yaml). JSON is preferred for agents. If --user-id is set, user_id is auto-injected.
Glob "mount/notes/.savepoint/*" # List savepoints
Read "mount/notes/.savepoint/before-investigation/description" # Read description
Bash "rm mount/notes/.savepoint/old-savepoint" # Delete savepointAuto-Savepoints
TigerFS automatically creates savepoints when it detects an inactivity gap (default 30 minutes). Named auto-<user>-<timestamp> or auto-<timestamp>. Configure via --auto-savepoint-interval (set to 0 to disable).
Undo
The .undo/ directory provides a preview-then-apply interface for reversing operations.
Three Modes
| Mode | Purpose | Listing |
|---|---|---|
.undo/id/<log_id>/ | Undo a single operation | Summary + apply only (use .log/<id>/before for diffs) |
.undo/to-id/<log_id>/ | Undo all operations after a log entry | Preview tree of affected files |
.undo/to-savepoint/<name>/ | Undo all operations after a savepoint | Preview tree of affected files |
Preview and Apply
# What would undo do?
Read "mount/notes/.undo/to-savepoint/before-investigation/.info/summary"
# Diff all affected files
Bash "cd mount/notes && diff -ru .undo/to-savepoint/before-investigation . -x '.*'"
# Diff since a specific log entry
Bash "cd mount/notes && diff -ru .undo/to-id/<log_id> . -x '.*'"
# Single-file diff (drift since a specific change)
Bash "diff -u --color mount/notes/.log/<id>/before mount/notes/.log/<id>/current"
# Apply undo (destructive -- always preview first)
Bash "touch mount/notes/.undo/to-savepoint/before-investigation/.apply"Per-User Undo
Only undo a specific user's changes, preserving other users' work:
Bash "touch mount/notes/.undo/to-savepoint/before-investigation/.by/user_id/agent-7/.apply"Undo of Undo
Undo operations are logged. You can undo an undo by targeting its log entry. Create a savepoint before a major undo for extra safety.
Timestamps After Undo
When undo restores a row (whether undoing an edit, rename, or delete), the file's modified_at is reset to the restore time, not the original write time. This is intentional: NFS and FUSE clients use mtime to invalidate readdir and getattr caches, so a fresh mtime is what causes restored entries to reappear correctly in ls output. Build tools (make, file watchers) also expect mtime to advance when content changes.
The original timestamp is still recoverable:
- Each write is logged with its real time in
.log/<id>(read.log/<id>/.info/summaryor pull the JSON from.log/.by/file_id/<uuid>/). - Each pre-change snapshot in
.history/<file>/carries the source row'smodified_atat capture time.
If you need to answer "when was this content originally written," use the log or history -- not stat.
CLI Reference
TigerFS CLI commands for mounting, creating, forking, and managing databases.
Mounting
tigerfs mount CONNECTION MOUNTPOINTConnection formats:
tiger:SERVICE_ID-- Tiger Cloud service (requires tiger CLI)ghost:DATABASE_ID-- Ghost database (requires ghost CLI)postgres://user:pass@host:5432/dbname-- direct connection string- Omit connection to use environment variables (
PGHOST,PGUSER, etc.)
Mountpoint is auto-derived from the connection if omitted (e.g., /tmp/mydb).
Key flags:
--read-only-- mount as read-only--schema-- default schema for queries--query-timeout-- global query timeout (e.g.,30s,1m)--foreground-- run in foreground (don't daemonize)--user-id <id>-- user identity for undo log entries (also:TIGERFS_USER_IDenv)--auto-savepoint-interval <duration>-- inactivity gap before auto-savepoint (default30m,0disables)--undo-list-limit <N>-- default listing limit for.undo/sub-directories (default100)
Cloud Backends
TigerFS integrates with cloud database providers for creating, forking, and credential-free mounting. TigerFS auto-detects which CLIs are installed; specify a backend with a prefix (tiger: or ghost:) or set default_backend in config.
| Tiger Cloud | Ghost | |
|---|---|---|
| Best for | Production databases, data-first exploration | Ephemeral/dev databases, file-first workspaces |
| Sign up | https://www.tigerdata.com/cloud | https://ghost.build/ |
| Install CLI | `curl -fsSL https://cli.tigerdata.com \ | sh` |
| Authenticate | tiger auth login | ghost login |
Creating a Database (Cloud backends only)
tigerfs create [BACKEND:]NAME [MOUNTPOINT]Creates a new cloud database and mounts it. Requires a cloud backend CLI to be installed and authenticated.
tigerfs create tiger:my-project # Create on Tiger Cloud, mount at /tmp/my-project
tigerfs create ghost:my-project # Create on Ghost, mount at /tmp/my-project
tigerfs create my-project # Uses default_backend config
tigerfs create tiger: # Auto-generate name
tigerfs create tiger:my-project --no-mount # Create without mountingForking a Database (Cloud backends only)
tigerfs fork SOURCE [DEST]Forks (copies) a cloud database and mounts the fork. Requires a cloud backend CLI to be installed and authenticated. Use fork to safely explore or experiment with existing data without affecting the original.
Source can be:
- A mountpoint path --
tigerfs fork /mnt/prod(looks up backend from mount registry) - A backend reference --
tigerfs fork tiger:SERVICE_ID
tigerfs fork /mnt/prod # Fork mounted database, auto-mount the fork
tigerfs fork /mnt/prod /mnt/staging # Fork and mount at specific path
tigerfs fork tiger:abc123 --name my-fork # Fork by service ID with custom name
tigerfs fork /mnt/prod --no-mount # Fork without mountingWhen to Create vs Fork
| Scenario | Use |
|---|---|
| Starting a new project from scratch | tigerfs create |
| Experimenting with existing data safely | tigerfs fork |
| Creating a dev/staging copy of production | tigerfs fork |
| Branching a shared workspace for isolated work | tigerfs fork |
Status and Info
tigerfs status # List all active mounts
tigerfs info /mountpoint # Detailed info about a mount and its backing service
tigerfs list # Simple list output (for scripting)Unmounting
tigerfs unmount /mountpointConfiguration
Config file: ~/.config/tigerfs/config.yaml
tigerfs config show # Show current configuration
tigerfs config path # Show config file locationKey settings:
default_backend--tigerorghost(used when no prefix specified)default_mount_dir-- base directory for auto-derived mountpoints (default:/tmp)
Recipes
Practical patterns for file-first workflows. Workflow patterns come first (how to work safely), then application patterns (what to build).
---
Workflow Patterns
Recipe 1: Safe Agent Exploration
Create a savepoint before investigating, exploring, or making uncertain changes. Auto-savepoints detect session boundaries automatically.
Pattern: Savepoint, Explore, Review, Keep or Revert
# 1. Create savepoint before starting
Bash "echo '{\"description\":\"Before investigating bug #42\"}' > mount/notes/.savepoint/before-investigation.json"
# 2. Explore and make changes (edits, creates, deletes)
# ... agent works ...
# 3. Review what changed
Read "mount/notes/.undo/to-savepoint/before-investigation/.info/summary"
Bash "cd mount/notes && diff -ru .undo/to-savepoint/before-investigation . -x '.*'"
# 4a. Keep changes (do nothing -- changes are already saved)
# 4b. Revert all changes (after user confirmation)
Bash "touch mount/notes/.undo/to-savepoint/before-investigation/.apply"Auto-Savepoints
TigerFS creates savepoints automatically after 30 minutes of inactivity. If an agent starts working after a gap, an auto-savepoint captures the state before the new session. Named auto-<user>-<timestamp>.
Glob "mount/notes/.savepoint/auto-*" # List auto-savepointsRecipe 2: Compare Approaches with Savepoints
Try two implementations and keep the better one.
# 1. Savepoint the baseline
Bash "echo '{\"description\":\"Clean baseline\"}' > mount/app/.savepoint/baseline.json"
# 2. Implement approach A
# ... agent implements DFS approach ...
# 3. Savepoint after A
Bash "echo '{\"description\":\"DFS implementation complete\"}' > mount/app/.savepoint/after-dfs.json"
# 4. Undo to baseline (clean slate for approach B)
Bash "touch mount/app/.undo/to-savepoint/baseline/.apply"
# 5. Implement approach B
# ... agent implements BFS approach ...
# 6. Compare: B is current, preview A via undo
Read "mount/app/.undo/to-savepoint/after-dfs/.info/summary"
# 7a. Keep B (already current -- do nothing)
# 7b. Keep A instead
Bash "touch mount/app/.undo/to-savepoint/after-dfs/.apply"
# This works because undo-to-savepoint restores the state at that savepoint,
# regardless of what happened after (including B's implementation).Recipe 3: Multi-Agent Selective Undo
Multiple agents work on the same data with separate identities. An orchestrator can selectively undo one agent's changes while preserving another's.
Setup: Separate User IDs
Each agent mounts with its own identity:
# Agent 1: research
tigerfs mount --user-id agent-research postgres://... /mnt/research
# Agent 2: implementation
tigerfs mount --user-id agent-implement postgres://... /mnt/implementBoth see the same data. Operations are tagged with the agent's user_id in the log.
Selective Undo
# Create a shared savepoint
Bash "echo '{\"description\":\"Sprint start\"}' > mount/notes/.savepoint/sprint-start.json"
# Both agents work...
# agent-research explores and edits files
# agent-implement makes changes too
# View changes by user
Read "mount/notes/.log/.by/user_id/agent-research/.last/10/.export/json"
# Undo only agent-research's changes (preserves agent-implement's work)
Bash "touch mount/notes/.undo/to-savepoint/sprint-start/.by/user_id/agent-research/.apply"Caveat: If two agents edit the same file, per-user undo reverts the file to its state before the specified agent's first edit -- which also reverts the other agent's interleaved edits on that same file.
---
Application Patterns
Recipe 4: Task Board
Works as: todo list, kanban board, project tracker, shared queue, work coordination. The core pattern: directories = states, files = items, mv = transitions, author = ownership.
Setup
Bash "echo 'markdown,history' > mount/.build/tasks"
Bash "mkdir mount/tasks/todo mount/tasks/doing mount/tasks/done"Add a Task
Write "mount/tasks/todo/fix-auth-bug.md" with content:
---
title: Fix Auth Bug
priority: high
---
The login endpoint returns 500 when session cookie is expired.Claim a Task
Bash "mv mount/tasks/todo/fix-auth-bug.md mount/tasks/doing/fix-auth-bug.md"Optionally update the file to set author: your-name for ownership tracking.
Complete a Task
Bash "mv mount/tasks/doing/fix-auth-bug.md mount/tasks/done/fix-auth-bug.md"View Board State
Glob "mount/tasks/todo/*.md" # pending
Glob "mount/tasks/doing/*.md" # in-progress
Glob "mount/tasks/done/*.md" # completed
Glob "mount/tasks/**/*.md" # everythingFind Tasks by Author
Grep pattern="author: your-name" path="mount/tasks/" glob="*.md"Multi-Agent Coordination
Multiple agents can read/write concurrently. Each agent sets their name as author when claiming. Use Grep to find what others are working on:
Grep pattern="author:" path="mount/tasks/doing/" glob="*.md"Review Task History
Glob "mount/tasks/.history/doing/fix-auth-bug.md/*"Shows when the task was moved, who edited it, previous content.
Custom States
Use any directory names: backlog/, sprint/, review/, shipped/. The directory IS the state. mv IS the transition. No status columns needed.
Recipe 5: Knowledge Base with History
Setup
Bash "echo 'markdown,history' > mount/.build/kb"
Bash "mkdir mount/kb/architecture mount/kb/debugging mount/kb/conventions"Store a Fact
Write "mount/kb/architecture/chose-jwt.md" with content:
---
title: Chose JWT Over Server Sessions
author: alice
confidence: high
---
## Decision
Use JWT tokens instead of server-side sessions.
## Reasoning
- Stateless -- no session store
- Works across multiple server instancesOrganize by Topic
Directories = categories. Move to recategorize:
Bash "mv mount/kb/debugging/null-bytes.md mount/kb/conventions/null-bytes.md"Search All Knowledge
Grep pattern="authentication" path="mount/kb/"
Grep pattern="confidence: high" path="mount/kb/" glob="*.md"Track Changes
Glob "mount/kb/.history/architecture/chose-jwt.md/*"Read old version vs current to see what evolved.
Suggested Frontmatter
| Key | Values | Purpose |
|---|---|---|
confidence | high, medium, low | How certain |
source | free text | Where you learned this |
supersedes | filename | If this replaces an older fact |
Recipe 6: Session Context (Resuming Work)
Setup
Bash "echo 'markdown' > mount/.build/sessions"Save at End of Session
Write "mount/sessions/2026-02-24-auth-refactor.md" with content:
---
title: Auth Refactor
status: in-progress
---
## Completed
- Migrated to JWT
- Updated /src/auth/middleware.ts
## Next Steps
- Implement refresh token rotationResume at Start of Next Session
Glob "mount/sessions/*.md"
Grep pattern="status: in-progress" path="mount/sessions/" glob="*.md"
Read "mount/sessions/2026-02-24-auth-refactor.md"Naming Convention
Date + topic: 2026-02-24-auth-refactor.md. Use status frontmatter for filtering.
Recipe 7: Activity Log
Append-only log of what agents and users have done. One file per activity, immutable once written. Multiple agents can write simultaneously without conflicts.
Setup
Bash "echo 'markdown' > mount/.build/activity"Log an Activity
Write "mount/activity/2026-03-21T150000.000Z-fixed-auth-bug.md" with content:
---
author: agent-a
type: fix
---
Fixed the auth bug in login endpoint. Changed session cookie handling to check expiry before validating.Use timestamp + description as filename: YYYY-MM-DDTHHMMSS.mmmZ-short-description.md. Timestamps ensure chronological ordering.
Review Recent Activity
Glob "mount/activity/*.md"
Glob "mount/activity/2026-03-21*" # Today's activity
Grep pattern="author: agent-a" path="mount/activity/" glob="*.md" # By agent
Grep pattern="type: fix" path="mount/activity/" glob="*.md" # By type