Skip to content
AI systems & agentsLive2026

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
ArchitectureGated system — no screenshot
Interface
  • HTMX
  • vanilla CSS (no JS build step)
Edge
  • Cloudflare Tunnel
Application
  • FastAPI
  • Anthropic Python SDK (streaming)
Data
  • SQLite
  • Voyage AI embeddings (voyage-3-l…
Operations
  • Docker Compose
Supporting
  • Authlib + Starlette sessions (Go…

A.I. Portal holds live data, so this shows the verified technology stack by layer rather than a screenshot. 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, but the only path available was sharing one person's personal account or API key across everyone, with 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) also meant every team member reinventing the same prompt structure by hand each time, with 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, creating two competing systems of record for the same data.

What was built

The A.I. Portal is a private web app the team logs into with their company Google account, where each person sees only the agents their role grants them (content, brand, transactions, client relations, etc.), 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), and the portal also generates images, video, voice, and design assets directly through several vendor APIs, pulling 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, and 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, backed by SQLite for conversations, knowledge, scheduled jobs, and API tokens, running in Docker Compose on an owned Mac Mini behind a Cloudflare Tunnel rather than 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), and inbound webhooks authenticate purely via an unguessable per-inbox URL token rather than 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 gated behind a "not configured" state until its key exists rather than crashing; Canva specifically required a second-generation flow using OAuth 2.0 with PKCE rather than a static API key, and its PKCE state had to be stored server-side keyed to the admin rather than in the session cookie, because 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, so 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 rather than 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, and the scheduler for both single-agent and pipeline jobs runs as a simple in-process asyncio loop on a 60-second tick rather than a separate job-queue service, matching the app's single-host deployment scale.

Creative approach

Craft

Categories map directly to the shape of the team's actual work (Make, Generate, Polish, Reach, Learn, Nurture, Transact, Deliver, Build) rather than 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, and 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, which keeps the surface simple enough for a solo builder to extend quickly without a front-end toolchain, and matches the internal-tool nature of the product rather than 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 rather than let 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 rather than rebuilding contact and deal tracking a second time. The second reframe was treating generated media as an asset the product owns rather than 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 — a failure mode discovered in production use, not caught in design. Webhook delivery reliability required building out a full retry-with-backoff-then-dead-letter policy after simpler fire-and-forget delivery proved insufficient for external systems that occasionally reject or drop a call. The clearest reversal was structural rather than 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, and has grown through multiple minor-version releases (integrations, workspaces, pipelines, scheduling, knowledge base, spend dashboards, asset centralization, Canva) rather than 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 self-hosted on owned hardware behind a private tunnel rather than a third-party SaaS subscription per seat.