Skip to main content

Architecture

Three deployments and a Postgres. They are independent, and the only thing they must agree on is DATABASE_URL and BETTER_AUTH_SECRET.

PathWhat it is
apps/appNext.js front end · :3000
apps/apiNestJS — HTTP, auth, tRPC, Google sync · :3001
apps/agentThe research agent — tools, skills, schedules, sandbox · :2000
packages/dbPrisma schema, migrations, shared Postgres client
packages/authAuth config and the sign-in allow-list
packages/uiShared components and the theme
packages/envFinds and loads the root .env

Intelligence never lives in the API​

This is an agentic-first platform. The API serves HTTP, auth, tRPC and the Google sync. It does not research, enrich, score, summarize, match identities, or decide anything about a person or a company — not as a fallback, not "just the cheap bit", not behind a flag.

Nest's half of the contract is to report that something happened — a thread was ingested, a company was created, an attendee is unknown — and let the agent decide what it means. It writes a task row rather than making an HTTP call: the agent leases work from that table already, so the row is the message, and it survives the agent being down, redeployed, or slower than the request that produced it.

The reason is in the git history: two copies of an identity matcher, one in the API and one in the agent, silently drifted until one matched every employer on earth.

There are no organizations​

Single tenant, deliberately. There is no organization-id on any CRM record, no org context, no org-scoped cache keys. What exists is a singleton workspace — one row whose id is the literal string workspace — there to answer what the company is called, who works there, and what it sells, and for nothing else. An organizationId that is always the same value is a column, an index and a permissions check that buys nothing and reads like a real one at review time.

The work queue​

lib/tasks.ts is the queue. claimDue leases rows with FOR UPDATE SKIP LOCKED, so two dispatchers take disjoint work and a run that dies frees its row when the lease expires. The task rows carry contactId and companyId as plain columns with no foreign key — they outlive the records they name on purpose, so the queue survives a redeploy — which means a record delete has to clear them itself.

The bridge​

The Agent tab talks to the running agent through the app, which is an enforcement point, not a passthrough:

browser  →  /eve/v1/*  (same origin, session cookie)
→ app route: checks the session, strips the cookie,
mints a 2-minute token naming the rep + the record id
→ AGENT_URL/eve/v1/* (verifies the token)

The agent never sees the session cookie. The record travels in the token, never in the message.

The sandbox​

The agent's shell tools run in a /workspace with deny-all egress set on the backend factory so it cannot be forgotten per session. That costs nothing — web fetch runs in the app runtime and web search at the model provider, so retrieval is unaffected. The sandbox is never given DATABASE_URL: CRM access is authored tools in the app runtime, and a shell with credentials and network is exfiltration-shaped even in an internal tool. See Security model.

Hrida.ai is proprietary software of Zlabs Innovation. See the license for terms. © 2026 Zlabs Innovation.