10 KiB
AGENTS.md
Guidelines for using automation and AI agents (GitHub Copilot, dependency/CI bots, and in-repo runtime schedulers/workers) with the g0v0-server repository.
API References
This project must stay compatible with the public osu! APIs. Use these references when adding or mapping endpoints:
- v1 (legacy): https://github.com/ppy/osu-api/wiki
- v2 (OpenAPI): https://osu.ppy.sh/docs/openapi.yaml
Any implementation in app/router/v1/, app/router/v2/, or app/router/notification/ must match official endpoints from the corresponding specification above. Custom or experimental endpoints belong in app/router/private/.
Agent Categories
Agents are allowed in three categories:
- Code authoring / completion agents (e.g. GitHub Copilot or other LLMs) — allowed only when a human maintainer reviews and approves the output.
- Automated maintenance agents (e.g. Dependabot, Renovate, pre-commit.ci) — allowed but must follow strict PR and CI policies.
- Runtime / background agents (schedulers, workers) — part of the product code; must follow lifecycle, concurrency, and idempotency conventions.
All changes produced or suggested by agents must comply with the rules below.
Rules for All Agents
- Human review required. Any code, configuration, or documentation generated by an AI or automation agent must be reviewed and approved by a human maintainer familiar with g0v0-server. Do not merge agent PRs without explicit human approval.
- Single-responsibility PRs. Agent PRs must address one concern only (one feature, one bugfix, or one dependency update). Use Angular-style commit messages (e.g.
feat(api): add ...). - Lint & CI compliance. Every PR (including agent-created ones) must pass
pyright,ruff,pre-commithooks, and the repository CI before merging. Include links to CI runs in the PR. - Never commit secrets. Agents must not add keys, passwords, tokens, or real
.envvalues. If a suspected secret is detected, the agent must abort and notify a designated human. - API location constraints. Do not add new public endpoints under
app/router/v1orapp/router/v2unless the endpoints exist in the official v1/v2 specs. Custom or experimental endpoints must go underapp/router/private/. - Stable public contracts. Avoid changing response schemas, route prefixes, or other public contracts without an approved migration plan and explicit compatibility notes in the PR.
Copilot / LLM Usage
Consolidated guidance for using GitHub Copilot and other LLM-based helpers with this repository.
Key project structure (what you should know)
-
App entry:
main.py— FastAPI application with lifespan startup/shutdown orchestration (fetchers, GeoIP, schedulers, cache and health checks, Redis messaging, stats, achievements). -
Routers:
app/router/contains route groups. Important routers exposed by the project include:api_v1_router(v1 endpoints)api_v2_router(v2 endpoints)notificationrouters (chat/notification subsystems)auth_router(authentication/token flows)private_router(internal or server-specific endpoints)
Rules:
v1/andv2/must mirror the official APIs. Put internal-only or experimental endpoints underapp/router/private/. -
Models & DB helpers:
- SQLModel/ORM models live in
app/models/. - DB access helpers and table-specific helpers live in
app/database/. - For model/schema changes, draft an Alembic migration and manually review the generated SQL and indexes before applying.
- SQLModel/ORM models live in
-
Services:
app/service/holds domain logic (e.g., user ranking calculation, caching helpers, notification/email logic). Heavy logic belongs in services rather than in route handlers. -
Schedulers:
app/scheduler/contains scheduler starters; implementstart_*_scheduler()andstop_*_scheduler()and register them inmain.pylifespan handlers. -
Caching & dependencies: Use injected Redis dependencies from
app/dependencies/and shared cache services (follow existing key naming conventions such asuser:{id}:...). -
Rust/native extensions:
packages/msgpack_lazer_apiis a native MessagePack encoder/decoder. When changing native code, runmaturin develop -Rand validate compatibility with Python bindings.
Practical playbooks (prompt patterns)
- Add a v2 endpoint (correct): Add files under
app/router/v2/, export the router, implement async path operations using DB and injected caching dependencies. Do not add non-official endpoints to v1/v2. - Add an internal endpoint: Add under
app/router/private/; keep route handlers thin and move business logic intoapp/service/. - Add a background job: Put pure job logic in
app/service/_job.py(idempotent, retry-safe). Add scheduler start/stop functions inapp/scheduler/_scheduler.py, and register them in the app lifespan. - DB schema changes: Update SQLModel models in
app/models/, runalembic revision --autogenerate, inspect the migration, and validate locally withalembic upgrade headbefore committing. - Cache writes & responses: Use existing
UserResppatterns andUserCacheServicewhere applicable; use background tasks for asynchronous cache writes.
Prompt guidance (what to include for LLMs/Copilot)
- Specify the exact file location and constraints (e.g.
Add an async endpoint under app/router/private/ ... DO NOT add to app/router/v1 or v2). - Ask for asynchronous handlers, dependency injection for DB/Redis, reuse of existing services/helpers, type annotations, and a minimal pytest skeleton.
- For native edits, require build instructions, ABI compatibility notes, and import validation steps.
Conventions & quality expectations
- Commit message style:
type(scope): subject(Angular-style). - Async-first: Route handlers must be async; avoid blocking the event loop.
- Separation of concerns: Business logic should live in services, not inside route handlers.
- Error handling: Use
HTTPExceptionfor client errors and structured logging for server-side issues. - Types & linting: Aim for
pyright-clean,ruff-clean code before requesting review. - Comments: Avoid excessive inline comments. Add short, targeted comments to explain non-obvious or "magical" behavior.
Human reviewer checklist
- Is the code async and non-blocking, with heavy logic in
app/service/? - Are DB and Redis dependencies injected via the project's dependency utilities?
- Are existing cache keys and services reused consistently?
- Are tests or test skeletons present and runnable?
- If models changed: is an Alembic migration drafted, reviewed, and applied locally?
- If native code changed: was
maturin develop -Rexecuted and validated? - Do
pyrightandruffpass locally?
Merge checklist
- Run
uv syncto install/update dependencies. - Run
pre-commithooks and fix any failures. - Run
pyrightandrufflocally and resolve issues. - If native modules changed: run
maturin develop -R. - If DB migrations changed: run
alembic upgrade headlocally to validate.
Tooling reference
uv sync
pre-commit install
pre-commit run --all-files
pyright
ruff .
maturin develop -R # when native modules changed
alembic revision --autogenerate -m "feat(db): ..."
alembic upgrade head
uvicorn main:app --reload --host 0.0.0.0 --port 8000
PR scope guidance
- Keep PRs focused: one concern per PR (e.g., endpoint OR refactor, not both).
- Update README/config docs when adding new environment variables.
- If unsure about conventions, align with the closest existing service and leave a clarifying comment.
Performance Tips
Below are practical, project-specific performance tips derived from this repository's architecture (FastAPI + SQLModel/SQLAlchemy, Redis caching, background schedulers, and a Rust-native messagepack module).
Database
- Select only required fields. Fetch only the columns you need using
select(Model.col1, Model.col2)instead ofselect(Model).
stmt = select(User.id, User.username).where(User.active == True)
rows = await session.execute(stmt)
- Use `` for existence checks. This avoids loading full rows:
from sqlalchemy import select, exists
exists_stmt = select(exists().where(User.id == some_id))
found = await session.scalar(exists_stmt)
-
Avoid N+1 queries. Use relationship loading strategies (
selectinload,joinedload) when you need related objects. -
Batch operations. For inserts/updates, use bulk or batched statements inside a single transaction rather than many small transactions.
-
Indexes & EXPLAIN. Add indexes on frequently filtered columns and use
EXPLAIN ANALYZEto inspect slow queries. -
Cursor / keyset pagination. Prefer keyset pagination for large result sets instead of
OFFSET/LIMITto avoid high-cost scans.
Caching & Redis
-
Cache hot reads. Use
UserCacheServiceto cache heavy or frequently-requested responses and store compact serialized forms (e.g., messagepack via the native module). -
Use pipelines and multi/exec. When performing multiple Redis commands, pipeline them to reduce roundtrips.
-
Set appropriate TTLs. Avoid never-expiring keys; choose TTLs that balance freshness and read amplification.
-
Prevent cache stampedes. Use early recompute with jitter or distributed locks (Redis
SET NXor a small lock library) to avoid many processes rebuilding the same cache. -
Atomic operations with Lua. For complex multi-step Redis changes, consider a Lua script to keep operations atomic and fast.
Background & Long-running Tasks
-
BackgroundTasks for lightweight work. FastAPI's
BackgroundTasksis fine for quick follow-up work (send email, async cache write). For heavy or long tasks, use a scheduler/worker (e.g., a dedicated async worker or job queue). -
Use schedulers or workers for heavy jobs. For expensive recalculations, use the repository's
app/scheduler/pattern or an external worker system. Keep request handlers responsive — return quickly and delegate. -
Throttling & batching. When processing many items, batch them and apply concurrency limits (semaphore) to avoid saturating DB/Redis.
API & Response Performance
- Compress large payloads. Enable gzip/deflate for large JSON responses