# AGENTS.md — nullclaw Agent Engineering Protocol

This file defines the default working protocol for coding agents in this repository.
Scope: entire repository.

## 1) Project Snapshot (Read First)

nullclaw is a Zig-first autonomous AI assistant runtime optimized for:

- minimal binary size (target: < 1 MB ReleaseSmall)
- minimal memory footprint (target: < 5 MB peak RSS)
- zero dependencies beyond libc and optional SQLite
- full feature parity with ZeroClaw (Rust reference implementation)

Core architecture is **vtable-driven** and modular. All extension work is done by implementing
vtable structs and registering them in factory functions.

Key extension points:

- `src/providers/root.zig` (`Provider`) — AI model providers
- `src/channels/root.zig` (`Channel`) — messaging channels
- `src/tools/root.zig` (`Tool`) — tool execution surface
- `src/memory/root.zig` (`Memory`) — memory backends
- `src/observability.zig` (`Observer`) — observability hooks
- `src/runtime.zig` (`RuntimeAdapter`) — execution environments
- `src/peripherals.zig` (`Peripheral`) — hardware boards (Arduino, STM32, RPi)

Current scale: **245 source files, ~204K lines of code, 5,640+ tests**.

Build and test:

```bash
zig build                           # dev build
zig build -Doptimize=ReleaseSmall  # release build
zig build test --summary all        # run all tests
```

## 2) Deep Architecture Observations (Why This Protocol Exists)

These codebase realities should drive every design decision:

1. **Vtable + factory architecture is the stability backbone**
   - Extension points are explicit and swappable via `ptr: *anyopaque` + `vtable: *const VTable`.
   - Callers must OWN the implementing struct (local var or heap-alloc). Never return a vtable interface pointing to a temporary — the pointer will dangle.
   - Most features should be added via vtable implementation + factory registration, not cross-cutting rewrites.

2. **Binary size and memory are hard product constraints**
   - `zig build -Doptimize=ReleaseSmall` is the release target. Every dependency and abstraction has a size cost.
   - Avoid adding libc calls, runtime allocations, or large data tables without justification.
   - `MaxRSS` during `zig build test` must stay well under 50 MB.

3. **Security-critical surfaces are first-class**
   - `src/gateway.zig`, `src/security/`, `src/tools/`, `src/runtime.zig` carry high blast radius.
   - Defaults are secure-by-default (pairing, HTTPS-only, allowlists, AEAD encryption). Keep it that way.

4. **Zig 0.16.0 API is the baseline — no newer features**
   - HTTP client: `std.http.Client.fetch()` with `std.Io.Writer.Allocating` for response body capture.
   - Child processes: `std.process.Child.init(argv, allocator)`, `.Pipe` (capitalized).
   - stdout: `std.fs.File.stdout().writer(&buf)` → use `.interface` for `print`/`flush`.
   - `std.io.getStdOut()` does NOT exist in the current Zig stdlib — use `std.fs.File.stdout()`.
   - SQLite: linked via `/opt/homebrew/opt/sqlite/{lib,include}` on the compile step, not the module.
   - `ArrayListUnmanaged`: init with `.empty`, pass allocator to every method.

5. **All 5,640+ tests must pass at zero leaks**
   - The test suite uses `std.testing.allocator` (leak-detecting GPA). Every allocation must be freed.
   - `Config.load()` allocates — always wrap in `std.heap.ArenaAllocator` in tests and production.
   - `ChaCha20Poly1305.decrypt` can segfault on tag failure with heap-allocated output on macOS with older Zig toolchains — use a stack buffer then `allocator.dupe()`.

## 3) Engineering Principles (Normative)

These principles are mandatory. They are implementation constraints, not suggestions.

### 3.1 KISS

Required:
- Prefer straightforward control flow over meta-programming.
- Prefer explicit comptime branches and typed structs over hidden dynamic behavior.
- Keep error paths obvious and localized.

### 3.2 YAGNI

Required:
- Do not add config keys, vtable methods, or feature flags without a concrete caller.
- Do not introduce speculative abstractions.
- Keep unsupported paths explicit (`return error.NotSupported`) rather than silent no-ops.

### 3.3 DRY + Rule of Three

Required:
- Duplicate small local logic when it preserves clarity.
- Extract shared helpers only after repeated, stable patterns (rule-of-three).
- When extracting, preserve module boundaries and avoid hidden coupling.

### 3.4 Fail Fast + Explicit Errors

Required:
- Prefer explicit errors for unsupported or unsafe states.
- Never silently broaden permissions or capabilities.
- In tests: `builtin.is_test` guards are acceptable to skip side effects (e.g., spawning browsers), but the guard must be explicit and documented.

### 3.5 Secure by Default + Least Privilege

Required:
- Deny-by-default for access and exposure boundaries.
- Never log secrets, raw tokens, or sensitive payloads.
- All outbound URLs must be HTTPS. HTTP is rejected at the tool layer.
- Keep network/filesystem/shell scope as narrow as possible.

### 3.6 Determinism + No Flaky Tests

Required:
- Tests must not spawn real network connections, open browsers, or depend on system state.
- Use `builtin.is_test` to bypass side effects (spawning, opening URLs, real hardware I/O).
- Tests must be reproducible across macOS and Linux.

## 4) Repository Map (High-Level)

```
src/
  main.zig              CLI entrypoint and command routing
  root.zig              module exports (lib root)
  agent.zig             orchestration loop
  config.zig            schema + config loading/merging (~/.nullclaw/config.json)
  gateway.zig           webhook/HTTP gateway server
  onboard.zig           interactive setup wizard
  health.zig            component health registry
  runtime.zig           runtime adapters (native, docker, wasm, cloudflare)
  tunnel.zig            tunnel providers (cloudflared, ngrok, tailscale, custom)
  skillforge.zig        skill discovery and integration
  migration.zig         memory migration from other backends
  hardware.zig          hardware discovery and management
  peripherals.zig       hardware peripherals (Arduino, STM32/Nucleo, RPi)
  security/             policy, pairing, secrets, sandbox backends
  memory/               SQLite + markdown backends, embeddings, vector search
  providers/            50+ AI provider implementations (9 core + 41 compatible services)
  channels/             17 channel implementations
  tools/                30+ tool implementations
  agent/                agent loop, context, planner
```

## 5) Risk Tiers by Path (Review Depth Contract)

- **Low risk**: docs, comments, test additions, minor formatting
- **Medium risk**: most `src/**` behavior changes without boundary/security impact
- **High risk**: `src/security/**`, `src/gateway.zig`, `src/tools/**`, `src/runtime.zig`, config schema, vtable interfaces

When uncertain, classify as higher risk.

## 6) Agent Workflow (Required)

1. **Read before write** — inspect existing module, vtable wiring, and adjacent tests before editing.
2. **Define scope boundary** — one concern per change; avoid mixed feature+refactor+infra patches.
3. **Implement minimal patch** — apply KISS/YAGNI/DRY rule-of-three explicitly.
4. **Validate** — `zig build test --summary all` must show 0 failures and 0 leaks.
5. **Document impact** — update comments/docs for behavior changes, risk, and side effects.

### 6.1 Code Naming Contract (Required)

Apply these naming rules consistently:

- Functions and methods: `camelCase` (e.g., `parseCommand`, `buildSimpleRequestBody`, `healthCheck`). This follows standard Zig convention.
- Variables, fields, modules, files: `snake_case` (e.g., `workspace_dir`, `bot_user_id`, `config_parse.zig`).
- Types, structs, enums, unions: `PascalCase` (e.g., `AnthropicProvider`, `BrowserTool`, `CommandRiskLevel`).
- Value constants (numeric limits, URLs, timeouts): `SCREAMING_SNAKE_CASE` (e.g., `MAX_BODY_SIZE`, `DEFAULT_BASE_URL`, `KEY_LEN`).
- Comptime array/table constants: `snake_case` (e.g., `high_risk_commands`, `compat_providers`, `default_allowed_commands`).
- Vtable implementer naming: `<Name>Provider`, `<Name>Channel`, `<Name>Tool`, `<Name>Memory`, `<Name>Sandbox`.
- Vtable function-pointer fields: `camelCase` for new vtables (e.g., `chatWithSystem`, `supportsNativeTools`, `getName`). Note: some older vtable fields use `snake_case` (`supports_streaming`, `record_event`); prefer `camelCase` for new additions and consolidate over time.
- Factory registration keys: stable, lowercase, user-facing (e.g., `"openai"`, `"telegram"`, `"shell"`). Use hyphens for multi-word keys (e.g., `"together-ai"`, `"aws-bedrock"`).
- Tests: named with space-separated descriptive phrases as the test block string (e.g., `"command risk low for read commands"`, `"pushover execute missing message"`). Prefix with the subject or subsystem when helpful. Fixtures use neutral names.

### 6.2 Architecture Boundary Contract (Required)

- Extend capabilities by adding vtable implementations + factory wiring first.
- Keep dependency direction inward to contracts: concrete implementations depend on vtable/config/util, not on each other.
- Avoid cross-subsystem coupling (provider code importing channel internals, tool code mutating gateway policy).
- Keep module responsibilities single-purpose: orchestration in `agent/`, transport in `channels/`, model I/O in `providers/`, policy in `security/`, execution in `tools/`.

## 7) Change Playbooks

### 7.1 Adding a Provider

- Add `src/providers/<name>.zig` implementing `Provider.VTable` (`chatWithSystem`, `chat`, `supportsNativeTools`, `getName`, `deinit`).
- Register in `src/providers/root.zig` factory.
- `chatImpl` must extract system/user from `request.messages` (see existing providers for pattern).
- Add tests for vtable wiring, error paths, and config parsing.

### 7.2 Adding a Channel

- Add `src/channels/<name>.zig` implementing `Channel.VTable`.
- Keep `send`, `listen`, `name`, `isConfigured` semantics consistent with existing channels.
- Cover auth/config/health behavior with tests.

### 7.3 Adding a Tool

- Add `src/tools/<name>.zig` implementing `Tool.VTable` (`execute`, `name`, `description`, `parameters_json`).
- Validate and sanitize all inputs. Return `ToolResult`; never panic in the runtime path.
- Add `builtin.is_test` guard if the tool spawns processes or opens network connections.
- Register in `src/tools/root.zig`.

### 7.4 Adding a Peripheral

- Implement the `Peripheral` interface in `src/peripherals.zig`.
- Peripherals expose `read`/`write` methods that delegate to real hardware I/O.
- Use `probe-rs` CLI for STM32/Nucleo flash access; serial JSON protocol for Arduino.
- Non-Linux platforms must return `error.UnsupportedOperation` (not silent 0).

### 7.5 Security / Runtime / Gateway Changes

- Include threat/risk notes in the commit or PR.
- Add/update tests for failure modes and boundaries.
- Keep observability useful but non-sensitive (no secrets in logs or errors).

### 7.6 Updating the Required Zig Version

Whenever a documentation or code change indicates that the required Zig
toolchain version is changing (for example: `README.md`, `AGENTS.md`,
`CLAUDE.md`, `docs/**/installation.md`, `docs/**/development.md`,
`docs/**/termux.md`, `flake.nix`, `Dockerfile`, `.github/workflows/**`, or any
`zig version` / "Zig 0.16.0" string in the repo), the per-distro install
guides must be updated in the same change so they keep matching the pinned
version.

Required steps:

1. Identify the new pinned Zig version (e.g. `0.16.1`, `0.17.0`).
2. Visit `https://ziglang.org/download/` and locate the section for that exact
   version. Do not guess URLs — fetch them from the official download page so
   the filename, archive layout, and presence of the build are confirmed.
3. For every architecture referenced in the install guides (at minimum
   `x86_64-linux`, plus any others already documented), copy the canonical
   tarball URL and checksum from `ziglang.org/download/`.
4. Update every `docs/**/zig-installation.md` file (currently
   `docs/en/zig-installation.md` and `docs/zh/zig-installation.md`) so that
   every URL, checksum, filename, and extracted directory name reflects the new
   version. The version number must appear consistently in:
   - the linked download URL
   - the `wget` command
   - the checksum verification command
   - the `tar -xf` filename
   - the `export PATH="$PWD/zig-...:$PATH"` directory name
5. Keep all `docs/**/zig-installation.md` translations in sync — if the
   English version changes, the Chinese version (and any future translations)
   must be updated in the same commit.
6. Cross-check that the version pin matches everywhere else it appears
   (`AGENTS.md` §2.4, `CLAUDE.md` build commands, other `docs/**` pages,
   `flake.nix`, `Dockerfile`, CI workflows). Mismatches are a hard fail.

Do not bump the Zig version pin in one place without sweeping all of the
above. A partial bump leaves users following copy-paste instructions that
download a tarball that no longer matches the pinned toolchain.

## 8) Validation Matrix

Required before any code commit:

```bash
zig build test --summary all        # all tests must pass, 0 leaks
```

For release changes:

```bash
zig build -Doptimize=ReleaseSmall  # must compile clean
```

Before any version bump, release branch, or tag work: read `RELEASING.md` and follow it exactly. Do not tag feature branches.

Additional expectations by change type:

- **Docs/comments only**: no build required, but verify no broken code references.
- **Security/runtime/gateway/tools**: include at least one boundary/failure-mode test.
- **Provider additions**: test vtable wiring + graceful failure without credentials.

If full validation is impractical, document what was run and what was skipped.

### 8.1 Test Coverage Mandate

**Every code change must be accompanied by tests.** No exceptions.

- **Behavior changes**: add or update tests that directly exercise the changed code path.
- **Bug fixes**: add a regression test that reproduces the original failure *before* the fix and passes after.
- **Logging-only or pure error-propagation changes** (e.g., `catch |err|` + `log.err(...)`): unit testing may not be practical. In this case add a comment near the change explaining why formal test coverage is omitted. Example:
  ```zig
  // NOTE: No unit test for this log path — would require a mock session manager.
  // Covered by manual integration testing against a running NullClaw instance.
  ```
- **Error path resource cleanup**: when a function allocates resources before returning an error, always free them before the `return error.Foo`. Verify with `zig build test` that the test allocator reports 0 leaks.
- Tests that were added to cover a specific fix must have a comment citing the bug they guard against, e.g.:
  ```zig
  // Regression: GLM-5 returns content=null on context-limit; parseNativeResponse must not silently succeed.
  ```

### 8.2 Git Hooks

The repository ships with pre-configured hooks in `.githooks/`. Activate once per clone:

```bash
git config core.hooksPath .githooks
```

Hooks:

| Hook | What it does |
|------|-------------|
| `pre-commit` | Runs `zig fmt --check src/` — blocks commit if any file is not formatted |
| `pre-push` | Runs `zig build test --summary all` — blocks push if any test fails or leaks |

To bypass a hook in an emergency: `git commit --no-verify` / `git push --no-verify`.

## 9) Privacy and Sensitive Data (Required)

- Never commit real API keys, tokens, credentials, personal data, or private URLs.
- Use neutral placeholders in tests: `"test-key"`, `"example.com"`, `"user_a"`.
- Test fixtures must be impersonal and system-focused.
- Review `git diff --cached` before push for accidental sensitive strings.

## 10) Anti-Patterns (Do Not)

- Do not add C dependencies or large Zig packages without strong justification (binary size impact).
- Do not return vtable interfaces pointing to temporaries — dangling pointer.
- Do not use `std.io.getStdOut()` — it does not exist in Zig 0.16.
- Do not silently weaken security policy or access constraints.
- Do not add speculative config/feature flags "just in case".
- Do not skip `defer allocator.free(...)` — every allocation must be freed.
- Do not use `ArrayListUnmanaged.writer()` as `?*Io.Writer` — incompatible types.
- Do not modify unrelated modules "while here".
- Do not include personal identity or sensitive information in tests, examples, docs, or commits.
- Do not use `SQLITE_TRANSIENT` in auto-translated C code — use `SQLITE_STATIC` (null) instead.
- Do not use heap-allocated output buffers in `ChaCha20Poly1305.decrypt` — use stack buffer + `allocator.dupe()`.

## 11) Handoff Template (Agent → Agent / Maintainer)

When handing off work, include:

1. What changed
2. What did not change
3. Validation run and results (`zig build test --summary all`)
4. Remaining risks / unknowns
5. Next recommended action

## 12) Vibe Coding Guardrails

When working in fast iterative mode:

- Keep each iteration reversible (small commits, clear rollback).
- Validate assumptions with code search before implementing.
- Prefer deterministic behavior over clever shortcuts.
- Do not "ship and hope" on security-sensitive paths.
- If uncertain about Zig 0.16 API, check `src/` for existing usage patterns before guessing.
- If uncertain about architecture, read the vtable interface definition before implementing.
