---
url: 'https://www.botholomew.com/prompts.md'
---
# Prompts & agent self-modification

The project's `prompts/` directory holds markdown files that shape how
the agent thinks. Anything you drop here that parses against the
prompt schema is treated like a first-class prompt — there are no
hard-coded special filenames. The CLI and the agent itself both have
full CRUD access (`list`, `read`, `create`, `edit`, `delete`).

***

## What ships in a fresh project

`botholomew init` seeds three files. None of them are special — `init`
just writes them so a new project has something to start from. You can
rename, delete, or replace any of them; the loader doesn't care about
filenames.

| File | Purpose |
|---|---|
| `goals.md` | The agent's identity (the wise-owl prose) plus the current goal list. Updated as goals complete. |
| `beliefs.md` | Priors the agent has learned about the world / project. |
| `capabilities.md` | LLM-summarized, thematic inventory of what the agent can do. Auto-regenerated by `botholomew capabilities`. |

Every prompt requires three frontmatter fields — exactly these three,
no extras:

```yaml
---
title: Beliefs
loading: always          # or "contextual"
agent-modification: true # or false
---

# Beliefs

- I should be concise and clear in my work products.
- I should ask for help when I'm stuck rather than guessing.
```

***

## Strict validation

Every prompt file is validated on load. The schema requires
`title` (non-empty string), `loading` (`always` | `contextual`), and
`agent-modification` (boolean). Unknown keys are rejected.

If any file under `prompts/` fails validation, the loader throws and
**the worker fails the task** (or the chat turn refuses to start) with
a message naming the offending path and reason. There is no
quarantine — bad prompts must be fixed.

Run `botholomew prompts validate` at any time to check every file
without starting a worker. The command exits non-zero if anything is
wrong.

> **Upgrading from an older project?** Older `init` runs wrote
> `soul.md`, and none of the seed files had a `title:` field. After
> upgrading you must either delete `soul.md` (its content has been
> merged into `goals.md` for new projects) or add a `title:` line, and
> add `title:` to the other prompt files in `prompts/`. The validator
> will name any file it can't load.

***

## Loading modes

**`loading: always`** — the file is concatenated into every system
prompt, verbatim. Use sparingly.

**`loading: contextual`** — the file is included only if its content
shares keywords with the caller's current intent. The worker derives
keywords from the running task's name and description; the chat agent
derives them from your most recent message. Good for topic-specific
notes ("Everything I know about our invoicing system") that shouldn't
pollute the prompt on unrelated tasks.

See `loadPersistentContext()` and `extractKeywords()` in
`src/worker/prompt.ts`.

***

## The hardcoded `## Style` block

After the prompt files (and after the optional MCP section), every
system prompt — worker and chat alike — appends a short `## Style`
block defined as `STYLE_RULES` in `src/worker/prompt.ts`. It tells the
model to skip sycophantic preambles ("You're absolutely right!",
"Great question!"), push back when the user is wrong, and report
failures and uncertainty directly. This is hardcoded so it applies to
every install without needing to re-run `botholomew init`. Anything
you put in your own prompt files still loads above it and can layer on
top.

***

## CRUD: CLI and agent tools

Both surfaces validate on every write. They reject names containing
slashes, `..`, or characters outside `[a-zA-Z0-9._-]`, and they reject
files whose round-tripped content wouldn't load back.

### CLI: `botholomew prompts …`

| Command | Description |
|---|---|
| `prompts list [-l <n>] [-o <n>]` | Tabular list with name, title, loading, editable, size, status |
| `prompts show <name>` | Print raw file (frontmatter + body) |
| `prompts create <name> [--title <s>] [--loading always\|contextual] [--no-agent-modification] [--from-file <path>] [--force]` | Create a new prompt; body from file (`-` for stdin) or a default `# Title` skeleton |
| `prompts edit <name>` | Open `$EDITOR` on the file; refuse to keep invalid output (writes a `.tmp.invalid` sibling so you can recover) |
| `prompts delete <name> [--force]` | Delete a prompt; respects `agent-modification: false` unless `--force` |
| `prompts validate` | Validate every file under `prompts/`; exits non-zero on any failure |

### Agent tools

The chat and worker agents have the same CRUD surface:

* `prompt_list` — list all prompts with metadata and per-file `valid` flag.
* `prompt_read` — read a prompt; returns parsed `title` / `loading` / `agent_modification`.
* `prompt_create` — create a new prompt (frontmatter assembled from arguments, validated before commit).
* `prompt_edit` — apply git-style line-range patches via the shared
  [hunk patch format](./files.md#patch-format). Refuses files marked
  `agent-modification: false` and rolls back any patch that would clear
  that flag. Atomic-write-via-rename with mtime guard.
* `prompt_delete` — remove a prompt. Files marked
  `agent-modification: false` are protected. A malformed prompt can
  still be deleted (so the agent can clean up after itself).

A `context_update` interaction is logged to the current thread on
every successful edit / create / delete, so you can audit every change
the agent makes to its own priors.

***

## `capabilities.md` — auto-generated tool inventory

`capabilities.md` is otherwise just a normal prompt — same schema,
same loader. The only thing special about it is that
`botholomew capabilities` and the `capabilities_refresh` agent tool
know how to regenerate the body by scanning the built-in tool registry
and any configured MCPX servers.

The body is a **thematic summary** of what the agent can do —
built-in capabilities grouped into coarse themes (task management,
files, search, threads, …) and one theme per external service reachable
through MCPX. Specific tool names are intentionally **omitted** from
the rendered file; the agent uses `mcp_list_tools`, `mcp_search`, or
`mcp_info` to look up exact names when it actually needs to invoke a
tool. This keeps the always-loaded context small.

Summarization uses Claude (the `chunker_model` from config) on every
refresh. When no Anthropic API key is configured, a static fallback
listing is rendered with internal themes + MCPX server names and tool
counts.

`init` seeds it with the built-in tools already populated. Regenerate
any time via:

* `botholomew capabilities` — CLI refresh (honors `--no-mcp`)
* `capabilities_refresh` — agent tool, useful when the agent suspects
  the inventory has drifted (new MCPX servers added, tools renamed,
  file deleted)
* `/capabilities` — the matching slash command in chat

Frontmatter (including `title: Capabilities`) is preserved on
regeneration.

***

## What this actually looks like

A typical `beliefs.md` after a few weeks of use:

```yaml
---
title: Beliefs
loading: always
agent-modification: true
---

# Beliefs

- Evan prefers bullet-point summaries over paragraphs.
- The "Q4 planning" doc in /notes is the canonical source for revenue targets.
- The worker should escalate to a "waiting" status if a task needs access
  to a tool that isn't configured, instead of failing outright.
- When summarizing email, strip quoted replies — they add tokens without
  value.
```

None of those bullets were in the seed template — they accumulated as
the agent worked tasks and the chat user confirmed them. That's the
whole point: the agent gets smarter about *your* workflow over time,
and you can read (and edit) exactly what it believes.

***

## Why not put this all in a vector store?

Prompt files are high-priority, always-loaded text — they're what the
agent uses to decide *what to do*, not raw reference material. Burying
them in a vector index means the agent might not retrieve them when it
matters. Keeping them as flat markdown with a hard `always` flag makes
them impossible to miss.

Long-form reference material (ingested PDFs, web pages, meeting notes)
lives in the [context & embeddings system](context-and-search.md)
instead. The two are complementary:

* **Prompts** (`prompts/`) = how the agent thinks.
* **`context/` + the search index** = what the agent knows.

***

## Adding your own

The CLI is the easiest path:

```bash
botholomew prompts create deploy-checklist \
  --title "Deploy checklist" \
  --loading contextual \
  --from-file ./checklist.md
```

Or write the file directly with any editor. Tasks mentioning
"deploy", "release", or "version" — and chat messages mentioning the
same — will now include this file in the system prompt automatically.
You didn't have to register it anywhere. On every tick the worker
reads every `.md` file in `prompts/`, extracts words longer than three
characters from the current task's name and description, and includes
any `loading: contextual` file whose content contains at least one of
those words. The chat agent does the same on every turn, using your
most recent message as the keyword source. See
`loadPersistentContext()` in `src/worker/prompt.ts` for the exact
logic.
