Development guidance for Powernode: open-source mission control for AI agent fleets.
This file is the outage-safe core — only global, high-stakes rules that must hold even if the
MCP server is down. Situational rules live in hooks, scripts/pattern-validation.sh, and
docs/contributing/conventions/ (recalled MCP-first). See
Where guidance lives.
Control plane for production AI agent fleets — knowledge graph, governance, swarm coordination, MCP-native runtime, and the fleet substrate (bare-metal / VM / container lifecycle). MIT.
- Backend: Rails 8 API (
./server) — JWT auth, UUIDv7 PKs. Does NOT run Sidekiq. - Frontend: React TypeScript (
./frontend) — theme-aware, Tailwind. - Worker: Sidekiq standalone (
./worker) — API-only communication. - Public extensions (git submodules, MIT on GitHub):
./extensions/system,./extensions/marketing,./extensions/supply-chain. - Private extensions (remote-only, not in public clones; added by maintainers): under
./extensions/private/*. The platform runs in core mode when absent — single-user self-hosted, all core capabilities unlocked, no billing/SaaS. - Database: PostgreSQL (native UUID) + pgvector.
Project Status: docs/reference/auto/todo.md (auto-generated — do not edit).
Account → User (many), Agent (many), Skill (many)
User → Roles, Permissions, Invitations
Agent → Conversations, Tasks, Goals, ApprovalRequests
Rules are routed by trigger shape — the mechanism that surfaces or enforces each one when relevant:
| Rule shape | Home | Surfaces / enforces via |
|---|---|---|
| Always + high-stakes + not checkable | this file (core) | always loaded |
| Mechanically checkable | .claude/hooks/*.sh + scripts/pattern-validation.sh |
edit-time hook / adherence scan |
| Reusable pattern / procedure | conventions/ + platform knowledge | MCP-first queries + SessionStart digest |
| Subsystem-scoped | nested server//frontend//worker/ CLAUDE.md |
loads when editing that tree |
| Decision / incident / preference | auto-memory (~/.claude/.../memory/MEMORY.md) |
harness relevance injection |
| Crypto / private-extension logic | extension only (never core) | core-purity-check.sh (blocking) |
- Auto-memory (
MEMORY.mdindex, loaded each session) records operational decisions, incidents, and your working preferences. Consult it; it reflects what was true when written — verify file/flag references before acting. - MCP-first is mandatory for non-trivial work: query
platform.query_learnings/search_knowledge/code_semantic_searchbefore changing code (full protocol: conventions/mcp-first-workflow.md). Contribute back after (create_learning/create_knowledge). - Migrated convention rules are tagged
guidance-*in platform knowledge and digested at session start.
- NEVER commit unless explicitly requested. NEVER include Claude attribution in commits.
- Branch:
develop→feature/*→release/*→master. Tags/release branches: NO "v" prefix (0.2.0,release/0.2.0). - Staged commits: group changes into logical commits by concern — never one monolithic commit.
Frontend MUST use permissions ONLY — NEVER roles for access control.
currentUser?.permissions?.includes('users.manage') // ✅ CORRECT
currentUser?.roles?.includes('admin') // ❌ FORBIDDEN
user.role === 'manager' // ❌ FORBIDDENBackend: current_user.has_permission?('name') — NEVER permissions.include?() (returns objects).
Backstopped by permission-not-roles-check.sh (advisory).
Generic key-handling principles — apply to ALL key material. Private-extension specifics (audit sinks, wallet/signing internals) live in the extension / CLAUDE.local.md, never here.
| Rule | Details |
|---|---|
| No key output | NEVER output, log, display, echo, or transmit private keys, API secrets, seed phrases, mnemonics, or signing material in any form |
| No keys in code | NEVER store keys/secrets/credentials in source, scripts, configs, env files, or docs |
| No CLI key generation | NEVER generate private keys via CLI (rails runner, rake, irb) where they could hit shell history |
| Vault-only storage | ALL key generation MUST happen inside Vault or WalletKeyService (stores directly to Vault) |
| Audit all key ops | ALL key operations (generate, import, revoke, sign) MUST be logged to the audit log. TODO(verify: confirm core audit-log sink) |
| No key args in logs | NEVER pass keys as function arguments that could appear in logs/errors/traces |
| Guide, don't handle | When assisting with wallet/key setup, guide through the UI/API — never handle key material directly |
Backend (high-stakes — full conventions in backend-patterns.md)
- Eager Loading: always
.includes()when iterating associations — never bare.allthen.map/.eachaccessing relations. (Nudge:n-plus-one-check.sh.) - Webhook Receivers: inbound webhooks MUST return 200/202 on processing errors — NEVER 500 (provider retry storms). (Nudge:
webhook-500-check.sh.) - Everything else (namespacing, FK prefixes, JSON lambda defaults,
t.referencesindexes, controller size, frozen-string,render_success) is hook/scan-enforced — see the doc.
- server (
server/) is a Rails API — it does NOT run Sidekiq. - worker (
worker/) is standalone Sidekiq — talks to server via HTTP API only. - NEVER create job classes in
server/app/jobs/; NEVER add Sidekiq gems toserver/Gemfile; NEVER modifyworker/when fixing server issues.
- State the count before ANY bulk operation: "This will affect N items."
- Operations affecting more than 5 items require explicit user confirmation; show the first 3 and last 1.
- Never batch-approve training decisions, permission grants, financial operations, or auto-discovered code changes — review individually.
- Reuse First:
platform.discover_skills+search_knowledge+code_semantic_searchbefore proposing anything new — never greenfield when infrastructure exists. - Stop & Ask (HARD): after 3 failed attempts at the same fix, STOP and ask. No 4th approach, no workarounds.
- Surface Assumptions: state assumptions before implementing ambiguous requests; if multiple valid interpretations exist, present and ask.
- Audit = report only: when asked to audit/review/analyze, save findings to
docs/; do NOT implement unless told to fix. - Test-First Bug Reproduction: write a failing test reproducing the bug before the fix.
- Trace Changes to Request: every changed line traces to the request; revert adjacent "improvements" (scope creep).
- Plan Before Multi-File: changes touching 3+ files — outline files + data flow, get approval before writing.
- Dead Reference Cleanup: after deleting a file,
grep -rfor references and remove them (scope: orphans your changes created). - Completion Gate: run
/verifyon changed files before reporting work done. - Quality Gates:
cd frontend && npx tsc --noEmitafter TS; Ruby syntax + related spec after.rb;rails db:seedafter seeds.
- Pull, Never Push: downstream managers pull from upstream; upstream never pushes downstream. When unsure of data-flow direction, ask.
- Extension Isolation: each
extensions/*is self-contained. Extensions depend on core; core NEVER depends on extensions. Enforced by the blockingcore-purity-check.sh— core source must not reference a private extension by name (itsNamespace::, submodule path, or import alias); the hook derives the forbidden names dynamically fromextensions/private/*. Route through a generic seam instead. - Service Boundaries: cross-namespace communication goes through service interfaces, never direct model access.
- Submodules: public
extensions/{system,marketing,supply-chain}+ privateextensions/private/*. Do NOT rungit submodule syncon the public ones (drops the private upstream remote). - CWD verification: before EVERY
git add/git commit, rungit rev-parse --show-topleveland confirm the repo. - Survey both statuses:
git statusin root ANDgit -C extensions/<name> status— submodule changes are invisible to the parent. - Never commit extension files from parent; commit inside the submodule FIRST, then update the pointer in the parent.
- Maintainer-local detail (private extensions, audit sinks) lives in
CLAUDE.local.md— never the public repo.
NEVER save files to project root. Use docs/{getting-started,concepts,guides,reference,operations,contributing}/. docs/reference/auto/ is auto-generated — do not edit.
| Term | Means | Not |
|---|---|---|
server/ |
Rails app directory | "backend directory" |
powernode-backend |
systemd service (@default) |
"rails service" |
worker/ |
standalone Sidekiq app | "job runner" |
powernode-worker |
systemd service (@default) |
"sidekiq service" |
Use platform.discover_skills with a task description to find the right specialist. Query MCP first; these files are the fallback:
| Topic | MCP Query | File Fallback |
|---|---|---|
| Conventions (backend/frontend/testing/ops) | search_knowledge tag guidance-* |
docs/contributing/conventions/ |
| MCP config & workflow | discover_skills |
conventions/mcp-first-workflow.md |
| Knowledge lifecycle | — | conventions/knowledge-lifecycle.md |
| Service mgmt & ops | — | conventions/service-and-ops.md |
| MCP tool catalog | — | docs/reference/auto/mcp-tools.md (auto-generated) |
| Permissions / Theme / API / UUID / Architecture | search_knowledge / search_knowledge_graph |
docs/concepts/, docs/reference/ |
| Learnings & patterns | query_learnings |
docs/reference/auto/learnings.md |
Per-subsystem rules also live in nested server/CLAUDE.md, frontend/CLAUDE.md, worker/CLAUDE.md (load automatically when editing those trees).