# Netclaw Agent Constitution This file is the repository's stable agent constitution. Keep it small. Keep it durable. Keep it routing-focused. ## Authority and Scope - You are authorized to plan, design, implement, test, and document Netclaw. - Default to smallest safe change that advances MVP. - Prefer explicit tradeoffs over hidden complexity. ## Communication Standard Write all agent output in ASD-STE100 Simplified Technical English (STE). This applies to chat replies, commit messages, PR descriptions, code comments, docs, and spec text. Rules: - Give one sentence one idea. Keep an instruction to 20 words or fewer. Keep a descriptive sentence to 25 words or fewer. - Use the active voice. Name the actor that does the action. - Use simple tenses: past, present, and future. Do not use perfect or progressive tenses. - Do not use an `-ing` word as a noun or as an adjective. Use a noun or a relative clause. - Keep the articles `a`, `an`, and `the`. Do not delete words to make a sentence shorter. - Give one word one meaning. Do not use the same word as a noun and as a verb. - Keep a descriptive paragraph to six sentences or fewer. - Put complex information in a vertical list. - Put a warning or a caution before the step that it applies to. STE does not apply to code identifiers, file paths, log text, or quoted material. Keep those exact. STE does not override accuracy. If the approved vocabulary cannot state a technical fact correctly, state the fact correctly and keep the sentence short. ## Current Product Direction - Netclaw is an open-source, self-hosted autonomous operations agent built on Akka.Agents. - MVP is single process, actor-driven, and persistence-backed. - Session identity is Slack thread: `{channelId}/{threadTs}`. - Security posture is default deny. Read first: - `PROJECT_CONTEXT.md` - `TOOLING.md` - `IMPLEMENTATION_PLAN.md` - `docs/prd/README.md` - `docs/architecture/*.md` - `.opencode/skills/netclaw-*/SKILL.md` - `.claude/skills/ralph-*.md` - relevant `openspec/specs/*/spec.md` ## Required Task Routing Use these modes based on requested outcome. ### MODE=planning Use when producing PRDs, specifications, risk analysis, IA, mockups, or execution plans. Expected outputs: - updates in `docs/prd/`, `docs/spec/`, `docs/ui/` - OpenSpec changes and spec deltas in `openspec/` - explicit traceability to PRD IDs ### MODE=build Use when implementing production code, tests, and runtime wiring. Expected outputs: - code changes with validation steps - matching spec updates when behavior changes - no undocumented behavior drift ## OpenSpec Workflow Use OpenSpec skills for substantial planning and spec work — new features, capability additions, or changes that touch multiple components. Skip it for targeted bug fixes, small refactors, and changes where scope is already clear. Do not manually create or edit OpenSpec artifacts (specs, changes, proposals, delta specs, design docs, task files); use the skills below instead. ### When Planning (new feature, capability, or spec change) 1. `/opsx-new` — create a new OpenSpec change 2. `/opsx-continue` — create next artifact in the change workflow 3. `/opsx-ff` — fast-forward: generate all remaining artifacts at once ### When Implementing (building code from a change) 4. `/opsx-apply` — implement tasks from an OpenSpec change ### When Finishing (syncing and archiving) 5. `/opsx-sync` — sync delta specs from a change to main specs 6. `/opsx-verify` — verify implementation matches change artifacts 7. `/opsx-archive` — archive a completed change ### Supporting Workflows - `/opsx-explore` — think through ideas before creating a change - `/opsx-onboard` — guided walkthrough of the full OpenSpec workflow - `/opsx-bulk-archive` — archive multiple completed changes If you do need to create or modify files under `openspec/`, use the appropriate skill above rather than editing them directly. The only exception is updating task checkboxes in `openspec/changes/*/tasks.md` during RALPH iterations. ## Specification Review Contract - Link [the engineering glossary](docs/spec/GLOSSARY.md) for cross-cutting terms. Do not copy its definitions into each specification. - Add a glossary term when two or more capabilities need the same meaning. - Show an ordered flow with pseudocode or a small diagram when sequence, authority, state, or persistence affects the design. - Give at least one concrete positive example and one negative example for each new policy or security boundary. - Name the component that owns each decision. State whether its data is call-local, actor-local, or durable. - Label pseudocode as schematic when it omits a security gate or runtime step. ## Architecture Document Rule `docs/architecture/` holds the canonical architecture documents for people. `docs/architecture/tool-authorization.md` describes tool authorization: its contexts, owners, diagrams, guidelines, and future scenarios. - A PR that changes an authorization context must update `docs/architecture/tool-authorization.md` in the same diff. - An authorization context change includes a new decision owner, a moved check, a new grant scope, a new candidate kind, or a new consent surface. - The document describes the current code. Put a planned shape only in its "Future scenarios" section. - Keep testable rules in `openspec/specs/tool-authorization/spec.md`. The document links to those rules and does not copy them. ## Discovery Rules Before coding a capability, discover in this order: 1. active task in `IMPLEMENTATION_PLAN.md` 2. matching PRD in `docs/prd/` 3. matching engineering spec in `docs/spec/` 4. matching OpenSpec capability in `openspec/specs/` 5. active change plan in `openspec/changes//` If planning and implementation artifacts conflict, fix planning artifacts first. If discovery artifacts conflict with each other, update them before implementing. ## Cross-Boundary Contract Rule When a change writes data consumed by another subsystem, identify the consumer before implementation and verify the producer emits the consumer's canonical representation. This applies to config editors, persistence records, actor messages, protocol payloads, tool schemas, and security policy inputs. For configuration changes, tests must prove both: - invalid or unresolved values are rejected before persistence - persisted values match what runtime ACL/routing/startup code expects Do not treat UI-level save success or schema validity as sufficient when runtime behavior depends on provider IDs, canonical names, permissions, or security policy keys. ## Tool Composition Rule Model agent behavior as a composition of existing tools before you add a new tool. Return machine-actionable paths or identifiers when the session already owns the resource and current policy permits access. Prefer small tools that agents can combine. For example, an agent must use `file_read`, `file_list`, and `file_search` for its own session logs. Do not add a special session-log reader for that behavior. Add a new tool only when existing tools cannot express the operation or its authority boundary safely. State the missing contract and explain why tool composition cannot satisfy it. ## Shell Approval Abstraction Rule Netclaw shell approval code must not parse an executable's private command, subcommand, option, or operand grammar. Approval analysis must use general shell facts. These facts include syntax, control flow, typed values, path scopes, and authority boundaries. Explicit safe-verb and hard-deny lists are policy data. They must not become executable-specific parsers. If ShellSyntaxTree lacks a required fact, keep the input unresolved. Propose a general ShellSyntaxTree capability instead. Do not move a Netclaw executable special case into ShellSyntaxTree. ## Automation Floor Recent regressions define mandatory automated proof classes. TUI text input must have headless typed-key coverage and native smoke coverage for critical flows. Dynamic validation must have fake-failure tests proving save is blocked before persistence. Legacy/new config paradigm changes must have load/round-trip tests from the old shape to the runtime-consumed shape. Human manual testing is a last-mile confidence check, not a substitute for these gates. ## Focused Mutation Test Rule Use focused Stryker tests for critical security and authority boundaries, and for load-bearing reliability boundaries. Review the mutation scope after each security fix, authority policy change, or reliability change. Also review the mutation scope at each minor release. Add one small target when deterministic tests reject a specific unsafe mutation. Keep each target narrow. Keep the total CI cost within the documented budget. CI must fail if an expected mutant survives or does not run. Do not use a broad mutation score as a coverage target. See [TOOLING.md](TOOLING.md#focused-mutation-tests) for the current targets, procedure, cost limits, and expansion criteria. ## Configuration Schema Sync Rule When adding or changing properties on any `*Config` type in `Netclaw.Configuration`, update `src/Netclaw.Configuration/Schemas/netclaw-config.v1.schema.json` in the same PR. Most schema objects set `"additionalProperties": false`, so a new property that is missing from the schema is rejected by `ConfigSchemaDoctorCheck` at runtime, and `netclaw doctor --fix` deletes it without a backup. Provider entries under `Providers` stay open because the binder ignores key case. **Migration-friendly schema changes:** `netclaw doctor --fix` uses `SchemaFixResolver` to auto-fix common schema validation errors. To ensure smooth upgrades for existing configs: - When adding a new **required** property, include a `"default"` value in the schema so the fix resolver can insert it automatically. - When adding an **enum** property, always use `"type": "string"` with named values (not integers) so the resolver can coerce stale numeric values. - When **removing** a property, no special action is needed — the resolver detects and removes properties disallowed by `additionalProperties: false`. ## Release Channel Rule Releases are cut by pushing a git tag (full procedure: `CONTRIBUTING.md` → Releasing). Two conventions the release version gate enforces — violate them and the release fails: - The tag MUST match `Directory.Build.props`: `` for a stable tag, `` + `` for a prerelease. - Prerelease tags MUST use the **dotted** `beta.N` form (`0.23.0-beta.1`, never `beta1`). A mixed identifier like `beta1` is one opaque token that SemVer orders lexically, so `beta10` would rank below `beta2` — in the C# comparator *and* the manifest generator. A tag with a `-` is a prerelease: it becomes a GitHub prerelease and only advances the Docker `:beta` tag and the manifest `latestPrerelease`. A stable tag moves `:latest`, `:major.minor`, and the manifest `latest`. The C# `SemVer` comparator and the bash generator's `feeds/scripts/semver_key.py` are two implementations of one precedence rule kept in lockstep by a shared fixture (`feeds/scripts/semver-order.txt`) — change both (and the fixture) together if precedence ever changes. Never hand-edit the release manifest or installer feed. ## Stacked Pull Requests Stack PRs only when a change needs several reviewable steps that depend on each other. Otherwise, open one PR. - You need contributor (write) access to `netclaw-dev/netclaw` to stack. A PR's base must be a branch in the base repository, so each stack branch must live upstream. A contributor without write access opens one PR from a fork. - Name each branch that another PR uses as its base `feature/` from the start. `pr_validation` runs only for PRs into `dev`, `main`, `master`, or `feature/*`; a PR into another base gets no test CI. - Caution: a branch rename retargets the PRs that use the branch as their base, but it closes the PR whose head is that branch. That PR cannot be reopened. If a head branch must be renamed, open a replacement PR from the renamed branch and link the closed PR to it. - Write each PR description so that it stands alone, and name its base PR. - Verify each PR on the combined stack, not on its isolated branch. - Merge from the bottom up. GitHub treats dependent PRs as a native stack, and `gh pr merge` fails with "must be merged using the asynchronous merge REST API". Use `PUT /repos/{owner}/{repo}/pulls/{n}/merge-async` with `merge_method=squash` and `sha=`. Then poll `GET /repos/{owner}/{repo}/pulls/{n}/merge-async/{uuid}` until the state is `merged`. - After a squash merge, GitHub retargets the next PR to `dev` and can rebase it. If the next PR shows a conflict, run `git rebase --onto origin/dev ` so that only its own commits remain. Then push with `--force-with-lease`. - The repository deletes merged head branches automatically. ## Universal Quality Bar - secure-by-default behavior for gateway and tools - no hidden bypasses around ACL/policy checks - no north-star/deferred features in MVP without explicit PRD update - actor boundaries remain transport-agnostic (pub/sub over direct transport asks) - persistence types remain framework-owned and serialization-safe - **No silent fallbacks.** When something fails or is misconfigured, fail loudly — do not silently degrade to a default. Fallbacks hide bugs and misconfigurations, and on security-relevant paths they can silently escalate privileges. A fallback is only justified when partial failure is a normal runtime condition (e.g., a network retry). If you think you need one, ask first. - no new Slopwatch violations: run `/dotnet-skills:slopwatch` after code changes - use `TimeProvider` (not `DateTime.UtcNow` / `DateTimeOffset.UtcNow`) so time can be virtualized in tests. Inject `TimeProvider` via DI; default to `TimeProvider.System` in production. Standardize on `DateTimeOffset`, not `DateTime`. Usage: `_timeProvider.GetUtcNow()` returns `DateTimeOffset`, `.ToUnixTimeMilliseconds()` for persistence timestamps. - **Reuse before you add — look for existing constructs before creating new ones.** Before introducing a new config knob, constructor parameter, field, interface, or helper, check what already carries the data you need — especially what is *already flowing through the seam you're editing* (the execution context, an injected policy, an existing `*Paths`/`*Config` type). A new construct that parallels an existing one is a defect: it duplicates state, drifts from the original, and forces plumbing (threading a value through N constructors) that the existing path already solved. Anchor the design on the data already present at the call site; if you find yourself adding plumbing to feed a new construct, stop — the value is usually already reachable. Adding a new construct is justified only when no existing one fits and you can name why. - **NEVER add implicit conversions to/from primitive types on value objects.** Value objects exist to prevent accidental misuse — an implicit conversion back to the primitive defeats the purpose. Use `.Value` for explicit access and explicit casts where truly needed. If a value object can silently become a string, it provides no more safety than a raw string. - **Never use the `global::` namespace qualifier in C# source.** Resolve name collisions with an ordinary `using` directive or type alias instead. - **Optional/nullable parameters are rare by default — make dependencies required.** A constructor or method parameter should be optional (nullable or defaulted) only when its absence is a genuine, intended runtime state the callee handles explicitly (a real feature toggle: "no search backend configured" → no web-search tool). NEVER add an optional parameter for backward/source compatibility or to avoid updating call sites — update the call sites instead. This is acute for security-relevant dependencies (path roots, deny-list / access / command policies): a nullable security dependency consumed with `?.` (`_policy?.IsDenied(p) == true`, `if (_policy is not null)`) means a null silently **disables the check** — exactly how trust-zone and privilege-escalation bugs hide. Require it so the type system guarantees it is always present; if it is unconditionally constructed in production, there is no justification for letting it be null. - **Comments: skip noise, keep signal.** Don't narrate what code does when identifiers already say it (`// increment counter`). Do write comments that help a human reviewer scanning in isolation: security gate explanations (what's blocked, for which audience, why), hidden constraints, subtle invariants, non-obvious fallback strategies, and cross-cutting concerns that aren't visible from the call site. A good comment answers "why would this surprise me?" — not "what does this line do?" - **File organization: do NOT enforce "one type per file."** This repo groups closely related types in the same file when that keeps the call chain readable — an interface plus its implementations (`IToolApprovalMatcher` + `ShellApprovalMatcher` + `DefaultApprovalMatcher`), a base type plus its concrete subtypes, a record plus the helpers that produce it. Splitting types into separate files purely for the sake of separation creates review noise without improving navigation. Reviewers (and agents running cleanup passes) SHALL NOT suggest file splits on the basis of "one type per file" alone. Real reasons to split: a file has grown past ~600 lines AND the contained types serve distinct callers; a type genuinely belongs in a different namespace or assembly; circular-include relief in generated code. ## Testing Guidelines - Do not write tests for trivial code — string formatting, simple concatenation, constructor assignment, and other zero-logic paths are not worth testing. - Tests should exercise meaningful behavior: state transitions, error handling, serialization round-trips, routing decisions, integration boundaries. - If the test is just asserting that `$"{a}/{b}"` equals `"a/b"`, delete it. - Prefer fewer tests that cover real behavior over many tests that pad coverage. - **NEVER use `Thread.Sleep` or `Task.Delay` in tests to wait for conditions.** This is a design smell, not just a test smell — if you need a sleep to make a test pass, the production code lacks a proper synchronization signal. Fix the design: - Add request/response acks (e.g., `Ask`) so callers know a state transition has occurred before proceeding. - Use Akka.TestKit's `AwaitAssertAsync` for polling assertions on async state. - `Task.Delay` in fake/mock services to simulate latency is acceptable only in the fake itself, never in test orchestration logic. - **A test MUST delete every file and folder that it creates in the temp directory.** Use `DisposableTempDir` or `TestSessionTempDirectory`. Delete the path in `Dispose`. A `TestKit` class has `IAsyncDisposable`, so xunit does not call its `IDisposable.Dispose`. `TestKit` also stops its actor system after `AfterAllAsync` returns, and it fails the test when `AfterAllAsync` takes more than 5 seconds. A `TestKit` class that owns a temp folder MUST re-implement `IAsyncDisposable.DisposeAsync`. It calls `base.DisposeAsync()`, and then deletes the folder. A class that derives from `LlmSessionTestBase` overrides `DeleteOwnedDirectories` instead. `TestKitTeardownGuardTests` fails when an `AfterAllAsync` override deletes or disposes. Each test process gets a private temp root (`tests/Shared/TestRunTempRoot.cs`). The test run fails with "Test Assembly Cleanup Failure" when a test leaves an entry in that root. The CI log prints the leaks. A test project opts in with `true`. - **TUI / Termina changes MUST be validated with the native smoke harness** before being marked done. xUnit cannot drive Spectre-style prompts, and the non-interactive smoke scenarios only cover the HTTP/JSON surface, so a Termina change that breaks the wizard, model picker, provider picker, or any other prompt flow will pass every other gate. Run: ```bash ./scripts/smoke/run-smoke.sh light # all PR-gating tapes + scenarios ./scripts/smoke/run-smoke.sh init-wizard # one tape, fastest loop ``` See `TOOLING.md` § Interactive CLI Smoke Tests for the full set of commands and `tests/smoke/tapes/README.md` for tape authoring conventions (no `Sleep`, anchor every step, pair with an assertion). ## Post-Code Quality Check After any code changes, run: ```bash dotnet slopwatch analyze # Detect reward hacking (new violations fail CI) ./scripts/Add-FileHeaders.ps1 -Verify # Ensure all .cs files have copyright headers ``` Slopwatch detects: disabled/skipped tests, suppressed warnings, empty catch blocks, hardcoded values, TODO-as-done comments. Baseline is in `.slopwatch/baseline.json` — existing entries are accepted, new violations must be fixed or explicitly baselined with justification. ## Eval Suite Run the behavioral eval suite (`./evals/run-evals.sh`) only when changing the prompt or identity grounding that the agent reads: - Identity file templates (`SOUL.md`, `AGENTS.md`, `TOOLING.md` in init wizard) - System prompt assembly (`SystemPromptAssembler`, `FileSystemPromptProvider`) - Skill content (any `SKILL.md` under `feeds/skills/.system/files/`) - Skill matching logic (`SkillRegistry` keyword handling) Do not run the eval suite for other changes. For those changes, the normal test gates in Definition of Done apply. Update eval cases when: - Adding a new system skill — add a skill auto-load case - Changing identity grounding rules — update identity assertion patterns - A production session exhibits a new failure pattern — add a regression case **Debugging eval failures:** Eval failures are VERY RARELY the model's fault. Almost always the root cause is an instrumentation issue — how we're parsing or asserting on the model's output (regex mismatch, output format change, assertion too brittle). If instrumentation checks out, the next most likely cause is a genuine alignment problem (system prompt, skill content, or context assembly not giving the model the right information). Only after ruling out both should you consider model capability as the cause. ## System Skills Sync Rule Runtime skill use is logical: call `skill_load` by canonical name and `skill_read_resource` for bundled files. Do not expose physical skill roots in prompt indexes or teach agents to derive `SKILL.md` paths. Direct filesystem inspection is reserved for explicit operator diagnostics. One exception applies. The `skill_read_resource` result starts with the resolved absolute path of that one resource, so an agent can run a bundled script by its real path. Do not put that path, or any skill root, in skill indexes, skill listings, or `skill_load` output. System skills in `feeds/skills/.system/files/` are the agent's operational guidance — they tell the running agent how to use features. When you change a feature area, the corresponding skill **must** be updated in the same PR. | Feature area changed | Skill to update | |----------------------|-----------------| | Identity files, SOUL/AGENTS/TOOLING paths, progressive disclosure | `netclaw-operations` | | Memory provider routing, SQLite memory tools, general memory guidance | `netclaw-memory` | | Config format, daemon health, logs, MCP wiring, diagnostics CLI, doctor | `netclaw-operations` | | Skill file format, discovery, authoring workflow | `skill-authoring` | | Tool definitions, CLI commands, grant categories, search_tools, scheduling tools | `netclaw-operations` | | Search tool behavior, citation policy, web_search/web_fetch usage guidance | `search-citation` | | Workspaces, project creation/discovery | `netclaw-projects` | **Workflow:** 1. Edit the skill at `feeds/skills/.system/files/{name}/SKILL.md` 2. Bump `metadata.version` in the YAML frontmatter 3. Do NOT run `generate-skill-manifest.sh` locally — the maintenance workflow generates the legacy manifest on manual `workflow_dispatch` **Publishing:** - The binary release does not publish system skills. - Use `gh workflow run publish_skills.yml` for legacy feed maintenance. - Normal dev pushes do NOT publish to the live feed If a new feature area needs agent guidance, create a new skill file and add a row to this table. ## Definition of Done Done means all of the following are true: - behavior aligns with PRD + spec - acceptance criteria are testable and verified - `dotnet slopwatch analyze` passes (no new violations) - copyright headers present on all `.cs` files (`./scripts/Add-FileHeaders.ps1 -Verify`) - operational impact is documented (runbooks or CLI help) - OpenSpec artifacts are updated or archived appropriately - system skills updated if a mapped feature area was changed (see table above) - eval suite passes for changes to identity or skill grounding (see Eval Suite section) - interactive tape harness passes for changes to Termina TUI surfaces (init wizard, model/provider/webhook pickers, chat page) — see Testing Guidelines and `TOOLING.md` § Interactive CLI Smoke Tests ## Agent Guidance: dotnet-skills IMPORTANT: Prefer retrieval-led reasoning over pretraining for any .NET work. Workflow: skim repo patterns -> consult dotnet-skills by name -> implement smallest-change -> note conflicts. Routing (invoke by name): - Akka.NET: akka-best-practices, akka-hosting-actor-patterns, akka-testing-patterns - C# / code quality: csharp-coding-standards, csharp-concurrency-patterns, csharp-api-design, csharp-type-design-performance - DI / config: microsoft-extensions-dependency-injection, microsoft-extensions-configuration - Serialization: serialization - Testing: akka-testing-patterns, snapshot-testing, testing-strategy - Project structure: project-structure, package-management Quality gates (use when applicable): - dotnet-skills:slopwatch — after substantial new/refactor/LLM-authored code - dotnet-skills:crap-analysis — after tests added/changed in complex code Specialist agents: - akka-net-specialist, dotnet-concurrency-specialist, dotnet-performance-analyst, dotnet-benchmark-designer ## Continuous Improvement Rule - If a workflow repeats twice, extract or refine a skill/workflow doc. - Put volatile detail in repo-owned docs, not this constitution. - Keep this file stable and high-signal. - **Compressing a skill into a rule is a retrieval operation, not a memory operation.** When distilling a dotnet-skills skill (or any upstream authority) into an audit rule, review rubric, or one-line bullet, open the skill first. Preserve the distinctions the skill draws — not just its headline. Rules that collapse two orthogonal axes into one sentence produce false positives that burn trust in the audit. If a rule cannot be written without losing a distinction the source makes, drop it rather than eliding.