
My Claude Code Stack: How One IT Company Runs on AI Agents
Benjamin Thomas · April 2026 · 8 min read
I run a lot of Claude Code instances. Some days a handful, some days more than I want to admit. Each one is working on a different project — one's debugging an Azure Function, another's writing SQL migrations, a third is reviewing a PR I haven't looked at yet. They share a single Mac Mini, a consolidated database, and a set of custom tools I've spent months building.
This is how ForIT actually operates. Not the marketing version. The real setup.
The Hardware
One Mac Mini. Apple M4 chip, 10 cores (4 performance + 6 efficiency), 32 GB of unified memory. That's the entire development infrastructure for a company serving aviation clients across North America.
It runs 24/7 as a headless server. I SSH in from wherever I am. No monitors, no keyboards, no GUI. Everything happens in the terminal.
What's running on it on any given day:
- Claude Code instances spread across tmux panes (count varies with what's in flight)
- FastMCP gateway, two instances — local on port 8100, remote on 8101
- OpenMemory + Qdrant for semantic memory
- Shortcuts MCP for Apple Shortcuts control
- Various LaunchAgents keeping everything alive
It costs well under what a cloud workstation with similar capacity would, and I own the hardware.
The Account Switcher
Here's the thing about running AI agents for multiple clients: you need isolation. When Claude is working on Great North Airlines code, it shouldn't have context from WM Aviation leaking in. Different clients, different codebases, different rules.
I have four accounts set up: forit-main, forit-backup, forit-work, pivot-main. Each gets its own .claude.json with separate auth tokens and MCP server configs. Everything else — settings, hooks, plugins — is symlinked from a shared base. Same tools, same guardrails, different identity.
The tooling around this evolved a lot recently. The current entry point is cx — it shows me a picker of accounts and projects, swaps the active account when I pick a different one, and lands me in a persistent tmux dashboard. There's a family of related commands (cxs for single-spawn picker, cxr for resume, cxx to jump straight to the dashboard, cxk/cxa for kill/attach), but cx covers most of what I do.
Full disclosure: this took embarrassingly long to get right. The symlink management alone went through several iterations before I stopped accidentally breaking authentication.
The Session Manager
tmux is the backbone. Not because it's trendy — because when you're SSH'd into a headless Mac Mini and your connection drops, you need your sessions to keep running.
The session engine is aoe — it orchestrates the per-project tmux sessions. Each pane runs a Claude Code instance pointed at a specific project. Sessions survive disconnects, reboots, everything. (It replaced an earlier homegrown manager called tms after that one became hard to maintain. Tooling on this stack gets rewritten as it earns it.)
When my SSH connection from a phone or laptop is unreliable, I use Mosh. There's a separate story about getting Mosh working over UDP/443 with a hysteria2 relay so I can connect from networks that block UDP — that's its own post.
The Hooks
Claude Code supports lifecycle hooks — scripts that fire on specific events. I have ten of them in the repo right now, totaling about 161 KB of Python. They're the immune system for the whole setup.
The Guard Hook (~62 KB)
The biggest one. claude-guard-hook.py intercepts every tool call before it executes and blocks dangerous operations:
- DNS changes require explicit confirmation tokens
- Software installs need consent
- Graph API writes against production mailboxes are blocked (read-only validation only)
- Destructive git operations get caught
- Cross-tenant calls are guarded
The Autonomous Loop Hook (~30 KB) This is the one that makes multi-step work possible. When Claude finishes a task, this hook checks if there's more work to do — open tasks, failing builds, unresolved issues. If yes, it injects a continuation prompt. Combined with TaskCreate/TaskUpdate, this means I can say "fix these five things" and walk away.
The Attention Signal Hook (~18 KB) Newer addition. Routes signals between Claude sessions and external triggers — push notifications, macOS Focus filters — so a long-running session can ping me when it actually needs me, instead of me checking on it every five minutes.
The Context Recovery Hook (~14 KB) When a session crashes or gets compacted, this hook reinjects critical context — previous session summaries, project-specific instructions, service status. The session starts fresh but the agent isn't lost.
The MCP Resilience Hook (~12 KB) MCP servers crash. It happens. This hook catches tool call failures, checks if the server is alive, and triggers automatic restarts via launchctl. The agent retries transparently.
The Prompt Context Hook (~9 KB) Every user message gets preprocessed. This hook injects relevant context: which project am I in, what's the git status, did the last session leave anything broken. It's like giving the agent situational awareness before it even reads my message.
The remaining four hooks (session-start, session-end, verify, compact-reinject) are smaller utility wrappers around lifecycle events.
The MCP Gateway
FastMCP is the brain. One Python server, around 3,000 lines, with about 138 tool registrations spanning a dozen-plus categories — CRM, agile, M365, browser control, desktop control, search, Reddit, 1Password, accounting, trading, fitness tracking, and more.
Two instances run simultaneously — local for direct work, remote for cloud workstation access. Both auto-restart via LaunchAgents.
The Microsoft M365 tools are worth singling out. They handle device code auth, token caching with MSAL, and can execute PowerShell against any ForIT-managed tenant. One tool call to run Exchange commands, create Azure resources, or query SharePoint. No separate az login needed.
The Custom MCP Servers
Beyond the gateway, several dedicated MCP servers run as daemons:
OpenMemory + Qdrant — Semantic memory. Every Claude session can save and retrieve knowledge. "Remember that the forit.io DNS is on Cloudflare" gets embedded and searchable across all future sessions.
Shortcuts MCP — Controls Apple Shortcuts on the Mac Mini. Reads shortcut actions as XML, compiles Cherri source, manages Focus modes. This sounds niche until you realize Shortcuts is how I automate physical-world tasks from Claude.
LinkedIn MCP — Patchright browser automation. Scrapes profiles, companies, job postings, and messaging conversations. The inbox scraper feeds into the CRM's activity timeline. It's the most fragile thing in the stack — LinkedIn's anti-scraping measures are a constant cat-and-mouse, so it spends real time offline between fixes.
The Database
Everything runs against one consolidated Azure SQL database. CRM, productivity, agile, support, HR, quoting, budgets — multiple schemas in one database. No more juggling six different connection strings across six different databases.
Every scheduled function reports to a centralized logging schema. If a sync job fails at 3 AM, I know about it before I wake up.
What This Actually Looks Like
A typical morning:
- SSH into the Mac Mini, run
cx, pick the project I want. - Claude Code lands in a pane. "Check overnight builds, fix anything that failed."
- The autonomous loop hook takes over. Claude reads build logs, identifies failures, pushes fixes, verifies the rebuild. I'm making coffee.
- Switch panes: Great North Airlines ops queries. Claude has direct SQL access to the operations database. "How many flights were delayed yesterday, and what were the top delay codes?"
Meanwhile, the daily intelligence cron is running in the background — scraping Reddit, RSS, and Google News, feeding everything through Claude for summarization, writing client news to the CRM.
I don't type code most days. I describe what I want, review what Claude produces, and handle the decisions that require human judgment. The typing-to-thinking ratio has completely inverted.
The Rough Edges
I'm not going to pretend this is polished.
The guard hook has false positives. Sometimes it blocks legitimate operations because the pattern matcher is too aggressive. I'd rather have false positives than let Claude delete a production database — but it's annoying.
MCP server crashes still happen. The resilience hook handles most of them, but occasionally a Patchright browser session gets wedged and needs manual intervention.
Context window management is real. Multiple concurrent sessions mean multiple context windows, and long-running sessions hit the limit. Compaction loses nuance. The context recovery hook helps, but it's not perfect.
The session and account tooling has been rewritten more times than I'd like to admit. cs became cx. tms became aoe. Whole subsystems have been wired in, then torn out when they stopped earning their keep. The lesson: when tooling is cheap to rewrite, it gets rewritten — and that's mostly good, but it does make blog posts about your stack age faster than you expect.
Who This Is For
If you're running a small IT consultancy and you've been thinking about AI agents but haven't taken the leap — this is what it looks like when you go all in. It's not clean. It's not a product. It's a custom-built development environment that happens to use AI agents instead of human developers for most of the typing.
It works for us because I spent months building the tooling. The hooks, the MCP servers, the account switcher, the session manager — none of that exists out of the box. Claude Code is the engine. Everything around it, I built.
If you're willing to invest the time in tooling, the payoff is real. If you want something that works on day one, this isn't it.
But if you're the kind of person who builds their own tools — and you're reading a blog post about someone else's Claude Code stack, so you probably are — the foundation is more accessible than you'd think. Start with one MCP server. Add one hook. Iterate. The first one changed everything.
Benjamin Thomas
ForIT Team

