---
name: phoenix-server
user-invocable: false
description: >
  Backend development guide for the Phoenix AI observability platform (Strawberry GraphQL,
  SQLAlchemy async, FastAPI). Use this skill when writing or modifying Python server code
  in the phoenix repo — adding mutations, types, migrations, or tests. Trigger on any
  backend task touching src/phoenix/server/, src/phoenix/db/, or tests/unit/server/.
metadata:
  internal: true
---

# Phoenix Backend Development

Phoenix is an AI observability platform. The backend is Python: FastAPI serving a REST API and Strawberry
GraphQL API over an async SQLAlchemy ORM (PostgreSQL + SQLite).

## Development Guide Index

Read `DEVELOPMENT.md` (env setup, `uv`, tests, debugpy, pre-commit, REST API conventions) and `CONTRIBUTING.md` (PR format, conventional commits, code review expectations) if you have not already.

### Everyday Commands

```bash
make dev-backend                        # backend only, no frontend build needed
uv run pytest path/to/test -n auto      # run specific tests in parallel
make test-python                        # full test suite
make graphql                            # regenerate schema after GQL changes
make format                             # format all code
make typecheck-python                   # mypy + pyright
```

## Key Directories

```
src/phoenix/server/api/
  mutations/        Domain-specific mutation mixins, composed in __init__.py
  types/            GraphQL types with field resolvers
  input_types/      Strawberry @input classes with validation
  subscriptions.py  Async generator subscriptions (streaming)
  queries.py        Root query type
  context.py        Request context: db, dataloaders, auth, event queue
  dataloaders/      Batch loaders (prevent N+1 queries)
  auth.py           Permission classes (IsNotReadOnly, IsNotViewer, etc.)
  routers/          REST API endpoints (v1/)
src/phoenix/db/
  models.py         SQLAlchemy ORM models (single file)
  migrations/       Alembic migrations
tests/unit/server/api/
  mutations/        Mutation tests
  types/            Type resolver tests
  conftest.py       Fixtures: db, gql_client, test data factories
```

## What Are You Doing?

| Task | Reference |
|------|-----------|
| Adding or modifying a mutation, type, subscription, or input | `references/graphql-patterns.md` |
| Writing or modifying tests | `references/test-patterns.md` |
| Writing tests for code that emits OpenInference spans (VCR cassettes, span attribute assertions) | `references/llm-trace-tests.md` |
| Adding a migration or modifying database models | `references/database-patterns.md` |

## Hard Rules

- **Side effects belong on `Mutation`, not `Query`.** A resolver that makes outbound
  network calls, reads secrets, writes state, or accepts a user-supplied URL/host
  MUST be a `@strawberry.mutation` with `permission_classes=[...]`. Query fields
  bypass the `make check-graphql-permissions` CI guard and are reachable
  unauthenticated by default — this has been exploited as an SSRF vector. See
  `references/graphql-patterns.md` → "Query vs Mutation".

## Naming

- **Avoid acronyms and single/double-letter abbreviations for local variables.**
  Prefer the full noun: `session` / `project_session` over `ps`, `trace` over `t`,
  `example` / `dataset_example` over `de`. The cost of a longer identifier is trivial; the
  cost of having to mentally expand an acronym while reading unfamiliar code is
  not.
- Established domain acronyms used in the codebase (`db`, `gql`, `otel`, `llm`)
  are fine — they're vocabulary, not abbreviations of local nouns.

## Docstrings

The project rule of "default to no comments" is about **inline comments**, not
docstrings. Public APIs should be documented.

- **Document parameters and return values on public methods of reusable classes**
  (clients, services, factories, builders). Use Google-style `Args:` / `Returns:`
  /  `Raises:` blocks when the meaning isn't fully recoverable from the type
  signature. Do not strip these during refactors — semantics outlive file moves.
- **Describe behavior, not implementation.** A method on a docs-search client
  says "Invoke a backend tool and return its text result", not "Invoke a tool
  on the MCP server" — the underlying transport is an implementation detail and
  the docstring should survive a transport swap. Internal helpers (leading `_`)
  may reference the transport directly since their scope is bounded.
- **One-liner docstrings are fine** when the name and types fully convey intent
  (`close()`, `is_backend_tool(name)`). Don't pad them with restated signatures.
- **Module docstrings** belong at the top of any file that exposes public
  surface (a client class, a router, a service module). One sentence on what
  the module is for is enough.
