AI agent instructions for working in the Clawlet codebase.
Clawlet is a self-hosted AI assistant with a dual-loop cognitive architecture. Fast triage path for instant replies, deep worker for async task processing, consolidation cycle for memory management. Runs in Docker.
- Language: TypeScript
- Runtime: Node.js 22
- Lines of Code Target: ~1,200–1,500 (guidance, not hard limit)
- Files Target: ≤ 10 source files
- Dependencies Target: ≤ 10 npm packages
npm install
npm run build # TypeScript → dist/
npm run dev # Development with hot reload
npm start # Production startnpm run typecheck # Type check (no emit)
npm run lint # ESLint
npm run lint:fix # Auto-fix lint issues
npm run format # Prettier
npm run format:check # Check formattingnpm test # Run all tests
npx vitest run src/agent.test.ts # Single test file
npx vitest run -t "test name" # Single test by name
npx vitest --watch # Watch mode- Maximum 10 source files in
src/ - Each file has a single responsibility
- Target file sizes: 50-500 lines
- Keep it readable in one sitting
- Files: kebab-case.ts
- Functions: camelCase
- Types: PascalCase
- Constants: UPPER_SNAKE_CASE
- Database tables: snake_case
- Strict mode enabled
- Explicit return types on exported functions
- Avoid
any— useunknownwith type guards - All types in
src/types.ts - Use
import typefor type-only imports
- Use explicit named exports
- Group imports: builtins → npm → local
- One blank line between import groups
- No barrel files
// 1. Imports (grouped)
import { readFile } from 'fs/promises';
import OpenAI from 'openai';
import type { Message } from './types.js';
// 2. Constants
const MAX_RETRIES = 3;
// 3. Exported functions
export async function init(): Promise<void> { ... }
// 4. Private functions
function helper(): void { ... }- Parameterized queries only
- Idempotent migrations (CREATE TABLE IF NOT EXISTS)
- SQLite exclusively — 7 tables: episodic, semantic, personality, agenda, work_log, meta, schedules
- Unit tests co-located:
foo.ts→foo.test.ts - Mock external services (OpenRouter, filesystem)
- Keep tests fast (< 100ms per test)
Current allowed dependencies:
openai— OpenRouter clientbetter-sqlite3— SQLite driverexpress— HTTP serverws— WebSocket serveryaml— YAML parsingulid— Unique IDsglob— File pattern matchingcron-parser— Cron expression parsing for scheduled tasks
- Environment:
.envfile only - User rules:
config/rules.yaml(all fields overridable by env vars) - Personality:
config/personality.yaml
- Queue processor — polls incoming/ every 100ms →
processFastPath() - Deep worker — polls agenda every 1s → tool-calling loop with coherence check
- Consolidation — every N minutes → extract facts, compact, review agenda, reflect
- Fast (llama-3.1-8b): triage, coherence checks
- Deep (claude-sonnet-4): reasoning, tools, consolidation
src/
├── agent.ts # Dual-loop logic: fast path, deep worker, consolidation (~590 lines)
├── memory.ts # SQLite store, 7-table schema, JSONL log, decay (~250 lines)
├── schedule.ts # Cron-based scheduled tasks, CRUD (~95 lines)
├── agenda.ts # Working memory CRUD with cap + domain (~90 lines)
├── queue.ts # File-based message queue (~60 lines)
├── server.ts # Express + WebSocket + schedule API (~185 lines)
├── llm.ts # Dual-tier OpenRouter client (~60 lines)
├── tools.ts # 7 tools for deep worker (~70 lines)
├── types.ts # Shared interfaces (~165 lines)
└── index.ts # Entry point (~28 lines)
npm run typecheckpassesnpm run lintpassesnpm testpassesnpm run buildsucceedswc -l src/*.ts≤ ~1,500 linesls src/*.ts | wc -l≤ 10 files
- Fits in your head — Understand entire codebase in 15 minutes
- Queue everything — Debug with
lsandcat - Separate conversation from cognition — Fast replies, async deep work
- All intelligence in the cloud — Local process is a thin orchestrator
- Observe, don't control — Dashboard is a window, not a control plane
- Complexity budget is law — Fork instead of bloat