One Session, One Brain: Building a Full AI Stack Without Leaving the Terminal

An empty directory and a Substack article. Ninety minutes later: a full AI memory stack, two deployed Edge Functions, and two published blog posts. This is the build log: the real commands, the real errors, and what AI-embedded engineering actually looks like.

One Session, One Brain: Building a Full AI Stack Without Leaving the Terminal

An empty directory and a Substack article. Ninety minutes later: a Postgres database with vector search, two deployed Edge Functions, an MCP server connected to three AI clients, 21 seeded memories, an autonomous agent delivering morning briefings, and two published blog posts. All from one Claude Code session.

This isn’t a tutorial. I already wrote the architecture post and the ecosystem post. This is the build log. The real commands, the real errors, and what it actually looks like when AI is embedded in the engineering workflow: not answering questions in a chat window, but pair-programming in a terminal.

The Starting Point

The directory was /Users/steven/Documents/AI/Github/OpenBrain. It was empty. My machine had Node.js, a homelab Kubernetes cluster, and a text editor. No Supabase CLI. No Deno. No plan beyond a single input: Nate B. Jones’ Open Brain guide on Substack, plus roughly 200 community comments full of bug reports, workarounds, and fixes.

The goal: build a personal AI memory layer, something that captures thoughts from Slack, stores them with vector embeddings, and exposes them over MCP so that every AI client I use can search the same brain. The Substack guide laid out the concept. The comments told me where it would break.

What AI Did vs. What I Did

Here’s the honest split.

AI handled:

  • Installing the Supabase CLI via Homebrew
  • Writing both Edge Functions, ingest-thought (Slack webhook processor) and open-brain-mcp (MCP server with four tools), from scratch, with security hardening baked in from the start
  • Generating the database schema, the match_thoughts search function, and the pgvector index
  • Managing secrets via supabase secrets set
  • Deploying both functions to Supabase
  • Migrating 21 memories from my existing life-hub system into Open Brain
  • Writing and publishing two blog posts through the Ghost Admin API
  • Creating the full README with setup instructions

I handled:

  • Creating the Supabase project (browser, no API for this)
  • Creating the Slack app and configuring its permissions (browser)
  • Running supabase login (interactive terminal, more on this below)
  • Pasting credentials when prompted
  • Approving every action before execution

The ratio: AI handled roughly 90% of the keystrokes. I handled 100% of the decisions and all browser-based service provisioning. This is the part that gets lost in the “AI is replacing developers” discourse. Nobody is getting replaced. The cognitive load just shifted: from syntax and boilerplate to architecture and judgment.

The Errors That Actually Happened

This is the part I care about most, because it’s where the real engineering happened.

1. supabase login fails in non-TTY

Claude Code runs commands in a sandboxed shell. The supabase login command tries to open a browser and wait for an OAuth callback, which doesn’t work when the shell isn’t a full TTY. No error message. It just hung.

Fix: I generated an access token manually at supabase.com/dashboard/account/tokens and passed it as an environment variable. Ten seconds, problem solved. But it’s the kind of thing no tutorial mentions because tutorials assume you’re in a normal terminal.

This one was stubborn. The Supabase CLI is a Go binary, and its HTTP client consistently timed out on TLS handshake, even though curl to the same API endpoint worked fine from the same shell. We tried four times, cleared proxy environment variables, checked DNS resolution, verified the API was reachable. Same timeout every time.

Root cause: a known Go HTTP client TLS issue on macOS in certain sandboxed shell environments. The fix wasn’t technical: I just opened my own terminal and ran the command there. Took five seconds. We’d spent ten minutes debugging.

This is the kind of moment that matters in an AI-assisted workflow. The AI diagnosed the likely cause (Go’s TLS stack behaving differently than the system’s OpenSSL), but it couldn’t fix a binary it didn’t compile. The human intervention was minimal but essential: open a different terminal, paste the command, move on.

3. OpenRouter API key corrupted by line break

This one was subtle. After deploying the functions, the first Slack capture attempt failed with:

Error: Invalid header value: "Bearer sk-or-v1-ee821da67ace02e76d44e8b9cacf1d2df9cb1b9eb30256d
  a0abfc8af9de6824b"

See that line break in the middle of the API key? The terminal word-wrapped the key during supabase secrets set, splitting it across two lines. But that wasn’t all: the key contained a ! character, and because I used double quotes, zsh interpreted it as history expansion and silently mangled the value.

Fix: re-set the secret using single quotes instead of double quotes, then redeployed. The lesson: always single-quote secrets in zsh. Always.

4. Three missing secrets: silent failure

After fixing the API key, things still weren’t working. Running supabase secrets list revealed only 6 of the expected 9 secrets were set. Three (MCP_ACCESS_KEY, SLACK_SIGNING_SECRET, and SLACK_CAPTURE_CHANNEL) had been set in my terminal session but apparently never persisted. No error on the set command. No warning. Just silently missing.

The AI diagnosed this from the Edge Function error logs: “Invalid header value” with a newline suggested a missing or malformed secret, not a code bug. It re-set the missing secrets and redeployed both functions. First Slack message came through clean thirty seconds later.

Every one of these errors was diagnosed in real-time from symptoms, not retried blindly. That distinction matters. An AI that retries the same failing command ten times is useless. An AI that reads the error message, identifies the root cause, and proposes a targeted fix is a force multiplier.

The Security Hardening Story

Before writing a single line of code, the AI read all 200 Substack comments on the original guide. Not skimmed. Read. Here’s what the community had already found:

  • Open ingest endpoint: the original guide had no authentication on the webhook. Anyone who discovered the URL could inject memories into your brain. Fix: HMAC-SHA256 Slack signature verification on every request.
  • Duplicate entries from Slack retries: Slack retries webhook deliveries if it doesn’t get a 200 within 3 seconds. The original code processed synchronously, often exceeding that timeout. Fix: immediate 200 response with async processing, plus a unique constraint on slack_event_id.
  • Zod version that doesn’t exist: the guide specified [email protected]. That version was never published. Mark Madsen in the comments caught it. Fix: [email protected].
  • Route matching bug: Jay Smith identified that the MCP server’s route handler needed app.all("*") instead of specific path matching to work with mcp-remote clients.
  • Accept header incompatibility: Karin Byom found that some MCP clients send Accept headers that the original code rejected. The fix was permissive header handling on the server side.
  • Multiple additional fixes: Pokemon Is Awful (yes, that’s a username) contributed several Edge Function corrections that prevented runtime crashes.

None of these were copy-paste fixes. Each one required understanding why the original code failed, what the correct architectural response was, and how to integrate it without breaking other functionality. The AI handled the implementation. I validated the reasoning. The community provided the field data.

The Meta Moment

Here’s where it gets recursive. My Ghost blog runs on the same homelab Kubernetes cluster. Publishing a blog post means generating a fresh JWT, calling the Ghost Admin API, and routing it all through kubectl exec into the Ghost pod, because HTTP-to-HTTPS redirects in Ghost strip POST bodies, so the API call has to originate from inside the container.

The blog post about Open Brain was written by the same Claude Code session that built Open Brain. The AI that deployed the memory system then used the memory system’s architecture as source material for the blog post. The blog post was published through the same Kubernetes infrastructure that hosts the blog. The recursion wasn’t planned. It was just what happened when you don’t close the terminal between building and writing.

What This Means for Engineering Workflows

I want to be precise about this, because the discourse around AI and engineering is mostly vibes.

I made every decision. I provisioned every service. I approved every deployment. The AI never had unsupervised access to anything that mattered: no credentials it could use independently, no ability to push to production without my explicit approval.

But the ratio of thinking to typing shifted dramatically. Before this session, building an MCP server from scratch would have been a weekend project, not because the code is complex, but because of the yak-shaving: installing the right CLI versions, debugging configuration mismatches, reading docs for three different APIs, wiring up authentication, handling edge cases. With AI embedded in the workflow, I spent my time on the parts that actually required engineering judgment:

  • Should the ingest endpoint verify Slack signatures or use a simpler API key? (Signatures, because the endpoint is public.)
  • Should the MCP server handle streaming or just request-response? (Both: MCP spec requires SSE support.)
  • Should I store raw embeddings or normalized? (Raw: pgvector handles cosine similarity natively.)
  • Should I respond to Slack before or after processing? (Before: Slack’s 3-second timeout causes retry storms.)

The human-AI split wasn’t “AI does the easy parts.” It was “AI handles the execution surface while I handle the decision surface.” When the TLS issue blocked CLI access, I didn’t debug Go networking. I ran the command myself. When secrets silently failed, the AI diagnosed it from error messages and fixed it. Each of us operated where we were most effective.

What’s Next

The system is live. I’ve been capturing thoughts through Slack for a few weeks now, and the morning briefings from my life-hub agent are already surfacing connections I missed: threads between work problems and side project ideas, patterns in what I’m reading, recurring themes in what I choose to capture.

The interesting part isn’t the technology. It’s what happens when you give an AI enough context about how you actually think. That’s a post for another day.

The code is on GitHub. The architecture deep-dive is here. The ecosystem walkthrough is here.