Skip to content

Eval CI: trigger behavioral packs on behavior-bearing surfaces - #90

Merged
JRichlen merged 2 commits into
mainfrom
claude/rq001-behavioral-eval-triggers
Aug 30, 2026
Merged

JRichlen merged 2 commits into
mainfrom
claude/rq001-behavioral-eval-triggers

Conversation

@JRichlen

Copy link
Copy Markdown
Owner

Addresses #87

What changed

The behavioral tier's per-plugin filter only watched plugins/<p>/evals/promptfoo/** + evals/paid/** — not even that plugin's own SKILL.md — and the routing filter watched only evals/routing/**, plugins/*/skills/*/SKILL.md, and the marketplace. A command, skill reference, plugin AGENTS.md, hook, or manifest edit could steer the model and merge green without any behavioral evaluation.

  • ci/behavior-surfaces.json (new) — behavior-surfaces-as-code, the single source of truth for behavior-bearing paths, same pattern as ci/required-checks.json. Per-plugin: skills/**/SKILL.md, skills/**/references/**, commands/**, AGENTS.md, hooks/**, .claude-plugin/plugin.json, the promptfoo pack, and the shared paid harness. Routing: the routing pack, every SKILL.md, every plugin.json, and marketplace.json. Guard scripts (skills/**/scripts/**) deliberately stay the deep tier's frozen surface.
  • .github/workflows/evals.yml — the behavioral-run and routing-eval paths-filter blocks now carry those patterns verbatim. No job renamed; python3 ci/check_branch_protection.py --repo . reports branch protection in sync: 6 checks + 2 safety paths.
  • ci/check_behavior_surfaces.py (new) — deterministic, offline, no model call. Asserts (1) spec ↔ workflow filter lockstep, both directions; (2) a counterfeit table of changed-file cases, evaluated against the globs extracted from the workflow itself, proving behavior edits outside SKILL.md select the right pack and unrelated edits select nothing (no spurious paid spend); (3) both legs still announce EVALUATED vs SKIPPED.
  • evals/cheap/run.sh — one clearly delimited section appended before the summary wires the checker in (self-test + --repo .). Inert in roots without the workflow/spec; green in the counterfeit tier's synthetic root.
  • Auditable evaluated/skipped reporting — every behavioral matrix leg and the routing leg now emits a ::notice:: and a $GITHUB_STEP_SUMMARY line saying EVALUATED or SKIPPED, extending the existing "announce that this leg did NOT run" pattern, so green can never be confused with not-exercised. The checker fails if either marker disappears.

Semantic thresholds (pass-rate.sh floors, repeat:), spend gating (fork-PR guards, per-plugin billing), the routing result schema, and composition are untouched (#88 owns the schema).

Counterfeit proof — RED on the pre-fix filters

python3 ci/check_behavior_surfaces.py --repo . run against the unmodified workflow from main (verbatim):

  FAIL behavioral filter is missing spec pattern: plugins/${{ matrix.plugin }}/.claude-plugin/plugin.json
  FAIL behavioral filter is missing spec pattern: plugins/${{ matrix.plugin }}/AGENTS.md
  FAIL behavioral filter is missing spec pattern: plugins/${{ matrix.plugin }}/commands/**
  FAIL behavioral filter is missing spec pattern: plugins/${{ matrix.plugin }}/hooks/**
  FAIL behavioral filter is missing spec pattern: plugins/${{ matrix.plugin }}/skills/**/SKILL.md
  FAIL behavioral filter is missing spec pattern: plugins/${{ matrix.plugin }}/skills/**/references/**
  FAIL routing filter is missing spec pattern: plugins/*/.claude-plugin/plugin.json
  FAIL routing filter is missing spec pattern: plugins/*/skills/**/SKILL.md
  FAIL routing filter carries a pattern the spec does not: plugins/*/skills/*/SKILL.md
  FAIL behavioral[graveyard] plugins/graveyard/commands/bury.md -> not selected, expected selected (command markdown steers the model — behavior edit OUTSIDE SKILL.md (RQ-001 counterfeit))
  FAIL behavioral[redgate] plugins/redgate/skills/redgate/references/verified-rounds.md -> not selected, expected selected (skill reference docs are progressively disclosed into context)
  FAIL behavioral[redgate] plugins/redgate/hooks/session-gate.sh -> not selected, expected selected (hooks change runtime behavior)
  FAIL behavioral[graveyard] plugins/graveyard/AGENTS.md -> not selected, expected selected (plugin AGENTS.md is the harness's entry map into the plugin)
  FAIL behavioral[graveyard] plugins/graveyard/.claude-plugin/plugin.json -> not selected, expected selected (manifest description is routing-relevant metadata)
  FAIL behavioral[graveyard] plugins/graveyard/skills/graveyard/SKILL.md -> not selected, expected selected (SKILL.md prose (the original trigger surface))
  PASS behavioral[graveyard] plugins/graveyard/evals/promptfoo/promptfooconfig.yaml -> selected (the pack itself changed)
  PASS behavioral[graveyard] evals/paid/pass-rate.sh -> selected (the shared paid harness changed)
  PASS behavioral[graveyard] plugins/graveyard/docs/upkeep.md -> not selected (plugin docs/ prose is not a behavior surface — must not bill a paid leg)
  PASS behavioral[graveyard] README.md -> not selected (repo-level docs are not a behavior surface)
  PASS behavioral[redgate] plugins/graveyard/commands/bury.md -> not selected (another plugin's surface must not bill this plugin's leg)
  PASS behavioral[graveyard] plugins/graveyard/skills/graveyard/scripts/archive-repo.sh -> not selected (guard scripts are the DEEP tier's frozen surface, not the behavioral pack's)
  PASS routing evals/routing/prompt.txt -> selected (the routing pack itself)
  PASS routing plugins/graveyard/skills/graveyard/SKILL.md -> selected (skill descriptions feed the roster)
  PASS routing .claude-plugin/marketplace.json -> selected (marketplace descriptions are what gen-roster.sh renders)
  FAIL routing plugins/graveyard/.claude-plugin/plugin.json -> not selected, expected selected (manifest descriptions are parity-locked to the marketplace entry)
  PASS routing README.md -> not selected (repo docs do not feed the roster)
  PASS routing plugins/graveyard/commands/bury.md -> not selected (commands are the plugin pack's surface, not the roster's)
  FAIL behavioral leg announces EVALUATED — marker 'behavioral tier EVALUATED for' missing from the workflow; green would be indistinguishable from not-exercised
  PASS behavioral leg announces SKIPPED
  FAIL routing leg announces EVALUATED — marker 'routing tier EVALUATED' missing from the workflow; green would be indistinguishable from not-exercised
  PASS routing leg announces SKIPPED
exit: 1

Counterfeit proof — GREEN after the fix

Same command against this branch (verbatim):

  PASS behavioral 'paid' filter in lockstep with the spec (8 patterns)
  PASS routing filter in lockstep with the spec (4 patterns)
  PASS behavioral[graveyard] plugins/graveyard/commands/bury.md -> selected (command markdown steers the model — behavior edit OUTSIDE SKILL.md (RQ-001 counterfeit))
  PASS behavioral[redgate] plugins/redgate/skills/redgate/references/verified-rounds.md -> selected (skill reference docs are progressively disclosed into context)
  PASS behavioral[redgate] plugins/redgate/hooks/session-gate.sh -> selected (hooks change runtime behavior)
  PASS behavioral[graveyard] plugins/graveyard/AGENTS.md -> selected (plugin AGENTS.md is the harness's entry map into the plugin)
  PASS behavioral[graveyard] plugins/graveyard/.claude-plugin/plugin.json -> selected (manifest description is routing-relevant metadata)
  PASS behavioral[graveyard] plugins/graveyard/skills/graveyard/SKILL.md -> selected (SKILL.md prose (the original trigger surface))
  PASS behavioral[graveyard] plugins/graveyard/evals/promptfoo/promptfooconfig.yaml -> selected (the pack itself changed)
  PASS behavioral[graveyard] evals/paid/pass-rate.sh -> selected (the shared paid harness changed)
  PASS behavioral[graveyard] plugins/graveyard/docs/upkeep.md -> not selected (plugin docs/ prose is not a behavior surface — must not bill a paid leg)
  PASS behavioral[graveyard] README.md -> not selected (repo-level docs are not a behavior surface)
  PASS behavioral[redgate] plugins/graveyard/commands/bury.md -> not selected (another plugin's surface must not bill this plugin's leg)
  PASS behavioral[graveyard] plugins/graveyard/skills/graveyard/scripts/archive-repo.sh -> not selected (guard scripts are the DEEP tier's frozen surface, not the behavioral pack's)
  PASS routing evals/routing/prompt.txt -> selected (the routing pack itself)
  PASS routing plugins/graveyard/skills/graveyard/SKILL.md -> selected (skill descriptions feed the roster)
  PASS routing .claude-plugin/marketplace.json -> selected (marketplace descriptions are what gen-roster.sh renders)
  PASS routing plugins/graveyard/.claude-plugin/plugin.json -> selected (manifest descriptions are parity-locked to the marketplace entry)
  PASS routing README.md -> not selected (repo docs do not feed the roster)
  PASS routing plugins/graveyard/commands/bury.md -> not selected (commands are the plugin pack's surface, not the roster's)
  PASS behavioral leg announces EVALUATED
  PASS behavioral leg announces SKIPPED
  PASS routing leg announces EVALUATED
  PASS routing leg announces SKIPPED
exit: 0

Test evidence

  • evals/cheap/run.sh — exit 0, 1178 passed, 0 failed (includes the new behavior-surface trigger map section and the checker's 10-case --self-test, which itself re-proves RED-on-pre-fix/GREEN-on-fix against synthetic filter sets)
  • evals/counterfeits/run.sh — exit 0, 22 passed, 0 failed (baseline calibration stays green with the new section firing in the synthetic root)
  • python3 ci/check_branch_protection.py --repo . — in sync, 6 checks + 2 safety paths (no frozen check name touched)
  • Workflow YAML parses (yaml.safe_load)

🤖 Generated with Claude Code


Generated by Claude Code

…(RQ-001)

The behavioral tier's per-plugin filter only watched the promptfoo pack and
the shared paid harness, and the routing filter only watched SKILL.md and the
marketplace — so a command, skill reference, plugin AGENTS.md, hook, or
manifest edit could steer the model differently and merge green without any
behavioral evaluation.

- Freeze the behavior-bearing path definition in ci/behavior-surfaces.json
  (behavior-surfaces-as-code, same pattern as ci/required-checks.json).
- Expand the behavioral-run and routing paths-filter blocks in evals.yml to
  the full behavior surface: skills/**/SKILL.md, skills/**/references/**,
  commands/**, plugin AGENTS.md, hooks/**, plugin.json, plus the existing
  pack/harness paths; routing adds plugin.json and nested-skill SKILL.md.
  Guard scripts deliberately stay the deep tier's surface.
- Add ci/check_behavior_surfaces.py: offline, deterministic drift guard that
  keeps spec and workflow in verbatim lockstep both directions, and proves a
  counterfeit table of changed-file cases — including behavior edits OUTSIDE
  SKILL.md — selects exactly the right packs (and nothing spurious). Proven
  RED against the pre-fix filters, GREEN after.
- Wire it into evals/cheap/run.sh as a delimited section before the summary;
  inert in roots without the workflow/spec, green in the counterfeit tier's
  synthetic root.
- Every behavioral/routing leg now announces EVALUATED vs SKIPPED via
  ::notice:: and a step-summary line, so green can never be confused with
  not-exercised; the checker asserts both markers stay present.

No job names changed (branch-protection lock verified in sync); semantic
thresholds and paid spend gating untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsPQTo67oovz8ogirsH6uL
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T22:21:46.966825Z 1425113 PR opened
🔒 Security Review Completed 2026-08-30T22:26:27.172933Z 1425113 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 142511327b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/evals.yml
Comment thread ci/behavior-surfaces.json
Review on PR #90 found two shipped model-instruction shapes the trigger map
missed: declared agent files (plugin.json 'agents' component — present in
fleet-playbook-curator and tailscale-wif) and companion prompts beside a
SKILL.md (fleet-playbook-curator's PROMPT.md). Add plugins/<p>/agents/** and
plugins/<p>/skills/**/PROMPT.md to ci/behavior-surfaces.json and the workflow
filter, with counterfeit table cases proven RED against the previous filter
set and GREEN after.

The root AGENTS.md stays deliberately absent: it is repo governance at the
marketplace root, outside every plugin source dir, and never shipped by an
install (ci/install-smoke.sh walks marketplace source -> plugin dir ->
declared components) — an all-legs trigger for it would be exactly the
spurious paid spend RQ-001's acceptance criteria forbid. A counterfeit
negative case now pins that decision.

Also state the honest limit in the spec's _comment: selection guarantees the
matching pack RUNS; it does not put the changed file into the pack's prompt —
packs inject SKILL.md today, and injecting the other triggering surfaces is
behavioral-pack design tracked under the testing-strategy work (issue #89).

cheap tier: 1178 passed, 0 failed. counterfeits: 22 passed. branch
protection in sync (6 checks + 2 safety paths).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsPQTo67oovz8ogirsH6uL
@JRichlen
JRichlen merged commit 222da28 into main Aug 30, 2026
52 checks passed
JRichlen pushed a commit that referenced this pull request Aug 30, 2026
Resolved the expected evals/cheap/run.sh conflict by keeping both appended
sections: the testing-doc drift guard (this PR) followed by the RQ-001
behavior-surface trigger map (merged via #90). Gates on the merged tree:
cheap 1510 passed / 0 failed, counterfeits 22/0, branch protection in sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsPQTo67oovz8ogirsH6uL
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