OpenMacaw β TODO
Based on a full codebase audit (March 2026). Items marked π΄ are high priority.
OpenMacaw β TODO
Based on a full codebase audit (March 2026). Items marked π΄ are high priority.
π΄ UI/UX β Chat Sidebar Overflow
- Add a Chat History page (
/chat/history) β paginated list of all sessions, like Google AI Studio. The sidebar should only show the most recent sessions (e.g. last 10β20). - Add a "View all chats β" link at the bottom of the sidebar that navigates to
/chat/history - Chat History page: search by title, date filter, bulk delete
- Sidebar "Older" group: collapse by default, show count badge + "View all" link once it exceeds ~30 sessions
General Chat UX
- Message branching / alternate responses (DB already has
parentId+isActiveβ UI needs to expose a branching selector) - Keyboard shortcut for new chat (
Ctrl/Cmd + N) - Conversation export to Markdown (currently only JSON)
- Session folders β
folderIdcolumn exists in schema but UI has no folder create/manage UI - Search messages within a session
- Voice input (Web Speech API)
π΄ Multi-Tenancy / Isolation
Current state: Sessions and messages are scoped to
userId. MCP servers, permissions, pipelines, and the audit log are global (shared across all users) β this causes bleed between accounts.
Per-User MCP Servers
- Add nullable
userIdtoserverstable βNULL= workspace-wide (admin-managed), non-null = private to that user - Server CRUD API must filter by
userIdfor non-admin requests - Admin panel: expose workspace-shared servers separately from user-private ones
- MCP Catalog: "Install as private (just me) or shared (workspace)?" prompt for admins
Per-User Pipelines
- Add
userIdtopipelinestable; scope pipeline CRUD to owner - Admins see all pipelines; users see only their own
Per-User Audit Log
- Add
userIdtoactivity_logtable (denormalized fromsessionId β sessions.userId) -
/api/activitymust filter byuserIdfor non-admin users; admins get a global view with user filter
Config Clarity
- Settings page: clearly label which settings are workspace-wide vs. personal
π΄ Open WebUI Compatibility
Implementing the Open WebUI API surface allows OpenMacaw to interoperate with Open WebUI frontends and tools.
Tools API
-
GET /api/v1/toolsβ list tools in Open WebUI format -
POST /api/v1/toolsβ register a custom tool -
DELETE /api/v1/tools/:id - Map Open WebUI tool calls β MCP server calls through the PermissionGuard
Functions API
-
GET/POST /api/v1/functionsβ pipe, filter, and action function types - Pipe: intercepts/transforms messages before/after LLM
- Filter: pre/post hooks on every message
- Action: button-triggered tool call from the UI
Prompts API
-
GET/POST/PUT/DELETE /api/v1/promptsβ saved prompt templates - Slash-command trigger in chat input (e.g.
/summarize) - Template variables:
{{USER_MESSAGE}},{{CLIPBOARD}}, etc. - Add
promptstable to DB schema
Models API
-
GET /api/v1/modelsβ unified model list (Anthropic + OpenAI + Ollama) with capability flags (vision,tool_use,json_mode)
Misc
-
POST /api/v1/chat/completionsβ OpenAI-compatible completions endpoint - Bearer token auth on
/api/v1/*routes
π Security
Cross-referenced against OpenClaw CVEs (2026) and Invariant Labs MCP Tool Poisoning research (Apr 2025), MCP Safety Audit paper (arXiv:2504.03767), and MCP-Scan vulnerability categories. Status: β = mitigated, β οΈ = partial/gap, π΄ = not mitigated.
π΄ WebSocket Authentication Missing (analog: CVE-2026-25253 "ClawJacked")
OpenClaw's critical ClawJacked flaw was an unauthenticated WebSocket that trusted any local connection and reflected auth tokens. In OpenMacaw:
-
/ws/chathas no authentication check βchat.tsroute registers the WebSocket handler without verifying the JWT. Any client that can open a WS connection can send messages and execute agent runs against arbitrary sessions. AddjwtVerify()on the WebSocket upgrade request before accepting the connection. - No
Originheader validation on WebSocket β browsers allow cross-origin WebSocket connections tolocalhost. A malicious page can connect to a local OpenMacaw instance. Add an allowlistedOrigincheck (e.g. same host header) on the WS upgrade. -
/api/chat-testendpoint is unauthenticated β the HTTP test endpoint atchat.ts:191callsgetSession()and runs the full agent without anyjwtVerify(). This is a backdoor that bypasses all auth. Either remove this endpoint or gate it behind auth + dev-only env flag. (Removed entirely.)
β οΈ JWT Stored in localStorage (XSS Token Exfiltration)
OpenClaw's ClawJacked attack exfiltrated tokens from LocalStorage. OpenMacaw has the same architecture:
- JWT stored in
localStorage(AuthContext.tsx:25) β readable by any JavaScript on the page (XSS, malicious browser extension, injected script). Consider migrating toHttpOnlycookie storage so the token is inaccessible to JS. If localStorage is kept, add a__Host-prefixed cookie as a secondary CSRF guard. -
openmacaw_userobject stored in localStorage β exposes user role, email, and profile data to any XSS. Store only the essential identity fragment or derive it from the token on the client.
β οΈ Login Rate Limit Not Persistent (Brute Force Risk)
OpenClaw had no rate limiting at all; OpenMacaw has in-memory rate limiting, but:
- Rate limit map is in-memory and lost on restart β
loginAttemptsmap inauth.tsresets every time the server restarts, allowing unlimited brute force attempts across restarts. Persist counts in Redis or SQLite, or use@fastify/rate-limitwith a store adapter. (Now SQLite-backed; survives restarts.) - Rate limiting only on
/api/auth/loginβ all other endpoints (register, password reset, API key endpoints) are unthrottled. Use@fastify/rate-limitglobally with per-route overrides.
β οΈ Log Poisoning / Indirect Prompt Injection (CVE-2026-2.13 analog)
OpenClaw allowed log poisoning via WebSocket β attacker writes to logs, agent reads logs, logs contain injected instructions:
- Console logs echo raw tool inputs and user messages β
chat.tslogsuserMessage.substring(0, 50)and tool names. If logs are fed back into the agent context (e.g. for debugging), this is an injection vector. Sanitize all user-controlled values before logging. (Removed.) - Tool result content is not sanitized before LLM injection β malicious file contents or web page responses returned from MCP servers flow directly into the LLM context. The PIP layer exists but is opt-in per server. Default PIP to ON for all new servers; make disabling it an explicit opt-out.
β οΈ SSRF via Web Fetch (CVE-2026-26322 analog)
OpenClaw's Gateway had a high-severity SSRF that let attackers proxy requests to internal services:
- Web fetch domain allowlist does not block internal IP ranges β even with the domain allowlist enabled, a crafted domain (e.g. via DNS rebinding) can resolve to
10.x.x.x,192.168.x.x,127.x.x.x, or169.254.x.x. Add an outbound SSRF guard: resolve the URL hostname and reject requests to RFC-1918 / loopback / link-local ranges before forwarding. (Implemented withdns.lookup()in evaluator.ts.) - No DNS rebinding protection on webfetch tools β validate the resolved IP at connection time, not just the hostname string. (Covered by same fix.)
β οΈ Command Injection via Args Parsing (CVE-2026-24763 analog)
OpenClaw had a command injection in Docker sandbox via unsafe PATH env var handling:
-
normalizeArgsfalls back to.split(' ')string splitting β inservers.ts:25:argsStr.split(' ').filter(Boolean). Space-splitting is unsafe for args containing quoted spaces. This can cause argument injection. Replace with a proper shell-words parser (shell-quote,shlex) or enforce JSON array format strictly. (Now usesshell-quote.) - No validation on MCP server
commandfield β the command field accepts any string. Validate that it matches an allowlist of known-safe executables (e.g.npx,node,python,uvx) and reject absolute paths to system binaries like/bin/sh. (validateCommand()added in servers.ts.)
β οΈ Malicious Catalog Package Installs (Malicious ClawHub Skills analog)
OpenClaw's marketplace was exploited with hundreds of malicious skill packages:
- Catalog installs use
npx -ywith no integrity verification βnpx -yauto-installs any npm package without prompting. A typosquatted or compromised package (e.g.@modelcontextprotocol/server-filesytem) installs and runs as the server process. Add npm package provenance verification (npm--provenanceflag) or compare package checksums against a pinned manifest. - No MCP server command sandboxing β installed MCP servers run with full user privileges. Consider running them in a restricted subprocess (seccomp/namespaces on Linux, or via Docker).
- No warning when installing community registry servers vs. curated servers β the UI should visually distinguish curated (vetted by OpenMacaw team) from official registry (unvetted third-party) servers with a clear risk label.
β / β οΈ Path Traversal (CVE-2026-26329 analog)
OpenClaw had browser-upload path traversal allowing writes outside intended directories:
- Path normalization exists β
evaluator.tsusesresolvePath()andrelativePath()to detect traversal. This correctly handles../sequences. - Symlink traversal not checked β
resolvePathresolves symlinks on the OS, but if an allowed path contains a symlink pointing outside the intended directory, the evaluator will approve it. Add arealpath()check that follows symlinks before comparing against allowed paths. (Fixed:resolveIncomingPathnow callsrealpathSync().) - Windows path separator normalization β
evaluator.ts:127replaces\with/for Windows paths, but this is done in the evaluator, not at the server command level. MCP server args containing Windows paths passed tonormalizeArgsare not normalized first.
β Auth Bypass (CVE-2026-25593 analog)
- JWT required on all REST routes β global auth middleware in
index.tsverifies JWT before any route handler. - Self-healing JWT on
/api/auth/meβ always re-reads role from DB, preventing stale JWT privilege escalation. - WebSocket still bypasses global auth middleware (see first section above β now fixed).
General Hardening
- Canary token leak detection β inject unique canary strings into tool results; alert if they appear in outbound webfetch/network requests
- Sandbox mode β wrap high-risk tool calls (bash, write, delete) in a container or VM
- Content-Security-Policy headers β add strict CSP to all HTML responses to limit XSS blast radius (Added
onSendhook inindex.tswith CSP + X-Frame-Options + X-Content-Type-Options + Referrer-Policy.) - Tool name collision protection β two MCP servers with the same tool name must not silently alias; return an error and require manual disambiguation
- Mask
isSecretenv var values in Servers UI (showβ’β’β’β’β’β’instead of plaintext) - Remove all
[DEBUG]console logs before any public/production release (auth.ts,chat.ts) (Done.) - Audit log tamper-evidence β HMAC-sign log entries so they cannot be silently edited
- Tool poisoning defense β strip or truncate excessively long tool
descriptionfields from MCP servers before injecting them into the LLM system prompt (tool metadata is an injection vector) (Fixed:toolSanitizer.tsstrips injection markers and caps descriptions at 2000 chars.)
π΄ MCP Tool Poisoning Attacks (Invariant Labs TPA β Apr 2025)
Invariant Labs demonstrated that malicious instructions hidden in MCP tool
descriptionfields are invisible to users but fully visible to the LLM. A poisonedaddtool can instruct the LLM to read~/.ssh/id_rsaand exfiltrate it via a hidden parameter. This affects all MCP hosts (Cursor, Claude Desktop, OpenClaw, OpenMacaw).
- π΄ Tool descriptions injected into LLM context unsanitized β
client.ts:loadTools()stores descriptions verbatim;anthropic.tsandopenai.tspasstool.descriptiondirectly to the provider API with no length cap, no sanitization, no injection pattern stripping. Implement: (a) hard length cap on descriptions (e.g. 2000 chars), (b) strip<IMPORTANT>,[SYSTEM],[INST], and other prompt injection markers from descriptions, (c) show full raw descriptions to the user in the Servers UI for manual review. (Fixed:toolSanitizer.tsenforces 2000-char cap and strips 30+ injection marker patterns from descriptions and schemas.) - π΄ Tool input schemas not validated against advertised schema β MCP tools can declare hidden parameters (e.g.
sidenote) that the LLM fills with exfiltrated data. The permission evaluator does not inspect or restrict which parameters the LLM populates. Add a schema-enforcement layer that rejects tool calls with unexpected parameters not in the approved schema. (Fixed:validateToolCallArgs()intoolSanitizer.tsrejects tool calls with undeclared parameters.) - No user visibility into full tool descriptions β the Servers UI shows tool names but not the full raw descriptions that the LLM sees. Add a "View raw description" expander per tool so users can inspect for hidden instructions.
π΄ MCP Rug Pulls / Tool Description Mutation (Invariant Labs β Apr 2025)
A malicious MCP server can initially advertise benign tool descriptions to pass user review, then silently change them on reconnect/restart to include poisoned instructions. No MCP host currently detects this.
- π΄ No tool pinning or description hashing β
client.ts:loadTools()replaces the tool list in memory on everyconnect()call with no comparison to previously-approved versions. Implement: (a) SHA-256 hash each tool's(name, description, inputSchema)on first registration, (b) on reconnect, compare hashes and alert the user if any tool changed, (c) require explicit re-approval for changed tools before they become available to the agent. - No
tools/list_changednotification handling β the MCP protocol supports server-initiated notifications when tools change. OpenMacaw does not listen for these. Implement the handler and trigger a re-hash + user alert.
π΄ Cross-Server Tool Shadowing (Invariant Labs β Apr 2025)
A malicious MCP server can inject instructions in its tool description that override behavior of tools from other trusted servers, even if the malicious tool is never called directly.
- Cross-server instruction isolation β tool descriptions from one MCP server can reference and modify behavior of tools from another server. Add per-server instruction isolation: (a) prepend each tool description with
[Server: <name>]context, (b) add system prompt instructions explicitly forbidding cross-server instruction following, (c) warn in the UI when a tool description mentions another server's tool names. - Tool description cross-reference scanning β scan new tool descriptions for references to other registered server names or tool names. Flag for user review if cross-references are detected.
π΄ Credential Theft via process.env Leakage (MCP Safety Audit β arXiv:2504.03767)
The MCP Safety Audit paper demonstrated credential theft attacks where MCP tools (e.g.
printEnvfrom the Everything server) expose environment variables containing API keys, and multi-server RADE attacks where stolen credentials are exfiltrated via Slack or web fetch tools.
- π΄
process.envspread to child MCP server processes βclient.ts:168passes...(process.env as Record<string, string>)to every spawned MCP server, exposingANTHROPIC_API_KEY,OPENAI_API_KEY,JWT_SECRET,DATABASE_URL, and all other host secrets to every MCP server process. This completely undermines the env var access control. Fix: construct a minimal env object containing only the server's declaredenvVars+ essential system vars (PATH,HOME,NODE_PATH), never spreadprocess.env. (Fixed: now only forwardsPATH,HOME,NODE_PATH,LANG,TERM,SHELL,USER,TMPDIR,TMP,TEMP, and XDG dirs + declared envVars.) -
envReadAllowedheuristic is trivially bypassable βevaluator.ts:141only checks for'env' in toolInput || 'environment' in toolInput. A tool using parameter names likevariables,environ,envVars,config_vars, orsystem_infobypasses this check entirely. Replace the keyword heuristic with a proper deny-by-default approach: inspect all tool results for patterns matching API key formats (sk-ant-*,sk-*,hf_*,ghp_*,xoxb-*, etc.). (Fixed: expanded to 18 env-related parameter name synonyms with case-insensitive matching.)
π΄ Secret Exfiltration via Tool Results / Outbound Args (MCP Safety Audit β arXiv:2504.03767)
RADE (Retrieval-Agent Deception) attacks demonstrated end-to-end credential theft: a poisoned file is read by an MCP tool, its contents (including exfiltration instructions) are injected into the LLM context, the LLM then uses another tool (web fetch, Slack, email) to send the stolen data to the attacker.
- π΄ No scanning of tool results for leaked secrets β tool results from MCP servers are passed directly into the LLM conversation context (
runtime.ts:536) with no scanning for credential patterns. Implement a secret-detection scanner that checks all tool results for patterns matching known API key formats and redacts them before LLM injection. (Fixed:secretScanner.tsscans and redacts 15+ credential patterns before injecting results into LLM context.) - π΄ No scanning of outbound tool call arguments for exfiltrated data β the LLM can pass stolen credentials as arguments to web fetch, email, or messaging tools. Add an outbound argument scanner in the PermissionGuard that checks tool call arguments for credential-like patterns and blocks/flags the call. (Fixed: outbound tools are scanned for credential patterns before execution; blocked if secrets detected.)
- No data-flow taint tracking between servers β data read by Server A's tools can be passed to Server B's tools without restriction. Implement per-server data boundaries: tag tool results with their source server ID and warn/block when data from one server flows to another server's outbound tools.
β οΈ WebSocket Hardening Gaps
- No
maxPayloadon WebSocket βapp.ts:52registers@fastify/websocketwith no options; defaults to ~100MBmaxPayload. SetmaxPayload: 1_048_576(1MB) to prevent memory exhaustion from oversized messages. (Fixed:maxPayload: 1_048_576set inapp.ts.) - No WebSocket message rate throttling β authenticated clients can flood the WebSocket with unlimited messages. Add per-connection message rate limiting (e.g. 10 messages/second).
- No CSRF token on WebSocket upgrade β the Origin check provides partial protection, but a dedicated CSRF token (generated at page load, required as query param on WS upgrade) would add defense-in-depth.
- WebSocket session ownership not enforced β
chat.ts:115callsgetSession(sessionId)withoutuserId, allowing any authenticated user to interact with any other user's session via WebSocket. PassuserIdfrom the JWT payload and verify ownership. (Fixed:getSession(sessionId, authenticatedUserId)now enforced on bothchatandregeneratemessage types.)
β οΈ Network / Deployment Hardening
- Bind host hardcoded to
0.0.0.0βindex.ts:24binds to all interfaces with no override. AddHOSTenv var (default127.0.0.1for non-Docker,0.0.0.0for Docker). Document in docker-compose.yml. (Fixed: usesHOSTenv var, defaults to127.0.0.1unlessDOCKERenv is set.) - Hardcoded JWT secret fallback β
app.ts:54uses'super-secret-openmacaw-key-change-me'whenJWT_SECRETis not set. In production this allows trivial token forgery. Generate a random secret on first run and persist it, or refuse to start without an explicitJWT_SECRET. (Fixed: production refuses to start withoutJWT_SECRET; dev uses random ephemeral secret.)
β οΈ API Key / Secret Storage
- API keys stored as plaintext in SQLite β
userSettings.valueandsettings.valuecolumns store ANTHROPIC_API_KEY, OPENAI_API_KEY as raw text. Implement AES-256-GCM encryption at rest (seeSECURITY_HARDENING.mdSection 8 for reference implementation). - MCP server env vars stored as plaintext JSON β
servers.env_varscolumn stores sensitive values (API tokens, credentials) as plaintext JSON strings. Encrypt before storing; decrypt only when spawning the server process. - API keys returned in REST API responses β settings endpoints may return raw API key values. Redact secrets in API responses (return
sk-ant-****instead of full key) except at the moment of initial save.
π€ Agent Capabilities
- Context compaction β summarize older messages when history grows large and continue seamlessly
- Parallel tool calls β execute multiple concurrent tool calls when the LLM requests them simultaneously
- Tool call retry on transient MCP failures (exponential backoff)
- Add Google Gemini as an LLM provider
- Add Mistral / Groq provider adapters
- Streaming token usage display (live counter during generation)
- Agent memory across sessions (native store or Memory MCP server integration)
ποΈ Infrastructure
- Versioned DB migrations β replace single-snapshot
migrate.tswith a proper migration runner (e.g.drizzle-kit migrate) -
GET /api/healthendpoint β DB status, MCP server statuses, uptime - Graceful shutdown β drain in-flight requests and MCP connections before exit
- Structured logging β replace
console.logwithpino(Fastify native) - Docker multi-arch build (
linux/amd64+linux/arm64) for Raspberry Pi / Apple Silicon - Optional PostgreSQL backend (Drizzle supports it β add a PG adapter + env toggle)
- S3/R2 storage for avatar images instead of base64-in-SQLite
π§ͺ Testing
- Unit tests for
PermissionGuardevaluator (path traversal, glob matching, trust policy edge cases) β 26 tests via Vitest - Integration tests for auth flows (register, login, rate limit, pending approval) β 13 tests via Vitest
- Integration tests for WebSocket JWT auth & origin validation β 10 tests via Vitest
- Integration tests for HTTP security headers (CSP, X-Frame-Options, etc.) β 6 tests via Vitest
- Unit tests for command injection prevention (
validateCommand,normalizeArgs) β 30 tests via Vitest - E2E test: start MCP server β chat message β tool call β approval β result
- Pipeline integration tests (Discord, Telegram mocks)
- Load test: WebSocket streaming under concurrent users
- Fuzz: tool inputs with path traversal payloads against the evaluator
π Developer Experience
- OpenAPI / Swagger spec at
/api/docs(auto-generated from Fastify schemas) -
CONTRIBUTING.mdβ setup guide, conventions, PR checklist -
CHANGELOG.mdwith versioned release notes - Dev seed script: demo users, servers, sessions for local testing
- Auto-generate env var docs from the Zod config schema
π§© Skills System
Current state: Agent behavior is defined entirely by the global/session system prompt and MCP server tool descriptions. There is no way to save, share, or reuse task-specific instruction sets across sessions. OpenClaw's "ClawHub Skills" solved this but allowed arbitrary command execution β OpenMacaw's approach is instruction-only, with all tool calls still gated through the PermissionGuard. Implementation order: Skills System β Scheduled Automation β Self-Improving Agent.
Database
- Add
skillstable β columns:id(UUID PK),name(text, unique per scope),description(text),instructions(text β markdown body),toolHints(JSON β suggested MCP tools the skill works best with),triggers(JSON β slash-command names, keyword patterns),userId(nullable FK βusers.idβNULL= workspace-global),isGlobal(boolean),enabled(boolean, default true),createdAt,updatedAt - Add
skill_versionstable β columns:id,skillId(FK),version(integer, auto-increment per skill),instructions(text β snapshot of instructions at this version),changedBy(FK βusers.id),changeNote(text, nullable),createdAtβ append-only history for rollback and audit
Agent Runtime Integration
- Skill loader in agent runtime β before each LLM call, query active skills for the current session/user and concatenate their
instructionsinto the system prompt (integration point:runtime.tswheresession.systemPromptis already injected). Skills append after the base system prompt, separated by---delimiters with skill name headers - Per-session skill activation β sessions can enable/disable individual skills; store as a JSON array of skill IDs on the session or in a
session_skillsjoin table - Tool hint injection β when a skill declares
toolHints, include a note in the system prompt suggesting the agent use those specific MCP tools for the skill's task - Slash-command trigger in chat input β if a skill declares triggers (e.g.
/summarize,/review), intercept the chat input, match against registered triggers, and prepend the skill's instructions to the user message before sending to the agent
API Routes (/api/skills)
-
GET /api/skillsβ list skills visible to the authenticated user (own + global); support?search=,?global=,?enabled=query filters -
POST /api/skillsβ create a skill; non-admins can only create personal skills (userId= self,isGlobal= false); admins can create global skills -
PUT /api/skills/:idβ update a skill; creates a newskill_versionsentry before overwritinginstructions -
DELETE /api/skills/:idβ soft-delete or hard-delete a skill; only the owner or an admin can delete -
GET /api/skills/:id/versionsβ list version history for a skill -
POST /api/skills/:id/revert/:versionIdβ revert a skill's instructions to a previous version
Web UI β Skills Management Page
- Skills page (
/skills) β paginated list of all accessible skills with search, filter by scope (personal / global), and enable/disable toggles - Skill editor β create/edit form with name, description, markdown instructions editor (with preview), tool hints multi-select (populated from registered MCP tools), trigger configuration
- Skill detail view β read-only view with version history timeline and diff viewer
- Session skill picker β in the chat UI, add a skill selector (popover or sidebar panel) to activate/deactivate skills for the current session
- Sidebar integration β show active skill count badge on the chat input area; click to manage
Import / Export
- Skill import from SKILL.md β parse markdown files with YAML frontmatter (
name,description,triggers,toolHints) into theskillstable; support drag-and-drop upload or file picker on the Skills page. Compatible with OpenClaw's SKILL.md format - Skill export to markdown β download a skill as a
.mdfile with YAML frontmatter for portability and version control - Bulk import from directory β scan a folder for
*.skill.mdfiles and batch-import
Scoping & Permissions
- Per-user vs workspace-global scoping β admins can create global skills visible to all users; regular users can only create personal skills scoped to their
userId - Skill visibility rules β users see their own skills + all enabled global skills; admins see everything with an owner filter
- Skill approval for global promotion β optional workflow: user submits a personal skill for global promotion β admin reviews and approves/rejects
Versioning
- Automatic version tracking β every update to a skill's
instructionsfield creates askill_versionsentry with the previous content, version number, and author - Diff viewer in UI β show side-by-side or inline diff between any two versions of a skill
- Rollback β one-click revert to a previous version (creates a new version entry pointing to the old content)
π§ Self-Improving Agent
Current state: The agent can use MCP tools but cannot create new skills, modify its own instructions, or install new capabilities. OpenClaw allowed skills to self-modify and execute arbitrary code β this led to the "Malicious ClawHub Skills" exploit chain. OpenMacaw's approach is security-hardened: all self-improvement actions are proposals requiring human approval, never silent mutations. Depends on: Skills System must be implemented first.
Internal Agent Tools
-
create_skilltool β the agent can call this to propose a new skill with a name, description, and instructions body. The proposal is surfaced to the user as an approval card in the chat UI (similar to tool call approval). On approval, the skill is persisted to theskillstable as a personal skill owned by the session's user. On denial, the proposal is discarded with optional feedback -
update_skilltool β the agent can propose modifications to an existing skill's instructions. Shows a diff in the approval card. On approval, creates a newskill_versionsentry and updates the skill. The agent must reference an existing skill by ID or name -
install_mcp_servertool β the agent can propose installing a new MCP server from the Catalog (reuses the existing one-click install flow from/api/registry). The proposal card shows the server name, package, description, and a security risk label (curated vs. community). On approval, the server is registered and started via the existingregistry.tsβservers.tsflow
Skill Suggestions
- Post-task skill suggestion β after completing a multi-step task (detected by agent step count or tool call diversity), the agent can suggest creating a reusable skill from the workflow it just performed. The suggestion includes a draft skill name, description, and distilled instructions
- Suggestion suppression β user can dismiss suggestions with "Don't suggest this again" (stored as a user preference) to avoid repetitive prompts
- Suggestion quality gate β only suggest skills when the workflow involved 3+ distinct tool calls or spanned 5+ agent steps, to avoid trivial suggestions
Safety & Approval
- All self-improvement actions require human-in-the-loop approval β the agent cannot silently create, modify, or delete skills. Every mutation surfaces an approval card in the chat UI with full details of the proposed change
- Approval card UI β rich approval cards for
create_skill,update_skill, andinstall_mcp_serverproposals, showing the full content/diff and approve/deny buttons with optional feedback textarea - Audit trail β all self-improvement proposals (approved and denied) are logged to the
activity_logwith action typeskill_proposal/server_install_proposal - Rate limiting β cap the number of self-improvement proposals per session (e.g. max 5 per session) to prevent agent loops that repeatedly propose skills
- No silent execution β unlike OpenClaw where skills could execute arbitrary commands, OpenMacaw skills are instruction-only. The agent's proposed skills go through the same PermissionGuard pipeline as all other tool calls β skills cannot bypass permission checks
β° Scheduled Automation
Current state: Agent runs are triggered by user messages (chat) or external platform messages (Discord/Telegram/LINE pipelines). There is no way to run the agent on a timer. Architecturally similar to pipelines β a background runner calling the agent runtime on a trigger β just timer-triggered instead of message-triggered. Depends on: Skills System (for skill-based schedules). Can be built in parallel with Skills for the prompt-only mode.
Database
- Add
schedulestable β columns:id(UUID PK),userId(FK βusers.id),sessionId(FK βsessions.id, nullable βNULL= create a new session per run),name(text),cronExpression(text β standard 5-field cron),skillId(FK βskills.id, nullable β if set, run this skill's instructions as the prompt),prompt(text β freeform prompt used whenskillIdis null),enabled(boolean, default true),lastRun(timestamp, nullable),nextRun(timestamp),status(text βidle/running/error),errorMessage(text, nullable),createdAt,updatedAt - Add
schedule_runstable β columns:id,scheduleId(FK),sessionId(FK β the session used for this run),startedAt,completedAt,status(text βsuccess/error/timeout),errorMessage(text, nullable),toolCallCount(integer),tokenUsage(JSON, nullable) β run history for debugging and monitoring
Scheduler Service
- Scheduler loop β a lightweight service (using
node-cronor a simplesetIntervalloop) that checksschedulestable for rows whereenabled = trueandnextRun <= now(). On match, enqueue the run and updatenextRunbased oncronExpression - Run execution β each scheduled run creates or reuses a session, injects the
prompt(or the referenced skill'sinstructions), and calls the agent runtime inautoExecutemode (same pattern as pipeline runs inpipelines/) - Concurrency control β only one run per schedule at a time; if a run is still in progress when the next trigger fires, skip and log a warning
- Timeout β configurable per-schedule timeout (default 5 minutes); kill the agent run if it exceeds the limit
- Error handling β on failure, set
status = 'error', storeerrorMessage, and optionally disable the schedule after N consecutive failures (configurable)
API Routes (/api/schedules)
-
GET /api/schedulesβ list schedules for the authenticated user; admins see all with user filter -
POST /api/schedulesβ create a schedule; validate cron expression, resolveskillIdif provided -
PUT /api/schedules/:idβ update a schedule; recalculatenextRunon cron change -
DELETE /api/schedules/:idβ delete a schedule and its run history -
POST /api/schedules/:id/runβ manually trigger an immediate run (bypasses cron timing) -
GET /api/schedules/:id/runsβ paginated run history with status, duration, token usage
Web UI β Schedules Management Page
- Schedules page (
/schedules) β list of all schedules with name, cron expression (human-readable), next run time, last run status, and enable/disable toggle - Schedule editor β create/edit form with name, cron builder (visual cron picker or freeform input with validation + human-readable preview), prompt textarea or skill selector dropdown, session reuse toggle, timeout configuration
- Run history view β per-schedule run log showing start/end times, status, error messages, token usage, and a link to the session used for each run
- Next run preview β show the next 5 scheduled fire times based on the cron expression for verification
Permissions & Security
- Owner-scoped execution β scheduled runs use the schedule owner's
userIdfor permission resolution, BYOK API key cascade (same as the existinguserSettingsβsettingsfallback), and session ownership - Security policy per schedule β configurable per schedule:
autoExecute(full autonomy β all tool calls execute without approval, same as pipeline mode) orqueueApproval(high-risk tool calls are queued for approval β the user reviews them on next visit to the Schedules page or via notification) - Notification on completion β optional webhook, email, or in-app notification when a scheduled run completes or errors
- Schedule CRUD scoped to owner β users can only manage their own schedules; admins can view/manage all
Integration with Skills
- Skill-based schedules β a schedule can reference a
skillIdinstead of a freeformprompt; on run, the skill's currentinstructionsare used as the agent prompt. This means updating the skill automatically updates all schedules that reference it - Quick-schedule from skill β on the Skills page, add a "Schedule this skill" action that pre-fills the schedule editor with the skill reference
Last updated: March 2026 β security section expanded with Invariant Labs TPA, MCP Safety Audit (arXiv:2504.03767), and full codebase audit findings
Related Documents
Valet V1 β Architecture & Implementation Plan
1. [Vision & Scope](#1-vision--scope)
Spotipy Types - Implementation Plan
A standalone type stub package for spotipy using Pydantic models generated from the official Spotify Web API OpenAPI schema.
Writing Effective Skills
What makes a skill actually work vs. being ignored or misapplied. Based on studying production skills across Claude Code (Superpowers, Trail of Bits, Anthropic's official plugins), Codex (babysit-pr, skill-creator, curated catalog), OpenClaw (55 bundled skills, 13,700+ community), and Cursor/Cline rule systems (BMAD-METHOD, RIPER-5, steipete/agent-rules).
AutoDoc Demo: Step-by-Step Walkthrough
> **Quick setup?** See the [main README](./README.md) - it takes 2 minutes.