From bcbdc273a53351dd2e7f85fbb0a7324e496a7f38 Mon Sep 17 00:00:00 2001 From: Illia Polosukhin Date: Mon, 9 Mar 2026 23:19:25 +0000 Subject: [PATCH] Restructure CLAUDE.md into modular rules + add pr-shepherd command (#750) * refactor: restructure CLAUDE.md into modular rules and add pr-shepherd command Trim CLAUDE.md from 710 lines to 92 by moving detailed guidance into path-scoped `.claude/rules/` files that load on demand. Add a new `/pr-shepherd` command that consolidates the full PR lifecycle (review, fix, quality gate, CI fix loop, merge) into one workflow. Changes: - CLAUDE.md: keep only essentials (build commands, code style, architecture, module specs, config reference, debugging) - .claude/rules/review-discipline.md: 15+ review rules, scoped to src/**/*.rs - .claude/rules/database.md: dual-backend rules with SQL dialect translation table, scoped to src/db/** and migrations/** - .claude/rules/safety-and-sandbox.md: safety layer and sandbox rules, scoped to src/safety/**, src/sandbox/**, src/secrets/** - .claude/rules/testing.md: test tiers and patterns, scoped to src/** and tests/** - .claude/rules/tools.md: tool architecture and implementation pattern, scoped to src/tools/** and tools-src/** - .claude/commands/pr-shepherd.md: 7-phase PR lifecycle command that subsumes review-pr, respond-pr, ship, and manual CI fix loops [skip-regression-check] Co-Authored-By: Claude Opus 4.6 * fix: address review feedback on CLAUDE.md restructure - Restore project structure tree in CLAUDE.md (zmanian blocking) - Create .claude/rules/skills.md with trust model, SKILL.md format, selection pipeline, and skill tools (zmanian blocking) - Restore configuration section with key env vars (zmanian medium) - Restore "Adding a New Channel" guide (zmanian medium) - Add heartbeat mention to Workspace & Memory section (zmanian low) - Fix pr-shepherd: replace `git add -A` with specific file staging (zmanian) - Fix pr-shepherd: ask user for merge strategy instead of hardcoding --squash (zmanian) - Fix pr-shepherd: replace `--watch` with polling + 10min timeout (zmanian) - Fix testing.md: "skipped if DB is unreachable" not "expected to fail" (Copilot) [skip-regression-check] Co-Authored-By: Claude Opus 4.6 * fix: address review comments on PR #750 - Narrow `crate::` import rule: `super::` is fine in tests and intra-module refs - Fix capabilities file naming: `.capabilities.json` sidecar, not bare `capabilities.json` - Update mechanical verification checklist to match narrowed import rule Co-Authored-By: Claude Opus 4.6 * refactor: move Bedrock docs from CLAUDE.md to src/llm/CLAUDE.md Bedrock provider details (auth, config, feature flag) belong in the LLM module spec, not the top-level guide. Added file map entry, provider table row, and dedicated section in src/llm/CLAUDE.md. Co-Authored-By: Claude Opus 4.6 * refactor: move env var config block out of CLAUDE.md Replace 20-line config block with one-liner pointing to .env.example and src/llm/CLAUDE.md. Config details are only needed during deployment, not everyday coding. Co-Authored-By: Claude Opus 4.6 * fix: use gh pr checkout for fork-safe PR checkout in pr-shepherd Replaces git fetch/checkout with gh pr checkout {number} which handles both same-repo and fork-based PRs automatically. Co-Authored-By: Claude Opus 4.6 * fix: address Copilot review round 5 on PR #750 - Add gh pr list and gh pr checkout to pr-shepherd allowed-tools - Align crate:: import rule in pr-shepherd with updated CLAUDE.md guidance - Fix vector type in database.md: BLOB (flexible dims), not F32_BLOB(1536) - Update MCP limitation: stdio/HTTP/Unix transports exist, no streaming Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .claude/commands/pr-shepherd.md | 303 +++++++++++++ .claude/rules/database.md | 63 +++ .claude/rules/review-discipline.md | 48 ++ .claude/rules/safety-and-sandbox.md | 34 ++ .claude/rules/skills.md | 56 +++ .claude/rules/testing.md | 25 ++ .claude/rules/tools.md | 39 ++ CLAUDE.md | 674 ++++------------------------ src/llm/CLAUDE.md | 13 + 9 files changed, 662 insertions(+), 593 deletions(-) create mode 100644 .claude/commands/pr-shepherd.md create mode 100644 .claude/rules/database.md create mode 100644 .claude/rules/review-discipline.md create mode 100644 .claude/rules/safety-and-sandbox.md create mode 100644 .claude/rules/skills.md create mode 100644 .claude/rules/testing.md create mode 100644 .claude/rules/tools.md diff --git a/.claude/commands/pr-shepherd.md b/.claude/commands/pr-shepherd.md new file mode 100644 index 00000000..c6dc87a1 --- /dev/null +++ b/.claude/commands/pr-shepherd.md @@ -0,0 +1,303 @@ +--- +description: Full PR lifecycle — review, fix findings, address comments, quality gate, push, CI fix loop, merge +disable-model-invocation: true +allowed-tools: Bash(gh pr view:*), Bash(gh pr diff:*), Bash(gh pr comment:*), Bash(gh pr merge:*), Bash(gh pr checks:*), Bash(gh pr edit:*), Bash(gh pr list:*), Bash(gh pr checkout:*), Bash(gh api:*), Bash(gh repo view:*), Bash(gh run view:*), Bash(gh run watch:*), Bash(git diff:*), Bash(git log:*), Bash(git fetch:*), Bash(git checkout:*), Bash(git status:*), Bash(git branch:*), Bash(git add:*), Bash(git commit:*), Bash(git push:*), Bash(git merge:*), Bash(git rebase:*), Bash(cargo fmt:*), Bash(cargo clippy:*), Bash(cargo test:*), Bash(cargo check:*), Read, Edit, Write, Grep, Glob, Agent +argument-hint: " [--fix] [--merge] [--review-only]" +--- + +# PR Shepherd + +Full PR lifecycle: review → fix → quality gate → push → CI → merge. + +Parse `$ARGUMENTS`: +- Extract PR number from bare number or `https://github.com/owner/repo/pull/123` URL. +- Flags: `--fix` (auto-fix without asking), `--merge` (merge when CI green), `--review-only` (stop after review, don't fix). +- If no PR number, detect from current branch: `gh pr list --head $(git branch --show-current) --json number --jq '.[0].number'` +- If still nothing, stop and ask the user. + +--- + +## Phase 1: Situational Awareness + +Gather everything in parallel: + +**PR metadata:** +``` +gh pr view {number} --json number,title,body,author,baseRefName,headRefName,headRefOid,state,isDraft,mergeable,mergeStateStatus,files,additions,deletions,labels,reviewRequests +``` + +**Diff:** +``` +gh pr diff {number} +gh pr diff {number} --name-only +``` + +**CI status:** +``` +gh pr checks {number} --json name,status,conclusion,detailsUrl +``` + +**Review comments (human + bot):** +``` +gh api --paginate repos/{owner}/{repo}/pulls/{number}/comments +gh api --paginate repos/{owner}/{repo}/pulls/{number}/reviews +``` + +Resolve `{owner}/{repo}`: +``` +gh repo view --json owner,name --jq '"\(.owner.login)/\(.name)"' +``` + +Save `headRefOid` — needed for posting line comments later. + +**Assess the situation and print a status card:** + +``` +PR #{number}: {title} +Author: {author} Base: {base} ← {head} +Size: +{additions} -{deletions} across {file_count} files +CI: {PASS|FAIL|PENDING|NONE} Mergeable: {yes|no|conflict} +Reviews: {N approved, N changes_requested, N comments-only, N bot-only} +Unresolved comments: {N} +Draft: {yes|no} +``` + +**Decide the mode** based on situation: +- **Has unresolved review comments** → Phase 2a (address comments first, then review remaining) +- **No reviews yet / bot-only reviews** → Phase 2b (full deep review) +- **CI failing, no review issues** → Phase 4 (jump to CI fix) +- **Everything green + approved** → Phase 6 (ready to merge) + +--- + +## Phase 2a: Address Existing Review Comments + +For each unresolved review comment or review with CHANGES_REQUESTED: + +1. **Read the referenced code** at the file and line mentioned. Never assess without reading. +2. **Classify each comment:** + - ✅ **Valid & unresolved** — needs a code fix + - ✅ **Already fixed** — a later commit addressed it + - ❌ **False positive** — explain why the code is correct + - 🔧 **Nit** — optional improvement, not blocking + +3. **Deduplicate** — bots (Copilot, Gemini) often post the same finding. Group by actual issue. + +Present a table: + +| # | Source | File:Line | Issue | Status | Planned Fix | +|---|--------|-----------|-------|--------|-------------| + +Wait for user confirmation (unless `--fix` flag set), then proceed to Phase 3. + +--- + +## Phase 2b: Deep Review (6 Lenses) + +Read EVERY changed file in full (not just diff hunks). For PRs touching >20 files, prioritize: service logic > handlers > types > tests > docs. Batch reads in parallel via Agent tool. + +### IronClaw-specific checks (always) +- No `.unwrap()` or `.expect()` in production code +- Prefer `crate::` for cross-module imports (`super::` OK in tests/intra-module) +- Error types use `thiserror` +- If persistence touched, both backends updated (postgres.rs AND libsql/) +- New tools implement `Tool` trait correctly and registered +- External tool output passes through safety layer +- Tool parameters redacted before logging/SSE +- No byte-index slicing on external strings +- Case-insensitive comparisons where needed + +### Correctness +Off-by-one, wrong operators, inverted conditions, unreachable code, type confusion, error propagation, broken invariants, TOCTOU races. + +### Edge cases & failure handling +Empty/None/zero-length input, external service failures, integer boundaries, malformed/adversarial input, partial failure handling. + +### Security (assume adversarial actors) +Auth/authz bypass, IDOR, injection (SQL/command/log/header), data leakage in logs/errors/API responses, resource exhaustion, replay/race conditions. + +### Test coverage +New public functions tested? Error paths tested? Edge cases covered? Existing tests still valid? + +### Architecture +Follows existing patterns? Unnecessary abstractions? Duplicated logic? Clean module dependencies? + +**Present findings as a table:** + +| # | Severity | Category | File:Line | Finding | Suggested Fix | +|---|----------|----------|-----------|---------|---------------| + +Severity: Critical > High > Medium > Low > Nit + +If `--review-only` flag is set, post findings as GitHub comments (see Phase 2c) and STOP. + +Otherwise, ask which findings to fix (default: all Critical + High + Medium). Then proceed to Phase 3. + +--- + +## Phase 2c: Post Review Comments on GitHub + +For each finding the user approved (or all Critical/High/Medium if `--fix`): + +**Line-specific findings** — post as PR review comments: +``` +gh api repos/{owner}/{repo}/pulls/{number}/comments \ + -f body="**{Severity}**: {finding}\n\n{explanation}\n\n**Suggested fix:** {suggestion}" \ + -f path="{file}" \ + -f commit_id="{headRefOid}" \ + -F line={line} \ + -f side="RIGHT" +``` + +**Cross-cutting/architectural findings** — post as regular PR comment: +``` +gh pr comment {number} --body "..." +``` + +--- + +## Phase 3: Fix + +Checkout the PR branch if not already on it (handles fork PRs automatically): +``` +gh pr checkout {number} +``` + +**Implement fixes** for: +1. All approved review comment fixes (from Phase 2a) +2. All approved review findings (from Phase 2b) + +Follow IronClaw conventions: +- `thiserror` for errors +- `crate::` imports +- No `.unwrap()` in production +- Both DB backends if persistence touched +- Regression test for every bug fix (enforced by commit-msg hook; bypass only with `[skip-regression-check]` if genuinely not feasible) + +After all fixes implemented, proceed to Phase 4. + +--- + +## Phase 4: Quality Gate + +Run the full IronClaw shipping checklist: + +```bash +cargo fmt +``` + +```bash +cargo clippy --all --benches --tests --examples --all-features +``` + +```bash +cargo test --lib +``` + +If persistence changes are present, also verify feature isolation: +```bash +cargo check --no-default-features --features libsql +cargo check --all-features +``` + +**If any step fails:** fix the issue and re-run. Do NOT proceed past a failing step. Loop up to 3 times per step. If still failing after 3 attempts, report the failure and stop. + +--- + +## Phase 5: Commit & Push + +Stage changed files by name (never `git add -A` — it can include unintended files): +```bash +git add path/to/changed/file1 path/to/changed/file2 +git commit -m "{message}" +``` + +Commit message format: +- For review fixes: `fix: address review findings on PR #{number}` +- For comment responses: `fix: address review comments on PR #{number}` +- For CI fixes: `fix: resolve CI failures on PR #{number}` +- Include specifics in the body (which findings/comments were addressed) + +Push: +```bash +git push origin {headRefName} +``` + +**Reply to addressed review comments on GitHub.** For each comment that was fixed, reply with the commit SHA and a brief description of what was done. For false positives, reply explaining why no change was needed. + +--- + +## Phase 6: CI Monitor & Fix Loop + +Wait briefly for CI to start, then poll (do NOT use `--watch` as it can hang indefinitely): +``` +gh pr checks {number} --json name,status,conclusion +``` + +Re-check every 30 seconds, up to 10 minutes. If still pending after 10 minutes, report status and ask the user whether to keep waiting. + +**If CI passes** → proceed to Phase 7. + +**If CI fails** (up to 3 fix attempts): + +1. Identify the failing check: + ``` + gh run view {run_id} --log-failed + ``` + If `--log-failed` shows nothing useful: + ``` + gh run view {run_id} --log | tail -100 + ``` + +2. Diagnose and fix the failure. +3. Re-run Phase 4 (quality gate). +4. Commit and push (Phase 5). +5. Go back to top of Phase 6. + +**After 3 failed CI fix attempts:** Report what's failing and why, then stop. Don't keep looping. + +--- + +## Phase 7: Merge Decision + +Print final status: +``` +PR #{number}: {title} +CI: ✅ PASS +Reviews: {summary} +Findings fixed: {N} +Comments addressed: {N} +Commits added: {N} +``` + +**Auto-merge conditions** (if `--merge` flag or user confirms): +- CI is passing +- No unresolved CHANGES_REQUESTED reviews +- PR is not draft +- PR is mergeable (no conflicts) + +If all conditions met, ask the user for merge strategy: + +"CI is green. Merge this PR? [squash/rebase/merge/no]" + +Then execute: +``` +gh pr merge {number} --{strategy} --delete-branch +``` + +If any condition NOT met, report what's blocking and let the user decide. + +--- + +## Rules + +- **Read before judging.** Never comment on code you haven't read in full. Verify line numbers. +- **Be specific.** "Line 42 returns 404 but should return 400 because X" not "this might have issues." +- **Fix the pattern, not just the instance.** When fixing a bug, grep for the same pattern across `src/`. +- **Respect the commit-msg hook.** Bug fixes need regression tests. Use `[skip-regression-check]` only if genuinely not feasible. +- **Don't over-fix.** Only change what was flagged. Don't refactor surrounding code or add improvements beyond the review scope. +- **Credit original authors.** If taking over someone else's PR, credit them in commits and comments. +- **No secrets in comments.** Never include customer data, credentials, or PII in GitHub comments. +- **Distinguish certainty.** "This IS a bug" vs "This COULD be a bug if X." Be honest. +- **Round up severity when uncertain.** Cheaper to dismiss a false alarm than miss a real bug. +- **Parallel where possible.** Use Agent tool for parallel file reads on large PRs. Batch `gh api` calls. diff --git a/.claude/rules/database.md b/.claude/rules/database.md new file mode 100644 index 00000000..07accf07 --- /dev/null +++ b/.claude/rules/database.md @@ -0,0 +1,63 @@ +--- +paths: + - "src/db/**" + - "src/history/**" + - "migrations/**" +--- +# Database Rules + +Dual-backend persistence: PostgreSQL + libSQL/Turso. **All new persistence features must support both backends.** + +See `src/db/CLAUDE.md` for full schema, dialect differences, and libSQL limitations. + +## Adding a New Operation + +1. Decide which sub-trait it belongs to (`ConversationStore`, `JobStore`, `SandboxStore`, `RoutineStore`, `ToolFailureStore`, `SettingsStore`, `WorkspaceStore`) or create a new one +2. Add the async method signature to that sub-trait in `src/db/mod.rs` +3. Implement in `src/db/postgres.rs` (delegate to `Store`/`Repository`) +4. Implement in `src/db/libsql/.rs` (use `self.connect().await?` per operation) +5. Add migration if needed: + - PostgreSQL: new `migrations/VN__description.sql` + - libSQL: add `CREATE TABLE IF NOT EXISTS` to `libsql_migrations.rs` +6. Test feature isolation: + ```bash + cargo check # postgres (default) + cargo check --no-default-features --features libsql # libsql only + cargo check --all-features # both + ``` + +## SQL Dialect Translation Checklist + +When writing SQL for both backends, translate these types: + +| PostgreSQL | libSQL | +|-----------|--------| +| `UUID` | `TEXT` | +| `TIMESTAMPTZ` | `TEXT` (ISO-8601, write with `fmt_ts()`, read with `get_ts()`) | +| `JSONB` | `TEXT` (JSON string) | +| `BOOLEAN` | `INTEGER` (0/1 -- use `get_i64(row, idx) != 0` to read) | +| `NUMERIC` | `TEXT` (preserves `rust_decimal` precision) | +| `TEXT[]` | `TEXT` (JSON-encoded array) | +| `VECTOR` | `BLOB` (flexible dimensions; vector index dropped, brute-force search fallback) | +| `jsonb_set(col, '{key}', val)` | `json_patch(col, '{"key": val}')` -- replaces top-level keys entirely, cannot do partial nested updates | +| `DEFAULT NOW()` | `DEFAULT (datetime('now'))` | +| `tsvector` + `ts_rank_cd` | FTS5 virtual table + sync triggers | + +## Schema Translation Beyond DDL + +Don't just translate `CREATE TABLE`. Also check: +- **Indexes** -- diff `CREATE INDEX` statements between backends +- **Seed data** -- check for `INSERT INTO` in migrations (e.g., `leak_detection_patterns`) +- **Triggers** -- PostgreSQL functions vs SQLite triggers (no stored procs in SQLite) + +## Transaction Safety + +Multi-step operations (INSERT+INSERT, UPDATE+DELETE, read-modify-write) MUST be wrapped in a transaction. Ask: "If this crashes between step N and N+1, is the database consistent?" If not, wrap in a transaction. Applies to both backends. + +## libSQL Connection Model + +`LibSqlBackend::connect()` creates a fresh connection per operation with `PRAGMA busy_timeout = 5000`. This is intentional -- no pool exists. Never hold connections open across `await` points. Satellite stores (`LibSqlSecretsStore`, `LibSqlWasmToolStore`) receive `Arc` via `shared_db()` and call `.connect()` themselves -- never pass a live `Connection`. + +## Fix the Pattern, Not the Instance + +When fixing a bug in one backend's SQL, always grep for the same pattern in the other. A fix to `postgres.rs` that doesn't also fix `libsql/jobs.rs` is half a fix. Same applies to satellite stores. diff --git a/.claude/rules/review-discipline.md b/.claude/rules/review-discipline.md new file mode 100644 index 00000000..74ace30a --- /dev/null +++ b/.claude/rules/review-discipline.md @@ -0,0 +1,48 @@ +--- +paths: + - "src/**/*.rs" +--- +# Review & Fix Discipline + +Hard-won lessons from code review -- follow these when fixing bugs or addressing review feedback. + +**Fix the pattern, not just the instance:** When a reviewer flags a bug (e.g., TOCTOU race in INSERT + SELECT-back), search the entire codebase for all instances of that same pattern. A fix in `SecretsStore::create()` that doesn't also fix `WasmToolStore::store()` is half a fix. + +**Propagate architectural fixes to satellite types:** If a core type changes its concurrency model (e.g., `LibSqlBackend` switches to connection-per-operation), every type that was handed a resource from the old model must also be updated. Grep for the old type across the codebase. + +**Schema translation is more than DDL:** When translating a database schema between backends (PostgreSQL to libSQL, etc.), check for: +- **Indexes** -- diff `CREATE INDEX` statements between the two schemas +- **Seed data** -- check for `INSERT INTO` in migrations (e.g., `leak_detection_patterns`) +- **Semantic differences** -- document where SQL functions behave differently (e.g., `json_patch` vs `jsonb_set`) + +**Feature flag testing:** When adding feature-gated code, test compilation with each feature in isolation: +```bash +cargo check # default features +cargo check --no-default-features --features libsql # libsql only +cargo check --all-features # all features +``` + +**Regression test with every fix:** Every bug fix must include a test that would have caught the bug. Add a `#[test]` or `#[tokio::test]` that reproduces the original failure. Exempt: changes limited to `src/channels/web/static/` or `.md` files. Use `[skip-regression-check]` in commit message or PR label if genuinely not feasible. The `commit-msg` hook and CI workflow enforce this automatically. + +**Zero clippy warnings policy:** Fix ALL clippy warnings before committing, including pre-existing ones in files you didn't change. Never leave warnings behind. + +**Transaction safety:** Multi-step database operations (INSERT+INSERT, UPDATE+DELETE, read-then-write) MUST be wrapped in a transaction. Never assume sequential calls are atomic. This applies to both postgres and libsql backends. + +**UTF-8 string safety:** Never use byte-index slicing (`&s[..n]`) on user-supplied or external strings -- it panics on multi-byte characters. Use `is_char_boundary()` or `char_indices()`. Grep for `[..` in changed files. + +**Case-insensitive comparisons:** When comparing user-supplied strings (file paths, media types, extension names), normalize to lowercase with `.to_ascii_lowercase()`. Path comparisons must be case-insensitive on macOS/Windows. + +**Decorator/wrapper trait delegation:** When adding a new method to `LlmProvider` (or any trait with decorator wrappers), update ALL wrapper types to delegate. Grep for `impl LlmProvider for` to find all implementations. Test through the full provider chain. + +**Sensitive data in logs & events:** Tool parameters and outputs MUST be redacted before logging or broadcasting via SSE/WebSocket. Use `redact_params()` before any `tracing::info!`, `JobEvent`, or SSE emission that includes tool call data. + +**Test temporary files:** Use the `tempfile` crate. Never hardcode `/tmp/...` paths. + +**Trust boundaries in multi-process architecture:** Data from worker containers is untrusted. The orchestrator MUST validate: tool domain, nesting depth (server-side tracking), and parameter sensitivity. + +**Mechanical verification before committing:** +- `cargo clippy --all --benches --tests --examples --all-features` -- zero warnings +- `grep -rnE '\.unwrap\(|\.expect\(' ` -- no panics in production +- `grep -rn 'super::' ` -- prefer `crate::` for cross-module imports (`super::` OK in tests/intra-module) +- If you fixed a pattern bug, `grep` for other instances across `src/` +- Run `scripts/pre-commit-safety.sh` to catch UTF-8, case-sensitivity, hardcoded /tmp, and logging issues diff --git a/.claude/rules/safety-and-sandbox.md b/.claude/rules/safety-and-sandbox.md new file mode 100644 index 00000000..50e1135e --- /dev/null +++ b/.claude/rules/safety-and-sandbox.md @@ -0,0 +1,34 @@ +--- +paths: + - "src/safety/**" + - "src/sandbox/**" + - "src/secrets/**" + - "src/tools/wasm/**" +--- +# Safety Layer & Sandbox Rules + +## Safety Layer + +All external tool output passes through `SafetyLayer`: +1. **Sanitizer** - Detects injection patterns, escapes dangerous content +2. **Validator** - Checks length, encoding, forbidden patterns +3. **Policy** - Rules with severity (Critical/High/Medium/Low) and actions (Block/Warn/Review/Sanitize) +4. **Leak Detector** - Scans for 15+ secret patterns at two points: tool output before LLM, and LLM responses before user + +Tool outputs are wrapped in `` XML before reaching the LLM. + +## Shell Environment Scrubbing + +The shell tool scrubs sensitive env vars before executing commands. The sanitizer detects command injection patterns (chained commands, subshells, path traversal). + +## Sandbox Policies + +| Policy | Filesystem | Network | +|--------|-----------|---------| +| ReadOnly | Read-only workspace | Allowlisted domains | +| WorkspaceWrite | Read-write workspace | Allowlisted domains | +| FullAccess | Full filesystem | Unrestricted | + +## Zero-Exposure Credential Model + +Secrets are stored encrypted on the host and injected into HTTP requests by the proxy at transit time. Container processes never see raw credential values. diff --git a/.claude/rules/skills.md b/.claude/rules/skills.md new file mode 100644 index 00000000..ded26de9 --- /dev/null +++ b/.claude/rules/skills.md @@ -0,0 +1,56 @@ +--- +paths: + - "src/skills/**" + - "skills/**" +--- +# Skills System + +SKILL.md files extend the agent's prompt with domain-specific instructions. Each skill is a YAML frontmatter block (metadata, activation criteria, required tools) followed by a markdown body injected into the LLM context. + +## Trust Model + +| Trust Level | Source | Tool Access | +|-------------|--------|-------------| +| **Trusted** | User-placed in `~/.ironclaw/skills/` or workspace `skills/` | All tools available to the agent | +| **Installed** | Downloaded from ClawHub registry (`~/.ironclaw/installed_skills/`) | Read-only tools only (no shell, file write, HTTP) | + +## SKILL.md Format + +```yaml +--- +name: my-skill +version: 0.1.0 +description: Does something useful +activation: + patterns: + - "deploy to.*production" + keywords: + - "deployment" + exclude_keywords: + - "rollback" + tags: + - "devops" + max_context_tokens: 2000 +metadata: + openclaw: + requires: + bins: [docker, kubectl] + env: [KUBECONFIG] +--- + +# Skill instructions here... +``` + +## Selection Pipeline + +1. **Gating** -- Check binary/env/config requirements; skip skills whose prerequisites are missing +2. **Scoring** -- Deterministic scoring: keywords (10/5 pts, cap 30) + patterns (20 pts, cap 40) + tags (3 pts, cap 15). `exclude_keywords` veto (score = 0 if any present) +3. **Budget** -- Select top-scoring skills within `SKILLS_MAX_TOKENS` prompt budget +4. **Attenuation** -- Minimum trust across active skills determines tool ceiling; installed skills lose dangerous tools + +## Skill Tools + +- `skill_list` -- List all discovered skills with trust level and status +- `skill_search` -- Search ClawHub registry for available skills +- `skill_install` -- Download and install a skill from ClawHub +- `skill_remove` -- Remove an installed skill diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 00000000..3d50b3ea --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,25 @@ +--- +paths: + - "src/**/*.rs" + - "tests/**" +--- +# Testing Rules + +## Test Tiers + +| Tier | Command | External deps | +|------|---------|---------------| +| Unit | `cargo test` | None | +| Integration | `cargo test --features integration` | Running PostgreSQL | +| Live | `cargo test --features integration -- --ignored` | PostgreSQL + LLM API keys | + +Run `bash scripts/check-boundaries.sh` to verify test tier gating. + +## Key Patterns + +- Unit tests in `mod tests {}` at the bottom of each file +- Async tests with `#[tokio::test]` +- No mocks, prefer real implementations or stubs +- Use `tempfile` crate for test directories, never hardcode `/tmp/` +- Regression test with every bug fix (enforced by commit-msg hook) +- Integration tests (`--test workspace_integration`) require PostgreSQL; skipped if DB is unreachable diff --git a/.claude/rules/tools.md b/.claude/rules/tools.md new file mode 100644 index 00000000..a35d9e23 --- /dev/null +++ b/.claude/rules/tools.md @@ -0,0 +1,39 @@ +--- +paths: + - "src/tools/**" + - "tools-src/**" +--- +# Tool Architecture + +**Keep tool-specific logic out of the main agent codebase.** The main agent provides generic infrastructure; tools are self-contained units that declare requirements through `.capabilities.json` sidecar files (in dev mode: `tools-src//-tool.capabilities.json`). + +Tools can be WASM (sandboxed, credential-injected, single binary) or MCP servers (ecosystem, any language, no sandbox). Both are first-class via `ironclaw tool install`. + +See `src/tools/README.md` for full architecture, adding new tools, auth JSON examples, and WASM vs MCP decision guide. + +## Tool Implementation Pattern + +```rust +#[async_trait] +impl Tool for MyTool { + fn name(&self) -> &str { "my_tool" } + fn description(&self) -> &str { "Does something useful" } + fn parameters_schema(&self) -> serde_json::Value { + serde_json::json!({ + "type": "object", + "properties": { + "param": { "type": "string", "description": "A parameter" } + }, + "required": ["param"] + }) + } + async fn execute(&self, params: serde_json::Value, ctx: &JobContext) + -> Result + { + let start = std::time::Instant::now(); + // ... do work ... + Ok(ToolOutput::text("result", start.elapsed())) + } + fn requires_sanitization(&self) -> bool { true } // External data +} +``` diff --git a/CLAUDE.md b/CLAUDE.md index e51177cb..dbdf6289 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,73 +1,37 @@ # IronClaw Development Guide -## Project Overview - -**IronClaw** is a secure personal AI assistant that protects your data and expands its capabilities on the fly. - -### Core Philosophy -- **User-first security** - Your data stays yours, encrypted and local -- **Self-expanding** - Build new tools dynamically without vendor dependency -- **Defense in depth** - Multiple security layers against prompt injection and data exfiltration -- **Always available** - Multi-channel access with proactive background execution - -### Features -- **Multi-channel input**: TUI (Ratatui), HTTP webhooks, WASM channels (Telegram, Slack), web gateway -- **Parallel job execution** with state machine and self-repair for stuck jobs -- **Sandbox execution**: Docker container isolation with network proxy and credential injection -- **Claude Code mode**: Delegate jobs to Claude CLI inside containers -- **Skills system**: SKILL.md prompt extensions with trust model, tool attenuation, and ClawHub registry -- **Routines**: Scheduled (cron) and reactive (event, webhook) task execution -- **Web gateway**: Browser UI with SSE/WebSocket real-time streaming -- **Extension management**: Install, auth, activate MCP/WASM extensions -- **Extensible tools**: Built-in tools, WASM sandbox, MCP client, dynamic builder -- **Persistent memory**: Workspace with hybrid search (FTS + vector via RRF) -- **Prompt injection defense**: Sanitizer, validator, policy rules, leak detection, shell env scrubbing -- **Multi-provider LLM**: NEAR AI, OpenAI, Anthropic, Ollama, OpenAI-compatible, Tinfoil private inference -- **Setup wizard**: 7-step interactive onboarding for first-run configuration -- **Heartbeat system**: Proactive periodic execution with checklist +**IronClaw** is a secure personal AI assistant — user-first security, self-expanding tools, defense in depth, multi-channel access with proactive background execution. ## Build & Test ```bash -# Format code -cargo fmt - -# Lint (fix ALL warnings before committing, including pre-existing ones) -cargo clippy --all --benches --tests --examples --all-features - -# Run all tests -cargo test - -# Run specific test -cargo test test_name - -# Run with logging -RUST_LOG=ironclaw=debug cargo run - -# Run integration tests (may require running services/DB) -cargo test --test workspace_integration -cargo test --test ws_gateway_integration -cargo test --test heartbeat_integration - -# Run E2E tests (Python/Playwright — requires a running ironclaw instance) -# See tests/e2e/CLAUDE.md for full setup instructions -cd tests/e2e -python -m venv .venv && source .venv/bin/activate # On Windows: .venv\Scripts\activate -pip install -e . -playwright install chromium -pytest scenarios/ # all scenarios -pytest scenarios/test_chat.py # specific scenario +cargo fmt # format +cargo clippy --all --benches --tests --examples --all-features # lint (zero warnings) +cargo test # unit tests +cargo test --features integration # + PostgreSQL tests +RUST_LOG=ironclaw=debug cargo run # run with logging ``` -### Test Tiers +E2E tests: see `tests/e2e/CLAUDE.md`. -| Tier | Command | What runs | External deps | -|------|---------|-----------|---------------| -| Unit | `cargo test` | All `mod tests` + self-contained integration tests | None | -| Integration | `cargo test --features integration` | + PostgreSQL-dependent tests | Running PostgreSQL | -| Live | `cargo test --features integration -- --ignored` | + LLM-dependent tests | PostgreSQL + LLM API keys | +## Code Style -Run `bash scripts/check-boundaries.sh` to verify test tier gating and other architecture rules. +- Prefer `crate::` for cross-module imports; `super::` is fine in tests and intra-module refs +- No `pub use` re-exports unless exposing to downstream consumers +- No `.unwrap()` or `.expect()` in production code (tests are fine) +- Use `thiserror` for error types in `error.rs` +- Map errors with context: `.map_err(|e| SomeError::Variant { reason: e.to_string() })?` +- Prefer strong types over strings (enums, newtypes) +- Keep functions focused, extract helpers when logic is reused +- Comments for non-obvious logic only + +## Architecture + +Prefer generic/extensible architectures over hardcoding specific integrations. Ask clarifying questions about the desired abstraction level before implementing. + +Key traits for extensibility: `Database`, `Channel`, `Tool`, `LlmProvider`, `SuccessEvaluator`, `EmbeddingProvider`, `NetworkPolicyDecider`, `Hook`, `Observer`, `Tunnel`. + +All I/O is async with tokio. Use `Arc` for shared state, `RwLock` for concurrent access. ## Project Structure @@ -95,78 +59,35 @@ src/ │ ├── channel.rs # Channel trait, IncomingMessage, OutgoingResponse │ ├── manager.rs # ChannelManager merges streams │ ├── cli/ # Full TUI with Ratatui -│ │ ├── mod.rs # TuiChannel implementation -│ │ ├── app.rs # Application state -│ │ ├── render.rs # UI rendering -│ │ ├── events.rs # Input handling -│ │ ├── overlay.rs # Approval overlays -│ │ └── composer.rs # Message composition │ ├── http.rs # HTTP webhook (axum) with secret validation │ ├── webhook_server.rs # Unified HTTP server composing all webhook routes │ ├── repl.rs # Simple REPL (for testing) │ ├── web/ # Web gateway (browser UI) — see src/channels/web/CLAUDE.md │ └── wasm/ # WASM channel runtime -│ ├── mod.rs -│ ├── bundled.rs # Bundled channel discovery -│ ├── capabilities.rs # Channel-specific capabilities (HTTP endpoint, emit rate) -│ ├── error.rs # WASM channel error types -│ ├── runtime.rs # WASM channel execution runtime -│ └── wrapper.rs # Channel trait wrapper for WASM modules │ ├── cli/ # CLI subcommands (clap) │ ├── mod.rs # Cli struct, Command enum (run/onboard/config/tool/registry/mcp/memory/pairing/service/doctor/status/completion) -│ ├── config.rs # config list/get/set subcommands -│ ├── tool.rs # tool install/list/remove subcommands -│ ├── registry.rs # registry list/install subcommands -│ ├── mcp.rs # mcp add/auth/list/test subcommands -│ ├── memory.rs # memory search/read/write subcommands -│ ├── pairing.rs # pairing list/approve subcommands -│ ├── service.rs # service install/start/stop subcommands -│ ├── doctor.rs # Active health diagnostics -│ ├── status.rs # System health/status display -│ ├── completion.rs # Shell completion script generation -│ └── oauth_defaults.rs # Default OAuth redirect URIs +│ └── config.rs, tool.rs, registry.rs, mcp.rs, memory.rs, pairing.rs, service.rs, doctor.rs, status.rs, completion.rs │ ├── registry/ # Extension registry catalog -│ ├── mod.rs # Public API; re-exports RegistryCatalog, RegistryInstaller, manifest types │ ├── manifest.rs # ExtensionManifest, ArtifactSpec, BundleDefinition types │ ├── catalog.rs # RegistryCatalog: load from filesystem and embedded JSON -│ ├── installer.rs # RegistryInstaller: download, verify, install WASM artifacts -│ ├── artifacts.rs # Artifact download and caching -│ └── embedded.rs # Catalog compiled into binary at build time (via build.rs) +│ └── installer.rs # RegistryInstaller: download, verify, install WASM artifacts │ -├── hooks/ # Lifecycle hooks for intercepting agent operations -│ ├── mod.rs # 6 HookPoints: BeforeInbound, BeforeToolCall, BeforeOutbound, OnSessionStart, OnSessionEnd, TransformResponse -│ ├── hook.rs # Hook trait, HookContext, HookEvent, HookOutcome, HookFailureMode -│ ├── registry.rs # HookRegistry: register, prioritize, execute hooks -│ └── bundled.rs # Built-in hooks: rule-based filters, webhook forwarders, HookBundleConfig +├── hooks/ # Lifecycle hooks (6 points: BeforeInbound, BeforeToolCall, BeforeOutbound, OnSessionStart, OnSessionEnd, TransformResponse) │ -├── tunnel/ # Tunnel abstraction for public internet exposure -│ ├── mod.rs # Tunnel trait, TunnelProviderConfig, create_tunnel() factory -│ ├── cloudflare.rs # CloudflareTunnel (cloudflared binary) -│ ├── ngrok.rs # NgrokTunnel -│ ├── tailscale.rs # TailscaleTunnel (serve/funnel modes) -│ ├── custom.rs # CustomTunnel (arbitrary command with {host}/{port}) -│ └── none.rs # NoneTunnel (local-only, no exposure) +├── tunnel/ # Tunnel abstraction (cloudflare, ngrok, tailscale, custom, none) │ -├── observability/ # Pluggable event/metric recording -│ ├── mod.rs # create_observer() factory, ObservabilityConfig -│ ├── traits.rs # Observer trait, ObserverEvent, ObserverMetric -│ ├── noop.rs # NoopObserver (zero overhead, default) -│ ├── log.rs # LogObserver (tracing-based) -│ └── multi.rs # MultiObserver (fan-out to multiple backends) +├── observability/ # Pluggable event/metric recording (noop, log, multi) │ ├── orchestrator/ # Internal HTTP API for sandbox containers -│ ├── mod.rs │ ├── api.rs # Axum endpoints (LLM proxy, events, prompts) │ ├── auth.rs # Per-job bearer token store │ └── job_manager.rs # Container lifecycle (create, stop, cleanup) │ ├── worker/ # Runs inside Docker containers -│ ├── mod.rs │ ├── runtime.rs # Worker execution loop (tool calls, LLM) │ ├── claude_bridge.rs # Claude Code bridge (spawns claude CLI) -│ ├── api.rs # HTTP client to orchestrator │ └── proxy_llm.rs # LlmProvider that proxies through orchestrator │ ├── safety/ # Prompt injection defense @@ -174,179 +95,68 @@ src/ │ ├── validator.rs # Input validation (length, encoding, patterns) │ ├── policy.rs # PolicyRule system with severity/actions │ ├── leak_detector.rs # Secret detection (API keys, tokens, etc.) -│ └── credential_detect.rs # HTTP request credential detection (headers, URL params) +│ └── credential_detect.rs # HTTP request credential detection │ ├── llm/ # Multi-provider LLM integration — see src/llm/CLAUDE.md │ ├── tools/ # Extensible tool system │ ├── tool.rs # Tool trait, ToolOutput, ToolError │ ├── registry.rs # ToolRegistry for discovery -│ ├── sandbox.rs # Process-based sandbox (stub, superseded by wasm/) -│ ├── rate_limiter.rs # Shared sliding-window rate limiter for built-in and WASM tools -│ ├── builtin/ # Built-in tools -│ │ ├── echo.rs, time.rs, json.rs, http.rs -│ │ ├── web_fetch.rs # GET URL → clean Markdown (readability + html-to-md conversion) -│ │ ├── file.rs # ReadFile, WriteFile, ListDir, ApplyPatch -│ │ ├── shell.rs # Shell command execution -│ │ ├── memory.rs # Memory tools (search, write, read, tree) -│ │ ├── message.rs # MessageTool: agent proactively messages users on any channel -│ │ ├── job.rs # CreateJob, ListJobs, JobStatus, CancelJob -│ │ ├── routine.rs # routine_create/list/update/delete/history -│ │ ├── extension_tools.rs # Extension install/auth/activate/remove -│ │ ├── skill_tools.rs # skill_list/search/install/remove tools -│ │ ├── secrets_tools.rs # secret_list/secret_delete (zero-exposure: no values exposed) -│ │ ├── html_converter.rs # HTML→Markdown via readability + html-to-markdown-rs -│ │ ├── path_utils.rs # Shared path validation/canonicalization helpers -│ │ └── marketplace.rs, ecommerce.rs, taskrabbit.rs, restaurant.rs (stubs) +│ ├── rate_limiter.rs # Shared sliding-window rate limiter +│ ├── builtin/ # Built-in tools (echo, time, json, http, web_fetch, file, shell, memory, message, job, routine, extension_tools, skill_tools, secrets_tools) │ ├── builder/ # Dynamic tool building -│ │ ├── core.rs # BuildRequirement, SoftwareType, Language -│ │ ├── templates.rs # Project scaffolding -│ │ ├── testing.rs # Test harness integration -│ │ └── validation.rs # WASM validation -│ ├── mcp/ # Model Context Protocol -│ │ ├── client.rs # MCP client over HTTP -│ │ ├── protocol.rs # JSON-RPC types -│ │ └── session.rs # MCP session management (Mcp-Session-Id header, per-server state) -│ └── wasm/ # Full WASM sandbox (wasmtime) -│ ├── runtime.rs # Module compilation and caching -│ ├── wrapper.rs # Tool trait wrapper for WASM modules -│ ├── host.rs # Host functions (logging, time, workspace) -│ ├── limits.rs # Fuel metering and memory limiting -│ ├── allowlist.rs # Network endpoint allowlisting -│ ├── credential_injector.rs # Safe credential injection -│ ├── loader.rs # WASM tool discovery from filesystem -│ ├── rate_limiter.rs # Per-tool rate limiting -│ ├── error.rs # WASM-specific error types -│ └── storage.rs # Linear memory persistence +│ ├── mcp/ # Model Context Protocol client +│ └── wasm/ # Full WASM sandbox (wasmtime) — runtime, host functions, fuel metering, allowlist, credential injection │ ├── db/ # Dual-backend persistence (PostgreSQL + libSQL) — see src/db/CLAUDE.md │ -├── workspace/ # Persistent memory system (OpenClaw-inspired) -│ ├── mod.rs # Workspace struct, memory operations -│ ├── document.rs # MemoryDocument, MemoryChunk, WorkspaceEntry -│ ├── chunker.rs # Document chunking (800 tokens, 15% overlap) -│ ├── embeddings.rs # EmbeddingProvider trait, OpenAI implementation -│ ├── search.rs # Hybrid search with RRF algorithm -│ └── repository.rs # PostgreSQL CRUD and search operations +├── workspace/ # Persistent memory system — see src/workspace/README.md │ -├── context/ # Job context isolation -│ ├── state.rs # JobState enum, JobContext, state machine -│ ├── memory.rs # ActionRecord, ConversationMemory -│ └── manager.rs # ContextManager for concurrent jobs -│ -├── estimation/ # Cost/time/value estimation -│ ├── cost.rs # CostEstimator -│ ├── time.rs # TimeEstimator -│ ├── value.rs # ValueEstimator (profit margins) -│ └── learner.rs # Exponential moving average learning -│ -├── evaluation/ # Success evaluation -│ ├── success.rs # SuccessEvaluator trait, RuleBasedEvaluator, LlmEvaluator -│ └── metrics.rs # MetricsCollector, QualityMetrics +├── context/ # Job context isolation (JobState, JobContext, ContextManager) +├── estimation/ # Cost/time/value estimation with EMA learning +├── evaluation/ # Success evaluation (rule-based, LLM-based) │ ├── sandbox/ # Docker execution sandbox -│ ├── mod.rs # Public API, default allowlist -│ ├── config.rs # SandboxConfig, SandboxPolicy enum +│ ├── config.rs # SandboxConfig, SandboxPolicy enum (ReadOnly/WorkspaceWrite/FullAccess) │ ├── manager.rs # SandboxManager orchestration │ ├── container.rs # ContainerRunner, Docker lifecycle -│ ├── error.rs # SandboxError types -│ └── proxy/ # Network proxy for containers -│ ├── mod.rs # NetworkProxyBuilder -│ ├── http.rs # HttpProxy, CredentialResolver trait -│ ├── policy.rs # NetworkPolicyDecider trait -│ └── allowlist.rs # DomainAllowlist validation +│ └── proxy/ # Network proxy: domain allowlist, credential injection, CONNECT tunnel │ -├── secrets/ # Secrets management -│ ├── mod.rs # SecretsStore trait, public API -│ ├── types.rs # Core types (Secret, SecretRef, SecretMetadata) -│ ├── crypto.rs # AES-256-GCM encryption -│ ├── keychain.rs # OS keychain integration (macOS Keychain, GNOME Keyring) for master key -│ └── store.rs # Encrypted secret storage +├── secrets/ # Secrets management (AES-256-GCM, OS keychain for master key) │ -├── setup/ # Onboarding wizard (spec: src/setup/README.md) -│ ├── mod.rs # Entry point, check_onboard_needed() -│ ├── wizard.rs # 7-step interactive wizard -│ ├── channels.rs # Channel setup helpers -│ └── prompts.rs # Terminal prompts (select, confirm, secret) +├── setup/ # 7-step onboarding wizard — see src/setup/README.md │ -├── skills/ # SKILL.md prompt extension system -│ ├── mod.rs # Core types (SkillTrust, LoadedSkill) -│ ├── registry.rs # SkillRegistry: discover, install, remove -│ ├── selector.rs # Deterministic scoring prefilter -│ ├── attenuation.rs # Trust-based tool ceiling -│ ├── gating.rs # Requirement checks (bins, env, config) -│ ├── parser.rs # SKILL.md frontmatter + markdown parser -│ └── catalog.rs # ClawHub registry client +├── skills/ # SKILL.md prompt extension system — see .claude/rules/skills.md │ -└── history/ # Persistence - ├── store.rs # PostgreSQL repositories - └── analytics.rs # Aggregation queries (JobStats, ToolStats) +└── history/ # Persistence (PostgreSQL repositories, analytics) tests/ ├── *.rs # Integration tests (workspace, heartbeat, WS gateway, pairing, etc.) -├── test-pages/ # HTML→Markdown conversion fixtures (CNN, Medium, Yahoo) +├── test-pages/ # HTML→Markdown conversion fixtures └── e2e/ # Python/Playwright E2E scenarios (see tests/e2e/CLAUDE.md) ``` -## Key Patterns +## Database -### Architecture +Dual-backend: PostgreSQL + libSQL/Turso. **All new persistence features must support both backends.** See `src/db/CLAUDE.md` and `.claude/rules/database.md`. -When designing new features or systems, always prefer generic/extensible architectures over hardcoding specific integrations. Ask clarifying questions about the desired abstraction level before implementing. +## Module Specs -### Error Handling -- Use `thiserror` for error types in `error.rs` -- Never use `.unwrap()` or `.expect()` in production code (tests are fine) -- Map errors with context: `.map_err(|e| SomeError::Variant { reason: e.to_string() })?` -- Before committing, grep for `.unwrap()` and `.expect(` in changed files to catch violations mechanically +When modifying a module with a spec, read the spec first. Code follows spec; spec is the tiebreaker. -### Async -- All I/O is async with tokio -- Use `Arc` for shared state across tasks -- Use `RwLock` for concurrent read/write access +| Module | Spec | +|--------|------| +| `src/agent/` | `src/agent/CLAUDE.md` | +| `src/channels/web/` | `src/channels/web/CLAUDE.md` | +| `src/db/` | `src/db/CLAUDE.md` | +| `src/llm/` | `src/llm/CLAUDE.md` | +| `src/setup/` | `src/setup/README.md` | +| `src/tools/` | `src/tools/README.md` | +| `src/workspace/` | `src/workspace/README.md` | +| `tests/e2e/` | `tests/e2e/CLAUDE.md` | -### Traits for Extensibility -- `Database` - Add new database backends (must implement all ~78 methods) -- `Channel` - Add new input sources -- `Tool` - Add new capabilities -- `LlmProvider` - Add new LLM backends -- `SuccessEvaluator` - Custom evaluation logic -- `EmbeddingProvider` - Add embedding backends (workspace search) -- `NetworkPolicyDecider` - Custom network access policies for sandbox containers -- `Hook` - Lifecycle hook at 6 interception points (BeforeInbound, BeforeToolCall, BeforeOutbound, OnSessionStart, OnSessionEnd, TransformResponse) -- `Observer` - Observability backend (noop/log/multi; future: OpenTelemetry, Prometheus) -- `Tunnel` - Tunnel provider for public internet exposure +## Job State Machine -### Tool Implementation -```rust -#[async_trait] -impl Tool for MyTool { - fn name(&self) -> &str { "my_tool" } - fn description(&self) -> &str { "Does something useful" } - fn parameters_schema(&self) -> serde_json::Value { - serde_json::json!({ - "type": "object", - "properties": { - "param": { "type": "string", "description": "A parameter" } - }, - "required": ["param"] - }) - } - - async fn execute(&self, params: serde_json::Value, ctx: &JobContext) - -> Result - { - let start = std::time::Instant::now(); - // ... do work ... - Ok(ToolOutput::text("result", start.elapsed())) - } - - fn requires_sanitization(&self) -> bool { true } // External data -} -``` - -### State Transitions -Job states follow a defined state machine in `context/state.rs`: ``` Pending -> InProgress -> Completed -> Submitted -> Accepted \-> Failed @@ -354,315 +164,17 @@ Pending -> InProgress -> Completed -> Submitted -> Accepted \-> Failed ``` -### Code Style +## Skills System -- Use `crate::` imports, not `super::` -- No `pub use` re-exports unless exposing to downstream consumers -- Prefer strong types over strings (enums, newtypes) -- Keep functions focused, extract helpers when logic is reused -- Comments for non-obvious logic only +SKILL.md files extend the agent's prompt with domain-specific instructions. See `.claude/rules/skills.md` for full details. -### Review & Fix Discipline - -Hard-won lessons from code review -- follow these when fixing bugs or addressing review feedback. - -**Fix the pattern, not just the instance:** When a reviewer flags a bug (e.g., TOCTOU race in INSERT + SELECT-back), search the entire codebase for all instances of that same pattern. A fix in `SecretsStore::create()` that doesn't also fix `WasmToolStore::store()` is half a fix. - -**Propagate architectural fixes to satellite types:** If a core type changes its concurrency model (e.g., `LibSqlBackend` switches to connection-per-operation), every type that was handed a resource from the old model (e.g., `LibSqlSecretsStore`, `LibSqlWasmToolStore` holding a single `Connection`) must also be updated. Grep for the old type across the codebase. - -**Schema translation is more than DDL:** When translating a database schema between backends (PostgreSQL to libSQL, etc.), check for: -- **Indexes** -- diff `CREATE INDEX` statements between the two schemas -- **Seed data** -- check for `INSERT INTO` in migrations (e.g., `leak_detection_patterns`) -- **Semantic differences** -- document where SQL functions behave differently (e.g., `json_patch` vs `jsonb_set`) - -**Feature flag testing:** When adding feature-gated code, test compilation with each feature in isolation: -```bash -cargo check # default features -cargo check --no-default-features --features libsql # libsql only -cargo check --all-features # all features -``` -Dead code behind the wrong `#[cfg]` gate will only show up when building with a single feature. - -**Regression test with every fix:** Every bug fix must include a test that would have caught the bug. Add a `#[test]` or `#[tokio::test]` that reproduces the original failure. Exempt: changes limited to `src/channels/web/static/` or `.md` files. Use `[skip-regression-check]` in commit message or PR label if genuinely not feasible. The `commit-msg` hook and CI workflow enforce this automatically. - -**Zero clippy warnings policy:** Fix ALL clippy warnings before committing, including pre-existing ones in files you didn't change. Never leave warnings behind — treat `cargo clippy` output as a zero-tolerance gate. - -**Transaction safety:** Multi-step database operations (INSERT+INSERT, UPDATE+DELETE, read-then-write) MUST be wrapped in a transaction. Never assume sequential calls are atomic. Before committing DB code, ask: "If this crashes between step N and N+1, is the database consistent?" If not, wrap in a transaction. This applies to both postgres and libsql backends. - -**UTF-8 string safety:** Never use byte-index slicing (`&s[..n]`) on user-supplied or external strings — it panics on multi-byte characters. Use `is_char_boundary()` to walk backwards from the desired length, or iterate with `char_indices()`. Grep for `[..` in changed files to catch violations. - -**Case-insensitive comparisons:** When comparing user-supplied strings (file paths, media types, extension names), always normalize to lowercase first with `.to_ascii_lowercase()`. On case-insensitive filesystems (macOS, Windows), path comparisons must be case-insensitive. File extension checks (`.png`, `.jpg`) and media type checks (`image/jpeg`) are common offenders. - -**Decorator/wrapper trait delegation:** When adding a new method to `LlmProvider` (or any trait with decorator wrappers), you MUST update ALL wrapper types to delegate to their inner provider. Grep for `impl LlmProvider for` to find all implementations. Add a test that exercises the method through the full provider chain (`build_provider_chain()`), not just the base impl. - -**Sensitive data in logs & events:** Tool parameters and outputs MUST be redacted before logging or broadcasting via SSE/WebSocket. Use `redact_params()` before any `tracing::info!`, `JobEvent`, or SSE emission that includes tool call data. Never log raw parameters from tool calls. - -**Test temporary files:** Use the `tempfile` crate for test directories/files. Never hardcode `/tmp/...` paths — they collide in parallel test runs and break on non-Unix platforms. - -**Trust boundaries in multi-process architecture:** Data from worker containers is untrusted. The orchestrator MUST validate: tool domain (never execute `Container`-domain tools on the host), nesting depth (server-side tracking, not client-supplied), and parameter sensitivity (redact before logging/broadcasting). - -**Mechanical verification before committing:** Run these checks on changed files before committing: -- `cargo clippy --all --benches --tests --examples --all-features` -- zero warnings -- `grep -rnE '\.unwrap\(|\.expect\(' ` -- no panics in production -- `grep -rn 'super::' ` -- use `crate::` imports -- If you fixed a pattern bug, `grep` for other instances of that pattern across `src/` -- Fix commits must include regression tests (enforced by `commit-msg` hook; bypass with `[skip-regression-check]`) -- Run `scripts/pre-commit-safety.sh` to catch UTF-8, case-sensitivity, hardcoded /tmp, and logging issues +- **Trust model**: Trusted (user-placed in `~/.ironclaw/skills/` or workspace `skills/`, full tool access) vs Installed (registry, read-only tools) +- **Selection pipeline**: gating (check bin/env/config requirements) -> scoring (keywords/patterns/tags) -> budget (fit within `SKILLS_MAX_TOKENS`) -> attenuation (trust-based tool ceiling) +- **Skill tools**: `skill_list`, `skill_search`, `skill_install`, `skill_remove` ## Configuration -Environment variables (see `.env.example`): -```bash -# Database backend (default: postgres) -DATABASE_BACKEND=postgres # or "libsql" / "turso" -DATABASE_URL=postgres://user:pass@localhost/ironclaw -LIBSQL_PATH=~/.ironclaw/ironclaw.db # libSQL local path (default) -# LIBSQL_URL=libsql://xxx.turso.io # Turso cloud (optional) -# LIBSQL_AUTH_TOKEN=xxx # Required with LIBSQL_URL - -# NEAR AI (when LLM_BACKEND=nearai, the default) -# Two auth modes: session token (default) or API key -# Session token auth (default): uses browser OAuth on first run -NEARAI_SESSION_TOKEN=sess_... # hosting providers: set this -NEARAI_BASE_URL=https://private.near.ai -# API key auth: set NEARAI_API_KEY, base URL defaults to cloud-api.near.ai -# NEARAI_API_KEY=... # API key from cloud.near.ai -NEARAI_MODEL=claude-3-5-sonnet-20241022 - -# Agent settings -AGENT_NAME=ironclaw -MAX_PARALLEL_JOBS=5 - -# Embeddings (for semantic memory search) -OPENAI_API_KEY=sk-... # For OpenAI embeddings -# Or use NEAR AI embeddings: -# EMBEDDING_PROVIDER=nearai -# EMBEDDING_ENABLED=true -EMBEDDING_MODEL=text-embedding-3-small # or text-embedding-3-large - -# Heartbeat (proactive periodic execution) -HEARTBEAT_ENABLED=true -HEARTBEAT_INTERVAL_SECS=1800 # 30 minutes -HEARTBEAT_NOTIFY_CHANNEL=tui -HEARTBEAT_NOTIFY_USER=default - -# Web gateway -GATEWAY_ENABLED=true -GATEWAY_HOST=127.0.0.1 -GATEWAY_PORT=3001 -GATEWAY_AUTH_TOKEN=changeme # Required for API access -GATEWAY_USER_ID=default - -# Docker sandbox -SANDBOX_ENABLED=true -SANDBOX_IMAGE=ironclaw-worker:latest -SANDBOX_MEMORY_LIMIT_MB=512 -SANDBOX_TIMEOUT_SECS=1800 -SANDBOX_CPU_LIMIT=1.0 # CPU cores per container -SANDBOX_NETWORK_PROXY=true # Enable network proxy for containers -SANDBOX_PROXY_PORT=8080 # Proxy listener port -SANDBOX_DEFAULT_POLICY=workspace_write # ReadOnly, WorkspaceWrite, FullAccess - -# Claude Code mode (runs inside sandbox containers) -CLAUDE_CODE_ENABLED=false -CLAUDE_CODE_MODEL=claude-sonnet-4-20250514 -CLAUDE_CODE_MAX_TURNS=50 -CLAUDE_CODE_CONFIG_DIR=/home/worker/.claude - -# Routines (scheduled/reactive execution) -ROUTINES_ENABLED=true -ROUTINES_CRON_INTERVAL=60 # Tick interval in seconds -ROUTINES_MAX_CONCURRENT=3 - -# Skills system -SKILLS_ENABLED=true -SKILLS_MAX_TOKENS=4000 # Max prompt budget per turn -SKILLS_CATALOG_URL=https://clawhub.dev # ClawHub registry URL -SKILLS_AUTO_DISCOVER=true # Scan skill directories on startup - -# Tinfoil private inference -TINFOIL_API_KEY=... # Required when LLM_BACKEND=tinfoil -TINFOIL_MODEL=kimi-k2-5 # Default model - -# AWS Bedrock (native Converse API, requires --features bedrock) -# LLM_BACKEND=bedrock -# BEDROCK_REGION=us-east-1 # AWS region -# BEDROCK_MODEL=anthropic.claude-opus-4-6-v1 # Required model ID -# BEDROCK_CROSS_REGION=us # Cross-region prefix (us/eu/apac/global) -# AWS_PROFILE=my-profile # Named profile (SSO/assume-role) - -# Tunnel (public internet exposure for webhooks) -TUNNEL_URL=https://abc123.ngrok.io # Static public URL (manual tunnel) -# Or use a managed tunnel provider: -TUNNEL_PROVIDER=none # none (default), cloudflare, tailscale, ngrok, custom -TUNNEL_CF_TOKEN=... # Required for TUNNEL_PROVIDER=cloudflare -TUNNEL_NGROK_TOKEN=... # Required for TUNNEL_PROVIDER=ngrok -# TUNNEL_NGROK_DOMAIN=... # Custom domain (paid ngrok plan) -# TUNNEL_TS_FUNNEL=true # Use tailscale funnel (public) vs serve (tailnet) -TUNNEL_CUSTOM_COMMAND=... # Command with {host}/{port} for custom providers - -# Observability backend -OBSERVABILITY_BACKEND=none # none/noop (default) or log -``` - -### LLM Providers - -Backends: `nearai` (default), `openai`, `anthropic`, `ollama`, `openai_compatible`, `tinfoil`, `bedrock` (requires `--features bedrock`) — set via `LLM_BACKEND`. See [src/llm/CLAUDE.md](src/llm/CLAUDE.md) for per-provider auth and configuration details. - -**AWS Bedrock** -- Uses the native Converse API via `aws-sdk-bedrockruntime`. Requires `--features bedrock` at build time (not included in default features due to heavy AWS SDK dependencies). Supports standard AWS auth methods: IAM credentials (`AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`), SSO profiles (`AWS_PROFILE`), and instance roles. Configure with `BEDROCK_REGION` (default: `us-east-1`), `BEDROCK_MODEL` (required, e.g., `anthropic.claude-opus-4-6-v1`), and `BEDROCK_CROSS_REGION` (optional: `us`, `eu`, `apac`, `global` for cross-region inference profiles). The SDK credential chain resolves auth automatically from the environment. - -## Database - -Dual-backend persistence (PostgreSQL + libSQL/Turso). **All new persistence features must support both backends** — see [src/db/CLAUDE.md](src/db/CLAUDE.md) for schema, SQL dialect differences, adding operations, and libSQL limitations. - -Implement every new operation in both `src/db/postgres.rs` and `src/db/libsql/mod.rs`. Test in isolation: -```bash -cargo check # postgres (default) -cargo check --no-default-features --features libsql # libsql only -cargo check --all-features # both -``` - -Database configuration: see Configuration section above. - -## Safety Layer - -All external tool output passes through `SafetyLayer`: -1. **Sanitizer** - Detects injection patterns, escapes dangerous content -2. **Validator** - Checks length, encoding, forbidden patterns -3. **Policy** - Rules with severity (Critical/High/Medium/Low) and actions (Block/Warn/Review/Sanitize) -4. **Leak Detector** - Scans for 15+ secret patterns (API keys, tokens, private keys, connection strings) at two points: tool output before it reaches the LLM, and LLM responses before they reach the user. Actions per pattern: Block (reject entirely), Redact (mask the secret), or Warn (flag but allow) - -Tool outputs are wrapped before reaching LLM: -```xml - -[escaped content] - -``` - -### Shell Environment Scrubbing - -The shell tool (`src/tools/builtin/shell.rs`) scrubs sensitive environment variables before executing commands, preventing secrets from leaking through `env`, `printenv`, or `$VAR` expansion. The sanitizer (`src/safety/sanitizer.rs`) also detects command injection patterns (chained commands, subshells, path traversal) and blocks or escapes them based on policy rules. - -## Skills System - -Skills are SKILL.md files that extend the agent's prompt with domain-specific instructions. Each skill is a YAML frontmatter block (metadata, activation criteria, required tools) followed by a markdown body that gets injected into the LLM context when the skill activates. - -### Trust Model - -| Trust Level | Source | Tool Access | -|-------------|--------|-------------| -| **Trusted** | User-placed in `~/.ironclaw/skills/` or workspace `skills/` | All tools available to the agent | -| **Installed** | Downloaded from ClawHub registry | Read-only tools only (no shell, file write, HTTP) | - -### SKILL.md Format - -```yaml ---- -name: my-skill -version: 0.1.0 -description: Does something useful -activation: - patterns: - - "deploy to.*production" - keywords: - - "deployment" - max_context_tokens: 2000 -metadata: - openclaw: - requires: - bins: [docker, kubectl] - env: [KUBECONFIG] ---- - -# Deployment Skill - -Instructions for the agent when this skill activates... -``` - -### Selection Pipeline - -1. **Gating** -- Check binary/env/config requirements; skip skills whose prerequisites are missing -2. **Scoring** -- Deterministic scoring against message content using keywords, tags, and regex patterns -3. **Budget** -- Select top-scoring skills that fit within `SKILLS_MAX_TOKENS` prompt budget -4. **Attenuation** -- Apply trust-based tool ceiling; installed skills lose access to dangerous tools - -### Skill Tools - -Four built-in tools for managing skills at runtime: -- **`skill_list`** -- List all discovered skills with trust level and status -- **`skill_search`** -- Search ClawHub registry for available skills -- **`skill_install`** -- Download and install a skill from ClawHub -- **`skill_remove`** -- Remove an installed skill - -### Skill Directories - -- `~/.ironclaw/skills/` -- User's global skills (trusted) -- `/skills/` -- Per-workspace skills (trusted) -- `~/.ironclaw/installed_skills/` -- Registry-installed skills (installed trust) - -### Testing Skills - -- `skills/web-ui-test/` -- Manual test checklist for the web gateway UI via Claude for Chrome extension. Covers connection, chat, skills search/install/remove, and other tabs. - -Skills configuration: see Configuration section above. - -## Docker Sandbox - -The `src/sandbox/` module provides Docker-based isolation for job execution with a network proxy that controls outbound access and injects credentials. - -### Sandbox Policies - -| Policy | Filesystem | Network | Use Case | -|--------|-----------|---------|----------| -| **ReadOnly** | Read-only workspace mount | Allowlisted domains only | Analysis, code review | -| **WorkspaceWrite** | Read-write workspace mount | Allowlisted domains only | Code generation, file edits | -| **FullAccess** | Full filesystem | Unrestricted | Trusted admin tasks | - -### Network Proxy - -Containers route all HTTP/HTTPS traffic through a host-side proxy (`src/sandbox/proxy/`): -- **Domain allowlist** -- Only allowlisted domains are reachable (default: package registries, docs sites, GitHub, common APIs) -- **Credential injection** -- The `CredentialResolver` trait injects auth headers into proxied requests so secrets never enter the container environment -- **CONNECT tunnel** -- HTTPS traffic uses CONNECT method; the proxy validates the target domain against the allowlist before establishing the tunnel -- **Policy decisions** -- The `NetworkPolicyDecider` trait allows custom logic for allow/deny/inject decisions per request - -### Zero-Exposure Credential Model - -Secrets (API keys, tokens) are stored encrypted on the host and injected into HTTP requests by the proxy at transit time. Container processes never have access to raw credential values, preventing exfiltration even if container code is compromised. - -Sandbox configuration: see Configuration section above. - -## Testing - -Tests are in `mod tests {}` blocks at the bottom of each file. Run specific module tests: -```bash -cargo test safety::sanitizer::tests -cargo test tools::registry::tests -``` - -Key test patterns: -- Unit tests for pure functions -- Async tests with `#[tokio::test]` -- No mocks, prefer real implementations or stubs - -## Current Limitations / TODOs - -1. **Domain-specific tools** - `marketplace.rs`, `restaurant.rs`, `taskrabbit.rs`, `ecommerce.rs` return placeholder responses; need real API integrations -2. **Integration tests** - Need testcontainers setup for PostgreSQL -3. **MCP stdio transport** - Only HTTP transport implemented -4. **WIT bindgen integration** - Auto-extract tool description/schema from WASM modules (stubbed) -5. **Capability granting after tool build** - Built tools get empty capabilities; need UX for granting HTTP/secrets access -6. **Tool versioning workflow** - No version tracking or rollback for dynamically built tools -7. **Full channel status view** - Gateway status widget exists, but no per-channel connection dashboard -8. **Observability backends** - Only `log` and `noop` implemented; OpenTelemetry/Prometheus not yet supported - -## Tool Architecture - -**Keep tool-specific logic out of the main agent codebase.** The main agent provides generic infrastructure; tools are self-contained units that declare their requirements through `capabilities.json` files (API endpoints, credentials, rate limits, auth setup). Service-specific auth flows, CLI commands, and configuration do not belong in the main agent. - -Tools can be built as **WASM** (sandboxed, credential-injected, single binary) or **MCP servers** (ecosystem of pre-built servers, any language, but no sandbox). Both are first-class via `ironclaw tool install`. Auth is declared in capabilities files with OAuth and manual token entry support. - -See `src/tools/README.md` for full tool architecture, adding new tools (built-in Rust and WASM), auth JSON examples, and WASM vs MCP decision guide. +See `.env.example` for all environment variables. LLM backends (`nearai`, `openai`, `anthropic`, `ollama`, `openai_compatible`, `tinfoil`, `bedrock`) documented in `src/llm/CLAUDE.md`. ## Adding a New Channel @@ -671,48 +183,24 @@ See `src/tools/README.md` for full tool architecture, adding new tools (built-in 3. Add config in `src/config/channels.rs` 4. Wire up in `src/app.rs` channel setup section +## Workspace & Memory + +Persistent memory with hybrid search (FTS + vector via RRF). Four tools: `memory_search`, `memory_write`, `memory_read`, `memory_tree`. Identity files (AGENTS.md, SOUL.md, USER.md, IDENTITY.md) injected into system prompt. Heartbeat system runs proactive periodic execution (default: 30 minutes), reading `HEARTBEAT.md` and notifying via channel if findings. See `src/workspace/README.md`. + ## Debugging ```bash -# Verbose logging -RUST_LOG=ironclaw=trace cargo run - -# Just the agent module -RUST_LOG=ironclaw::agent=debug cargo run - -# With HTTP request logging -RUST_LOG=ironclaw=debug,tower_http=debug cargo run +RUST_LOG=ironclaw=trace cargo run # verbose +RUST_LOG=ironclaw::agent=debug cargo run # agent module only +RUST_LOG=ironclaw=debug,tower_http=debug cargo run # + HTTP request logging ``` -## Module Specifications +## Current Limitations -Some modules have a `README.md` that serves as the authoritative specification -for that module's behavior. When modifying code in a module that has a spec: - -1. **Read the spec first** before making changes -2. **Code follows spec**: if the spec says X, the code must do X -3. **Update both sides**: if you change behavior, update the spec to match; - if you're implementing a spec change, update the code to match -4. **Spec is the tiebreaker**: when code and spec disagree, the spec is correct - (unless the spec is clearly outdated, in which case fix the spec first) - -| Module | Spec File | -|--------|-----------| -| `src/setup/` | `src/setup/README.md` | -| `src/workspace/` | `src/workspace/README.md` | -| `src/tools/` | `src/tools/README.md` | -| `src/agent/` | `src/agent/CLAUDE.md` | -| `src/channels/web/` | `src/channels/web/CLAUDE.md` | -| `src/db/` | `src/db/CLAUDE.md` | -| `src/llm/` | `src/llm/CLAUDE.md` | -| `tests/e2e/` | `tests/e2e/CLAUDE.md` | - -## Workspace & Memory System - -OpenClaw-inspired persistent memory with a flexible filesystem-like structure. Principle: "Memory is database, not RAM" -- if you want to remember something, write it explicitly. Uses hybrid search combining FTS (keyword) + vector (semantic) via Reciprocal Rank Fusion. - -Four memory tools for LLM use: `memory_search` (hybrid search -- call before answering questions about prior work), `memory_write`, `memory_read`, `memory_tree`. Identity files (AGENTS.md, SOUL.md, USER.md, IDENTITY.md) are injected into the LLM system prompt. - -The heartbeat system runs proactive periodic execution (default: 30 minutes), reading `HEARTBEAT.md` and notifying via channel if findings are detected. - -See `src/workspace/README.md` for full API documentation, filesystem structure, hybrid search details, chunking strategy, and heartbeat system. +1. Domain-specific tools (`marketplace.rs`, `restaurant.rs`, etc.) are stubs +2. Integration tests need testcontainers for PostgreSQL +3. MCP: no streaming support; stdio/HTTP/Unix transports all use request-response +4. WIT bindgen: auto-extract tool schema from WASM is stubbed +5. Built tools get empty capabilities; need UX for granting access +6. No tool versioning or rollback +7. Observability: only `log` and `noop` backends (no OpenTelemetry) diff --git a/src/llm/CLAUDE.md b/src/llm/CLAUDE.md index a1eb72be..d1b9eea2 100644 --- a/src/llm/CLAUDE.md +++ b/src/llm/CLAUDE.md @@ -19,6 +19,7 @@ Multi-provider LLM integration with circuit breaker, retry, failover, and respon | `rig_adapter.rs` | Adapter bridging rig-core `CompletionModel` → `LlmProvider`; used by OpenAI, Anthropic, Ollama, Tinfoil | | `smart_routing.rs` | `SmartRoutingProvider` — 13-dimension complexity scorer routes cheap vs primary model | | `recording.rs` | `RecordingLlm` — trace capture for E2E replay testing (`IRONCLAW_RECORD_TRACE`) | +| `bedrock.rs` | AWS Bedrock provider via native Converse API (feature-gated: `--features bedrock`) | ## Provider Selection @@ -32,6 +33,18 @@ Set via `LLM_BACKEND` env var: | `ollama` | Ollama local | `OLLAMA_BASE_URL` | | `openai_compatible` | Any OpenAI-compatible endpoint | `LLM_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL` | | `tinfoil` | Tinfoil TEE inference | `TINFOIL_API_KEY`, `TINFOIL_MODEL` | +| `bedrock` | AWS Bedrock (requires `--features bedrock`) | `BEDROCK_REGION`, `BEDROCK_MODEL`, `AWS_PROFILE` | + +## AWS Bedrock Provider + +Uses the native Converse API via `aws-sdk-bedrockruntime` (`bedrock.rs`). Requires `--features bedrock` at build time — not in default features due to heavy AWS SDK dependencies. + +**Auth:** Standard AWS credential chain — IAM credentials (`AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`), SSO profiles (`AWS_PROFILE`), or instance roles. The SDK resolves auth automatically from the environment. + +**Config:** +- `BEDROCK_REGION` — AWS region (default: `us-east-1`) +- `BEDROCK_MODEL` — Required model ID (e.g., `anthropic.claude-opus-4-6-v1`) +- `BEDROCK_CROSS_REGION` — Optional cross-region inference prefix (`us`, `eu`, `apac`, `global`) ## NEAR AI Provider Gotchas