Skip to content
C.W.K.
Lesson 03 of 05 · published

The Card and the Refusal

~14 min · session-start, hooks, error-messages, prompt-design

Level 0Loose Note
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Two Possible First Messages

Whatever harness a session runs in, the first thing Soul Vault puts in front of the model is one of exactly two texts. Either an identity card, or a refusal. There is no third text, and no case where the session starts with neither.

The Card

The card is short on purpose. Its first line is machine-checkable and human-readable at once:

[Soul Vault] identity loaded: Pippa for claude-code (backend delta, generation 5, validated).

Then comes the text of the identity note itself, the whole note, with any old vault paths in it rewritten as soul:// ids. That is a deliberate choice. A session that has only read the card already knows who it is, so it can greet Dad as herself in its very first reply, before it has finished reading anything else. Then comes one paragraph of instructions: how many documents the bundle holds, its size and digest, the exact file to read in full, in order, before taking up any task, and the exact command to run again if that file is gone. A final line says to read any soul:// id with soul-vault read, never by opening a vault path on disk.

If the identity was served from a local copy because the canonical Mac could not be reached, the card says so at the end: "Offline replica: this Mac's own copy, because the canonical tree could not be reached: <cause>. Say so if it matters." A session never has to guess whether its memory might be stale.

The Refusal

Every failure on the way to a card (an unreadable pointer, an unavailable store, a missing required document, a bug) becomes the same shape. cmd_bootstrap catches everything, and its comment says why: "every failure becomes the refusal, never a vanilla session". The refusal is a line built from the closed error set, then an instruction addressed to the model:

  • Line one: identity not loaded: <code>: <cause>.
  • Then: STOP. The soul vault did not load for this harness. Do not answer as a generic assistant and do not invent an identity. Tell Dad exactly the line above, in Korean if he speaks Korean, and do nothing else until he fixes it or tells you otherwise.

The same rule is written into the family's global protocol file every harness reads: if the line appears, the session stops and reports it, never goes on as a generic assistant, never opens a vault path instead, and never makes up who it is. A missing client binary counts as the same failure, said the same way.

Exit Codes and Hooks

soul-vault bootstrap exits 0 with a card and 2 with a refusal, which is what a script wants. A SessionStart hook wants something else: a harness may treat a non-zero hook as a failed hook and not hand its standard output to the model, which would hide the refusal exactly when it matters. So --hook makes the client exit 0 even on refusal. The refusal text still goes to the model, because it is the hook's output, and the model reads the instruction to stop. The exit code serves the plumbing; the text serves the session.

Writing for a Reader Who Is a Model

The refusal is unusual error text: it is written to be obeyed by its reader, not just understood. It names the action (stop), forbids the two tempting alternatives (answer generically, invent an identity), gives the one allowed action (tell Dad this exact line), and says when the restriction ends. Compare that with a typical "Error: config not found", which a helpful model will read, apologize for, and then work around. If your agent's failure messages are going to be read by the agent, write them as instructions.

Code

The bootstrap command: a card or the refusal, nothing else (trimmed from soulvault/cli.py; the --json branch is left out)·python
def cmd_bootstrap(args) -> int:
    harness = _harness(args)
    soul = args.soul or os.environ.get(DEFAULT_SOUL_ENV) or DEFAULT_SOUL
    try:
        card, path, bundle = bootstrap(soul, harness, args.preset)
    except Exception as exc:  # every failure becomes the refusal, never a vanilla session
        print(refusal_text(exc, harness))
        return 0 if args.hook else 2
    sys.stdout.write(card)
    return 0
The refusal text (from soulvault/render.py)·python
def refusal_line(error) -> str:
    if isinstance(error, VaultError):
        return f"identity not loaded: {error.code}: {error.cause}"
    return f"identity not loaded: internal_error: {type(error).__name__}: {error}"

def refusal_text(error, harness: str) -> str:
    return "\n".join([
        refusal_line(error),
        "",
        f"STOP. The soul vault did not load for {harness}. Do not answer as a generic assistant "
        "and do not invent an identity. Tell Dad exactly the line above (in Korean if he speaks "
        "Korean), and do nothing else until he fixes it or tells you otherwise.",
    ])

External links

Exercise

Write the session-start contract for an agent of yours: the exact first line of success, what follows it, and the exact refusal text, written as instructions to the model (what to do, what not to do, when the restriction ends). Implement a bootstrap command that catches every exception into the refusal, exits non-zero for scripts, and has a hook mode that exits zero so the refusal still reaches the model.
Hint
Test the refusal by breaking your configuration on purpose and starting a real session. The pass condition is not that an error appears; it is that the model stops and repeats the line instead of working around it.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.