feat(personas): named system-prompt overrides for hands run#27
Merged
Conversation
3 tasks
Adds two new flags to `hands run`:
--persona <name> use a named persona (bundled or user file)
--system-prompt <path> use an arbitrary prompt file (bypasses --persona)
Resolution order for --persona:
1. ~/.hands/personas/<name>.md (user override, takes precedence)
2. one of the four bundled personas (minimal, thorough, concise,
security-aware)
3. error: persona '<name>' not found, with the bundled set listed
The resolved prompt REPLACES hands' default OS-aware system prompt
in SDK mode. Default behavior (no flag) is unchanged. CLI mode
(spawning `claude`) doesn't plumb --persona through yet — adding
that is a future PR; the `--append-system-prompt` integration there
is meaningfully different from a string replacement.
Why this is safe to ship: dario research (askalf/dario#172) confirmed
that Anthropic's billing classifier doesn't fingerprint system prompt
content — content, length, and block count are not classifier inputs
as long as the rest of the request shape (effort, max_tokens,
tool array, body field order, billing tag, anthropic-beta) is
preserved. Combined with hands routing through dario for OAuth
subscription billing, that means swapping the system prompt does
NOT flip billing from five_hour to overage. Personas are the
operator-facing surface for that capability.
Bundled personas, by intent:
minimal short, no constraints, "use tools when useful"
thorough take initiative, exhaustive code with comments
concise terse, one-or-two sentences, no preamble
security-aware confirm-before-destructive, read-only OK without
Mutex: --persona and --system-prompt are mutually exclusive — both
set is a clear operator error and we exit 1 before burning any
work. Resolution happens BEFORE config load and screenshot
capture so "persona not found" surfaces with a clear message
fast.
Test coverage: test/personas.test.mjs — 7 cases covering the
bundled set, unknown name (with helpful error message listing
the bundled set), user-file override taking precedence over
bundled, content trimming on the user-file path, explicit path
loading via --system-prompt, missing file throwing Error,
listBundledNames identity. HOME-redirect via tmpdir for the
user-file tests (mocks ~/.hands/ without touching the user's
real config).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two new flags to `hands run`:
```
--persona use a named persona (bundled or ~/.hands/personas/.md)
--system-prompt use an arbitrary prompt file (bypasses --persona)
```
The resolved prompt replaces hands' default OS-aware system prompt in SDK mode. Default behavior (no flag) is unchanged.
Bundled personas:
Resolution order for `--persona`:
`--system-prompt ` bypasses persona lookup entirely — direct file load.
Why this is safe to ship
dario research #172 confirmed that Anthropic's billing classifier doesn't fingerprint system prompt content — content, length, and block count are not classifier inputs as long as the rest of the request shape (effort, max_tokens, tool array, body field order, billing tag, anthropic-beta) is preserved. Combined with hands routing through dario for OAuth subscription billing, that means swapping the system prompt does NOT flip billing from `five_hour` to `overage`. Personas are the operator-facing surface for that capability.
Mutex + early-exit
`--persona` and `--system-prompt` are mutually exclusive — both set exits 1 with a clear error before doing anything else. Resolution happens BEFORE config load and screenshot capture, so "persona not found" surfaces fast with a helpful error message that lists the bundled set.
Scope: SDK mode only (for now)
Plumbed through `runSdkMode` (the API-key / dario-routed path). CLI mode (spawning `claude --append-system-prompt`) doesn't plumb `--persona` through yet — the integration there is meaningfully different (`--append` vs replace) and deserves its own PR. Today's verification is SDK-mode + dario.
Test plan
Notes