Thanks for your interest in contributing! Catalyst Code is a coding-agent harness made of four cooperating components around a single stdio JSONL protocol.
| Component | Language | Role |
|---|---|---|
core/ |
Rust (tokio) | The engine: conversation, model streaming, tools, sessions, plugins, subagents |
tui/ |
Go (Bubble Tea) | Terminal UI; spawns core and speaks JSONL |
sdk/ |
TypeScript | Thin pi-compatible wrapper so pi-web can swap in the harness |
web/ |
Rust (axum) | Thin HTTP + WebSocket hub; spawns core and never thinks |
Build the core and TUI from the repo root:
./build.sh # cargo --release --all-features (core) + go build (tui), in parallel
./build.sh --run # build, then launch the TUI with that exact coreOr individually:
cargo build --release --all-features --manifest-path core/Cargo.toml # → core/target/release/core
cd tui && go build -o catcode . # → tui/catcodeThe web hub is a Rust crate: cargo run --manifest-path web/Cargo.toml.
Core (Rust):
cd core
cargo fmt --all -- --check
cargo clippy --all-targets
cargo test --lockedLinux source builds need WebKitGTK 4.1 + GTK 3 headers (libwebkit2gtk-4.1-dev
libgtk-3-dev). Runtime also wants xvfb on headless hosts. Run /browser
inside catcode if those packages are missing.
TUI (Go):
cd tui
gofmt -l . # must be empty
go vet ./...
go test ./...
go build ./...CI runs all of the above, so run them locally first to save a round-trip.
- Rust:
cargo fmt(rustfmt default). Avoidunwrap()/expect()on data that comes from the model, files, or the network — prefer?/unwrap_or/ explicit error returns so adversarial input can't crash the core. - Go:
gofmt -s. The TUI is single-threadedUpdate+ channel-only goroutines; keep shared state behind thesessionmodel and communicate via channels/tea.Cmd, not shared mutable globals.
- File tools confine paths to the workspace (
../absolute/symlink escapes are rejected). Don't add a path-handling tool that bypassesworkspace::resolve. - The
bashtool runs under an optional microVM sandbox (--sandbox microsandbox,--no-network). Treat the denylist as a tripwire, not a sandbox. When sandboxing is enabled, the denial-of-host-access is real (separate kernel + filesystem root). - Secrets: never log API keys or OAuth tokens. The
set_keypath logs only the provider name, never the key. Config files holding keys are written0600. - Plugins from a repo's
.catalyst-code/plugins/load only with an explicit--trust-project-pluginsopt-in — never read that flag from a config file a repo could ship.
The Microsandbox integration lives in core/src/sandbox/ (backend, manager,
preflight, setup, policy, error) behind a microsandbox cargo feature that is
on by default. All Microsandbox SDK calls are isolated behind CatCode-owned
abstractions (a ExecutionBackend trait with HostExecutionBackend /
MicrosandboxExecutionBackend implementations) — do not call the SDK or
spawn msb/firejail/sandbox-exec/unshare directly from tools, plugins, git
helpers, or diagnostics. Add a new agent-controlled workload by routing it
through the shared sandbox execution layer.
Use a short, imperative subject (add fetch SSRF hardening, not added).
Reference the issue/PR number in the body when relevant.
Please do not open a public issue for security vulnerabilities. See
SECURITY.md if present, or contact the maintainers privately.
By contributing, you agree your contributions are licensed under the MIT
License (see LICENSE).