Repository navigation
[feat] Judge a session with the driver's own tool #1386
Description
Example
The caller's agent judges the session itself, for example in a subagent or a separate agent it launches: it takes the lease, gives that judge prompt.md and the evidence directory, and submits the judge's answer. teamai launches nothing on this path.
agent session (Cursor, Claude Code, an orchestrator such as T3…)
teamai score --scorer … --session … --json '{…"can":{"judge":{…}}}' → lease: prompt.md, evidence, answer schema, submit command
subagent or separate agent: reads only prompt.md and the evidence → {"label":"good","reasoning":"…"}
teamai score --lease <ref> --submit --stdin → label in scores/<member>/<YYYY-MM>.jsonl
A member works in Cursor. teamai reads Cursor sessions, but cannot launch Cursor as a judge. Their Cursor session takes the lease and judges it in a subagent:
$ teamai score --scorer review-quality --session 7c1e… --source cursor \
--json '{"driver":{"kind":"cursor","machine":"m1","workspace":"<workspace>","can":{"judge":{"isolation":"best-effort","access":"read","tools":["cursor"]}}}}'
{"data":{"lease":{"ref":"s5d1e07c2a9b40/a91c0d7e0","source":"session","scorer":"review-quality",
"subject":{"platform":"cursor","sessionId":"7c1e…"},"cwd":"<project-directory>",
"evidence":"…/scores/evidence/a91c0d7e0/","prompt":"…/prompt.md","answer":{"schema":{…}},
"submit":"teamai score --lease s5d1e07c2a9b40/a91c0d7e0 --submit --stdin"}}}
$ teamai score --lease s5d1e07c2a9b40/a91c0d7e0 --submit --stdin <<'JSON'
{"driver":{"kind":"cursor","machine":"m1","workspace":"<workspace>"},"outcome":"success",
"answer":{"label":"good","reasoning":"…"},"judge":{"tool":"cursor"}}
JSON
{"data":{"label":{"scorer":"review-quality","label":"good","score":1,"passed":true,…},"published":true}}The stored label in scores/<member>/<YYYY-MM>.jsonl records the judge:
"judge": {"kind": "model", "tool": "cursor", "isolation": "best-effort", "access": "read"}Without --json, nothing changes: the local judge runs as today, with a judge tool installed on this machine.
$ teamai score --scorer review-quality --session 7c1e…
ℹ Scoring claude-code session 7c1e… in <project-directory>A team that requires verified isolation:
$ teamai score --scorer secure-review --session 7c1e… --source cursor --json '{"driver":{…,"can":{"judge":{"isolation":"best-effort",…}}}}'
{"error":{"code":"isolation_unmet","message":"scorers/secure-review.md asks for verified isolation, and this driver offers best-effort. No judge work was taken."}}A scorer that pins a model, with a judge tool that is not a recognized tool id:
$ teamai score --scorer review-quality --session 7c1e… --source cursor --json '{"driver":{…,"can":{"judge":{…,"tools":["my-judge"]}}}}'
{"error":{"code":"judge_model_unavailable","message":"…"}}Behavior
Which path a driver should use for a session:
| Session | Who judges it |
|---|---|
| Any session, judged by teamai on this machine | teamai score --session without --json: today's choice among the installed judge tools (claude, codex, opencode families, codebuddy), whatever tool wrote the transcript. Unchanged |
A pass of a skill-driven run, where describe answers judging here |
the verified judge teamai runs for the orchestrating session's tool (#1304). Unchanged |
A pass of a skill-driven run, where describe answers judging elsewhere (Cursor, for example) |
the skill driver itself, with score --next and the run's id, best-effort, judged in a subagent. New, below |
| A session the driver wants judged by its own tool, such as Cursor, which teamai cannot launch | the driver's tool, through the lease below |
| Any session, from an external driver such as T3 | that driver's tool, through the lease below |
This table is routing advice. The CLI returns a lease for any session when --json declares can.judge, whatever its tool.
The judge tool is not the session's platform. --source names where the session is read, a platform teamai has a session adapter for (teamai session list shows them). can.judge.tools names the tools the driver runs its judge with.
| Input | Result |
|---|---|
--scorer + session, no --json |
local judge, as today |
--scorer + session + --json with can.judge |
a lease with source: session, the same shape score --next gives |
--json without driver.can.judge |
invalid_json, naming driver.can.judge |
several --scorer with --batch |
one batch lease (scorers, batched: true), as score --next gives for a pass |
several --scorer without --batch, with --json |
invalid_arguments, asking for --batch or one --scorer |
| a live lease already holds the entry (same scorer, rubric and session) | busy, the answer takeJudgeLease gives for a held entry. A released, lapsed or failed attempt does not block |
required isolation above can.judge.isolation |
new: isolation_unmet naming the files that ask for it, no attempt written. score --next keeps answering idle in that case |
scorer sets no model |
any declared judge tool is accepted: the driver runs the judge and the label records its judge.tool |
scorer pins a model |
the judge tool must be a recognized tool id (isAgentToolId), and chooseJudgeModel must resolve the model as today: a literal model name, a mapped alias, the member default or a switched model. A tool that is not recognized gets judge_model_unavailable, no lease |
--source names a platform with no session adapter (Pi, Muse, Copilot, …) |
session_not_found, with the adapter's unknown-platform message, as score --session returns today. The judge tool can still be any tool the driver runs |
| Filter of the background session judge | An explicit session request |
|---|---|
sampling (samplingRate) |
skipped |
| finished (6 hours idle, #1385) | skipped: the current session can be judged |
scoring.sessions switch |
skipped |
scorer sessions: true |
skipped: any scorer teamai pull cached |
daily_limit check |
skipped. The attempt it writes still counts toward the background day's launches |
| judged once per (scorer, rubric, session) | skipped: an earlier attempt or label does not block it, as re-scoring with --session does not today |
Skill driver without a confined judge
Today (#1304) a skill-driven run in Cursor reaches done with its pass judge work left open, because teamai has no confined Cursor judge. After this issue the driver judges that work itself:
describe → judging
here → teamai runs the confined judge (#1304), unchanged
- elsewhere → the judge work stays open for another driver's score --next
+ elsewhere → the driver takes its own run's judge work:
+ teamai score --next --json '{"run":"r3f9…","driver":{…,"can":{"judge":{"isolation":"best-effort","access":"read","tools":["cursor"]}}},"running":[]}'
+ a subagent judges each lease from prompt.md and the evidence only
+ teamai score --lease <ref> --submit --stdin
+ work a scorer requires verified for stays open for another driver, as todayturn score --next answer driver does
1 lease s5d1…/a91c… (review-quality) subagent judges it, submit → label best-effort
2 lease s5d1…/b2e0… (style) subagent judges it, submit → label best-effort
3 idle stops; secure-review (verified) is left open
Acceptance criteria
-
The explicit request leases through
takeJudgeLease: the same attempt store, lease ref, evidence directory, redaction, answer schema, submit path and labels asscore --next. Its entries come from the named session and scorers, resolved asscore --sessionresolves them today (session, scope, pins). There is no second judging path. -
This deliberately changes the
entriesinvariant "Never a session" (JudgeRequest,src/workflow/scoring.ts): an explicit request may now name a session entry. The background driver's selection is unchanged. -
The label records
judge.tool(the lease's tool, which the submit may repeat),judge.accessfrom the request'scan.judge.access(defaultfull), andjudge.model.reportedfrom the submit'sjudge.model. -
isolation_unmetat lease time is new behavior, covered by a test. Before this change it existed only on submit. -
A model-pinned scorer with
can.judge.tools: ["cursor"]and a literal model name gets a lease. Covered by a test with an empty alias map. -
--sessionwith--jsonis accepted for this request; theinvalid_argumentsmessage changes accordingly. The error rows in the input table are covered by tests. -
Without
--json,score --sessionbehaves exactly as today, its error codes included. -
Skill driver, judging
elsewhere:skill-data/workflow/references/judging.mdtells the driver, afterdone, to take its own run's judge work withscore --nextandrun, declaringisolation: best-effortand its session's tool, to judge each lease in a fresh subagent that reads onlyprompt.mdand the evidence directory, to submit each answer, and to stop atidle. With judginghere, the confined judge stays the only path: a driver never downgrades verified judging to best-effort. -
A lease taken across projects is found again. From project B,
score --nextwithrunfinds a run of project A throughfindRunConfigand leases there (runConfig,src/score-commands.ts), butscore --lease <ref>and--submitlook only in this directory's scope and the user scope (leaseConfig, same file), so neither finds that lease.leaseConfigresolves a lease in the same scopesrunConfigsearches. Covered by a test that takes the lease from project B and judges and submits it from B. A driver that follows the lease'scwdalready succeeds today; this makes a submit from B succeed as well, andlease_unknown's message stays true. Carried over from feat(workflow): add session discovery and managed public leases #1304's last review, merged unfixed. -
describe offers "Same, but without judging" for judging
elsewheretoo, and a run started with--no-judgetakes no judge work. Covered by a test of the describe answer for a session tool with no confined judge.
Definition of done
docs/guide/scorers.mdanddocs/guide/zh-CN/scorers.md: "Score a session" gains the driver example; the "Any driver" section and the isolation table link to it.scorehelp text:--sessionwith--jsonandcan.judge, and the filters an explicit request skips.skill-data/core/references/commands.mdregenerated withnpx vitest run commands-reference -u. The skill body does not change.- Real-CLI record: one lease taken and submitted for a captured session with a
--jsondriver.
Out of scope
- When scoring is offered or automatic ([feat] Publish and score sessions by run mode #1385, [feat] Default workflows a team can override, starting with session-close #1388). The friction reminder is [feat] Default workflows a team can override, starting with session-close #1388's and is not changed here.
- Background judging, unchanged.
- Reading sessions of a tool teamai has no session adapter for (Pi, for example). Such a session cannot be scored until an adapter exists.
- New judge tools teamai launches itself. [feat] One launcher for every AI call, from the tool descriptor #1389 adds none either.
Notes
- Session demand:
sessionDemandOf(src/score-commands.ts:75-86) rejects a payload today.scoreroutes at:118-161. Session resolution and evidence:src/session-flow/scoring.ts:57-117(sessionOf,scopeOf,scoreSession). Today's local judge choice:installedJudgeTools([])andchooseJudgeModel(src/session-flow/scoring.ts:124-132). - One lease path with a second entry source:
scoreOnDemandalready passesentriesintotakeJudgeLeasefor passes (src/workflow/scoring.ts). The background filters live inentries(src/session-flow/session-work.ts:256) and intakeOneLease, which also checks the daily limit (src/workflow/scoring.ts). A held entry answers{ kind: 'busy' }there. - Access is taken from the request at lease time, in
takeOneLease(src/workflow/scoring.ts); the submit payload,SubmitPayloadSchema, has noaccess. The submitted tool must match the lease's (labelMembers). - Isolation today:
takeOneLeasefilters leasable entries bymeets(src/workflow/scoring.ts); the submit-timeisolation_unmetis inlabelMembers. - Judge tools teamai launches:
src/scorers/judge.ts:36.can.judge.toolstakes any string (NextPayloadSchema,src/workflow/scoring.ts). A model-pinned scorer resolves only for recognized tool ids (src/scorers/judge-model.ts:38-47,isAgentToolIdinsrc/tools.ts:856-860), throughresolveModelValue, which takes a name the alias map does not hold as a literal model (src/models/aliases.ts:566). This issue keeps that rule. - Session platforms teamai reads:
src/session-flow/adapters/platform-facts.ts. An unsupported--sourcepassessessionOf(src/session-flow/scoring.ts:63-64);AgentSession.findthen failsno_adapter(src/session-flow/agent-session.ts:145-152), andscoreSessionreturns it assession_not_found(src/session-flow/scoring.ts:94-95). This issue keeps that public code. - The skill driver's judging today (feat(workflow): add session discovery and managed public leases #1304): describe resolves
judging(resolveJudging,src/workflow/protocol.ts).heremeans teamai runs the confined judge of the session's tool;elsewheremeans it has none (Cursor: "has no confined judge teamai runs"), and the run's pass judge work stays open in that data home for another driver'sscore --next, whichteamai score --drainreports as left open ([feat] Publish and score sessions by run mode #1385).score --nextalready takesrun(NextPayloadSchema,src/workflow/scoring.ts), andtakeOneLeaseleases only that run's entries when it is set, so the driver's own-work loop needs no new command or flag. The "Same, but without judging" option is built only forheretoday (src/workflow/drivers/skill/preflight.ts,optionsOf). This issue's explicit--sessionlease is a different entry source and is not used for passes. - Lease ref format:
s<13 hex>/a<8 hex>(src/scorers/entry.ts:16-20). - Docs to extend:
docs/guide/scorers.md:101-128("Any driver").
Activity
SaulMoro commentedon Oct 25, 2026
Rewrote the body. The example uses Cursor with a Cursor driver: teamai reads Cursor sessions but cannot launch Cursor as a judge, and Pi has no session adapter, so
--source pifailsno_adapter(src/session-flow/agent-session.ts:145-152).The body now says who judges what: teamai's own judge when it can launch the session's tool, as the skill driver does, and this lease otherwise and for external drivers. The judge tool is not the session's platform.
A tool teamai cannot launch is accepted only when the scorer sets no
model, or when it is an agent tool the aliases map (src/scorers/judge-model.ts:38-43).An explicit request lists every background filter it skips and deliberately changes the
entries"Never a session" invariant.isolation_unmetat lease time is marked as new.SaulMoro commentedon Oct 25, 2026
score --sessionpicks among the installed judge tools, whatever tool wrote the transcript (src/session-flow/scoring.ts:124-132); the routing table now says so.chooseJudgeModelresolves it, literal names included (src/models/aliases.ts:566), not only through aliases.--sourcereturnssession_not_foundfromscore --session(src/session-flow/scoring.ts:94-95), notno_adapter.--scorerwithout--batchisinvalid_arguments, a held entry isbusy, and--jsonwithoutcan.judgeisinvalid_json. [feat] One launcher for every AI call, from the tool descriptor #1389 adds no judge tool.SaulMoro commentedon Oct 25, 2026
Fixed where pass judge work goes when an unattended skill-driven run has no judge installed here. It stays open in that data home for another driver's
score --next, andteamai score --drainreports it.This issue's explicit
--sessionlease is not that path.SaulMoro commentedon Oct 25, 2026
Updated:
prompt.mdand the evidence only, and the caller submits. teamai launches nothing on this path.elsewhere(Cursor, for example), the skill driver takes its own run's judge work withscore --nextandrun, declaresbest-effort, and judges each lease in a fresh subagent. With judgingherethe confined judge stays the only path. Work a scorer requiresverifiedfor stays open, as today.heretoday.runfilter ofscore --nextand describe'sjudgingcome from feat(workflow): add session discovery and managed public leases #1304.SaulMoro commentedon Oct 26, 2026
#1304 merged with one finding from its last review left open, and this issue owns it.
score --nextwithrunfinds a run in another project, butscore --leaseand--submitlook only in the current scope and the user scope, so they cannot find that lease. Added an acceptance criterion:leaseConfigsearches the same scopes asrunConfig, with a test.SaulMoro commentedon Oct 26, 2026
The lease-scope criterion now says that a driver which follows the lease's
cwdalready succeeds today. Line numbers are updated formain.docs(readme): mark scorers for tools that judge only their skill runs (…
docs(readme): shorten the skill-driver scorers note (Tencent#1386)
docs(tools): record which tools' sessions judge their skill runs (Ten…
feat(score): lease a session, or a skill run, to the driver's own jud…