# Prompt Customization (D-0f / D-0g)

OpenClaw assembles each agent's system prompt from ~30 numbered **sections** spread across 5 layers (platform, agent-type, identity, user, session). Each section emits prose at runtime, and most of that prose now lives in editable markdown files instead of being baked into TypeScript.

This doc covers how operators customize that prose — globally or per agent — without touching the gateway source.

## Quick reference

| You want to…                        | Where to edit                                         | Takes effect |
| ----------------------------------- | ----------------------------------------------------- | ------------ |
| Change boilerplate every agent sees | `<gateway>/src/agents/sections/prose/<layer>/<id>.md` | Next render  |
| Override prose for one agent        | `<workspaceDir>/prompts/<id>.md`                      | Next render  |
| Edit through the hub UI             | Click the **STATIC** pill in the Prompt tab           | Immediately  |
| See what files a section embeds     | Click the **FILE** pill in the Prompt tab             | Immediately  |

## The pill system

Each section in the hub's Prompt tab shows an origin pill:

- **STATIC** (gray) — prose owned by the section. Clickable → opens the editor.
- **FILE** (cyan) — content from workspace files. Clickable → opens a read-only inspector listing the embedded files.
- **GENERATED** (purple) — produced at request time from runtime state (skills, memory, workspace scan). Not editable.
- **CONFIG** (amber) — per-agent setting (personality, sandbox, permissions). Edit via the Settings tab, not the prose modal.
- **CUSTOM** (emerald) — user-authored YAML section from Phase 19 customisation.

### Scope toggle

When editing a STATIC section, the modal header shows two buttons:

- **Global** — writes to `<gateway-install>/dist/agents/sections/prose/<layer>/<id>.md`. Affects every agent.
- **Agent** — writes to `<workspaceDir>/prompts/<id>.md` for the currently-viewed agent only. Wins over global at render time.

Agent overrides only exist for sections that already have global prose. Deleting the agent file falls back to global.

## Scope precedence (load order)

The prose loader resolves files in this order on every render:

1. **Agent override**: `<workspaceDir>/prompts/<id>[.<variant>].md`
2. **Global**: `<gateway-install>/dist/agents/sections/prose/<layer>/<id>[.<variant>].md`
3. **Built-in templateText**: the seed string shipped with the section definition (rendered into the file by "Initialize from default")
4. **Section's inline TS render**: only for sections not yet externalized

The mtime cache invalidates whenever you save through the hub OR edit the file with another tool — no gateway restart needed.

## Variants

Some sections emit different prose under different conditions:

- `safety` has `safety.full.md` and `safety.minimal.md` (selected by `promptMode`)
- `silent-replies` has `.full` + `.minimal`
- `reactions` has `.minimal` + `.extensive`

The hub modal shows variant tabs. Markers:

- `○` — variant has no file yet (defaults to templateText if shipped, otherwise stays empty)
- `•` — variant has unsaved changes in this modal

## Mustache variables

Some sections inject runtime values via `{{var}}` placeholders. Examples:

- `silent-replies` uses `{{token}}` → resolves to `SILENT_REPLY_TOKEN`
- `docs` uses `{{docsPath}}` → resolves to the agent's docs root
- `reactions` uses `{{channel}}` → resolves to the active channel name

Missing variables render as empty string. Unknown placeholders pass through literally.

## Tier system

Sections fall into one of three tiers based on what their prose contains:

| Tier                           | Examples                                                                                                              | Editable? | Initialize button? |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | --------- | ------------------ |
| **Tier 1** (pure prose)        | identity, safety, tool-call-style, tool-errors, reply-tags, cli-reference, silent-replies, self-update, model-aliases | ✅ Yes    | ✅ Yes             |
| **Tier 2** (prose + mustache)  | docs (`{{docsPath}}`), reactions (`{{channel}}`)                                                                      | ✅ Yes    | ✅ Yes             |
| **Tier 3** (conditional logic) | tooling, messaging, sandbox                                                                                           | ❌ No     | n/a                |

Tier-3 sections contain conditional branches (`if availableTools.has(...)`) that mustache can't express. They stay rendered at runtime as `source: "generated"` and don't surface an edit pill.

## Hot-edit semantics

The prose loader caches file content keyed by absolute path + mtime. On every render it `statSync`s each candidate path:

- mtime unchanged → serve from cache
- mtime changed → re-read from disk, update cache
- file no longer exists → fall back to next candidate in the load order

After a `prompt.sections.prose.write` RPC, the gateway explicitly clears the cache (regardless of filesystem mtime resolution).

In practice: edit any prose file with `vim`, `code`, the hub modal, or `git pull` — the next agent turn picks up the change. No restart required.

## Anthropic prompt-cache cost

Most platform-layer sections are marked `cacheable: true`. The Anthropic API caches the system prompt prefix for ~5 minutes; identical prefixes across calls cost ~10× less than fresh ones.

**Every edit to a cacheable section invalidates that prefix on the agent's next call.** Small but real cost on long contexts. The hub modal shows an amber warning when you have unsaved changes on a cacheable section — that's intentional friction, not a bug.

When iterating heavily, expect the first call after each save to take longer and bill more. Subsequent calls within the cache window get the discount back.

## Conflict detection (D-0g-2)

If two operators have the same section open and both save, the second save returns `CONFLICT` instead of clobbering the first. The hub modal shows an amber banner with the live server content and two resolution paths:

- **Discard my changes** — adopts the server version
- **Keep my changes (overwrite on next save)** — acknowledges the conflict and lets your next save win

External edits (vim, git pull) also trigger the conflict UI if you had the modal open.

## RPCs (for tooling that bypasses the hub)

All admin/assignee gated:

| RPC                                                                         | Purpose                                                                                           |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `prompt.sections.prose.read({layer, sectionId, variant?, scope, agentId?})` | Returns `{path, content, exists, scope, templateDefault?, mtimeMs?, cacheable?}`                  |
| `prompt.sections.prose.write({...same, content, expectedMtimeMs?})`         | Returns `{path, bytes, mtimeMs}` or `CONFLICT` error                                              |
| `prompt.sections.workspaceFiles.list({agentId})`                            | Returns `{files: [{path, chars, truncatedPreview, exists, synthetic}]}` for FILE-source inspector |

## File paths reference

```
<gateway-install>/dist/agents/sections/prose/
  platform/
    identity.md
    safety.full.md
    safety.minimal.md
    tool-call-style.md
    tool-errors.md
    reply-tags.md
    cli-reference.md
    silent-replies.full.md
    silent-replies.minimal.md
  agent-type/
    self-update.md
    model-aliases.md
    docs.md
    cron-guidance.md
    google-auth.md
  session/
    reactions.minimal.md
    reactions.extensive.md

<workspaceDir>/prompts/
  <sectionId>[.<variant>].md   ← per-agent overrides
```

## Migration / audit

If you upgrade from a pre-D-0f gateway and your `<workspaceDir>/prompts/` already has files, they are picked up automatically as agent overrides. Run `git diff` against the global tree to see what's been customised.

The `bot-prd` deployment had no pre-existing prompts/ overrides at D-0f cutover (audit confirmed clean).
