Naming

  • snake_case — functions, variables, module files, field names
  • PascalCase — classes (BrainConfig, VaultIngester, RecallResult)
  • UPPER_SNAKE_CASE — module-level constants (MEMORY_CATEGORY_MAP, KNOWN_USERS)
  • Agent singletons: module-level lowercase (recall_agent, ask_agent)
  • MCP tools: verb phrases (recall, ask, learn, create_project, vault_ingest)

Logging

Every module declares its own logger at the top, before any other code:

import logging
logger = logging.getLogger(__name__)

Use logger.info() for progress, logger.warning() for recoverable failures, logger.debug() for non-critical skips. Never use print().

Error Handling

  • External API calls: try/except Exception → log with logger.warning() → graceful fallback
  • Non-critical paths (embeddings, reranking): catch, log at debug, continue without failing
  • MCP tools: always return a str — never raise; on failure return f"Error: {e}"
  • Agents: use ModelRetry (from pydantic_ai) for retryable LLM failures

Docstrings

One-line module docstring on every file. One-line class/function docstrings for public API. MCP tool docstrings become the tool description in Claude — make them clear and include Args:.

Timeouts

Wrap every external call with asyncio.timeout(deps.config.api_timeout_seconds):

try:
    async with asyncio.timeout(timeout):
        result = await some_api_call()
except TimeoutError:
    return f"Timed out after {timeout}s."

Config Access

Never hardcode limits or thresholds. Always read from config or deps.config:

# Good
rows = rows[:ctx.deps.config.pattern_preview_limit]
# Bad
rows = rows[:200]