Vanilla openclaw is a capable AI gateway, but it ships without answers to four production concerns: what happens when the model is manipulated, where credentials go when the user pastes them, how you audit what the agent actually did, and what stops a malicious skill from the registry running before you ever look at it. oasis-claw is a thin plugin layer that fills exactly those gaps — no fork, no divergence from upstream, eleven focused extensions on top of the standard plugin SDK. Six are the security/observability surface; five ship capability: voice (oasis-voice), LLM routing (model-switcher), headless browser (browser), local embeddings (oasis-semantics), and the biomimetic sleep/wake lifecycle (sleep-cycle — doze mutex, nightly session compact+reset, waking summary). Private long-term memory is the bundled upstream memory-core plugin, enabled and configured (not forked) with nightly dreaming consolidation on by default. Anything we vendor in from upstream (currently just browser) is pinned, audited, and refresh-gated — see AUDIT_LOG.md.
New to this repo? Read this section first. Everything below assumes it.
Gateway. One long-running process per bot. It owns the model connection, the channels, the plugins, and the session state. Nothing talks to the model directly — everything goes through the gateway. When a bot "is down", this is what is down.
Bot (agent). An identity: a name, a workspace, a model, a set of channels, and a policy. Each bot in this fleet is one container running its own gateway. Bots share the runtime image but never share state.
Channel. How a human reaches a bot — Telegram, Microsoft Teams, a terminal,
the browser Control UI. A bot can have several at once. Channels are pluggable;
openclaw channels list --all shows what is installable.
Session. One conversation thread, keyed by name (main by default). Sessions
carry history and compact themselves when they approach the context ceiling.
Workspace. The bot's own files at ~/.openclaw/workspace inside its
container. IDENTITY.md and SOUL.md define who it is; .swarm/ holds
coordination state it manages itself.
Plugin (extension). A capability, loaded at boot. The eleven in this repo are
listed in the table below. A plugin's agent tools are silently uncallable unless
they are also declared in the manifest's contracts.tools — a real and
easy-to-miss trap.
Sidecar. A separate container the bot depends on: oasis-voice (speech),
oasis-semantics (embeddings), oasis-egress-proxy (the only way out to the
internet). Sidecars are reached by container name over a private network.
Reviewer. The gate between the agent deciding to run something and it actually running. Layer 1 is deterministic regex; Layer 2 is a model judging the call against a written constitution. Verdicts are allow, deny, or escalate — where escalate means a human is asked and a timeout means no.
Role file. role.yaml, the declarative per-bot policy: which commands are
allowed, which hosts are reachable, which directories are mounted.
you ──▶ channel ──▶ gateway ──▶ agent turn ──▶ model (via provider)
│ │
│ ▼
│ wants a tool
│ │
│ ▼
│ REVIEWER ──▶ deny
│ │ escalate ──▶ you approve
│ ▼ allow
│ tool runs
▼ │
you ◀── reply ◀─────────┴──────────────────────────┘
Every step above is logged. That is the point of the security layer: the transcript exists whatever the model claims it did.
| Gap in vanilla openclaw | oasis-claw plugin | What it does |
|---|---|---|
| No agent-side injection reporting | prompt-injection-reporting |
Agent calls report_injection when it detects manipulation; signed JSONL entry + Telegram operator alert |
| Credentials land in plaintext in LLM context | secrets-vault |
AES-256-GCM at-rest vault; agent gets an opaque handle, not plaintext; redaction hook strips secrets before any JSONL write |
| Sensitive actions execute without human sign-off | approval-gate |
forward_captcha tool routes CAPTCHA images through Telegram and returns the operator's typed solution; API approval policy hooks for HTTP requests |
| No immutable session transcript | session-history |
Append-only JSONL at llm_input, llm_output, and tool_call events; sandbox invariant tests verify the writer never escapes its logDir |
| Agent loses all context on session reset | dot-swarm |
Shared .swarm/ stigmergy: injects state.md + queue.md into every session's memory section via registerMemoryPromptSupplement, provides the swarm_read + compact tools, and registers the swarm-compact CompactionProvider so context-ceiling compaction resumes from the agent's own handoff note |
| Memory never consolidates — agent forgets recent work | memory-core (bundled, configured) |
Vanilla openclaw ships memory-core with dreaming off; oasis-claw enables it by default — a nightly light→REM→deep sweep promotes recent recalls into durable MEMORY.md and writes a Dream Diary |
| Clawhub skills install with zero security review | clawhub-skill-audit |
Auto-fires an Opus 4.7 audit on every newly installed skill (SKILL.md + bundled scripts), writes an immutable JSON trail, and optionally quarantines block-verdict skills before they're loaded by an agent |
| LLM provider is hardcoded per session | model-switcher |
setmodel agent tool + /setmodel slash command for hot-swapping the active model without a recreate; optional allowedProviders lock-down |
| No native voice (TTS / streaming STT) | oasis-voice |
Registers as openclaw SpeechProvider + RealtimeTranscriptionProvider; lite tier is Piper TTS + Moonshine STT, runnable on a laptop CPU |
| No headless browser tool — vendored from upstream | browser |
Chromium via Playwright with control-auth, SSRF guards, and evaluateEnabled forced off-by-default at our config layer; pinned + audited per AUDIT_LOG.md |
openclaw's upstream external-content.ts runs regex patterns on inbound content passively. prompt-injection-reporting adds the complementary agent-side layer: when the model recognises an attempt, it calls report_injection, which writes a tamper-evident signed entry to the attack log and fires an operator alert. Both layers run simultaneously — they target different failure modes.
secrets-vault ensures the gateway is never an accidental credential exfiltration path. Plaintext never appears in tool-call history, JSONL transcripts, or memory supplements. The only path to re-materialization is through tool calls that explicitly request the secret, scoped to the plugin's stateDir.
clawhub-skill-audit closes the supply-chain gap. Every newly installed skill in the workspace is fed to Opus 4.7 via a forced-tool-use call and graded against a concrete catalogue of malicious patterns: prompt-injection in SKILL.md, credential exfiltration, exfil over curl/webhooks, persistence backdoors, supply-chain curl|sh, sandbox-evasion, and explicit attempts to disable other oasis-claw plugins. Verdicts are pass / warn / block; block can optionally move the skill to a quarantine directory. The audit trail is one immutable JSON file per skill at ~/.openclaw/logs/skill-audits/YYYY/MM/DD/<auditId>.json.
Every LLM input, output, and tool call is written to an append-only JSONL file by session-history. This is structural transparency: the transcript exists regardless of what the model says it did or didn't do.
dot-swarm makes cross-session coordination state observable. .swarm/state.md and .swarm/queue.md are human-readable files you can inspect, diff, and version-control. The agent reads and writes them via structured tools (swarm_read, compact) — there is no hidden state. Combined with session-history's JSONL transcripts and memory-core's durable MEMORY.md, you have a full audit trail: what the agent knew (memory supplement + memory-core recall), what it did (tool calls + transcript), and what it decided to carry forward (compact handoff note).
The runtime image bakes all eleven extensions in at build time. Credentials come from .env. The build also pulls in pinned Chromium + Playwright via the browser plugin (~400MB on top of bookworm-slim). See AUDIT_LOG.md for the per-plugin audit verdicts that gate every release.
cp .env.example .env
# Fill in ANTHROPIC_API_KEY (or another provider — see below),
# OASIS_TELEGRAM_BOT_TOKEN, OASIS_TELEGRAM_CHAT_ID
make rebuild # first boot: builds image, starts container
make healthz # → {"ok":true}
make logs # tail gateway logs
make smoke # plugin registration smoke test (mock API, no live LLM)| Target | What it does | When to use |
|---|---|---|
make restart |
Restart gateway process | After openclaw config set changes |
make recreate |
Recreate container | After .env changes (creds, cortex swap) |
make rebuild |
Rebuild image + recreate | After Dockerfile or entrypoint changes |
make smoke |
Plugin smoke test | After plugin code changes |
make token |
Print gateway auth token | Needed for direct API calls |
make healthz |
Authenticated healthz probe | Verify gateway is up |
make assets-list |
Per-bot avatar inventory (size, dims, hash) | Checking which face each bot wears |
make assets-set BOT=x AVATAR=f.png |
Swap a bot's avatar + reload | Giving a bot a new face (add RELOAD=0 to defer) |
make assets-show BOT=x |
Open a bot's current avatar | Eyeballing before/after a swap |
Each bot's face is an image under workspace/avatars/, referenced from the
- **Avatar:** avatars/<file> line in its IDENTITY.md. The
scripts/claw-assets CLI (wrapped by the assets-*
targets above) keeps the three copies of that fact consistent:
- Running bot — writes the image into the live volume as the
nodeuser (the container dropsCAP_DAC_OVERRIDE, so a plaindocker cplands files the gateway can't read), repointsIDENTITY.mdif the file extension changed, and restarts the gateway so the persona reload takes effect. - Stopped persona bot — stages the image +
IDENTITY.mdedit into the gitignoredbots/<name>/workspace/overlay instead; the nextmake -C bots up BOT=<name>/seedapplies it. Nimbus has no overlay — its volume is the source of record. - Telegram profile photo —
scripts/claw-assets telegram-photo <bot>validates the current avatar against Telegram's limits (square, ≥512px, <5 MB, static) and hands off to BotFather/setuserpic; the Bot API cannot set a bot's own photo, so that last click stays manual.
scripts/claw-assets theme <bot> probes the pinned openclaw build for
theme/appearance config keys and reports honestly when there are none (the
current pin exposes no UI theming — personality lives in the avatar plus
IDENTITY.md/SOUL.md).
Two interfaces ship with the runtime. Neither needs a messaging account, so both work when a chat channel is unavailable — on a locked-down corporate host, for example.
openclaw chat opens a terminal UI against the gateway. It is a full two-way
conversation: the same agent, sessions, tools, and reviewer prompts you get from
a chat channel.
Most bots in this fleet publish no host port — they sit on an internal network with the egress proxy as their only exit. So run the TUI inside the container:
docker exec -it oasis-claw-kolmogorov openclaw chatPodman is identical — swap the command:
podman exec -it oasis-claw-kolmogorov openclaw chatUseful flags (openclaw tui --help for the rest):
| Flag | Use |
|---|---|
--session <key> |
Pick a conversation thread. Defaults to main. |
--message "<text>" |
Send one message on connect — good for scripting. |
--history-limit <n> |
How much backlog to load. Defaults to 200. |
--thinking <level> |
Override the reasoning effort for this session. |
--url / --token |
Attach to a remote gateway rather than the local one. |
--local |
Bypass the gateway and run an embedded agent. Rarely what you want. |
Leave with Ctrl-C. The session persists — reconnecting resumes it.
openclaw dashboard prints the Control UI URL and opens a browser:
docker exec oasis-claw-runtime openclaw dashboard --no-openThe gateway serves it at http://127.0.0.1:<published-port>/, and it is
token-authenticated. Inside a container there is no browser to hand the token
to, so append it yourself as a URL fragment:
http://127.0.0.1:18789/#token=<gateway-token>
Get the token with make BOT=<name> token, or read
/home/node/.openclaw/.gateway-token in the container.
The Control UI gives you chat, session history, channel status, model and plugin configuration, and the approval queue — approving a reviewer escalation here is usually easier than over a chat channel.
Only bots with a published port are reachable this way. In this fleet that is deliberate: sandboxed bots publish nothing, so their Control UI is unreachable from the host by design. Use the TUI for those, or publish a loopback port for that bot if you want the browser.
A cookbook for the tasks that come up most. BOT=<name> selects the bot;
make list shows the names.
| Task | Command |
|---|---|
| Start everything | make up-all |
| Start or restart one bot | make BOT=house up / make BOT=house restart |
Pick up an .env or role.yaml change |
make BOT=house recreate |
| Pick up a Dockerfile or extension change | make BOT=house rebuild |
| Watch what a bot is doing | make BOT=house logs |
| Health and plugin count | make BOT=house status |
| Shell inside a bot | make BOT=house shell |
| Talk to a bot | docker exec -it oasis-claw-house openclaw chat |
| Stop everything, keep data | make down |
| Which engine am I on? | make engine |
Reading further:
- Model not answering?
make BOT=<name> logsfirst. A provider error, an expired key, and a reviewer denial look nothing alike in the log. - Tool calls all failing? Check the reviewer.
OASIS_REVIEWER_L2=enforcewith an unreachable judge model fails closed — every call is denied. This has bitten this fleet before. - Bot can't reach a host? It is the egress proxy allowlist, not DNS. Each sandboxed bot exits through one proxy with an explicit host list.
- Changed a plugin?
rebuild, notrecreate. Extensions are baked into the image at build time. - Changed the reviewer policy? It is baked in too. If you iterate on it often, bind-mount the host copy read-only over the baked path instead — same file, no image rebuild, and the agent gains no extra reach.
The gateway is provider-agnostic. The active language model is a single config value — swap it without touching plugin code, rebuilding the image, or rewriting prompts. Works the same whether you're running locally on a MacBook or deployed to EC2.
Set OPENCLAW_DEFAULT_MODEL in .env and run make recreate. The entrypoint writes it into openclaw.json on every boot.
# Anthropic (default)
ANTHROPIC_API_KEY=sk-ant-...
OPENCLAW_DEFAULT_MODEL=anthropic/claude-sonnet-4-6
# Google Gemini
GEMINI_API_KEY=...
OPENCLAW_DEFAULT_MODEL=gemini/gemini-2.0-flash
# also: gemini/gemini-2.5-pro gemini/gemini-2.5-flash
# OpenAI
OPENAI_API_KEY=...
OPENCLAW_DEFAULT_MODEL=openai/gpt-4o
# also: openai/o3 openai/o4-mini openai/gpt-4o-mini
# Amazon Bedrock (IAM credentials, no API key)
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=us-east-1
OPENCLAW_DEFAULT_MODEL=bedrock/anthropic.claude-sonnet-4-5-v1:0
# also: bedrock/amazon.nova-pro-v1:0 bedrock/meta.llama3-70b-instruct-v1:0
# Ollama — fully local, runs on the Mac host
# Pull the model first: `ollama pull llama3.3`
# No API key. compose already wires host.docker.internal → host gateway.
OPENCLAW_DEFAULT_MODEL=ollama/llama3.3
# also: ollama/qwen3 ollama/mistral ollama/deepseek-r1Live swap (no recreate needed if the API key is already in the container):
make shell
openclaw config set agents.defaults.model.primary "gemini/gemini-2.0-flash"
exit
make restartThe .env.example in the repo documents all four priority providers with every model string and required credential.
Most of what we ship is our own code. But for capabilities that already exist upstream (the browser plugin is the canonical example — Chromium+Playwright with auth, SSRF guards, profiles, ~39k LOC), forking is the wrong shape and an unaudited npm install is worse. Our policy:
- Vendor, don't fork. We
cp -Rthe upstream plugin intoextensions/<id>/and record the source commit inextensions/<id>/UPSTREAM. The vendored tree is buildable as-is; any local modifications live as numbered patches inextensions/<id>/patches/and are also applied tosrc/so there's a single working tree. - Three layered pins, separately bumpable. For plugins with binary dependencies, we pin (a) the openclaw plugin source SHA, (b) any JS library version (e.g.
playwright-core), and (c) the binary blob revision (e.g. Chromium revision). A CVE at any one layer can be addressed without touching the others. The Dockerfile asserts the pinned binary path exists at build time so a silently-republished build trips the build, not a deploy. - Audit before merge. Every vendored plugin is fed to
clawhub-skill-auditin--inspectmode (Opus 4.7, multi-turn, with budgeted file inspection from the plugin source tree). Verdicts ofpass/warn/blockand the full inspection trail are written undervendor/sandbox-skill-audit/_meta/<id>.audit-verdict.json. Server-side rule: any high-severity unaudited path or medium+ auditability finding caps the verdict atwarnand blockspass. For thebrowserplugin we ran four targeted audit slices (broad / auth / SSRF / evaluate / AI-loop) to drivepct_visibleabove 70% on the security-sensitive surface. The verdict files and the runbook are in AUDIT_LOG.md. - Findings become entrypoint config, not aspirational docs. The browser audit found that upstream defaults
evaluateEnabled = true. The fix isn't a doc note; it's a literal line inscripts/runtime-entrypoint.shthat writesbrowser.evaluateEnabled = falseintoopenclaw.jsonon every boot. Removing that line requires writing the per-session opt-in + audit-log patches first (CLAW-015). - Refresh, don't auto-merge. The CLAW-016 weekly job (
scripts/refresh-browser-plugin.sh)rsyncs upstream into a scratch tree, replays our patches, re-runs the audit, and either opens a PR (success) or an issue (conflict / new high-severity finding). The refresh job is the only mechanism by which upstream code reaches a deployed image — until the PR merges, ourextensions/browser/stays at the pinned SHA. That property is load-bearing: it converts "supply-chain drift" into "a normal commit on top, gated by an audit." - Upstream-bumps publish to the same log. Every audit verdict, refresh PR, and CVE response gets a timestamped row in AUDIT_LOG.md. It's the single place to see what's been audited, when, by what model, with what verdict, and what mitigations are in force.
When a Chromium CVE drops in the gap window between Google's patch and Playwright's release, our mitigations in priority order are: (a) tighten navigation-guard allowlist via patch + rebuild, (b) flip enabledByDefault: false as a kill switch (voice/messaging stay up; only browser-tool agents are affected), (c) build our own Chromium tarball as escalation. The runbook lives in extensions/browser/UPSTREAM.
Agent-callable report_injection tool. The model invokes it when it detects what it believes is a prompt-injection attempt. The plugin:
- Appends a signed JSONL entry to the attack log (
~/.openclaw/logs/attacks/) - Emits a Telegram alert to the operator chat (if configured)
- Returns acknowledgement to the model so it can continue with hardened behaviour
The cross-cutting adversarial.test.ts (22 tests) lives here as the end-to-end backstop for the security feature set.
AES-256-GCM at-rest secrets store. The agent never sees the plaintext after deposit — it gets an opaque handle that re-materializes only inside tool calls that explicitly request it:
deposit_secrettool — the model invokes this when the user pastes a credential- Redaction hook — runs before any history write so plaintext can't slip into JSONL transcripts
- Optional Telegram deposit confirmations
Human-in-the-loop approval surface:
forward_captchaagent tool — sends CAPTCHA images via Telegram and returns the operator's typed solution
Library code awaiting core integration (re-exported from the plugin entry):
loadApiApprovalPolicy,checkApiApproval,requestApiApproval,handlePotentialApiApprovalResponse— utility functions for HTTP request approval policy. These need to be invoked from openclaw's HTTP middleware layer; that integration point doesn't yet exist in vanilla upstream.
Browser navigation approvals are handled entirely by upstream's approvals.exec infrastructure — no plugin code required, just configuration. See extensions/approval-gate/README.md for the config recipe.
Append-only JSONL session transcripts hooked at llm_input, llm_output, and tool_call events. Includes the sandbox-isolation.test.ts invariant suite which verifies the JSONL writer never escapes its configured logDir even under adversarial path inputs.
Memory prompt supplement that injects the contents of .swarm/state.md, .swarm/queue.md, and any other configured peer files into the agent's memory section at session start. Registers via api.registerMemoryPromptSupplement — non-exclusive, so it coexists cleanly with memory-core, memory-lancedb, memory-wiki, and active-memory rather than competing for the kind: "memory" slot.
Also registers a swarm_read agent tool for explicit mid-session re-reads (when stigmergic state has been updated by a sibling agent or the operator).
Configuration:
{
"plugins": {
"entries": {
"dot-swarm": {
"swarmDir": "/path/to/repo/.swarm",
"includeFiles": ["state.md", "queue.md", "memory.md"],
"maxBytes": 32768,
"registerSwarmReadTool": true
}
}
}
}If swarmDir is omitted, the plugin probes $PWD/.swarm first and falls back to ~/.openclaw/.swarm. Tracks under oasis-x ORG-030.
Auto-runs a security audit against every newly installed clawhub skill. The plugin subscribes to openclaw's registerSkillsChangeListener (so it fires the moment clawhub install finishes) and also performs a debounced periodic filesystem scan (so first-boot skills and out-of-band installs aren't missed). Each newly-seen (skillId, contentHash) pair triggers a single Opus 4.7 audit:
- The skill's
SKILL.mdand all bundled.sh/.py/.js/.ts/.md/etc. files are wrapped in<<<FILE>>>sentinels and sent to the auditor as untrusted data - The auditor is forced to call an
emit_audittool, returning structured JSON (verdict,risk_score,summary,findings[]) - One immutable JSON record per audit is written to
~/.openclaw/logs/skill-audits/YYYY/MM/DD/<auditId>.json— never overwritten, never deleted by the plugin warn/blockverdicts trigger an operator Telegram alert if the bot creds are configuredblockverdicts optionally move the skill directory toquarantineDir(with aQUARANTINED.txtmarker pointing to the audit id)
The threat model targets recent supply-chain incidents in agent-skill registries: hidden prompt-injection in SKILL.md, credential harvesting (~/.ssh, ~/.aws, env dumps), exfiltration over webhooks/pastebin/DNS, persistence via cron/launchctl/rc files, destructive ops, curl|sh typosquats, and explicit attempts to disable other oasis-claw plugins (approval-gate, secrets-vault, prompt-injection-reporting).
Configuration:
{
"plugins": {
"entries": {
"clawhub-skill-audit": {
"anthropicApiKey": "...",
"auditModel": "claude-opus-4-7",
"skillsDirs": ["./skills", "~/.openclaw/skills"],
"auditLogDir": "~/.openclaw/logs/skill-audits",
"quarantineDir": "~/.openclaw/quarantine/skills",
"telegramBotToken": "...",
"telegramAlertChatId": "...",
"pollIntervalMs": 30000
}
}
}
}anthropicApiKey falls back to the ANTHROPIC_API_KEY env var if unset (so you don't have to copy the key into openclaw.json). auditModel defaults to claude-opus-4-7 — the strong audit model is the point of the plugin, only override for cost-trial. With quarantineDir unset, the plugin is audit-only: it will log and alert but never move files.
Private memory and shared coordination are deliberately separate concerns.
Private memory is the bundled upstream memory-core plugin — openclaw's default memory slot. It indexes MEMORY.md + memory/*.md and exposes memory_search / memory_get. oasis-claw enables its dreaming sweep (off in vanilla openclaw) so consolidation actually runs:
{
"plugins": {
"entries": {
"memory-core": {
"config": {
"dreaming": { "enabled": true, "frequency": "0 3 * * *", "timezone": "America/New_York" }
}
}
}
}
}The nightly light→REM→deep sweep ranks recent recalls, promotes durable ones into MEMORY.md, and writes a Dream Diary. Without it, recall works but memory never grows — the agent "forgets" recent work.
Shared coordination is dot-swarm — the .swarm/ stigmergy surface. Beyond injecting state.md + queue.md into the memory prompt, it provides the compact tool and the swarm-compact CompactionProvider: the agent writes a handoff note into .swarm/state.md, and at the context ceiling openclaw's compaction serves that note back instead of a generic summary. Activated by agents.defaults.compaction.provider: "swarm-compact", which the runtime entrypoint pins.
Each plugin reads its own block under plugins.entries in ~/.openclaw/openclaw.json. The runtime entrypoint (scripts/runtime-entrypoint.sh) merges these automatically from environment variables on each boot — you don't edit the JSON directly.
{
"plugins": {
"entries": {
"prompt-injection-reporting": {
"telegramBotToken": "...",
"telegramAlertChatId": "...",
"attackLogDir": "~/.openclaw/logs/attacks"
},
"secrets-vault": { "secretsDir": "~/.openclaw/state/secrets" },
"approval-gate": { "telegramBotToken": "...", "telegramChatId": "..." },
"session-history": { "logDir": "~/.openclaw/logs/history" },
"clawhub-skill-audit": {
"auditLogDir": "~/.openclaw/logs/skill-audits",
"skillsDirs": ["~/.openclaw/skills"]
}
}
}
}| Upstream | What it does | Our relationship |
|---|---|---|
src/infra/approval-handler-* |
Generic exec-approval routing (Telegram, Discord, Slack delivery channels) | approval-gate configures it via approvals.exec; previously had a stub browser-approvals.ts here that has been pruned |
src/security/external-content.ts SUSPICIOUS_PATTERNS |
Regex-based prompt injection detection on inbound external content | Complementary to prompt-injection-reporting (voluntary agent self-report); both run simultaneously |
extensions/active-memory/ |
Bounded blocking memory sub-agent that injects relevant memory into context | Adjacent to dot-swarm (which targets static .swarm/ file injection rather than sub-agent recall) |
extensions/memory-core/, memory-lancedb/, memory-wiki/ |
Pluggable memory backends | dot-swarm registers via registerMemoryPromptSupplement, not kind: "memory", so it's a peer not a competitor |
extensions/diagnostics-otel/ |
OpenTelemetry diagnostics export | Complementary to session-history JSONL writer; runs together |
extensions/telegram/ |
Full Telegram channel plugin (user conversations) | Different from our slim telegram.ts HTTP wrappers (operator alerts only) |
If upstream ships something that subsumes one of our extensions, prune ours when bumping the pin.
We tried the fork pattern twice (MikeHLee/hyperclaw, MikeHLee/oasis-claw-archive-2026-03). Both drifted thousands of commits behind upstream within a few months. The actual IP — a security plugin and an adversarial test suite — is small (≈7 commits of substance) and already plugin-shaped: it imports openclaw/plugin-sdk and registers via the standard register(api) surface. The fork was scaffolding around the plugin, not substance.
By keeping openclaw as a submodule and our plugins as first-class code in this repo:
- Upstream upgrades are a single tag bump in
.gitmodules— no merge conflicts on 7,000+ commits of unrelated changes - Upstream security fixes (gateway secret redaction, hello-ok auth, hook fallback bypass, session identity scoping) are inherited automatically on the next bump
- Our plugins ship as standard npm packages on top of any compatible openclaw release
- The container-native deployment story is cleaner: one Dockerfile, no patching
vendor/openclaw/ is pinned to v2026.4.26 (commit be8c24633a). The pin is deliberate: we bump it on a schedule, never automatically, so we control when upstream changes land.
cd vendor/openclaw
git fetch --tags
git checkout v2026.X.Y # whatever the new stable tag is
cd ../..
git add vendor/openclaw .gitmodules
git commit -m "chore: bump openclaw to v2026.X.Y"Before bumping, audit the upstream changelog at vendor/openclaw/CHANGELOG.md for changes that affect:
- Plugin SDK surface (
packages/plugin-sdk/,src/plugins/hook-types.ts) — would require updates to our plugins'register()signatures src/security/external-content.ts— adjacent toprompt-injection-reportingsrc/infra/approval-handler-*— whatapproval-gatere-exports as library code targets- Any new extensions that overlap with what we ship; prune ours if upstream is now better
oasis-claw/
vendor/openclaw/ # git submodule, pinned to v2026.4.26
extensions/
prompt-injection-reporting/ # report_injection tool + signed attack log + Telegram alert
secrets-vault/ # AES-256-GCM at-rest store + deposit_secret + redaction hook
approval-gate/ # forward_captcha tool + API approval library code
session-history/ # append-only JSONL transcripts + sandbox-isolation invariants
dot-swarm/ # .swarm/ stigmergy: prompt supplement + swarm_read/compact tools + swarm-compact CompactionProvider
clawhub-skill-audit/ # Opus 4.7 auto-audit of newly installed skills + JSON audit trail
model-switcher/ # setmodel tool + /setmodel slash; hot-swap LLM provider mid-session
oasis-voice/ # speech + realtime-STT provider (Piper TTS + Moonshine STT lite tier)
browser/ # VENDORED: openclaw browser plugin (Chromium/Playwright); evaluate off-by-default
AUDIT_LOG.md # per-plugin audit verdict log; gates every release
scripts/
runtime-entrypoint.sh # mints token, links plugins, merges config, execs gateway
smoke-runner.mjs # plugin-registration smoke test (mock API, no live gateway)
archive/
hyperclaw-fork-patches/ # the 7 commits from the deprecated fork, kept as patches
Dockerfile.runtime # full runtime image: openclaw + tsx + sharp + Chromium + our 10 plugins
docker-compose.runtime.yml # loopback-only port binding, cap_drop ALL, non-root
Makefile # restart / recreate / rebuild / logs / healthz / smoke
.env.example # all provider keys + OPENCLAW_DEFAULT_MODEL documented
README.md
LICENSE # MIT, matching upstream openclaw
git clone --recurse-submodules https://github.com/oasis-main/oasis-claw.git
cd oasis-claw
pnpm install
pnpm testThis repo follows the standard oasis-x branch model: push experiments freely to dev, merge to main requires approval AND green CI. Three workflows in .github/workflows/:
| Workflow | Trigger | Blocks merge? | What it does |
|---|---|---|---|
test.yml |
PR + push to dev/main |
Yes (on main) |
pnpm -r run test + plugin smoke + per-extension tsc --noEmit |
image-build.yml |
PR touching Dockerfile / entrypoint / extensions | No (informational) | Builds the runtime image, asserts the pinned Chromium revision lands, posts image size + pin status to the PR summary |
refresh-browser.yml |
Weekly cron (Mon 09:00 UTC) + manual | N/A (opens its own PR/issue) | Runs scripts/refresh-browser-plugin.sh — refreshes the vendored browser plugin against upstream openclaw, replays our patches, re-runs the audit cohort live, opens a PR on success or an issue on conflict/regression |
Live LLM audits run on refresh only. Routine PRs trust the pre-PR audit cohort that gated the change; re-auditing on every PR would burn API credits without adding coverage we don't already have. The refresh job is the choke point through which upstream code reaches our deployed image — until its PR merges, extensions/browser/ stays at the SHA recorded in extensions/browser/UPSTREAM.
Notifications are GitHub-native (PR comments, issue mentions, branch-protection failure emails). Telegram is reserved for user/operator workflows (prompt-injection-reporting, approval-gate, clawhub-skill-audit) and is not used for CI.
Branch protection on main (set in repo settings, not in YAML):
- Required status checks:
test(all three jobs) - Required reviews: 1
- Linear history, no direct pushes
devstays unprotected — fix on the fly
Secrets the workflows need:
ANTHROPIC_API_KEY— onlyrefresh-browser.ymluses this
MIT, matching upstream openclaw. See LICENSE.
This repo replaces two earlier fork attempts:
MikeHLee/hyperclaw(deleted 2026-04-29)MikeHLee/oasis-claw-archive-2026-03(archived in place)
The 7 substantive commits from the hyperclaw-security branch are preserved as patches under archive/hyperclaw-fork-patches/. The architecture decision to drop the fork is documented in oasis-x/.swarm/state.md under the 2026-04-29 handoff note.