Skip to content

Storage and filesystem layout

What lives on disk vs. in postgres, and how to plan for it.

Two roots

Trimus writes to two filesystem roots:

Variable Default What goes there
TRIMUS_HOME $HOME/.trimus/ Per-company / per-agent / per-project directories
TRIMUS_STORAGE_DIR ./storage Uploaded attachments and assets

Both should be on persistent disk in production. A single Trimus install can host many companies; the layout below is per-company under TRIMUS_HOME.

TRIMUS_HOME layout

$TRIMUS_HOME/
├── companies/
│   └── <company-id>/
│       ├── files/                           Company-scope files
│       ├── skills/                          Company-scope FS skills
│       │   └── <slug>/
│       │       ├── SKILL.md
│       │       └── skill.yaml
│       ├── projects/
│       │   └── <project-id>/
│       │       └── files/                   Fallback project workspace
│       │                                    (when project.working_dir is unset)
│       └── agents/
│           └── <agent-id>/
│               ├── files/                   Per-agent files
│               ├── skills/                  Per-agent FS skills
│               ├── work/                    claude_local working directory
│               │                            (used when no project working_dir)
│               └── bootstrap/
│                   └── credentials.env      Hired-agent API key bootstrap

companies/<id>/files/

Generic file storage scoped to the company. Visible at the Files sidebar entry in the UI, accessible via /api/companies/{id}/files. Mostly used for shared reference material agents need.

companies/<id>/skills/<slug>/

FS skills shared across every agent in the company. Each is a directory with SKILL.md + skill.yaml. Editable through the UI (Skills → click into a skill) or directly on disk; changes take effect on the next agent run.

companies/<id>/projects/<id>/files/

The Trimus-managed fallback workspace for a project. Used when project.working_dir is not set. Files agents write via /write-file land here. Operators rarely touch these directly — they're managed through the project's UI page.

When project.working_dir IS set, the workspace resolves to that operator-managed path instead, and these directories aren't created.

companies/<id>/agents/<id>/

Per-agent home. Contains the agent's per-agent files, per-agent FS skills, and a work/ directory used as the CLI's CWD by the local and sandboxed CLI adapters (claude_local, codex_local, claude_sandbox, codex_sandbox) when an issue has no project working_dir.

The bootstrap/credentials.env file is created when an agent is hired by another agent — it holds the new agent's API key so its first heartbeat can authenticate. Sensitive: 0600 perms, do not log.

TRIMUS_STORAGE_DIR layout

$TRIMUS_STORAGE_DIR/
├── attachments/
│   └── <issue-id>/
│       └── <attachment-id>/<filename>
├── assets/
│   └── <asset-id>/<filename>
└── <company-id>/
    └── logos/<uuid>                          Company logo image bytes

Attachments are uploaded files attached to issues. Assets are company-scoped reference uploads. Logos are raster company brand images for the UI (PNG/JPEG/GIF/WebP — SVG is rejected). See branding.md.

Total size scales with usage. The default TRIMUS_MAX_UPLOAD_SIZE (25 MB per upload) is the per-file cap; nothing limits aggregate size besides your disk.

Per-company / per-agent home overrides

Both companies and agents have an optional home_dir override. When set:

  • The company's resolved home becomes that absolute path instead of $TRIMUS_HOME/companies/<id>/.
  • An agent's resolved home becomes that path instead of the default-under-the-company.

Use cases:

  • Multi-host setups where different companies live on different block-storage volumes.
  • Compliance: keeping one company's data physically isolated from another's.
  • Migrating from another tool: pointing a company's home at an existing directory tree.

Changing home_dir does not move existing files. The old path remains on disk; the new path is just where future writes go. If you need to migrate, copy first and then update the override.

Permissions

The OS user running trimus-server needs:

  • Read + write + execute on TRIMUS_HOME and TRIMUS_STORAGE_DIR.
  • Read + write + execute on every operator-managed project.working_dir for any project where agents will write.

Recommended: dedicated trimus user, TRIMUS_HOME owned trimus:trimus with mode 750, files written 0600. The shipped Dockerfile already sets this up under /data/.

Disk planning

Rough sizing for a small team running ~10 agents on 50 issues/day:

  • Database: 500 MB / month with default retention.
  • TRIMUS_HOME: 100 MB baseline + project workspaces (depends on what agents write — could be tiny, could be GBs).
  • TRIMUS_STORAGE_DIR: depends on attachment volume — typically small unless humans are uploading lots of binaries.

Monitor disk on both roots. The clean failure mode (disk full → write errors → run failures) is loud, but you'd rather catch it ahead of time.

Backups

The filesystem roots need to be backed up alongside postgres. A postgres-only backup is not enough to fully restore — you'll have rows referencing files that don't exist.

Three things to back up:

  1. Postgres (see database.md).
  2. TRIMUS_HOMEtar it nightly, or use filesystem-level snapshots if your storage supports them.
  3. TRIMUS_STORAGE_DIR — same.

Restore order: postgres first, then files. Trimus gracefully handles "row references a missing file" with 404s rather than crashing, but you'll see broken downloads until the files come back.

Cleanup

Trash

Soft‑deleted skills / home entities move into $TRIMUS_HOME/trash/ (organised by type, e.g. trash/company-skills/<company>/<slug>), each with a trash-info.json recording the original path. The restore path is validated so a tampered trash-info.json can't redirect a restore outside its expected scope (SEC‑025). The Admin → Trash (/admin/trash) page lets instance admins inspect, restore, or hard‑delete early.

Run-event pruning

heartbeat_run_events rows in postgres age out after TRIMUS_RUN_EVENTS_RETENTION_DAYS (default 30) for succeeded runs; failed‑run events are kept indefinitely. The associated heartbeat_runs records have their own retention (TRIMUS_HEARTBEAT_RUN_RETENTION_DAYS, default 90).

Manual cleanup

If a company is being decommissioned:

  1. Delete the company through the UI (admin only). This CASCADEs in postgres and queues the on-disk tree for trash.
  2. After the trash retention window, the on-disk tree is removed.
  3. To force-remove immediately, hard-delete from Admin → Trash.

Where to next