# openclaw.net
**Repository Path**: devai/openclaw.net
## Basic Information
- **Project Name**: openclaw.net
- **Description**: Self-hosted OpenClaw gateway + agent runtime in .NET (NativeAOT-friendly)
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-03-13
- **Last Updated**: 2026-03-13
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# OpenClaw.NET
[](https://opensource.org/licenses/MIT)
> **Disclaimer**: This project is not affiliated with, endorsed by, or associated with [OpenClaw](https://github.com/openclaw/openclaw). It is an independent .NET implementation inspired by their excellent work.
Self-hosted OpenClaw.NET gateway + agent runtime in .NET (NativeAOT-friendly).
## Docs
- [Quickstart Guide](QUICKSTART.md) — Fast local setup, runtime mode selection, and first usage flow.
- [Tool Guide](TOOLS_GUIDE.md) — Detailed setup for all 18+ native tools.
- [Hardening Profiles](TOOLS_GUIDE.md#copypaste-hardening-profiles) — Copy/paste dev/staging/prod security setups.
- [User Guide](USER_GUIDE.md) — Core concepts, providers, tools, skills, and channels.
- [Startup Architecture Notes](docs/architecture-startup-refactor.md) — Current bootstrap/composition/profile/pipeline layout.
- [Security Guide](SECURITY.md) — Mandatory reading for public deployments.
- [Changelog](CHANGELOG.md) — Tracked project changes.
- [Docker Hub Overview](DOCKERHUB.md)
Published container images:
- `ghcr.io/clawdotnet/openclaw.net:latest`
- `tellikoroma/openclaw.net:latest`
- `public.ecr.aws/u6i5b9b7/openclaw.net:latest`
## Architecture
OpenClaw.NET now separates gateway startup and runtime composition into explicit layers instead of a single large startup path.
Startup flow:
1. `Bootstrap/`
- Loads config, resolves runtime mode, applies validation and hardening, and handles early exits such as `--doctor`.
2. `Composition/` and `Profiles/`
- Registers services and applies the effective runtime lane: `aot` for trim-safe deployments or `jit` for expanded compatibility.
3. `Pipeline/` and `Endpoints/`
- Wires middleware, channel startup, workers, shutdown handling, and the grouped HTTP/WebSocket surfaces.
Runtime flow:
- The Gateway handles HTTP, WebSocket, webhook, auth, policy, and observability concerns.
- The Agent Runtime owns reasoning, tool execution, memory interaction, and skill loading.
- Native tools run in-process.
- Upstream-style plugins run through the Node.js bridge, with capability enforcement depending on the effective runtime lane.
- Pure `SKILL.md` packages remain independent of the plugin bridge.
```mermaid
graph TD
Client[Web UI / CLI / Companion / WebSocket Client] <--> Gateway[Gateway]
Webhooks[Telegram / Twilio / WhatsApp / Generic Webhooks] --> Gateway
subgraph Startup
Bootstrap[Bootstrap]
Composition[Composition]
Profiles[Runtime Profiles]
Pipeline[Pipeline + Endpoints]
Bootstrap --> Composition --> Profiles --> Pipeline
end
subgraph Runtime
Gateway <--> Agent[Agent Runtime]
Agent <--> Tools[Native Tools]
Agent <--> Memory[(Memory + Sessions)]
Agent <-->|Provider API| LLM{LLM Provider}
Agent <-->|Bridge transport| Bridge[Node.js Plugin Bridge]
Bridge <--> JSPlugins[TS / JS Plugins]
end
```
Runtime lanes:
- `OpenClaw:Runtime:Mode="aot"` keeps the trim-safe, low-memory lane.
- `OpenClaw:Runtime:Mode="jit"` enables expanded bridge surfaces and native dynamic plugins.
- `OpenClaw:Runtime:Mode="auto"` selects `jit` when dynamic code is available and `aot` otherwise.
For the full startup-module breakdown, see [docs/architecture-startup-refactor.md](docs/architecture-startup-refactor.md).
## Quickstart (local)
See [QUICKSTART.md](QUICKSTART.md) for the fastest path from zero to a running gateway.
The shortest local path is:
1. Set your API key:
- `export MODEL_PROVIDER_KEY="..."`
- Optional workspace: `export OPENCLAW_WORKSPACE="$PWD/workspace"`
- Optional runtime selection: `export OpenClaw__Runtime__Mode="jit"`
2. Run the gateway:
- `dotnet run --project src/OpenClaw.Gateway -c Release`
- Or validate config first: `dotnet run --project src/OpenClaw.Gateway -c Release -- --doctor`
- Optional config file: `dotnet run --project src/OpenClaw.Gateway -c Release -- --config ~/.openclaw/config.json`
3. Use one of the built-in clients:
- Web UI: `http://127.0.0.1:18789/chat`
- WebSocket endpoint: `ws://127.0.0.1:18789/ws`
- CLI: `dotnet run --project src/OpenClaw.Cli -c Release -- chat`
- Companion app: `dotnet run --project src/OpenClaw.Companion -c Release`
Environment variables for the CLI:
- `OPENCLAW_BASE_URL` (default `http://127.0.0.1:18789`)
- `OPENCLAW_AUTH_TOKEN` (only required when the gateway enforces auth)
For advanced provider setup, webhook channels, and deployment hardening, see the [User Guide](USER_GUIDE.md) and [Security Guide](SECURITY.md).
## Usage
Common local usage paths:
- Browser UI: start the gateway and open `http://127.0.0.1:18789/chat`
- CLI chat: `dotnet run --project src/OpenClaw.Cli -c Release -- chat`
- One-shot CLI run: `dotnet run --project src/OpenClaw.Cli -c Release -- run "summarize this README" --file ./README.md`
- Desktop companion: `dotnet run --project src/OpenClaw.Companion -c Release`
- Doctor/report mode: `dotnet run --project src/OpenClaw.Gateway -c Release -- --doctor`
Common runtime choices:
- Use `aot` when you want the trim-safe mainstream lane.
- Use `jit` when you need expanded plugin compatibility or native dynamic plugins.
- Leave `auto` if you want the artifact to choose based on dynamic-code support.
The most practical local setup is:
- Web UI or Companion for interactive usage
- CLI for scripting and automation
- `--doctor` before exposing a public bind or enabling plugins
## Companion app (Avalonia)
Run the cross-platform desktop companion:
- `dotnet run --project src/OpenClaw.Companion -c Release`
Notes:
- For non-loopback binds, set `OPENCLAW_AUTH_TOKEN` on the gateway and enter the same token in the companion app.
- If you enable “Remember”, the token is saved to `settings.json` under your OS application data directory.
## WebSocket protocol
The gateway supports **both**:
### Raw text (legacy)
- Client sends: raw UTF-8 text
- Server replies: raw UTF-8 text
### JSON envelope (opt-in)
If the client sends JSON shaped like this, the gateway replies with JSON:
Client → Server:
```json
{ "type": "user_message", "text": "hello", "messageId": "optional", "replyToMessageId": "optional" }
```
Server → Client:
```json
{ "type": "assistant_message", "text": "hi", "inReplyToMessageId": "optional" }
```
## Internet-ready deployment
### Authentication (required for non-loopback bind)
If `OpenClaw:BindAddress` is not loopback (e.g. `0.0.0.0`), you **must** set `OpenClaw:AuthToken` / `OPENCLAW_AUTH_TOKEN`.
Preferred client auth:
- `Authorization: Bearer `
Optional legacy auth (disabled by default):
- `?token=` when `OpenClaw:Security:AllowQueryStringToken=true`
Built-in WebChat auth behavior:
- The `/chat` UI connects to `/ws` using `?token=` from the Auth Token field.
- For Internet-facing/non-loopback binds, set `OpenClaw:Security:AllowQueryStringToken=true` if you use the built-in WebChat.
- Tokens are stored in `sessionStorage` by default. Enabling **Remember** also stores `openclaw_token` in `localStorage`.
### TLS
You can run TLS either:
- Behind a reverse proxy (recommended): nginx / Caddy / Cloudflare, forwarding to `http://127.0.0.1:18789`
- Directly in Kestrel: configure HTTPS endpoints/certs via standard ASP.NET Core configuration
If you enable `OpenClaw:Security:TrustForwardedHeaders=true`, set `OpenClaw:Security:KnownProxies` to the IPs of your reverse proxies.
### Hardened public-bind defaults
When binding to a non-loopback address, the gateway **refuses to start** unless you explicitly harden (or opt in to) the most dangerous settings:
- Wildcard tooling roots (`AllowedReadRoots=["*"]`, `AllowedWriteRoots=["*"]`)
- `OpenClaw:Tooling:AllowShell=true`
- `OpenClaw:Plugins:Enabled=true` or `OpenClaw:Plugins:DynamicNative:Enabled=true` (third-party plugin execution)
- WhatsApp official webhooks without signature validation (`ValidateSignature=true` + `WebhookAppSecretRef` required)
- WhatsApp bridge webhooks without a bridge token (`BridgeTokenRef` / `BridgeToken` required)
- `raw:` secret refs (to reduce accidental secret commits)
To override (not recommended), set:
- `OpenClaw:Security:AllowUnsafeToolingOnPublicBind=true`
- `OpenClaw:Security:AllowPluginBridgeOnPublicBind=true`
- `OpenClaw:Security:AllowRawSecretRefsOnPublicBind=true`
### Tooling warning
This project includes local tools (`shell`, `read_file`, `write_file`). If you expose the gateway publicly, strongly consider restricting:
- `OpenClaw:Tooling:AllowShell=false`
- `OpenClaw:Tooling:AllowedReadRoots` / `AllowedWriteRoots` to specific directories
### WebSocket origin
If you are connecting from a browser-based client hosted on a different origin, configure:
- `OpenClaw:Security:AllowedOrigins=["https://your-ui-host"]`
If `AllowedOrigins` is not configured and the client sends an `Origin` header, the gateway requires same-origin.
## Plugin Ecosystem Compatibility 🔌
OpenClaw.NET now exposes two explicit runtime lanes:
- `OpenClaw:Runtime:Mode="aot"` keeps the low-memory, trim-safe lane. Supported plugin capabilities here are `registerTool()`, `registerService()`, plugin-packaged skills, and the supported manifest/config subset.
- `OpenClaw:Runtime:Mode="jit"` enables the expanded compatibility lane. In this mode the bridge also supports `registerChannel()`, `registerCommand()`, `registerProvider()`, and `api.on(...)`, and the gateway can load JIT-only in-process native dynamic plugins.
Pure ClawHub `SKILL.md` packages are independent of the bridge and remain the most plug-and-play compatibility path.
When you enable `OpenClaw:Plugins:Enabled=true`, the Gateway spawns a Node.js JSON-RPC bridge. When you enable `OpenClaw:Plugins:DynamicNative:Enabled=true`, the gateway also loads JIT-only in-process .NET plugins through the native dynamic host.
Across both lanes, unsupported surfaces fail fast with explicit diagnostics instead of silently degrading:
- `registerGatewayMethod()`
- `registerCli()`
- any JIT-only capability when the effective runtime mode is `aot`
The `/doctor` report includes per-plugin load diagnostics, and the repo now includes hermetic bridge tests plus a pinned public smoke manifest for mainstream packages. For the exact matrix and TypeScript requirements such as `jiti`, see **[Plugin Compatibility Guide](COMPATIBILITY.md)**.
## Semantic Kernel interop (optional)
OpenClaw.NET is not a replacement for Semantic Kernel. If you're already using `Microsoft.SemanticKernel`, OpenClaw can act as the **production gateway/runtime host** (auth, rate limits, channels, OTEL, policy) around your SK code.
Supported integration patterns today:
- **Wrap your SK orchestration as an OpenClaw tool**: keep SK in-process, expose a single "entrypoint" tool the OpenClaw agent can call.
- **Host SK-based agents behind the OpenClaw gateway**: use OpenClaw for Internet-facing concerns (WebSocket, `/v1/*`, Telegram/Twilio/WhatsApp), while your SK logic stays in your app/tool layer.
More details and AOT/trimming notes: see `SEMANTIC_KERNEL.md`.
Conceptual example (tool wrapper):
```csharp
// Your tool can instantiate and call Semantic Kernel. OpenClaw policies still apply
// to *when* this tool runs, who can call it, and how often.
public sealed class SemanticKernelTool : ITool
{
public string Name => "sk_example";
public string Description => "Example SK-backed tool.";
public string ParameterSchema => "{\"type\":\"object\",\"properties\":{\"text\":{\"type\":\"string\"}},\"required\":[\"text\"]}";
public async ValueTask ExecuteAsync(string argumentsJson, CancellationToken ct)
{
// Parse argsJson, then run SK here (Kernel builder + plugin invocation).
throw new NotImplementedException();
}
}
```
Notes:
- **NativeAOT**: Semantic Kernel usage may require additional trimming/reflection configuration. Keep SK interop optional so the core gateway/runtime remains NativeAOT-friendly.
Available:
- `src/OpenClaw.SemanticKernelAdapter` — optional adapter library that exposes SK functions as OpenClaw tools.
- `samples/OpenClaw.SemanticKernelInteropHost` — runnable sample host demonstrating `/v1/responses` without requiring external LLM access.
## Telegram Webhook channel
### Setup
1. Create a Telegram Bot via BotFather and obtain the Bot Token.
2. Set the auth token as an environment variable:
- `export TELEGRAM_BOT_TOKEN="..."`
3. Configure `OpenClaw:Channels:Telegram` in `src/OpenClaw.Gateway/appsettings.json`:
- `Enabled=true`
- `BotTokenRef="env:TELEGRAM_BOT_TOKEN"`
- `MaxRequestBytes=65536` (default; inbound webhook body cap)
### Webhook
Register your public webhook URL directly with Telegram's API:
- `POST https://api.telegram.org/bot/setWebhook?url=https:///telegram/inbound`
Notes:
- The inbound webhook path is configurable via `OpenClaw:Channels:Telegram:WebhookPath` (default: `/telegram/inbound`).
- `AllowedFromUserIds` currently checks the numeric `chat.id` value from Telegram updates (not the `from.id` user id).
## Twilio SMS channel
### Setup
1. Create a Twilio Messaging Service (recommended) or buy a Twilio phone number.
2. Set the auth token as an environment variable:
- `export TWILIO_AUTH_TOKEN="..."`
3. Configure `OpenClaw:Channels:Sms:Twilio` in `src/OpenClaw.Gateway/appsettings.json`:
- `Enabled=true`
- `AccountSid=...`
- `AuthTokenRef="env:TWILIO_AUTH_TOKEN"`
- `MessagingServiceSid=...` (preferred) or `FromNumber="+1..."` (fallback)
- `AllowedFromNumbers=[ "+1YOUR_MOBILE" ]`
- `AllowedToNumbers=[ "+1YOUR_TWILIO_NUMBER" ]`
- `WebhookPublicBaseUrl="https://"` (required when `ValidateSignature=true`)
- `MaxRequestBytes=65536` (default; inbound webhook body cap)
### Webhook
Point Twilio’s inbound SMS webhook to:
- `POST https:///twilio/sms/inbound`
Recommended exposure options:
- Reverse proxy with TLS
- Cloudflare Tunnel
- Tailscale funnel / reverse proxy
### Security checklist
- Keep `ValidateSignature=true`
- Use strict allowlists (`AllowedFromNumbers`, `AllowedToNumbers`)
- Do not set `AuthTokenRef` to `raw:...` outside local development
## Webhook body limits
Inbound webhook payloads are hard-capped before parsing. Configure these limits as needed:
- `OpenClaw:Channels:Sms:Twilio:MaxRequestBytes` (default `65536`)
- `OpenClaw:Channels:Telegram:MaxRequestBytes` (default `65536`)
- `OpenClaw:Channels:WhatsApp:MaxRequestBytes` (default `65536`)
- `OpenClaw:Webhooks:Endpoints::MaxRequestBytes` (default `131072`)
For generic `/webhooks/{name}` endpoints, `MaxBodyLength` still controls prompt truncation after request-size validation.
If `ValidateHmac=true`, `Secret` is required; startup validation fails otherwise.
## Docker deployment
### Quick start
1. Set required environment variables:
**Bash / Zsh:**
```bash
export MODEL_PROVIDER_KEY="sk-..."
export OPENCLAW_AUTH_TOKEN="$(openssl rand -hex 32)"
```
**PowerShell:**
```powershell
$env:MODEL_PROVIDER_KEY = "sk-..."
$env:OPENCLAW_AUTH_TOKEN = [Convert]::ToHexString((1..32 | Array { Get-Random -Min 0 -Max 256 }))
$env:EMAIL_PASSWORD = "..." # (Optional) For email tool
```
> **Note**: For the built-in WebChat UI (`http://:18789/chat`), enter this exact `OPENCLAW_AUTH_TOKEN` value in the "Auth Token" field. WebChat connects with a query token (`?token=`), so on non-loopback binds you must also set `OpenClaw:Security:AllowQueryStringToken=true`. Tokens are session-scoped by default; check **Remember** to persist across browser restarts. If you enable the **Email Tool**, set `EMAIL_PASSWORD` similarly.
# 2. Run (gateway only)
docker compose up -d openclaw
# 3. Run with automatic TLS via Caddy
export OPENCLAW_DOMAIN="openclaw.example.com"
docker compose --profile with-tls up -d
```
### Build from source
```bash
docker build -t openclaw.net .
docker run -d -p 18789:18789 \
-e MODEL_PROVIDER_KEY="sk-..." \
-e OPENCLAW_AUTH_TOKEN="change-me" \
-v openclaw-memory:/app/memory \
openclaw.net
```
### Published images
The same multi-arch image is published to:
- `ghcr.io/clawdotnet/openclaw.net:latest`
- `tellikoroma/openclaw.net:latest`
- `public.ecr.aws/u6i5b9b7/openclaw.net:latest`
Example pull:
```bash
docker pull ghcr.io/clawdotnet/openclaw.net:latest
```
### Push to a registry (Docker Hub, GHCR, or ECR Public)
Multi-arch push (recommended):
```bash
docker buildx build --platform linux/amd64,linux/arm64 \
-t ghcr.io/clawdotnet/openclaw.net:latest \
-t ghcr.io/clawdotnet/openclaw.net: \
-t tellikoroma/openclaw.net:latest \
-t tellikoroma/openclaw.net: \
-t public.ecr.aws/u6i5b9b7/openclaw.net:latest \
-t public.ecr.aws/u6i5b9b7/openclaw.net: \
--push .
```
The Dockerfile uses a multi-stage build:
1. **Build stage** — full .NET SDK, runs tests, publishes NativeAOT binary
2. **Runtime stage** — Ubuntu Chiseled (distroless), ~23 MB NativeAOT binary, non-root user
### Volumes
| Path | Purpose |
|------|---------|
| `/app/memory` | Session history + memory notes (persist across restarts) |
| `/app/workspace` | Mounted workspace for file tools (optional) |
## Production hardening checklist
- [ ] Set `OPENCLAW_AUTH_TOKEN` to a strong random value
- [ ] Set `MODEL_PROVIDER_KEY` via environment variable (never in config files)
- [ ] Use `appsettings.Production.json` (`AllowShell=false`, restricted roots)
- [ ] Enable TLS (reverse proxy or Kestrel HTTPS)
- [ ] Set `AllowedOrigins` if serving a web frontend
- [ ] Set `TrustForwardedHeaders=true` + `KnownProxies` if behind a proxy
- [ ] Set `MaxConnectionsPerIp` and `MessagesPerMinutePerConnection` for rate limiting
- [ ] Set `OpenClaw:SessionRateLimitPerMinute` to rate limit inbound messages (also applies to `/v1/*` OpenAI-compatible endpoints)
- [ ] Monitor `/health` and `/metrics` endpoints
- [ ] Pin a specific Docker image tag (not `:latest`) in production
## TLS options
### Option 1: Caddy reverse proxy (recommended)
The included `docker-compose.yml` has a Caddy service with automatic HTTPS:
```bash
export OPENCLAW_DOMAIN="openclaw.example.com"
docker compose --profile with-tls up -d
```
Caddy auto-provisions Let's Encrypt certificates. Edit `deploy/Caddyfile` to customize.
If you want the gateway to trust `X-Forwarded-*` headers from your proxy, set:
- `OpenClaw__Security__TrustForwardedHeaders=true`
- `OpenClaw__Security__KnownProxies__0=`
### Option 2: nginx reverse proxy
```nginx
server {
listen 443 ssl http2;
server_name openclaw.example.com;
ssl_certificate /etc/letsencrypt/live/openclaw.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/openclaw.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:18789;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
### Option 3: Kestrel HTTPS (no reverse proxy)
Configure directly in `appsettings.json`:
```json
{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://0.0.0.0:443",
"Certificate": {
"Path": "/certs/cert.pfx",
"Password": "env:CERT_PASSWORD"
}
}
}
}
}
```
### Memory retention (opt-in)
OpenClaw now supports a background retention sweeper for persisted **sessions + branches**.
- Default is off: `OpenClaw:Memory:Retention:Enabled=false`
- Expired items are archived as raw JSON before delete
- Default TTLs: sessions `30` days, branches `14` days
- Default archive retention: `30` days
Example config:
```json
{
"OpenClaw": {
"Memory": {
"Retention": {
"Enabled": true,
"RunOnStartup": true,
"SweepIntervalMinutes": 30,
"SessionTtlDays": 30,
"BranchTtlDays": 14,
"ArchiveEnabled": true,
"ArchivePath": "./memory/archive",
"ArchiveRetentionDays": 30,
"MaxItemsPerSweep": 1000
}
}
}
}
```
Recommended rollout:
1. Run a dry-run first: `POST /memory/retention/sweep?dryRun=true`
2. Review `GET /memory/retention/status` and `/doctor/text`
3. Confirm archive path sizing and filesystem permissions
Compaction remains disabled by default. If enabling compaction, `CompactionThreshold` must be greater than `MaxHistoryTurns`.
### Observability & Distributed Tracing
OpenClaw natively integrates with **OpenTelemetry**, providing deep insights into agent reasoning, tool execution, and session lifecycles.
| Endpoint | Auth | Description |
|----------|------|-------------|
| `GET /health` | Token (if non-loopback) | Basic health check (`{ status, uptime }`) |
| `GET /metrics` | Token (if non-loopback) | Runtime counters (requests, tokens, tool calls, circuit breaker state, retention runs/outcomes) |
| `GET /memory/retention/status` | Token (if non-loopback) | Retention config + last run state + persisted session/branch counts |
| `POST /memory/retention/sweep?dryRun=true|false` | Token (if non-loopback) | Trigger an immediate retention sweep (manual admin action) |
### Structured logging
All agent operations emit structured logs and `.NET Activity` traces with correlation IDs. You can export these to OTLP collectors like Jaeger, Prometheus, or Grafana:
```
[abc123def456] Turn start session=ws:user1 channel=websocket
[abc123def456] Tool browser completed in 1250ms ok=True
[abc123def456] Turn complete: Turn[abc123def456] session=ws:user1 llm=2 retries=0 tokens=150in/80out tools=1
```
Set log levels in config:
```json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"AgentRuntime": "Debug",
"SessionManager": "Information"
}
}
}
```
## CI/CD
GitHub Actions workflow (`.github/workflows/ci.yml`):
- **On push/PR to main**: build + test
- **On push to main**: publish NativeAOT binary artifact + Docker image to GitHub Container Registry
## Contributing
Looking for:
- Security review
- NativeAOT trimming improvements
- Tool sandboxing ideas
- Performance benchmarks
If this aligns with your interests, open an issue.
⭐ If this project helps your .NET AI work, consider starring it.