FastAPI backend and LangChain agent patterns for chatServer/ and src/. Use when writing or modifying Python code in chatServer/, src/core/, or tests/. Covers service layer, dependency injection, Pydantic validation, RLS, auth (ES256), agent tools (BaseTool), agent loading, executor caching, prompt template rendering, and content block handling.
"heartbeat": channel="heartbeat", appends checklist to prompt, suppresses notification when output starts with HEARTBEAT_OK, status="heartbeat_ok"
Checklist lives in agent_schedules.config JSONB under heartbeat_checklist key. See docs/architecture/heartbeat-system.md.
Onboarding Detection
build_agent_prompt() accepts memory_notes parameter. When both memory_notes and user_instructions are empty on interactive channels (web/telegram), an onboarding section is injected into the system prompt. Self-resolving: once the agent calls store_memory or update_instructions, subsequent loads skip onboarding.
Prompt Template Rendering (SPEC-019)
build_agent_prompt() accepts an optional prompt_template parameter (from agent_configurations.prompt_template). When set, uses string.Template ($placeholder syntax) instead of hardcoded assembly. Empty sections are auto-stripped via regex.
Available placeholders: $soul, $identity, $operating_model, $channel_guidance, $tool_guidance, $instructions, $memory_notes, $session. Falls back to hardcoded assembly when prompt_template is None/empty.
Agent Tool Pattern
All agent tools use dedicated BaseTool subclasses — BaseTool subclass in chatServer/tools/ + Service in chatServer/services/. See task_tools.py / task_service.py or reminder_tools.py / reminder_service.py as references.
CRUDTool (DB-configured via JSONB) was deprecated in SPEC-019. All tools are now dedicated BaseTool subclasses with explicit args_schema, async support, and service-layer delegation.
Recipe: Add a New Tool (End-to-End)
Per A6 (tools are the unit of agent capability):
DB row: Insert into agent_tools with agent_id UUID FK, tool name (verb_resource per A10), description, config JSONB
Service: Create chatServer/services/<resource>_service.py with business logic (per A1)
Tool class: Create chatServer/tools/<resource>_tools.py — BaseTool subclass calling the service
Registry: Register in chatServer/tools/__init__.py or via agent_tools DB config
Auth token source — Frontend must use supabase.auth.getSession(), not Zustand.
Supabase client timing — May not be initialized when agent tools are first wrapped. Non-fatal warning.
AgentExecutor.agent must not be replaced — Use self.agent.runnable = new_runnable, not self.agent = new_runnable_sequence. Bypasses Pydantic validator, strips aplan().
Settings created before load_dotenv() — Call settings.reload_from_env() after load_dotenv() in main.py.
PostgREST upsert requires real UNIQUE constraint — Partial unique indexes don't work with Supabase ON CONFLICT. Use select-then-insert if needed.
Executor cache survives new sessions — The agent executor is cached per (user_id, agent_name), not per session. After changing tool rows in the DB, a new session ID won't pick up the changes. Restart chatServer to clear the executor cache after any tool DB changes.
Mocking a method entirely hides its internals — If a test mocks _digest_single entirely, the query-building logic inside it is never exercised. When you mock a whole method, ask: "is the logic inside this method tested anywhere?" If not, add a direct test of the internals (e.g., call _digest_single with a mock search tool and assert the query string).
Circular imports via chatServer/services/__init__.py — __init__.py re-exports all services. If a tool file imports a service at module level, and that service transitively imports __init__.py, you get a circular import that crashes on startup. Fix: Use lazy imports inside _arun() or a helper function, never at module top level. See calendar_tools.py_get_calendar_service() for the pattern.
Tool registration triplet must be atomic — Adding a new tool requires updating three files together: TOOL_REGISTRY in src/core/agent_loader_db.py, TOOL_APPROVAL_DEFAULTS in chatServer/security/approval_tiers.py, and CANONICAL_TOOL_NAMES in tests/chatServer/services/test_tool_registry_validator.py. Missing any one causes CI failure.
agent_tool_type enum no longer exists — Removed in SPEC-019. tools.type is now TEXT. Do NOT use ALTER TYPE agent_tool_type ADD VALUE in migrations — it will fail.
Detailed Reference
For full patterns with code examples, see reference.md.
1---2name: backend-patterns3description: FastAPI backend and LangChain agent patterns for chatServer/ and src/. Use when writing or modifying Python code in chatServer/, src/core/, or tests/. Covers service layer, dependency injection, Pydantic validation, RLS, auth (ES256), agent tools (BaseTool), agent loading, executor caching, prompt template rendering, and content block handling.4---56# Backend Patterns78## Principles That Apply910| ID | Rule | Enforcement |11|----|------|-------------|12| A1 | Thin routers, fat services — no `.select()` in routers | `validate-patterns.sh` BLOCKS |13| A3 | Supabase REST+RLS for user CRUD; psycopg for framework ops | Reviewer |14| A5 | Auth via `Depends(get_current_user)` and `getSession()` | `validate-patterns.sh` BLOCKS in hooks |15| A6 | New capability = BaseTool subclass + service + DB row | backend-patterns recipe |16| A8 | Routers use `get_user_scoped_client`; raw client blocked | `validate-patterns.sh` BLOCKS |17| A10 | Entity "foo" → `foo_service.py`, `foo_router.py` | Reviewer + `task-completed-gate.sh` |1819For full rationale on any principle: `.claude/skills/architecture-principles/reference.md`2021## Architecture: Routers → Services → Database2223```24chatServer/25 routers/ → HTTP handling only (request/response)26 services/ → Business logic only27 models/ → Pydantic schemas28 dependencies/ → auth.py, agent_loader.py (FastAPI Depends)29 database/ → connection.py, supabase_client.py30 config/ → settings.py31```3233**Never put business logic in routers. Never put HTTP handling in services.**3435## Quick Checklist3637Before writing backend code, verify:38- [ ] Env vars use canonical names: `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY` (not `VITE_SUPABASE_URL` or `SUPABASE_SERVICE_KEY`)39- [ ] New Python deps added to BOTH `requirements.txt` AND `chatServer/requirements.txt`40- [ ] Using `Depends(get_user_scoped_client)` in routers, `get_system_client()` in background services — never raw `get_supabase_client` (A8/SPEC-017)41- [ ] Business logic in services, not routers42- [ ] Pydantic models for request/response validation43- [ ] RLS handles user scoping (no manual `user_id` filtering)44- [ ] `Depends(get_current_user)` for auth — no manual header parsing45- [ ] Error handling re-raises HTTPException, logs unexpected errors46- [ ] Agent tools use dedicated BaseTool subclass + service (see "Agent Tool Pattern" below)47- [ ] Content block lists normalized to strings in chat responses48- [ ] Tool name follows `verb_resource` pattern (e.g., `create_reminder`, `list_reminders`)49- [ ] Tool verb is from approved list: create, list, get, update, delete, search, save, read, send, fetch5051## Scheduled Execution Patterns5253### Heartbeat vs Regular Schedules5455`ScheduledExecutionService.execute()` handles both types based on `config.schedule_type`:5657- **`"scheduled"` (default)**: channel=`"scheduled"`, always notifies, status=`"success"`58- **`"heartbeat"`**: channel=`"heartbeat"`, appends checklist to prompt, suppresses notification when output starts with `HEARTBEAT_OK`, status=`"heartbeat_ok"`5960Checklist lives in `agent_schedules.config` JSONB under `heartbeat_checklist` key. See `docs/architecture/heartbeat-system.md`.6162### Onboarding Detection6364`build_agent_prompt()` accepts `memory_notes` parameter. When both `memory_notes` and `user_instructions` are empty on interactive channels (`web`/`telegram`), an onboarding section is injected into the system prompt. Self-resolving: once the agent calls `store_memory` or `update_instructions`, subsequent loads skip onboarding.6566### Prompt Template Rendering (SPEC-019)6768`build_agent_prompt()` accepts an optional `prompt_template` parameter (from `agent_configurations.prompt_template`). When set, uses `string.Template` (`$placeholder` syntax) instead of hardcoded assembly. Empty sections are auto-stripped via regex.6970Available placeholders: `$soul`, `$identity`, `$operating_model`, `$channel_guidance`, `$tool_guidance`, `$instructions`, `$memory_notes`, `$session`. Falls back to hardcoded assembly when `prompt_template` is None/empty.7172## Agent Tool Pattern7374All agent tools use **dedicated BaseTool subclasses** — `BaseTool` subclass in `chatServer/tools/` + `Service` in `chatServer/services/`. See `task_tools.py` / `task_service.py` or `reminder_tools.py` / `reminder_service.py` as references.7576CRUDTool (DB-configured via JSONB) was deprecated in SPEC-019. All tools are now dedicated BaseTool subclasses with explicit `args_schema`, async support, and service-layer delegation.7778## Recipe: Add a New Tool (End-to-End)7980Per A6 (tools are the unit of agent capability):81821. **DB row:** Insert into `agent_tools` with `agent_id` UUID FK, tool `name` (verb_resource per A10), `description`, `config` JSONB832. **Service:** Create `chatServer/services/<resource>_service.py` with business logic (per A1)843. **Tool class:** Create `chatServer/tools/<resource>_tools.py` — `BaseTool` subclass calling the service854. **Registry:** Register in `chatServer/tools/__init__.py` or via `agent_tools` DB config865. **Tests:** `tests/chatServer/tools/test_<resource>_tools.py` + `tests/chatServer/services/test_<resource>_service.py`876. **Agent config:** Add tool name to agent's `tool_names` array in `agent_configurations`8889Reference implementations: `task_tools.py`/`task_service.py`, `reminder_tools.py`/`reminder_service.py`9091## Recipe: Add a New API Endpoint9293Per A1 (thin routers, fat services):94951. **Router:** `chatServer/routers/<resource>_router.py` — `Depends(get_current_user)`, delegates to service962. **Service:** `chatServer/services/<resource>_service.py` — business logic, DB calls973. **Models:** `chatServer/models/<resource>.py` — Pydantic request/response schemas984. **Register:** Add router to `chatServer/main.py` with appropriate prefix995. **Tests:** `tests/chatServer/routers/test_<resource>_router.py` + service tests1006. **Frontend hook:** (handed off to frontend-dev) `webApp/src/api/hooks/use<Resource>Hooks.ts`101102## Data Plane Guidance (A3)103104| Use Supabase REST+RLS when... | Use PostgreSQL (psycopg) when... |105|-------------------------------|----------------------------------|106| User CRUD operations | High-volume reads/writes |107| RLS handles authorization | Framework operations (LangChain message history) |108| Simple queries (select, insert, update) | Complex joins or CTEs |109| Frontend-initiated operations | Background/scheduled jobs |110111## Key Gotchas1121131. **ES256 tokens** — Supabase issues ES256, not HS256. Don't revert auth.py to HS256-only.1142. **Content block lists** — Newer `langchain-anthropic` returns `[{"text": "...", "type": "text"}]`. Normalize in chat.py.1153. **Auth token source** — Frontend must use `supabase.auth.getSession()`, not Zustand.1164. **Supabase client timing** — May not be initialized when agent tools are first wrapped. Non-fatal warning.1175. **AgentExecutor.agent must not be replaced** — Use `self.agent.runnable = new_runnable`, not `self.agent = new_runnable_sequence`. Bypasses Pydantic validator, strips `aplan()`.1186. **Settings created before load_dotenv()** — Call `settings.reload_from_env()` after `load_dotenv()` in main.py.1197. **PostgREST upsert requires real UNIQUE constraint** — Partial unique indexes don't work with Supabase `ON CONFLICT`. Use select-then-insert if needed.1208. **Executor cache survives new sessions** — The agent executor is cached per `(user_id, agent_name)`, not per session. After changing tool rows in the DB, a new session ID won't pick up the changes. **Restart chatServer** to clear the executor cache after any tool DB changes.1219. **Mocking a method entirely hides its internals** — If a test mocks `_digest_single` entirely, the query-building logic inside it is never exercised. When you mock a whole method, ask: "is the logic inside this method tested anywhere?" If not, add a direct test of the internals (e.g., call `_digest_single` with a mock search tool and assert the query string).12210. **Circular imports via `chatServer/services/__init__.py`** — `__init__.py` re-exports all services. If a tool file imports a service at module level, and that service transitively imports `__init__.py`, you get a circular import that crashes on startup. **Fix:** Use lazy imports inside `_arun()` or a helper function, never at module top level. See `calendar_tools.py` `_get_calendar_service()` for the pattern.12311. **Tool registration triplet must be atomic** — Adding a new tool requires updating three files together: `TOOL_REGISTRY` in `src/core/agent_loader_db.py`, `TOOL_APPROVAL_DEFAULTS` in `chatServer/security/approval_tiers.py`, and `CANONICAL_TOOL_NAMES` in `tests/chatServer/services/test_tool_registry_validator.py`. Missing any one causes CI failure.12412. **`agent_tool_type` enum no longer exists** — Removed in SPEC-019. `tools.type` is now `TEXT`. Do NOT use `ALTER TYPE agent_tool_type ADD VALUE` in migrations — it will fail.125126## Detailed Reference127128For full patterns with code examples, see [reference.md](reference.md).
Run npx skillmds@latest add tim-o-private/backend-patterns in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
FastAPI backend and LangChain agent patterns for chatServer/ and src/. Use when writing or modifying Python code in chatServer/, src/core/, or tests/. Covers service layer, dependency injection, Pydantic validation, RLS, auth (ES256), agent tools (BaseTool), agent loading, executor caching, prompt template rendering, and content block handling. It is listed under AI & ML on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: makes network calls, reads secrets. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
tim-o-private (@tim-o-private) published this skill. Their other Agent Skills are listed on their SkillMD profile.