Skip to content

feat(score): lease a session, or a skill run, to the driver's own judge (#1386) - #1470

Merged
SaulMoro merged 6 commits into
Tencent:mainfrom
hz89Dev:saulmoro/1386-driver-session-judge
Oct 27, 2026
Merged

SaulMoro merged 6 commits into
Tencent:mainfrom
hz89Dev:saulmoro/1386-driver-session-judge

Conversation

@hz89Dev

@hz89Dev hz89Dev commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A Cursor session, or an orchestrator whose tool teamai has no verified judge for here, could not get its work judged. score --session <id> --json <driver> was rejected, and a skill-driven run reached done with its scored pass left open. Now the driver judges with its own tool, through the same lease and submit path score --next uses:

  • teamai score --session <id> --scorer <name> --json <driver> leases that session to the driver.
  • A skill run whose session tool has no verified judge here is judged by that session once it is done. describe says so and offers "Same, but without judging". done carries score, the score --next literal for the run's judge work. The workflow skill tells the session to judge each lease in a fresh subagent that reads only the evidence.

It also fixes score --lease / --submit failing with lease_unknown from another project's directory, where score --next with run already found the run.

The README scorers column marks GitHub Copilot CLI and Muse Code ✓³: they have no session reader, so their own sessions are still not scored, but a workflow they drive with the skill driver is now judged by the session. Pi Coding Agent stays —: the skill driver does not name a session tool without agent support. The other tools without a session reader set no agent session variable. TOOLS records this as a sessionJudge feature cell, shown as the Session judge column of the tool support page.

score --session <id> --scorer s --json {driver, can.judge}
  sessionJudgeLease               isolation_unmet | judge_model_unavailable | --batch scorers on different models
    takeJudgeLease(entries: {source: session})   the one lease path; background filters skipped
      lease | busy (a live lease holds it) | evidence_empty

workflow start --describe <wf> --driver skill
  judging: here       confined judge of the session tool, unchanged
  judging: session    new: no verified judge of the session tool here; the session judges at best-effort
  judging: elsewhere  unchanged meaning: nobody here (scoring off, or every scorer asks for verified)
start / resume
  skill.json sessionJudge {tool}
  done.score = teamai score --next --json '{run, driver: skill identity + can.judge {best-effort, read, [tool]}}'
Case Before After
score --session ... --json invalid_arguments a lease. invalid_json without can.judge, or with run / canTakeLease
Explicit session lease vs background filters n/a skips sampling, the six idle hours, scoring.sessions, scorer sessions, judged-once and the daily limit check. Still counts toward the day's launches
Submit a lease on a session a judge labelled earlier lease_expired settles. Only labels made since the lease count
Submit a lapsed lease after another lease took the session n/a lease_expired, and the live lease's answer is kept
Cursor skill session, scored step done, pass left open describe judging.kind: session, done.score, the session judges
Every scorer of the scored steps requires verified n/a describe elsewhere. With only some, that work stays open for another driver
score --lease / --submit from project B for a run of project A lease_unknown found in the run's scope

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature causing existing behavior to change)
  • Documentation only
  • Refactor / internal cleanup

Test Plan

Proof: two real-CLI runs on the built dist/ (below), plus unit and e2e tests. Each new test was seen failing without its fix.

  • npx tsc --noEmit passes, at each commit
  • npm run lint passes
  • npx vitest run passes: 590 files, 17541 tests
  • npm run test:e2e passes for score-session and score-external-driver (7 tests)
  • Added/updated tests for the change

Tests added:

  • session-scoring.test.ts, "with a driver's own judge":
    • lease and submit, with the label keeping the judge's tool, access and reported model
    • busy, then a new lease after release or label
    • submit after an earlier label
    • a lapsed lease refused once another lease holds the session
    • isolation_unmet with no attempt written
    • a literal model with cursor, and judge_model_unavailable
    • --batch rules; scorer file and --push refused; session_not_found
    • the lease holder asking again with other tools
  • workflow-skill-transitions.test.ts:
    • describe answers session for a tool with no confined judge
    • elsewhere when the scorer requires verified, and when scoring is off
    • a resume keeps done.score, --no-judge drops it, and the decision uses the scorers the run pinned
  • workflow-skill-driver.test.ts: an unattended run its session judges reaches done with a score literal whose payload leases the pass. A --no-judge start has none.
  • readme-matrix.test.ts: the scorers cell is ✓³ for a tool with no session reader whose features.sessionJudge is ✓. That cell must agree with the agent session variable the skill driver names the tool from. Before the README edit the test failed for Copilot CLI and Muse Code. With Pi's cell set to ✓t it fails for Pi.
  • e2e score-session.test.ts: lease, busy, payload errors, submit from the lease cwd, stored label.
  • e2e score-external-driver.test.ts: the lease is taken, judged (wrong_driver shows it was found) and submitted from a second initialized project. Without the fix it fails with lease_unknown.

Real CLI: a Cursor session drives a scored workflow and judges it itself

macOS, Node 24, cursor-agent 2026.10.21 on PATH (best-effort judge profile), CURSOR_CONVERSATION_ID set. Team repo and member in temp dirs. The script runs each literal the CLI prints.

## cursor-agent on PATH: 2026.10.21-ac3b220
## $ teamai workflow start --describe review --driver skill   (CURSOR_CONVERSATION_ID set)
{"judging":{"kind":"session","tool":"cursor","reason":"cursor 2026.10.21-ac3b220 has no verified judge profile."},"ask":{"kind":"ask","question":{"text":"- review is judged by this session in a subagent once the run is done, at best-effort isolation. cursor 2026.10.21-ac3b220 has no verified judge profile.\nHow should it run?","inputs":[],"options":[{"label":"Recommended: with judging","start":"teamai workflow start review --driver skill"},{"label":"Same, but without judging","start":"teamai workflow start review --driver skill --no-judge"}]}}}
## $ teamai workflow next <run> --driver skill   (after the review step reported)
{"status":"done","actions":[{"kind":"done","outcome":"succeeded","prs":[],"score":"teamai score --next --json '{\"run\":\"rc38f354460076029\",\"driver\":{\"kind\":\"skill\",\"machine\":\"<machine>\",\"workspace\":\"<root>/app\",\"can\":{\"judge\":{\"isolation\":\"best-effort\",\"access\":\"read\",\"tools\":[\"cursor\"]}}},\"running\":[]}'"}],"then":null}
## $ <done.score>
{"ref":"sd40d415cd07d5/a90ed028e","source":"workflow","subject":{"runId":"rc38f354460076029","step":"review","pass":1},"judge":{"tool":"cursor","by":"tool_default","isolation":"best-effort","access":"read"},"cwd":"<root>/app","submit":"teamai score --lease sd40d415cd07d5/a90ed028e --submit --stdin"}
## $ teamai score --lease sd40d415cd07d5/a90ed028e --submit --stdin   (from the lease cwd)
{"data":{"label":{"scorer":"review-quality","subject":{"runId":"rc38f354460076029","step":"review","pass":1},"label":"thorough","score":1,"passed":true,"labelId":"54f87b83cdd868b2db3231ed799fff115a9b3e314f964099cd689db93275a3c7","reasoning":"<root>/home/.teamai/scores/reasoning/2aee82ea186223ca974667fa01b07888f1f38bd6553e6d382e436320e53cea35.md"},"published":true}}
## $ <done.score> again
{"data":{"idle":{"open":0,"leased":0}}}
## stored label
{"scorer":"review-quality","label":"thorough","source":"workflow","subject":{"runId":"rc38f354460076029","step":"review","pass":1},"judge":{"kind":"model","tool":"cursor","model":{"reported":"composer-1"},"isolation":"best-effort","access":"read"}}

Real CLI: score --session --json on a hook-captured Claude Code session

## captures
<root>/home/.teamai/sessions/captures/claude-code/7c1e0000-0000-4000-8000-0000000013a6/903d0865-f413-4b0c-8188-947183c8ab67.json
## $ teamai score --session <id> --scorer review-quality --json {driver with can.judge}   (no --source: the captures name the platform)
{"data":{"lease":{"ref":"sad1cd5e9c3687/a21eab50a","source":"session","scorer":"review-quality","rubric":{"sourceHash":"5e6140d2be1ef8b6bf496482f15c3a2af04e8239153b13c856537bc98941f3b6","teamRevision":"3ba3b4cb0a3a417d1ad7f4dc39a533665f2b965b"},"subject":{"platform":"claude-code","sessionId":"7c1e0000-0000-4000-8000-0000000013a6"},"judge":{"tool":"cursor","by":"tool_default","isolation":"best-effort","access":"read"},"evidence":"<root>/home/.teamai/scores/evidence/a21eab50a","prompt":"<root>/home/.teamai/scores/evidence/a21eab50a/prompt.md","answer":{"schema":{"type":"object","required":["label","reasoning"],"additionalProperties":false,"properties":{"label":{"enum":["thorough","shallow"]},"reasoning":{"type":"string"}}}},"expiresAt":"2026-10-27T16:02:05.721Z","cwd":"<root>/home/.teamai","submit":"teamai score --lease sad1cd5e9c3687/a21eab50a --submit --stdin"}}}
## prompt.md holds the transcript: 1 match
## same request again while the lease is live
{"error":{"code":"busy","message":"A live judge lease holds review-quality on claude-code session 7c1e0000-0000-4000-8000-0000000013a6. Submit or release it, or ask again once it lapses, 30 minutes after it was taken or renewed."}}
## $ teamai score --lease sad1cd5e9c3687/a21eab50a --submit --stdin   (from the lease's cwd)
{"data":{"label":{"scorer":"review-quality","subject":{"platform":"claude-code","sessionId":"7c1e0000-0000-4000-8000-0000000013a6"},"label":"thorough","score":1,"passed":true,"labelId":"0ef9fa33023dc8bffe6997597576edf60605c4fff9af5de7f574311d2073b4db","reasoning":"<root>/home/.teamai/scores/reasoning/3737aa4a155f79143bbb384138c5b91bcd677f57cc776f40419aa229be5a4a49.md"},"published":true}}
## stored label
{"scorer":"review-quality","label":"thorough","source":"session","subject":{"platform":"claude-code","sessionId":"7c1e0000-0000-4000-8000-0000000013a6"},"judge":{"kind":"model","tool":"cursor","model":{"reported":"composer-1"},"isolation":"best-effort","access":"read"}}
## published to teamai-reports
scores/tester/2026-10.jsonl

Not run here: the gitlab / github providers and the Codex, CodeBuddy and OpenCode agents. Left to CI.

Related Issues

Closes #1386

Notes for Reviewers

  • Since feat(tool-launch): one launcher for every AI call, from the tool descriptor (#1389) #1444, teamai launches Cursor and Pi as best-effort judges. The issue predates that. A Cursor session therefore gets judging: session with the reason "cursor has no verified judge profile". The skill judges in a fresh subagent, as the issue specifies.
  • describe for a Cursor session with a scored step now asks a question ("with judging" / "without judging") where it used to offer a direct start.
  • busy now has two causes: the attempts lock is held, or a live lease holds the named session. The messages differ.
  • The score literal is not among the stub's pre-approved commands, so the agent asks before running it, as for any teamai score.
  • Rollback is a two-way door. skill.json gains an optional sessionJudge, and older files read as before. describe gains a session kind, and done gains an optional score.
  • The existing lease-ref examples in docs/guide/scorers.md (en and zh-CN, lines 88-107) are not valid refs: they are s<12 hex>/a<7 hex>, and the CLI takes s<13>/a<8>. The new example uses a valid ref. I left the old ones alone to keep this diff on its goal.

…ent#1386)

`score --next` with `run` finds a run of another project through
`findRunConfig`, but `score --lease <ref>` and `--submit` looked only in
this directory's scope and the user scope, so a driver that took the
lease from project B could not judge or submit it from B.

`leaseConfig` now also searches the project partitions `findRunConfig`
scans. A run's lease is read in its run's scope, as `runConfig` resolves
it. The partition scan moves out of `findRunConfig` so both share it.
`teamai score --session <id> --scorer <name> --json <driver>` now hands
out a lease, as `score --next` does, instead of rejecting the payload. A
driver whose tool teamai does not run as a verified judge here, such as a
Cursor session or an external orchestrator, judges the session from
`prompt.md` and the evidence only, then submits through the existing
submit path.

There is one lease path: `takeJudgeLease` takes the named session as a
second entry source (`JudgeRequest.entries` with `source: session`). The
explicit request skips the background filters (sampling, the six idle
hours, `scoring.sessions`, the scorer's `sessions`, the daily limit
check, judged once) but its attempt still counts toward the day's
launches. It takes nothing on `isolation_unmet` (new at lease time),
`judge_model_unavailable`, scorers of one `--batch` that need different
models, or a live lease on the entry (`busy`). The driver that holds the
lease gets the session again when it asks with other tools, as its
earlier lease is released. The payload names no `run` or `canTakeLease`.

The submit check that refused a session lease once any judge had
labelled the session now counts only labels made since the lease was
taken, so an explicit lease of a session labelled before can settle. A
lapsed lease no longer settles once another lease holds the session.
Without `--json`, `score --session` is unchanged.
…o verified judge here (Tencent#1386)

A skill-driven run whose session tool has no verified judge on this
machine, such as Cursor, reached `done` with its pass judge work left
open. describe now answers `judging.kind: session` for it, says the
session judges the run in a subagent at best-effort, and offers the
"Same, but without judging" start (`--no-judge`). The start or resume
keeps that in skill.json, and `done` then carries `score`: the
`score --next` literal that leases the run's judge work to the session's
own judge. The workflow skill tells the session to run it, judge each
lease in a fresh subagent from `prompt.md` and the evidence only, and
submit until `idle`, or release a lease it cannot answer and stop. A
session that resumes or takes over the run gets the same `score`, and a
`--no-judge` run gets none.

When every scorer of the scored steps asks for `verified`, describe
answers `elsewhere` as before, since the session cannot judge them. With
some of them, the session judges the rest and describe says that work
stays open for another driver. A resume reads the scorers its run
pinned. With judging `here`, the confined judge stays the only path.
@github-actions

Copy link
Copy Markdown

No findings I’m confident warrant reporting in the specified PR diff.

The PR description documents sufficient testing: two representative real-CLI runs against built dist/, plus type checking, lint, unit tests, and targeted e2e tests. Deferring additional providers and agents to CI is acceptable under the trusted review rules.

Review was read-only; I did not run, build, or install PR code.

…Tencent#1386)

GitHub Copilot CLI and Muse Code have no session reader, so score --session
still cannot judge their sessions. A workflow they drive with the skill
driver is now judged by the session itself, so their scorers cell gets a
footnoted mark. The matrix test derives the mark from the agent session
variable the skill driver reads, so a tool gains it only when the driver
can name it.
@github-actions

Copy link
Copy Markdown
  • [P2 non-blocking] README.md:129: Copilot and Muse now advertise workflow-scoring support, but neither entry in TOOLS (src/tools.ts) is updated. The trusted Code Review Rules require updating a tool’s descriptor when its supported capabilities change; record the limited session-judging support there too.

The PR description documents sufficient testing: two representative real-CLI runs against built dist/, plus targeted e2e tests. Deferring additional providers and agents to CI is acceptable.

The earlier pass raised no findings, so there are no resolved findings to list. Review was read-only; I did not run, build, or install PR code.

…cent#1386)

TOOLS gains a sessionJudge feature cell, shown as the Session judge
column of the tool support page. The README test takes the scorers mark
from it, and checks it against the agent session variable the skill
driver names the tool from.
@github-actions

Copy link
Copy Markdown

No new findings I’m confident warrant reporting in the specified PR diff.

The earlier [P2 non-blocking] descriptor finding is resolved: Copilot and Muse now have sessionJudge entries in TOOLS, reflected in both tool-support pages.

The PR description documents sufficient testing: two representative real-CLI runs against built dist/, plus unit and targeted e2e tests. Deferring additional providers and agents to CI is acceptable.

Review was read-only; I did not run, build, or install PR code.

@SaulMoro SaulMoro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This was generated by AI during triage.

@hz89Dev Thanks, this meets #1386. We compared the built CLI with main:

  • Session lease. score --session <id> --json <driver> returns a lease, where main refuses it with invalid_arguments.
    • A second request while the lease is live gets busy.
    • A scorer that requires verified gets isolation_unmet and writes no attempt.
    • An unknown tool gets judge_model_unavailable.
    • Submitting the same lease twice writes one label.
    • After the lease lapses a new one can be taken, and the old one is refused with lease_expired.
    • Without --json, the output matches main.
  • Skill-driven Cursor run. describe answers session and offers both options, and done.score leases and labels the pass. A verified scorer's work stays open, and --no-judge gives no score.
  • Failure paths. A lapsed or released lease can be taken again, the run stays done, and another driver can still take the open work.
  • Older CLI. An older CLI reads the new skill.json field without error.

tsc, lint, the unit files (also under a symlinked TMPDIR) and commands-reference pass. The new tests fail on main for the intended reasons. The two e2e files that timed out under load pass when run alone. The last three commits only touch the tool descriptor, the generated pages and the README matrix, and they resolve the CI review's descriptor note.

Non-blocking:

  • [P3 nit] done keeps carrying score after all the work is judged (src/workflow/drivers/skill/turn.ts:362), so an agent that re-reads it makes one extra call that returns idle.

Not run here: a real Cursor judge answer (submits used hand-written JSON), and the Codex, CodeBuddy and OpenCode agents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] Judge a session with the driver's own tool

3 participants