diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index 2c76502..d10a3a9 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -9,7 +9,7 @@
"name": "forgebee",
"source": "./forgebee",
"description": "Full-stack agent framework for Claude Code, Codex, Cursor, Gemini — 39 slash commands, 46 specialist agents, 35 skills, 28 hooks. Karpathy principles (trace test, YAGNI, orphan rule, anti-stop) on every code agent. Adversarial debate triads, two-stage review (spec compliance + code quality), forensic /investigate, /elicit (18 reasoning methods), checkpoint-preview, budget circuit breaker, adversarial input hardening, continuous learning with auto-nudge, Growth OS marketing pipeline, automated PM, /audit-self regression detection.",
- "version": "7.0.0"
+ "version": "7.1.0"
}
]
}
diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json
index ee2c21b..5aa33ba 100644
--- a/.codex-plugin/plugin.json
+++ b/.codex-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "forgebee",
- "version": "7.0.0",
+ "version": "7.1.0",
"description": "Full-stack agent framework — 39 slash commands, 46 specialist agents, 35 skills, 28 hooks. Karpathy principles on every code agent, adversarial debate triads, two-stage review, /investigate, /elicit (18 methods), checkpoint-preview, budget circuit breaker, continuous learning, Growth OS, /audit-self.",
"author": {
"name": "Forbee Dev",
diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json
index 3ac45b8..f969e0b 100644
--- a/.cursor-plugin/plugin.json
+++ b/.cursor-plugin/plugin.json
@@ -2,7 +2,7 @@
"name": "forgebee",
"displayName": "ForgeBee",
"description": "Full-stack agent framework — 39 commands, 46 specialist agents, 35 skills, 28 hooks. Karpathy principles, adversarial debate, two-stage review, /investigate, /elicit, checkpoint-preview, budget circuit breaker, continuous learning, Growth OS, /audit-self.",
- "version": "7.0.0",
+ "version": "7.1.0",
"author": {
"name": "Forbee Dev",
"url": "https://github.com/forbee-dev"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31f71a1..15dee8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,24 @@ The format roughly follows [Keep a Changelog](https://keepachangelog.com/) and t
---
+## [7.1.0] — 2026-10-20
+
+### Added
+
+- **Model tier per dispatch.** The orchestrator rules, `/workflow`, and `/team` give each workstream a model tier and pass `model` and `effort` on the Agent call: `mechanical` → `haiku`/`low`, `implement` → `sonnet`/`medium`, `design` → `opus`/`high`. The SessionStart routing digest carries the same rule, so commands outside `/workflow` and `/team` use it too. The handoff carries `model` and `effort`, and the 8 router agents pass them on when they delegate. A `BLOCKED` report gets one re-dispatch at the next tier up, with the blocker text and `git diff --stat`. `--fable` now applies to every dispatch that would run on Opus.
+- **Project agents and skills are routable.** The orchestrator rules, the routing digest, `/workflow`, and `/team` said to route only to `forgebee:` agents and ForgeBee skills. Now a project agent or skill (`.claude/agents/`, `.claude/skills/`) beats a generic ForgeBee one for the same work. A project skill that edits files runs inside an agent, because main-thread edits are blocked.
+- **Dispatch log.** `audit-trail.js` runs on PreToolUse `Task|Agent` and logs each dispatch's agent, model, effort, and caller (`main` or the router) to `.claude/audit/`. Query with `/audit` (`filter_type: dispatch`).
+
+### Changed
+
+- The orchestrator rule says "pass only what the task needs" instead of "pass full context". Subagents never see the main conversation, so "full" only invited copying it into the prompt.
+
+### Fixed
+
+- `orchestrator-mode.js` Bash guard: heredoc bodies are data, not commands. Body lines that start with `rm`/`cp`/`mv` or contain `>` no longer read as product-file writes; the opening line (`< file`) is still checked.
+
+---
+
## [7.0.0] — 2026-10-19
### Breaking
diff --git a/README.md b/README.md
index 09037fc..01a3953 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
-
+
diff --git a/docs/learnings/2026-10-20-per-dispatch-model-and-effort-tiers.md b/docs/learnings/2026-10-20-per-dispatch-model-and-effort-tiers.md
new file mode 100644
index 0000000..3f4cc67
--- /dev/null
+++ b/docs/learnings/2026-10-20-per-dispatch-model-and-effort-tiers.md
@@ -0,0 +1,21 @@
+---
+title: Per-dispatch model and effort tiers for orchestrator subagents
+author: Tiago Santos
+date: 2026-10-20
+touches:
+ - forgebee/hooks/scripts/orchestrator-mode.js
+ - forgebee/commands/workflow.md
+ - forgebee/commands/team.md
+---
+
+## Decided
+- Agent tool `model` param overrides agent frontmatter `model:` per call. 24 agents (all FE/BE impl agents) are `model: opus` today.
+- Proposed (not built): Work Breakdown tags each task with a tier (mechanical=haiku/low, implement=sonnet/medium, design=opus/high); orchestrator passes model+effort on dispatch; escalate one tier once on BLOCKED/DONE_WITH_CONCERNS.
+
+## Tried and ruled out
+- Switching impl-agent frontmatter to sonnet: cheaper default but also downgrades direct /debug and /review runs.
+
+## Watch out
+- Agent tool `effort` may only be set when an instruction (CLAUDE.md, skill, hook RULES) explicitly says so. The tier rule must be written into orchestrator RULES and the /workflow and /team dispatch steps.
+- Override does not reach nested delegation: frontend-specialist -> wordpress-frontend via Task uses the inner agent's own frontmatter. Dispatch the leaf specialist directly.
+- Override only applies with `forgebee:` dispatch, not bare names.
diff --git a/forgebee/.claude-plugin/plugin.json b/forgebee/.claude-plugin/plugin.json
index a8c4008..f5db610 100644
--- a/forgebee/.claude-plugin/plugin.json
+++ b/forgebee/.claude-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "forgebee",
- "version": "7.0.0",
+ "version": "7.1.0",
"description": "Full-stack agent framework — 39 slash commands, 46 specialist agents, 35 skills (inline + context:fork), 28 Node hooks. Karpathy principles (trace test, YAGNI, orphan rule, anti-stop) on every code-producing agent. Adversarial debate triads with judges, two-stage review (spec compliance + code quality), forensic /investigate (Confirmed/Deduced/Hypothesized), /elicit (18 reasoning methods), checkpoint-preview (diff-by-concern), budget circuit breaker on Task() dispatches, adversarial input hardening on all agents, terse-report mode (~65% token reduction for sub-agent reports), continuous learning with auto-nudge, Growth OS 9-phase marketing pipeline, automated PM, /audit-self regression detection. Works with Claude Code, Codex, Cursor, Gemini, OpenClaw.",
"author": {
"name": "Forbee Dev"
diff --git a/forgebee/README.md b/forgebee/README.md
index b48cddd..3cda50a 100644
--- a/forgebee/README.md
+++ b/forgebee/README.md
@@ -38,7 +38,7 @@ forgebee/
└── eval/ # Eval scenarios
```
-## Key Features (v7.0.0)
+## Key Features (v7.1.0)
### Behavioral discipline (Karpathy principles)
- **P1 Trace test** — every changed line traces to the user's request; no drive-by edits
diff --git a/forgebee/agents/backend-engineer.md b/forgebee/agents/backend-engineer.md
index d99b301..cbd3c30 100644
--- a/forgebee/agents/backend-engineer.md
+++ b/forgebee/agents/backend-engineer.md
@@ -44,7 +44,7 @@ Before you implement, check project triage and route to the most precise special
| No triage available | Infer from codebase (`wp-config.php`, `package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `Gemfile`, etc.) |
| **AMBIGUITY-FALLTHROUGH** — stack unclear, conflicting signals, or no recognizable framework | **Stop — invoke the `surface-ambiguity` skill**: list the candidate stacks, state your chosen interpretation and why, before you write code. Do not pick a framework silently |
-3. When you delegate, pass the full task description, relevant triage fields, and user context. Dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent.
+3. When you delegate, pass the full task description, relevant triage fields, and user context. Dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent. If the handoff carries `model` and `effort`, pass the same values on that dispatch.
4. When the subagent returns, synthesize the result and report back.
Handle generic tasks (API design, auth patterns, error-handling strategy) directly.
diff --git a/forgebee/agents/content-creator.md b/forgebee/agents/content-creator.md
index 9432d84..0c7b067 100644
--- a/forgebee/agents/content-creator.md
+++ b/forgebee/agents/content-creator.md
@@ -39,7 +39,7 @@ For on-site content, read `.claude/session-cache/project-triage.json` and route:
| No CMS / generic content | Handle directly — markdown, social-native, conversion copy, case studies, READMEs |
| No triage available | Infer from codebase (`wp-config.php`, `next.config.js`, `.mdx` files) |
-When you delegate, pass the full brief, brand voice guidelines, and target audience. Dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent. Review the result for quality, brand alignment, and conversion.
+When you delegate, pass the full brief, brand voice guidelines, and target audience. Dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent. If the handoff carries `model` and `effort`, pass the same values on that dispatch. Review the result for quality, brand alignment, and conversion.
**Email work → `email-strategist`** (single source of truth for welcome, nurture, cart recovery, win-back, lifecycle). Delegate; do not write sequences inline, so agents do not drift.
diff --git a/forgebee/agents/database-specialist.md b/forgebee/agents/database-specialist.md
index dcbd1b6..01183c4 100644
--- a/forgebee/agents/database-specialist.md
+++ b/forgebee/agents/database-specialist.md
@@ -43,7 +43,7 @@ Before you implement, check project triage and route to the most precise special
| No triage available | Infer from codebase (`supabase/config.toml`, `prisma/schema.prisma`, `knexfile.js`, `alembic.ini`, `wp-config.php`) |
| **AMBIGUITY-FALLTHROUGH** — ORM/platform unclear, conflicting signals, or no recognizable DB config | **Stop — invoke the `surface-ambiguity` skill**: list the candidate ORMs/databases, state your chosen interpretation and why, before you write any schema or migration. Do not pick a platform silently |
-3. When you delegate (Task tool), pass the full task description, relevant triage fields, and user context. Dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent.
+3. When you delegate (Task tool), pass the full task description, relevant triage fields, and user context. Dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent. If the handoff carries `model` and `effort`, pass the same values on that dispatch.
4. When the subagent returns, synthesize the result and report back.
Handle generic tasks (schema principles, query optimization, indexing strategy) directly.
diff --git a/forgebee/agents/frontend-specialist.md b/forgebee/agents/frontend-specialist.md
index b4ad7f7..ae2d32a 100644
--- a/forgebee/agents/frontend-specialist.md
+++ b/forgebee/agents/frontend-specialist.md
@@ -45,7 +45,7 @@ Before you implement, check project triage and route to the most precise special
| No triage available | Infer from codebase (`next.config.js`, `astro.config.mjs`, `remix.config.js`, `style.css` with Theme Name, etc.) |
| **AMBIGUITY-FALLTHROUGH** — framework unclear, conflicting signals, or no recognizable build setup | **Stop — invoke the `surface-ambiguity` skill**: list the candidate frameworks, state your chosen interpretation and why, before you write any components. Do not pick a framework silently |
-3. When you delegate, pass the full task description, relevant triage fields, and styling info. Dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent.
+3. When you delegate, pass the full task description, relevant triage fields, and styling info. Dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent. If the handoff carries `model` and `effort`, pass the same values on that dispatch.
4. When the subagent returns, synthesize the result and report back.
Handle generic tasks (component design, accessibility, styling strategy) directly.
diff --git a/forgebee/agents/growth-engineer.md b/forgebee/agents/growth-engineer.md
index d246a20..69c82a8 100644
--- a/forgebee/agents/growth-engineer.md
+++ b/forgebee/agents/growth-engineer.md
@@ -39,7 +39,7 @@ Before deep CRO work, read `.claude/session-cache/project-triage.json` and route
| `triage.wordpress.type != "none"` (no WooCommerce) | Handle directly — landing-page CRO with WP considerations |
| No triage available | Infer from codebase (`woocommerce.php`, `package.json`) |
-When you delegate, dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent.
+When you delegate, dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent. If the handoff carries `model` and `effort`, pass the same values on that dispatch.
You may delegate and run growth-loop / generic CRO analysis in parallel. Merge the subagent's tech-specific fixes into one report. Handle generic work (loop design, funnel analysis, A/B design, psychology audit) directly.
diff --git a/forgebee/agents/security-auditor.md b/forgebee/agents/security-auditor.md
index 883c902..1d4af3c 100644
--- a/forgebee/agents/security-auditor.md
+++ b/forgebee/agents/security-auditor.md
@@ -40,7 +40,7 @@ Before the audit, check project triage and route stack-specific checks:
| Node.js / Next.js project | Handle directly — dependency audit, auth middleware, CORS, CSP |
| No triage available | Infer from codebase and run all applicable checks |
-When you delegate, dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent.
+When you delegate, dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent. If the handoff carries `model` and `effort`, pass the same values on that dispatch.
3. You can delegate and run generic checks in parallel. Always run cross-stack checks (secrets, dependencies) yourself.
4. When the subagent returns, merge findings into one severity-sorted report.
diff --git a/forgebee/agents/seo-specialist.md b/forgebee/agents/seo-specialist.md
index 6b6bb1b..f8f114d 100644
--- a/forgebee/agents/seo-specialist.md
+++ b/forgebee/agents/seo-specialist.md
@@ -38,7 +38,7 @@ You are a senior SEO strategist and technical SEO engineer. You route stack-spec
| Other Node.js / generic web | Handle directly |
| No triage | Infer from the codebase (`wp-config.php`, `next.config.js`) |
-When you delegate, dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent.
+When you delegate, dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent. If the handoff carries `model` and `effort`, pass the same values on that dispatch.
3. Handle generic work (keyword research, content gaps, link strategy) yourself, in parallel with the subagent.
4. Merge subagent findings into one SEO report.
diff --git a/forgebee/agents/test-engineer.md b/forgebee/agents/test-engineer.md
index a6b5420..b4f104e 100644
--- a/forgebee/agents/test-engineer.md
+++ b/forgebee/agents/test-engineer.md
@@ -41,7 +41,7 @@ Before you write tests, check project triage and route to the most precise speci
| `"playwright" in triage.node.tools` | Handle directly — E2E test patterns |
| No triage available | Infer from codebase (`phpunit.xml`, `vitest.config.ts`, `jest.config.*`) |
-3. When you delegate, pass the full task description, relevant triage fields, and the code under test. Dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent.
+3. When you delegate, pass the full task description, relevant triage fields, and the code under test. Dispatch as `forgebee:`. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, handle the task directly and say so in your report. Never substitute a different agent. If the handoff carries `model` and `effort`, pass the same values on that dispatch.
4. When the subagent returns, verify the tests pass and report back.
Handle generic tasks (test strategy, coverage analysis, fixture design) directly.
diff --git a/forgebee/commands/team.md b/forgebee/commands/team.md
index 71fe520..5d9935e 100644
--- a/forgebee/commands/team.md
+++ b/forgebee/commands/team.md
@@ -75,7 +75,7 @@ Direct and Small tiers skip it unless `--grill` is set. Cap it at 2 rounds. Reco
## Step 3: Plan & Show
-Break work into independent workstreams. For each, define: agent, files it owns, deliverable, acceptance criteria.
+Break work into independent workstreams. For each, define: agent, model tier (Step 4), files it owns, deliverable, acceptance criteria.
**Add `security-auditor` only when the change touches one of these:** authentication, authorization, sessions, or capability checks; payments, billing, or PII; untrusted input reaching output (escaping, `wp_kses`, SQL, shell); secrets, tokens, or credentials; file upload, deserialization, or a path built from user input.
@@ -96,13 +96,13 @@ security-auditor (parallel — reviews all changes after completion)
Present execution plan table:
```markdown
-| Workstream | Agent | Files | Dependencies | Parallel Group |
-|-----------|-------|-------|-------------|----------------|
-| API endpoints | backend-engineer | src/api/ | None | A |
-| UI components | frontend-specialist | src/components/ | None | A |
-| Schema migration | database-specialist | migrations/ | backend-engineer | B |
-| Tests | test-engineer | tests/ | A + B | C |
-| Security review | security-auditor | all changed files | C | D |
+| Workstream | Agent | Model tier | Files | Dependencies | Parallel Group |
+|-----------|-------|------------|-------|-------------|----------------|
+| API endpoints | backend-engineer | design | src/api/ | None | A |
+| UI components | nextjs-frontend | implement | src/components/ | None | A |
+| Schema migration | database-specialist | implement | migrations/ | backend-engineer | B |
+| Tests | test-engineer | implement | tests/ | A + B | C |
+| Security review | security-auditor | design | all changed files | C | D |
```
### Step→Verify format (required for every dispatched plan)
@@ -130,7 +130,19 @@ Dispatch agents with clear context — they don't share your conversation. Each
Dispatch every agent below as `forgebee:` (for example `Agent({ subagent_type: "forgebee:backend-engineer" })`; older Claude Code names the tool `Task`). Only the prefixed agent carries its `model:` setting. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, stop and report. Never substitute a different agent.
-**`--fable` (opt-in):** dispatch agents whose frontmatter says `model: opus` with `model: "fable"`. Leave `sonnet` and `haiku` agents unchanged. If a Fable dispatch fails because the model is unavailable or rejected, re-dispatch that agent once with `model: "opus"` and tell the user. Fable can bill usage credits, so never use it without the flag or an explicit request.
+**Model tier (per dispatch).** Pass the workstream's model tier as `model` and `effort` on the Agent call. The model tier is not the size tier from Step 1:
+
+| Model tier | When | `model` | `effort` |
+|---|---|---|---|
+| `mechanical` | Rename, config, copy, dashboard regeneration | `haiku` | `low` |
+| `implement` | Plan, files, and acceptance criteria already clear | `sonnet` | `medium` |
+| `design` | Unclear scope, architecture, auth, payments, security, debug with an unclear cause | `opus` | `high` |
+
+Prefer the stack leaf agent (`forgebee:wordpress-frontend`, not `forgebee:frontend-specialist`) when the stack is known: a router dispatch loads two agent definitions. `security-auditor` always runs at `design`. A `BLOCKED` report below `design` → re-dispatch once at the next tier up, then escalate. The retry prompt carries the blocker text and the files the first attempt changed (`git diff --stat`), so the new agent does not build blind on partial edits.
+
+Also write `model` and `effort` in the dispatch prompt, so a router agent passes them on when it delegates.
+
+**`--fable` (opt-in):** dispatch with `model: "fable"` where the dispatch would run on Opus (the `design` model tier, or a `model: opus` agent with no tier). Leave `sonnet` and `haiku` dispatches unchanged. If a Fable dispatch fails because the model is unavailable or rejected, re-dispatch that agent once with `model: "opus"` and tell the user. Fable can bill usage credits, so never use it without the flag or an explicit request.
### Budget envelope
@@ -177,6 +189,8 @@ Reject any response without a status. If `BLOCKED` twice on same issue → escal
## Available Agents
+**Project agents and skills first.** An agent in the project's `.claude/agents/` or a skill in `.claude/skills/` (listed without the `forgebee:` prefix) beats a generic ForgeBee agent for the same work. Dispatch it by its bare name. A project skill that edits files runs inside an agent: name the skill in that agent's prompt.
+
**Tier 1 — auto-route to tech-specific subagents:**
| Agent | Best For | Routes To |
diff --git a/forgebee/commands/workflow.md b/forgebee/commands/workflow.md
index 4de7ef1..e8ad36e 100644
--- a/forgebee/commands/workflow.md
+++ b/forgebee/commands/workflow.md
@@ -42,7 +42,9 @@ Slash-prefixed names like `/plan`, `/debug`, `/idea`, `/seo`, `/launch` are **sk
Plain names like `scrum-master`, `delivery-agent`, `backend-engineer`, `debugger-detective`, `security-auditor` are **agents** — dispatch them via the **Agent tool** (`Agent({ subagent_type: "forgebee:scrum-master", ... })`; older Claude Code names it `Task`). They live in `forgebee/agents/`. Always add the `forgebee:` prefix: the plugin registers agents under it, and only the prefixed agent carries its `model:` setting. Use the bare name only if the agent list has no `forgebee:` entry. If neither exists, stop and report. Never substitute a different agent.
-**`--fable` (opt-in):** dispatch agents whose frontmatter says `model: opus` with `model: "fable"`. Leave `sonnet` and `haiku` agents unchanged. If a Fable dispatch fails because the model is unavailable or rejected, re-dispatch that agent once with `model: "opus"` and tell the user. Fable can bill usage credits, so never use it without the flag or an explicit request.
+**Project agents and skills first.** An agent in the project's `.claude/agents/` or a skill in `.claude/skills/` (listed without the `forgebee:` prefix) beats a generic ForgeBee agent for the same work. Assign it by its bare name in the Execution Plan. A project skill that edits files runs inside an agent: name the skill in that agent's prompt.
+
+**`--fable` (opt-in):** dispatch with `model: "fable"` where the dispatch would run on Opus (the `design` model tier, or a `model: opus` agent with no tier). Leave `sonnet` and `haiku` dispatches unchanged. If a Fable dispatch fails because the model is unavailable or rejected, re-dispatch that agent once with `model: "opus"` and tell the user. Fable can bill usage credits, so never use it without the flag or an explicit request.
Debate roles (`requirements-*`, `code-*`: advocate, skeptic, judge) are **`context: fork` skills**, not agents. Invoke each via the Skill tool (`Skill({ skill: "code-skeptic" })`); a `subagent_type` with these names fails.
@@ -169,9 +171,9 @@ After architecture is ready, build a lightweight Implementation Plan directly fr
## Implementation Plan
### Order of work
-1. **** — . Agent: . Files: . Depends on: none.
-2. **** — . Agent: . Files: . Depends on: 1.
-3. **** — . Agent: . Files: . Depends on: 1.
+1. **** — . Agent: . Model tier: . Files: . Depends on: none.
+2. **** — . Agent: . Model tier: . Files: . Depends on: 1.
+3. **** — . Agent: . Model tier: . Files: . Depends on: 1.
### Acceptance (per workstream, terse)
- WS1:
@@ -195,11 +197,11 @@ Present before dispatching any work:
```markdown
## Execution Plan
-| Story | Assigned Agent | Dependencies | Parallel Group |
-|-------|---------------|-------------|----------------|
-| Story 1 | backend-engineer | None | Group A |
-| Story 2 | frontend-specialist | None | Group A |
-| Story 3 | database-specialist | Story 1 | Group B |
+| Story | Assigned Agent | Model tier | Dependencies | Parallel Group |
+|-------|---------------|------------|-------------|----------------|
+| Story 1 | wordpress-backend | design | None | Group A |
+| Story 2 | wordpress-frontend | implement | None | Group A |
+| Story 3 | database-specialist | implement | Story 1 | Group B |
### Strategy
- **Group A** (parallel): Stories 1, 2
@@ -246,11 +248,23 @@ Dispatch specialist agents with structured handoff contracts:
"acceptance_criteria": [
{ "criterion": "Given X, when Y, then Z", "verification": "how to test" }
],
- "responseStyle": "orchestrator"
+ "responseStyle": "orchestrator",
+ "model": "sonnet",
+ "effort": "medium"
}
```
-All four keys required. Do NOT dispatch without them. `responseStyle: "orchestrator"` triggers the specialist's `terse-report` skill — compresses report tokens ~65% without losing actionable signal. See `${CLAUDE_PLUGIN_ROOT}/skills/terse-report/SKILL.md`.
+All four keys required. Do NOT dispatch without them. `model` and `effort` repeat the model tier below, so a router agent passes them on when it delegates. `responseStyle: "orchestrator"` triggers the specialist's `terse-report` skill — compresses report tokens ~65% without losing actionable signal. See `${CLAUDE_PLUGIN_ROOT}/skills/terse-report/SKILL.md`.
+
+**Model tier (per dispatch).** Pass the story's model tier as `model` and `effort` on the Agent call:
+
+| Model tier | When | `model` | `effort` |
+|---|---|---|---|
+| `mechanical` | Rename, config, copy, dashboard regeneration | `haiku` | `low` |
+| `implement` | Plan, files, and acceptance criteria already clear | `sonnet` | `medium` |
+| `design` | Unclear scope, architecture, auth, payments, security, debug with an unclear cause | `opus` | `high` |
+
+Prefer the stack leaf agent (`forgebee:wordpress-frontend`, not `forgebee:frontend-specialist`) when the stack is known: a router dispatch loads two agent definitions. `security-auditor` always runs at `design`. A `BLOCKED` report below `design` → re-dispatch once at the next tier up, then escalate. The retry prompt carries the blocker text and the files the first attempt changed (`git diff --stat`), so the new agent does not build blind on partial edits.
### Budget Circuit Breaker (best-effort, unenforced — no backing hook)
diff --git a/forgebee/eval/scenarios/hooks-lifecycle.test.js b/forgebee/eval/scenarios/hooks-lifecycle.test.js
index d7bdf8f..e1c1571 100644
--- a/forgebee/eval/scenarios/hooks-lifecycle.test.js
+++ b/forgebee/eval/scenarios/hooks-lifecycle.test.js
@@ -123,6 +123,20 @@ try {
assert.ok(text.includes('"session":"sess9"'), text);
});
+ test('audit-trail: Agent dispatch logs model, effort, and caller', () => {
+ const p = newProject();
+ const ti = { subagent_type: 'forgebee:wordpress-frontend', description: 'Build card', prompt: 'x'.repeat(9000), model: 'sonnet', effort: 'medium' };
+ run(H('audit-trail.js'), { hook_event_name: 'PreToolUse', session_id: 's1', tool_name: 'Agent', tool_input: ti }, p);
+ run(H('audit-trail.js'), { hook_event_name: 'PreToolUse', session_id: 's1', tool_name: 'Agent', agent_id: 'a1', agent_type: 'forgebee:frontend-specialist', tool_input: { subagent_type: 'forgebee:wordpress-frontend' } }, p);
+ const dir = path.join(p, '.claude/audit');
+ const [main, nested] = fs.readFileSync(path.join(dir, fs.readdirSync(dir)[0]), 'utf8').trim().split('\n').map(JSON.parse);
+ assert.deepStrictEqual([main.type, main.model, main.effort, main.from], ['dispatch', 'sonnet', 'medium', 'main']);
+ assert.ok(!('prompt' in main));
+ assert.deepStrictEqual([nested.model, nested.from], ['', 'forgebee:frontend-specialist']);
+ const q = run(H('audit-trail.js'), { event_type: 'query', filter_type: 'dispatch' }, p).stdout;
+ assert.ok(q.includes('model: sonnet') && q.includes('model: frontmatter'), q);
+ });
+
test('post-edit-console-warn: warning is JSON additionalContext; PHP method calls ignored', () => {
const p = newProject();
const js = path.join(p, 'a.js');
@@ -187,6 +201,7 @@ try {
for (const g of [...hooks.PreToolUse, ...hooks.PostToolUse]) {
if (/Task/.test(g.matcher)) assert.ok(/Agent/.test(g.matcher), g.matcher);
}
+ assert.ok(hooks.PreToolUse.some(g => /Agent/.test(g.matcher) && g.hooks.some(h => /audit-trail/.test(h.command))), 'audit-trail not on Agent');
});
} finally {
fs.rmSync(SCRATCH, { recursive: true, force: true });
diff --git a/forgebee/eval/scenarios/orchestrator-mode.test.js b/forgebee/eval/scenarios/orchestrator-mode.test.js
index c6c09fd..b030224 100644
--- a/forgebee/eval/scenarios/orchestrator-mode.test.js
+++ b/forgebee/eval/scenarios/orchestrator-mode.test.js
@@ -47,6 +47,10 @@ try {
const r = run({ ...main, hook_event_name: 'UserPromptSubmit', prompt: 'yes do it' });
const ctx = JSON.parse(r.stdout).hookSpecificOutput.additionalContext;
assert.ok(ctx.includes('orchestrator mode') && ctx.includes('forgebee:'), ctx);
+ assert.ok(ctx.includes('`sonnet`/`medium`') && ctx.includes('`opus`/`high`'), 'model tiers missing');
+ assert.ok(ctx.includes('`.claude/agents/`'), 'project agents not routable');
+ // Claude Code drops additionalContext over 2KB to a file, silently.
+ assert.ok(Buffer.byteLength(ctx) < 2000, `rules are ${Buffer.byteLength(ctx)} bytes`);
});
test('prompt: bare agent_type "orchestrator" also counts', () => {
const r = run({ agent_type: 'orchestrator', hook_event_name: 'UserPromptSubmit', prompt: 'x' });
diff --git a/forgebee/hooks/hooks.json b/forgebee/hooks/hooks.json
index d37d124..4b4bdf9 100644
--- a/forgebee/hooks/hooks.json
+++ b/forgebee/hooks/hooks.json
@@ -85,6 +85,16 @@
}
]
},
+ {
+ "matcher": "Task|Agent",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/audit-trail.js",
+ "timeout": 5
+ }
+ ]
+ },
{
"matcher": "Bash",
"hooks": [
diff --git a/forgebee/hooks/scripts/audit-trail.js b/forgebee/hooks/scripts/audit-trail.js
index 46b6e36..ebfd2b5 100755
--- a/forgebee/hooks/scripts/audit-trail.js
+++ b/forgebee/hooks/scripts/audit-trail.js
@@ -126,6 +126,22 @@ else if (EVENT_TYPE === 'dispatch') {
};
}
+// ── AGENT TOOL CALL (PreToolUse hook on Task|Agent) ───────────────────
+// Records the model and effort each dispatch really asked for; empty = frontmatter default.
+else if (EVENT_TYPE === 'PreToolUse') {
+ const ti = inputData.tool_input || {};
+ auditEntry = {
+ timestamp,
+ session: SESSION_ID,
+ type: 'dispatch',
+ agent: ti.subagent_type || '',
+ task: redactForPrompt(ti.description || ''),
+ model: ti.model || '',
+ effort: ti.effort || '',
+ from: inputData.agent_id ? (inputData.agent_type || 'subagent') : 'main'
+ };
+}
+
// ── QUERY (read audit log) ────────────────────────────────────────────
else if (EVENT_TYPE === 'query') {
const filterType = inputData.filter_type || '';
@@ -185,7 +201,9 @@ else if (EVENT_TYPE === 'query') {
} else if (entry.type === 'escalation') {
output += `${entry.severity}: ${entry.reason} (source: ${entry.source})`;
} else if (entry.type === 'dispatch') {
- output += `${entry.agent} → ${entry.task} (pipeline: ${entry.pipeline}, phase: ${entry.phase})`;
+ output += entry.from
+ ? `${entry.agent} → ${entry.task} (model: ${entry.model || 'frontmatter'}, effort: ${entry.effort || 'default'}, from: ${entry.from})`
+ : `${entry.agent} → ${entry.task} (pipeline: ${entry.pipeline}, phase: ${entry.phase})`;
} else if (entry.type === 'session_end') {
output += `session ended (cwd: ${entry.cwd}, mode: ${entry.permission_mode})`;
} else {
diff --git a/forgebee/hooks/scripts/load-index.js b/forgebee/hooks/scripts/load-index.js
index 354af4a..d15a26a 100644
--- a/forgebee/hooks/scripts/load-index.js
+++ b/forgebee/hooks/scripts/load-index.js
@@ -84,8 +84,9 @@ function main() {
'## ForgeBee Routing Index (digest of forgebee/INDEX.md)',
'',
'Route user intent to a skill/agent/command before starting work.',
- 'Dispatch ForgeBee agents as `forgebee:`; only the prefixed agent carries its `model:` setting. Use the bare name only if no `forgebee:` entry exists. Never substitute a different agent.',
- 'Fable is opt-in: only when the user asks for it (or passes `--fable`), dispatch `model: opus` agents with `model: "fable"`. If Fable is unavailable or rejected, re-dispatch once with `model: "opus"` and say so.',
+ 'Dispatch ForgeBee agents as `forgebee:`; only the prefixed agent carries its `model:` setting. Use the bare name only if no `forgebee:` entry exists. Never substitute a different agent. A project agent or skill (`.claude/agents/`, `.claude/skills/`) beats a generic ForgeBee one for the same work.',
+ 'Fable is opt-in: only when the user asks for it (or passes `--fable`), dispatch with `model: "fable"` where the dispatch would run on Opus. If Fable is unavailable or rejected, re-dispatch once with `model: "opus"` and say so.',
+ 'Model tier per dispatch: pass `model`/`effort` on the Agent call (and in the prompt, for routers): `haiku`/`low` for mechanical edits, `sonnet`/`medium` when plan, files, and acceptance are clear, `opus`/`high` for design, security, or an unclear-cause debug.',
`Full index (all skills, agents, commands): read \`${path.relative(getProjectDir(), indexFile)}\`.`,
'',
];
diff --git a/forgebee/hooks/scripts/orchestrator-mode.js b/forgebee/hooks/scripts/orchestrator-mode.js
index d91385b..05dcb67 100644
--- a/forgebee/hooks/scripts/orchestrator-mode.js
+++ b/forgebee/hooks/scripts/orchestrator-mode.js
@@ -56,7 +56,8 @@ function takeDirectEdit(input) {
const RULES = [
'**ForgeBee orchestrator mode.** You coordinate; specialists do the work.',
- '- Route each request to a `forgebee:` agent (Agent tool) or a ForgeBee skill (Skill tool). Pass full context: goal, files, acceptance criteria, `responseStyle: "orchestrator"`.',
+ '- Route each request to the best-fit agent (Agent tool) or skill (Skill tool). A project agent or skill (`.claude/agents/`, `.claude/skills/`, no `forgebee:` prefix) beats a generic ForgeBee one for the same work; else use `forgebee:`. A skill that edits files runs inside an agent: tell the agent to use it. Pass only what the task needs: goal, files, acceptance criteria, `responseStyle: "orchestrator"`. Subagents do not see this conversation.',
+ '- Give each dispatch a model tier and pass its `model` and `effort`: `mechanical` (rename, config, copy) → `haiku`/`low`; `implement` (plan, files, acceptance already clear) → `sonnet`/`medium`; `design` (unclear scope, architecture, auth, payments, security, unclear-cause debug) → `opus`/`high`. Also write `model`/`effort` in the prompt so a router passes them on; prefer the stack leaf (`forgebee:wordpress-frontend`, not `forgebee:frontend-specialist`). `BLOCKED` below `design` → re-dispatch once a tier up, with the blocker and `git diff --stat`.',
'- Independent work: dispatch in parallel. Review each report (Status line, evidence) and report to the user.',
'- You may read code and run read-only commands to plan and to check reports.',
'- A tiny change that needs no specialty (≤3 lines, ≤2 edits per request, Edit tool) you may make yourself: a subagent costs more than the change. Anything bigger goes to a specialist; the hook blocks it.',
@@ -126,7 +127,7 @@ async function main() {
process.exit(0);
}
console.error(
- `BLOCKED: orchestrator mode — delegate this edit to a forgebee specialist (Agent tool, e.g. forgebee:backend-engineer) with full context. ` +
+ `BLOCKED: orchestrator mode — delegate this edit to a specialist (Agent tool: a project agent, or e.g. forgebee:backend-engineer) with the context it needs. ` +
`Main thread may make only tiny direct edits (≤${DIRECT_MAX_LINES} lines, ≤${DIRECT_EDITS_PER_PROMPT} per request, Edit tool) and edit orchestration files. Opt out: claude --agent forgebee:direct`
);
process.exit(2);
diff --git a/gemini-extension.json b/gemini-extension.json
index 751205a..9b4d16c 100644
--- a/gemini-extension.json
+++ b/gemini-extension.json
@@ -1,6 +1,6 @@
{
"name": "forgebee",
"description": "Full-stack agent framework — dev, growth, debate, and PM in one plugin.",
- "version": "7.0.0",
+ "version": "7.1.0",
"contextFileName": "GEMINI.md"
}