zeroclaw/CONTRIBUTING.md
Chummy dfe648d5ae
chore(ci): establish PR governance for agent collaboration (#177)
* chore(ci): establish PR governance for agent collaboration

* docs: add AGENTS playbook and strengthen agent collaboration workflow

---------

Co-authored-by: chumyin <183474434+chumyin@users.noreply.github.com>
2026-02-15 12:41:16 -05:00

7.6 KiB

Contributing to ZeroClaw

Thanks for your interest in contributing to ZeroClaw! This guide will help you get started.

Development Setup

# Clone the repo
git clone https://github.com/theonlyhennygod/zeroclaw.git
cd zeroclaw

# Enable the pre-push hook (runs fmt, clippy, tests before every push)
git config core.hooksPath .githooks

# Build
cargo build

# Run tests (all must pass)
cargo test

# Format & lint (must pass before PR)
cargo fmt && cargo clippy -- -D warnings

# Release build (~3.4MB)
cargo build --release

Pre-push hook

The repo includes a pre-push hook in .githooks/ that enforces cargo fmt --check, cargo clippy -- -D warnings, and cargo test before every push. Enable it with git config core.hooksPath .githooks.

To skip it during rapid iteration:

git push --no-verify

Note: CI runs the same checks, so skipped hooks will be caught on the PR.

High-Volume Collaboration Rules

When PR traffic is high (especially with AI-assisted contributions), these rules keep quality and throughput stable:

  • One concern per PR: avoid mixing refactor + feature + infra in one change.
  • Small PRs first: prefer PR size XS/S/M; split large work into stacked PRs.
  • Template is mandatory: complete every section in .github/pull_request_template.md.
  • Explicit rollback: every PR must include a fast rollback path.
  • Security-first review: changes in src/security/, runtime, and CI need stricter validation.

Full maintainer workflow: docs/pr-workflow.md.

Agent Collaboration Guidance

Agent-assisted contributions are welcome and treated as first-class contributions.

For smoother agent-to-agent and human-to-agent review:

  • Keep PR summaries concrete (problem, change, non-goals).
  • Include reproducible validation evidence (fmt, clippy, test, scenario checks).
  • Add brief workflow notes when automation materially influenced design/code.
  • Call out uncertainty and risky edges explicitly.

We do not require PRs to declare an AI-vs-human line ratio.

Agent implementation playbook lives in AGENTS.md.

Architecture: Trait-Based Pluggability

ZeroClaw's architecture is built on traits — every subsystem is swappable. This means contributing a new integration is as simple as implementing a trait and registering it in the factory function.

src/
├── providers/       # LLM backends     → Provider trait
├── channels/        # Messaging         → Channel trait
├── observability/   # Metrics/logging   → Observer trait
├── runtime/         # Platform adapters → RuntimeAdapter trait
├── tools/           # Agent tools       → Tool trait
├── memory/          # Persistence/brain → Memory trait
└── security/        # Sandboxing        → SecurityPolicy

How to Add a New Provider

Create src/providers/your_provider.rs:

use async_trait::async_trait;
use anyhow::Result;
use crate::providers::traits::Provider;

pub struct YourProvider {
    api_key: String,
    client: reqwest::Client,
}

impl YourProvider {
    pub fn new(api_key: Option<&str>) -> Self {
        Self {
            api_key: api_key.unwrap_or_default().to_string(),
            client: reqwest::Client::new(),
        }
    }
}

#[async_trait]
impl Provider for YourProvider {
    async fn chat(&self, message: &str, model: &str, temperature: f64) -> Result<String> {
        // Your API call here
        todo!()
    }
}

Then register it in src/providers/mod.rs:

"your_provider" => Ok(Box::new(your_provider::YourProvider::new(api_key))),

How to Add a New Channel

Create src/channels/your_channel.rs:

use async_trait::async_trait;
use anyhow::Result;
use tokio::sync::mpsc;
use crate::channels::traits::{Channel, ChannelMessage};

pub struct YourChannel { /* config fields */ }

#[async_trait]
impl Channel for YourChannel {
    fn name(&self) -> &str { "your_channel" }

    async fn send(&self, message: &str, recipient: &str) -> Result<()> {
        // Send message via your platform
        todo!()
    }

    async fn listen(&self, tx: mpsc::Sender<ChannelMessage>) -> Result<()> {
        // Listen for incoming messages, forward to tx
        todo!()
    }

    async fn health_check(&self) -> bool { true }
}

How to Add a New Observer

Create src/observability/your_observer.rs:

use crate::observability::traits::{Observer, ObserverEvent, ObserverMetric};

pub struct YourObserver { /* client, config, etc. */ }

impl Observer for YourObserver {
    fn record_event(&self, event: &ObserverEvent) {
        // Push event to your backend
    }

    fn record_metric(&self, metric: &ObserverMetric) {
        // Push metric to your backend
    }

    fn name(&self) -> &str { "your_observer" }
}

How to Add a New Tool

Create src/tools/your_tool.rs:

use async_trait::async_trait;
use anyhow::Result;
use serde_json::{json, Value};
use crate::tools::traits::{Tool, ToolResult};

pub struct YourTool { /* security policy, config, etc. */ }

#[async_trait]
impl Tool for YourTool {
    fn name(&self) -> &str { "your_tool" }

    fn description(&self) -> &str { "Does something useful" }

    fn parameters_schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "input": { "type": "string", "description": "The input" }
            },
            "required": ["input"]
        })
    }

    async fn execute(&self, args: Value) -> Result<ToolResult> {
        let input = args["input"].as_str()
            .ok_or_else(|| anyhow::anyhow!("Missing 'input'"))?;
        Ok(ToolResult {
            success: true,
            output: format!("Processed: {input}"),
            error: None,
        })
    }
}

Pull Request Checklist

  • PR template sections are completed (including security + rollback)
  • cargo fmt --all -- --check — code is formatted
  • cargo clippy --all-targets -- -D warnings — no warnings
  • cargo test — all 129+ tests pass
  • New code has inline #[cfg(test)] tests
  • No new dependencies unless absolutely necessary (we optimize for binary size)
  • README updated if adding user-facing features
  • Follows existing code patterns and conventions

Commit Convention

We use Conventional Commits:

feat: add Anthropic provider
feat(provider): add Anthropic provider
fix: path traversal edge case with symlinks
docs: update contributing guide
test: add heartbeat unicode parsing tests
refactor: extract common security checks
chore: bump tokio to 1.43

Recommended scope keys in commit titles:

  • provider, channel, memory, security, runtime, ci, docs, tests

Code Style

  • Minimal dependencies — every crate adds to binary size
  • Inline tests#[cfg(test)] mod tests {} at the bottom of each file
  • Trait-first — define the trait, then implement
  • Security by default — sandbox everything, allowlist, never blocklist
  • No unwrap in production code — use ?, anyhow, or thiserror

Reporting Issues

  • Bugs: Include OS, Rust version, steps to reproduce, expected vs actual
  • Features: Describe the use case, propose which trait to extend
  • Security: See SECURITY.md for responsible disclosure

Maintainer Merge Policy

  • Require passing CI Required Gate before merge.
  • Require review approval for non-trivial changes.
  • Require CODEOWNERS review for protected paths.
  • Prefer squash merge with conventional commit title.
  • Revert fast on regressions; re-land with tests.

License

By contributing, you agree that your contributions will be licensed under the MIT License.