Imported from punk-raven/dafter (
AGENTS.md). Install upstream withnpx skills add punk-raven/dafter. Copyright stays with the author.
Dafter agent context
Realtime AI media toolkit (agents, translation, transcription, sealed recording) behind one API, one SDK, one config document. Read docs/dafter.md first; section 6 is the delivery plan.
Layout
schemas/: the only source of truth. All language types are generated from it.go/(control plane, state, egress, seal) andpython/(agent runtime, providers, batch, evals). They touch only at the resolved config document and the event envelope.go/tools/enumgen: emits enum constants for both halves.Makefileis the entry point;.github/workflows/ci.ymlruns it.testdata/: fixtures both halves read, so a cross-language claim is checked on both sides rather than asserted on one. Each directory has a README saying what it pins;testdata/rfc8785/is vendored verbatim and must never be edited or reformatted.
Generated files: never hand-edit, never commit
Produced by make generate, git-ignored, policed by make generate-check (first step of make check):
go/internal/**/*_gen.go, python/dafter_core/src/dafter_core/enums.py, go/internal/schema/schemas/, python/dafter_core/src/dafter_core/_schemas/.
Every target regenerates before it runs, so a clone only needs ./scripts/setup.sh once. See the schema-change skill.
Commands
./scripts/setup.shonce per clone (a fresh checkout does not compile until it runs)make check(the Go and Python checks CI runs)- Go:
make build vet lint test tidy - Python:
make py-lint py-test(ruff, mypy strict, pytest viauv run --frozenfrompython/)
Rules enforced in code, kept identical on both halves
- Validate the raw document, then decode:
config.Parse,events.Parse;config.parse,events.parse_event. - Unknown fields rejected: schemas close with
unevaluatedProperties; Go also usesDisallowUnknownFields. - Cross-field rules the schema cannot express are one table per half,
go/internal/config/rules.goandpython/dafter_core/src/dafter_core/rules.py, every broken rule reported with its pointer: sealed forbids agent; recording needs consent artifact;session_createstart needsroom_compositelayout. - Error messages safe to log (no name, email, phone, transcript):
schemas/errors/v1/error.schema.json. Report every problem, located by JSON pointer. - Identifiers are opaque patterns (
schemas/common/v1/ids.schema.json, minted ingo/internal/ids); credentials aresecret://refs; region tokens opaque. Real credentials reach the process through the environment only. - Config resolution is layers then axes,
go/internal/config/resolve.go: defaults, tenant, profile, session overrides, then the language and channel overlays. The overlays land last, so a channel overlay wins over a session override. - The config hash is RFC 8785 then SHA-256 with
configHashstripped, mirrored ingo/internal/config/hash.goandpython/dafter_core/src/dafter_core/hashing.py. Both halves are pinned to the vectors intestdata/; changing either without the other fails on its own side. - Token grants derive from the role and never from a client request, and no role is ever issued a room-admin, room-create, room-list, room-record or ingress grant:
grantsForingo/internal/transport/livekit.go. Every permission is stated rather than left unset, because the media server grants an absent permission by default. - A session is written to the store before its token is minted. A token issued for a room whose config was never stored lets a worker join a session nobody can explain afterwards.
- Enum drift tested on both halves:
TestGeneratedEnumsMatchSchemain each owning Go package andpython/dafter_core/tests/test_enums.py; each Go package carries at most one test file, named after the package.
Dependency graph
depguard in go/.golangci.yml holds the whole graph: core below everything, state and transport siblings above it, control above them. github.com/livekit/* is denied outside go/internal/transport, which is the seam every media server concern goes behind. Target graph: docs/dafter.md section 5.
Dev stack
make dev from a clean clone builds and starts everything: control plane, LiveKit SFU, Redis, MinIO (recordings), Jaeger (tracing), Prometheus, Grafana, and CF tunnel (if configured). make dev-down tears it down. Config lives in deploy/livekit.yaml and deploy/egress.yaml; dev credentials are devkey/secret. The control plane is at http://127.0.0.1:8080.
The SFU advertises --node-ip (LIVEKIT_NODE_IP, default 127.0.0.1) on the one published UDP port; anything else in rtc (a port range, use_external_ip) breaks local media, see the comments in deploy/livekit.yaml. LiveKit metrics are on port 6789, not 7880.
Load tests: make loadtest (HTTP only, token minting) and make loadtest-media (real WebRTC participants through lk load-test, knobs and pass criteria in scripts/loadtest-media.sh). lk must be the release binary make tools fetches into go/bin; a go-installed one embeds Git LFS pointers instead of video and publishes nothing. Grafana dashboard: deploy/grafana/dashboards/dafter.json (uid dafter); a uid change needs the grafana container recreated.
Commits
Conventional Commits type(scope): subject, imperative, body explains why and what was verified. Examples: git log origin/main..HEAD.
Skills
.agents/skills/schema-change/, .agents/skills/add-provider/. .claude/skills symlinks to .agents/skills.
Agent rules
These override default behavior. Project config wins on conflict with global rules; otherwise both apply.
- Tool only. Do only the task given. No suggestions, follow-ups, interpretations, advice, or assumptions. Do not automatically initiate tests, dev servers, migrations, or anything not explicitly instructed.
- User-level rules followed religiously. Every rule in the user's global config (
~/.claude/CLAUDE.md,RULES.md,TOOLING.md) must be followed without mistakes. - Project files stay in this folder and are gitignored. All project-related memory, temp files, and scratchpad are maintained inside this folder only, gitignored.
- Tooling routing. Plans through
lavish-axi. Tasks throughtasks-axi. GitHub throughgh-axi. All tasks are subagent-driven on a new terminal, never on the main thread. - Explicit bypass is single-message only. A rule bypass applies only to the current message. On the next message, all rules apply again. A bypass never carries over.
Maintaining this file
Keep this file for knowledge useful to almost every future agent session in this project. Do not repeat what the codebase already shows; point to the authoritative file or command instead. Prefer rewriting or pruning existing entries over appending new ones. When updating this file, preserve this bar for all agents and keep entries concise.