A.I. Portal
An internal AI command center that gives a small real-estate team shared, permissioned access to curated Claude agents, multi-agent pipelines, and direct media generation…
- 41
- curated agents at MVP launch, expanded since
- 18
- ready-made multi-agent pipelines across business, education, and agency workflows
- 27
- Jinja templates covering the full UI surface
- $50
- default per-user monthly spend cap
A private command centre for one team, behind Workspace sign-in. Its screens are full of live agent runs and spend, so the architecture is shown instead.
- 01Interface
- HTMX
- vanilla CSS (no JS build step)
- 02Edge
- Cloudflare Tunnel
- 03Identity
- Authlib + Starlette sessions (Go…
- 04Application
- FastAPI
- Anthropic Python SDK (streaming)
- 05Data
- SQLite
- Voyage AI embeddings (voyage-3-l…
- 06Operations
- Docker Compose
What is shown instead is the verified stack by layer. Layers, not connections — which service calls which is not something a dependency list can prove. Hosts, ports and topology are deliberately absent.
Problem
A small real-estate team needed broad, everyday access to Claude for content generation, research, and operational workflows. The only path available was sharing one person's personal account or API key across everyone. That meant no per-person permissions, no usage accountability, no persisted history, and no way to chain agents into a repeatable workflow.
Prompting a raw model from scratch for recurring tasks — a listing launch, a lead follow-up sequence, a weekly social plan — meant every team member reinvented the same prompt structure by hand each time. There was no shared library of what actually worked, and no way to enforce brand voice or a company-specific SOP on the output. Generated media (images, video, voice, design assets) lived only at the vendor's own expiring link once created, with nowhere the team could reliably find a past generation again.
Separately, an earlier iteration of the tool had grown a leads/deals layer that started duplicating the team's actual CRM. That created two competing systems of record for the same data.
What was built
The A.I. Portal is a private web app. The team logs in with their company Google account, and each person sees only the agents their role grants them — content, brand, transactions, client relations, and so on — organized into categories like Make, Generate, Reach, and Transact.
Conversations persist and are searchable. Agents can be chained into ready-made pipelines for common workflows: new listing launch, warm-lead workup, weekly social plan, client onboarding. The portal also generates images, video, voice, and design assets directly through several vendor APIs. It pulls every finished asset into its own storage instead of linking out to a link that will eventually expire.
An admin view shows spend by day and by user against a monthly cap. A small brand-voice checker flags banned marketing phrases as an agent's output streams in.
Beyond single-agent chat, the portal supports scheduled jobs (both one-off agent runs and full pipelines), a searchable internal knowledge base a chat can optionally draw on, workspaces that bundle context around a specific piece of work, an A/B prompt-tuning surface for iterating on agent instructions, and scoped programmatic API tokens so other internal tools can call an agent without a person in the loop. It also installs as a mobile PWA and accepts voice dictation on any chat page.
Technical approach
The service is a single FastAPI/HTMX app with no client-side build step. It runs on SQLite for conversations, knowledge, scheduled jobs, and API tokens, deployed in Docker Compose on an owned Mac Mini behind a Cloudflare Tunnel, not a cloud host. Authorization is entirely server-side: a USER_ROLES mapping assigns each user role tags, a ROLE_AGENTS mapping expands roles into allowed agent names, and every single agent endpoint — both browser and the token-based /api/v1/... surface — checks the resolved permission set before invoking anything. There is no client-side trust boundary.
Programmatic access uses eap_-prefixed tokens stored only as a SHA-256 hash; the plaintext is shown once, at creation. Inbound webhooks authenticate purely via an unguessable per-inbox URL token instead of a header, on the assumption that external systems triggering the webhook often can't attach custom headers.
Direct media generation is built as a common adapter pattern per vendor: Leonardo, Ideogram, Higgsfield, HeyGen, Suno, Gamma, and Canva via OAuth. Each vendor is gated behind a "not configured" state until its key exists, so a missing key degrades the feature instead of crashing the app. Canva needed a second-generation flow using OAuth 2.0 with PKCE, not a static API key, and its PKCE state had to be stored server-side keyed to the admin instead of in the session cookie — the session cookie does not survive the cross-site redirect back from Canva's authorization page.
A later architectural addition, asset centralization, was driven by a concrete failure mode. Generated media was originally just linked from vendor CDN URLs, which expire. Every completed generation is now downloaded server-side into local storage and served from the portal's own /generated/file/{id} route, falling back to the original URL only in metadata.
The knowledge base layers a real embedding model (Voyage AI, 512-dim) over SQLite FTS5 keyword search as a fallback, so semantic search degrades gracefully instead of failing outright if the embedding call is unavailable. Webhook delivery uses exponential backoff — 1s, 4s, 16s, 64s, 256s — before a subscriber is marked dead after five failed attempts. The scheduler for both single-agent and pipeline jobs runs as a simple in-process asyncio loop on a 60-second tick, matching the app's single-host deployment scale over a separate job-queue service.
Creative approach
Craft
Categories map directly to the shape of the team's actual work — Make, Generate, Polish, Reach, Learn, Nurture, Transact, Deliver, Build — instead of to underlying model or vendor names, so a non-technical user picks a workflow verb instead of a tool. Streaming output is checked live against a small brand-voice denylist (banned phrases like "luxury," "stunning," "must-see," "won't last"), so drafted copy gets corrected before it ever reaches a person's outbox. Every per-employee onboarding guide was produced in both English and Spanish to match the actual team.
The interface itself is deliberately unglamorous: FastAPI plus HTMX with vanilla CSS and no JavaScript build step. That keeps the surface simple enough for a solo builder to extend quickly without a front-end toolchain, and it matches the internal-tool nature of the product instead of aiming for a polished consumer look. Vendor integrations that aren't configured yet show a plain "not configured" state instead of erroring or disappearing, so the full capability surface stays visible to a user even before every key is wired up.
Reframe
The defining product decision was negative. An earlier version of the portal had accumulated a leads/deals layer, and the team removed it explicitly instead of letting it calcify, codifying "AI hub, not a CRM" as a standing rule enforced in the project's own operating documentation. That discipline then generalized outward: it is cited as the direct precedent for a later, separate project drawing the same boundary against its own CRM instead of rebuilding contact and deal tracking a second time.
The second reframe was treating generated media as an asset the product owns, not a link it forwards. Once a generation completes, the file itself — not a pointer to someone else's CDN — becomes the artifact of record.
Process and what failed
The Canva integration needed a second design pass once OAuth entered the picture. State meant to survive a cross-site redirect couldn't live in the session cookie the way every other integration's config did, and had to move to server-side storage keyed to the admin instead. The asset-centralization feature — pulling every generated file into local storage — was itself a reaction to an earlier version of the product that just linked to vendor URLs and would silently break once those links expired. That failure mode was discovered in production use, not caught in design.
Webhook delivery reliability required building out a full retry-with-backoff-then-dead-letter policy. Simpler fire-and-forget delivery had proved insufficient for external systems that occasionally reject or drop a call. The clearest reversal was structural, not technical: a leads/deals feature was built directly into the portal, used, and then deliberately torn back out once it became clear the team now had two places tracking the same contact. The fix was documented as a standing rule against ever re-adding it, not just a one-time cleanup.
Outcome
The portal has been in daily production use by the team since its initial MVP deploy, authenticated through Workspace-restricted Google OAuth. It has grown through multiple minor-version releases — integrations, workspaces, pipelines, scheduling, knowledge base, spend dashboards, asset centralization, Canva — instead of staying static after launch. It replaced ad-hoc shared-account access to Claude with per-person permissions, persisted history, and visible spend accounting, and its "no CRM layer" boundary has since been reused as an explicit design precedent on at least one other internal project.
What began as a single-shot agent-runner MVP is now a multi-surface internal platform spanning chat, scheduled automation, direct media generation, and programmatic API access. All of it runs self-hosted on owned hardware behind a private tunnel, not a third-party SaaS subscription per seat.
Related work
- Internal2026WebshooterA site crawler that walks every page and interaction of a website and generates a documentation-quality HTML gallery and printable PDF manual, with a before/after screenshot…
- Internal2026ChrysalisA self-hosted CRM and transaction-management platform for real-estate teams, built to replace Follow Up Boss and Sisu on team-owned hardware.
- Prototype2026Evolve AI StudioA campaign-production pipeline that turns one brief into a validated multi-shot creative spec and assembled multi-format exports, built end to end as a walking skeleton with…