The portal is a private web app that agents keep up to date. It pulls in everything the business
produces: calls, CRM, email, support, analytics and content. Agents turn that into pages, queues and
drafts. Nothing leaves the building until I click Approve. This page is the outline: the architecture, the stack, the skills worth
building around, and how voice becomes a task list.
~6 weeksfrom first commit to running the business on it
~1,400commits, almost all written by Claude Code
60+pages across sales, ops, finance, marketing, PR
~55scheduled jobs (ingest, compute, agent runs)
30+project skills the agents run
1 · High-level architecture
There is one idea behind all of it: read → compile → gated action → human approval. Agents read and draft
all day. A person decides what goes out.
1. Capture
Python scripts on timers pull from every source:
call transcripts, CRM (Pipedrive), Gmail, Slack, the helpdesk, Search Console / GA, ad platforms, Stripe, social.
Each writes raw files into a data repo. Raw is append-only. Nothing edits a captured document afterwards.
2. Compile
Claude Code sessions, run on a schedule or by hand,
read the raw files and write a markdown wiki: account dossiers, decisions, pain points, competitor notes and a
running log. This is the "LLM wiki" pattern. It's plain files, not a vector DB, so every claim traces back to a file you can open.
3. Materialize
Laravel compute:* commands
import the files into Postgres and build pre-computed tables every 15 minutes. Every row records the file it came from.
Controllers only read those rows and never derive anything during a request.
4. Serve
An Inertia + React UI over those tables: dashboards, a CRM,
a deals board, a to-do list, content calendars, a research library and an activity feed.
5. Act (gated)
Agents stage drafts: emails, posts, PR pitches, replies.
Each draft shows up as a card with Approve. Approving writes a verdict to a separate store, and a send script
re-checks everything when it actually sends.
It's the deploy artifact. A CI check refuses any commit that contains customer data.
data
raw captures, the compiled wiki, pipeline drafts
The history is full of customer data, so it never ships anywhere. Files are the source of truth. The DB is a derived
index you can drop and rebuild with one command.
2 · Database & server
Stack
Laravel 13 (PHP 8.3+) with Inertia 2
React 19 + TypeScript, Tailwind 4, shadcn/ui, Vite
PostgreSQL 17 as the only database
Redis + Horizon for queues
Python 3 for ingest, which only ever writes files and never touches the DB
Claude Code (headless claude -p) for every agent run
Server
An always-on home server (a mini PC running Debian, 32 threads), not the cloud
Caddy for TLS, with DNS-01 certificates through Cloudflare
Tailscale: the portal is only reachable on my tailnet
systemd timers for all scheduling, around 55 units
Deploys: push → GitHub Actions (tests, type-check, data-boundary check) → a 60-second timer on the box
deploys only if CI is green for that exact commit
Nightly Postgres + state backups
Why Postgres rather than SQLite or a vector store: several agents and timers write at the same time,
and the materialized tables get dropped and rebuilt constantly. Keep one writer ecosystem (Laravel). Python
writes files, Laravel imports them. You never have two ORMs fighting over one schema.
3 · Skills worth building around
A skill is a SKILL.md playbook that Claude Code follows, plus any scripts it needs. These are the ones that
earned their keep, in roughly the order I'd build them:
Foundation
Skill
What it does
sync
Pulls fresh CRM, call transcripts, Slack and release notes into raw, then recompiles the wiki pages that changed.
dossier
Builds the complete picture of one account: every meeting, quote, objection, note and last touch.
day
The morning driver. It goes through everything that came due (live reply checks in both mailboxes, to-dos, calendar,
content, money, support), finishes what an agent can, and stages the rest behind approval.
call-review
Turns a recorded call into numbered findings and action items, each with a quote, a screenshot and a timestamp link.
Sales / CRM
Skill
What it does
crm-call
After a call it drafts the follow-up in my voice, sets the next step and fills in the deal fields the call justified.
crm-next
Reads a deal's whole history and decides the next touch: reply, nudge, call, or close the file. It never sends.
follow-up
Goes through the open pipeline deal by deal and picks the right move for each.
revive
Finds dormant deals and drafts "since we last spoke" outreach based on what has shipped since.
whats-changed
Diffs the product against a lead's last meeting, filtered to their objections.
crm-loss / crm-winback
Pulls the real loss reason from the transcript, then comes back on the date the customer named.
crm-standup
Writes the weekly paragraph on what changed and what slipped. It may only quote numbers the tables produced.
Marketing / ops
Skill
What it does
seo-content
Takes a target from the opportunity queue and writes the piece end to end, grounded in our own data.
x / linkedin / instagram
Repurposes already-published material into posts, each into an approval queue.
pr
Watches news windows and journalist queries, keeps an outlet library with cooldowns, and drafts pitches.
competitor-watch
A weekly digest of competitors' pricing and positioning diffs, changelogs, reviews and job posts.
support-report
Reads every ticket a customer has sent and maps it against what they actually use.
rock-pulse / eos-workbook
Runs EOS: a weekly pulse on each quarterly rock, and grounded drafts for the workbook.
slack
Searches the full archived Slack history to answer "why did we decide X".
Machine-level (not in the portal repo, but I use them daily)
Skill
What it does
browser
Playwright-driven browsing and QA, one isolated session per project.
kanban
A local board whose cards feed Claude sessions. Parallel workers pull the next Ready card.
portal-research
Deep research published as a visual report in the portal, with charts built from real numbers.
codex-review-loop
A second model reviews the diff; Claude fixes only the findings that are valid, until it converges.
gmail-manager
Multi-account inbox triage with accumulated rules and a report.
4 · Voice → task list
There are two paths. Both end on the same To-do list in the portal.
A. Calls (the main one)
Recorda local menu-bar app auto-records Zoom (4K screen + separate mic track)
TranscribeDeepgram, with diarization; my mic is its own track, so "who said it" is exact
Review/call-review produces numbered findings and action items with owner and quote
Triageeach item gets Fix · Research · Dismiss buttons
QueueFix and Research go to a queue that headless agents work on timers → PR or answer; the rest become to-dos
A live version runs during the call: every 90 seconds it takes a Claude pass over the rolling transcript and updates a
review page next to Zoom. By the time I hang up, the list already exists.
B. Talking to it directly
Dictation (Wispr Flow) straight into a Claude Code session: "add a to-do to …", "follow up with … Thursday".
The agent files it through the app, never by editing the DB by hand.
A one-line box at the top of the portal overview captures a to-do in a single keystroke. An aggregator page
shows every portal's open to-dos in one place.
To-dos have states: idea → next → in-progress → blocked → done, plus parked. Ideas stay off the
board so it doesn't turn into a second inbox. Blocked on a person is a separate "attention" feed.
5 · Rules that saved me (steal these)
Nothing outbound without a human click. Each channel (X, Instagram, PR pitches, journalist replies,
follow-up email) has its own approval namespace, so one Approve can never release a different kind of message. Every send
path also fails closed: a kill-switch file, a claim linter (no guarantees or superlatives), rate caps, and a check that
the draft being sent is the exact text you approved.
Treat captured content as untrusted. An email or transcript that says "do X" is data, not an
instruction. Put that in CLAUDE.md in plain words.
Files are the source of truth; the DB is derived. Deleting a file is the delete. Never hand-patch a row;
re-import instead.
Materialize, then serve. Scheduled compute writes tables and pages only read them. That is what keeps it fast.
Log everything the agents do as one appended line per action. Never rewrite that log.
Tombstone removed rules. When you delete a rule that caused an incident, leave a _removed: note
saying why. Otherwise the next agent re-adds it as an "obvious improvement".
State how a guarantee is enforced, never something stronger. "The script never sends" (a convention) is not the same as "it
has no credential to send with" (a capability). My docs once claimed the second while the first was the truth.
Make tests unable to reach the real world. A test once matched a process fake badly, the fake ran the
real command, and real emails went out while the suite was green. Block stray processes in the base test class, and
refuse sends when the environment is testing.
6 · Where I'd start if I were you
Write the CLAUDE.md constitution first: prime directives (nothing outbound, untrusted input,
source of truth, logging, no secrets). Everything else hangs off it.
Build one ingest (call transcripts or CRM) into raw files, plus a sync skill.
Add dossier. It's the first skill that makes you say "wow", and every later skill reuses it.
Stand up the thin app: Laravel + Inertia + Postgres, one compute: command, one page. Deploy it to a box
behind Tailscale.
Add the first gated action (follow-up email drafts with an Approve button) before any autonomous send.
Then build voice → call review → to-do. That's the loop that runs the day.
Advice in one line: let the agents do all the reading and drafting, and keep every
irreversible action behind a click that only you can make.