Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/container-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ on:
- bun.lock
- package.json
- patches/**
- rtk/**
- .github/workflows/container-image.yml
push:
branches:
Expand All @@ -28,7 +27,6 @@ on:
- bun.lock
- package.json
- patches/**
- rtk/**
- .github/workflows/container-image.yml

concurrency:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ Thumbs.db
config/
workspace/
.local/
rtk-data/
13 changes: 5 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ Carapace is a security-hardened Docker container for running [OpenClaw](https://

```
carapace/
├── plugins/ # OpenClaw TypeScript plugins (3 packages)
├── plugins/ # Repo-local OpenClaw TypeScript plugins (2 packages)
│ ├── mcp-bridge/ # MCP stdio server → OpenClaw tool bridge
│ ├── rtk-rewrite/ # exec hook: rewrites commands through rtk
│ └── context-mode/ # Spawns context-mode MCP, registers cm_* tools
├── rtk/ # 37 thin shell wrappers (cat→rtk read, rg→rtk grep, etc.)
├── browser/ # Isolated Chromium container (Dockerfile + entrypoint.sh)
├── workspace/ # Agent workspace (SOUL.md, memory/, skills/) — mounted volume
├── config/ # OpenClaw state — mounted volume, not in git
├── rtk-data/ # Optional RTK history mount (ignored, created locally)
├── patches/ # Bun patches for upstream deps
└── .github/workflows/ # CI (typecheck) + container image build (multi-arch)
```
Expand All @@ -26,7 +25,7 @@ carapace/
|------|----------|-------|
| Add/modify a plugin | `plugins/{name}/src/` | Each plugin has own package.json + openclaw.plugin.json |
| Plugin entry point | `plugins/{name}/index.ts` | Re-exports from `./src/handler.js` |
| RTK command mappings | `rtk/` + `plugins/rtk-rewrite/src/routing.ts` | Wrappers are legacy; plugin does the routing now |
| RTK command rewriting | `Dockerfile` + `Dockerfile.rtk` + `openclaw.json` | RTK image provides the upstream `openclaw/` plugin directory, gateway copies it into `/opt/openclaw/plugins/rtk-rewrite` |
| Docker build | `Dockerfile` | node:24-bookworm-slim, copies rtk binary from companion image |
| Container security | `docker-compose.yml` | read_only, cap_drop ALL, tmpfs, pid limits |
| Browser isolation | `browser/` | Separate container, static IP 172.20.0.10 for CDP |
Expand All @@ -46,8 +45,8 @@ carapace/

## ANTI-PATTERNS (THIS PROJECT)

- **Never use both RTK modes**: Use `rtk-rewrite` plugin OR `tools.exec.pathPrepend` — never both
- **Never enable `cm_ctx_execute`/`cm_ctx_batch_execute`**: Disabled by default to enforce exec→rtk split
- **Never run local Docker image builds unless the user explicitly asks**: `docker build`, `docker compose build`, and similar image-build validation are too heavy for normal agent sessions here; prefer static checks plus CI
- **Never run Chromium in gateway container**: Use the isolated `browser` service instead
- **Don't touch `workspace/AGENTS.md`**: That file is the agent's runtime workspace config, not project docs

Expand Down Expand Up @@ -76,8 +75,6 @@ bun run down # docker compose down

# Tests
bun test plugins/*/src/*.test.ts
# or run a single file
bun test plugins/rtk-rewrite/src/routing.test.ts
```

## PATCHING MODELS
Expand Down Expand Up @@ -131,8 +128,8 @@ A model entry needs: `id`, `name`, `api`, `provider`, `baseUrl`, `reasoning`, `i

## NOTES

- The `rtk/` directory (37 wrapper scripts) is the legacy PATH-prepend approach. The `rtk-rewrite` plugin supersedes it but wrappers remain for the companion Docker image
- `openclaw` binary comes from npm (`openclaw@2026.4.14`), not built from source
- RTK's OpenClaw plugin directory is copied in `Dockerfile.rtk` from the upstream RTK repo and then copied into the gateway image; it is not vendored under `plugins/`
- Browser container gets static IP (172.20.0.10) because CDP rejects hostname-based Host headers
- Container runs as `node` user (UID 1000) — mounted volumes must match ownership
- **This is a Raspberry Pi** — do not spawn heavy/parallel agents that consume excessive RAM
7 changes: 4 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ RUN \
# set home directory
&& usermod -d ${HOME} node \
# create directories
&& mkdir -p ${APP} ${HOME} \
&& mkdir -p ${APP} ${APP}/plugins ${HOME} \
# set ownership
&& chown -R node: ${APP} ${HOME}

Expand All @@ -57,6 +57,7 @@ ENV OPENCLAW_STATE_DIR=${HOME}/.openclaw
ENV OPENCLAW_WORKSPACE_DIR=${OPENCLAW_STATE_DIR}/workspace

COPY --from=rtk-image /usr/local/bin/rtk /usr/local/bin/rtk
COPY --chown=node: --from=rtk-image /opt/rtk-openclaw-plugin /opt/openclaw/plugins/rtk-rewrite

WORKDIR ${APP}

Expand Down Expand Up @@ -90,7 +91,7 @@ RUN --mount=type=cache,target=${HOME}/.bun,uid=1000 \
*) [ ! -e "${APP}/node_modules/$name" ] && ln -s "$pkg" "${APP}/node_modules/$name" || true ;; \
esac; \
done; \
done \
&& chmod -R u=rwX,go=rX ${APP}/node_modules/openclaw/dist/extensions
done \
&& chmod -R u=rwX,go=rX ${APP}/node_modules/openclaw/dist/extensions ${APP}/plugins

ENTRYPOINT ["openclaw"]
15 changes: 11 additions & 4 deletions Dockerfile.rtk
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ FROM rust:bookworm AS builder
ARG RTK_VERSION=v0.37.0

RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
cargo install --git https://github.com/rtk-ai/rtk --tag ${RTK_VERSION}
--mount=type=cache,target=/usr/local/cargo/git \
cargo install --git https://github.com/rtk-ai/rtk --tag ${RTK_VERSION}

FROM debian:bookworm-slim AS rtk

ARG RTK_VERSION=v0.37.0

RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
&& apt-get install -y --no-install-recommends ca-certificates git \
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder /usr/local/cargo/bin/rtk /usr/local/bin/rtk

RUN git clone --depth 1 --branch ${RTK_VERSION} https://github.com/rtk-ai/rtk.git /tmp/rtk \
&& mkdir -p /opt/rtk-openclaw-plugin \
&& cp -R /tmp/rtk/openclaw/. /opt/rtk-openclaw-plugin/ \
&& rm -rf /tmp/rtk
49 changes: 9 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The container image includes:
| **Media** | ffmpeg, imagemagick, yt-dlp |
| **Dev** | git, gh (GitHub CLI), ssh, python3, bun, typescript |
| **Network** | curl, wget |
| **Token Optimization** | [rtk](https://github.com/rtk-ai/rtk) (with selective shell wrappers) |
| **Token Optimization** | [rtk](https://github.com/rtk-ai/rtk) (via OpenClaw plugin rewrite hook) |
| **System** | trash-cli, unzip |

### Optional: Isolated Browser Container
Expand Down Expand Up @@ -151,7 +151,7 @@ Everything else under `/home/openclaw` is a 2GB tmpfs — it resets on restart.

## Plugins

Carapace includes OpenClaw plugins in `plugins/`. Each plugin is loaded via the gateway config's `plugins.load.paths` and `plugins.allow` lists, and can be scoped per-agent using `tools.deny`.
Carapace keeps repo-local OpenClaw plugins in `plugins/`. RTK is the exception: the gateway image fetches RTK's official OpenClaw plugin from the matching RTK release and installs it into `/opt/openclaw/plugins/rtk-rewrite` during the image build. All plugins are then loaded via the gateway config's `plugins.load.paths` and `plugins.allow` lists, and can be scoped per-agent using `tools.deny`.

### mcp-bridge

Expand All @@ -163,9 +163,9 @@ See [`plugins/mcp-bridge/README.md`](plugins/mcp-bridge/README.md) for config de

### rtk-rewrite

Replaces rtk's PATH-prepend shell wrappers with a `before_tool_call` hook. Intercepts `exec` tool calls and rewrites commands through rtk for token compression — same routing logic as the wrapper scripts, but in TypeScript with no PATH manipulation.
Uses RTK's official OpenClaw plugin model: a `before_tool_call` hook delegates `exec` commands to `rtk rewrite`, which returns the optimized command when RTK has a matching filter.

When this plugin is enabled, `tools.exec.pathPrepend` is not needed.
Carapace does not vendor the plugin source in this repo. Instead, the RTK companion image copies the entire upstream `openclaw/` plugin directory from the matching RTK release, and the gateway image copies that directory into `/opt/openclaw/plugins/rtk-rewrite`.

### context-mode

Expand Down Expand Up @@ -322,12 +322,11 @@ How you structure this is entirely up to you — the mounts are just directories

[rtk](https://github.com/rtk-ai/rtk) is a CLI proxy that compresses shell command output before it reaches the LLM context, reducing token usage by 40-90% on common operations (git, ls, grep, etc.).

Carapace ships a companion RTK image (published as `ghcr.io/<owner>/<repo>-rtk`) built from `Dockerfile.rtk`. The gateway Dockerfile defaults to consuming a local image tagged `carapace:rtk`, and CI/other builds can override that with `--build-arg RTK_IMAGE=<image-ref>`. That image contains:
Carapace ships a companion RTK image (published as `ghcr.io/<owner>/<repo>-rtk`) built from `Dockerfile.rtk`. The gateway Dockerfile defaults to consuming a local image tagged `carapace:rtk`, and CI/other builds can override that with `--build-arg RTK_IMAGE=<image-ref>`.

- the `rtk` binary (currently v0.36.0)
- thin wrapper scripts from `rtk/` (mounted in the gateway image at `/opt/rtk`)
The gateway image resolves `RTK_IMAGE` into a named build stage (`ARG RTK_IMAGE=carapace:rtk` + `FROM ${RTK_IMAGE} AS rtk-image`) and copies the `rtk` binary plus the upstream `openclaw/` plugin directory from that stage, so other projects can reuse the exact same RTK package without recompiling Rust.

The gateway image resolves `RTK_IMAGE` into a named build stage (`ARG RTK_IMAGE=carapace:rtk` + `FROM ${RTK_IMAGE} AS rtk-image`) and copies the `rtk` binary from that stage, so other projects can reuse the exact same RTK package without recompiling Rust or duplicating wrappers.
That companion image carries both the `rtk` binary and RTK's official OpenClaw plugin directory, so Carapace tracks upstream plugin behavior without carrying a local plugin copy.

To build the RTK companion image locally for gateway/cli builds:

Expand All @@ -341,41 +340,11 @@ docker compose build

The gateway Dockerfile does not compile rtk itself; it expects the image named by `RTK_IMAGE` to be available locally or via your build pipeline.

**Two integration modes** (use one, not both):

**1. Plugin (carapace default):** The `rtk-rewrite` plugin intercepts `exec` tool calls via a `before_tool_call` hook and rewrites commands through rtk. No PATH manipulation needed. See [rtk-rewrite](#rtk-rewrite).

**2. PATH prepend (legacy):** Set `tools.exec.pathPrepend` to `["/opt/rtk"]` in your OpenClaw config. Shell wrapper scripts intercept commands via PATH ordering.
**Integration mode:** The `rtk-rewrite` plugin intercepts `exec` tool calls via a `before_tool_call` hook and delegates rewrite decisions to `rtk rewrite`. No PATH manipulation is needed.

**Built-in split:** shell commands go through `exec` + rtk. Context-mode is kept for the things rtk does not solve well — file processing, indexing/search, fetch-and-index, and retrieval-heavy workflows. To enforce that split, carapace disables `cm_ctx_execute` and `cm_ctx_batch_execute` by default.

**Common mappings:**

| Command | Routes to | Why |
|---|---|---|
| `cat file` | `rtk read file` | rtk's file reader with intelligent filtering |
| `rg pattern` | `rtk grep pattern` | ripgrep → rtk's compact grep |
| `eslint` | `rtk lint` | rtk's lint formatter |
| `head -N file` | `rtk read file --max-lines N` | Only when a file arg is present (piped `head` passes through) |
| `mypy` | `rtk mypy` | Grouped type-check output |
| `aws sts get-caller-identity` | `rtk aws sts get-caller-identity` | AWS CLI output formatter |
| `psql -c 'select 1'` | `rtk psql -c 'select 1'` | SQL output formatter |

**Configuration (PATH prepend mode)** — add the following to your OpenClaw config and restart the gateway:

```json
{
"tools": {
"exec": {
"pathPrepend": ["/opt/rtk"]
}
}
}
```

**Why `pathPrepend` instead of container `PATH`?** `pathPrepend` only affects agent-initiated `exec` tool calls. Container-internal processes (OpenClaw itself, git hooks, npm scripts) still use the real binaries. This avoids subtle breakage in non-LLM contexts where compressed output would be wrong.

If using the `rtk-rewrite` plugin, `pathPrepend` is not needed — the plugin handles all command rewriting via hooks.
**What gets rewritten:** whatever `rtk rewrite` currently supports. That keeps Carapace aligned with upstream RTK without maintaining a separate command registry here.

**Tracking savings:** rtk records token savings in a SQLite database at `~/.local/share/rtk/history.db`. Run `rtk gain` inside the container to see cumulative stats, or `rtk gain --graph` for a daily breakdown. To persist this across restarts, bind-mount the directory (e.g., `./rtk-data:/home/openclaw/.local/share/rtk:rw`).

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ services:
- .env
volumes:
- ./config:/home/openclaw/.openclaw:rw
- ./rtk:/home/openclaw/.local/share/rtk:rw
- ./rtk-data:/home/openclaw/.local/share/rtk:rw
- ./workspace:/home/openclaw/.openclaw/workspace:rw
ports:
- 18789:18789
Expand Down
12 changes: 2 additions & 10 deletions plugins/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## OVERVIEW

Three OpenClaw plugins extending the gateway: MCP server bridging, RTK command rewriting, and context-mode FTS5 search. All share the same plugin architecture pattern.
Two repo-local OpenClaw plugins extend the gateway: MCP server bridging and context-mode FTS5 search. RTK command rewriting comes from RTK's upstream OpenClaw plugin directory, which the RTK companion image copies from the RTK repo and the gateway image copies into `/opt/openclaw/plugins/rtk-rewrite`.

## PLUGIN ARCHITECTURE

Expand All @@ -18,10 +18,9 @@ Every plugin follows this structure:
└── *.test.ts # Co-located tests (node:assert, run via `bun test`)
```

**Lifecycle hooks** available to plugins:
**Lifecycle hooks** available to repo-local plugins:
- `gateway_start` — register tools, spawn processes
- `gateway_stop` — cleanup, disconnect
- `before_tool_call` — intercept/rewrite tool calls (used by rtk-rewrite)

**Config flow**: `openclaw.plugin.json` defines the JSON schema → OpenClaw validates at load → handler receives typed config.

Expand All @@ -30,7 +29,6 @@ Every plugin follows this structure:
| Task | Location | Notes |
|------|----------|-------|
| Add new MCP server | Config only — no code change | Add entry to `plugins.entries.mcp-bridge.config.servers` |
| Change RTK routing | `rtk-rewrite/src/routing.ts` | Maps command → rtk subcommand |
| Change context-mode tool prefix | Config: `plugins.entries.context-mode.config.toolPrefix` | Default: `cm` |
| Skip context-mode tools | Config: `plugins.entries.context-mode.config.skipTools` | Array of tool names |
| Understand MCP bridging internals | `mcp-bridge/src/bridge.ts` + `runtime.ts` | Process spawn, tool discovery, call proxy |
Expand All @@ -44,12 +42,6 @@ Key files: `bridge.ts` (McpServerBridge class), `runtime.ts` (retry logic, norma

Exports public API beyond just the handler — other plugins can import `McpServerBridge`, `executeWithRetry`, etc.

## rtk-rewrite

Intercepts `exec` tool calls via `before_tool_call` hook. Parses the command, looks up routing rules, rewrites through `rtk` for token compression. Same logic as the shell wrappers in `rtk/` but without PATH manipulation.

Key files: `handler.ts` (hook registration), `routing.ts` (command → rtk mapping rules + tests).

## context-mode

Spawns the context-mode MCP server as a child process and registers `cm_*` tools. Reuses `mcp-bridge` internals (`McpServerBridge`) for process management and tool registration.
Expand Down
2 changes: 1 addition & 1 deletion plugins/mcp-bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ OpenClaw plugin that bridges MCP stdio servers into agent tools.
- ✅ Graceful disconnect on `gateway_stop`
- ⚠️ `lifecycle: "session"` is currently treated as shared/gateway lifecycle

> **Note:** RTK command rewriting was originally planned here but shipped as a standalone plugin (`rtk-rewrite`). Context-mode also shipped as a separate plugin (`context-mode`) — see `plugins/context-mode/`.
> **Note:** RTK command rewriting is provided by RTK's upstream OpenClaw plugin, fetched during the gateway image build. Context-mode ships as a separate repo-local plugin — see `plugins/context-mode/`.

## Install (linked local plugin)

Expand Down
1 change: 0 additions & 1 deletion plugins/rtk-rewrite/index.ts

This file was deleted.

22 changes: 0 additions & 22 deletions plugins/rtk-rewrite/openclaw.plugin.json

This file was deleted.

10 changes: 0 additions & 10 deletions plugins/rtk-rewrite/package.json

This file was deleted.

69 changes: 0 additions & 69 deletions plugins/rtk-rewrite/src/handler.test.ts

This file was deleted.

Loading
Loading