Storage

Every file agentty writes, where it lives, how long it stays, and how to move it.

Everything agentty writes, where it goes, how long it stays, and how to move it.

agentty config prints this for your machine, with real paths and sizes.

commandwhat it does
agentty configevery folder, where it is, how big
agentty config <name>one folder in detail: where it's read from, where it's written, how long things stay
agentty config envevery variable, and its current value
agentty config move <dir> <path>move a folder and its data, safely (below)
agentty config doctorcheck every folder is usable, secrets are private, no override is silently ignored
agentty config cleanlist files older versions left behind; --yes deletes them

Two roots

agentty keeps its files in two places.

rootfollowsdefaultmove it with
user rootyou~/.agentty$AGENTTY_HOME
project rootthe code<project>/.agentty$AGENTTY_PROJECT_DIR

The project is the nearest folder above where you started agentty that has a .git, .agentty, .hg or .svn in it. So cd src && agentty uses the same project root as running it at the top of the repo. If there is no marker, the start folder is the project. / is never one.

When $AGENTTY_PROJECT_DIR is set, every project gets its own subfolder under it, named <folder>-<hash>, so two checkouts never share an index.

What lives where

User root, ~/.agentty (0700)

pathwhatgrows?kept
settings.jsonyour settingsnountil you change them
settings.json.locklock so two instances don't clobber each othernoalways
agentty.running.lockheld shared by every running agentty, so config move can tellnoalways
credentials/ (0700)sign-ins and API keys: credentials.json (Claude), accounts.json, provider-keys.json, embed_keys.json, copilot/codex/kimi files, each 0600nountil you sign out
threads/ (0700)conversations: <id>.jsonl (the log), <id>.ofs (offsets), <id>.meta.json, index.json (picker cache), <id>.transcript.md (exports)yesuntil deleted, or AGENTTY_THREADS_KEEP_DAYS
threads/blobs/images and big tool outputs, shared between threads by content hashyesswept daily once no thread uses them (24 h grace)
memory.jsonlfacts saved with remember, user scopeslowlyuntil forgotten
state/approval hashes: skills_approved.json, hooks_approved.json, mcp_approvals.jsonnountil revoked
cache/modelsdev.json (model catalog), update_check.json, copilot_model_support.jsonnorefetchable, safe to delete
logs/agentty.log, agentty.log.old, agentty-diagnostics.txt, agentty-flight.txtcappedlog rotates at 32 MB, one old copy kept
mcp.json, hooks.jsonyour MCP servers and hooksnohand-edited
skills/, agents/, commands/, AGENTS.mdyour skills, subagents, slash commands, global guidancenohand-edited

Project root, <project>/.agentty

It comes with a .gitignore of *, so none of it gets committed by accident. Delete a line from it to start tracking something.

pathwhatkept
cache/rag_code.<tag>.ragdb, cache/rag_docs.<tag>.ragdb (+ .meta.json)search indexes. <tag> names the embedder, so switching one never reads vectors from anotherrebuildable. the newest 1 old variant is kept so switching back is cheap
state/rag_feedback.tsvwhich search results helped, so ranking learnsuntil deleted
memory.jsonlfacts saved with remember, project scopeuntil forgotten
mcp.json, skills/, agents/, commands/project configcommitted by you if you want

A project mcp.json starts processes, so it only runs after you approve its exact bytes. The approval lives in the user state/, so a repo can never approve itself.

Read but never written

  • .agents/ and .claude/ (project and user): skills, agents and commands in the shared markdown format. Same files, no conversion.
  • ~/.claude.json: only through agentty mcp import --from claude.

Outside both roots

  • hook payloads: $TMPDIR/agentty_hook_<pid>_*, 0600, removed after the hook runs.

Moving things

Storage is configured with environment variables only. Nothing in agentty, including the TUI, changes where your data lives. Set them in your shell profile:

export AGENTTY_THREADS_DIR=/mnt/big/agentty/threads
export AGENTTY_LOGS_DIR=~/.local/state/agentty
whatvariable
whole user rootAGENTTY_HOME
threadsAGENTTY_THREADS_DIR
credentialsAGENTTY_CREDENTIALS_DIR
approvalsAGENTTY_STATE_DIR
cacheAGENTTY_CACHE_DIR
logsAGENTTY_LOGS_DIR
whole project rootAGENTTY_PROJECT_DIR
search indexesAGENTTY_RAG_DIR
search feedbackAGENTTY_PROJECT_STATE_DIR
thread retention, daysAGENTTY_THREADS_KEEP_DAYS
one MCP config fileAGENTTY_MCP_CONFIG (a file, not a dir)

Rules, the same for all of them:

  • empty means unset
  • a relative path is relative to its root, never to your current folder. AGENTTY_LOGS_DIR=logs2 is always ~/.agentty/logs2
  • if the folder can't be made, you get one warning naming why, and the default is used
  • credentials and threads are forced to 0700 wherever they go
  • a search-index or project-state override pointed at one shared folder gives each project its own <name>-<hash> subfolder inside it, so projects never share an index

Moving existing data

Setting a variable points agentty at a new folder; it doesn't bring your data along. config move does that part:

agentty config move threads /mnt/big/agentty/threads
# then add what it prints to your shell profile:
export AGENTTY_THREADS_DIR='/mnt/big/agentty/threads'

agentty config move threads default moves it back and tells you which variable to unset. It:

  • refuses while any agentty is running (every session holds a shared lock on agentty.running.lock; the kernel drops it if agentty crashes, so there is never a stale one)
  • renames when the target is on the same disk, which is instant
  • otherwise copies, then checks the file count and byte total match before touching the original. If they don't, the copy is removed and nothing changes
  • refuses a target that isn't empty, or that is inside the source
  • keeps credentials and threads at 0700
  • never edits a config file. It only moves data and prints the line to add

Keeping it small

Threads are the only thing that grows without a limit. To expire them:

export AGENTTY_THREADS_KEEP_DAYS=90

Once a day, threads with no activity for that many days are deleted, along with their files. A thread open in a running agentty is never touched. Their blobs go in the same pass if nothing else uses them. 0 or unset keeps everything, which is the default.

Other things that clean themselves:

  • blobs: once a day, anything no thread points at and older than 24 h
  • search indexes: old embedder variants beyond the newest one, after a save
  • logs: rotate at 32 MB

Files from older versions that nothing reads now (an old ~/.cache/agentty, *.thread.ragdb, routing_memory.tsv, settings.json~) are listed by agentty config clean and deleted by agentty config clean --yes.

Backing up

Copy ~/.agentty. That's everything except per-project indexes, which rebuild on their own. If you moved a directory out, copy that too; agentty config shows where each one is.

Leave out credentials/ if the backup goes somewhere less private than your home folder.

For contributors

One rule: every path is declared once, in include/agentty/config/inventory.hpp, and nothing else builds one. A dirs::Spec names the root, the leaf, the override variable and the retention. config_inventory_test fails the build if a file declares its own. agentty config prints the same table the code reads, so the two can't disagree.

  • user dirs resolve through include/agentty/util/user_root.hpp
  • project dirs through agentty::dirs::resolve (include/agentty/dirs/dirs.hpp)
  • settings dirs keys map to variables in src/util/storage_env.cpp, applied at the top of main() before any thread starts
  • new storage: add a Spec to the inventory, a key to storage_env.cpp, and a row to the tables above

Design notes and history: docs/design/data-dirs.md, docs/design/dot-agentty.md.