AGENTS.md
Rama main · réplica de lectura del repositorio original.
# LangWatch agent contract
This file is the short operating contract for repository work. Accepted ADRs
remain authoritative; do not copy them into local docs. Read the nearest
`AGENTS.md`, the owning feature ADR/spec, and the relevant source before editing.
## Use the repository skills
- Use `module` to build or change a module: create one, extend it, convert it to the
annotation shape, wire it into a process, move code into it, or publish a web surface.
- Use `module-review` to audit a module, a directory, a diff or a branch, including
before a broad split, when ownership is unclear, or before staging a conversion batch.
The skills live under `.claude/skills/` (`.agents/skills` is a link to the same
directory). They supplement, rather than replace, the accepted ADRs and the
architecture linter.
## Architecture
- `modules/catalogue.json` is the ownership authority. Feature names
are singular. Do not create a package per endpoint, table, or helper.
- A feature may have `contract`, `server`, and `web` workspace packages. Create
only the surfaces it needs. API and worker code are process composition or
feature server adapters, not extra implementations of the domain.
- Strict features use `feature.json` with `layoutVersion: 0` and the exact
lower-kebab/dotted-role layout in the strict-feature ADR.
- A feature contract exposes portable values, errors, schemas, and one
callable `<Feature>Api` interface in `<feature>.api.ts` with a same-named
runtime API token. Concrete `<Feature>App` implementations own services
privately and implement that API; callable forwarding methods are the public
boundary. Apps expose no service fields, repositories, transport objects or
lookup API. Peers depend only on API tokens. ADR-133 applies this boundary to
every server feature, including Enterprise implementations; missing adoption
is a lint failure. Browser-only features need no fabricated server or API.
App state and nonpublic helpers use ECMAScript `#private` members.
- A route's authorization is declared with `.withPermission(...)` and asked by
the door before the handler runs. Do not declare `anyAuthenticated` and then
check a permission in a middleware fact, a handler or the application. When
the library cannot express a check, extend `packages/api` and the door.
- Product analytics and lifecycle messaging belong to the nurturing feature. A
feature records a fact event from its service; nurturing subscribes and talks
to PostHog and Customer.io. No other feature holds an analytics channel,
client or key.
- Repository count does not determine service count. Merge duplicate ways of
loading the same domain data when doing so leaves one coherent owner.
- A concrete service has a private constructor and `static create`. Construct
one instance per process. Do not construct services in request handlers.
- A service receives its own private repositories and complete peer APIs from
other feature contracts. Do not pass callback bags, service locators,
`Pick`/`Omit` views, `Parameters`/`ReturnType` mirrors, or another feature's
repository.
- Required service and repository operations return a value or throw a concrete
domain error. Use `find*` only when absence is a normal outcome the caller handles;
it returns `null` or `undefined`. Do not name methods `try*` or `require*`.
Add a method only for a real caller.
- REST and tRPC use inline `@langwatch/api` handlers with parsed input and
explicit app/actor/scope. Parsed middleware context values are typed trailing
arguments, never merged into input; no raw request, response or headers.
Return a schema-compatible JSON object, array or void. Special protocols need
framework integrations. Output mismatches log safe validation details and
metadata without content, then preserve the response; input and authorization
remain enforced. See ADR-133. Workers receive the composed app explicitly.
A tenant target may be input when authorization checks that same target.
- Only composition roots import feature server installers. Feature packages
import other features through contracts. No `getApp`, `tryGetApp`, global
Prisma, per-request construction, or import-time registration.
- Generated Prisma is private to strict Prisma repository adapters. Services,
contracts, web packages, and public declarations never expose it.
- Environment access belongs to boot/config composition. Parse and validate it
once, then inject typed semantic config. Packages do not read env modules.
- Enterprise implementation stays under `enterprise/**`. `saas` is
Enterprise-licensed; `ops` is core. Core never imports Enterprise
implementations.
## Composition
- One feature installer, one construction path, explicit lifecycle
([ADR-133](dev/docs/adr/133-composition-spec.md), spec
`specs/server/composition-spec.feature`). A feature declares its required
contract dependencies, typed config, provided app and transport/background
contributions; its setup returns the app and constructs its services once per
process; API and worker reuse that setup.
- Peer API clients forward directly to installed Apps without serialization or
repeated auth/schema middleware. Boot allocates clients before constructing
Apps; factories store peers but never read or call them before readiness.
- Declaring installs nothing. Boot validates the declarations and constructs;
start begins serving. Imports and constructors never start background work.
- System migrations run one framework in two modes: startup (blocking, gates
readiness until finalized) and background (incremental, resumes from a
persisted checkpoint). Awaiting the convergence helper is not a completion
assertion.
## Eventing
- Projections and process managers are deterministic and synchronous. They do
no network, database, queue, clock, random, or heavy work.
- Projections derive state/writes. Processes derive state, wakes, and
deterministically keyed intents. External work belongs in retry-safe intent
execution.
- Subscribers may perform effects only behind a named idempotency/redelivery
boundary. Durable follow-up events go through the owning command/pipeline;
event handlers do not append fabricated events directly.
## Migration and parity
- Move a vertical slice, not a second copy. Rewire callers and delete displaced
production files. Temporary compatibility files are thin named transport or
composition adapters and carry no business logic.
- Once a feature package exists, do not leave its behaviour scattered across
`src/runtime`, `src/features`, `src/server`, `src/server/app-layer`,
`src/components`, and the package. Record every deliberate residual.
- Keep existing URLs, tRPC names, OpenAPI/RPC shapes, response fields, auth,
error mapping, ordering, pagination, money/time units, side effects,
idempotency, and query semantics unless the user explicitly changes them.
- Before replacing a query or mapper, characterise the full old response. For
traces in particular, do not drop fields or interchange `trace_analytics`,
`trace_summaries`, or timeseries rollups.
- UI packages own reusable presentation and browser behaviour. App UI owns page
composition, routing, and transport hooks. Pass controlled data/actions or a
small named render port; never inject tRPC hooks through a giant context bag.
- The eventual process split is physical: UI composes browser surfaces, API
composes backend transports, and worker composes background execution.
## TypeScript and source shape
- Import Zod 4 only as `from "zod"`. Schemas are the source of truth at JSON,
persistence, process, and transport boundaries. Do not replace parsing with
`typeof` walls or assertions.
- No `any`, double assertions, `@ts-ignore`, or `@ts-expect-error` in new code.
Fix the boundary. Runtime absence is `void 0`; type-level `undefined` is fine.
- Filenames are lower kebab case. Dots separate architectural roles, for
example `prisma.project.repository.ts` and `project.service.ts`.
- Use Oxfmt, never Prettier. Prefer braces, blank lines between logical steps,
and named intermediate values over dense chains, nested ternaries, or large
boolean walls.
- Keep service modules and methods below the measured architecture ceilings by
extracting real collaborators. Do not add or raise a baseline for new code.
- Comments explain durable non-obvious constraints. Soft review starts at 30
comment lines attached to one declaration; 61 is a hard failure. Trim
narration, history, section labels, and restatements of the code.
## Tests and documentation
- Move behavioural and integration coverage with the implementation. Never
delete a test suite until equivalent canonical coverage exists.
- Tests must be able to fail and assert observable behaviour. Avoid bare
`expect`, literal tautologies, empty snapshots, duplicate bodies, and mocking
the unit under test. Use mutation testing separately for deeper confidence.
- Feature ADRs/specs describe current facts, decisions, and behaviour. Compress
live-blog history and generic architecture prose; link shared ADRs instead.
- After a coherent slice, run package typechecks/tests, focused app tests,
Oxfmt, Oxc, architecture lint, and `git diff --check`. Also run:
`pnpm --filter @langwatch/architecture-enforcer review:test-quality` for changed
tests and `review:comment-blocks` when reviewing long comments.
Run Oxc from the repository root with
`pnpm exec oxlint --quiet --config .oxlintrc.jsonc <paths>`.
Whole-feature compliance requires the whole feature directory, including tests;
a plain Oxc run or selected-file pass does not establish architecture compliance.
- If the full workspace is red from unrelated work, prove the changed slice and
report the exact remaining diagnostics. Do not call a blocked check green.
## Shared worktree and commits
- Other changes in the worktree belong to the user or another agent. Do not
rewrite, reset, or stage them. Stage exact paths or hunks and inspect the
cached diff before committing.
- Keep commits small and coherent. Do not mix a feature move with unrelated
lockfile, baseline, generated, or formatting churn.