Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Paste this loader rather than the body of `scripts/cloud-bootstrap.sh`, so the l

```bash
#!/bin/bash
# rev: 33
# rev: 34
curl -fsSL https://raw.githubusercontent.com/gborges0727/claude-plugins/main/scripts/cloud-bootstrap.sh | bash || true
exit 0
```
Expand All @@ -63,7 +63,7 @@ Every PR bumps the `rev`, in the snippet above and in `scripts/cloud-bootstrap.s
| `strip-attribution.py` | `PreToolUse` hook | Removes AI-attribution footers from GitHub writes. Enforces the style's ban mechanically |
| `flag-server-attribution.py` | `PostToolUse` hook | Tells the session to delete the footer the GitHub server adds to a new PR body, which the `PreToolUse` hook cannot reach |
| `route-spawns.py` | `PreToolUse` hook | Decides which agent every spawn runs on, then appends the style's rules to its prompt, since the output style never reaches a subagent. Rewrites an unpinned type (`general-purpose`, `claude`, `default-agent`, or none) to `opus-medium`. Refuses a `fork` on a Fable session, since a fork copies the whole transcript onto the session's model, unless the user's latest message asked for one. Refuses a `fable-xhigh` dispatch the user did not summon, and rewrites it to `opus-xhigh` when `~/.claude/gborges-standard.json` says the account cannot run Fable. Appends Anthropic's long-output note to a `fable-xhigh` prompt, so Fable writes a long deliverable once instead of drafting it in thinking and again as the reply |
| `remind-writing-rules.py` | `UserPromptSubmit` hook | Returns the style's Reminder paragraph as context with every user message, so the rules sit next to the reply being written. Records whether the message named `@agent-fable-xhigh`, used the word fork, or named Astra. An invocation of `pair-debate` records both Fable and Astra |
| `remind-writing-rules.py` | `UserPromptSubmit` hook | Returns the style's Reminder paragraph as context with every user message, so the rules sit next to the reply being written. Records whether the message named Fable, used the word fork, or named Astra. An invocation of `pair-debate` records both Fable and Astra |
| `writing-voice` | Skill | Two-pass ritual for every artifact (a file, a PR body, a commit message, a comment), whatever its length. The style alone shapes chat replies |
| `read-aloud-prep` | Skill | Rewriting documents so a TTS voice reads them cleanly |
| `bear-notes` | Skill | Writing into Bear without minting junk tags and wikilinks |
Expand All @@ -76,7 +76,7 @@ Every PR bumps the `rev`, in the snippet above and in `scripts/cloud-bootstrap.s
| `sonnet-medium` | Agent | Sonnet 5 at medium effort. Edits and runs with a command check in the brief, parallel copies of one such task, and fetching a named doc page |
| `opus-medium` | Agent | Opus 5 at medium effort. The default, and the floor for anything that reads code to reach a conclusion |
| `opus-xhigh` | Agent | Opus 5 at xhigh effort. One escalation step for a task that failed below it, and the stand-in for Fable on an account without it |
| `fable-xhigh` | Agent | Fable 5.1 at xhigh effort. Runs only when the user's message names `@agent-fable-xhigh`. See [docs/subagent-routing.md](docs/subagent-routing.md) for the routing rule and the cost reasoning |
| `fable-xhigh` | Agent | Fable 5.1 at xhigh effort. Runs only when the user's message names Fable. See [docs/subagent-routing.md](docs/subagent-routing.md) for the routing rule and the cost reasoning |
| `frontend-design` | Dependency | From `claude-plugins-official` |
| `mattpocock-skills` | Dependency | From `claude-plugins-official` |
| `context7` | Dependency | From `claude-plugins-official` |
Expand Down Expand Up @@ -231,9 +231,8 @@ The definitions alone change nothing, because Claude reaches for
rule. `opus-medium` is the default and the floor for any task that reads
code to reach a conclusion. `sonnet-medium` takes work a command can check.
`opus-xhigh` is one escalation step, taken once, after a failed check.
`fable-xhigh` runs only when the user's own message names
`@agent-fable-xhigh`, and `route-spawns.py` refuses every other
dispatch of it. [docs/subagent-routing.md](docs/subagent-routing.md) holds
`fable-xhigh` runs only when the user's own message names Fable, in any
form, and `route-spawns.py` refuses every other dispatch of it. [docs/subagent-routing.md](docs/subagent-routing.md) holds
the cost reasoning.

`/gborges-standard:setup` writes `~/.claude/gborges-standard.json`, two
Expand Down
2 changes: 1 addition & 1 deletion docs/model-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dispatch in the name it types.
| mechanical | `sonnet-medium` | `luna-xhigh` | An edit or a run whose brief names the exact change and a command that checks it. Parallel copies of one such task. Fetching a named doc page |
| default | `opus-medium` | `sol-xhigh` | Any task that reads code to reach a conclusion (an investigation, a diagnosis, a review, a design choice). On Codex, also any brief that must read past 272K tokens |
| escalation | `opus-xhigh` | `astra-medium` | A task that failed once below it. One long dependent chain the orchestrator cannot split |
| summoned | `fable-xhigh` | `astra-xhigh` | Only when the user's own message names the agent. On Codex, any Astra call above medium effort |
| summoned | `fable-xhigh` | `astra-xhigh` | Only when the user's own message names the model, in any form. On Codex, any Astra call above medium effort |

On Claude Code, a Codex rung is not a Claude agent. It is a `codex exec`
command with the rung's model and effort, run in the background through
Expand Down
13 changes: 8 additions & 5 deletions docs/subagent-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Fable 5.1 prompting guide as of 2026-09-01.
| `sonnet-medium` | Sonnet 5 | medium | An edit or a run whose brief names the exact change and a command that checks it. Parallel copies of one such task across files. Fetching a named doc page outside the codebase |
| `opus-medium` | Opus 5 | medium | The default. The floor for any task that reads code to reach a conclusion |
| `opus-xhigh` | Opus 5 | xhigh | A task that failed once below it. A task that is one dependent chain the orchestrator cannot split. The stand-in for Fable on an account without it |
| `fable-xhigh` | Fable 5.1 | xhigh | Only when the user's message names `@agent-fable-xhigh` |
| `fable-xhigh` | Fable 5.1 | xhigh | Only when the user's message names Fable |

Each name states its model and effort so the orchestrator sees the cost of
a dispatch in the name it types.
Expand Down Expand Up @@ -122,9 +122,12 @@ against `opus-medium` on real briefs, and the rule stands until then.
The orchestrator judging a task "hard enough for Fable" is the guess that
spends the most money when wrong, so no judgment is allowed. Cheaper cache
hits do not change that, because the guess is about which task deserves
the model, not about the per-token price. The user types
`@agent-fable-xhigh`, and a hook refuses every dispatch that did not follow
such a message.
the model, not about the per-token price. The user names Fable in their
message, in any form (fable, fable xhigh, an @-mention), and a hook refuses
every dispatch that did not follow such a message. The rule stops the
orchestrator from choosing Fable. It does not make the user type the full
agent name, so the same word match that unlocks Astra on Codex unlocks
Fable here.

## What the dispatch history says

Expand Down Expand Up @@ -193,7 +196,7 @@ Anthropic's long-output note, which tells Fable at xhigh to write a long
deliverable once instead of drafting it in thinking and again as the reply.

`hooks/remind-writing-rules.py` runs on every user message. It records
whether the message named `@agent-fable-xhigh`, and whether it used the
whether the message named Fable, and whether it used the
word fork, in two per-session state files under
`~/.claude/gborges-standard/state/`.

Expand Down
2 changes: 1 addition & 1 deletion plugins/gborges-standard/agents/fable-xhigh.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: fable-xhigh
description: Fable 5.1 at xhigh effort. Runs only when the user's own message names @agent-fable-xhigh. A hook refuses every other dispatch and points it at opus-xhigh.
description: Fable 5.1 at xhigh effort. Runs only when the user's own message names Fable. A hook refuses every other dispatch and points it at opus-xhigh.
model: fable
effort: xhigh
---
Expand Down
22 changes: 10 additions & 12 deletions plugins/gborges-standard/hooks/plugin_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
non-boolean key all fall back to fable = True and codex = False. Hooks read
this on every event, so a broken file must never stop a spawn or a turn.

The same folder also tracks whether the user's latest message named the
Fable agent. mention_from_prompt() decides that from the message text,
The same folder also tracks whether the user's latest message named
Fable. mention_from_prompt() decides that from the message text,
write_mention() records the answer for one session, and read_mention()
reports it back. The record is one file per session under
~/.claude/gborges-standard/state, holding the single character 1 or 0. The
Expand All @@ -39,14 +39,12 @@

DEFAULTS = {"fable": True, "codex": False}

# The three ways a user can name the Fable agent in a message. The first is
# what Claude Code inserts for an @-mention of an agent. The other two are
# what a person types by hand.
MENTIONS = (
"@agent-fable-xhigh",
'@"fable-xhigh (agent)"',
"@gborges-standard:fable-xhigh",
)
# Any mention of Fable counts, the same way any mention of Astra unlocks
# Astra. The rule exists to stop the model from picking Fable on its own,
# not to make the user type the agent's full @-mention. The word match
# catches "fable", "fable xhigh", "fable-xhigh", and the @-mention forms
# Claude Code inserts, since each of those contains the word.
FABLE_WORD = re.compile(r"\bfable\b", re.IGNORECASE)


def load():
Expand All @@ -69,10 +67,10 @@ def load():


def mention_from_prompt(prompt):
"""Say whether this message text names the Fable agent."""
"""Say whether this message text names Fable."""
if not isinstance(prompt, str):
return False
return any(form in prompt for form in MENTIONS)
return FABLE_WORD.search(prompt) is not None


FORK_WORD = re.compile(r"\bfork\b", re.IGNORECASE)
Expand Down
10 changes: 6 additions & 4 deletions plugins/gborges-standard/hooks/route-spawns.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@
would break forks on every host whose transcript format differs.

The same hook also decides who may run the Fable agent. Fable is the
expensive model, so gborges-standard:fable-xhigh runs only when the user
asked for it by name in their latest message. remind-writing-rules.py
expensive model, so gborges-standard:fable-xhigh runs only when the user's
latest message used the word Fable. Any mention counts, the same way any
mention of Astra unlocks Astra in route-codex.py. The rule stops the main
agent from picking Fable on its own, not the user from asking casually. remind-writing-rules.py
records that answer per session, and this hook reads the record. A Fable
spawn with no such record comes back denied, and the reason tells the main
agent to send the same brief to gborges-standard:opus-xhigh instead.
Expand Down Expand Up @@ -206,8 +208,8 @@ def on_fable(transcript_path):
)

DENY_REASON = (
"The Fable agent runs only when the user's own message names "
"@agent-fable-xhigh. This message did not, so send the same brief to "
"The Fable agent runs only when the user's own message names Fable. "
"This message did not, so send the same brief to "
"gborges-standard:opus-xhigh instead."
)

Expand Down
3 changes: 2 additions & 1 deletion plugins/gborges-standard/output-styles/plain-english.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ Spend words on clarity, save them on scope.
without asking, say so in the report, and after a second failure report
to the user instead of climbing again.
- `gborges-standard:fable-xhigh` runs only when the user's own message names
`@agent-fable-xhigh`. Never pick it yourself. If a dispatch of it errors
Fable, in any form (fable, fable xhigh, an @-mention). Never pick it
yourself. A hook refuses any dispatch the user did not ask for. If a dispatch of it errors
because the account cannot run the model, send the same brief to
`opus-xhigh` and say you substituted.
- The per-machine setup file, `~/.claude/gborges-standard.json`, says
Expand Down
2 changes: 1 addition & 1 deletion scripts/cloud-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# missing or the two numbers differ. After the merge, paste the new rev into
# each environment's Setup script field when the change cannot wait out the
# snapshot expiry.
# rev: 33
# rev: 34

set -u

Expand Down
15 changes: 15 additions & 0 deletions tests/test_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,21 @@ def test_full_name_mention_form_also_unlocks_fable(self):
out = self.spawn(FABLE)
self.assertEqual(out["hookSpecificOutput"]["permissionDecision"], "allow")

def test_the_bare_word_fable_unlocks_fable(self):
self.submit("Send this one to fable xhigh, it's a hard one.")
out = self.spawn(FABLE)
self.assertEqual(out["hookSpecificOutput"]["permissionDecision"], "allow")

def test_the_word_fable_in_any_case_unlocks_fable(self):
self.submit("Have Fable do it.")
out = self.spawn(FABLE)
self.assertEqual(out["hookSpecificOutput"]["permissionDecision"], "allow")

def test_a_word_that_only_contains_fable_does_not_unlock_it(self):
self.submit("Read the fables folder and summarize it.")
out = self.spawn(FABLE)
self.assertEqual(out["hookSpecificOutput"]["permissionDecision"], "deny")

def test_a_mention_in_one_session_does_not_unlock_another(self):
self.submit("Use @agent-fable-xhigh.", session_id="s1")
out = self.spawn(FABLE, session_id="s2")
Expand Down
Loading