Naming
snake_case— functions, variables, module files, field namesPascalCase— 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 withlogger.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 returnf"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]