Skip to content

feat(stats): memory_stats reports abandoned workers from abandoned_work (#728, slice 2) - #902

Merged
github-actions[bot] merged 4 commits into
mainfrom
fix/728-abandoned-worker-report
Nov 6, 2026
Merged

github-actions[bot] merged 4 commits into
mainfrom
fix/728-abandoned-worker-report

Conversation

@cdeust

@cdeust cdeust commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

Summary

Refs #728 (slice 2, does not close the issue). #903 added the abandoned_work registry. This PR exposes it through memory_stats, so a client can read it from the server process that answers.

Change. memory_stats now returns abandoned_workers, which is abandoned_work.snapshot():

  • in_flight: per tool, the workers still running after their caller was cancelled;
  • ended: per tool, how abandoned workers ended since the process started (completed / raised / cancelled).

The tool description documents both fields. This PR keeps no second registry: worker_ledger.py, from the first version of this PR, is removed after main was merged in, as the owner asked.

Type of change

  • New feature (non-breaking change which adds functionality): one new key in the memory_stats response.

Test plan

  • tests_py/handlers/test_memory_stats_mapping.py (new) maps each memory_stats field, including abandoned_workers, to its source, on a stub store with the grooming helpers stubbed (no dependence on the clock). Changing handler() brings its 58 pre-existing survivors into the ADR-1122 gate: the existing tests only checked types.
  • Local ADR-1122 mutation gate after the merge (GITHUB_BASE_REF=main python scripts/ci_mutation_diff.py): 1 file(s), 0 failed, 0 untested.
  • pytest tests_py/handlers tests_py/server tests_py/test_main.py: 1877 passed, 81 skipped. ruff 0.17.3 on the changed files and check_craftsmanship.py: OK.

Audit notes

  • Not verified: PostgreSQL is absent here (those tests are skipped).

Breaking changes

None.

Reviewer checklist

  • CHANGELOG.md updated (Unreleased › Added).
  • Documentation: memory_stats description.
  • No secrets in the diff.
  • CI passes on the latest commit (pending).

https://claude.ai/code/session_018kyZYtgfAGUYWDSLAfQPUp


Generated by Claude Code

…ended (#728, slice 2)

A caller cancelled while its worker thread runs gets no result, yet the work
goes on and, for a mutating tool, its writes land. Slice 1 only logged that
outcome. worker_ledger now keeps, per tool, the workers running now, those
still running after their caller was cancelled, and how each abandoned worker
ended (completed / raised / cancelled). run_admitted records each run through
one end callback (_Run), so a reader never sees an abandoned worker that is no
longer active; a caller cancelled after its worker ended counts as abandoned
too. memory_stats returns the ledger as `tool_workers`.

test_memory_stats_mapping pins every field of memory_stats to its store
reading: touching handler() put its 58 pre-existing survivors in the
ADR-1122 diff-scoped mutation gate.

Refs #728

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtgfAGUYWDSLAfQPUp
…w they ended (#728, slice 2)

fixup! feat(admission): memory_stats reports abandoned workers and how they ended (#728, slice 2)

fix(test): _drain waited on one slot of remember's budget of 4, so it returned before the abandoned worker ended (unit, run 42168539790). It now holds the whole budget at a barrier.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtgfAGUYWDSLAfQPUp

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

unit en échec (run 42168539790, job 127730708489) : la cause est un test de cette PR, corrigé dans 399e8b31 (fixup).

  • test_abandoned_worker_that_raises_is_counted_as_raised (lancé par le gate de mutation) lisait le ledger avant que le worker abandonné ait fini. _drain() attendait un seul slot, alors que remember a un budget de 4 : l'appel passait sans attendre la fin du worker.
  • _drain() occupe maintenant tout le budget à la fois, sur une threading.Barrier. Il ne passe donc qu'une fois tous les slots rendus, et le rendu du slot comme l'écriture dans le ledger se font dans le même callback (_Run.worker_done).
  • Reproduit localement avant le correctif, avec un délai de 0,2 s dans le worker après release : 2 échecs (completed et raised). Après : 7/7. Gate de mutation local sur worker_ledger.py : 0 survivant. Le code de production n'a pas changé.
  • Non vérifié : le nouveau run CI (en cours).

Generated by Claude Code

@cdeust

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

Conflit de conception avec main, à reprendre par l'autopilote (pas de résolution mécanique).

#903 (540df96f, même tranche 2 de #728) est fusionnée. Elle a ajouté mcp_server/handlers/abandoned_work.py, qui compte les workers abandonnés encore en cours et leur issue (record_abandoned, record_ended), et l'a branché dans admission.run_admitted et _outcome. Cette PR apporte un second registre pour la même chose (worker_ledger.py, run.caller_cancelled()), d'où le conflit dans admission.py. J'ai annulé ma fusion de main.

main n'expose pas encore ces comptes dans memory_stats. C'est l'apport propre de cette PR. À reprendre ainsi :

  • partir de main, en fusionnant main dans la branche (pas de rebase ni de force push) ;
  • retirer worker_ledger.py et brancher memory_stats sur abandoned_work, en complétant abandoned_work si un champ manque, plutôt que de garder deux registres ;
  • garder les tests de test_memory_stats_mapping.py adaptés à cette source.

La PR reste en draft jusque-là.

#903 (540df96) brought abandoned_work, the same registry as worker_ledger.
Keep main's admission.py, drop worker_ledger and its tests, and have
memory_stats return abandoned_work.snapshot() as abandoned_workers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYtgfAGUYWDSLAfQPUp
@cdeust cdeust changed the title feat(admission): memory_stats reports abandoned workers and how they ended (#728, slice 2) feat(stats): memory_stats reports abandoned workers from abandoned_work (#728, slice 2) Nov 6, 2026

cdeust commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

Repris comme demandé dans ed0c4c18, par un merge de main (sans rebase ni force push) :

  • admission.py est pris tel quel depuis main ; worker_ledger.py et test_worker_ledger.py sont supprimés.
  • memory_stats renvoie abandoned_workers = abandoned_work.snapshot() (in_flight, ended). Aucun champ n'a été ajouté à abandoned_work, qui contient déjà ce que la tranche rapporte.
  • test_memory_stats_mapping.py est adapté à cette source.
  • Face à main, la PR ne touche plus que memory_stats.py, ses deux tests et CHANGELOG. Le gate de mutation local donne 0 survivant ; tests_py/handlers, tests_py/server et test_main : 1877 passés.
  • Titre et description mis à jour. La PR reste en draft.

Generated by Claude Code

@cdeust
cdeust marked this pull request as ready for review November 6, 2026 22:32
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T22:33:18.193478Z 9842d53 Draft marked ready
🔒 Security Review ✅ Completed 2026-11-06T22:34:35.852554Z 9842d53 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Claude finished @cdeust's task in 2m 3s —— View job


Review of PR #902 (review 1 of 3)

None of the five reviews found a Blocker. Two Notes need no action before merge. CI was still running when I posted.

Verdict: approve

@claude

claude Bot commented Nov 6, 2026

Copy link
Copy Markdown

Review 1 of 3 (ADR-1119): PR #902

No Blockers. I traced the diff against origin/main and ran five reviews (code quality, performance, tests, documentation, security). None of them found a defect that would break CI, security or a CLAUDE.md non-negotiable.

  • Layers: memory_stats (handlers) imports abandoned_work (handlers). That is allowed.
  • Craftsmanship: handler spans 39 lines (end_lineno - lineno = 113 − 74; the limit is 40). The file is 113 lines.
  • Lint: ruff does not enable isort (I), so importing handlers after infrastructure passes.
  • Tests: test_each_reading_reaches_its_field compares the whole response dict. The monkeypatches on days_since, is_stale and _store take effect at call time, and abandoned_work.reset() runs before and after each test. Mutants in handler are killed.
  • Security: snapshot() exposes only tool names and counts. The names are hardcoded in the safe_handler(tool_name=...) calls, so a client cannot inject them. READ_ONLY is unchanged.
  • Documentation: the claims in the description and the CHANGELOG match abandoned_work.py and admission.py. "Completed" means the transaction scope committed.

I could not run ruff, pytest or check_craftsmanship.py in this job because of tool permissions. The checks above come from reading the code. CI (Pipeline run 42305481546) was still running when I posted this.

Notes, no action before merge

Verdict: approve

@github-actions
github-actions Bot merged commit fa6e716 into main Nov 6, 2026
21 of 28 checks passed
github-actions Bot pushed a commit that referenced this pull request Nov 7, 2026
…pletion (#728, slice 5) (#946)

* docs(admission): ADR-1125, a cancelled tool call's worker runs to completion (#728, slice 5)

Issue #728 asks to keep work counted "until completion or a documented
cooperative cancellation boundary". Slices 1-4 (#885, #903, #902, #936,
#943) implement and prove the first: the slot is released by the worker's
done callback, the caller awaits through asyncio.shield, abandoned workers
are reported in memory_stats.abandoned_workers. Nothing forwards the
cancellation into the worker thread, so no cooperative boundary exists.

ADR-1125 records those semantics, amends ADR-0329 (the slot belongs to the
worker), states what a handler that wants to stop early must specify, and
lists the tests that hold each point. Mirror regenerated with
wiki_decision_mirror.generate_mirror.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018kyZYs8nydviS39BjwT88E

* fixup! docs(admission): ADR-1125, a cancelled tool call's worker runs to completion (#728, slice 5)

---------

Co-authored-by: Claude <[email protected]>
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.

2 participants