Skip to content

Internal apps

Recon, Hire, Pipeline, Studio and Knowledge are built and run the same way, on the same shared code (internal-kit), so a new internal app starts from the same pieces and anyone who knows one knows the others. This page is the standard. Each app’s own docs/RUNBOOK.md has its specifics (resource names, secrets, commands), and the kit’s docs/apps.md registers every app’s ranges and local ports.

ReconHirePipelineStudioKnowledge
What it isIndustry intelligenceHiring pipelineSales pipeline (CRM)Film productionCompany documents and recordings, cited and searchable
Webrecon.lumiciq.comhire.lumiciq.com (planned)Azure addressstudio.lumiciq.comknowledge.lumiciq.com
MCP for AI appsmcp.recon.lumiciq.com/mcpnoneAzure address, /mcpmcp.studio.lumiciq.com/mcpmcp.knowledge.lumiciq.com/mcp
Public (no sign-in)REST API for other apps, behind API keys (mcp.recon.lumiciq.com/api/v1)webhooks appnonereview.studio.lumiciq.com (review links); REST API for other apps, behind API keys (mcp.studio.lumiciq.com/api/v1)REST API for other apps, behind API keys (mcp.knowledge.lumiciq.com/api/v1)
RepoLumicIQ/reconLumicIQ/hireLumicIQ/pipelineLumicIQ/studioLumicIQ/knowledge
Azurerg-recon-dev, rg-recon-prod (centralus)rg-hire-dev, rg-hire-prod (centralus)rg-pipeline-dev, rg-pipeline-prod (centralus)rg-studio-dev, rg-studio-prod (northcentralus)rg-knowledge-dev, rg-knowledge-prod (northcentralus)
Google Cloud projectrecon-509616 “LumicIQ Recon”lumiciq-hire “LumicIQ Hire”lumiciq-pipelinelumiciq-studio “LumicIQ Studio”lumiciq-knowledge “LumicIQ Knowledge”

Every app takes its shared parts from LumicIQ/internal-kit (~/Development/internal-kit): the Python foundation (lumiciq_kit: settings, database, sign-in and roles, API keys, the MCP connector’s OAuth, the queue worker, spend limits, health), the UI kit (styles, shell, tables, Settings and System pages), the Bicep modules, the deploy and ops scripts, and the CI actions.

  • The kit’s code is copied into each app (tools/sync) and pinned in the app’s kit.toml and kit.lock. Fix a shared piece in the kit, then sync it into the apps; never patch the copy inside one app (tools/check and CI refuse local edits).
  • Before writing something a second app could use (auth, keys, queue, a UI component), look in the kit first; if it’s missing, add it there.
  • Studio keeps its own header, sidebar and look on top of the kit’s styles.
  • Two environments, each its own resource group: rg-<app>-dev and rg-<app>-prod. Pushing to main deploys dev; prod is deployed by hand once dev looks right (gh workflow run deploy -f environment=prod).
  • Container Apps (Consumption) in a VNet, one image for every entry point (<app> web | worker | mcp | migrate …).
  • Postgres Flexible Server with Entra-only auth. The app’s managed identity (mi-<app>-<env>) signs in with a token: no database passwords anywhere.
  • Storage with shared keys turned off and network access limited to the app’s subnet.
  • Logs to Log Analytics, an Application Insights resource, and alerts for failed jobs and for the sign-in check.
  • VNet ranges: Recon 10.60–10.61, Hire 10.62–10.63, Studio 10.64–10.65, Knowledge 10.66–10.67. Pipeline reuses Knowledge’s 10.66–10.67 (it was copied from Hire before Knowledge took them); nothing breaks while the two aren’t peered. The next app takes 10.68–10.69.

A dev environment should cost little more than its fixed parts (about $15–45 a month: Postgres, the environment’s load balancer, a registry, and any file share). Measured in October 2026, Hire dev cost about $17 a month and Recon dev about $53, most of the difference from an availability test that kept waking Recon dev.

  • Apps scale to zero in dev (min replicas 0); prod keeps one replica of anything people wait on.
  • Availability tests, nightly backups and scheduled jobs run in prod only. A test that pings dev every few minutes keeps it awake around the clock.
  • Stop dev’s Postgres when nobody is testing (az postgres flexible-server stop): a stopped server bills only its storage. The deploy script starts it again.
  • Dev gets a small copy of the data, not all of prod’s.
  • Use fake or disabled providers in dev for anything that costs per call (search, models, email).
  • GitHub Actions with OIDC (no Azure secrets in GitHub). scripts/bootstrap.sh <env> creates the deploy identity, the GitHub environment and its variables once.
  • scripts/deploy.sh fingerprints each stage (image, platform, migrations, config) and skips what hasn’t changed; a docs-only commit deploys nothing.
  • Secrets live in GitHub environment secrets and become Container Apps secrets. Nothing is stored in the repo.
  • One Google Cloud project per app, in the lumiciq.com organization: ID lumiciq-<app>, name “LumicIQ <App>”. Nothing is shared between apps, so each app’s sign-in screen shows its own name and one app’s settings can’t break another’s.
  • In that project, Google Auth Platform:
    • Branding: app name “LumicIQ <App>”, support and contact email of the owner.
    • Audience: Internal. Only lumiciq.com Google Workspace accounts can sign in; Google enforces it, and no verification is needed.
    • One web client, “<App> sign-in”, with every callback the app uses:
      • web: https://<app>.lumiciq.com/.auth/login/google/callback and the same on the Azure address (https://<app>-web.<environment domain>/.auth/login/google/callback), per environment
      • MCP (if any): https://mcp.<app>.lumiciq.com/oauth/google/callback and the Azure address variant
  • The app puts the web app behind Container Apps’ built-in sign-in (Google, redirect to login, no excluded paths) and checks the signed-in person again itself: Google sign-in, a lumiciq.com address and the Workspace hosted-domain claim. Admins come from ADMIN_EMAILS.
  • Other Google integrations an app needs (for example Hire’s Gmail client) go in the same project as their own client.
  • The client ID goes in GitHub as the environment variable GOOGLE_CLIENT_ID, the secret as GOOGLE_CLIENT_SECRET. Google shows the secret once, when the client is made.
  • A separate container app (<app>-mcp), public, not behind the web sign-in, with its own OAuth: AI apps register themselves, the person signs in with Google (lumiciq.com checked again), and the app issues its own revocable tokens. Codes go only to Claude, ChatGPT and loopback addresses. Every call is recorded with who made it.
  • People connect with claude mcp add --transport http <app> https://mcp.<app>.lumiciq.com/mcp, or in Claude’s settings as a custom connector.

Company knowledge, and an app’s own files

Section titled “Company knowledge, and an app’s own files”

Every app reads company knowledge from Knowledge instead of keeping its own copy:

  • How: its REST API (KNOWLEDGE_API_URL, KNOWLEDGE_API_KEY in the app’s GitHub environments). Each app has its own key, made with the knowledge repo’s scripts/api-key.sh, and the secret goes straight into the app’s GitHub secret.
  • What a key can do: search, read and propose notes. Two scopes add more: confidential (read confidential notes) and write (add or change approved notes after a person approved the change in the app). Only Recon has them.
  • No copies: an app doesn’t show its own page of what Knowledge holds (Recon has no company-profile page). It reads Knowledge when it needs to and keeps a record of what it used.

Knowledge holds only what other LumicIQ tools could reuse: true things about LumicIQ, its customers and its market. Files made for or about one piece of work stay in the app that owns them and never go to Knowledge:

  • a film’s use-case script or storyboard (Studio’s Materials);
  • a candidate’s resume (Hire);
  • a deal’s proposal (Pipeline).

The test: would another tool be better for finding it, and is it true outside that one piece of work? A demo script with fictional people and numbers fails both, even when it is about a real customer.

  • DNS is in Cloudflare (zone lumiciq.com), DNS only (grey cloud), never proxied.
  • Each hostname needs a CNAME to the container app’s Azure address and a TXT record asuid.<name> with the environment’s domain verification ID. scripts/bind-domain.sh <hostname> <app> prints both, then adds the hostname with a free Azure-managed certificate (a few minutes per hostname).
  1. Repo: make it with the kit’s starter app, never by copying another app’s repo: ~/Development/internal-kit/tools/new-app (name, title, key prefix, the next VNet pair and local ports from the kit’s docs/apps.md). It brings the standard Azure layout, deploy scripts, CI, sign-in, security, UI shell and docs. Register the app in docs/apps.md in the same change.
  2. Azure: scripts/bootstrap.sh dev and scripts/bootstrap.sh prod (needs Owner on the subscription).
  3. Google: create the project lumiciq-<app> “LumicIQ <App>” in the lumiciq.com organization; Google Auth Platform → Get started: app name, support email, audience Internal, contact email; then Clients → Create → Web application with the callbacks above, for both environments. Copy the client ID and secret into both GitHub environments.
  4. First deploy: push to main (dev), then gh workflow run deploy -f environment=prod. The web app stays internal-only until the Google client is set.
  5. Domains: add the Cloudflare records bind-domain.sh prints, bind each hostname, redeploy.
  6. Check: sign in on the real address; for an MCP, connect Claude and call a tool; confirm a non-lumiciq.com account is refused.

Google has no public API for Google Auth Platform branding or web OAuth clients, so step 3 is done in the console (about five minutes). Everything else is scripted in each app’s repo.