[feat] Judge whether team skills load when they should, without writing a scorer
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Feature
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- developer-experience, testing
Research direction
Start with src/skill-use.ts:264, src/session-flow/session-work.ts:104-131, and src/session-flow/scoring.ts:242, then trace evidence creation and session scorer resolution. The issue names unit and end-to-end coverage for capture, manifest masking and filtering, routing settings, and scoring; begin by mapping those paths to the acceptance criteria. Done means the evidence, generated routing questions, batching and on-demand scoring, and both scorer guides satisfy the listed criteria.
Written by the indexing model from the issue text.
Description
Parent: #408
Goal
#408's trigger quality asks whether a skill is loaded where it should be, and only there. Today a session judge cannot tell. Its evidence.json holds the subject, the kind and the gaps (judgeSession, src/session-flow/session-work.ts:104-131). It does not say which team skills the session was delivered, or which it loaded. used.skills goes on the label, but the judge never reads it.
Give the judge both, and let a team turn on routing questions for its skills in scorers/scoring.yaml. No scorer file to write, no new command.
Example
A team that judges finished sessions turns it on:
# scorers/scoring.yaml
sessions: true
skill_routing: true
A member finishes a session that asked for a review. It had the root skill review-notes and the skill release from the ops skills namespace, and loaded review-notes. The judge reads:
{
"subject": { "platform": "claude-code", "sessionId": "7c1e…" },
"kind": "session",
"gaps": [],
"skills": {
"available": [
{ "name": "release", "namespace": "ops", "description": "Cut an npm release: bump, changelog, tag, publish. Use when asked to release or publish a version." },
{ "name": "review-notes", "description": "Write review notes for a PR. Use when asked to review a branch or a PR." }
],
"used": ["review-notes"]
}
}
One judge call answers one routing question per skill. On demand:
$ teamai score --session 7c1e… --scorer teamai:routing:ops:release --scorer teamai:routing:review-notes
ℹ teamai:routing:ops:release: idle (score 1, passed). Reasoning: ~/.teamai/scores/reasoning/4b1d….md
ℹ teamai:routing:review-notes: fit (score 1, passed). Reasoning: ~/.teamai/scores/reasoning/8e07….md
After a few weeks, the scorer view counts each skill by value (#1472):
teamai:routing:ops:release · session · 30 days
label score labels
fit 1 31
idle 1 112
extra 0 6
missed 0 9
precision(ops:release) ≈ 31 / (31 + 6) = 84% loaded where it was not needed 6 times
recall(ops:release) ≈ 31 / (31 + 9) = 78% not loaded where it was needed 9 times
The team rewrites the description of release. It splits the view at that commit (#1182) to see whether the failed share drops, and reads missed in the counts of the weeks after it. A rewritten description stays in the same measurement.
The rubric, the same for every skill:
fit 1 <skill> is in skills.used, and the session needed what its description says.
idle 1 <skill> is not in skills.used, and the session did not need what its description says, or another skill or the agent's own steps did it.
extra 0 <skill> is in skills.used, and the session did not need what its description says.
missed 0 The session needed what the description of <skill> says, <skill> is not in skills.used, and nothing else did it.
Judge only <skill>, against its entry in skills.available.
A scorers namespace turns it off for its members, as it does sessions:
# scorers/ops/scoring.yaml
skill_routing: false
A team that wants its own rubric leaves skill_routing off and writes a scorer per skill.
scorers/ops/routing-release.md:
---
labels:
- { value: fit, score: 1, description: "release is in skills.used, and the session needed it." }
- { value: idle, score: 1, description: "release is in skills.available, is not in skills.used, and the session did not need it, or another skill or the agent's own steps did it." }
- { value: unavailable, score: 1, description: "release is not in skills.available." }
- { value: extra, score: 0, description: "release is in skills.used, and the session did not need it." }
- { value: missed, score: 0, description: "The session needed what the description of release says, release is not in skills.used, and nothing else did it." }
passingScore: 1
samplingRate: 100
sessions: true
---
Judge only the team skill `release`, against its entry in `skills.available` in `evidence.json`.
With batch: true in scorers/scoring.yaml, a session's scorers share one judge call. Each scorer samples sessions on its own, so ten per-skill scorers at 25% still reach about 94% of sessions, with about a quarter of the answers. At 100% the same launches answer every scorer. To spend less, lower daily_limit, not samplingRate.
Scope
Evidence
- At session start, the capture records the frontmatter
descriptionof each delivered team skill, read from the sameSKILL.mdit already checks against the delivery record (deliveredTeamSkills,src/skill-use.ts:264), and the skill's namespace. The session's own pull adds the skills it delivers with the description they then have, as it adds their files. A later pull changes nothing. - The namespace is the skills namespace pull delivered the skill from, for the namespaces active at that capture.
collectTeamRepoSkillskeeps only the first namespace of a name, and the delivery record is path to hash, so neither gives it alone. skills.availablelists the team skills the session's captures recorded under the session tool's skill roots and the shared roots. A copy installed only for another tool is not listed. When captures differ, the list is their union and the latest description wins. A root skill has nonamespace.skills.usedis the label'sused.skills, unchanged: bare names.- Descriptions are masked with the scope's
maskbefore they go into the manifest, and count toward the evidence cap as the manifest already does. - A capture that read the delivery record and found no team skills gives
skills: { available: [], used: [] }and no gap. When no capture of the session recorded descriptions, for example a capture written before this change, there is noskillskey and the evidence gets a new gap,skills_unknown. - The control of a tried scorer file (#1399) gets the session's own captures instead of none (
src/session-flow/scoring.ts:242), so it keepsskills.available. Itsskills.usedholds only skills the first prompt itself invoked. Without the list, a per-skill scorer passes the control asunavailableon almost every try.
Routing questions
skill_routingis a key ofscorers/scoring.yaml, off by default, and resolves assessionsdoes: an active scorers namespace'sscoring.yamloverrides the root file, and one that setsfalseturns it off. It acts only where session judging is on. There is no member key inconfig.yaml.- A session with routing on gets one question per skill in its
skills.available, and none for any other skill. A session withskills_unknowngets none. - The scorer name is
teamai:routing:<skill>for a root skill andteamai:routing:<namespace>:<skill>for a skill from a skills namespace. A namespace cannot contain:, and a--scorervalue with a/is read as a file, so:is the separator. Two namespaces'releaseare never counted together. In the rubric,<skill>is the bare name, matched againstskills.available[].namewith its namespace, and againstskills.used. - The names are generated and reserved: a scorer file name cannot contain
:. This reuses thegeneratedpin flag of #1180's correctness scorers, and adds resolving generated names for session entries andscore --session --scorer. - A session's routing questions always go in one judge call, whatever
batchsays. Withbatch: true, the team's session scorers that resolve to the same judge tool and model join that call, as batching does. Withbatchunset, the routing call takes no team scorer. On demand, the routing questions of one command share one call, without--batch, under a lease too. On demand, a routing name resolves whateverskill_routingsays, and only for a skill in that session'sskills.available; otherwisescorer_not_found. - There is no
samplingRate: every finished session the judge leases gets them. Routing launches count toward the samedaily_limitas the team's session scorers, and sessions over it wait for a later day. Withbatch: true, a session costs one launch. - The rubric ships with teamai. Its
sourceHashcovers the rubric with the skill's name filled in, not the description, so a rewritten description stays in the same measurement. A teamai release that changes the rubric changes the hash, as a team editing a scorer does.
Acceptance criteria
- The session above produces the
evidence.jsonabove, underteamai score --sessionand under a background session lease. - A pull after the session's own pull that changes the description of
releaseleaves the description in the evidence as it was. - A skill from a namespace the member was not delivered, or installed only for another tool, never appears in
skills.available. - A description that holds a declared team secret reaches the evidence masked.
- A session whose captures recorded no descriptions has the gap
skills_unknown, noskillskey and no routing question. A session whose checkout had no team skills hasskills: { available: [], used: [] }and no gap. skills.usedequals the label'sused.skillsfor the same read.- With
sessions: trueandskill_routing: trueat the root, the session above gets exactly the two labels above, from one judge launch, and so does thescorecommand above. - A member with the
opsscorers namespace active andskill_routing: falseinscorers/ops/scoring.yamlgets no routing question. - Two skills namespaces
opsandwebthat each have a skillreleasegiveteamai:routing:ops:releaseandteamai:routing:web:release, never one name. - With
batch: trueand a team session scorerreview-qualitywith nomodel, the session's routing questions andreview-qualityshare one launch. - With
skill_routingunset everywhere, no routing question is asked. - Trying
scorers/ops/routing-release.mdwithteamai score --scoreron a session that neededreleaseand loaded it gives a control ofmissed, with no warning. - With
batchunset and ten skills, every finished session the judge leases gets its ten routing labels from one launch, andreview-quality, when it samples the session, from another.
Definition of done
docs/guide/scorers.md and docs/guide/zh-CN/scorers.md, a "Skill routing" section under finished sessions, led by examples in this order: the scoring.yaml lines, the session's evidence.json, the score output, the dashboard counts with precision and recall worked out, the rubric, the namespace override, and the per-skill scorer with the batch and samplingRate note. Each example gets one or two sentences of prose, no more. The per-skill scorer is introduced by a `scorers/ops/routing-release.md`: line, so the scorer guide's example test parses it; the test creates the ops/ directory. skill-data/ gets the skill_routing line where it lists scoring settings.
- The tests in Test plan pass, including the e2e for criteria 1, 2, 5, 6, 7, 8, 12 and 13.
- The PR records one real-CLI run of a finished session with
skill_routing: trueand its labels.
Test plan
Unit tests for the capture's descriptions and namespaces, the manifest (masking, the tool filter, the gap, the empty case), and routing under root and namespace settings. E2E with a fixture session and a fake judge for criteria 1, 2, 5, 6, 7, 8, 12 and 13. One real-CLI run recorded in the PR.
Out of scope
- A setting per skill or per skills namespace, or a list of skills to leave out.
- Routing on workflow passes and benchmark trials.
- Skills that are not team skills: the member's own, plugins, and other tools'.
- Computing precision or recall in teamai (#1472 counts by value).
- List-valued answers (#1391).
Dependencies
- Blocked by: #1180. Its PR (#1379) adds the
generatedpin flag and the reserved-name rule, which routing questions reuse rather than adding a second kind:planpinsteamai:correctness:<task>the same way, and a workflow step'sscorersnow accept a name with:. The evidence part does not need it and can land first as its own PR. - Touches: #1379 (also edits
src/scorers/evidence.ts), #1269 (hand labels apply unchanged). - Reading the results: #1472 counts labels by value in the scorer view of #1182, which precision and recall need.
- Dominant language
- TypeScript
- Stars
- 5.6k
- Forks
- 471
- Avg merge
- 19h
- Merged PRs (30d)
- 415
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from Tencent/teamai-cli
-
enhancement
Difficulty 5/5 Over a week Newbie friendliness 25/100
Tencent/teamai-cli#1605 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 5/5 Over a week Newbie friendliness 35/100
Tencent/teamai-cli#1590 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
Tencent/teamai-cli#1589 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 5/5 Over a week Newbie friendliness 35/100
Tencent/teamai-cli#1579 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 20/100
Tencent/teamai-cli#1564 ·
Maintainers usually reply within 1 day
All issues in Tencent/teamai-cli
Similar issues
-
area:cli bug P4
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
bug from-studio
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
esengine/DeepSeek-Reasonix#14868 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
linxiv-dev/linXiv#384 · 1 comment ·
Maintainers usually reply within 1 day
-
factory-active task-identify-harness-labels-done task-identify-issue-type-done
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day