How Trellaris is built, and why it is built that way. The through-line is a
single rule: there is one path that runs an agent. Every
surface, every trigger, and every integration converges on it, which is what
lets one governance decision be authoritative everywhere instead of being
re-implemented per feature.
The rules the code obeys
Rule
What it buys
One execution path
Chat, automations, channels, the orchestrator, A2A and MCP all call the same executor. One event bus, one usage ledger, one set of governance checks — no surface can drift into a second set of semantics.
Tenancy is not optional
Every repository extends a base that force-injects the organization filter and the visibility predicate. There is no raw database access outside a repository, so a query cannot return another tenant's row.
Strict layering
Router handles HTTP, service holds business logic, repository owns all data access. No logic in routers, no queries outside repositories.
Modules talk through services
A module never reads another module's tables or imports its models. Cross-module references are a UUID plus a service call, so a module can be reasoned about — and changed — on its own.
Dependency inversion at every seam
Model providers, storage, the sandbox, policy providers, tool discovery, identity — each sits behind an interface with a deterministic default. Swapping a real backend in is configuration, not a rewrite, and CI can run without any of them.
One error contract
Every failure is a problem document with a stable machine-readable code — including terminal events on a stream — so clients handle errors identically wherever they arise.
Immutability where it matters
Agent versions, skill versions, and canvas revisions are immutable with a pointer to the current one. Rollback moves a pointer; nothing is mutated or lost.
How the pieces fit together
The functional picture: what an agent is made of, what it is granted,
where requests come from, and what a run leaves behind.
Functional composition. Requests enter from any surface, pass governance, and reach the agent — the one entity that carries identity, holds the tool grant, and forms the policy boundary.
Reading the diagram
The workspace and ring are the outer frame: every entity inside belongs to one organization and is visible only within its ring.
The agent is the only thing that runs. Skills, knowledge, and memory are attached to it; tools, connectors, and MCP servers are granted by it.
Governance sits on the way in, not beside it. A surface cannot route around it, because the surface's only route to the agent goes through the executor.
The run record is what makes the whole thing auditable after the fact — events for replay, an artifact set, a cost line, and audit entries.
Canvas appears as a surface because that is what it is: a shared document the agent writes to and the human edits back, with each revision immutable.
What happens on every run
The same six steps regardless of what triggered the work. The right-hand column
is where governance actually lands — each check is at a specific point, not a
general aspiration.
Steps 4 and 5 loop until the agent has an answer or hits its turn cap. A parked approval suspends the loop here, with its state saved, and resumes it on a decision.
What the loop actually does
Assembly is per run, not per agent. The tool registry is rebuilt every time from the declaration plus configuration, so a policy change or a revoked credential takes effect on the next run without touching the definition.
The system prompt is composed from the guardrail preamble, the agent body, a compact skill index, and injected context — memories, knowledge citation rules, attached documents, and a canvas diff if the human edited past the agent.
The model turn streams. Tokens, tool calls, and status land on the run's event stream as they happen, so the interface is live and the same sequence can be replayed later.
Tool results are treated as data. They are screened before re-entering the model — this is where a prompt-injection attempt planted in a fetched page is caught.
Parking is real suspension. Loop state is serialized to the run row; the worker is released. Approving reconstructs the loop and executes the call exactly once.
Nothing is left hanging. Workers hold a heartbeat lease. If one dies, a reaper resolves the run — resuming a background run from its last checkpoint, or failing it with a reason.
Deployment topology
Four planes and one ingress. Each service scales independently, and the
documentation site you are reading is its own container with no runtime
dependency on the API.
Outbound traffic originates from the API, the worker, and the gateway. The sandbox has no network at all, so nothing a model writes can call out from inside it.
What each plane is responsible for
Plane
Contains
Scales on
Service
The Next.js web app (which also proxies /api and /docs in development), the FastAPI application, the MCP gateway as its own deployment, and this documentation container.
Request volume; the gateway independently, since it is the external face.
Async
Celery workers on per-class queues — runs, scans, ingest — plus the beat scheduler for dispatch, sweeps, and rollups, and one ephemeral sandbox container per code execution.
Run volume and scan/ingest backlog.
Data
PostgreSQL with pgvector for rows and embeddings alike, Valkey for queues and per-run event streams, object storage for documents and artifacts, Keycloak as the identity broker.
Managed externally at the team and enterprise tiers.
External
Model providers, MCP servers, SaaS systems reached through connectors, and A2A peers.
—
Why the sandbox is a separate concern
Code the model wrote runs in a throwaway container with no network, a non-root
user, dropped capabilities, capped memory, CPU, process count and wall clock,
and no package managers installed. Files move in and out as archives rather
than host mounts. In production the container runtime class swaps to a
stronger isolation boundary — a configuration change, not a code change.
Module map
The API is one deployable but twelve owned boundaries. Each module owns its
tables and exposes a service; nothing reaches across.
Module
Owns
Code
Identity & tenancy
Organizations, teams, users, workspaces, API keys, SCIM, the visibility primitive
Rollups, dashboards, budgets, SLOs, showback, the opportunity miner
analytics/
Platform
Database session and base repository, migrations, crypto, storage, cache, telemetry
platform/ · workers/
Data model highlights
Structure
Why it is shaped that way
Immutable versions + a current pointer
Rollback is a pointer move, promotion can copy and freeze a known-good version, and a diff between any two versions is always available.
A run state machine
A transition table is the single gate on every status write, so parked, resumed, reaped and cancelled runs can never reach an undefined state.
A per-run event stream
One append-only sequence serves live streaming, the task tree, and later replay — the interface and the audit view read the same data.
Message parts
A message is a list of typed parts (text, tool call, artifact update, data card), so approval prompts, connect cards, canvas updates, and citations all persist without schema churn.
A usage ledger
One row per run, including delegated children and remote invocations, so a cost figure is derived from records rather than estimated.
Policies as rows
Kind, scope, and config in one table read by one engine — governance is data, editable at runtime, not code that needs a release.
Embeddings alongside rows
pgvector keeps semantic search in the same database and the same transaction as the visibility filter, so search can never leak what a query would not return.
Extension seams
Every external dependency sits behind an interface with a deterministic
default, which is why the whole test suite runs with no cloud account and no
model provider.
Everything Trellaris depends on is OSI open source, and CI enforces an
allowlist on every build so a proprietary transitive dependency fails the
pipeline rather than shipping. Licenses below are as published by each
project; the build's own check is the authoritative gate.
API — runtime
Package
License
Used for
fastapi
MIT
The HTTP application and OpenAPI schema
uvicorn[standard]
BSD-3-Clause
ASGI server
pydantic, pydantic-settings
MIT
Schemas, validation, typed configuration
sqlalchemy[asyncio]
MIT
ORM and query construction
alembic
MIT
Database migrations
asyncpg
Apache-2.0
Async PostgreSQL driver
pgvector
MIT
Vector column and index support
celery
BSD-3-Clause
Background workers and the beat scheduler
redis
MIT
Queues, run event streams, rate limits, semaphores
Evaluation assertions and approval-edit validation
qrcode
BSD-3-Clause
The qr_code tool
langgraph
MIT
The orchestrator's supervisor graph
langgraph-checkpoint-postgres
MIT
Durable graph checkpoints
psycopg[binary]
LGPL-3.0
Driver for the checkpointer
hatchling
MIT
Build backend
Agent-format package
Package
License
Used for
pydantic
MIT
Frontmatter schema and validation
python-frontmatter
MIT
Splitting YAML frontmatter from the markdown body
pyyaml
MIT
YAML parsing
API — development and CI
Package
License
Used for
pytest, pytest-asyncio
MIT · Apache-2.0
Test runner and async support
anyio
MIT
Async test utilities
moto[s3]
Apache-2.0
Mock S3 for storage tests
respx
BSD-3-Clause
httpx request mocking
ruff
MIT
Linting and formatting
mypy
MIT
Strict static typing
pip-licenses
MIT
The CI license allowlist gate
boto3-stubs[s3]
MIT
Type stubs
Web application
Package
License
Used for
next
MIT
The application framework and BFF proxy
react, react-dom
MIT
UI runtime
next-auth (Auth.js)
ISC
Sign-in, session handling, the assertion exchange
next-themes
MIT
Light and dark theming
@monaco-editor/react
MIT
The definition editor
@radix-ui/react-slot
MIT
Composable component primitives
class-variance-authority
Apache-2.0
Component variant styling
clsx, tailwind-merge
MIT
Class-name composition
lucide-react
ISC
Icons
react-markdown, remark-gfm
MIT
Rendering agent responses
tailwindcss, tailwindcss-animate
MIT
Styling system
shadcn/ui
MIT
Component source vendored into the app
Web — development and CI
Package
License
Used for
typescript
Apache-2.0
Type checking
eslint, eslint-config-next
MIT
Linting
prettier, prettier-plugin-tailwindcss
MIT
Formatting
postcss, autoprefixer
MIT
CSS pipeline
@playwright/test
Apache-2.0
End-to-end golden-path tests
@types/*
MIT
Type definitions
Security scanners
Pinned binaries baked into the API and worker image, each reporting its own stage.
Tool
License
Stage
Opengrep
LGPL-2.1
Static analysis, against Trellaris-authored OWASP LLM Top 10 rules
gitleaks
MIT
Secret detection in skill bundles
Trivy
Apache-2.0
Optional container and npm dependency audit
pip-audit
Apache-2.0
Python dependency CVE audit
Why Opengrep and not Semgrep
The Semgrep engine is open source, but its community rule registry moved to a
source-available license with commercial-use restrictions. Trellaris uses the
LGPL fork with its own rule set, so the whole scanning path stays OSI open
source.
Sandbox image
Preinstalled so sandboxed scripts start fast and work entirely offline — the
container has no network, so nothing can be installed at run time.
Package
License
numpy, pandas
BSD-3-Clause
matplotlib
PSF-based (Matplotlib licence)
pillow
MIT-CMU (HPND)
python-dateutil
Apache-2.0 and BSD-3-Clause
requests
Apache-2.0
beautifulsoup4
MIT
reportlab
BSD-3-Clause
fpdf2
LGPL-3.0
pypdf
BSD-3-Clause
Infrastructure
Component
License
Role
PostgreSQL
PostgreSQL Licence
Primary datastore
pgvector
PostgreSQL Licence
Embedding storage and similarity search
Valkey
BSD-3-Clause
Queues, run streams, rate limits — chosen over Redis to keep the stack permissive
MinIO
AGPL-3.0
Local S3-compatible storage; run unmodified as a service, and replaceable with S3, Azure Blob, or an Apache-2.0 alternative