The Fort: Anatomy of an AI Workspace
Deep Dive

The Fort: Anatomy of an AI Workspace

A sandbox that grew into an operating system. Not because I planned it — because I kept asking “wait, can we do that?”

Corey Thomas
March 2026
35 min read
fort·map

I’m a designer. I’ve never really learned to code — I just learn enough to get the thing done, whatever it is at the time. Google, forums, Stack Overflow, now Claude. The tool changes. The approach doesn’t.

Late last year I started using Claude Code. Nothing serious — I’d cd into a project, try something, bounce to another folder, come back. My real design iteration was still happening in Figma. Claude was just another tab.

Two things pulled me toward putting everything in one place. ADHD brain — I’d be working on one project and need something from another, and the context-switching was killing me. And trust. If Claude was going to edit files, I wanted it somewhere I could watch what it was doing without risking my actual work.

So I made a folder. Not a plan — a sandbox. And then I’d find an interesting article, or stumble on a tool, and ask Claude “could we do that?” Most of the time the answer was yes. So we’d try it. And then we’d try the next thing. Five months of “wait, we can do that?” and the folder grew teeth.

This is what it looks like now.

Part 1What the Fort Is

It’s a git repo.

Not a framework. Not a CLI on npm. A monorepo that holds everything I work on, plus the infrastructure that makes working on it better. That’s it.

The initial commit was four files. A CLAUDE.md, a basic Orbstack sandbox setup, and the first Beads issue tracker. December 2025. That was the entire “Fort.”

git log — the beginning
git log --oneline --reverse | head -4
d4c22be Initial commit: Claude's Fort workspace with plugins
acba653 Beads: Add repo fingerprint
6798687 Beads: Complete setup with fort prefix
001a439 Fort: Add Orbstack sandbox and documentation

The rule from day one: don’t build infrastructure because it’s cool. Build it because you did the same thing manually three times and got annoyed enough to automate it.

Today it looks like this. Everything under one roof — projects, deploy configs, memory, skills, scratch work. The monorepo pattern means Claude Code can see everything in a single session. Cross-reference a deploy config with a memory file with a project README without switching contexts. That matters more than it sounds.

Part 2The Layers

What’s actually in here

The repo structure:

plaintext
claudes-fort/
├── .claude/          # Hooks, rules, agents, settings
│   ├── hooks/        # 32 shell scripts — deterministic enforcement
│   ├── rules/        # Split CLAUDE.md — guardrails, output, routing
│   └── agents/       # Sub-agent definitions
├── memory/           # Johnny Decimal persistent knowledge (50-78)
├── plugins/fort/     # Skills, commands, tool configs
│   └── skills/       # 55 slash commands
├── logs/             # Daily session streams (YYYY-MM-DD.md)
├── projects/         # Active codebases
│   ├── home-dashboard/
│   ├── analytics-dash/
│   ├── web/          # Work project
│   └── ...20 more
├── deploy/           # Server deployment configs (Docker Compose)
├── scratch/          # Explorations, prototypes, one-offs
│   ├── design-lab/   # Visual experiments (grouped by project)
│   ├── playground/   # Prototypes
│   ├── scripts/      # One-off scripts
│   └── archive/      # Finished scratch work
├── .beads/           # Git-backed issue tracking
└── worktrees/        # Parallel git branches
CLAUDE.mdIdentity, quick reference, directory map
RulesSplit concerns in .claude/rules/
Hooks32 shell scripts across 7 lifecycle events
Skills55 slash commands, loaded on demand
MemoryJohnny Decimal persistent knowledge (50-78)
Services32 containers on a home server
CrewMulti-agent orchestration on a worker node
The Fort's layers, from config to compute

.claude/ — The config layer

Where Claude Code reads its instructions. The main CLAUDE.md sits at the repo root, but the real work happens in .claude/rules/— focused topic files: guardrails.md for security, output-style.md for visual formatting, tool-routing.md for matching what you say to the right skill, workflow-intelligence.md for behavioral conventions that need judgment calls.

Splitting rules has a practical payoff. Edit one concern without touching the others. Claude loads them all automatically. The root CLAUDE.md stays lean — identity, quick reference, directory map — instead of a 2000-line wall of text that Claude half-ignores.

memory/ — Persistent knowledge

Long-term memory. Numbered files using the Johnny Decimal system: 53-claude-code.md, 62-data-tracking.md, 55-notifications.md. Each file holds operational details about a specific topic — not documentation, but things Claude needs to remember between sessions. More on this in Part 3.

plugins/fort/skills/ — On-demand playbooks

55 markdown files, each a slash command. Type /eod, Claude loads the 40-line skill that defines end-of-day cleanup. Type /ship, it loads the chain that orchestrates review, verification, commit, and PR creation. Nothing burns context until invoked. That’s the whole point.

logs/ — Session streams

Every day gets a markdown file: 2026-03-20.md. Three capture layers write to it automatically — git commits, tool usage, and semantic entries like decisions, deploys, blockers. The daily log becomes the source of truth for what happened across all sessions that day. /distill synthesizes it into memory updates.

projects/ and deploy/

Active codebases and their deploy configs. Projects range from a Next.js analytics dashboard to a home automation panel to this blog. Deploy configs are Docker Compose files targeting the home server — a headless Linux box running all the self-hosted services.

scratch/ — The sandbox

Strict convention: no loose files. Everything in a subdirectory, grouped by project, not date. design-lab/ for visual explorations, playground/ for structured prototypes, archive/for finished scratch work so it doesn’t clutter the active directories.

★ Insight

The “no loose files” rule exists because we once had 47 orphaned HTML files in scratch/ with names like test3.html and final-v2-REAL.html. Now everything goes in a subdirectory grouped by project. Small rule. Difference between a workshop and a junk drawer.

.beads/ — Issue tracking

Git-backed issue tracking with short IDs like fort-j0n0. Issues survive session boundaries and compaction — which makes them essential for multi-session work. When context gets compressed, a Beads issue is the lifeline that tells the next session what to pick up.

Part 3The Memory System

Remembering things between sessions

Claude Code sessions are ephemeral. Context windows have limits. Compaction is inevitable. The memory system exists to preserve what matters across the boundary where context just.. evaporates.

The Fort uses Johnny Decimal — a numbering system originally designed for organizing files at work. Here it organizes knowledge. Every topic gets a two-digit number. Those numbers are shared between the Fort’s memory/ directory and an Obsidian vault called BigBrain.

plaintext
# Fort Memory — JD Index (excerpt)

## 50-59 Fort Infrastructure
50  Server & Docker       → 50-server-docker.md
51  Fort CLI & Scripts    → 51-fort-cli.md
52  n8n Workflows         → 52-n8n.md
53  Claude Code Config    → 53-claude-code.md
55  Notifications         → 55-notifications.md

## 60-73 Fort Projects
60  Home Dashboard        → 60-home-dashboard.md
62  Data & Analytics      → 62-data-tracking.md
65  Work Project          → 65-work-project.md
69  Reef Tank Tech        → 69-reef-tank.md

The numbering is the key. Finding about notifications? Goes in 55-notifications.md. Someone references “see 55” in a Beads issue or session log? Same topic, zero ambiguity. That number also maps to a folder in BigBrain. One number, two stores.

Auto-loading the right context

This is one of the more useful patterns. A routing table in MEMORY.md maps file paths to memory files. Claude is about to edit something in projects/analytics-dash/? The workflow rule loads memory/62-data-tracking.md first. Automatic, based on path prefix. No manual prompting. Claude just shows up already knowing the context.

plaintext
# Memory Loading Routes (excerpt)
| Path prefix              | Memory file            | Tab title       |
|--------------------------|------------------------|-----------------|
| projects/analytics-dash/ | 62-data-tracking.md    | fort:analytics  |
| projects/home-dashboard/ | 60-home-dashboard.md   | fort:dashboard  |
| projects/web/            | 65-work-project.md     | fort:work       |
| .claude/ or plugins/fort/| 53-claude-code.md      | fort:infra      |

The routing table also sets tab titles. When you’re running 3 parallel Claude sessions, knowing which is which matters more than you’d think.

Two stores, one numbering system

BigBrain (the Obsidian vault) is the personal knowledge source of truth. The Fort’s memory files hold operational details — API gotchas, deploy targets, config values. BigBrain holds durable knowledge — architecture decisions, learning notes, reference material. When Claude makes claims about a topic, workflow rules tell it to cross-check BigBrain for recent notes. Contradictions get surfaced.

This two-store pattern emerged naturally. I was already using Obsidian. Rather than migrating everything into one place, we let each system do what it’s good at. Obsidian has backlinks, graph view, mobile access. The Fort has session context, routing tables, automatic capture. Forcing one tool to do both jobs would have made both worse.

Part 4Hooks

Rules that can’t be sweet-talked

This is probably the single biggest insight from building the Fort: CLAUDE.md is probabilistic. Hooks are deterministic. Instructions in CLAUDE.md are suggestions — followed most of the time, but under pressure (complex task, deep context, competing instructions), Claude might skip a step. Hooks are shell scripts. They run every time.

Put guardrails in hooks, not in prompts. Agents ignore instructions under pressure. Hooks don’t.

32 hook scripts across six lifecycle events. Three categories: security, workflow automation, quality gates.

Security hooks

Non-negotiable. No amount of context pressure can make Claude commit a .env file when a PreToolUse hook is blocking the action.

plaintext
# Security hooks
guard-env-files.sh       # Hard deny: blocks staging .env files
guard-secrets.sh         # Scans for API keys, tokens, passwords
redact-secrets-bash.sh   # Scrubs sensitive output from Bash results
guard-push-scope.sh      # Prevents pushes to wrong branches
guard-push-rebase.sh     # Blocks force pushes to main
guard-deploy.sh          # Requires confirmation for deploy commands
guard-git-add.sh         # Prevents git add -A (too broad)

guard-env-files.sh is the canonical example. Runs on every git add invocation. Returns {"decision": "deny"}if the target matches an env pattern. Claude can’t talk its way around it. The hook doesn’t care about context or intent — it just says no.

★ Insight

CLAUDE.md said “NEVER write API keys to files” in bold, all caps. Claude followed it… 95% of the time. The other 5% wrote an AWS key to a config file that got committed and pushed. A 10-line hook script took 5 minutes to write and made it 100%. That’s when it clicked: CLAUDE.md is guidance. Hooks are guarantees.

Workflow hooks

Automating the tedious stuff Claude would otherwise need instructions to remember — and sometimes forget.

plaintext
# Workflow hooks
git-post-commit.sh       # Auto-logs commits to session stream
post-tool-stream.sh      # Catches tool-based commits (dedup with git hook)
fort-memory-start.sh     # Loads memory, checks for queued distill
fort-memory-end.sh       # Queues distill if uncaptured changes exist
format-commit-msg.sh     # Enforces commit message conventions
enforce-draft-pr.sh      # Silently adds --draft flag to PR creation
tab-status.sh            # Updates tab title on session events
n8n-event-bridge.sh      # Mirrors events to n8n for workflow automation

Quality hooks

Catching mistakes before they ship.

plaintext
# Quality hooks
async-test-runner.sh     # Runs tests in background after file edits
stop-no-todos.sh         # Warns about unresolved to-dos at session end
stop-lint-check.sh       # Runs linter before session close
precompact-extract.sh    # Saves decisions/insights before compaction
precompact-distill.sh    # Emergency distill before context compression
stop-distill-reminder.sh # Queues distill for next session (never blocks)

The stop hooks have a story. Original design had the stop hook block session close and force a distill run. Terrible UX — sessions hung waiting for distill to finish, and background agent dispatch from a stop hook was unreliable. The fix: make it non-blocking. Stop hook writes a .distill-neededmarker file. Next session’s start hook picks it up. Zero disruption.

Where hooks run today

Claude Code exposes several hook lifecycle events. The Fort uses seven heavily: PreToolUse, PostToolUse, PreCompact, Stop, SessionStart, SessionEnd, and UserPromptSubmit. A deep ecosystem research session in February 2026 formalized the deterministic-vs-probabilistic distinction, and immediately after, hook coverage jumped from 3 events to 7. That research session paid for itself many times over.

Part 5Skills

Load it when you need it

Hooks are the deterministic foundation. Skills are the on-demand intelligence layer. 41 markdown files defining slash commands. Type /brainstorming, Claude loads 30 lines of structured ideation instructions. Type /ship, it loads a chain that orchestrates review, verification, commit, PR creation.

Skills only load when called. Zero context cost until you need them. Context is the most expensive resource you have — treating it like it’s free is how you end up with a 2000-line CLAUDE.md that Claude half-reads.

Daily rituals

Four skills define the daily rhythm:

  • /bod— Beginning of day. Loads context, checks beads, reviews yesterday’s log, sets focus. Has a quick mode for short sessions.
  • /eod— End of day. Finalizes the daily log, sets reminders, runs distill if needed.
  • /distill— Extracts learnings from the session stream into memory files. Runs as a background agent so it doesn’t block the conversation.
  • /pulse— Lightweight status check. Mail, workers, beads, reminders — quick dashboard of what needs attention.

Workflow chains

Individual skills are useful. Chains are where it clicks. Tool routing rules in .claude/rules/tool-routing.md map natural language to skill sequences:

plaintext
# Workflow chains from tool-routing.md
"ship it"       → /ship (review → verify → commit → PR → beads cleanup)
"build feature" → beads check → /brainstorming → /writing-plans → /executing-plans
"finish branch" → /verification-before-completion → /finishing-a-development-branch

A skill transparency rule in CLAUDE.md requires announcing which skill is loading before invoking. Single skill: “Reaching for /brainstorminghere.” Chain: “I’d chain /code-review/verify /commit-push-pr— sound right?” Keeps me in the loop without micromanaging every decision.

Tool routing

One of the denser rules files. tool-routing.mdmaps natural language to the right tool or skill. “Check the data” goes to /analytics. “Check my email” goes to /email. “Optimize this” has a disambiguation rule — images route to /compress, parameters route to /autoresearch.

I say “check the data” and Claude loads the right playbook. 40+ patterns now. Started with maybe 5.

Part 6The Crew

Agents on a spare Mac

The Crew is the multi-agent layer. Runs on a headless MacBook Pro — M1 Max with a busted screen, repurposed as a worker node because.. why not. Crew members are Claude Code sessions running in tmux, each with a defined role and permission set.

The engine behind this is Fort Rules — a permission-enforced orchestration layer wrapping Claude Code. Rather than trusting the agent to follow instructions about what it can and can’t access, permissions are checked at the tool level before every action.

Fort Rules adds crew orchestration on top: rooms (named tmux sessions with shared context), roles (researcher, writer, sysadmin), managers that can dispatch and check on subordinates, and an MCP server for native dispatch from any Claude session. There’s a lot more here — crew orchestration gets its own post.

Crew dispatch
ft crew errand researcher "Survey MCP security landscape"
Dispatched researcher to room 'research-mcp'
Session: research-mcp-a7b3

ft crew list
researcher research-mcp active 12m Survey MCP security...
writer draft-post idle -- (awaiting errand)

The MCP server (plugins/fort-rules/mcp/server.js) exposes eight tools: crew_errand, crew_list, crew_status, crew_peek, crew_dismiss, crew_manager_check, walkie_send, and walkie_inbox. Any Claude session with Fort Rules loaded can dispatch and monitor crew members without SSH-ing into the worker node.

★ Insight

First time we dispatched a crew member to do research, it came back with a beautifully formatted report — about the wrong topic. Task said “investigate auth options” and the agent researched OAuth libraries instead of our internal auth middleware. Now every dispatch includes explicit context: what repo, what files, where to report. Agents are literal in ways that catch you off guard.

The permission layer handles access. The schema defines relationships between users, rooms, crew members, and MCP servers. A researcher gets read access to specific rooms and a defined set of tools. A manager can dispatch errands and review results. Permissions are checked before every tool invocation — the agent never decides its own access.

Part 7Fort Channel

Push, don’t poll

The comms system went through three phases. All within a few months.

Phase one: fire-and-forget. fort-notify sending push notifications via ntfy to my phone. No response path, no context. Just alerts.

Phase two: Fort Mail — a message store with HTTP API on the home server. Agents could send and receive messages, but every consumer had to poll. The crew’s “walkie-talkie” system was originally a separate filesystem-based bus, but that got merged into Fort Mail as a single store.

Phase three: Fort Channel — real-time event push using Server-Sent Events. Two components:

  • fort-collector— Node.js daemon on the worker node. Watches Fort systems (mail, Docker containers, crew sessions, beads issues) and exposes an SSE endpoint.
  • fort-channel— MCP channel server running locally. Connects to the collector’s SSE stream and pushes <channel> tags into Claude Code sessions. Real-time events, no polling.

Discord bot bridges everything to my phone. Per-system channels keep things organized — deploys to #deploys, crew events to #crew, alerts to #alerts. High-priority events get pushed as DMs. Replies flow back through the collector into Fort Mail, closing the loop.

The evolution from polling to push happened in a single day — March 20, 2026. Four phases shipped back to back: foundation, walkie-to-mail merge, agent lifecycle cleanup (which swept 200 ghost agents from Fort Mail), and the full watcher suite. The trigger? A comms audit that revealed Fort Mail had 217 registered agents and zero consumers. Nobody was reading the mail.

Part 8How It Evolved

Four files to an operating system

The Fort didn’t arrive fully formed. Here’s the timeline, traced through git history, memory files, and session logs.

December 2025 — The seed

A CLAUDE.md, an Orbstack sandbox, Beads issue tracking. The idea was just to have a persistent workspace — somewhere Claude Code could pick up where it left off. The initial commit says it all: “Claude’s Fort workspace with plugins.”

January 2026 — Projects move in

Home dashboard. Data analytics tracker. Day-job web work. Memory files appeared to track project context that kept vanishing between sessions. JD numbering happened organically — I was already using it in Obsidian, so adopting it for the Fort was the obvious move.

February 2026 — The ecosystem research

A deep research session on February 20th surveyed 50+ Claude Code community projects, plugins, power-user patterns. This was the inflection point. Finding R-49 — the distinction between deterministic hooks and probabilistic CLAUDE.md — changed how everything got built. Hook coverage jumped from 3 lifecycle events to 6. Security moved from “instructions in CLAUDE.md” to “shell scripts that block unconditionally.”

Other findings that shaped things directly:

  • R-32: Compound Engineering → became the /compound skill for post-work learning extraction
  • R-44: PreCompact hooks → precompact-extract.sh saves insights before context compression
  • R-41: PreToolUse input modification → enforce-draft-pr.sh silently injects --draft on PR creation
  • R-45: Async hooks → async-test-runner.sh runs tests in background after file edits

Late February — Skills explosion

Skill count went from a handful to 30+. Daily rituals formalized (/bod, /eod, /distill). Workflow chains emerged (/ship, /feature-dev). The progressive disclosure pattern crystallized — load 30 lines when needed, not 500 lines always.

Early March — Worker node and Fort Rules

Spare Mac. Busted screen. Still a perfectly good computer. Repurposed as a headless worker node. Fort Rules brought crew orchestration with permission enforcement baked into the tool layer. MCP server shipped, giving any Claude session native access to crew dispatch.

March 2026 — Real-time and convergence

Fort Channel replaced polling with push. Comms stack converged on Fort Mail as the single message store. Discord bridges added mobile and team visibility. Session stream matured into three layers of automatic capture.

The Blueprints system shipped — a chain runner with typed skill graphs that formalized what was previously ad-hoc sequencing. And today, a blog to write about it all.

The Fort today — by the numbers
41 skills (slash commands)
32 hooks (shell scripts across 7 lifecycle events)
27 memory files (JD 50-80)
~20 active projects
217 ghost agents swept (comms audit, March 2026)
501 autoresearch experiments (autonomous parameter optimization)
3 capture layers (git hook, PostToolUse, manual stream)
2 worker nodes (local Mac + headless worker node)
1 single message store (Fort Mail)
Part 9The Philosophy

Evolved, not designed

The Fort has a soul document (notes/soul.md) that defines its character. Not rules. Not config. Character. Five values:

  • Craft over speed— do it well, not just fast.
  • Security by default— trust is earned, not assumed. Graduated permissions.
  • Honesty over compliance— say “this seems wrong” rather than blindly executing.
  • Persistence over perfection— ship something that works, iterate, don’t gold-plate.
  • Teach while doing — share the why, not just the what.

Four boundaries: the Fort doesn’t pretend to be human, doesn’t take autonomous actions it can’t undo, protects Corey’s time by flagging scope creep, and admits when it doesn’t know something.

There’s a companion doc — notes/working-with-corey.md— that captures patterns learned across sessions. Concise over verbose. “We” not “I’ll do this for you.” Course corrections are normal, not failure. Start simple, prove it works, then add complexity.

That’s the core of it. Don’t build infrastructure for its own sake. Build it when the pain is real. The guard-env-files hook exists because a .env file almost got committed. The distill system exists because learnings were evaporating at compaction. The routing table exists because Claude kept loading the wrong memory file.

If you’re building your own version of this — and I think you should, because the patterns matter more than the specific implementation — start with the pain. What context do you keep losing? What mistakes keep recurring? What manual steps are you tired of? Those are your first hooks, your first memory files, your first skills.

Five months in, the initial commit’s bet has paid off. The Fort isn’t the folder I open when I need Claude’s help. It’s the environment where the work happens.

Post · Sawtooth Studio · March 2026