rsclaw 2026.5.1

AI Agent Engine Compatible with OpenClaw
Documentation
# AGENTS.md — RsClaw AI Collaboration Guide

AI coding assistant reference for Claude Code, Cursor, Copilot, and sub-agents.
Read this file before touching any code.

---

## Project Snapshot

RsClaw is a Rust-native AI agent gateway — a ground-up rewrite of OpenClaw (TypeScript) with full protocol compatibility, distributed A2A orchestration, and a Tauri desktop shell.

```
src/
  agent/     # Agent runtime, memory, tool dispatch, loop detection
  channel/   # 13 channels: Telegram WeChat Feishu DingTalk QQ Discord Slack
             #              WhatsApp Signal LINE Zalo Matrix WeCom
  config/    # JSON5 loader, schema, runtime resolution
  gateway/   # Startup orchestration, hot-reload, channel wiring
  server/    # Axum HTTP + REST + OpenAI-compatible endpoints
  provider/  # LLM providers: OpenAI Anthropic Gemini DeepSeek Qwen Ollama + failover
  store/     # redb KV + tantivy full-text + hnsw_rs vector
  ws/        # WebSocket protocol v3 (OpenClaw-compatible)
  acp/       # Agent Client Protocol
  a2a/       # Google A2A v0.3 (cross-network agent collaboration)
  mcp/       # MCP client (stdin/stdout JSON-RPC)
  plugin/    # Shell bridge, hook registration
  skill/     # Skill loader, ClawHub/SkillHub client
  cron/      # Cron scheduler and delivery
  browser/   # CDP headless Chrome automation
  cmd/       # CLI commands
  events.rs  # Global event bus
  i18n.rs    # 10 languages: cn en ja ko th vi fr de es ru

ui/
  app/       # Next.js chat UI + control panel
  src-tauri/ # Tauri v1 desktop shell + Rust commands
```

**Stack:** Rust 2024 · MSRV 1.91 · Tokio · Axum · Tauri v1 · Next.js · redb · tantivy · JSON5

---

## Team Roles

This project uses role-based AI sub-agents. Each role has a strict scope.
Role files live in `.claude/roles/`. Activate with: `cp .claude/roles/<role>.md CLAUDE.md`

| Role | Scope | Can Write |
|------|-------|-----------|
| `architect` | Requirements → interface definitions | `docs/` only |
| `backend-dev` | Rust implementation | `src/` |
| `ui-dev` | Frontend implementation | `ui/` |
| `backend-tester` | Rust test coverage | `tests/` |
| `ui-tester` | Frontend test coverage | `ui/test/` |
| `reviewer` | Rust code review | `docs/reviews/` |
| `design-reviewer` | UI/UX review | `docs/reviews/` |
| `qa-lead` | Quality gate, merge approval | PR description |

**Cross-role rule:** Never write outside your assigned scope. If a gap requires touching another scope, file a handoff note in `docs/interfaces/` and stop.

---

## Coding Standards

### Rust

```
- async fn in traits: native (Rust 2024). Never use async-trait macro.
- Error handling: never unwrap(). Use ? or .expect("reason") with explanation.
- No emojis anywhere: code, comments, logs, commit messages.
- i18n: all user-facing strings through src/i18n.rs only.
- LLM-facing prompts (system messages, tool descriptions, summarize/analyze
  prompts, agent instructions) are ALWAYS English and DO NOT go through
  i18n. The codebase standardises on one language to avoid zh/en drift,
  and English yields the most reliable instruction-following across
  providers. i18n is for what the human sees; English is for what the
  LLM reads.
- Config fields: camelCase in JSON5, snake_case in Rust via #[serde(rename_all = "camelCase")]
- Secrets: SecretOrString — plain string or { source: "env", id: "VAR_NAME" }
- Channel handler order: group policy → DM policy (pairing/allowlist) → per-user queue → agent dispatch
- New events: must be registered in events.rs before use
- New pub API: must have doc comment
```

### Frontend (ui/)

```
- Tauri invoke: window.__TAURI__?.invoke (v1 API, NOT core?.invoke)
- Hooks: all declared before any early return
- Data fetching: never fetch() inside components — use hooks or store
- WebSocket: all WS logic through ui/src/hooks/useRsClawSocket.ts
- Config (desktop): Tauri commands read_config_file / write_config, not gateway API
- Auth token priority: gateway.auth.token config > RSCLAW_AUTH_TOKEN env > localStorage
- Components: Container (data) + Presenter (render) separation for complex views
- Styling: Tailwind utility classes only, no inline style, no hardcoded color values
- State: every async component handles loading / error / empty
```

### WebSocket (ws/)

```
- event:chat must be broadcast to ALL operator connections, not just initiating connection
- Connection states: connecting → connected → disconnected → reconnecting → error
- UI must reflect all 5 states with distinct feedback
- Operator connections are separate from user connections — different hook instances
```

### Build

```
- Env vars required: RSCLAW_BUILD_VERSION, RSCLAW_BUILD_DATE
- Tag before build, not after
- Dev gateway flag: --dev (port 18889)
- Test command: RSCLAW_BUILD_VERSION=dev RSCLAW_BUILD_DATE=test cargo test
- Cross-compile targets:
    aarch64-apple-darwin       x86_64-apple-darwin
    x86_64-unknown-linux-musl  aarch64-unknown-linux-musl
    x86_64-pc-windows-msvc     aarch64-pc-windows-msvc
```

---

## Key Patterns

### i18n (Internationalisation)

All user-facing strings sent through channels (Telegram, Discord, WeChat, etc.) **must** go through `src/i18n.rs`. CLI output, log messages, and internal errors stay in English.

**Important:** strings that the LLM reads (system prompts, tool descriptions, summarize/analyze prompts, agent instructions) are NOT i18n'd. They live as English-only literals in the source. See the Rust rules above — different language per LLM call would drift over time and English instruction-following is consistently the strongest across providers.

**Adding a new message key**

```
1. Open src/i18n.rs
2. Add a msg!() block inside the MESSAGES LazyLock initialiser (before `m`):

   msg!("my_key",
       "en" => "English text",        // required
       "zh" => "中文文本",              // required
       "th" => "ข้อความภาษาไทย",
       "vi" => "Van ban tieng Viet",
       "ja" => "日本語テキスト",
       "es" => "Texto en español",
       "ko" => "한국어 텍스트",
       "ru" => "Русский текст",
       "fr" => "Texte français",
       "de" => "Deutscher Text",
   );

3. Use in Rust:
   - Static:      crate::i18n::t("my_key", lang)
   - With params: crate::i18n::t_fmt("my_key", lang, &[("param", value)])
   - Params in template strings use {param} syntax.

4. Get `lang` from config inside run_turn (already set as i18n_lang),
   or derive inline for tool handlers:
       let lang = self.config.raw.gateway.as_ref()
           .and_then(|g| g.language.as_deref())
           .map(crate::i18n::resolve_lang)
           .unwrap_or("en");
   For notification-only code outside a turn (e.g. background spawns), capture
   lang_bg = lang before tokio::spawn and move it into the closure.
```

**Rules**

- Supported languages: `en`, `zh`, `th`, `vi`, `ja`, `es`, `ko`, `ru`, `fr`, `de` (10 total; `json` is debug-only).
- Minimum required per key: `en` + `zh`. Add the rest as available.
- Keys are snake_case, grouped by feature (prefix: `acp_`, `cli_`, etc.).
- Desktop/system Notification strings use `crate::i18n::default_lang()`.
- Never hardcode Chinese (or any non-English) text outside i18n.rs.
- Review gate: `[BLOCK]` if user-facing string is hardcoded in channel code.

---

### New Channel

```
1. src/channel/{name}.rs         — implement Channel trait
2. src/config/schema.rs          — add config struct with #[serde(flatten)] pub base: ChannelBase
3. src/gateway/startup.rs        — add start_{name}_if_configured()
4. Wire DM policy enforcer        — pairing / allowlist / open / disabled
5. ui/app/components/rsclaw-panel.tsx — add to channel list
6. tests/channel_{name}.rs       — create test file (even if skeleton)
```

### New Tool

```
1. src/agent/runtime.rs          — add ToolDef in build_tool_list()
2. src/agent/runtime.rs          — add dispatch case in tool match block
3. src/agent/runtime.rs          — implement tool_{name}() method
```

### New LLM Provider

```
1. src/provider/registry.rs      — add provider config
2. Implement OpenAI chat completions protocol
3. ui/app/components/onboarding.tsx — add to ALL_PROVIDERS for UI
```

---

## Review Standards

Reviewers output to `docs/reviews/[branch].md` using these tags:

| Tag | Meaning |
|-----|---------|
| `[BLOCK]` | Must fix before merge |
| `[SUGGEST]` | Recommended improvement |
| `[NOTE]` | Non-blocking observation |

**Auto-BLOCK triggers (Rust):** unwrap() without explanation · silent error discard (`let _ =`) · new WS event not in events.rs · pub fn missing doc comment · channel change with no corresponding test file

**Auto-BLOCK triggers (UI):** hardcoded color values · operation with no loading feedback · WS disconnect not disabling input · breaking change without confirm dialog

---

## QA Gate (qa-lead only)

Merge only when ALL of the following pass:

```
Backend
  □ docs/reviews/[branch].md — zero [BLOCK] items
  □ cargo test --all passes
  □ New features have tests/ coverage
  □ events.rs has no orphaned events

Frontend
  □ docs/reviews/ui-[branch].md — zero [BLOCK] items
  □ yarn test passes
  □ yarn tsc --noEmit passes
  □ WS state machine: all 5 states tested

Docs
  □ API changes reflected in docs/interfaces/
  □ Architecture decisions recorded in docs/adr/
  □ README / AGENTS.md updated if needed
```

If any QA check is ambiguous or a breaking change touches `ws/` or `provider/`, **stop and wait for human decision**.

---

## API Reference

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/message` | POST | Send message to agent |
| `/api/v1/agents` | GET/POST | List / create agents |
| `/api/v1/agents/{id}` | PATCH/DELETE | Update / delete agent |
| `/api/v1/sessions` | GET | List sessions |
| `/api/v1/channels/pair` | POST | Approve pairing code |
| `/api/v1/channels/pairings` | GET | List pairings |
| `/api/v1/config` | GET/PUT | Read / write config |
| `/api/v1/health` | GET | Health check |
| `/v1/chat/completions` | POST | OpenAI-compatible |
| `/v1/models` | GET | OpenAI model list |
| `/.well-known/agent.json` | GET | A2A Agent Card |
| `/api/v1/a2a` | POST | A2A JSON-RPC tasks |

---

## License

MIT OR Apache-2.0 dual license. Contributions are dual-licensed under the same terms (see README).