openclaw/docs/tools
Ayaan Zaidi 8fd72da7f2
feat(agents): let owners hand keys, config, and skill edits to their agent in chat (#158120)
## What Problem This Solves

Fixes: owners who ask their agent in chat to change a key, a config value, or one of their own skills get refused, sent to a dashboard, or told to file a Workshop proposal. Examples: "you can't post API keys here", switching the embeddings provider routed to the web-search wizard, only proposals for handwritten skills.

## User Impact

An owner can hand the agent an API key or token in chat, ask it to change config such as the embeddings provider, and have it edit skills they own. Session permission modes are unchanged: Full Access applies, restricted sessions ask.

- **Keys from chat.** Masked setup flows still keep keys out of model context and remain the default. If the user already pasted a key or token, the agent stores it in the shared secret store and points the config key at it with a `store` SecretRef instead of refusing. It never echoes the value back. The pasted message already reached the model provider and transcript; redaction covers later logs and output only, and the docs say so.
- **Existing store entries are never touched.** Each save inserts a new entry named after the config key plus a random suffix (`GATEWAY_REMOTE_TOKEN_9B139B5E231299BC`). Nothing is overwritten, revived, or deleted, and a new name can never match anything already pointing into the store, including a stale reference to a removed and purged entry. Replacing a key leaves its previous entry for `openclaw secrets store rm`. Rotating a key keeps its configured store provider alias, and the audit records the alias actually used.
- **Embeddings.** The agent now treats the memory embeddings provider, model, and key as `memory.search.*` config, not the web-search setup wizard.
- **Skills.** When the user asks, the agent edits skills they own directly: repository skill source, workspace `skills/`, project `.agents/skills/`, and configured extra skill directories. Bundled, ClawHub-installed, and plugin-provided skills are replaced by their owners' updates. For those, the agent says so and offers to capture the change as a Workshop skill.
- **Tone.** The "never request / paste credentials in chat" lines are gone from the `openclaw` tools and system-agent prompts. "Never echo secret values" stays, and so do factual pointers for flows that genuinely need a UI: channel sign-in, provider OAuth/accounts, and model onboarding.

No config option, schema, or protocol change. The Full Access permission-policy floor from the first revision moved to #158142 for its own security review.

## Why This Change Was Made

After #149870, approved config writes may target any path. What still blocked owners was model-facing text telling the agent to refuse credentials, plus the missing ability to store a chat-provided value anywhere but plaintext config.

`config_set_ref` gains an optional `secret` argument (read without trimming; only emptiness is checked). With it, the system agent:

1. registers the value for redaction when the proposal is built;
2. keeps the key's existing store provider alias when it has one;
3. sends one `secrets.writeForConfigRef` command to the SQLite state worker with the requester's live-authority guard. The host re-checks that guard at the worker's transaction and commit admission (`createSqliteWorkerWriteAdmission`), so a run stopped while the command is queued writes nothing. The transaction inserts a new row under a freshly minted `NAME_<16 random hex>`;
4. writes the ref through the existing config writer, which re-checks authority. If that write fails (before or after the writer commits), OpenClaw rereads the config and the error says the key was saved as `<NAME>` and whether the config key points at it. There is no automatic delete: another consumer may have linked the fresh entry, or the writer may have committed before failing;
5. the normal config reload picks up the new ref, since its id always changes.

Nothing new runs SQLite on the Gateway main thread.

<details>
<summary>Out of scope / follow-ups</summary>

- Found while proving this: in Full Access, after a delegated change applies, the next agent turn in the same chat fails with `SQLite database already belongs to another worker backend`. It reproduces on unmodified `origin/main` (`71bb516`) with a `logging.level` change followed by one more message. This PR does not fix it.
- Built-in provider sign-in and model onboarding stay handoffs; they own live verification of the active inference route.
- Other secret-store set/delete paths remain synchronous migration debt, as `worker-access.md` already records.

</details>

## Evidence

Real Telegram Test Server (Convex-leased userbot, fresh Gateway, QA mock provider, Full Access, tester is owner), first revision:

| | Screenshot |
|---|---|
| Token given in chat, applied with no approval prompt and no refusal (synthetic QA token) | ![User sends a remote Gateway token and asks to save it; the agent replies without refusing or asking for approval](https://github.com/user-attachments/assets/4f68f3db-225c-4047-984d-ed0cff79cd0c) |

### Final effects at this head

qa-channel scenario `system-agent-owner-trust` passes through a real Gateway and state worker. The Gateway is seeded with an unrelated `GATEWAY_REMOTE_TOKEN` entry, then:

1. A command-allowed **non-owner** (`bob`) sends the key. The `openclaw` tool is owner-only.
2. The **owner** (`alice`, Full Access) sends it.

Captured step details (redacted by the Gateway; the store ref id prints as `__OPENCLAW_REDACTED__`):

```json
{
  "nonOwnerEntryNames": ["GATEWAY_REMOTE_TOKEN"],
  "storedRef": { "source": "store", "provider": "default", "id": "__OPENCLAW_REDACTED__" },
  "storeEntryNames": ["GATEWAY_REMOTE_TOKEN", "GATEWAY_REMOTE_TOKEN_9B139B5E231299BC"]
}
```

- After the non-owner turn: only the seeded entry exists and `gateway.remote.token` is unset.
- After the owner turn: `gateway.remote.token` is a `store` SecretRef, the token is in its own minted entry (`GATEWAY_REMOTE_TOKEN_9B139B5E231299BC`), and the seeded entry's `updatedAt`/`updatedBy` are unchanged. No approval prompt was posted, and the token is absent from chat, config, and the store listing.

**Revoked request**, through the production worker (Node main thread, real broker, `writeSecretStoreEntryForConfigRef`). The requester's guard passes the caller's check, then reports the run stopped:

```text
seeded: [ 'GATEWAY_REMOTE_TOKEN (cli)' ]
revoked request rejected: requesting run is no longer active
after revoked request: [ 'GATEWAY_REMOTE_TOKEN (cli)' ]
owner request saved as: GATEWAY_REMOTE_TOKEN_F1657B971F691824
after owner request: [ 'GATEWAY_REMOTE_TOKEN (cli)', 'GATEWAY_REMOTE_TOKEN_F1657B971F691824 (openclaw)' ]
seeded value intact: true
```

Tests (each fails without the behavior it covers):
- production worker path (`secret-store-config-ref.worker.test.ts`, forked database-worker lane with the real broker): a chat secret gets its own minted entry beside a live `GATEWAY_REMOTE_TOKEN` without touching it; a requester revoked after the caller's check writes nothing;
- store kernel: a refusal at commit admission rolls the transaction back; each save mints a new `NAME_<hex>` and leaves the key's previous entry unchanged; a stale name whose entry was removed and purged still resolves to nothing after a chat save;
- operations: stored and referenced with no value in output or audit; authority gone before the store write writes nothing; a failed config write names the saved entry and leaves it in place; rotating a key keeps its configured store provider alias, and the audit records it;
- tool: proposes a store write without repeating the key, preserving leading and trailing whitespace.

Measured single-worker wall time per new or materially changed test file at this head (`node scripts/run-vitest.mjs run <file>`, local M-series; vitest Duration includes import and setup):

| File | Tests | Wall | Vitest duration |
|---|---:|---:|---:|
| `src/secrets/store/secret-store-config-ref.worker.test.ts` (new, database-worker lane) | 2 | 14 s | 2.05 s |
| `src/secrets/store/secret-store.test.ts` | 32 | 16 s | 13.37 s |
| `src/system-agent/operations.test.ts` | 43 | 18 s | 15.30 s |
| `src/agents/tools/system-agent-tool.test.ts` | 36 | 15 s | 12.89 s |

QA scenarios: `system-agent-owner-trust` (mock-openai) runs in about 27 s after build; `skill-owner-direct-edit-live` is live-frontier only and took about 3 min with `claude-cli/claude-sonnet-4-6`.

Wording pins for the removed lecture text were deleted. The focused store, worker, exclusivity, operations, tool, approval, and delegate suites pass. `node scripts/check-changed.mjs` passes every gate except core lint, which fails only on three files this PR does not touch (`server-chat-metadata-lifecycle.integration.test.ts`, `session-companion-ask.ts`, `app-sidebar-session-list-render.ts` over `max-lines` on the base); oxlint on the changed files is clean.

Security decision: a Full Access owner's pasted key goes to the Gateway-wide team store without a separate approval. Maintainer (@obviyus) accepted this in the PR conversation.

**Rotation with a second consumer**, through the production worker (Node main thread, real broker). A second consumer references the key's first entry before the next save lands; value fingerprints only:

```text
owner saves key #1 -> MODELS_PROVIDERS_OPENAI_API_KEY_6D96F6E92B59E935 (sha256:4a5c5a4aa8de)
second consumer now references MODELS_PROVIDERS_OPENAI_API_KEY_6D96F6E92B59E935 (e.g. linked while the next save is queued)
owner saves key #2 -> MODELS_PROVIDERS_OPENAI_API_KEY_4066F18ABAAE6903 (sha256:28bc4e3fe10d)
second consumer's entry MODELS_PROVIDERS_OPENAI_API_KEY_6D96F6E92B59E935 after rotation: sha256:4a5c5a4aa8de
unchanged: true
```

**Config write fails after the save, with a second consumer on the fresh entry**, through the production worker and the system-agent apply path (fingerprints only):

```text
owner result: Saved the secret as GATEWAY_REMOTE_TOKEN_6B68C031E571F81C, but could not point gateway.remote.token at it: config write failed after commit (rollbackStatus: not-restored). Retry, or remove the entry with `openclaw secrets store rm GATEWAY_REMOTE_TOKEN_6B68C031E571F81C`.
config gateway.remote.token: null
second consumer's entry GATEWAY_REMOTE_TOKEN_6B68C031E571F81C: sha256:09ae5b4fd36b
second consumer keeps the credential: true
```

**Stale reference to a removed and purged entry**, production worker for purge and save:

```text
purged rows: 1
stale ref GATEWAY_REMOTE_TOKEN after purge: SECRET_STORE_NOT_FOUND
chat save -> GATEWAY_REMOTE_TOKEN_8F698B5396990ADD resolves (value hidden)
stale ref GATEWAY_REMOTE_TOKEN after chat save: SECRET_STORE_NOT_FOUND
```

**Owned-skill edit with a live model.** New scenario `skill-owner-direct-edit-live` (live-frontier; run with `claude-cli/claude-sonnet-4-6`, subscription auth) passes at this head. It seeds workspace skill `qa-owner-greeting` replying `OWNER-GREETING-V1`, and the owner asks in plain words: "Please change my qa-owner-greeting skill so it replies OWNER-GREETING-V2 instead of OWNER-GREETING-V1." Captured result:

Skill file after the turn:

```markdown
---
name: qa-owner-greeting
description: Greets the owner with a fixed marker
---
When the user asks for the owner greeting, reply with exactly: OWNER-GREETING-V2
```

Agent reply: "Let me find the skill file. Done. The `qa-owner-greeting` skill now replies `OWNER-GREETING-V2` instead of `OWNER-GREETING-V1`."

The model edited the skill file in place and confirmed it; it did not refuse or file a Workshop proposal.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-09-26 16:20:43 +05:30
..
acp-agents fix(acp): allow harness resets from read-only installations (#158551) 2026-09-25 22:04:19 -07:00
browser docs: fix 12 concrete defects from the ux audit remainder (#144128) 2026-09-25 17:28:14 +08:00
code-mode fix(agents): show readable purposes for Code Mode execution (#157150) 2026-09-24 12:22:44 +00:00
skill-workshop feat(agents): let owners hand keys, config, and skill edits to their agent in chat (#158120) 2026-09-26 16:20:43 +05:30
subagents fix(agents): deliver yielded batches independently of older children (#158642) 2026-09-26 10:57:40 +05:30
tts docs(tts): document channel voice-note conversion requirements (#144084) 2026-09-25 17:14:51 +08:00
acp-agents-setup.md docs: fix 12 concrete defects from the ux audit remainder (#144128) 2026-09-25 17:28:14 +08:00
acp-agents.md
agent-send.md
apply-patch.md fix: simplify guarded file operations and stream model verification (#154201) 2026-09-20 18:53:00 -07:00
ask-user.md docs: fix 12 concrete defects from the ux audit remainder (#144128) 2026-09-25 17:28:14 +08:00
brave-search.md
browser-control.md docs: fix 12 concrete defects from the ux audit remainder (#144128) 2026-09-25 17:28:14 +08:00
browser-linux-troubleshooting.md
browser-login.md
browser-wsl2-windows-remote-cdp-troubleshooting.md
browser.md
btw.md fix(codex): unify turn activation and cleanup ownership (#151753) 2026-09-18 08:00:13 -07:00
chrome-extension.md fix(browser): keep tabs usable while Chrome Web Store is open (#156890) 2026-09-26 12:29:43 +05:30
code-execution.md feat(xai): add Grok 4.7 support (#155379) 2026-09-21 20:27:04 -07:00
code-mode.md fix(agents): show readable purposes for Code Mode execution (#157150) 2026-09-24 12:22:44 +00:00
creating-skills.md
custodian-skills.md
diffs.md
duckduckgo-search.md
elevated.md
exa-search.md
exec-approvals-advanced.md
exec-approvals.md feat(tools): explain terminal access restrictions (#157014) 2026-09-24 02:53:23 -05:00
exec.md fix(codex): honor configured tool PATH in native commands (#157290) 2026-09-25 08:26:05 +08:00
firecrawl.md
gemini-search.md
goal.md fix(webchat): keep failed sends out of newer Goal drafts (#158382) 2026-09-25 22:20:21 +00:00
grok-search.md fix(search): cancel xAI searches waiting on credentials (#154559) 2026-09-21 14:40:55 +05:30
image-generation.md
index.md feat: edit personal instructions on multi-user gateways (#155256) 2026-09-23 05:55:12 -07:00
kimi-search.md
llm-task.md
lobster.md chore(deps): refresh dependencies with seven-day cutoff (#149908) 2026-09-16 09:06:40 -07:00
loop-detection.md perf(agents): unblock session lanes after no-progress loops (#158428) 2026-09-25 16:40:14 -07:00
mcp.md perf(mcp): back off failed bundle server starts (#158088) 2026-09-25 14:25:53 +00:00
media-overview.md
minimax-search.md
multi-agent-sandbox-tools.md refactor: retire pre-June config and upgrade test support (#156859) 2026-09-23 19:26:23 -07:00
music-generation.md
ollama-search.md
parallel-search.md feat(search): configure providers and verify search in Settings (#154135) 2026-09-21 03:51:42 -07:00
pdf.md fix: PDF analysis rejects available OpenAI credentials with Codex enabled (#156929) 2026-09-23 19:27:45 -07:00
permission-modes.md
perplexity-search.md
plugin.md refactor: retire the TaskFlow Webhooks plugin (#158225) 2026-09-25 20:29:15 -07:00
progress-card.md fix(ui): prevent task progress clipping below chat questions (#152665) 2026-09-23 18:19:18 +08:00
reactions.md
screen.md feat: open Crabbox apps beside the conversation (#152094) 2026-09-18 17:12:51 -07:00
searxng-search.md
secrets.md fix: preserve background command proxy access across turns (#152557) 2026-09-19 01:44:16 -07:00
self-learning.md fix: exclude incognito turns from automatic memory capture (#155594) 2026-09-23 17:53:28 +08:00
show-widget.md fix: restore Mac presence context and Canvas media playback (#153381) 2026-09-19 21:37:59 -07:00
skill-workshop.md
skills-config.md
skills.md perf(skills): preserve canonical sources in worktree sessions (#158110) 2026-09-25 13:19:24 +00:00
slash-commands.md fix(approvals): let OpenClaw change approvals complete in chat (#157947) 2026-09-25 15:05:57 +05:30
steer.md
subagents.md
swarm.md perf(swarm): give collector groups independent execution lanes (#158616) 2026-09-25 21:08:50 -07:00
tavily.md
theme.md feat(themes): support plugin hats and critter artwork (#154935) 2026-09-21 10:30:47 -07:00
thinking.md feat: extend Ultra harness mode across supported runtimes (#155393) 2026-09-23 15:56:42 +08:00
tokenjuice.md
tool-search.md feat: run Code Mode on Node or isolated QuickJS (#154522) 2026-09-21 17:33:04 -07:00
trajectory.md
tts.md
video-generation.md
web-fetch.md
web.md feat(search): configure providers and verify search in Settings (#154135) 2026-09-21 03:51:42 -07:00