Backend Development
Work on the SOIT FastAPI backend with kernel, module, adapter, and API boundaries.
The SOIT backend lives under soit/server and uses Python 3.11, FastAPI, SQLModel, SQLAlchemy, Alembic, Celery, PostgreSQL, Redis, Milvus, MinIO, Vault, and OpenTelemetry. This page summarizes the mandatory engineering standard.
Local commands
uv sync
uv run uvicorn app.main:app --reload
uv run pytest
uv run lint-imports --config importlinter.ini
uv run ruff check app tests
uv run mypy app testsThe six non-negotiables
- Spec-First — any new capability starts with a JSON Schema update under
app/kernel/specs/, with examples; runtime validates incoming spec JSON against the schema. - Scope-By-Default — workspace resources are isolated by
tenant_id + workspace_id. Business APIs never accept tenant or workspace ids as free parameters; scope resolves fromRequestContextonly. - Trace-Everything — every executable action creates a run and at least one run step; large payloads become artifacts; costs go to cost entries. See Runtime Ledger.
- Port-Only — LLM, tools, MCP, vector DB, object storage, and secrets are accessed through kernel ports, which enforce retry, timeout, audit, and rate limits consistently. Modules never call provider SDKs directly.
- Immutable-Versions —
agent_version,workflow_version, andplugin_versionrows are immutable; publish and rollback only move pointers. - Secure-by-Default — egress is deny-by-default, secrets are references only (never plaintext), and high-risk runtimes stay disabled unless explicitly enabled.
Import rules
Allowed dependency directions:
app/api→app/modules, and kernel contracts/policiesapp/modules→app/kernelapp/adapters→app/kernel/ports(implements gateway interfaces)
Forbidden (fails import-linter and code review): kernel importing modules or API, modules importing API, and any module importing another module's domain models directly — depend on contracts and ports instead.
Identity and scope
- Every request resolves a
RequestContext:tenant_idanduser_idfrom the JWT or session,workspace_idfrom header or path, plus membership and role checks. All services acceptctx: RequestContext. - Scope-aware repositories enforce
WHERE tenant_id = ctx.tenant_id AND workspace_id = ctx.workspace_idon every workspace-scoped query. - Services use guard decorators rather than hard-coded checks:
rbac_guard(resource_type, action)for resource actions (create | read | update | delete | run) andworkspace_guard(...)for workspace/tenant-level checks. The API layer stays thin and does not enforce permissions directly.
API conventions
All responses use a stable envelope:
{
"success": true,
"code": "OK",
"message": "OK",
"data": { "any": "json" },
"request_id": "req_...",
"run_id": "run_..."
}Errors use the same shape with success: false, an enum code, and a details object. Pagination is cursor-based (page_size / page_token in, items / next_page_token out), and write APIs should support an Idempotency-Key header.
Directory placement
- HTTP request/response schemas →
app/api/v1/schemas/ - Domain schemas →
app/modules/<domain>/**/schemas.py - Cross-layer stable contracts →
app/kernel/contracts/ - Provider implementations →
app/adapters/ - Dependency wiring →
app/wiring/ - JSON Schemas →
app/kernel/specs/v1/
Cross-resource references follow refs.schema.json, e.g. model:openai:gpt-4.1, knowledge:kb_support, tool:http:ticket_create, secret:zendesk_token.
Migrations and testing
- Schema changes go through reviewed Alembic migrations, reversible when feasible; avoid long locks on large tables.
- Minimum mandatory tests: cross-tenant and cross-workspace access return 403/404, spec validation rejects invalid JSON, every execution produces a run plus steps, and gateway mocks keep tests deterministic.
PR checklist
- Spec updated with examples for any new capability.
- Tenant/workspace scope enforced, with tests.
run/run_stepemitted for new execution paths.- Gateway (port) used for any external call.
- Breaking-change impact assessed.
- CI passes — see Testing and Quality Gate.