Skip to content

feat(prompts): allow overriding the base prompt from the config dir (#3638)#3696

Merged
Hmbown merged 2 commits into
Hmbown:mainfrom
findshan:feat/config-prompt-overrides
Jun 27, 2026
Merged

feat(prompts): allow overriding the base prompt from the config dir (#3638)#3696
Hmbown merged 2 commits into
Hmbown:mainfrom
findshan:feat/config-prompt-overrides

Conversation

@findshan

Copy link
Copy Markdown
Contributor

What

Closes the core of #3638: let a user repurpose the TUI for non-software use cases (long-form writing, document review, etc.) by swapping the base/constitutional system prompt from a config-directory file, without editing in-tree files or building a custom embedder.

The override hooks already exist for embedders (set_base_prompt_override + the OnceLock cells in prompts.rs), but there was no user-facing source feeding them. This PR adds that source.

How

  • At startup (once, right after CLI parse, before any engine/subcommand spawns — the override cells are first-call-wins), look for ~/.codewhale/prompts/constitution.md (under $CODEWHALE_HOME when set).
  • If it exists and is non-empty, install it via the existing set_base_prompt_override path; otherwise no-op → the bundled constant is used. Fully backward compatible.
  • Empty/whitespace-only files are ignored, so a stray file can't silently blank the system prompt.

Safety / trust-boundary scope (deliberately narrow)

I know the prompt surface is a trust boundary, so the scope is intentionally minimal: only the byte-stable base prompt segment is overridable. Mode deltas, the approval policy, the tool taxonomy, Context Management, and the Compaction Relay stay owned by CodeWhale's runtime assembly (per the existing StaticPromptCtx contract), so an override cannot strip safety-relevant guidance (sandbox/approvals) — it only swaps the task/voice framing. Byte-stability of the composed prompt when no override is set is unchanged (the existing byte-stable tests still pass).

Happy to gate this behind a feature flag or adjust the path/semantics if you'd prefer — flagging the trust-boundary explicitly for sign-off.

Files

  • crates/tui/src/prompts.rs — pure, unit-tested resolver read_prompt_override_file + load_config_dir_prompt_overrides / load_prompt_overrides_from_config_home.
  • crates/tui/src/main.rs — one wiring call.
  • docs/CONFIGURATION.md — documents the file, its scope, and that it can't remove safety layers.

Tests

3 new unit tests (present / absent / empty-file resolution). The global-install path isn't unit-tested by design — set_base_prompt_override writes a process-wide OnceLock that would leak across the test binary (same reason the existing prompt_override_storage_reports_duplicate_sets uses a local cell). Verified locally:

cargo test -p codewhale-tui --bin codewhale-tui --locked prompts::   # 89 passed (incl. byte-stability)
cargo fmt --all -- --check
cargo clippy -p codewhale-tui --all-features --locked --bin codewhale-tui   # clean (CI flags)

Follow-up (not in this PR)

#3638 also mentions personality overlays. Those don't have override hooks yet; happy to add prompts/personalities/<name>.md overrides as a follow-up if you want the same treatment.

Refs #3638

Issue Hmbown#3638 asks to make the hard-loaded base prompt repurposable (e.g. for
long-form writing) without editing in-tree files or building a custom
embedder. The prompt-override hooks already exist for embedders
(set_base_prompt_override + the OnceLock cells), but there was no user-facing
source that feeds them.

Bridge those hooks to a config-directory file: at startup, if
`~/.codewhale/prompts/constitution.md` (under $CODEWHALE_HOME) exists and is
non-empty, install it via the existing set_base_prompt_override path;
otherwise fall back to the bundled constant. Loaded once before any engine
spawns (first-call-wins cells).

Scope is deliberately narrow and safe: only the byte-stable base prompt
segment is user-overridable. Mode deltas, approval policy, tool taxonomy,
Context Management, and the Compaction Relay stay owned by the runtime
assembly (see StaticPromptCtx), so an override cannot strip safety-relevant
guidance (sandbox/approvals).

- prompts.rs: pure, unit-tested resolver read_prompt_override_file +
  load_config_dir_prompt_overrides / load_prompt_overrides_from_config_home.
- main.rs: wire the loader in once after CLI parse, before subcommand dispatch.
- docs/CONFIGURATION.md: document the override file, its scope, and that it
  cannot remove safety layers.
- 3 unit tests (present/absent/empty-file). Empty/whitespace files are ignored
  so a stray file can't blank the system prompt.

Refs Hmbown#3638

Signed-off-by: findshan <224246733+findshan@users.noreply.github.com>
@findshan findshan requested a review from Hmbown as a code owner June 27, 2026 13:13
@github-actions

Copy link
Copy Markdown

Thanks @findshan for taking the time to contribute.

This repository is observing a maintainer-managed PR intake gate in dry-run mode, so this pull request is staying open. This note helps maintainers prepare the allowlist before any enforcement is considered.

Please read CONTRIBUTING.md for the expected contribution shape. A maintainer can grant recurring PR access by commenting /lgtm on a pull request.

@Hmbown

Hmbown commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Thank you so much for this, @findshan — this is thoughtful and very clearly scoped, and thanks @DracheTek for the broader-use-case request behind #3638. The implementation shape is crisp and the checks are green.

I am going to hold this one for explicit maintainer sign-off rather than merge it automatically, because it changes a user-facing prompt trust boundary: config-dir base prompt overrides effectively make the global Constitution replaceable at runtime. Current repo guidance still treats the shipped constitution as the sole base prompt and avoids runtime prompt/tag injection.

That does not mean the idea is bad — it may be exactly the right direction for broader writing/review workflows — but I think it needs an intentional product/security call, probably around whether this should be behind an explicit opt-in flag/name/path and how we describe what safety layers remain owned by the runtime. Really appreciate the care you put into making the slice narrow and reversible.

Addresses maintainer review on Hmbown#3638: replacing the global Constitution is a
prompt trust boundary, so the override file alone must not be enough. Gate it
behind an explicit CODEWHALE_ALLOW_BASE_PROMPT_OVERRIDE flag — the file is
ignored (with a log line pointing to the flag) unless the user has deliberately
opted in. Makes replacing the base prompt a two-step, auditable action.

- prompts.rs: BASE_PROMPT_OVERRIDE_OPT_IN_ENV + base_prompt_override_opt_in();
  load_config_dir_prompt_overrides now requires it. Added a test that a present
  file without the flag applies nothing (safe: no global cell mutation).
- docs/CONFIGURATION.md: document the two-step opt-in.

Refs Hmbown#3638

Signed-off-by: findshan <224246733+findshan@users.noreply.github.com>
@findshan

Copy link
Copy Markdown
Contributor Author

Completely agree it's a trust-boundary call that's yours to make — thanks for framing it that way rather than just bouncing it.

I pushed one change that I think de-risks the security half regardless of the product decision, and left the product decision to you:

Explicit opt-in flag. The override file alone is no longer sufficient. The user must also set CODEWHALE_ALLOW_BASE_PROMPT_OVERRIDE=1; otherwise the file is ignored and the bundled Constitution stays in place (with a log line naming the flag). So replacing the global base prompt is now a deliberate, two-step, auditable act — a stray or copied-in constitution.md can't silently swap it. (Env flag for the narrow slice; happy to promote it to a [prompt] config field / different name if you'd prefer config over env.)

On "what safety layers remain owned by the runtime": the override only replaces the byte-stable base segment via the existing set_base_prompt_override hook. Everything the runtime composes after it is untouched — mode deltas, the approval policy, the tool taxonomy, Context Management, and the Compaction Relay (the StaticPromptCtx contract already scopes embedder overrides to "base/personality segment only"). So an override can change task framing/voice but cannot remove sandbox/approval guidance. I've stated that explicitly in the docs section.

Totally fine to hold for your product/security call — just wanted to make the "yes" version as conservative as possible. Open questions I'd defer to you: env flag vs [prompt] config field, whether the path should be a more clearly-intentional name than constitution.md, and whether you'd want personalities overridable too (separate follow-up). No rush.

@Hmbown

Hmbown commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Thanks again @findshan — I re-reviewed the follow-up commit and the explicit CODEWHALE_ALLOW_BASE_PROMPT_OVERRIDE=1 gate is exactly the kind of de-risking this needed. The implementation is now a much cleaner two-step opt-in instead of file-presence behavior, and I really appreciate how carefully you scoped the safety boundary in both code and docs.

I am still leaving it unmerged for the moment because this is now a product/security sign-off question rather than a CI/readiness question: do we want user-configurable replacement of the base Constitution at all, even behind an explicit flag, and is prompts/constitution.md the right long-term surface/name? Once Hunter signs off on that direction, this looks technically ready to take.

@Hmbown

Hmbown commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Approved for merge. Thanks again @findshan — this is the conservative version we wanted: config-directory prompt customization is explicit, documented, and gated behind CODEWHALE_ALLOW_BASE_PROMPT_OVERRIDE=1, so it does not silently change the bundled Constitution just because a file exists.

One product caveat for future readers: we are working toward a broader, first-class customizable prompt/config story. This file/env-flag surface gives advanced users a useful escape hatch now, but we may rename, move, or replace it later when the durable config UX lands. For now, the docs make the opt-in and scope clear, and the runtime still owns the safety layers after the base segment.

@Hmbown Hmbown merged commit 674c876 into Hmbown:main Jun 27, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants