Skip to content

feat(reporting): record what sessions and workflow steps delivered, and count it per run mode - #1471

Merged
SaulMoro merged 23 commits into
Tencent:mainfrom
hz89Dev:feat/1387-session-outcomes
Oct 27, 2026
Merged

SaulMoro merged 23 commits into
Tencent:mainfrom
hz89Dev:feat/1387-session-outcomes

Conversation

@hz89Dev

@hz89Dev hz89Dev commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The usage report counts sessions, prompts, tokens and interventions, but not what any of them delivered, so a session that shipped a fix and one that went nowhere look the same. Now a finished session, a workflow step pass and a run record what they delivered: commits, a pull request, learnings. The report and teamai digest count sessions, tokens and interventions per run mode and outcome. Ids, shas and links stay on the member's machine, where the dashboard shows them.

teamai contribute [--session-id]   learning frontmatter: session: { platform, sessionId }
                                   local session_outcome event: { kind: learning, id }
  inside a workflow attempt        + runs/<run>/outcomes/<attempt>.jsonl  (the session it names still gets its own outcome)
Stop hook (background pass)        event.gitHead, event.gitBranch
attempt settles (both drivers)     pass.outcomes = that file + run.deliveries for the step and pass  -> run end record
usage report (every pull)          a session 6 h quiet, not launched by teamai:
                                     commits  git log --author=<member email> --since=<first start> gitAtStart.head..last Stop head
                                              (from the root when it started with no commit yet; in its repo once its worktree is gone)
                                     pr       the PR of the last branch it stopped on, holding one of those commits, open at a
                                              report while it went on or when it finished (kept once found for that branch)
                                     learning what contribute recorded for it
                                   first report on this release: the sessions already finished are flagged, not counted
                                   -> local session_outcome event with ids, shas, links, and the usage counted for it
                                   -> stats/<member>.yaml  outcomes.<runMode | notRecorded>.<kind | none>: { sessions, tokens, interventions }
teamai digest                      Session Outcomes table, per run mode and outcome
dashboard                          Overview: delivered share, per run mode and outcome, recently finished
                                   session list and details: the session's learnings so far; run details: each pass's outcomes

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

Related e2e suites: npm run test:e2e -- contribute-namespace dashboard-unified dashboard-workflow-runs dashboard-scoping report-scope-reuse workflow-records-reports-branch-1083 workflow-delivery-drivers workflow-session-1086 workflow-command.test (9 files, 55 tests pass).

Real-CLI record on the built CLI (npm run build). The sandbox has its own HOME, a team repo with a bare origin (git provider) and an app repo with Claude hooks installed. Each session runs the installed hook commands (teamai hook-dispatch session-start | prompt-submit | stop --tool claude) with Claude hook payloads and a transcript, makes its commit during the session, and may run teamai contribute --session-id. Each session's events are then moved 7 hours back, so the member's next teamai pull finishes it.

--- 0012: a Stop on a feature branch records the head and the branch
{"type":"stop","gitHead":"a3b11f321c458c114ddf243c6bc34b4828c9f97a","gitBranch":"feat/retry-budget"}
--- branch commit: a3b11f321c458c114ddf243c6bc34b4828c9f97a

--- 0020: on feat/backoff, git user.email alice+team@example.test, a commit and a learning, then back to main
$ teamai contribute --file notes.md --title Backoff-caps-at-thirty-seconds --session-id 7c1e…0020
$ teamai pull
--- local counted session_outcome event
0020 attended [{"kind":"commit","sha":"97189bdefdf031256aeb772581b1abd72f081e07"},{"kind":"learning","id":"backoff-caps-at-thirty-seconds-2026-10-27-7ljkd1"}] {"tokens":{"input":90000,"output":6400},"interventions":1}
--- commits: feat/backoff 97189bd
--- learnings/backoff-caps-at-thirty-seconds-2026-10-27-7ljkd1.md on teamai-learnings
session: { platform: claude-code, sessionId: 7c1e5a2b-0000-4000-8000-000000000020 }

--- 0022: a session in a linked worktree; the worktree is removed after its Stop, before it is finished
--- Stop of 0022: gitHead 19508d808fdb96dcd193e284043a0753023e2845, gitBranch feat/worktree-task2, projectAnchor: the app repo
--- worktree removed; commit 19508d808fdb96dcd193e284043a0753023e2845 is still in the repository
$ teamai pull
--- local counted outcome event of 0022
attended [{"kind":"commit","sha":"19508d808fdb96dcd193e284043a0753023e2845"}] {"tokens":{"input":90000,"output":6400},"interventions":1}

--- 0040: a session that starts on an orphan worktree of the app (no commit yet) and makes the first commit
--- HEAD at start: no commit yet
session_start gitAtStart: null
stop gitHead: cc7fc634b877710f5790e6131bd50ea1ba73e2d2, gitBranch: scaffold
$ teamai pull
--- local counted outcome event of 0040
attended [{"kind":"commit","sha":"cc7fc634b877710f5790e6131bd50ea1ba73e2d2"}] {"tokens":{"input":90000,"output":6400},"interventions":1}

--- workflows/close.yaml: one command step, `teamai contribute --file step-notes.md --title "Retry budget per host"`
$ teamai workflow start close
--- run record workflows/runs/rcaddb703ed5ac3f4.json on teamai-reports
rcaddb703ed5ac3f4 attended [{"step":"close","pass":1,"outcomes":[{"kind":"learning","id":"retry-budget-per-host-2026-10-27-v0lf12"}]}]

--- stats/Alice.yaml on teamai-reports (counts only)
outcomes:
  attended:
    commit:   { sessions: 4, tokens: { input: 270000, output: 19200 }, interventions: 3 }
    learning: { sessions: 4, tokens: { input: 270000, output: 19200 }, interventions: 3 }
    none:     { sessions: 2, tokens: { input: 180000, output: 12800 }, interventions: 2 }
  unattended:
    commit:   { sessions: 1, tokens: { input: 90000, output: 6400 }, interventions: 1 }

$ teamai digest
📦 Session Outcomes (finished sessions):
  runMode       outcome    sessions   tokens/session   interventions/session
  attended      commit     4          72.3K            0.8
  attended      learning   4          72.3K            0.8
  attended      none       2          96.4K            1.0
  unattended    commit     1          96.4K            1.0
  A session with a pull request and a learning is in both rows, so rows do not add up.

Upgrade from 0.28.0, in a fresh sandbox. The published 0.28.0 records three sessions through its hook commands (two commit, one also contributes a learning) and reports. Then the member reports on this build.

--- 0.28.0: sessions 0001 (commit), 0002 (commit, learning), 0003, each 7 h quiet
$ teamai pull   # 0.28.0
--- dashboard events: 9 lines, none with gitHead
--- upgrade: this build
$ teamai pull
--- stats/Alice.yaml on teamai-reports: no outcomes block
--- reported-outcomes.json
0001 {"reported":true}
0002 {"reported":true}
0003 {"reported":true}
--- a session on this build, with a commit, 7 h quiet
$ teamai pull
--- stats/Alice.yaml on teamai-reports
outcomes:
  attended:
    commit: { sessions: 1, tokens: { input: 90000, output: 6400 }, interventions: 1 }
$ teamai digest
📦 Session Outcomes (finished sessions):
  runMode       outcome    sessions   tokens/session   interventions/session
  attended      commit     1          96.4K            1.0

A Stop through teamai hook-dispatch stop --tool claude while a git wrapper hangs on the Stop's head read and leaves a child holding the pipe:

--- Stop event of the session
stop gitHead: none tokens: {"input":90000,"output":6400,"cacheRead":210000,"cacheCreation":0}
[hook-dispatch] stop/claude background: 2382ms total, 5 handlers, slowest dashboard-report=2381ms | dashboard-report=2381ms(ok) …

Not run here: a real pull request lookup. The git provider opens none, so the dashboard's pull request row below comes from one seeded session_outcome event. The lookup itself (the branch the session last stopped on, after the checkout moved on; one of the session's commits; a PR found while the session went on and merged before it finished; the cache; no credentials) is covered by team-push-outcomes.test.ts. Codex, CodeBuddy and OpenCode and the gitlab and github providers are left to CI.

Dashboard on the same sandbox. Desktop 1280:

Overview: what finished sessions delivered Session list and details
Overview Sessions
Session details Run details, each pass's outcomes

zh-CN, dark, tablet 820 and mobile 430:

zh-CN, dark Tablet Mobile Mobile session details
zh-CN dark Tablet Mobile Mobile details

At 1280 the page no longer scrolls sideways. A table wrapper was not positioned, so the screen-reader text in its cells sized the page (scrollWidth 1392 before, 1280 after; 912 before and 415 after at 430). Wide tables scroll inside their panel.

Related Issues

Part of #1387. Unattended sessions are counted like attended ones, 6 hours after they go quiet, at the member's next report. Counting them when teamai score --drain ends a CI job waits on #1385.

Notes for Reviewers

  • A session's outcomes are one local session_outcome event per contribution and one counted event when the report finishes the session. readEvents skips them, so the live session fold is unchanged. A per-session flag in reported-outcomes.json, beside the existing baselines, keeps a session from being counted twice. A session's tokens and interventions are what the usage report itself counts for it, so the outcome rows agree with the top-level totals, including a rollout that event compaction dropped.
  • Outcome rows leave out sessions teamai launched, as [feat] Run mode: tell attended runs from unattended ones #1384 does. launchedBy is read for the session the outcome belongs to: contribute --session-id inside a workflow attempt still credits the member's session.
  • Commits are the member's (--author with the git email as a fixed string, so a + tag matches), dated after the session's first start, in gitAtStart.head..<last Stop head>. A session that started with no commit yet (or whose git read failed at start) is bounded by its start time alone. A session whose checkout is gone is read in its repository, found from its project anchor. A session with no recorded Stop head gets no commits.
  • The pull request target is the repository's default branch as getDefaultBranch resolves it (origin/HEAD, else origin/main, else origin/master), so a repository pushed to its origin rather than cloned is looked up too.
  • A pull request is looked up only for the last branch a Stop of the session recorded (a Stop on a detached HEAD records none, so a rebase or bisect in progress keeps the branch before it), and only once origin's copy of it holds one of the session's commits. Each report asks until it finds one, so a PR merged before the session is finished still counts (every session start runs a background pull), and once more when the session is finished. A session that later stops on another branch is asked about that branch, and only its answer counts. It uses the provider and credentials deliver: pr uses, and keeps the answer in outcome-prs.json until the session is reported. A failed lookup leaves pr out and never fails the report.
  • The first report on this release counts none of the sessions already finished: an earlier release kept no Stop head and no learning of theirs, so each would count under none. With no reported-outcomes.json, those sessions are flagged reported, and the file is written even when empty. The first report usually runs at the first session start after the upgrade, before any session on this release can be finished.
  • A Stop reads its head and branch within 2 seconds, and stops waiting at that cap even when git outlives its kill (a wrapper whose child holds the pipe), so the Stop event, tokens included, is always written.
  • Known limits: a PR opened and merged between two reports is not found (open PRs only). A 5000-line event compaction between a session's last Stop and the next report loses that Stop's head; the usage counts already share this. A learning from an attempt that expired without settling is not put on a pass.

@github-actions

Copy link
Copy Markdown
  • [P2 non-blocking] First commits are classified as nothing delivered — src/session-outcomes.ts:151: A session starting in an initialized repository with no commits records no gitAtStart, so startHead remains absent. Even if that session creates commits and records a valid Stop head, this guard returns no commits and permanently counts it under none unless it contributed a learning. Distinguish an unborn starting HEAD from unavailable git state.

  • [P2 non-blocking] PR lookup requires an optional local remote-HEAD alias — src/session-outcomes.ts:180: A locally created repository pushed to GitHub can have an open PR and origin/<branch> containing the session’s commits, but no origin/HEAD symbolic ref. This returns before querying the provider, permanently omitting the PR outcome. Resolve the target branch without requiring that local alias.

The description includes sufficient built real-CLI verification. No testing-record blocker; I did not run, build, or install PR code.

@hz89Dev

hz89Dev commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Both fixed in d0e9d80.

  • First commits: a session's start is now its first session_start, with or without a head. When that start saw no commit, its commits are git log --since=<start> <last Stop head> by the member. Covered by "finds the first commits of a session that started in a repository with no commit yet". On the built CLI, a session on an orphan worktree (gitAtStart absent) counted its first commit.
  • PR target: the lookup now resolves the target with getDefaultBranch (origin/HEAD, else origin/main, else origin/master) instead of requiring origin/HEAD. Covered by "looks up the pull request of a repository pushed to its origin rather than cloned, which has no origin HEAD".

@github-actions

Copy link
Copy Markdown

No new findings in the specified PR diff.

Both earlier findings are resolved:

  • First commits are now detected when the session starts without a HEAD.
  • PR lookup now uses getDefaultBranch, rather than requiring origin/HEAD.

The description documents sufficient built real-CLI verification. Deferring other providers and agents to CI is acceptable; no testing-record blocker.

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. We compared the built CLI with main (84fb146):

  • Local records. A Stop records gitHead and gitBranch, a learning gets session in its frontmatter, and a step's pass gets the learning its contribute recorded.
  • What the team sees. The stats file gets only counts, under outcomes. A grep found no sha, session id, branch or link there, and a second pull changed nothing.
  • Readers. digest shows the new table. The dashboard's /api/outcomes and run-detail views return the outcomes.
  • 0.28.0 on the same team repo. It reads this build's stats and run records, and keeps outcomes when it rewrites the stats file.
  • CI review. d0e9d80 fixes both of its findings: the first commits in a repo with no commits yet, and the PR lookup needing origin/HEAD.

One change before this can be approved:

  • [P1 blocking] The first report after an upgrade counts every past session as "delivered nothing". We used a local event log written by main, with 3 finished sessions: two committed, and one of those also contributed a learning. The first teamai pull on this build wrote outcomes.attended.none.sessions: 3, and digest printed attended none 3. The log is only compacted above 5,000 lines, so most members will hit this. The counters are cumulative, so nothing corrects them later. Fix: when there is no reported-outcomes.json yet, mark the sessions already finished as reported without counting them.

Non-blocking:

  • [P2 non-blocking] A PR merged within 6 hours of the session is never counted; the body lists this as a known limit.
  • [P3 nit] Two concurrent sessions in one checkout each get the other's commits.
  • [P3 nit] reported-outcomes.json grows without bound, as the existing baselines do.
  • [P3 nit] The body says "pull / push", but only pull sends the report.
  • [P3 nit] A git wrapper that leaves a child holding the pipe after the 2-second abort loses the whole Stop event.
  • [P3 nit] contribute can append to an attempt that has already settled, where nothing reads it.
  • [P3 nit] After a downgrade, 0.28.0's dashboard shows phantom live sessions.

Besides the counts, three new things reach the team repo, as #1387 describes:

  • a step's commit shas in the run's end record, which carried no shas before;
  • the step's learning ids, in the same record;
  • the session id, in the learning's frontmatter.

Overlap: textual conflicts with #1466 and #1379, no duplicates.

Verified at d0e9d80 (the hook and mixed-release runs at 5d112a8, which d0e9d80 doesn't touch): tsc and lint pass, the e2e files (116 tests) and self-mode-worktrees-808 pass, and the new tests fail on main. CI is green. Not run here: a real GitHub or GitLab PR lookup, other agents, and the dashboard in a browser.

@jeff-r2026 This adds data the team repo receives (#1387), so it's yours to decide.

@SaulMoro
SaulMoro requested a review from jeff-r2026 October 27, 2026 16:41
@hz89Dev
hz89Dev force-pushed the feat/1387-session-outcomes branch from d0e9d80 to 8535652 Compare October 27, 2026 18:04
@hz89Dev

hz89Dev commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

The blocking finding is fixed in 3c7bbb9, two of the nits in 3c7bbb9 and 8535652, and the rest are left as they are, with reasons below.

  • [P1] First report after an upgrade. With no reported-outcomes.json, the report now flags every session already finished as reported without counting it, and writes the file even when it is empty, so this happens once. Covered by "leaves out the sessions an earlier release recorded and finished before it, and adds the ones still going". On the built CLI, the published 0.28.0 recorded three sessions (two committed, one also contributed) and reported. The first teamai pull on this build wrote no outcomes and flagged the three sessions. A session on this build was counted under commit once finished. The record is in the body.
  • [P2] A PR merged within 6 hours. Each report now looks a session's PR up while the session is still going, until it finds one, and keeps it. Every session start runs a background pull, so a PR merged before the session is finished still counts. A PR opened and merged between two reports is still missed, and the body says so. Covered by "counts the pull request a report found while the session was still going, though it merged before the session finished".
  • [P3] Body says "pull / push". Fixed: only pull sends the report.
  • [P3] Git wrapper holding the pipe. Reproduced: without the fix, the new test's Stop takes 12.5 s, past the 4.5 s the dispatcher gives a handler, so its event goes unwritten. The Stop now stops waiting at its 2 s cap and is written without its head. Covered by "writes a Stop without its head, in time, when git outlives its time cap with a child holding the pipe". On the built CLI, dashboard-report took 2.4 s and wrote the Stop with its tokens.

Not changed:

  • [P3] Two concurrent sessions in one checkout. Git records no session. A commit the member made in that checkout while both sessions ran falls in both sessions' ranges, and nothing local says which one made it.
  • [P3] reported-outcomes.json grows. Same as the existing baselines. Pruning an entry is only safe once compaction has dropped the session's events, so a fix belongs with all the baselines, not this one alone.
  • [P3] contribute after the attempt settled. No driver keeps a process with the attempt marker running after the attempt settles. The exception is a command that outlives an expired attempt, which the body already lists. A guard on settledAttempts would be partial: the list is capped at 32 and leaves expired attempts out. The learning and the session's own outcome are still recorded.
  • [P3] Phantom live sessions after a downgrade. On 0.28.0, an unknown event opens a "running" session that turns idle after 5 minutes and is removed after 30. So only the counted lines from the last report before the downgrade show, for at most 30 minutes. Avoiding that means moving outcomes out of the shared event log, along with its lock, compaction and retention.

@github-actions

Copy link
Copy Markdown
  • [P2 non-blocking] Cached PR prevents lookup of the session’s final branch — src/session-outcomes.ts:243: If a report caches PR A while a session is still active, and the session later stops on branch B with a pushed commit and PR B, !prs[sessionId] prevents B’s lookup. The finished outcome retains A and omits B, contrary to the documented last-Stop-branch attribution. Invalidate the cached lookup when the recorded repository or branch changes.

Both earlier findings are resolved: first commits are detected, and PR lookup no longer requires origin/HEAD.

The description includes sufficient built real-CLI verification; no testing-record blocker. Deferring other providers and agents to CI is acceptable.

I did not run, build, or install PR code.

@hz89Dev

hz89Dev commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in f3dbe8f. The kept lookup now names its branch. A session that last stopped on another branch is asked about that one, and only the answer for that branch counts, so the earlier branch's PR is left out even when the later branch was never pushed or its lookup fails.

Covered by "counts the pull request of the branch the session last stopped on, not one a report found on an earlier branch": a report finds PR 41 on fix-retry while the session is going, the session then stops on fix-timeout, and the finished session counts PR 42, or no PR when fix-timeout was never pushed. Both cases fail without the fix.

@github-actions

Copy link
Copy Markdown

No new findings in the specified PR diff.

All three earlier findings are resolved:

  • Sessions starting without a HEAD can now receive commit outcomes.
  • PR lookup uses getDefaultBranch, not a required origin/HEAD alias.
  • A changed final Stop branch triggers a new PR lookup and excludes the previous branch’s cached PR.

The description documents sufficient built real-CLI verification; no testing-record blocker. Deferring additional providers and agents to CI is acceptable.

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.

Re-review at f3dbe8f, after the rebase onto main. Nothing blocking is left on our side. Whether these new records belong in the team repo (#1387) is still the maintainer's call, as in our earlier comment.

Earlier findings

Finding Status
[P1] The first report after an upgrade counted every past session under none Resolved in 3c7bbb9. Built CLI: main recorded 3 sessions and reported. This build's pull then reported no outcomes and marked the 3 as reported. A new session with a commit counted under commit, and a second pull changed nothing. The new test fails on the old head's source.
[P2] A PR merged within 6 h was never counted Resolved in 3c7bbb9. The body lists the remaining limit (opened and merged between two reports).
[P3] Body said "pull / push" Resolved.
[P3] A git wrapper holding the pipe lost the Stop Resolved in 8535652.
Four other nits Declined with reasons. We accept them.

The CI review's branch-switch finding is fixed in f3dbe8f: the kept lookup records its branch, and only the last-stopped branch counts.

New, non-blocking

  • [P3 nit] A still-going session with pushed commits and no PR yet is looked up again on every report, because a none answer is not kept (src/session-outcomes.ts:243-254). It costs API calls and some latency on a manual pull. Nothing fails.
  • [P3 nit] An unreadable reported-outcomes.json reads as a first report, so finished sessions are marked as reported without being counted once. Before, they were double-counted, so this is no worse.
  • [P3 nit] On a fresh install, sessions that finish before the scope's first report are skipped. That needs a failed or offline first report for 6 h or more. The body says so.

Verification at f3dbe8f

  • npm ci, npm run build, npx tsc --noEmit, npm run lint exit 0.
  • The PR's 10 unit files: 604 of 606 passed. The two failures are workflow-local-driver lease-timeout tests from main that fail only under heavy machine load; they pass on rerun.
  • 7 related e2e files: 35 of 35 passed. The full e2e suite was not rerun; the earlier review covered it, and these files reach the new commits.
  • CI is green at this head, and the body keeps its real-CLI record.

Not run here: a real GitHub or GitLab PR lookup, agents other than Claude, the dashboard in a browser.

@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.

Approving at f3dbe8f. The review is in our comment above: the first-report-after-upgrade finding is fixed and checked with the built CLI across a release upgrade, CI is green, and the body keeps its real-CLI record. The three nits there (repeat lookups for a still-open session, an unreadable reported-outcomes.json, an offline first report on a fresh install) can follow later.

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

#1466 just merged and this branch now conflicts with main. Could you rebase onto main? We will check the rebase and merge once CI is green at the new head.

contribute writes session: { platform, sessionId } into the learning's
frontmatter before queuing it, records the learning as an outcome of
that session in the local dashboard event log, and inside a workflow
attempt appends it to the attempt's outcome file. A learning's id is
its path under learnings/ without .md.

Refs Tencent#1387
The engine reads the attempt's outcome file when it settles the attempt,
for every driver, and keeps its entries on the pass. The end record gives
each pass its outcomes: the commits on its branch, the pull request
delivered for that step and pass, and what its own commands reported.
The run keeps prs; its union is derived from the steps.

Refs Tencent#1387
…s apart

The Stop event records gitHead, so a session's commit range is its
start's gitAtStart.head to its last Stop's gitHead. Session outcome lines
stay out of what every existing reader of the event log reads, and
compaction keeps them for 30 days, verbatim, after their session's own
events are dropped.

Refs Tencent#1387
The usage report adds each of the member's sessions to stats/<user>.yaml
under outcomes.<runMode | notRecorded>.<pr | commit | learning | none>
once it has been idle for 6 hours: sessions, tokens and interventions.
A session is counted once, flagged in reported-outcomes.json beside the
other baselines, which also keeps what its events said after compaction
drops them.

Commits are the member's own in the session's start..last-Stop range,
dated after it started. A pull request is the open one of the checkout's
branch holding one of them, looked up once and cached in
outcome-prs.json. Ids, shas and URLs stay in the local event log; the
report carries counts only.

Refs Tencent#1387
The digest adds up the team's outcome rows and shows, per run mode and
outcome, the sessions with their tokens and interventions per session.
A session in two rows is said to be, so the rows are not read as a sum.

Refs Tencent#1387
A run's steps table gains an outcomes column: the pass's commits, pull
requests and learnings, from the local run or the published record. An
outcome the page cannot read is left out, and a pull request links only
on https.

Refs Tencent#1387
Sessions are rebuilt with their outcome events folded in, so the session
table and a session's detail show the learnings recorded for it, and the
commits and pull requests the usage report found once it finished.

Refs Tencent#1387
The overview gains a panel built from the sessions the usage report
counted in the last 30 days: the share that delivered a commit, pull
request or learning, the rows per run mode and outcome with tokens and
interventions per session, and the latest sessions with their commits,
pull request links and learnings. A workspace shows the sessions its
own report counted.

Refs Tencent#1387
…edge guides

Clarify contribute --session-id: the session is recorded on the learning
and counted as that session's outcome.

Refs Tencent#1387
…written

A learning recorded inside a workflow attempt is appended to the attempt's
outcome file. A write error there no longer fails the contribution after
the learning is queued; it warns, and the session still counts as
contributed.

Refs Tencent#1387
…topped on

A Stop now records the branch beside the commit it saw. The report looks up
the pull request of that branch, so a member who switched branches after
opening it, before the session was finished, still has it counted.

Also stub the new Stop helper in the hook handler test's collector mock.

Refs Tencent#1387
Reuse CommitIdSchema for commit shas, and one SessionUsage schema for what
a counted session adds. The dashboard's 30-day window is the retention
compaction keeps outcome lines for. A pull request lookup is dropped once
its session is reported. Handle every attempt-outcome result explicitly,
and share the pull request link markup in the dashboard.

Refs Tencent#1387
…ass outcomes

A session row leaves the dashboard list before its commits and pull
request are counted, so the session detail and the dashboard guide point
to the Overview panel. The workflow guide lists the pass outcomes a run
record publishes; the knowledge guide says when a learning names its
session; the engine notes record the attempt outcome file.

Refs Tencent#1387
…s for it

A resumed session whose earlier rollout compaction dropped was counted
with only its remaining rollout. The outcome rows now take each session's
totals from the usage report, dropped rollouts included.

Refs Tencent#1387
git log reads --author as a basic regex, where the escaped + became a
quantifier. Match the author as a fixed string.

Refs Tencent#1387
…tree is gone

A session that worked in a worktree removed before it was finished lost
its commits. The report keeps the repository its checkout was in, and
reads the commits from a live checkout of it when the session's own is
gone.

Refs Tencent#1387
…e chips in the session list

A table-scroll wrapper was not positioned, so the absolutely positioned
screen-reader text in its cells sized the page instead of the wrapper and
the page scrolled sideways. The session list shows each outcome by kind
and short sha; the full id stays in the chip title and in the details.
… find its pull request without origin HEAD

A session that started on a checkout with no commit yet had no start head,
so its commits were never read and it counted as nothing delivered. Its
first start now bounds its commits by time when that start saw no head.

The pull request lookup read the target branch from origin/HEAD, which only
a clone sets, so a repository pushed to its origin was never looked up. It
now resolves the default branch as the rest of teamai does.
… request merged before its session finished

An earlier release recorded no Stop head and no learning for its sessions,
so the first report after an upgrade counted each one already finished
under `none`. With no reported-outcomes.json yet, those sessions are now
flagged reported without being counted, and the file is written even when
empty, so this happens once.

A session's pull request was looked up once, when the session was
finished, so one merged within those 6 hours was never found. Each report
now looks it up while the session is still going, until it finds one.
A git killed at its 2-second cap was still waited for while a child of it
held the pipe, as a wrapper's can, so the Stop handler ran past its limit
and its event was never written. The cap now ends the wait, and the Stop
is written without its head.
…ped on, not one found on an earlier branch

A report now looks a session's pull request up while it is still going,
so the answer it keeps may be for a branch the session later left. The
kept answer now names its branch: a session that last stopped on another
branch is asked about that one, and only the answer for that branch
counts.
@hz89Dev
hz89Dev force-pushed the feat/1387-session-outcomes branch from f3dbe8f to c28caa2 Compare October 27, 2026 19:55
@github-actions

Copy link
Copy Markdown
  • [P2 non-blocking] Detached final Stop retains the previous branch’s PR — src/session-outcomes.ts:111: If a report caches PR A and the session later stops on a detached HEAD, that Stop updates endHead but leaves endBranch pointing to A. The finished outcome consequently retains A’s cached PR despite the final Stop having no branch. Clear the previous branch when a Stop records a head without gitBranch.

All three earlier findings are resolved: first commits are detected, PR lookup no longer requires origin/HEAD, and switching to another named branch invalidates the earlier PR attribution.

The description documents sufficient built real-CLI verification; no testing-record blocker. Deferring additional providers and agents to CI is acceptable.

I did not run, build, or install PR code.

… it stopped on

A Stop on a detached HEAD records no branch, so the session keeps the
branch before it, as during a rebase or bisect. The guides said "the
branch the session last stopped on", which reads as none in that case.
@hz89Dev

hz89Dev commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Not changed: keeping the branch before a detached Stop is deliberate. endBranch is "the branch checked out at its last Stop that recorded one" (src/session-outcomes.ts:45). A detached HEAD in a session is usually a rebase or bisect in progress on that branch, or a look at another commit after the work was pushed. In each case the session still delivered that branch's PR, and clearing the branch would drop it. The lookup still needs origin's copy of the branch to hold one of the session's commits, so an unrelated PR is not counted.

The guides said "the branch the session last stopped on", which reads as no branch here. 24e26fc rewords them to "the last branch the session stopped on", and says a Stop on a detached HEAD keeps the branch before it.

@github-actions

Copy link
Copy Markdown

No new findings in the specified PR diff.

  • The first-commit, origin/HEAD, and named-branch cache findings remain resolved.
  • The detached-HEAD concern is addressed by an explicit policy in both reporting guides: retain the last named Stop branch. The code implements that policy; it does not clear the branch.

The description documents sufficient built real-CLI verification; no testing-record blocker. Deferring additional providers and agents to CI is acceptable.

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.

Approving at 24e26fc after the rebase onto #1466. The conflicts were only in the dashboard's client.ts, locales.ts and styles.ts, and both sides are kept: the retry handler has both the outcomes and the scorers branches, switching workspace runs both resetScorers() and loadOutcomes(), and the duplicate SESSIONS translation was dropped in favour of #1466's. The other 19 commits are unchanged, and 24e26fc only rewords docs and comments.

We agree that keeping the branch before a detached Stop is right: a PR still counts only if origin's copy of that branch holds one of the session's commits, and clearing it would drop real PRs during a rebase.

At this head: npm ci, npm run build, npx tsc --noEmit, npm run lint exit 0; the unit files of this PR and #1466 (660 tests) pass, also with a symlinked TMPDIR; seven dashboard and report e2e files pass; a built-CLI run (hooks, contribute, two pulls with no double count, digest, /api/outcomes and /api/scorers) behaves as before the rebase.

@SaulMoro
SaulMoro merged commit bd3adf9 into Tencent:main Oct 27, 2026
17 checks passed
Walternater pushed a commit to Walternater/teamai-cli that referenced this pull request Nov 2, 2026
…nd count it per run mode (Tencent#1471)

Each session and workflow step now records what it delivered (a commit, a pull request, a learning, or nothing), and the usage report and digest count it per run mode. A session's pull request is the one of the branch it last stopped on, and sessions an earlier release finished are not counted.

Refs Tencent#1387
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.

3 participants