← All posts

PAI: The Operating System I Built Around My AI Assistant

The first real sign I had a system rather than a workflow was when I noticed the assistant failing gracefully.

  • ai
  • developer-tools
  • claude-code
  • infrastructure
  • personal-ai
  • workflow

I noticed it failing.

I’d asked Claude Code to do something complicated — the kind of thing that, six months earlier, would’ve produced a wall of half-correct freeform text, or a confidently wrong answer delivered with the same tone as a correct one. But this time it didn’t do any of that. It loaded a planning document. It ran a structured analysis across multiple steps. It reported the result in a format I recognized. Then it wrote what it had learned into a memory file — a markdown note stored on disk that would survive the session ending — and waited.

I didn’t tell it to do any of that. I’d wired it into the scaffolding weeks earlier and forgotten about it.

That’s when I knew I had infrastructure, not just a workflow.

Personal AI Infrastructure stack

The difference between a workflow and infrastructure

I’ve written about project-level skills before — DevFlow, BugBot, things that live inside a specific repo and know how that repo works. Those matter. But there’s a layer underneath them: configuration that applies no matter what project I’m in, on any machine I sit down at.

I call that layer PAI — Personal AI Infrastructure. It has five pieces:

ComponentWhat It Does
CLAUDE.mdGlobal instructions: operating modes, stack preferences, machine topology
SkillsReusable workflows invoked by slash command across any project
HooksEvent-driven automation that fires on tool use and session events
MemoryPersistent markdown files that survive across sessions
claude-config repoGit-versioned source of truth, CI-deployed to all machines

The repo and deployment mechanics are in a companion post. Here I want to talk about what’s inside it.

Modes: locking the response format

The highest-leverage single thing I did was enforce response modes. Every Claude Code session starts by classifying what I’m asking:

  • MINIMAL — “ok,” “thanks,” short answers
  • NATIVE — quick single-step stuff
  • ALGORITHM — multi-step, complex, or hard

NATIVE mode uses a fixed output template: task, work, change, verify, summary. ALGORITHM mode loads a formal planning document — the kind of structured spec you’d write if you were handing work to a senior engineer — and follows it to the letter. Freeform prose isn’t allowed in either.

This sounds like it would slow everything down. It’s the opposite. When the output format is already decided, the assistant spends zero cycles on how to respond. It classifies and executes. Sessions got faster and more predictable.

I stumbled into this. Early on, I’d get different response structures in different sessions — sometimes useful, usually inconsistent. I’d spend the first five minutes of every session re-establishing how I wanted things formatted. Locking it down in the global config eliminated that entirely.

Skills that travel with you

Previous posts covered skills that live inside a project. User-level skills live in ~/.claude/skills/ — they’re deployed to every machine via the claude-config repo, and they’re available no matter what project you cd into.

I’ve got 43 of them now. They span the full dev lifecycle:

CategorySkills
Dev workflowDevFlow (pipeline enforcer), BugBot (adversarial review), CodeReview
ContentBlogWriter, Media, Art (Excalidraw diagrams), VideoToSpec
ResearchResearch (multi-agent, 4 modes), Investigation, ContentAnalysis
InfrastructureStandupService, DeployOneContext, ChromeMCP, AgentBrowser
AI developmentAgents, Thinking, Prompting, gcc (memory commits)

The distinction matters. These are identity skills — they define how the assistant behaves everywhere, not just in one repo. DevFlow at the user level enforces the same git pipeline whether I’m in a React app or a Python backend. I don’t have to re-teach it.

Hooks: automation that fires without asking

Claude Code supports hooks — scripts that trigger automatically on specific events during a session. I use three kinds:

Security. A pre-tool hook intercepts every shell command before it runs. It blocks patterns that look destructive — mass deletions, force pushes, anything that skips verification hooks. It’s a last-resort guardrail, written as a Bun TypeScript script that executes in under 50ms. If it catches something, the command never reaches the shell.

Audio. A voice hook calls a local notification server to announce which mode the assistant is entering. I hear when it shifts into a complex workflow without having to watch the screen. Useful when I’m pacing.

Memory. A post-operation hook fires after file writes and edits. It harvests significant context into structured markdown using the GCC memory system, so the next session can orient itself from where the last one left off.

The audio one was a wrong turn initially — I had it announcing every tool use, and it was unbearable. I dialed it back to mode transitions only.

Memory: making sessions remember each other

Claude Code doesn’t remember previous sessions by default. Each one starts fresh, with no knowledge of what happened before — total amnesia. The GCC system (described in detail here) fixes that by committing structured context to markdown files in the repo. Those files survive session boundaries and get loaded at startup.

Two kinds of memory:

how the hooks + memory wiring actually works give me the detail

Claude Code hooks are declared in settings.json and executed as subprocesses — they are not prompts, they are scripts. Each hook type maps to a lifecycle event:

// .claude/settings.json (abridged)
{
  "hooks": {
    "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "bun run .claude/hooks/SecurityPipeline.hook.ts" }] }],
    "PostToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "bun run .claude/hooks/WorkCompletionLearning.hook.ts" }] }],
    "Stop": [{ "hooks": [{ "type": "command", "command": "bun run .claude/hooks/ISASync.hook.ts" }] }]
  }
}

The hook receives the tool call as JSON on stdin and can block the operation by exiting non-zero. That’s the entire security model for the bash guardrail: parse the command, match against a pattern list, exit 1 with a reason string if blocked.

Memory retrieval uses BM25 keyword search over the markdown corpus (no embeddings required for fast lookup). At session start, a SessionStart hook calls a MemoryRetriever.ts script that scores all MEMORY/*.md files against the current project context, then @-imports the top matches into the live context window. Semantic organization by topic (one file per concern) is what makes BM25 effective here — a file named debugging.md with dense signal beats a chronological journal every time.

Try it: write a minimal Stop hook that appends a one-line summary to a local log file and wire it in settings.json. After a few sessions you’ll have a readable audit trail of what the assistant actually did — and a foundation to build the full memory system on.

The key insight from the GCC paper is that agents need semantic memory organized by topic, not chronological logs. A file called debugging.md is more useful than a timestamp-sorted journal. I learned this the hard way — my first attempt was a chronological log, and it was nearly useless for retrieval. BM25 over topic-organized files works because the filename itself carries signal about what’s inside.

What I learned

The global layer makes the project layer possible. Project-level skills can assume the global infrastructure is there. When a BugBot loop needs to spawn a parallel review agent, it doesn’t define that agent inline — the user-level Agents skill handles it. Each layer amplifies the ones around it.

Modes stop drift. Without a strict response format, the assistant makes different structural choices in different sessions. Sometimes that’s fine. Usually it’s noise. Locking the format means every session is predictable before it starts.

Infrastructure costs once and pays forever. Setting up the claude-config repo, writing the deploy script, configuring CI runners on GitHub Actions — that was a weekend. Every session since has drawn on it. The ROI compounds.


Tools used: Claude Code by Anthropic, Bun runtime for hooks, GitHub Actions for CI/CD. Source: RooseveltAdvisors/claude-agent-stack.