Skip to content
3 min read · 591 words

Coding Standards

These standards are descriptive first: they document the patterns the code already follows, so new code blends in. Where the codebase is inconsistent, the preferred form is marked.

Backend (Python / FastAPI)

Layering (non-negotiable)

  • Routers: HTTP, validation, tool gating, transactions, observability logging. No prompts, no OpenAI calls.
  • Services: all LLM/vector logic. No models imports, no HTTP concerns.
  • One-way imports: routers → services/schemas/models → database. See Backend Overview.

Endpoint shape

Every handler follows the house pattern:

python
@router.post("/{agent_id}/thing", response_model=schemas.ThingResponse)
def do_thing(agent_id: int, data: schemas.ThingRequest, db: Session = Depends(get_db)):
    agent = db.query(models.Agent).filter(models.Agent.id == agent_id).first()
    if agent is None:
        raise HTTPException(status_code=404, detail="Agent not found")   # guard first
 
    if not agent_has_tool(agent, "thing_tool"):                          # gate second
        raise HTTPException(status_code=403, detail="This agent does not have thing_tool tool enabled")
 
    try:
        start = time.time()
        result = service_function(...)                                    # work third
        latency_ms = int((time.time() - start) * 1000)
        log_tool_call(db, agent.id, "thing_tool", ..., latency_ms=latency_ms)
        db.commit()                                                       # commit once
        return {...}
    except Exception as e:
        log_tool_call(db, agent.id, "thing_tool", success=False, error_message=str(e))
        db.commit()                                                       # evidence survives
        raise HTTPException(status_code=500, detail=str(e))

Order: 404 guard → 403 gate → timed work → log → single commit → respond; failures log-then-raise (Error Handling).

Conventions

TopicStandard
Typingmodern unions (str | None), typed params and returns on services
Namingsnake_case functions, PascalCase models/schemas, XCreate/XRequest/XResponse schema suffixes
LLM callsguard the API key, temperature from the ladder, strict-JSON prompts parsed with json.loads + defensive .get() normalization
New columnsadditive + migration entry
Defensive parsingsafe_int/safe_float for string-typed numeric columns
Docstringsshort, purpose-level, on services ("""Apply small SQLite schema updates...""") — not restating signatures

Known inconsistencies (prefer the fix when touching the code): duplicated agent_has_tool/log_tool_call between routers (prefer extracting a shared helper), three separate OpenAI clients (prefer one factory), mixed async def/def handlers (prefer def unless actually awaiting).

Frontend (TypeScript / React / Next.js)

TopicStandard
Componentsserver components by default; "use client" only for interactivity (one exists: CreateAgentForm)
Propsexplicit type XProps = ; no any — unknown data is unknown + narrowing (see getApiErrorMessage)
Datareads: server-side fetch + cache: "no-store" + force-dynamic; writes: route-handler proxy (pattern)
Errorsfetch failures become rendered error panels, not thrown-to-boundary (Data Fetching)
Stylingdesign-system tokens only — no raw hex, no arbitrary spacing (Design System)
Types mirroring the APIkeep page-level response types in sync with backend schemas; npm run typecheck must pass
Nullablesformat defensively (value ?? "—", Number.isNaN guards) — backend metrics are nullable

Documentation standards

  • Every module/feature change updates its docs page (the Backend pages map 1:1 to modules).
  • Internal links are relative .md paths; mkdocs build must stay warning-free (Docs Site).
  • Document reality — limitations and bugs go to Known Issues, not under the rug.

Tooling status

No Python linter/formatter (ruff/black) and no ESLint/Prettier are configured. Until they land (Roadmap), the standard is: match the style of the file you are editing — 4-space Python, 2-space TSX, double quotes in TSX, and the patterns above.