Skip to content

fix(email): ship with Agent Skills off until the eval gate covers them - #2848

Open
kovtcharov-amd wants to merge 3 commits into
mainfrom
fix/email-skills-off-by-default
Open

fix(email): ship with Agent Skills off until the eval gate covers them#2848
kovtcharov-amd wants to merge 3 commits into
mainfrom
fix/email-skills-off-by-default

Conversation

@kovtcharov-amd

@kovtcharov-amd kovtcharov-amd commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Every email agent launch on main today loads three skill bodies into its system prompt. That costs ~1,334 prompt tokens and cuts the bulk-triage result envelope from 6144 to 4810 — a fifth of the room the agent has to hand back a tool result (the work set, a third: 4070) — traded for a triage quality improvement that no eval has measured. #2695 turned this on by default and it is live now. After this PR the agent resolves no skill set, loads nothing, and the envelope is back to 6144, byte-identical to pre-skills. The six skills stay in the wheel and the frozen binary, inert; re-enabling is uncommenting two blocks in gaia-agent.yaml, once an eval backs them.

Two things worth flagging for review:

  • --skill-set now fails at startup instead of quietly doing nothing. That is the intended no-silent-fallbacks behaviour. The sidecar used to render the empty case as Valid sets: .; it now names the real cause and the fix.
  • SCORECARD.md is deliberately not regenerated, and is now accurate again. It contains no reference to skills or a skill-adjusted budget, and its generated_at is 2026-07-16 — three weeks before the skills landed in bbf69fd. Every number in it, including tokens_per_triage 1906.03, was measured with zero skills loaded and a full 6144 envelope. Between feat(email,skills): bundled skills + account-keyed skill-set selection #2695 merging and this PR it described pre-skills behaviour while the shipped agent had a set active; with the blocks commented out the runtime matches its measurement conditions again.

The set-resolution tests move to a fixture manifest that switches the blocks back on, so the generic skill-set machinery keeps full coverage and re-enabling lands on tests that already pass, rather than on a rewrite.

Test plan

  • python -m pytest hub/agents/email/python/tests/test_skill_sets_2466.py — 42 passed, covering both the shipped state (zero skills, no active set, 6144 envelope, loud --skill-set refusal) and the generic machinery against the fixture manifest
  • python -m pytest tests/unit/test_skill_sets.py — framework coverage, unchanged by this PR (it builds inline manifests and never reads the email agent's). One pre-existing Windows-only path-separator failure in test_manifest_error_names_the_file, present identically on main
  • Confirm the shipped agent loads nothing:
    python -c "from gaia.hub.manifest import parse; from gaia_agent_email.agent import EmailTriageAgent as A; s=parse(A.SKILL_MANIFEST).skill_sets; print(bool(s), s.set_names, s.default_set)"False [] None
  • Confirm the envelope is restored:
    python -c "from gaia_agent_email.context_budget import envelope_budget_tokens as e; print(e())"6144
  • Confirm the skills still ship: six SKILL.md files under gaia_agent_email/skills/ and SKILL_DIRS still pointing at them
  • gaia-agent-email serve --skill-set personal exits non-zero naming the cause and the fix
  • Re-enable check: uncomment both manifest blocks → the fixture-parity test confirms the sets match what the framework tests already cover

Verified locally: the 42 skill-set tests above; envelope_budget_tokens() == 6144 proven by an actual run, not by reading the code; the shipped manifest parsing to zero sets; the real --skill-set startup error text; and the ~1,334 / 4810 / 4070 token figures quoted throughout, measured rather than assumed (an earlier "278 tokens of headroom" figure did not reproduce and was corrected everywhere it had propagated).

Not finished: a full-repo baseline comparison was still running when this PR opened, so unrelated pre-existing full-suite failures are not yet distinguished from anything this change might cause. The affected test files run in isolation are byte-identical with and without this change (2 failed / 130 passed either way, both pre-existing), but CI's run against main is the authoritative comparison. util/lint.py --all could not complete locally (its tools are fetched via uvx and the sandbox blocks PyPI); note it lints src/gaia and tests only, and this PR changes no Python under either.

@github-actions github-actions Bot added documentation Documentation changes agent::email Email agent changes labels Aug 5, 2026
itomek
itomek previously approved these changes Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Verdict: Approve

This ships the email agent's Agent Skills switched off — commenting out the skill_sets:/default_skill_set: blocks in gaia-agent.yaml pending an eval that shows they help triage — and, correctly, makes --skill-set / GAIA_EMAIL_SKILL_SET fail loudly at startup instead of silently loading nothing. It's the promised #2695 follow-up, and it's done thoroughly: the one functional code change is a five-line guard, and the bulk of the diff keeps every bundled doc (README, SPEC, SKILL, both CHANGELOGs, and the docs-site pages) and the test suite honest about the disabled state.

The bottom line: nothing is deleted — the six skill files, the selection machinery, and the budget accounting all still ship, inert — so re-enabling is a one-line manifest edit, and the tests are structured to keep full coverage behind that switch. No blocking issues.

One tiny thing to fix while you're in the file: the npm SPEC.md still says the tool count is 59, but the agent's real (test-guarded) tools_count is 65 — the new Python CHANGELOG entry already uses 65.

Real-world evidence

The CI evidence stage ran the gaia-testing skill and wrote evidence-bundle.md (ubuntu, no Lemonade/GPU/NPU). It exercised the two surfaces this PR actually changes — the CLI fail-loud path and the HTTP sidecar — on the real installed console script:

$ gaia-agent-email serve --skill-set work
gaia-agent-email: error: --skill-set requested skill set 'work', but this agent declares no skill sets — Agent Skills are switched off in this build. Drop the option, or uncomment the 'skill_sets:' and 'default_skill_set:' blocks in gaia-agent.yaml.
EXIT: 2

The GAIA_EMAIL_SKILL_SET env path hits the same branch and correctly attributes the source in the message. The shipped manifest was confirmed to parse to zero sets (set_names: [], default_set: None, bool(sets): False) — not a YAML error — and the booted sidecar returned HTTP 200 on /health and /version with the blocks commented out. Spot-regression checks (--print-openapi → 38 paths, no skill-named routes; serve -h unchanged; pytest test_skill_sets_2466.py42 passed) all held.

Deferred with reason: live model triage with/without a set active (needs Lemonade — pending strix-halo lane). No Agent-UI screenshot is applicable — skill sets have no UI surface (no skill-named REST route), correctly marked N/A rather than deferred. The evidence matches the surfaces the diff changes and supports the Approve.

🔍 Technical details

🟢 Minor — stale tool count in npm SPEC.md (hub/agents/email/npm/SPEC.md:753)

The line reads "tool count is unchanged (59)", but the agent's canonical, test-guarded count is 65 (gaia_agent_email/__init__.py:124tools_count=65; gaia-agent.yaml:26; CAPABILITY_MATRIX.md), and the new Python CHANGELOG entry (CHANGELOG.md:31) already says 65. Pre-existing staleness, but this PR edits the surrounding "What skill sets do NOT change" section, so it's cheap to correct here.

- The agent's tool count is unchanged (65), and so is every tool's behaviour.

(An older Python CHANGELOG entry, CHANGELOG.md:554, also carries the stale "stays 59" — historical entry, leave it, but worth a sweep if you touch it.)

Correctness — the guard (server.py:310-318)

Clean. if not declared: short-circuits before the existing requested_set not in declared branch, which would otherwise render a contentless Valid sets: .. parser.error() exits(2) with no fall-through. Both the flag and env-var sources reach it and the message names the source correctly.

Test structure

Well thought through: the declared_sets fixture rewrites the shipped manifest with the two blocks switched back on and repoints SKILL_MANIFEST, so the generic machinery tests (selection, overrides, runtime switching, budget arithmetic) keep exercising the real bundled skills — re-enabling stays a one-line edit with coverage already green. The new test_shipped_* tests pin the OFF state (zero skills loaded even with a work mailbox, envelope back to 6144, loud CLI/agent-level refusals), and test_the_fixture_sets_match_the_commented_out_manifest_blocks parses the commented YAML so the fixture can't silently drift from the manifest. That's the right guard against the fixture rotting.

Minor coverage note (not blocking): the new if not declared: server branch is unit-tested via the --skill-set flag path (test_shipped_sidecar_rejects_a_pinned_skill_set_with_an_actionable_error); the env-var path through the same branch is only shown in the evidence bundle, not a unit test. Same branch, so low risk.

Strengths

  • Exemplary doc-sync discipline: every bundled surface CLAUDE.md calls out (README, SPEC, SKILL, npm + python CHANGELOG) plus the three docs-site pages are updated together, and the token math (16384 − 9216 − 1024 = 6144; personal −1334 → 4810; work −2074 → 4070) is internally consistent across all of them.
  • Correct application of the no-silent-fallbacks rule — a pinned set with no declared sets is a startup error, not a quiet no-op, with an actionable message naming exactly how to re-enable.
  • Nothing deleted; the change is genuinely reversible by uncommenting two manifest blocks, and the tests enforce that.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🟡 The new CHANGELOG entry claims tools_count (65) but two existing docs in the same package say the count is 59 — the live SPEC.md was partially edited in this PR without updating that number. If 65 is the current count, SPEC.md needs the same fix; if 59 is still right, the new CHANGELOG entry has a typo. One of them will mislead the next person who checks whether a change moved the tool count.

🔍 Technical details

hub/agents/email/python/CHANGELOG.md:31 (added by this PR):

`tools_count` (65), the REST/MCP contract, the connector surface, and `SCHEMA_VERSION` are unaffected.

hub/agents/email/python/CHANGELOG.md:554 (existing #2466 entry, unchanged):

the skills declare no `tools:` and no `permissions:`, so `tools_count` stays 59

hub/agents/email/npm/SPEC.md:753 (unchanged context in this PR's diff, but surrounding paragraph was edited):

- The agent's tool count is unchanged (59), and so is every tool's behaviour.

This PR modifies the paragraph around that SPEC.md line but leaves (59) in place. If the real count is now 65 (due to tools added since #2466), update SPEC.md line 753 to (65). If it's still 59, correct the CHANGELOG entry.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Verdict: Approve (one optional doc nit)

This PR ships the email agent's Agent Skills switched off — the skill_sets: / default_skill_set: blocks in gaia-agent.yaml are commented out pending an eval that shows they help, because they were turned on with no eval behind them and an active set was eating most of the bulk-triage result budget (~1,334 tokens off a 6,144 envelope). The agent still bundles the six skill files; nothing is deleted. Pinning a set now fails loudly at startup instead of quietly doing nothing.

The bottom line: it's correct, the docs are kept in lockstep across every surface, and the tests cleanly separate "what ships today" (skills off) from "how the machinery behaves when re-enabled" so turning them back on stays a one-line manifest edit with coverage already behind it. The only thing worth touching is a stale tool-count number in the npm SPEC.

Real-world evidence

evidence-bundle.md is present and adequate for this docs+config change:

  • CLI — both --skill-set personal and GAIA_EMAIL_SKILL_SET=personal produce the exact new startup error (exit 2), matching the server.py branch and every doc claim; --help reflects "none declared".
  • HTTP API — the real sidecar boots on the new manifest (503 "Lemonade unreachable" is the correct shape on a no-inference runner, not a crash), and parse_manifest(...).skill_sets returns set_names == [], default_set is None.
  • Spot regression/docs, /openapi.json (38 paths) still 200; clean boot log.
  • Targeted test_skill_sets_2466.py → 42 passed.
  • Screenshot / live inference / MCP correctly marked N/A (email is a standalone sidecar, no Agent UI surface; no MCP wiring touched). Verdict rests on static review + this evidence.
🔍 Technical details

🟢 Minor — stale tool count in npm SPEC (hub/agents/email/npm/SPEC.md:753)
The canonical tools_count is 65 (guarded across gaia-agent.yaml:26, gaia_agent_email/__init__.py:124, and the reconcile tests), and this PR's own python CHANGELOG.md entry correctly says tools_count (65). The npm SPEC still reads "unchanged (59)". It's pre-existing, but you're editing this exact paragraph ("What skill sets do NOT change"), so it's a cheap moment to correct it.

- The agent's tool count is unchanged (65), and so is every tool's behaviour.

(The older python/CHANGELOG.md:554 historical entry also says "59", but that's a frozen past-release note — leave it.)

Correctness — verified clean:

  • server.py:310-318 — the new if not declared: branch is ordered correctly (after the read-error try, before the not in declared "Valid sets:" branch), so an empty declared list yields the actionable message instead of a contentless Valid sets: . Both the flag and env-var forms share requested_set, so both reach it.
  • Construction-time path is unchanged and consistent: test_shipped_agent_rejects_an_explicit_skill_set asserts the framework's SkillSetError ("declares no 'skill_sets:' block") from src/gaia/skills/sets.py:152, which is the right message for that layer.
  • Token math is internally consistent everywhere it appears: 16384 − 9216 − 1024 = 6144; 6144 − 1334 = 4810 (personal); work → 4070.
  • Doc anchors check out: agent-skills.mdx #reference-implementation-the-email-agent matches the renamed ### Reference implementation: the email agent; SKILL.md's #skill-sets--disabled-in-this-release matches its renamed heading.

Strengths:

  • Docs kept in lockstep across README, SPEC, SKILL, both CHANGELOGs, and the docs site (composing-skills / email / agent-skills) — exactly the "a functional change updates every doc" rule, done right.
  • Test structure — splitting shipped-state (test_shipped_*) from framework-machinery tests behind the declared_sets fixture keeps full coverage for re-enabling without duplicating the file. test_the_fixture_sets_match_the_commented_out_manifest_blocks parses the commented YAML so the fixture can't silently drift from the manifest — a nice anti-rot guard.
  • Fail-loud, actionable CLI error (names the two blocks to uncomment) instead of a silent no-op — matches the no-silent-fallbacks rule.
  • The conftest.py _contain_the_skill_set_env_var fixture fixes a real cross-test os.environ leak (server pins by env write, which monkeypatch can't undo) with a clear docstring on why.

Ovtcharov added 3 commits August 6, 2026 16:54
eval evidence behind them. Loading the `personal` set costs ~1,334 prompt
tokens, which cuts the bulk-triage result envelope from 6144 to 4810 (the
`work` set, to 4070) — a fifth to a third of the room the agent has for tool
results, traded for a quality improvement nothing has measured yet.

Comment out `skill_sets:` and `default_skill_set:` in gaia-agent.yaml so the
agent resolves no set and loads no skills. `envelope_budget_tokens()` returns
to 6144, byte-identical to pre-skills.

Nothing is deleted. The six SKILL.md bodies still ship in the wheel and the
frozen binary, and SKILL_DIRS, select_skill_set(), the account-type mapping,
`--skill-set`, and the skill_prompt_tokens accounting are all intact but
inert. Re-enabling is uncommenting the two blocks, together — a non-empty
skill_sets: with no default_skill_set: fails validation.

With no sets declared a pinned set is now a startup error rather than a
silent no-op. The sidecar previously rendered that as "Valid sets: ." on an
empty list; it now names the real cause and the fix.

The set-resolution tests move to a fixture manifest that switches the blocks
back on, so the generic machinery keeps full coverage and re-enabling lands
on tests that already pass. New tests pin the shipped state: zero skills, no
active set, a 6144 envelope, and a loud refusal for --skill-set.
server.main() pins a requested skill set by writing straight into os.environ
— that is how the override reaches per-request agent sessions, so it is
deliberate. monkeypatch cannot undo it: delenv(..., raising=False) records
nothing when the variable was already unset, so the --skill-set flag tests
left 'work' set for the rest of the session.

Harmless while the manifest declared 'work'. Now that it declares nothing,
the next EmailTriageAgent built by any later test file raises SkillSetError
— which is why test_trash_restore, test_undo_reachable, and
test_zero_connector_construction failed in CI but passed when run alone.

Restore the variable in the email package's conftest, next to the other
order-dependence guard it already owns.
@kovtcharov-amd
kovtcharov-amd force-pushed the fix/email-skills-off-by-default branch from e5d102b to 9b1563c Compare August 6, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent::email Email agent changes documentation Documentation changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants