Develop: Overview
AcasoOS is the foundational layer for building agents and the data foundation inside a company. The platform ships the runtime — tenancy, identity, memory, pipelines, the agent loop, the console — and client work happens on a small set of extension surfaces. You compose on top of the OS; you do not fork it.
This section is written for two audiences with the same needs: human engineers, and coding agents implementing AcasoOS for a client. Every instruction names exact paths and commands so it can be executed without tribal knowledge.
AcasoOS is domain-general. Client implementations span HR, sales, support, client-facing end users, third-party partner networks, and more — the platform privileges none of them. Everything domain-flavored in the tree today (the examples/hr_skills and examples/perf_evidence templates, the example agents) is reference material: copy it to start your own work, activate it only in dev/demo tenants, and never ship it as part of a client implementation.
The development model
One monorepo (acaso-os) is the single source of truth. All client-facing code — including code bespoke to a single client — lives in that repo, in namespaced locations, and ships inside the same versioned release. There are no client forks: a fork drifts, misses security fixes, and breaks the upgrade path. Bespoke code is isolated by namespace, not by repository.
Two deployment models consume those releases:
- Shared multi-tenantmany clients on acaso-hosted infrastructure, isolated per row by Postgres RLS. A new client is a new tenant. See Provision a new client.
- Standalone single-tenantthe whole stack in the client's own cloud, one tenant per instance, same code path and same container image. See Standalone deployment and Upgrades.
Where development happens
These are the blessed extension surfaces. Anything outside them is core platform and changes via the normal spec process (specs/), not during client work.
| Surface | Location in the repo | Activated by |
|---|---|---|
| Agents (client-bespoke) | packages/agenthub/src/agenthub/custom/<client_slug>/ | Explicit ACASO_AGENT_MODULES env var |
| Cultivator Kinds (client-bespoke) | packages/datahub/src/datahub/templates/custom/<client_slug>/cultivators/ | Tenant binding (only the matching tenant can bind) |
| DataHub templates (productized catalog) | packages/datahub/src/datahub/templates/<family>/<name>/ | tenant_template_state via the admin API |
| Cultivator Kinds (catalog) | packages/datahub/src/datahub/templates/<family>/<name>/cultivators/ | Tenant binding via POST /v1/cultivators |
| End-user apps (L3) | packages/apphub/ (@acaso/react SDK) | npm install in the client app |
| Examples — agents (copy, never ship) | packages/agenthub/src/agenthub/examples/ | Dev-default ACASO_AGENT_MODULES only |
| Examples — templates + Kinds (copy, never ship) | packages/datahub/src/datahub/templates/examples/ | Explicit activation, dev/demo tenants only |
No catalog template ships in-tree yet — sales/*, eng/*, hr/* families land via their own spec cycles. Until then, every real client surface is either bespoke (custom/<client_slug>) or pure configuration.
Configuration-level customization needs no code at all and is the first thing to reach for: per-tenant agent overrides (model, disabled tools), versioned system prompts, cultivator bindings (model override, prompt append, scope narrowing), tenant Connections for MCP, and roles/scopes. See Workflows: Per-tenant agent overrides and Workflows: Manage cultivators.
Repo map:
acaso-os/
├── packages/
│ ├── shared/ # acaso_shared — auth, tenancy/RLS, scope matching, @tool
│ ├── datahub/ # L1 — primitives, graphium memory, pipelines, templates, cultivators
│ ├── agenthub/ # L2 — agent runtime, chat, admin API, examples/, custom/
│ ├── apphub/ # L3 — @acaso/react SDK
│ └── web/ # Management console (Next.js static export)
├── specs/ # Feature specifications — read before non-trivial work
├── docs/ # ADRs, runbooks, in-repo authoring guides
├── infra/ # docker/, terraform/standalone-*/, helm/
└── Makefile # Source of truth for every workflow commandNon-negotiable rules
These hold for every line of client code. They are enforced by review, CI (the tenancy lint), and in several cases by the database itself.
- Every tenant-table access goes through
tenant_scope/request_scope(acaso_shared.tenancy.rls). The app connects as the non-privilegedacaso_approle, so RLS fires on every query — code that forgets to bind a tenant sees zero rows, not other tenants' rows. - The LLM never sees
tenant_id. Tools pull it from the request ContextVar (acaso_shared.tenancy.context.current_tenant_id), never from a model-visible argument. - Never write to the AGE graph directly. Go through
datahub.pipelines.nodes.write_graph_node/write_graph_edgeso the relational tables and the graph stay consistent. - Cultivators propose; the framework writes. A Cultivator returns
StatementProposals and never callsingest_contextitself — the Scope Validator sits between the model and the database. - Scope strings are built by code from trusted resolver output, never assembled by the LLM and never copied from raw event payloads.
- Never connect application code via
ADMIN_DATABASE_URL. That role is for migrations and DDL only; using it at runtime defeats the isolation posture. - Structured errors are
CustomError(status_code, message, error_code)anything else surfaces as a generic 500. - New tenant-scoped tables ship in the same PR as their RLS policy and a migration. CI's tenancy lint blocks violations.
Quality gates
Run all of these before considering any change done. CI runs the same set on every PR.
make lint # ruff check
make format_check # ruff format --check
make type_check # pyright
make test # full pytest suite (unit + integration)For console changes (packages/web/):
pnpm --filter @acaso/web type-check
pnpm --filter @acaso/web testTests follow the AAA pattern — # Arrange, # Act, # Assert comment blocks — and live under each package's tests/unit/ and tests/integration/, mirroring the source tree. Commits follow Conventional Commits with package scopes (feat(datahub): ..., fix(agenthub): ...); branches follow simplified GitFlow (feature/* off develop).
For coding agents
If you are an automated coding agent implementing AcasoOS for a client, work in this order:
- Read
CLAUDE.mdat the repo root — conventions, commands, architecture. - Read the umbrella spec
specs/001-acaso-os-platform/spec.mdfor anything non-trivial. - Read the authoring guide for the surface you are extending: Develop: Agents, Develop: Cultivators, or Develop: DataHub templates. Each has an in-repo counterpart under
docs/with the same content in greater depth. - Copy the worked example for that surface rather than starting from a blank file.
- Run every quality gate above. A task with a failing gate is not done.
Definition of done for any client-development task: code in the blessed location, tests in AAA shape, all four make gates green, scopes reviewed (who can see/run the thing), and — for anything touching storage — the tenancy rules above verifiably honored.