Skip to main content

Contributing to AW

This guide covers AW development checks. For repository-wide contribution and commit rules, see the repository contribution guide.

Run the checks​

Prepare Rust through rustup, Python 3 and Node.js. Rust, rustfmt and Clippy are pinned in rust-toolchain.toml. Run from the repository root:

python3 src/aw/scripts/check.py

The entry runs CI behavior tests, formatting, Clippy, all locked workspace tests, the Python/JavaScript digest vectors and rustdoc. Missing tools, empty or fully ignored configuration, Provider protocol/admission, Provider Host, contract, plan, Core execution, command execution, local service or journal test targets, invalid vectors and command failures return nonzero. Each command has a timeout and its child process group is cleaned up on failure or interruption. Logs identify the failing command; individual commands can be run from src/aw for diagnosis.

The database, inventory, transaction, runtime-lease and package CLI tests also run under child umasks 0022 and 0002, including package planning, configuration, Host and service lease ownership. Deterministic subprocess barriers cover lock release while unrelated children are between fork and exec. Give fixtures and copied executables explicit permissions; do not change a parallel test process's umask.

These checks run as a regular user without an Agent or service login. Cargo downloads uncached dependencies; schema validation reads only bundled resources. The runner, local service, Provider Host execution, command execution and FileJournal require Linux; this gate does not certify other operating systems or minimum supported versions.

For pull requests changing AW, Providers, AW workflows or the declared Tokenless consumer inputs, AW CI runs these checks against the candidate merge commit. Run the command above on Linux before submitting AW changes. If CI fails, find the failing command in the job log and rerun it locally from src/aw.

Regression matrix and source limits​

tests/regression-matrix.json maps each registered Agent, Hook event/effect and lifecycle to exact runnable Rust tests. The compiled Adapter catalog must match the matrix and configuration schema. Shared PreTool and PostTool cases iterate all matrix Agents with explicit native payloads; adding an Agent without its fixture, omitting a Hook cell, deleting a referenced test or marking it ignored fails the existing AW / required gate.

Security coverage includes denial, scan errors, cancellation and offline observation. Tokenless coverage includes fresh-call retry ownership, rejection of in-place mutation, final text replacement, unsupported/error/no-savings results, managed RTK isolation and optional view isolation. These Provider fixtures and callback tests run on every relevant push without credentials. Real SDK/CLI adoption and real Tokenless/RTK package acceptance remain separate, explicit checks; do not reference ignored native tests as required coverage.

The gate builds workspace test harnesses once with Cargo's JSON artifact output, lists each executable directly and reuses the build for the full workspace test run. Every integration target must contain runnable tests, including newly added targets. The explicitly declared opt_in_targets retain installed-package acceptance outside this gate; they must exist and contain only ignored tests. Scope selection, candidate SHA verification and fail-closed required status remain unchanged; shared security and Tokenless tests are never skipped based on the changed Adapter.

Rust, Python and JavaScript (.js, .mjs, .cjs) sources, scripts and fixtures have a 699-line ceiling, with an advisory at 600 lines. Only the existing crates/aw-contracts/src/validation.rs retains its fixed 711-line ceiling. Split by responsibility and preserve entrypoints and assertions; do not exempt test fixtures. Dependency/layout checks are in check_layout.py, compiled inventories in check_inventory.py, and matrix/user-guide consistency in check_matrix.py.

After changing supported capabilities, regenerate both user-guide grids and run the gate from src/aw:

python3 scripts/check_matrix.py --write-docs
python3 scripts/check.py

Gate-only development tests need Python 3.11+ and no Rust or Node runtime:

python3 -B -m unittest discover -s tests -p 'test_ci_*.py'

Daily CI and nightly integration​

AW/Provider and AW workflow changes run the full gate. The daily gate retains all credential-free security, Tokenless, lifecycle and Agent/Hook matrix contracts, including native-inventory validation and source-size checks. Other components' internal changes are owned by their own CI. Tokenless CLI/protocol/runtime/statistics source, its AW Provider and real fixtures, RTK setup/patches, dependency/toolchain inputs, shared Provider protocol/executor source and package-builder inputs select real Tokenless compatibility (the exact paths are in scripts/check.py). It uses the existing package builder, then explicitly runs four named real-CLI Provider tests, including the attributed RTK retry and PostTool contract. Missing/ignored tests, failures and mismatched tested SHAs fail the required result. Other backend public contracts need explicit compatibility acceptance.

Scope selection starts independent AW and Tokenless jobs on the same candidate SHA. Each job has its own runner, build directory and cache key; the Tokenless job does not install Node.js. The existing AW / required check always joins both job results and their tested SHAs. Failures, cancellations, unexpected skips and missing evidence block it, including failures after a test output was published. Only an explicit unselected scope permits a skipped job. AW checks retain a 35-minute deadline; the real backend build and contracts have a separate 60-minute deadline. Parallel elapsed time depends on available runners and caches. Ordinary Agent, service, CLI, installer/transaction, documentation and shared-crate test/example changes run the AW fixture gate without building real Tokenless/RTK. Mixed changes still select real contracts when any consumed input changes. Manual dispatch and forced pushes select both jobs. Tokenless's own component, adapter and prebuilt CI workflows retain their triggers and full coverage independently.

Run this gate locally from a clean, committed checkout:

python3 -B src/aw/scripts/check_tokenless.py --output /absolute/new/evidence-directory

The output must be new; it retains package manifests, binary hashes, test logs and cleanup records. No hosted credentials are required. Remove that exact evidence directory after review; normal component build caches remain reusable.

AW Integration runs every night at 03:23 Asia/Shanghai and accepts a full 40-character candidate_sha for manual pre-release validation. It builds all four Preview packages once, exercises real sec-core health and Tokenless/RTK contracts, and verifies installation, service lifecycle, side-by-side prefixes for rollback, and uninstall. Side-by-side acceptance uses the same candidate build; it does not certify cross-version data migration. Native jobs reuse checksummed binaries from that exact build.

The Python-free package stage uses scripts/check_packages.sh to cap test parallelism at two, retaining real AW payloads and the 30-second CLI deadline. Nested runner containers can expose more CPUs than their effective quota. Each Cargo suite has a 600-second outer deadline; failures stop later suites. The always-uploaded test-evidence/ contains commands, elapsed times, exit codes and before/after cgroup CPU, memory and I/O counters. CLI fixture failures identify the exact command and working directory without dumping the environment. Python is installed only after this stage for package inventory comparisons. Installed prefixes and their database live in one private /tmp directory; cleanup removes them while retaining demo evidence in the artifact directory.

tests/native-matrix.json records fixed runtime versions, owners, bounded commands and the precise acceptance boundary for every registered Agent. The nightly job matrix is generated from this inventory. All eight Agents have automated suites. OpenClaw covers official Hook runners; full OpenClaw Gateway model flows still require explicit acceptance.

Qoder CLI 1.1.64 and QwenPaw 2.2.2.post1 / AgentScope 2.0.10 each require four real sec-core cases: allow, scan-deny, backend-error and timeout-block. The jobs reuse the candidate's checksummed aw, Provider, agent-sec-cli and agent-sec-daemon binaries. Each case starts a private Ubuntu 24.04 container, pinned by the inventory to a multi-platform manifest digest, with the real daemon. The container has no network and only its socket directory is writable on the host. The resolved platform image ID is also recorded. The scheduled run selects github.sha once; containers never fetch a moving branch or a separately built nightly image.

A separate required Qoder 1.1.73 TUI case verifies native SessionStart readiness and empty → added-hook → removed-hook policy activation before dispatch, retaining the same PID/start ticks. It downloads a separately checksummed release and keeps the 1.1.64 print lane. Its named metadata report and cleanup are required by the native inventory. It does not certify hosted TUI tool turns or result replacement. The TUI fixture removes CI and GITHUB_ACTIONS only from its child environment and trusts only its disposable workspace through native permissions.trustDirectories. The runner and print lane keep their environment and permission checks.

The normal case must write its marker. The risk case must match the builtin shell-recursive-delete rule (warn, mapped to block), preserve its disposable deletion fixture and omit the marker. Stopping the actual container tests backend unavailability; pausing it tests the 2-second Provider deadline and on_error: block. Both must leave the marker absent, including after backend recovery. Native callbacks, sec-core events, AW audit and zero live bindings are checked together. QwenPaw's native approval API approves only the exact fixture command before AW rejects it. Every case stops its processes/container and checks socket, plugin and port cleanup; profiles and temporary state are removed, never uploaded. If a fixture exceeds its cleanup grace, the outer Linux subreaper kills and reaps adopted descendants across sessions and removes containers carrying that command's unique ownership label. It retains a cleanup report and fails the interrupted run.

QwenPaw drives its stock App/API with a deterministic loopback OpenAI-compatible model, requiring no cloud key. Qoder CLI uses its real hosted Performance model; Qoder SDK 1.0.56 uses its bundled Worker 1.1.78 and hosted auto model. Both require the repository QODER_PERSONAL_ACCESS_TOKEN Secret, as documented in Qoder authentication. Dependency installation does not receive the Secret. The Qoder launcher and its native callbacks inherit it during execution; backend probes, Docker commands and version checks explicitly remove it from their environments. Missing or expired credentials fail acceptance rather than skip it. Forks need their own Secret to run this nightly lane. Qoder downloads have fixed official URLs, archive checksums and a runtime version check; updates need an explicit inventory change. PR contract gates remain credential-free.

Every native command declares a report contract: unittest/tap requires an exact test count; json requires result files with exact case names or observed counts. Execution and final aggregation both verify the evidence; exit zero or an outer passed status alone cannot certify a suite. Keep reported counts tied to completed checks rather than constants, and update the inventory when coverage changes. Missing suites, stale SHAs and incomplete reports fail the final integration job. Evidence artifacts retain the candidate SHA, component package manifests, installed SDK versions/npm locks, command/PID/deadline records and per-suite results. @kongche-jbw owns AW integration failures and routes backend contract failures to the component CODEOWNERS. Before release, rerun on the exact release commit and validate any additional release-specific native boundaries.

The daily Rust cache is restored for PRs and written only by main pushes; the heavy build cache is written by nightly. Native Python/npm dependencies are cached separately, and native concurrency is capped at three jobs. The full profile requires dispatch from main and a candidate in the fetched origin/main history before any candidate script runs or receives the Qoder Secret. A manual fork dispatch can select credential_free=true for its exact workflow SHA. This profile runs packages, all six credential-free Agents and the resident container; its summary explicitly leaves Qoder CLI/SDK hosted-model acceptance to the full trusted-main nightly. Both the caller and native job withhold the Qoder Secret in this profile.

gh workflow run aw-ci.yml --ref main -f integration=true
gh workflow run aw-preview.yml --ref main -f candidate_sha=<40-character-SHA>

Minimal resident container​

The additional required container job reuses every tests/sdk_resident.py command and exact JSON report in the native inventory: currently 24 suites (16 Claude and eight Pi) out of 49 native suites (42 in the credential-free profile). They include 1,280-call sessions, state retirement, resource-growth assertions, callback drain, session-store reuse and native denial, alongside all earlier lifecycle/MCP cases. Model-relay and dispatch contract entrypoints remain in the host native lane.

The image contains only allowlisted public fixtures, pinned SDK dependencies and four candidate binaries: aw, aw-provider-tokenless, tokenless, and rtk. RTK is required by PreTool in the mixed Bash/MCP long-session suite. Ubuntu, Node and uv images use manifest digests; Python and SDK versions come from the native inventory, and Pi uses its committed npm lock. The build context excludes the repository checkout, deployment files and user configuration. Execution uses an unprivileged test user, synthetic data, loopback model fixtures, no external network, no host mounts and no forwarded credentials. One image archive is shared by all four Agent/regular-or-long shards through a one-day Actions artifact; its SHA-256, image ID and source revision are checked after loading. No image is pushed to a registry. Only declared result JSON, runtime versions, input digests and command/cleanup records are retained; private profiles and per-task data remain in the removed container. Aggregation rechecks every report, candidate SHA, runtime version and container/image removal.

Run the fork candidate profile explicitly:

gh workflow run aw-ci.yml --repo <owner>/anolisa --ref <branch> \
-f integration=true -f credential_free=true

Run only the container locally from src/aw, using the matching nightly bundle:

python3 -B scripts/resident_container.py --candidate-sha "$(git rev-parse HEAD)" \
--bin target/native-binaries --output target/resident-evidence

The output directory must be new and image inputs must be committed. The local command builds once and runs all four shards; CI builds once and runs up to two shards concurrently. Each shard deadline sums its declared case deadlines with cleanup allowance; its job reserves another 15 minutes for image load, evidence and cleanup. Host native job budgets likewise follow the inventory plus setup. Aggregation requires all shards and the same cleaned-up build identity. The runner removes its context, container and unique image tag on success or failure. Image build/load interrupts wait for the bounded Docker operation to settle before tag removal. A timed-out image operation records incomplete cleanup because the daemon may still publish its tag; use the recorded command to check and remove it. Retain target/resident-evidence for inspection; remove it with rm -rf -- target/resident-evidence after review. The container record includes exact stop/removal commands if Docker becomes unavailable during cleanup.

Resident composition with real sec-core, SDK saturation/recovery, request ingress/drain and supervisor restart remain separate acceptance work. The resident suite preserves fail-close, original permissions and metadata-only evidence; it does not certify production endpoints, traffic or deployment-specific shutdown integration.

Local inventory planning and native execution use the same entrypoints as CI:

python3 -B scripts/check_native.py --candidate-sha "$(git rev-parse HEAD)"
python3 -B scripts/native_acceptance.py --agent agentscope \
--candidate-sha "$(git rev-parse HEAD)" --bin target/native-binaries \
--output target/native-evidence/agentscope

To run only the pinned OpenClaw loader/runner suite from src/aw, reuse the inventory's isolated npm installation shape:

npm install --prefix target/openclaw-native --ignore-scripts --no-audit --no-fund openclaw@2026.9.6
OPENCLAW_PACKAGE_DIR="$PWD/target/openclaw-native/node_modules/openclaw" \
node --test --test-reporter=tap crates/aw-cli/tests/openclaw-native.mjs

All eight TAP tests must pass without skips. The suite loads the actual AW entrypoint and manifest through OpenClaw's official loader, then exercises its registered hooks and middleware. Policy aliases come from the pinned runtime. Missing SDK dependencies or loader failures fail the suite. This focused command does not replace native evidence aggregation or certify Gateway/model turns.

The binary directory must contain the nightly bundle and checksums. Install the inventory's Python version, Node 24.16.0 and uv 0.11.13 first. Qoder and QwenPaw also require a working local Docker daemon; Qoder requires its PAT in the environment. Select either Agent with --agent qoder or --agent qwenpaw in the same native entrypoint above. Native execution owns one short temporary directory for Linux Unix sockets and removes it on exit; retained evidence belongs under target/. Native jobs use standalone Python and private Hermes tools/environments so runner loader overrides or shared writable caches do not require weaker native admission. A full run failure is not converted to success by retrying or omitting a framework.

Crate boundaries​

Local package changes enter aw-package::plan, which derives a read-only plan, then plan::execute uses the shared durable transaction backend. Queries and dry runs must not create records or locks. Exercise these paths on Linux:

cargo test --locked -p aw-package --test management --test database
cargo test --locked -p aw-package transaction::tests
cargo test --locked -p aw-exec --test packages

The package tests cover independent tools, exact same-prefix dependencies, legacy record archival, conflicts and replay. Package leases in aw-exec are reused by the CLI, service and Host without adding a runtime dependency on the package database. Keep database-before-prefix lock ordering in mutation and configuration publication. System scope is explicit raw management, not a native package backend. See local package management for commit/recovery rules and remaining runtime discovery limits. The focused commands do not replace the full AW gate or native acceptance.

Framework crates live in src/aw/crates/<name>/, concrete Providers in the repository-root providers/<provider>/ with independent workspaces; the root manifest only organizes the workspace. See Provider collaboration for contribution and distribution, Architecture for ownership and dependencies, and the design index for protocol reading order.

Keep native dialects and bridge assets in aw-adapter; aw-cli owns argument parsing, RPCs, signals and resources. The service owns state, budgets and audit. Both reuse the Adapter without exposing service internals to it. aw-provider owns offline protocol validation; process execution uses aw-exec. Changes to crate boundaries must update check_layout.py, its regression tests, and the architecture tables.

Build the service​

Build the CLI and sample Provider from src/aw on Linux:

cargo build --locked -p aw-cli --bin aw
cargo build --locked -p aw-provider --example policy

The user guide walks through a foreground service and separate client, including state-directory and audit-history cleanup. The service uses the local aw-service/v1alpha7 protocol; Providers retain aw-provider/v1alpha1. They are versioned separately.

Runtime validation​

For focused local service checks:

cargo test --locked -p aw-service -p aw-cli

Service unit tests and CLI integration tests use real Unix sockets, fixture Provider processes and private temporary state directories. They need no model, Agent installation or cloud key. They check shared event budgets, binding and once-only invocation, cancellation and shutdown, durable audit, stale handles and endpoint ownership. Fixtures wait for owned processes and remove temporary directories; build artifacts stay in target/. See the local service contract for the validation boundary.

For focused Provider Host checks, run from src/aw on Linux:

cargo test --locked -p aw-host
cargo test --locked --manifest-path ../../providers/agent-sec-core/Cargo.toml

These tests use local fixture processes to check preparation, request binding, failure reporting, shared deadlines, cancellation and once-only event steps. The local example exercises the sample Provider using synthetic Adapter evidence and tool events. It is not native Agent acceptance or proof of effect adoption.

Protocol and Core tests use synthetic inputs and Hosts. Native integration needs separate evidence that callbacks were installed, tools ran or were blocked as intended, and returned effects were adopted by the Agent. Record validation commands and results in the pull request; keep experiment logs out of the README.

The Qoder SDK native inventory runs both ordinary aw run and --reloadable lanes. The latter adds existing-Query policy addition/removal, another session's busy gate and held native callback cleanup through real aw update RPCs. Both lanes use caller-owned startDaemon(), require the runtime-only PAT and assert unchanged daemon identity and zero bindings/events after cleanup.