# AGENTS.md — AgenticOS The one instruction file for coding agents in this repository - Claude Code, Codex and any other. There is no `CLAUDE.md`; this is it. Project-wide decisions and pointers to task-specific guidance. Read relevant rules, skills and documentation as needed. Keep detailed procedures and incident history outside this file. Current user instructions and higher-priority session rules take precedence. ## What this is A self-hosted, open-source, multi-tenant platform for a company's AI agents. **An agent is a versioned spec.** Users configure instructions, models, capabilities and budgets in the UI, publish a version and can export it as YAML. The same agent runs through web chat, the HTTP API and channel integrations. Change an individual agent through its spec; change Python when implementing platform behaviour or capabilities. Tools reach models through the capability registry. **Stack:** FastAPI, Pydantic v2, Pydantic AI, PostgreSQL with pgvector, Redis, Next.js, React, bun, next-intl and MkDocs Material. Use versions from the repository manifests and lockfiles; Python is pinned in `backend/.python-version`. The repository originated from the Full-Stack AI Agent Template. Platform and template-inherited subsystems have different coverage gates, described below. ## Quality and scope - Type owned boundaries and nontrivial helpers. Do not use `Any` or suppressions to hide errors. Necessary suppressions need a specific explanation; route return annotations below are an explicit project convention. - Use domain exceptions with `message` and `details`. Do not swallow exceptions or mask bugs with fallbacks. - Keep changes scoped. Avoid speculative abstractions, unused code and unrelated cleanup. Comments explain non-obvious constraints; API contracts belong in docstrings. See `.claude/rules/code-style.md`. - Test new behaviour and add regression tests for bugs. Documentation-only changes need documentation checks, not artificial application tests. - Integrate new user-facing features with the existing product: onboarding for new pages and creation flows, and dashboard widgets for activity or state that belongs at a glance. Follow `.claude/rules/frontend.md` for registries, permissions, translations and anchors. ## Hard boundaries - Routes call services; services coordinate repositories. Routes must not import or call repositories directly. - Repositories use `db.flush()` and `db.refresh()`, never `db.commit()`. Use `DBSession`: its `scope="function"` commits after the route returns and before the response is written. A bare `Depends(get_db_session)` has different timing. The agent run paths in `AgentRunnerService._run` and `ChatAgentRunner.run` explicitly commit before the model call and in terminal cleanup; `app.services.telephony.runtime` commits telephone admission, submission, transfer, hang-up and single-use media claims before external effects; unknown acceptance is retained for signed callbacks and cleanup, never automatic redial. `VoiceCallStarter.start` commits a call's row and its run before the call connects and `VoiceCallSession` commits again in its teardown (per-turn messages, heartbeats and approval writes use their own `get_db_context()` sessions); `SessionService.detect_refresh_reuse` commits the security response its caller is about to raise past; and `WorkflowExposureService._answer` commits a webhook's admission before waiting for its Respond to webhook step. See `docs/architecture.md#the-requests-transaction`. - Queue background work that must happen as a job with `app.services.background_jobs.enqueue`, in the request's own transaction, so it commits with the rows it reads. Best-effort work that may be lost goes to `spawn_after_commit`. See `docs/architecture.md#dispatching-background-work-from-a-request` and the `background-task` skill. - Collection routes use `require(...)` gates. Per-resource routes for agents, skills and collections delegate to a service using `resolve_access`; a role gate must not reject access allowed by a resource grant. Follow `permissions-rbac`. - Route handlers return `-> Any` and declare `response_model` for serialization. Keep service and repository return types precise. - Store credentials through `app/core/vault.py`. Connector sources reference vault secret IDs; do not put credentials in a connector's `CONFIG_MODEL` or introduce deployment-wide Fernet keys, `CHANNEL_ENCRYPTION_KEY` or `app/core/crypto.py`. - Organization authority comes from membership and the permission catalog. Do not restore `UserRole`, `User.has_role()`, `RoleChecker`, `CurrentAdmin`, `CurrentSuperuser` or the old `users.role` column. - Use `datetime.now(UTC)` and `secrets.compare_digest()` for API key comparisons. - Migration numbering restarted after a baseline squash. Verify references against `backend/alembic/versions/` and cite full filenames; use git history for removed revisions rather than assuming an old number still identifies the same migration. ## Read the matching rule before writing code Read the relevant files under `.claude/rules/`; their frontmatter defines the file scope. Load only the rules that apply to the change. | Editing | Read | |---|---| | Any `backend/app/**` Python | `architecture.md` — Routes → Services → Repositories, DI, thin vs. thick domains | | `schemas/`, `db/models/` | `schemas-models.md` — `*Create`/`*Update`/`*Read`/`*List`, SQLAlchemy | | `api/` | `api-conventions.md` — REST structure, status codes, pagination, auth aliases | | `core/`, `services/` | `exceptions-security.md` — domain exceptions, JWT, the permission model | | Any Python | `code-style.md` — formatting, naming, imports, type hints | | `tests/` | `testing.md` — the layers, anyio, fixtures, the 100% gate | | `frontend/` | `frontend.md` — App Router, data layer, stores, i18n, permissions | ## Read the matching skill before starting a task Skills live in `.claude/skills//SKILL.md`. Read the skills matching the task; `.claude/README.md` explains the layout. | Doing | Skill | |---|---| | Giving an agent a new tool, or a capability | `agent-capability` | | Changing `AgentSpec`, `SPEC_VERSION` or publish validation | `agent-spec` | | Anything authorization | `permissions-rbac` | | Anything with a credential at rest | `vault-secrets` | | MCP servers, or an integration with a SaaS product | `mcp-connections` | | Ingestion, retrieval, connectors, parsers | `rag-knowledge` | | Telegram / Slack / Mattermost | `channel-bot` | | Background jobs, schedules, or work that outlives a request | `background-task` | | A schema change or a backfill | `alembic-migration` | | Backend tests, or a failing coverage gate | `backend-tests` | | A Playwright spec | `e2e-tests` | | A page, store, hook or permission-gated control | `frontend-feature` | | The docs site | `project-docs` | ## Commands ```bash make dev # postgres, redis, api, worker, frontend make platform-bootstrap BOOTSTRAP_API_KEY=sk-... # an org, an owner, a model, an agent make check # every CI job but e2e - CI runs it, not your laptop ``` `make help` lists the rest. Day to day: | | | |---|---| | `make lint` / `make format` | ruff + ty + vulture + deptry + eslint + prettier + tsc + the guard scripts + codespell | | `make lint-backend` / `make lint-frontend` | one half of it — CI runs them in two jobs | | `make dead-code` | vulture + knip, unused functions — a report to read, not a gate | | `make test-fast` | no coverage — the write-run-write loop | | `make test` | backend + the 100% gate on the platform layer | | `make test-integration` | only the tests needing a real database | | `make test-frontend` / `make test-frontend-cov` | vitest / vitest with the gate CI applies | | `make test-e2e` | Playwright — needs a backend and its seed | | `make test-migrations` | the whole chain forwards and back | | `make db-check` | `alembic check` — a model change with no migration fails here (in `make check`) | | `make db-migrate` / `make db-upgrade` | autogenerate / apply | | `make docs` / `make docs-build` | serve on :8001 (`DOCS_PORT=` to move it) / `--strict` | | `uv run agenticos cmd doctor` | can this deployment actually run an agent? | | `uv run agenticos cmd --help` | every custom command, including all `rag-*` | ## Verification **Do not run the full suites on a laptop; CI runs them.** `make test`, `make test-frontend-cov`, `make check`, `make test-e2e` and a whole-directory pytest or vitest take tens of minutes and load the machine somebody is working on. Locally, run small batches: the test files covering what you changed, `make lint` or the half of it for the side you touched, and `make db-check` after a model change. Run frontend commands from `frontend/`, backend commands from `backend/` and make targets from the root. ```bash cd backend && uv run pytest tests/test_attachments.py tests/test_transcript.py -q cd frontend && bunx vitest run src/components/chat/chat-container.integration.test.tsx ``` The coverage gates, integration tests, e2e and the rest of `make check` are what GitHub Actions runs on every push. Push, then read the run for that commit's SHA: `gh run list --branch `, then `gh run view --log-failed` for a red job. A later push can cancel an earlier run, and path-filtered jobs may be skipped, so a green run belongs to the SHA it ran on. Report failures with the job and the error, and say when a gate has not run yet. See `docs/branching.md` and the workflow files for CI behaviour, and `.claude/rules/testing.md` for scoped commands. ## Environment - Use the repository's pgvector-enabled PostgreSQL image; stock PostgreSQL lacks the vector extension required by RAG. Check the image if ingestion fails in a fresh environment. - Match `backend/.python-version` when creating or syncing the backend environment. Check the interpreter actually used by `uv run` before relying on test results. ## Testing Read `docs/testing.md`, `.claude/rules/testing.md` and the relevant testing skill for fixtures and patterns. - The platform layer has a 100% coverage gate. Exact modules are configured in `backend/pyproject.toml`; template-inherited subsystems are additionally reported by `make coverage-all` without that gate. - When adding a platform module, keep `[tool.coverage.run] include` and the matching `[[tool.ty.overrides]] include` aligned, including order. `backend/tests/test_coverage_gate.py` checks this contract. - Async tests use anyio: `pytestmark = pytest.mark.anyio`. - Cover refusal and failure paths: tenant isolation, scopes and grants, budgets checked before model calls and recorded on failure, invalid specs rejected at publish, and secrets excluded from responses, logs and audit entries. Also cover channel sender identity, parser/routing compatibility and existing stored JSONB when tightening validation. The testing skills contain worked examples. ## Documentation Update existing documentation in the same change when described behaviour changes. Refactors with unchanged behaviour and test-only changes do not require unrelated prose edits. Update rules and skills when referenced paths or contracts change. API reference changes belong in the generating docstrings. Read the `project-docs` skill for site structure, writing conventions, generated pages and build checks. Add new pages to `mkdocs.yml`, relevant cross-links and the topic map below. `scripts/docs_drift.py` contains the code-to-page trigger map; its Stop hook is a reminder, not a completeness check or a gate. The site publishes in four languages, so a published page owes a `.pl.md`, a `.de.md` and an `.es.md` beside it, and editing an English page makes all three stale. The same is true of `README.md`, `CONTRIBUTING.md`, `SECURITY.md` and `CODE_OF_CONDUCT.md`, which GitHub renders rather than MkDocs: those record the fingerprint in an HTML comment, cannot pin an anchor, rewrite their own in-page links per language, and point every other link at the reader's own language. `scripts/check_docs_i18n.py` gates `make lint` on all of it and names what is missing; `docs/howto/translate.md` is the workflow. Record a retranslated page with `--update ..md` — it takes paths, because stamping one nobody retranslated is how a stale translation stops being visible. | Topic | Page | |---|---| | Spec, version, exposure, run | `docs/concepts.md` | | The three permission layers, scopes, grants | `docs/permissions.md` | | Groups, directory group mappings, LDAP and Kerberos sign-in | `docs/directory.md` | | Departments: groups as audiences, the group page, where an agent's knowledge comes from | `docs/departments.md` | | Budgets, approvals, alerts, audit | `docs/governance.md` | | What ships as a capability, its tools and config | `docs/reference/capabilities.md` | | The agent spec, field by field | `docs/reference/spec.md` | | Every workflow node: config, ports, errors, retries | `docs/reference/workflow-nodes.md` | | What the console calls each thing, and its name in the API and spec | `docs/reference/glossary.md` | | MCP connections, the server catalog, OAuth | `docs/mcp.md` | | Providers, model profiles, fallbacks, cost | `docs/models.md` | | Which model to pick, open weights vs closed | `docs/choosing-models.md` | | Adoption, roles, cost, the security review | `docs/rollout.md` | | The vault, secret kinds, what never leaks | `docs/secrets.md` | | Where personal data lives, what leaves, deletion, the controls and the open gaps | `docs/data-protection.md` | | Third-party licences, the notices, the review and its open findings | `docs/licenses.md` | | Every component a deployment is made of, and the release SBOM | `docs/reference/components.md` | | Threat model, data flow, at-rest, the controls matrix | `docs/security.md` | | Skills — format, library, skills vs knowledge | `docs/skills.md` | | Context files — standing knowledge bound to agents | `docs/context.md` | | Datasets — versioned eval cases, import/export, Add to dataset | `docs/datasets.md` | | Eval runs — estimate, isolation, baselines, confirmed regressions, model comparison | `docs/evals.md` | | Authoring assistant — suggestions from evidence, decisions, checking with evals | `docs/authoring.md` | | Virtual Tables — typed records, schema versions, revisions, idempotent writes, the `/tables` API | `docs/virtual-tables.md` | | Agent and team tasks: research and proposal only | `docs/design/agent-tasks.md` (repo only) | | Workflows — the visual editor: canvas, palette, bindings, autosave, publish, versions | `docs/workflows.md` | | Projects: shared files, instructions and chats | `docs/projects.md` | | Knowledge gaps — questions the knowledge base could not answer, grouped into topics | `docs/knowledge-gaps.md` | | Named environments, promotion, per-environment tracing, test connections, isolated (eval) runs | `docs/environments.md` | | Surfaces: widget, WebSocket, Slack, Telegram | `docs/channels.md` | | Voice conversations: calls, realtime models, limits, hang-up | `docs/voice.md` | | Published pages: artifacts, versions, public links, isolation | `docs/artifacts.md` | | The desktop app: the console, blobby, quick ask, local work and the screen | `docs/desktop.md` | | macOS Gatekeeper warnings, install verification and release signing | `docs/howto/macos-warning.md` | | The phone app: live activities, widgets, push, offline | `docs/mobile.md` | | Upload, parsing, ingestion | `docs/file-processing.md` | | The console: dashboard widgets, chat, slash commands, the map | `docs/console.md` | | Every module, screenshotted in both themes | `docs/screens.md` | | The sandbox: sessions, runtimes, isolation, lifetimes | `docs/sandbox.md` | | Routes → services → repositories | `docs/architecture.md` | | The Next.js console, for a contributor | `docs/frontend.md` | | Authenticating and calling the HTTP API | `docs/api.md` | | The ML services called without an agent, and what each one is | `docs/ml-services.md` | | Adding a feature end to end | `docs/adding_features.md` | | Test layers and what belongs in each | `docs/testing.md` | | The load suite: the workload, the thresholds, the measured runs | `docs/load-testing.md` | | Why the job queue is Postgres: ten engines, the OOM test, the limits | `docs/task-queue-benchmark.md` | | The automated pull request reviewer | `docs/code-review.md` | | Branches, rulesets and what protects `main` | `docs/branching.md` | | Recurring patterns | `docs/patterns.md` | | Translating a page, and the terminology that has to be exact | `docs/howto/translate.md` | | Getting it onto a host: sizing, TLS, the approved deploy | `docs/deploy.md` | | Kubernetes and the candidate OpenShift configuration | `docs/kubernetes.md` | | Past one host: an external Postgres, connection counts, more API hosts and workers, several deployments | `docs/scaling.md` | | The deployment's identity, sign-up policy, notices | `docs/deployment.md` | | Settings and the production checklist | `docs/configuration.md` | | What the platform does, on one page | `docs/features.md` | | Choosing a first task, and the 30 use-case tutorials it indexes | `docs/use-cases.md` (tutorials live in `docs/howto/`) | | The Learn track's own landing | `docs/learn/index.md` | | Help, contributing, extending the platform | `docs/resources/index.md` | | Why it exists, and what it is not | `docs/about/index.md` | | How it compares with other products, and its own gaps | `docs/about/comparison.md` | | The six decisions that shape the codebase | `docs/about/design.md` (repo only) | | Delivery state and what is left | `docs/ROADMAP.md` (repo only) | | Every notable change | `docs/release-notes.md` (reads `CHANGELOG.md`) | ## Git - Never commit on `main`. Use a feature or fix branch, one PR per coherent change, squashed on merge. - On a branch, commit and push each finished, verified piece without being asked. This is the project's exception to the global commit-on-request preference; explicit session instructions still take precedence. - No AI attribution in commits or PR descriptions: no `Co-Authored-By: Claude`, `Generated with Claude Code` or equivalent footer. - Stage only intended changes and review the staged diff. Exclude secrets, unrelated work and tool caches. - Use Conventional Commits: `type(scope): summary`, imperative, lower case after the colon, no trailing period, at most 72 characters. Types: `feat`, `fix`, `refactor`, `perf`, `test`, `docs`, `ci`, `build`, `chore`. Scope names the subsystem; omit it for repository-wide changes. Use `!` and a `BREAKING CHANGE:` footer for breaking changes. - Add a body when the reason, verification or tradeoffs are not clear from the subject; wrap it at 72 characters. Put issue references in footers (`Closes`, `Refs`, `Part-of`); use `Closes` only when the issue is fully resolved. - PR descriptions explain the problem, resulting behaviour, verification and material limitations. Review the diff and surrounding code before declaring work finished. `.claude/commands/review.md` defines the local review standard. `docs/code-review.md` and `.github/workflows/ai-review.yml` describe automated review. A failed reviewer is not a clean review; assess findings against the code and explain rejected ones. - Fix confirmed in-scope defects; record out-of-scope defects without expanding the change. Before filing or triaging issues, read `.claude/references/issue-triage.md` for the repository's board conventions. ## Releases Versions follow [Semantic Versioning](https://semver.org) from 1.0.0 onwards, except that the major stays 1 (below); every release before 1.0.0 was `0.0.N`. CHANGELOG.md and `docs/release-notes.md` state that exception to operators. A release is numbered by the largest change it ships, judged by someone running a deployment, not by the size of the diff: | Bump | When the release contains | |---|---| | MAJOR | Not used: AgenticOS stays on major version 1. A change an existing deployment has to act on - a removed or renamed API route, field or setting, a spec that no longer loads or must be republished, a migration that drops data, a changed default that alters stored behaviour - ships as the next MINOR. Its commit still carries `!` and a `BREAKING CHANGE:` footer, and its changelog entry says what to do | | MINOR | Something new a member or operator can do: a feature, a capability or tool, an endpoint, a setting, a new screen. Backwards compatible | | PATCH | Fixes, documentation, dependency updates and refactors with no new behaviour | - Cut after the merge, never beside it. Once a PR lands on `main`, a `chore: cut X.Y.Z` PR renames the `[Unreleased]` block in `CHANGELOG.md` to `## [X.Y.Z] - YYYY-MM-DD` under a new empty `[Unreleased]`, and bumps `backend/pyproject.toml`, `backend/uv.lock` and `frontend/package.json`. Feature PRs write their changelog entries under `[Unreleased]`; the cut does not add new ones. - Prepare a release train with `make release-cut BUMP=patch` (or `minor`); add `CREATE_PR=1` to create the cut PR from fresh `origin/main`. The default only prints a plan. CI and CodeQL may reuse successful full runs for a proven version-only cut; missing evidence runs full checks. See `docs/branching.md`. - After the cut merges, publish the release and its tag in one step: `gh release create vX.Y.Z --target --notes-file `. Never push the tag first: it starts `images.yml` at once, and that attaches the release SBOMs only to a release that already exists. Production deploys wait for approval (`deploy.yml`). - One release per merged change, or one for a train merged together, numbered by the largest change in it. Stacked PRs are released in merge order, each with its own number. - An agent asked to release may pick a PATCH or MINOR number by the table above. Never propose 2.0.0 or any later major; a number the maintainer has named is the maintainer's decision.