| name | Nerve Development |
| description | Nerve backend (Python) and frontend (React/TS) development and code contribution. Use when writing Python code for Nerve, fixing bugs, adding features, reviewing Nerve PRs, building the frontend, running tests, or working with the Nerve codebase. Triggers on "nerve code", "nerve PR", "fix nerve", "nerve feature", "nerve test", "build nerve UI", "nerve migration".
|
| version | 1.0.0 |
| context | domain |
Nerve Development Skill
Repository
Nerve lives under the ClickHouse organization on GitHub.
All changes go through PRs ā never push directly to main.
Getting Started (Contributors)
git clone git@github.com:<your-username>/nerve.git
cd nerve
git remote add upstream git@github.com:ClickHouse/nerve.git
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
cd web && npm install && cd ..
.venv/bin/pytest tests/ -v
cd web && npm run build
Project Layout
nerve/
āāā nerve/ # Python backend (FastAPI + Claude Agent SDK)
ā āāā cli.py # Click CLI (start/stop/restart/status/logs/doctor)
ā āāā config.py # YAML config loader
ā āāā db/ # SQLite async database package
ā ā āāā base.py # Database class (connection, _atomic, FTS, mixin composition)
ā ā āāā migrations/ # File-based migration system (numbered .py files + runner.py)
ā ā āāā sessions.py # SessionStore mixin
ā ā āāā messages.py # MessageStore mixin
ā ā āāā tasks.py # TaskStore mixin
ā ā āāā plans.py # PlanStore mixin
ā ā āāā notifications.py # NotificationStore mixin
ā ā āāā sources.py # SourceStore mixin (inbox, sync/consumer cursors)
ā ā āāā cron.py # CronStore mixin
ā ā āāā skills.py # SkillStore mixin
ā ā āāā mcp.py # McpStore mixin
ā ā āāā audit.py # AuditStore mixin
ā āāā bootstrap.py # Self-configuring onboarding (personal/worker mode)
ā āāā workspace.py # Workspace directory management
ā āāā agent/ # Agent engine, tools, sessions, streaming
ā āāā gateway/ # FastAPI server, WebSocket, auth
ā ā āāā server.py # App factory, lifespan, WebSocket, static serving
ā ā āāā auth.py # JWT auth, password verification
ā ā āāā routes/ # Domain-specific REST API route modules
ā ā āāā __init__.py # register_all_routes() assembler
ā ā āāā _deps.py # RouteDeps container (engine, db, notification_service)
ā ā āāā sessions.py # /api/sessions/*, /api/chat
ā ā āāā tasks.py # /api/tasks/*
ā ā āāā plans.py # /api/plans/*, /api/tasks/{id}/plans
ā ā āāā skills.py # /api/skills/*
ā ā āāā mcp_servers.py # /api/mcp-servers/*
ā ā āāā memory.py # /api/memory/*
ā ā āāā diagnostics.py # /api/diagnostics
ā ā āāā cron.py # /api/cron/*
ā ā āāā sources.py # /api/sources/*
ā ā āāā notifications.py # /api/notifications/*
ā ā āāā houseofagents.py # /api/houseofagents/*
ā āāā channels/ # Telegram bot, web UI channel adapters
ā āāā cron/ # APScheduler job runner
ā āāā sources/ # Data source adapters (Telegram, Gmail, GitHub)
ā āāā memory/ # Markdown files + memU semantic indexing
ā āāā skills/ # Filesystem-based skill loader
ā āāā tasks/ # Task markdown files + SQLite index
ā āāā notifications/ # Fire-and-forget + async question delivery
ā āāā proxy/ # CLIProxyAPI daemon (Docker/worker mode)
ā āāā houseofagents/ # Optional multi-agent runtime
ā āāā templates/ # Mode templates for nerve init (personal/, worker/)
āāā web/ # React + TypeScript + Vite frontend
ā āāā src/
ā ā āāā components/ # React components
ā ā āāā pages/ # Route pages
ā ā āāā stores/ # Zustand state stores
ā ā ā āāā chatStore.ts # Chat state + thin WS dispatcher
ā ā ā āāā handlers/ # Domain-specific WS message handlers
ā ā ā āāā helpers/ # Stateless helpers (block ops, buffer replay)
ā ā āāā api/ # API client utilities
ā ā āāā types/ # TypeScript definitions
ā āāā package.json
āāā tests/ # pytest suite
āāā docs/ # Architecture, config, API docs
āāā config.yaml # Main config (committed)
āāā config.local.yaml # Secrets (gitignored)
āāā pyproject.toml # Python project metadata + entry point
Gateway Routes Architecture
Routes are split into domain-specific modules under nerve/gateway/routes/. Each module:
- Defines its own
router = APIRouter() with related endpoints
- Imports
get_deps() from _deps.py to access engine/db/notification_service
- Keeps Pydantic request models co-located with the handlers
Adding a new endpoint: Create or edit the appropriate domain module, add the route handler ā it's automatically included via register_all_routes() in __init__.py.
Adding a new domain: Create routes/new_domain.py with a router = APIRouter(), add endpoints, then import and include it in routes/__init__.py.
Dependency access pattern:
from nerve.gateway.routes._deps import get_deps
@router.get("/api/example")
async def example(user: dict = Depends(require_auth)):
deps = get_deps()
Development Workflow
Mandatory order for every change:
- Create a feature branch ā
git checkout -b <username>/<feature-name> from up-to-date main
- Backend first ā modify Python files under
nerve/
- Frontend second ā modify TypeScript/React under
web/src/
- Run tests ā
cd ~/nerve && .venv/bin/pytest tests/ -v
- Build UI ā
cd ~/nerve/web && npm run build
- Commit ā focused, scoped commits to the feature branch
- Push to fork ā
git push origin <username>/<feature-name>
- Create a PR ā open PR targeting
ClickHouse/nerve:main
PR Workflow
git fetch upstream
git checkout main
git merge upstream/main
git checkout -b <username>/<feature-name>
git push origin <username>/<feature-name>
gh pr create --repo ClickHouse/nerve \
--title "Short description" \
--body "Details of the change"
PR conventions:
- Keep PRs focused ā one feature or fix per PR
- Include test coverage for new functionality
- Run the full test suite before opening
Build Commands
Backend
cd ~/nerve
.venv/bin/pytest tests/ -v
.venv/bin/pytest tests/test_sessions.py -v
.venv/bin/pytest tests/test_db.py::TestClassName -v
.venv/bin/uv pip install -e .
.venv/bin/nerve doctor
Frontend
cd ~/nerve/web
npm run build
npm install
npm run build runs tsc -b && vite build. Output goes to web/dist/ which FastAPI serves as static files.
Critical Rules
-
NEVER push directly to main. All changes require a PR to ClickHouse/nerve.
-
ALWAYS run npm run build after ANY frontend change. The server serves pre-built static files from web/dist/. Skip the build = changes invisible. This is the #1 gotcha.
-
ALWAYS run tests before committing. All tests must pass.
-
ALWAYS memory_recall before modifying Nerve code. Check for past issues, conventions, and preferences.
-
Config files: config.yaml (committed) + config.local.yaml (gitignored, secrets). Never put API keys in config.yaml.
-
Database migrations: Each migration is a numbered Python file in nerve/db/migrations/ (e.g. v018_your_feature.py) exporting an async def up(db) function. SCHEMA_VERSION is derived automatically from the highest migration file number. To add a new migration, create the next numbered file ā no other changes needed.
-
Database domain modules: Data access methods are organized into mixin classes by domain (sessions, tasks, plans, etc.) under nerve/db/. The Database class in base.py inherits all mixins. Add new methods to the appropriate domain mixin, not to base.py.
-
Commit style: Focused, scoped commits. Backend + frontend + docs for one feature = one commit. Don't bundle unrelated changes.
ā ļø Security: Pre-Commit Leak Check
Nerve is open source. This repo is PUBLIC. Before every commit and push, scan the staged diff for leaked secrets.
What to scan for:
- API keys and tokens (Anthropic, OpenAI, Brave, Telegram, Grafana, any
sk-, glsa_, bearer tokens)
- Passwords and hashes (bcrypt hashes, JWT secrets, keyring passwords)
- Personal emails or usernames
- Service URLs with credentials or internal hostnames
- Telegram API credentials (
api_id, api_hash, bot_token)
- Anything that looks like it belongs in
config.local.yaml
Safe to commit:
- Generic field names with empty defaults (
anthropic_api_key: str = "")
- References to
config.local.yaml in comments/docs
- Example placeholder values in docs
If anything leaks: Do NOT push. Unstage the file, fix it, re-scan. If already pushed, the secret must be rotated immediately.
No Real-Life Data in Examples
Never use real-world information in code comments, docstrings, test fixtures, or LLM prompt examples. This includes:
- Real project/company names ā use generic placeholders like
"deploy the app", "fix issue #101"
- Real incidents or tasks ā don't copy from your task list into test data; invent obviously synthetic scenarios
- Real bot/service names ā use generic names like
some-ci[bot]
- Real credit card numbers, even partial ā use standard test numbers like
4111111111111111
- Realistic-looking issue numbers from real trackers ā use low, obviously synthetic numbers
- Real usernames in examples ā use
<your-username> or alice/bob
Why: Even "innocent" examples build an identity fingerprint when cross-referenced. Multiple small leaks across comments, tests, and docs compound into a deanonymization risk.
Rule of thumb: If an example could be traced back to a specific person, company, or incident ā replace it with something generic.
Restarting Nerve
nerve restart
nerve stop && nerve start
nerve status
nerve logs
Key paths:
- PID:
~/.nerve/nerve.pid
- Log:
~/.nerve/nerve.log
- DB:
~/.nerve/nerve.db
Key Tech Stack
| Layer | Tech |
|---|
| Backend | Python 3.12+, FastAPI, Uvicorn, aiosqlite |
| AI | Claude Agent SDK, Anthropic API |
| Scheduler | APScheduler |
| Telegram | python-telegram-bot + Telethon |
| Auth | JWT (pyjwt) + bcrypt |
| Frontend | React 19, TypeScript 5.9, Vite 7, Tailwind 4 |
| State | Zustand |
| Tests | pytest + pytest-asyncio |