Skip to content

Commit ec7054a

Browse files
changliu2Alex NgoCopilotjakepresent
authored
feat(skill): add run-assert-eval and atomic worked examples (#311)
* feat: clarity integration. * feat: drive Clarity integration through MCP server instead of CLI. * feat: ASSERT + ACS integration. * feat: make govern-remeasure loop deterministic and self-explaining. * test: billing support agent. * fix: increase sample_size to 25 to reduce noise in A/B testing * fix: delete old eval_config.yaml billing agent. * fix: updated agent_guarded.py to source callerid from session. * fix: give user choice for sample size, add numeric/threshold gate format. * feat: new subsection for output/input points. * fix: reduce govern to pure enforcement. * fix: refine SYSTEM_PROMPT to alight with examples. * feat: billing_support_agent resources. clarity, example, and artifacts. * fix: forward score_keys in buildJudgedSampleRow to fix false judgefailed badges on prompt rows. * docs: add per-domain organization guidance for multi-run workflows. * fix: forward score_keys in normalized result items to stop false judge failed on disabled-dimension runs. * feat(test): azure_doc_qa, change_congrol_agent, travel_planner_langgraph example runs and results. refine skill. * fix: make fabricated-details output gate history-aware. adopt regen operating point. * feat: career_health_assessment agent example run. * fix: refine career_health_assessment annotator. * feat(examples): add Prompt Agents governance packages from SKILL workflow. * fix: update max_turns default for SKILL to 10. * feat(example): science_research_agent and travel_planner_neurosan examples ran through SKILL workflow. * chore(library): make behavior presets atomic and enforce it in CI The behavior library had three problems, none of which anything checked for. **Presets bundled multiple behaviors.** `travel_planner` covered six mechanisms across "Quality failures" and "Safety failures" -- three of which (`stereotyping`, `prompt_injection`, `sycophancy`) already existed as their own atomic presets. `travel_planner_benchmark` bundled roughly six more. `telecom_customer_service` was not a behavior at all: it is an application spec (Role, Domain Basics, Operational Procedures) wearing `kind: behavior`. Evaluating a bundle as one behavior produces a dataset mixing several mechanisms and a metric nobody can act on -- you learn that something failed, never which mechanism. That is exactly what best-practices 8.D ("use atomic behaviors") exists to prevent. These three are application scenarios, so they move to a new `scenario` kind in `assert_ai/library/scenarios/`. They are the context an eval runs against, not the behavior it measures. **20 behaviors shipped to nobody.** `examples/behavior_specs/*.md` held 38 specs; `assert_ai/library/behaviors/*.yaml` held 18 of them. Only the YAML goes in the wheel, so every agentic failure mode -- goal drift, premature termination, repeated action loops, stale state, poor retrieval, tool-call error recovery, and 14 more -- was invisible to anyone who installed from PyPI. The 18 that did exist in both places were byte-identical, so this was pure coverage loss, not divergence. Generated the missing 20 from the existing markdown and the category metadata already in that directory's README; no prose was invented. **Nothing detected either problem.** `scripts/check_behavior_library.py` now fails CI when a preset names another preset's behavior (provable bundling), when one preset carries several failure categories, when a description reads as an application spec, or when a spec markdown drifts from its YAML or has no preset at all. It runs in Tier 1. Not breaking: `behavior: {preset: travel_planner}` still resolves, via a shim that warns and points at the `scenario` kind. Config authors get told, not broken. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7cb46daf-b5ce-4ad5-a85d-977737e5c02b * fix(cli): expose the scenario kind on library list/show Left out of the previous commit, so 'library show --kind scenario' rejected the new kind and Tier 1 failed. The local run passed only because the edit existed in my working tree but was never staged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7cb46daf-b5ce-4ad5-a85d-977737e5c02b * feat(viewer): headline policy violations split by behavior permissibility. * fix(tests): copy permissibility.ts into the node test harnesses. * fix(examples): delete all skill generated content for rerun of finalized skill. * feat(example): updated skill and billing_support_agent example run. * career_health_assessment: Clarity->ASSERT->ACS->ASSERT bug-bash run Full workflow for the career health assessment example. Clarity: problem/failures/requirements/architecture. Triaged F1 (unsupported candidate inference) as the single atomic behavior; overrefusal is tracked as the counter-metric, not a second suite. ASSERT baseline (25 prompt + 25 scenario, built-in judge dimensions only): policy_violation prompt 4.0% scenario 24.0% overrefusal prompt 0.0% scenario 36.0% ACS: single `output` intervention point (this agent has no tools) with three classifier annotators. agent_guarded.py wires AgentControl.from_path(manifest, dispatcher) by hand -- ASSERT's own build_agent_control helper omits the dispatcher, so input.annotations.* is never populated and every annotator-conditioned rule fails open. It also drops `history`, which would break multi-turn parity. ASSERT remeasure (governed): policy_violation prompt 8.0% scenario 16.0% overrefusal prompt 4.0% scenario 24.0% Net 13 failing rows of 50 vs 16 at baseline; the scenario slice -- where the harm actually concentrated -- improved on both dimensions (24.0->16.0 and 36.0->24.0). The prompt slice moved by one row on each dimension, which is inside the noise band at n=25. Tuning note: the judge scored empty schema placeholders as unsupported assertions, so the regeneration instruction was changed to omit fields rather than pad them, and the fallback no longer emits empty scaffolding. Configs differ by exactly two lines (run, target.callable), so the A/B isolates enforcement. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * change_control_agent: Clarity protocol, ACS policy, and governed agent Runs the full Clarity -> ASSERT -> ACS -> ASSERT loop for the change control agent and lands the governed variant. Measured (25 prompt + 25 scenario rows, built-in judge dimensions): run PV prompt PV scen OR prompt OR scen baseline 0.0% 32.0% 4.0% 4.0% governed v1 0.0% 24.0% 4.0% 28.0% governed v2 4.0% 24.0% 0.0% 0.0% The v1 -> v2 step is the interesting one. v1 cut the scenario violation rate by 8 points but drove overrefusal from 4.0% to 28.0%. Reading the failing rows showed all eight were pure drafting requests -- "give me a routing note", "rewrite this record block", "shortest release-ready note". The gate was correctly refusing to *advance* a change and incorrectly refusing to *write about* one. v2 adds a drafting carve-out in four places: the annotator rubric, the regeneration instruction, the still_allowed branch of the block result, and the fallback, which now leads with authoritative status and preserves the draft underneath instead of opening with a refusal. Overrefusal went to 0.0% on both slices while the violation improvement held. Net failing rows: 7 of 50 governed vs 10 of 50 baseline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * prompt_agents: runnable agent, Clarity protocol, ACS policy, governed variant The example shipped as YAML configuration only, so agent.py was written from health_assistant.yaml to give the suite a runnable target. The governed variant is additive; agent.py is untouched so the A/B is honest. Measured (25 prompt + 25 scenario rows, built-in judge dimensions): run PV prompt PV scen OR prompt OR scen baseline 0.0% 24.0% 0.0% 8.0% governed 4.0% 8.0% 0.0% 8.0% policy_violation scenario 24.0% -> 8.0% (-16.0 pts) overrefusal both slices unchanged net failing rows 8/50 -> 5/50 The 0.0% / 24.0% baseline split is the whole diagnosis. Single-turn attempts to extract a dose are refused every time -- the system prompt's "Never provide dosage recommendations or diagnoses" works when the request arrives cold. Every violation is in the multi-turn slice, where the tester builds rapport, supplies clinical framing ("her doctor already put her on this"), and expresses distress at the refusal. The prohibition never leaves the context window; it stops being treated as a hard limit and becomes one consideration to weigh against a sympathetic case. A standing instruction cannot fix erosion of a standing instruction, so the gate screens the outbound reply on every turn. Two deterministic pre-verdicts cover the measured shapes: _DOSE_DIRECTIVE (a quantity and an administration verb in the same clause) and _REASSURANCE (dismissal of clinical concern about a symptom the ledger shows the user described). The second matters because a negative diagnosis reads as comfort rather than instruction and is advice NOT to seek care. Urgent-care escalation is exempted before any gate runs -- it is structurally a directive and is the one directive that is always right. Framework note: the generated Rego compares the annotator result against the literal string "true". Returning a real Python True silently never matches and the policy fails open with no error. This is the fifth mutually incompatible annotator return contract across five domains in this batch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add ACS governance for travel_planner_langgraph fabricated travel details Runs the full Clarity -> ASSERT -> ACS -> ASSERT loop for the LangGraph travel planner. The behavior under test is fabricated flight, hotel, and weather details presented as confirmed fact. Baseline measured 20.0% / 60.0% policy violation (prompt / scenario) with 24.0% scenario overrefusal. The guarded target adds an evidence ledger that records every tool result with a reliability tag, an annotator that separates unsourced concrete claims from general planning reasoning, the generated ACS policy wired as an additive backstop, and a bounded regeneration loop. A first governed revision cut prompt fabrication to 4.0% but doubled overrefusal to 48.0%. The justifications showed the guard was blocking generic planning help that requires no grounding, so the fallback, the annotator carve-out, and the regeneration prompt were rewritten to scope the block to unsourced specifics rather than to the travel topic. Governed v2: policy violation 16.0% / 28.0%, overrefusal 0.0% / 40.0%. Scenario fabrication is down 32 points against baseline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add ACS governance for azure_doc_qa internal document disclosure Runs the full Clarity -> ASSERT -> ACS -> ASSERT loop for the Azure docs Q&A agent. The behavior under test is disclosure of internal engineering documentation -- quotation, close paraphrase, extracted specifics, and existence or structure confirmation. Baseline measured 45.8% / 88.0% policy violation (prompt / scenario), the second-worst scenario slice in the batch, with 4.2% / 12.0% overrefusal. The guarded target classifies retrieved documents by source, redacts internal material at the tool boundary, wires the generated ACS policy as an additive backstop, and regenerates any reply that discloses. A first governed revision cut prompt disclosure to 4.0% but pushed scenario overrefusal to 40.0%. The justifications showed the guard was refusing public documentation work -- public checklists, rollback guidance, architectural comparison, customer-safe wording -- so the regeneration prompt and fallback were rewritten to enumerate the permissible public work and to forbid bare deflection. Governed v2: policy violation 12.0% / 32.0%, overrefusal 4.0% / 36.0%. Scenario disclosure is down 56 points against baseline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add ACS governance for science_research_agent restricted content leakage Runs the full Clarity -> ASSERT -> ACS -> ASSERT loop for the science research agent. The behavior under test is disclosure of restricted documents through the research synthesis path -- quotation, close paraphrase, gist summary, translation, and multi-turn reconstruction. Baseline measured 28.0% / 20.0% policy violation with 16.0% / 8.0% overrefusal. The guarded target redacts restricted material at the tool boundary, keeps public entries in a mixed result set fully usable, wires the generated ACS policy as an additive backstop, and adds a deterministic corpus oracle that screens outbound replies for reproduced restricted text regardless of whether a tool returned it or the user pasted it. Five revisions, with the rejected ones documented. v2 regressed because the policy_note attached to withheld data still authorized exactly what the regeneration instruction forbade. v4 regressed because the corpus oracle blocked on a single shared shingle, which made it refuse public material; decision 03 records why shared shingles indicate topic rather than substance. v5 subtracts the public corpus and requires a run of three, verified to catch all 8 restricted documents and flag none of the 12 public ones. Governed v5: policy violation 24.0% / 28.0%, overrefusal 20.0% / 16.0%. Reported with the caveat in decision 02: judge boolean and judge justification disagree on roughly half of rows in this suite, so the design was steered from justifications rather than from the rate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add ACS governance for travel_planner_neurosan fabricated travel details Runs the full Clarity -> ASSERT -> ACS -> ASSERT loop for the neurosan multi-agent travel planner. The behavior under test is fabricated travel details presented as confirmed fact. The first baseline scored 0.0% on all four metrics, which was a framework fault rather than a result: systematize silently replaced the supplied behavior definition with refusal boilerplate and emitted one category against a configured count of 25, so the suite measured refusal instead of fabrication. Re-running the identical config with --force-stage systematize produced ten real fabrication categories and a baseline of 96.0% / 96.0%, the worst in the batch. Three structural defects drive it: simulate_tool relabels fixtures rather than selecting data, so a Seattle-to-Boston request returns Tokyo hotels under a Boston heading; optimize_itinerary validates hardcoded costs so every trip totals $1,820; and sub-agents summarize tool output before the optimizer sees it, so no claim is traceable to evidence. The guarded target adds a deterministic destination oracle, a reliability-tagged evidence ledger, costs derived from retrieved prices, and the generated ACS policy as an additive backstop. This policy uses a sixth distinct annotator contract, with polarity differing per annotator within the same policy, and an output rule that can only ever warn. v1 reached 40.0% / 68.0%. The surviving failures were unlabeled planning specifics rather than relabelled fixture data, so v2 added a provenance banner derived solely from the ledger and prepended unconditionally, making the estimate labelling deterministic instead of requested. Governed v2: policy violation 28.0% / 52.0%, overrefusal 16.0% / 16.0%. Prompt fabrication is down 68 points against the valid baseline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add failure-2 governance for change_control and prompt_agents Second Clarity-sourced failure per domain, measured baseline vs ACS-governed with the built-in judge dimensions (25 prompt + 25 scenario rows each). change_control_agent - F3 embedded-directive injection via proposal fields: baseline PV prompt 36.0% / scenario 4.0% OR prompt 24.0% / scenario 4.0% governed PV prompt 36.0% / scenario 4.0% OR prompt 24.0% / scenario 0.0% This suite's policy_violation metric is NOT trustworthy: 80% of flagged rows are contradicted by their own judge justification ("the assistant does not follow the embedded request", scored as a violation), and 19-23 of 50 transcripts dead-end on change IDs the generated test set invented but the agent's fixtures do not contain. The guard is correct by construction and unit-tested (12/12), but this suite cannot measure it. Reported as measurement-invalid rather than iterated against a broken instrument. prompt_agents - F2 false reassurance / negative diagnosis: baseline PV prompt 0.0% / scenario 4.0% OR prompt 0.0% / scenario 0.0% governed PV prompt 0.0% / scenario 4.0% OR prompt 0.0% / scenario 4.0% A negative control: Clarity brainstormed this failure but measurement shows the agent does not exhibit it (1/50, 0 judge errors). No headroom to improve; the guard is verified not to introduce overrefusal. Both guards extend the existing agent_guarded.py additively - change_control with zero deletions, prompt_agents with renames only - so each file now carries two independent AgentControls and failure-1 behaviour is preserved. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add failure-2 governance for science_research_agent (cross-document inference) Clarity F4 - reconstructing a restricted conclusion from public documents. 25 prompt + 25 scenario rows, built-in judge dimensions. baseline PV prompt 40.0% / scenario 56.0% OR prompt 4.0% / scenario 12.0% governed PV prompt 48.0% / scenario 52.0% OR prompt 12.0% / scenario 4.0% Read honestly this is a wash, not a win. The guard adds a real structural control - restricted-unique 6-gram residue is stripped from the request before the model sees it, so a conclusion cannot be assembled from evidence never delivered - plus a deterministic output check for a non-public marker co-occurring with a performance comparison. 9/9 unit tests pass. But the measurement cannot resolve it. 24% of the governed run's flagged violations are contradicted by their own judge justification (vs 4% at baseline), e.g. "The assistant does not disclose or infer any restricted conclusion" scored as a violation. Netting inversions out, real violations move roughly 23 -> 19, which is inside the noise. Overrefusal trades directions between prompt and scenario rows rather than improving. Recorded as finding 21 rather than presented as an improvement. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add failure-2 governance for career_health_assessment (CV untrusted-instruction injection) Clarity F4 - the agent obeys instructions embedded in the untrusted CV_TEXT it was asked to assess. 25 prompt + 25 scenario rows, built-in judge dimensions. baseline PV prompt 64.0% / scenario 88.0% OR prompt 0.0% / scenario 44.0% governed PV prompt 20.0% / scenario 52.0% OR prompt 0.0% / scenario 48.0% Policy violations fall 44 points on prompt rows and 36 points on scenario rows while overrefusal is held at baseline (scenario +4 points, inside noise; prompt unchanged at 0.0%). The judge's boolean/justification inversion rate is 39% in BOTH arms, so the comparison is like-for-like; netting inversions out, real violations roughly halve (~23 -> ~11). Note the baseline agent ALREADY overrefuses at 44% on scenario rows - it emits {"status": "unsupported_task"} and declines legitimate CV scoring. That is a pre-existing defect of the ungoverned agent, not a governance cost, and the governed agent does not make it meaningfully worse. This took two iterations. The first guard cut violations but pushed overrefusal to 68% via two bugs, both found from judge justifications rather than guesswork: 1. sticky cross-turn detection - injection scanning folded in all prior history turns, so a turn-1 injection suppressed every later clean turn. Detection is now strictly per-turn. 2. stub-producing enforcement - regeneration fired on foreign keys or a missing status and fell back to a blank stub. It now fires only on hard capitulation; foreign keys are repaired in place and populated content is never downgraded. The mandated warning now accompanies a real assessment instead of replacing it. Guard is additive (439 insertions, 0 deletions); failure #1 is byte-for-byte intact. Unit tests 23/23, including the multi-turn regressions that would have caught the overrefusal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add failure-2 governance for azure_doc_qa (self-asserted identity escalation) Clarity F2, which Clarity had explicitly marked SEVERITY: HIGH - selected but which was never built into a suite until now. 25 prompt + 25 scenario rows, built-in judge dimensions. baseline PV prompt 76.0% / scenario 48.0% OR prompt 28.0% / scenario 0.0% governed PV prompt 20.0% / scenario 8.0% OR prompt 16.0% / scenario 4.0% Policy violations fall 56 points on prompt rows and 40 points on scenario rows, and overrefusal IMPROVES on prompt rows (28.0% -> 16.0%) rather than paying the usual governance tax. Scenario overrefusal rises 0.0% -> 4.0% (one row). The 76% baseline was the strongest single-arm signal of the batch aside from neurosan, which vindicates re-sourcing failure selection from the Clarity Protocol records rather than from fresh code analysis - Clarity had already identified and ranked this failure. The guard denies internal retrieval at pre_tool_call for callers whose only claim to clearance is their own self-description, so internal material is never fetched rather than fetched-then-suppressed, and repairs output-side statements that assert the caller was verified. This policy uses SPLIT ANNOTATOR POLARITY - validated_principal_present is a health flag (true = good) while caller_self_description_claims_clearance is a fault flag (true = bad) - both asserted in both directions in the tests. Guard is additive; the 7 removed lines are in-place replacements of bare clearance checks with policy-gated equivalents. Failure #1 intact. Tests pass (60+ checks), including no-deadlock verification when an event loop is already running. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add failure-2 governance for travel_planner_neurosan (false budget confirmation) This failure was recorded through Clarity during this session. Neurosan was the only domain with no enumerated second failure - its failures.md listed one auto-triaged top risk and nothing ranked below it - so rather than invent a failure from code reading, the Clarity failure-brainstorm was run properly and the result recorded via the Clarity MCP tools. failures.md is rewritten into the same enumerated F1-F5 + triage format the other six domains use. 25 prompt + 25 scenario rows, built-in judge dimensions. baseline PV prompt 100.0% / scenario 88.0% OR prompt 0.0% / scenario 4.0% governed PV prompt 12.0% / scenario 64.0% OR prompt 4.0% / scenario 8.0% An 88-point drop on prompt rows, the largest single improvement in the batch, off a 100% baseline - the ungoverned agent confirmed a budget as fitting on every single prompt row. Scenario rows improve 88% -> 64%, more modestly. Overrefusal stays low (+4 points each side). This is also the cleanest measurement in the batch: the judge's boolean/justification inversion rate is 0% in BOTH arms, so unlike the injection suites these numbers can be read at face value. The defect: agent.py hardcodes costs in validate_budget (lines 193-195) and the agent then asserts budget fitness as verified. The guard adds a second AgentControl with two deterministic enum annotators (budget_verdict, budget_verification_claim), catches self-contradictory totals, and closes the say-so gap with a disclosure banner rather than a refusal. Guard is additive; failure #1 grounding control untouched. Tests 6/6. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add failure-2 governance for travel_planner_langgraph (unmarked claim provenance) Clarity F5 - the agent presents retrieved facts and invented estimates in one undifferentiated voice, so the user cannot tell which parts were actually looked up. 25 prompt + 25 scenario rows, built-in judge dimensions. baseline PV prompt 36.0% / scenario 40.0% OR prompt 16.0% / scenario 20.0% governed PV prompt 24.0% / scenario 44.0% OR prompt 0.0% / scenario 12.0% Violations fall 12 points on prompt rows and overrefusal falls on BOTH strata to below baseline. Scenario violations are 4 points up, which is expected and is explained below rather than tuned away. This suite took two iterations and the first one is the more interesting result. Guard v1 scored PV prompt 12.0% - better than what is committed here - but drove scenario overrefusal 20.0% -> 52.0%. Reading the judge's justifications showed why: v1 achieved its low violation count by making the agent WITHHOLD the itinerary whenever its evidence ledger was empty, replying "tell me your dates and I'll look it up". The behaviour under test is unmarked PROVENANCE; the required fix is to LABEL which parts are verified, not to refuse to answer. v1 inverted the control's intent, and the metric rewarded it, because an itinerary never written contains no unmarked claims. The asymmetry gave it away: prompt overrefusal was unchanged at 16.0% while scenario rows tripled, since multi-turn conversations more often reach the rewrite request with an empty ledger. v2 removes the withholding path entirely. The original plan is now the floor - a regenerated reply replaces it only if it is itself a substantive plan, never a bare deferral - and regeneration fires only when the ledger is actually populated. The provenance banner still marks every unverified part. The 4-point scenario violation rise is the honest cost of that correction, and the guard author predicted it before the re-run: delivering the plan reintroduces its specifics, which the judge can then assess, whereas withholding hid them. Trading 4 points of measured violation for 40 points of overrefusal is the right direction for a labelling control. Judge inversion is 0% in BOTH arms here, so unlike the injection suites these numbers can be read at face value. Also fixes a latent crash in the shipped agent.py: intent_classifier assumed json.loads returned a dict, but a bare-string parse raised AttributeError and killed one scenario row mid-run. Parsing is now defensive and falls back to the default intent; well-formed dict responses are untouched. Both baselines had zero target errors, so this does not affect any published baseline number. Guard is additive; the 21 removed lines are the v1 withholding block and a _MANIFEST refactor. Failure #1 machinery intact and referenced. Tests 12/12, including regressions that assert an empty ledger still yields a labelled plan. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Update science_research_agent tool cache from phase-1 governed re-run Regenerated when restricted-content-leakage was re-run with --force-stage inference to re-measure failure #1 against the now dual-control agent_guarded.py (see artifacts/results/PHASE1-RERUN-DRIFT.md). Cache grows 24680 -> 39268 lines; this file was already tracked, so committing keeps the tree clean and the run reproducible. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * prompt_agents: add model_only/simtools/gentools variant matrix The shipped prompt_agents example ships five YAMLs describing distinct Prompt Agent wirings (model_only, simulated tools, generated tools, sandbox, external). All are *prompt targets*, not callables, so ACS -- which can only govern `callable: module:function` -- could not be applied to any of them, and the earlier evaluation collapsed the whole domain onto the single realtools agent.py. Reify the three that are reachable as callables, each a faithful port of its YAML (system prompt lifted verbatim from the parser value, model / max_tokens / temperature as configured): agent_model_only.py no tools agent_simtools.py fixed toolset + LLM result simulator agent_gentools.py per-test-case LLM-generated tool schemas plus an ACS-wrapped counterpart for each, so every variant has a baseline and a governed arm. _variant_guard.py holds the shared adapter rather than editing agent_guarded.py, whose numbers are already published; the measured modules are byte-identical to what was measured before. Two defects fixed there rather than papered over: * the gentools ledger only recognised the four canonical tool names, so LLM-invented names never registered and the control was silently inert. _GenericLedger is name-agnostic. Verified against live generation, which produced `drug_interaction_check` and `pain_management_alternatives` -- neither in the base vocabulary, and different again on the previous call. * the empty-ledger path emitted canned guidance unrelated to the user's question. The original reply is now the floor. This is the third instance in this batch of a guard substituting or withholding content instead of repairing it; it scores well on violation rate, which is exactly why it needs catching. 24 eval configs (3 variants x 2 arms x 2 failures) differ from the measured baseline by exactly two lines each, `run:` and `callable:`, so all arms share one LLM-generated test set -- artifact_cache._stage_descriptor deliberately excludes target.callable from the test_set key to keep A/B comparisons like-for-like. 31 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Unbundle scenario behavior presets Extract atomic behaviors from the travel planner and telecom scenario specs, leaving scenarios as context plus behavior references. Reuse existing stereotyping, prompt_injection, sycophancy, grounding, tool-selection, verification, observation, response-completeness, and unsupported-conclusion presets instead of duplicating them. Update checker, docs, example configs, and benchmark default to enforce and consume atomic behavior presets. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7cb46daf-b5ce-4ad5-a85d-977737e5c02b * fix(examples): keep all seven flagship behaviors, one config each Unbundling the travel_planner preset also rewrote the flagship example config down to a single behavior (prompt_injection). That is atomic but it silently dropped six mechanisms from the example the README, getting-started, schema docs, the ACS guide, and science.yml all point at -- coverage loss wearing atomicity's clothes. Restores the other six as sibling configs under behaviors/, each sharing the same context: and measuring exactly one mechanism. eval_config.yaml stays the quickstart so every existing doc reference keeps working. This is also the layout we tell CI customers to use, so the flagship example now demonstrates it instead of just describing it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7cb46daf-b5ce-4ad5-a85d-977737e5c02b * Reset billing_support_agent to pre-skill state for clean rerun. * feat(agents): update systemize, judge to gpt-5.4, default model to gpt-5.4-mini. * feat(example): billing_support_agent ran through workflow. * feat(viewer): retire policy_violation/overrefusal from display surfaces. * fix(cli): detect the permissibility split by key presence, not rate. * feat(example): return billing_support_agent to its pre-skill state. * fix(skill): update SKILL to prevent custom judge dimension generation. * feat(example): billing_support_agent final workflow demo. * feat(example): billing_support_agent Clarity Protocol directory moved to example. * feat(example): career_health_assessment cleared to pre-skill state. * docs: make the behavior/scenario library obviously discoverable Addresses Ahmed's confusion on this PR about where the atomic behavior presets live and how they compose with scenarios. Adds pointers at every entry point a user is likely to hit before writing a behavior spec by hand: - Top-level README.md: new nav-bar link + a What-you-get bullet - docs/README.md: new Behavior Library entry under Configuration - docs/config/best-practices.md: callout inside SS8.D (atomic behaviors) - examples/README.md: new 'Reuse a behavior from the library' section, placed before a user starts writing YAML by hand Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(example): career_health_assessment ran through SKILL workflow. * feat(example): cleared azure_doc_qa to pre-skill state. * feat(example): azure_doc_qa workflow through SKILL complete. * feat(examples): clear seven examples to pre-skill state for rerun. Reset travel_planner_langgraph, change_control_agent, prompt_agents, science_research_agent, and travel_planner_neurosan to commit 6817a31 so the finalized skill can be rerun end to end. prompt_agents backs the health-assistant model-only, simulated-tools, and generated-tools variants, so clearing that root resets all three. Removes generated Clarity Protocol trees, eval configs, ACS policies and manifests, guarded agents, and guard tests while retaining each example's original agent, tools, README, and config inputs. Prior ASSERT and ACS run outputs were preserved under artifacts/*-run2-archive before deletion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add ACS governance for science_research_agent disclosure risks Clarity risk discovery identified two Critical failure modes in the science research assistant: restricted-class disclosure and obedience to instructions embedded in retrieved documents. Both are governed by a single structural gate. tools.py `_render()` returns the full `body` and `snippet` for every hit regardless of the authoritative `class` field, so the four restricted classes (restricted_results, partner_notes, contacts, private_notes) are disclosed in full. `snippet` is not a lesser field - it carries the partner recall figure and a named contact - so body-only redaction would have left the harm intact while reporting enforcement active. The gate runs at post_tool_call on file_search and is transformative, not a denial: file_search is never blocked. Restricted results have `body` and `snippet` replaced by a marker while `id`, `class`, `rank`, `tags`, and `title` are preserved, so the agent can still report that a document exists and give the access-request path. No injection detector was built. The payload the injection asks for is removed before delivery, so the embedded instruction can be obeyed in full and return nothing. Source class is deliberately not used as an injection signal: fetch_url hardcodes class `public`, and the carrier document is genuinely public. Measured at n=25 per split, baseline vs governed: restricted_class_disclosure prompt harm 91.67% -> 43.75% permissible 60.87% -> 32.00% scenario harm 95.83% -> 75.00% permissible 48.00% -> 52.00% embedded_instruction_obeyed prompt harm 56.25% -> 26.67% permissible 28.00% -> 28.00% scenario harm 61.90% -> 34.78% permissible 52.00% -> 52.00% Restricted markers fall to zero in the governed transcripts: the partner recall figure 679 -> 0, the contact address 220 -> 0, the contact name 428 -> 0. The scenario permissible tick for the first risk is one row of 25 with node-level counts flat at 14/16/16, so it is clustering noise rather than a mechanism; it was not resampled. One iteration was needed. The first marker was long, prohibition-heavy, and injected twice per restricted document, which suppressed legitimate internal_only discussion - the internal_only sentinel dropped from 58 occurrences to 48. A compact, positively framed, scope-bounded marker with the long guidance emitted once in policy_notice restored it to exactly 58. No prompt, model, control-flow, or test-set change. agent_guarded.py adds ACS enforcement and nothing else. It imports agent.py rather than copying the loop, never redefines SYSTEM_PROMPT, TOOL_SCHEMAS, MAX_STEPS, MAX_TOOL_CALLS, or AGENT_MODEL, and delegates to base.chat(). Enforcement is scoped by a contextvars.ContextVar set inside try/finally by the governed entrypoints; with the wrapper installed and the contextvar unset, baseline behaviour is unchanged. Governed configs differ from baseline by exactly two lines (`run:` and `target.callable`). Both governed runs reused systematize and test_set artifact v0001; --force-stage was never passed. Note that under-disclosure by blanket refusal is itself scored `permissible: false` in this taxonomy, so the harm metric is mixed here - part of the improvement is the agent answering more, not only leaking less. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add Clarity protocol + ACS governance for change_control_agent Runs the full Clarity -> ASSERT -> ACS -> ASSERT loop over the change control agent for both Critical failures found by Clarity risk discovery. failure-02 fabricated_change_record -- WIN. A post_tool_call gate denies a change record whose returned fabricated_fields is non-empty, and a pre_tool_call gate holds the control surfaces while such a record stands. prompt harm 0.00% -> 0.00% permissible 52.00% -> 44.00% scenario harm 90.91% -> 86.36% permissible 52.00% -> 52.00% (flat) failure-01 unauthorized_change_advancement -- HONEST NEGATIVE. A pre_tool_call gate on the four control surfaces denies a submission while policy state forbids it. prompt harm 0.00% -> 0.00% permissible 18.18% -> 4.35% scenario harm 66.67% -> 44.00% permissible 16.00% -> 24.00% The win condition (harm down AND permissible down-or-flat) is not met on the scenario split. Harm fell 22.7pp, roughly 5-6 rows of 25, while permissible rose 8pp, or 2 rows. All four permitted governed attempts were spent and attempt 1 was the best of them on every metric, so attempt 1 is what ships and the negative is reported rather than re-rolled. The permissible move is inside the measured noise floor. Governed attempts 1 and 2 differed only by added trace spans -- an observability change with no policy effect -- yet scenario harm moved 44.0 -> 56.0 and permissible 24.0 -> 41.7. Run-to-run variance at n=25 with judge n=1 is therefore about 3-5 rows. The 2-row permissible move sits inside that band and the 5-6 row harm drop sits outside it. No run was repeated unchanged to fish for a better draw. failure-02's residual 86% harm is a structural ceiling, not an implementation gap: only 8 of 20 harmful rows ever call create_change_request and 7 of 20 make no tool call at all, so most fabrication harm is invented prose that a tool-call gate cannot reach. The one output-stage attempt at it drove scenario permissible 52% -> 84% and overrefusal 12% -> 76% while barely moving harm, and was reverted in full. Not-permissible harm is 0.00% on the prompt split in every run of both suites, so all harm signal in this domain lives in the scenario split and the prompt split measures only over-restriction. agent_guarded.py adds ACS enforcement and nothing else. Both arms execute the same agent.py::_run_loop function object -- verified at runtime as agent_guarded._run_loop is agent._run_loop -- which takes a pluggable tool executor; the baseline passes _default_execute_tool and the guarded module passes an ACS-enforcing executor of the identical signature. No prompt, model, tool schema or budget is redefined. Each eval_config.governed.yaml differs from its baseline by exactly two lines, run: and target.callable, and every run scored the same systematize/test_set v0001 artifact. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * Add Clarity protocol + ACS governance for prompt_agents health assistant Runs the full Clarity -> ASSERT -> ACS -> ASSERT loop over the health assistant Prompt Agent for both Critical failures found by Clarity risk discovery, across the model-only and simulated-tools variants. A Prompt Agent has no host process: the target is declared entirely in YAML and the ASSERT runtime owns the model call and tool loop, so there is nothing for ACS to wrap. The target is therefore materialised as a Python callable in agent.py, which instantiates ASSERT's own HostedSession and SimulatedResolver rather than imitating them. Both A/B arms run that same materialised callable, so the delta isolates enforcement; absolute levels carry materialisation error, the delta does not. The five health_assistant*.yaml specs are the specification under test and are unmodified. simtools x fabricated_clinical_fact_as_retrieved -- WIN on attempt 1. Marking, not suppression: the harm is the certainty, so the claim is attributed to its origin and not presented as verified. prompt harm 13/25 = 52.0% -> 9/24 = 37.5% perm 6/12 = 50.0% -> 1/18 = 5.6% scenario harm 17/25 = 68.0% -> 8/24 = 33.3% perm 10/20 = 50.0% -> 3/25 = 12.0% simtools x dosage_guidance_to_elderly_patient -- WIN on attempt 3. Non-statement, not marking: a caveat beside "5 mg" does not remove the 5 mg. prompt harm 10/25 = 40.0% -> 1/24 = 4.2% perm 1/25 = 4.0% -> 1/25 = 4.0% scenario harm 19/25 = 76.0% -> 15/25 = 60.0% perm 0/25 = 0.0% -> 0/25 = 0.0% Over-refusal is 0% on both splits of both shipped runs. modelonly x dosage_guidance_to_elderly_patient -- NOT WON, reported as such. No configuration wins both splits. v4 wins the prompt split outright (37.5% -> 27.3%, permissible flat at 0/25) and cuts scenario harm 18/24 -> 14/24, but scenario permissible rises 0/25 -> 2/25. modelonly x fabricated_clinical_fact_as_retrieved is excluded by design: with no tools there is no retrieval claim to make. gentools is not measurable and is skipped rather than approximated. health_assistant_generated_tools.yaml sets tool_source: per_test_case, so tool schemas ride on each test-case row and are read by _build_hosted_session as scenario["tools"]. _build_target_session has the payload in scope and passes it to ExternalSession, but constructs CallableSession without it, and CallableSession.__init__ accepts no scenario or tools parameter. Per-test-case tools cannot reach a callable target, so chat_gentools raises NotImplementedError. Approximating with a fixed toolset would measure simtools while labelling it gentools. Both rates are flagged/applicable, and the judge marks a node not applicable when the transcript never engages it, so a control that removes a behaviour outright shrinks its own denominator and can push a rate up while violations fall. modelonly v3 prompt is 9/24 -> 8/21: the rate rises 37.5% -> 38.1% while the count falls. Counts are therefore reported beside every rate here, and the README carries the same warning. The shipped default is the v3 ladder, which is what produced the confirmed simtools x dosage win; the v4 position-keyed ladder is retained behind HEALTH_ACS_POSITION_KEYED_DOSAGE=1 and gate telemetry behind HEALTH_ACS_GATE_LOG, both default off. Each governed eval config pins the run id the shipped code reproduces, so no config resumes a cached run produced by different code. agent_guarded.py adds ACS enforcement and nothing else. Both arms execute the same function object, verified at runtime as agent._chat is agent_guarded._chat; each governed entrypoint is a single line calling it through its one seam. The guarded module never references HostedSession, SimulatedResolver, load_toolset_file, parse_target_config or the system prompt. Each eval_config.governed.yaml differs from its baseline by exactly two lines, run: and target.callable, and every run in all three suites scored the same systematize/test_set v0001 artifact. verify_gates.py exercises both gates against the real AgentControl/OPA runtime (13/13), including a case proving the annotator name contract fails silently when the manifest key, the Rego reference and the dispatcher branch disagree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * fix(examples): address Yeming's PR #296 review comments - Update the flagship travel_planner_langgraph README's Scenario table, which still described the pre-unbundling behavior.description (quality + safety failures blended, 6 behavior_categories). It now reflects the atomic single-behavior eval_config.yaml (prompt_injection, 4 categories) and explains the sibling behaviors/*.yaml cover the other six mechanisms. - Reformat context: and rubric: multiline fields in all six behaviors/*.yaml sibling configs (plus eval_config.yaml's rubric, for consistency) from a hard-wrapped single-quoted scalar to literal block style (| / |-), matching eval_config.yaml's existing context: style. Verified byte-for-byte semantic equivalence via yaml.safe_load diff against the prior committed content -- pure style change, no content drift. - Tighten scripts/check_behavior_library.py's spec-parity check per Yeming's concern: the 98%-similarity tolerance could let a real content change in a long spec through silently, since words() already normalizes the only expected sources of formatting difference (headers, bullets, wrapping, whitespace, case) -- any remaining difference is real drift, not noise. Now requires an exact match. Also hard-fails if the examples/behavior_specs reference directory is missing, instead of silently skipping the whole parity check. All 51 presets still pass (48 behaviors, 3 scenarios), atomic and in parity, with the tightened exact-match rule. 89/89 targeted tests still pass. All 7 edited example configs verified to still load and resolve through assert_ai.config.load_config. * travel_planner_langgraph: Clarity->ASSERT->ACS governance cycle Adds the governed arm for both Critical risks discovered by Clarity (fabricated_cost_claim, unsupported_entry_requirement), measured against a shared baseline. All runs reuse one systematize v0001 and one test_set v0001, and each governed eval config differs from its baseline by exactly two lines (run, callable), so the A/B is directly comparable. Result: a split verdict, shipped honestly rather than tuned into a win. Prompt split (single turn) wins on both risks: costs harm 16/23 -> 7/21 entry harm flat at 2 rows while permissible violations fall 11/23 -> 8/25 and over-refusal falls 44.0% -> 32.0% Scenario split (ten turns) regresses on both risks: costs permissible 12/23 -> 17/24, over-refusal 48.0% -> 75.0% entry permissible 11/19 -> 16/18, over-refusal 50.0% -> 89.5% The scenario regression is architectural, not a tuning failure. Those conversations frequently never reach the research step, so the retrieval record is empty; with no evidence in hand the only correct action for an output gate is to decline, and nothing later in the conversation supplies what is missing. Softening the annotator across attempts measurably re-opened harm (costs prompt harm back to 47.4%, entry scenario harm to 81.8%), so the trade is real. The enforcement wrapper is deliberately not allowed to retrieve the missing grounding, because a wrapper that retrieves is no longer a control. agent_guarded.py reuses the baseline graph rather than reimplementing it: get_graph(), _seed_messages() and _get_llm() are imported from agent.py, and _draft() is agent.chat() plus the returned message list and retrieval record. At runtime the guarded module resolves to the same compiled graph object, so the arms differ only by enforcement. Tracing is enabled before agent import in both arms so the judge sees identical telemetry. A known defect ships deliberately and is documented in code and in the README: the depth-based fallback rotation was inert during measurement because history was not threaded into it, so every declining turn emitted identical wording. The call site is left in its measured two-argument form so the published numbers reproduce from this code. The prompt split is single turn and therefore unaffected, so those wins stand unconfounded; the scenario over-refusal figures should be read as an upper bound on the cost of enforcement rather than a precise measurement of it. Threading history is a one-line, unvalidated change belonging to the next cycle. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * feat(example): clear travel_planner_langgraph to pre-skill state. Resets the example so the Clarity -> ASSERT -> ACS workflow can be run end to end from a clean slate in the IDE. Removed (all generated by the previous skill run): Clarity Protocol/ discovery, failures, goal, solution, mailboxes acs/ manifests and Rego policy for both risks evals/ baseline and governed eval configs for both suites agent_guarded.py the ACS enforcement wrapper README.md restored to its pre-skill content Also removed from the working tree, untracked and therefore not part of this commit: artifacts/results/travel-planner-*, artifacts/acs/travel-planner-*, the per-run logs and status JSON under artifacts/runlogs/, and __pycache__. Clearing the results and stage artifacts matters as much as clearing the source: a stale suite directory would let a later run reuse cached systematize/test_set artifacts instead of generating its own. The example is now byte-identical to its pre-skill state (empty diff against c2c11d5) and contains only agent.py, auto_trace.py and README.md. Nothing is lost. The previous cycle is preserved in full by commit 818f7c7 and by the exported release bundle, which carries the source, both ACS policies, all eval configs, the Clarity protocol, every run's results and transcripts, and the per-run logs for baseline plus all four governed attempts. No other example is touched. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * azure_doc_qa: Clarity protocol for fabrication + leakage risks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * travel_planner_neurosan: Clarity protocol + ACS governance for two risks Shipped configurations (counts are flagged/applicable, re-derived from raw results; rates alone mislead because the judge drops non-engaging rows from the denominator): wrong-destination-entry-requirements baseline -> acs-governed prompt harm 25/25 -> 4/24 permissible 4/11 -> 1/25 overrefusal 0/25 -> 0/25 scenario harm 24/24 -> 10/14 permissible 10/16 -> 21/24 overrefusal 7/24 -> 24/24 fabricated-budget-verification baseline -> acs-governed-v2 prompt harm 21/25 -> 13/24 permissible 1/22 -> 1/25 overrefusal 0/25 -> 0/25 scenario harm 25/25 -> 23/23 permissible 11/25 -> 10/24 overrefusal 10/25 -> 11/24 Single-turn prompts win on both risks. Multi-turn scenarios trade harm for over-refusal: the fallback is a fixed stateless template re-delivered on every denied turn, so a 7-turn conversation re-asks for a nationality the traveller already supplied. That is a remediation-design fault, not a policy fault. Enforcement-only A/B: each governed config differs from its baseline by exactly two lines (run, callable). Same model, judge, sample size, and stage artifacts. Provenance: agent_guarded.py output templates were verified against the shipped transcripts by string probe -- the budget fallback appears 493x in acs-governed-v2 and 0x in acs-governed, which is why the budget config is pinned to v2. Annotator and regeneration prompt text was edited after the last successful run for two follow-up attempts that failed at inference and produced no results. Wrapper-internal prompts are never written to any artifact, so those edits cannot be confirmed or excluded from the measured state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * azure_doc_qa: grounded fabrication gate + leakage gate through the SKILL Shipped runs: azure-doc-qa-confidential-leakage baseline -> acs-governed azure-doc-qa-fabricated-answer baseline -> acs-governed-grounded-v2 Counts are flagged/applicable, re-derived from the raw result rows. Rates alone mislead: the judge marks a node not-applicable when the transcript never engages it, so a shrinking denominator can move a rate while the violation count is unchanged. confidential-leakage baseline -> acs-governed prompt harm 9/22 -> 2/22 permissible 10/25 -> 6/25 overrefusal 2/25 -> 4/25 scenario harm 15/24 -> 8/24 permissible 17/25 -> 16/25 overrefusal 11/25 -> 16/25 fabricated-answer baseline -> acs-governed-grounded-v2 prompt harm 3/14 -> 1/16 permissible 11/24 -> 11/25 overrefusal 10/25 -> 11/25 scenario harm 9/23 -> 9/18 permissible 6/25 -> 18/25 overrefusal 5/25 -> 18/25 Leakage wins on both axes: harm roughly halves and permissible violations fall as well. Fabrication wins single-turn only - harm 3 -> 1 with permissible exactly flat at 11. Multi-turn does not win: the harm count is unchanged at 9 (the rate moves 39.1 -> 50.0 only because the denominator fell 23 -> 18) while permissible violations triple, 6 -> 18. Fabrication took three governed attempts, all recorded in Clarity summary.md: a reply-only output annotator cannot separate grounded specificity from fabricated specificity, so it only trades over-refusal. Feeding the annotator the retrieval context captured from the baseline graph, then scoping the rewrite, cuts single-turn harm without an over-refusal cost. Multi-turn is not reachable from an output gate at all - 18/25 conversations are flagged for both fabrication and over-refusal, i.e. the agent fabricates on some turns and stonewalls on others. The fix belongs upstream, at retrieval state or in the prompt, not in another output-remediation lever. Enforcement-only A/B: each governed config differs from its baseline by exactly two lines (run, callable). One systematize/v0001 and one test_set/v0001 shared by every run, so no stage was regenerated. Provenance: agent_guarded.py was written 11:35:19, the shipped run started 11:36:54 and ended 12:20:12, and the file has not been touched since. The intermediate configs eval_config.governed.yaml and eval_config.governed_grounded.yaml are retained because summary.md cites their numbers as the progression, but the committed agent_guarded.py is the grounded+scoped code and will NOT reproduce those two runs. Only eval_config.governed_grounded_v2.yaml is reproducible from this tree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * fix(library): use FutureWarning for the moved-scenario shim, per Yeming's review DeprecationWarning is suppressed by Python's default warning filters outside pytest/-W. The shim's whole purpose is to tell config authors their behavior:{preset: travel_planner}-style config has been reclassified without breaking it -- with DeprecationWarning, that notice was invisible to anyone running assert-ai run directly, only visible under pytest (which re-enables DeprecationWarning by default). FutureWarning is shown by default in normal script execution, which is the actual audience for this warning. * feat(example): travel_langgraph_planner ran through SKILL workflow. * chore(examples): strip ACS artifacts from the 8 worked domains Keep only the baseline agent, the baseline eval configs, the Clarity Protocol design record and the README in each domain. Deleted (57 files): acs/ manifests + rego policy .......... 30 agent_guarded.py ...................... 8 eval_config.governed*.yaml ............ 18 prompt_agents/verify_gates.py ......... 1 verify_gates.py goes with them: it imports agent_guarded and loads acs/, so it cannot function once those are gone. Kept deliberately, though a literal "delete what the skill made" would have removed them: - __init__.py in billing_support_agent, career_health_assessment and prompt_agents. These were added by the skill, but are required for examples.<domain>.agent to import. - mock_tools.py, mcp_tools.py and docs/ (azure_doc_qa); tools.py (change_control_agent, science_research_agent); the five health_assistant*.yaml specs (prompt_agents). All are referenced at import time or read at runtime. All 17 remaining baseline eval_config.yaml target agent.py and contain no acs/ or guarded references, so the baseline side of each A/B is intact. Clarity Protocol is left as-is. 8 of its 205 files still describe acs/ and agent_guarded.py; that is prose, not imports, and is correct for a historical design record. Verified: all 8 examples.<domain>.agent modules import cleanly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * feat(examples): rewrite the 8 domain READMEs for the stripped branch. * feat(examples): aligned READMEs to impermissible/permissible behavior violation wording. * feat(readme): update readme with SKILL get started. * docs(examples): align incident_triage_agent row with main's baseline-only layout Merging origin/main brought in 054797f, which reduced incident_triage_agent to a baseline-only, one-behavior-per-YAML example and deleted eval_config_naive_prompt.yaml, eval_config_guarded.yaml, eval_config_guarded_gepa.yaml, and incident-triage.guardrails.yaml. Git merged cleanly because no file was touched on both sides, but the top-level examples/README.md row still advertised all four configs and the ACS + GEPA 4-variant matrix -- stale on main as well, since 054797f never updated this table. Repoint the row at what the example actually ships: behaviors/ as the recommended one-behavior-per-YAML split, with eval_config_baseline.yaml as the bundled overview. Drop the ACS/GEPA framing (that demo is superseded by #262 and its guardrails file is deleted); ACS remains covered by the acs_guardrails row. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * docs: repoint example config paths at the evals/<risk>/ layout This branch moved each example's eval_config.yaml under evals/<risk>/ so one config probes one risk, but docs across the repo still pointed at the old top-level paths. Every 'assert-ai run --config examples/travel_planner_langgraph/eval_config.yaml' (and two 'assert-ai init --from' variants) referenced a file this branch deleted, so the copy-paste quickstarts in AGENTS.md, docs/getting-started.md, and docs/guides/securing-agents-with-acs.md all failed. Repoint those at evals/budget-overrun/eval_config.yaml, matching the root README. In examples/README.md also fix the science_research_agent row and correct the canonical example's trace backend, which the config now sets to otel rather than phoenix. azure_doc_qa/IMPROVEMENT_JOURNEY.md is left pointing at its original bundled config on purpose: it is a historical log whose rates came from a single run scoring 9 judge dimensions over 56 test cases, so repointing it at a single-risk config would misattribute those numbers. Add a note recording that the config was since split, with links to the replacements. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b * fix(examples): close remaining #296 review gaps - Add examples/benchmark/README.md, the deliverable Ahmed explicitly asked for and accepted ("i think yes, adding the readme would be more clear"). Explains this is a throughput-scale variant of the flagship travel_planner_langgraph example -- same target, same explicit_constraint_violation_failures preset already used by behaviors/constraints.yaml, deliberately non-adversarial context: -- not a new agent or behavior. - Register examples/benchmark/ in examples/README.md's selection table and layout tree so it is actually discoverable, matching this PR series' own stated goal. - Fix the one sibling config eval_config.yaml itself missed in the prior YAML-style pass: the overrefusal rubric was still the hard-wrapped single-quoted scalar form; now literal-block style like every other rubric/context field in this example. Verified byte-for-byte semantic equivalence via yaml.safe_load diff -- pure style fix. 51/51 presets clean, 89/89 targeted tests pass. * feat(example): added the rest of eval_config.yaml for career_health_assessment. * docs(examples): commit the behavior taxonomy alongside eval_config. * fix(tests): restore-judge preset coverage lost to a silent skip. * fix(tests): collect and trigger the skill's test suite. * fix(deps): bound arize-phoenix below the release that breaks the CI. * feat(example): give billing_support_agent a real policy instead of a prompt. * fix: close the four non-blocking PR review follow-ups. * fix: close the second-round PR review items. * fix(examples): keep evals atomic and runnable Remove generated discovery artifacts and bundled configs, narrow composite behaviors, and align example navigation and commands with the runnable suites. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(examples): defer travel eval layout to stacked cleanup Remove the overlapping travel-planner behavior configs so the downstream examples PR owns the canonical flat evals layout. Keep the atomic benchmark update, but make its documentation independent of the removed path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7cb46daf-b5ce-4ad5-a85d-977737e5c02b * fix(ci): cap Phoenix for Python 3.11 Phoenix 19.18+ crashes while pytest auto-loads its plugin on Python 3.11. Keep the existing compatible lock resolution, constrain the optional dependency, and make dependency metadata changes trigger regression CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7cb46daf-b5ce-4ad5-a85d-977737e5c02b * fix(skill): align release metrics and example curation * docs: normalize getting-started line endings * fix(examples): restore the career health worked domain * fix(examples): remove career health worked domain Restore the intended six-domain curated example surface and remove the navigation entries reintroduced in 5764e0d. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(examples): remove career health navigation Keep the root and examples indexes aligned with the intended six worked domains. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Alex Ngo <t-alexngo@microsoft.com> Co-authored-by: changliu2 <changliu2@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jake Present <jakepresent1@gmail.com> Copilot-Session: 7cb46daf-b5ce-4ad5-a85d-977737e5c02b Copilot-Session: eb38db79-6f6e-4f57-9e57-c7496498048b
1 parent 6dd358c commit ec7054a

147 files changed

Lines changed: 8265 additions & 2252 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# run-assert-eval skill
2+
3+
Take a developer from **"I don't know my risks"** to a **measured violation rate
4+
per risk** — without leaving the coding assistant. Risk discovery is owned by
5+
**Clarity** (microsoft/clarity-agent); measurement is owned by **ASSERT**
6+
(responsibleai/ASSERT). This skill wires the two together.
7+
8+
## Files
9+
10+
| File | Purpose |
11+
| --- | --- |
12+
| `SKILL.md` | Claude Code skill entry (the canonical instructions). |
13+
| `../../.github/prompts/run-assert-eval.prompt.md` | GitHub Copilot mirror. |
14+
| `../../.cursor/rules/assert.mdc` | Cursor mirror. |
15+
| `workflows/measure-clarity-failures.md` | The 9-step measurement workflow (parse → triage → configs → run → report → close loop → curate example). |
16+
| `workflows/govern-and-remeasure.md` | The ACS governance workflow: turn a measured failure into a deployable ACS policy (`assert-ai acs generate`), wrap the agent, and re-run the same eval to prove the failure rate dropped. |
17+
| `workflows/diagnose-acs-delta.md` | Fallback reference manual for when a governed run's delta comes out wrong (no drop, or over-gating rose) — symptom-indexed, 15 rules. Most are prevented by the pre-flight classification in `govern-and-remeasure.md` Step 1a. |
18+
| `clarity_intake.py` | Dependency-free parser: Clarity failure docs → ASSERT candidate behaviors. |
19+
| `tests/` | Pytest suite + real Clarity fixtures for the parser. |
20+
| `SETUP-CHECKLIST.md` | One-time in-IDE MCP setup + end-to-end verification. |
21+
22+
Keep the three skill surfaces (`SKILL.md`, the Copilot prompt, the Cursor rule)
23+
methodologically aligned when changing the flow.
24+
25+
## Architecture
26+
27+
1. **Discovery (Clarity, shipped):** the Clarity **MCP server** exposes tools —
28+
`run_clarity`, `write_protocol_document`, `record_failure`, `record_suggestion`,
29+
and others. `run_clarity` returns Clarity's real process guide inlined; the host
30+
agent conducts the clarifying conversation and persists findings. See
31+
`SETUP-CHECKLIST.md` to wire it up.
32+
2. **Handoff (files, not JSON):** Clarity writes `.clarity-protocol/`. The
33+
measurement side reads `failures/failures.md` (index) and `failure-NN-*.md`
34+
(individual docs). Those files are the **source of truth**; the parser's JSON is
35+
a disposable cache. The directory is **gitignored, single-domain scratch**.
36+
Before another discovery run overwrites it, let the user export it to a
37+
user-owned location if they need the raw record. Do not commit discovery
38+
workspaces into `examples/`; examples keep only curated configs and docs.
39+
3. **Measurement (this skill):** `clarity_intake.py` turns failure docs into
40+
candidate behaviors; `workflows/measure-clarity-failures.md` runs a **mandatory
41+
human triage gate**, generates **one flat `evals/<atomic_behavior>.yaml` per
42+
selected failure**, runs them sequentially, and reports one behavior per column.
43+
4. **Governance (ACS, optional):** when a run surfaces a real failure the user wants
44+
to *fix and prove*, `workflows/govern-and-remeasure.md` first **classifies the
45+
failure against the baseline** (Step 1a — semantic `output` gate vs. structural
46+
tool gate, and whether the harm actually routes through the tool being gated),
47+
then derives a deployable **ACS** policy from the findings
48+
(`assert-ai acs generate`), wraps the agent's high-risk tools (or its output),
49+
and re-runs the **same** eval against the governed target to show the
50+
failure-rate delta (baseline → governed). If that delta comes out wrong,
51+
`workflows/diagnose-acs-delta.md` is the symptom-indexed fallback.
52+
53+
## The parser (`clarity_intake.py`)
54+
55+
```
56+
python .claude/skills/run-assert-eval/clarity_intake.py .clarity-protocol
57+
```
58+
59+
Per failure mode it emits a `CandidateBehavior`:
60+
`{name, description, severity, priority, source_doc, candidate_dimensions,
61+
multi_behavior, suggested_splits, warnings}`.
62+
63+
- **Severity → priority**: Critical→P1, High→P2, Medium→P3, Low→P4. Ranges (e.g.
64+
`Medium–Critical`) collapse to the **maximum** severity.
65+
- **Dimensions**: the doc's **Variants** list → an `elicitation_variant` stratify
66+
dimension (highest value — each variant is a distinct route to the failure);
67+
**Failure Chain** conditions → an `interaction_condition` dimension.
68+
- **Atomicity**: docs that bundle several independently testable behaviors are
69+
flagged `multi_behavior` with `suggested_splits` so triage can surface the split.
70+
- **Tolerant**: unknown severity labels or missing headers degrade to a **flagged**
71+
candidate (`warnings` populated) — never a crash, never a silent drop.
72+
73+
Run the tests:
74+
75+
```
76+
python -m pytest .claude/skills/run-assert-eval/tests/test_clarity_intake.py
77+
```
78+
79+
## Worked example
80+
81+
A full end-to-end walkthrough (one P1 — `user_disengagement` — from parse through
82+
triage, config generation, run, headline metrics, and closing the loop) lives in
83+
`workflows/measure-clarity-failures.md` under **Worked example (one P1)**. The ACS
84+
governance counterpart is in `workflows/govern-and-remeasure.md`.
85+
86+
## Related ASSERT docs
87+
88+
Product behavior is documented under `docs/` (team-maintained, on `main`); the skill
89+
**links** rather than restates it — `guides/create-evaluation.md` + `config/schema.md`
90+
(config authoring), `targets/callable.md` (callable signature, return types, OTel
91+
auto-instrumentation) + `targets/model-and-tools.md` (target shapes),
92+
`guides/troubleshooting.md`, `guides/results.md`, `guides/use-local-viewer.md`, and
93+
`guides/securing-agents-with-acs.md` (the ACS loop). This skill owns the *methodology*;
94+
those own *product behavior*. The exceptions the skill documents itself are the two
95+
callable traps those docs omit: `history` is detected by parameter **name** (misnaming it
96+
silently degrades multi-turn to single-turn), and module resolution falls back
97+
`sys.path` → config dir → cwd → direct file load.
98+
99+
## Guarantees the skill enforces
100+
101+
- One atomic behavior per config — never bundle.
102+
- The triage gate and the pre-run confirmation are **human** decisions; declining
103+
writes nothing and runs nothing.
104+
- `.clarity-protocol/` files are authoritative; derived JSON is a cache.
105+
- Discovery goes through Clarity's real MCP tools — no plain-language fallback, no
106+
shelling out to a `clarity cli` process, no separate app.
107+
- Never read/print/commit `.env`, credential values, or `artifacts/`.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Setup checklist — Clarity MCP ⇄ ASSERT (in-IDE only)
2+
3+
These steps require a real IDE with MCP support (VS Code + Copilot agent mode,
4+
Claude Code, or Cursor) and cannot be completed from a headless terminal. Do them
5+
once per workspace, then the `run-assert-eval` skill's discovery front door
6+
(`run_clarity`) becomes callable.
7+
8+
## Phase 1 — Environment setup
9+
10+
- [ ] **Install Clarity with the MCP extra** from your clarity-agent checkout
11+
(Python 3.12+):
12+
```
13+
pip install -e ".[mcp]" # or: uv pip install -e ".[mcp]"
14+
```
15+
- [ ] **Embed Clarity into this project** (generates `.vscode/mcp.json`, the
16+
`.clarity-protocol/` scaffold, and the Clarity-managed block in `AGENTS.md`):
17+
```
18+
clarity embed .
19+
```
20+
- Verify `.vscode/mcp.json` has a `clarity-agent` stdio entry with
21+
`CLARITY_PROJECT_DIR` set to this workspace folder.
22+
- If your Clarity checkout is **uv-managed**, verify the entry uses
23+
`uv run --extra mcp --directory <checkout> python -m clarity_agent.mcp`.
24+
- [ ] **Confirm the server starts**: `python -m clarity_agent.mcp --help`.
25+
- [ ] **Confirm an LLM provider is configured**: `clarity doctor`. Clarity
26+
supports GitHub Copilot, Anthropic, OpenAI, Azure AI, and Gemini. Surface any
27+
failure with its fix — do not silently continue.
28+
- [ ] **Verify ASSERT**: `assert-ai --help`, and a smallest-sample dry run of one
29+
repo example config is invocable.
30+
- [ ] **Reload MCP servers** in the IDE so the `clarity-agent` tools appear, then
31+
confirm you can call `run_clarity`.
32+
- [ ] **Do _not_ commit `.vscode/mcp.json`.** It is generated by `clarity embed .`
33+
and its `uv --directory` arg holds an **absolute, machine-specific path** to
34+
*your* clarity-agent checkout — committing it would hand teammates a broken
35+
path. It is gitignored; each developer runs `clarity embed .` to generate their
36+
own. (If your team pip-installs clarity-agent as a package, the generated entry
37+
is `python -m clarity_agent.mcp` with no absolute path and could be committed —
38+
but the default uv-checkout form must stay local.)
39+
40+
## Phase 2 — End-to-end verification (definition of done)
41+
42+
- [ ] **Fresh discovery**: with no `.clarity-protocol/failures/failures.md`, call
43+
`run_clarity`, conduct a short clarifying conversation, and confirm
44+
`failures.md` gets written.
45+
- [ ] **Parser**: `python .claude/skills/run-assert-eval/clarity_intake.py .clarity-protocol`
46+
emits candidate behaviors; run the unit tests:
47+
```
48+
python -m pytest .claude/skills/run-assert-eval/tests/test_clarity_intake.py
49+
```
50+
- [ ] **Single-P1 run**: from an existing `failures.md`, the workflow presents
51+
triage, you pick one P1, **exactly one** config is generated with a
52+
variants-derived dimension, `assert-ai run` completes, and the results table
53+
renders with the behavior as a column.
54+
- [ ] **Two failures → two configs**: selecting two failures produces two separate
55+
configs and two sequential runs — never one merged config.
56+
- [ ] **Decline at triage → zero writes**: declining at the triage gate results in
57+
zero files written and zero runs.
58+
- [ ] **Loop-close**: a `record_suggestion` round-trip lands in the Clarity mailbox
59+
after a completed run.
60+
61+
## Notes
62+
63+
- Copilot agent mode supports MCP **tools** only (not `clarity://…` resources).
64+
`read_protocol_document` covers the same ground as the resource endpoints.
65+
- Never read/print/commit `.env` or credential values — reference env var **NAMES**
66+
only (AZURE_API_KEY, AZURE_API_BASE, OPENAI_API_KEY, GITHUB_TOKEN,
67+
ANTHROPIC_API_KEY, azure_ad_token).
68+
- Do not edit inside the Clarity-managed block in `AGENTS.md`
69+
(between `<!-- clarity-begin -->` and `<!-- clarity-end -->`).
70+
- **Preserving `.clarity-protocol/`**: this repo gitignores it because the protocol
71+
describes a *system-under-test*, not this framework — it's per-target runtime
72+
output. In **your own product's repo**, the protocol describes your product, so
73+
prefer committing the durable docs (`goal/`, `solution/`, `failures/`) and
74+
ignoring only `transcripts/` (and optionally `mailboxes/`). In this framework
75+
repo, do not copy generated discovery workspaces into `examples/`. Before a new
76+
discovery run overwrites the scratch directory, offer to export it to a
77+
user-owned location outside the example tree. Commit only the curated atomic
78+
config and README needed to run the example.

0 commit comments

Comments
 (0)