fix: route "what did I complete?" to find-activity - #569
Merged
Conversation
Asked "what did I actually get done last week?", Haiku 4.5 reached for get-productivity-stats and find-completed-tasks and never got to find-activity. The two answer different questions: find-activity reports completion events, so a daily recurring task completed five times shows five occurrences, while find-completed-tasks lists completed tasks and the recurring work silently vanishes from the answer. The guidance existed, on find-activity and in the instructions block -- everywhere except the tool that actually gets picked. Put the caveat where the wrong choice is made, on find-completed-tasks and on get-productivity-stats. The eval scenario could not measure this, because it judged the first tool call outright. Sonnet 5 opens with user-info to learn today's date and timezone -- without them "last week" is not a date range -- and then calls find-activity perfectly, so it scored 0% while behaving correctly. An expect allowlist naming the tool under test cannot also name every reasonable lookup that precedes it. So the harness now answers a context tool with a canned result and judges the next call, and judges a multi-call turn on its first non-context call rather than on whichever block came first. completed-via-activity, 8 repeats, both measured against the fixed judge: claude-haiku-4-5 38% -> 100% claude-sonnet-5 100% -> 100% Full run 100/100 on both models, up from 45/50. Token footprint 33,453 -> 33,519 of a 35,000 budget. Closes #564
doistbot
reviewed
Aug 11, 2026
doistbot
left a comment
Member
There was a problem hiding this comment.
This PR reroutes "what did I complete?" to find-activity by adding routing caveats to find-completed-tasks and get-productivity-stats, and upgrades the eval harness to stub context-tool lookups like user-info so the substantive call is judged instead of the preliminary one.
Few things worth tightening:
find-activitydefaults to all users, so in shared projects a first-person query can surface collaborators' completions — guide the model to fetch the current user ID viauser-infoand pass it asinitiatorId, and add that assertion to the eval scenario.- Use
Object.hasOwn(CONTEXT_TOOLS, c.name)instead ofinso a hallucinated name like__proto__isn't treated as a context tool via prototype inheritance. - Only bypass a context tool when it isn't explicitly expected by the scenario, otherwise a future
expect: [ToolNames.USER_INFO]scenario can never pass — its expected call gets stubbed instead of judged.
I also included a few optional follow-up notes in the details below.
Optional follow-up notes (2)
scripts/eval-instructions.ts:216: The
user-infostub duplicates theUserInfoStructuredshape by hand, and the comment above it notes the need to keep it in step with the real output schema. That link already exists:UserInfoStructuredis exported fromsrc/tools/user-info.ts. Annotating the entry withsatisfies UserInfoStructured(keeping the outer map asRecord<string, unknown>so future context tools with other shapes still fit) would turn the manual "keep in step" discipline into a compile-time check, so a renamed or newly-required field on the real schema breaks here instead of silently drifting.scripts/eval-instructions.ts:222: The
currentLocalTimestub is'2026-08-11 09:30:00'(YYYY-MM-DD HH:MM:SS), but the realuser-infotool formats it viatoLocaleString('en-US', …), which produces'08/11/2026, 09:30:00'(MM/DD/YYYY, HH:MM:SS). The comment onCONTEXT_TOOLSsays to keep the stub in step with the real output so the model reads it as it would a live one. The other date fields (weekStartDate,weekEndDate) already match the realgetDateStringformat;currentLocalTimeis the only one that doesn't. Low impact today (both models pass at 100%, andweekStartDateis the primary field for deriving "last week"), but worth aligning so a future model that leans oncurrentLocalTimeisn't trained on a format it won't see in production.
Review raised that rerouting a first-person question to find-activity lands on a tool that reports every user's events by default, so in a shared project the answer includes collaborators' completions. The scenario now asserts initiatorId, which turned an argued point into a measured one: it was 0% on both models, since neither was asked for it. Sonnet 5 passed as soon as find-activity's description mentioned it. Haiku needed the routing rule to name the order of the two calls -- user-info first for the ID, then find-activity -- because it was answering in one shot and had no ID to pass. Every Haiku run that calls user-info first now passes; the ones that skip it are the remainder. completed-via-activity, 12 repeats, judged with the initiatorId assertion: claude-haiku-4-5 0% -> 67% claude-sonnet-5 0% -> 100% Also from review: - Object.hasOwn for the context-tool predicate, so a hallucinated __proto__ is not treated as a context tool via the prototype chain. - A tool a scenario expects is never stubbed as context, or a scenario testing the route to user-info itself could never pass. - The stub is tied to UserInfoStructured with satisfies, and its currentLocalTime now matches the toLocaleString format the real tool emits rather than ISO. Full run 100/100 on both models. Token footprint 33,519 -> 33,624.
doist-release-bot Bot
added a commit
that referenced
this pull request
Aug 12, 2026
## [12.5.6](v12.5.5...v12.5.6) (2026-08-12) ### Bug Fixes * route "what did I complete?" to find-activity ([#569](#569)) ([212ac69](212ac69))
Contributor
|
🎉 This PR is included in version 12.5.6 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #564.
The bug
Asked "what did I actually get done last week?", Haiku 4.5 reaches for
get-productivity-statsandfind-completed-tasksand never gets tofind-activity. Those answer different questions:find-activityreports completion events, so a daily recurring task completed five times shows five occurrences;find-completed-taskslists completed tasks, and the recurring work silently vanishes from the answer.The guidance already existed — on
find-activity, and in theinstructionsblock. Everywhere except the tool that actually gets picked.find-completed-tasksopened with "Get completed tasks", a perfect surface match for the question, and a model that has a hit stops reading. So the caveat now sits where the wrong choice is made, on bothfind-completed-tasksandget-productivity-stats.The scenario could not measure it
Worth flagging, because it is the more interesting half. #564 reports Sonnet 5 at 0%, and Sonnet 5 turns out to answer the question perfectly. It opens with
user-info— without today's date and the user's timezone, "last week" is not a date range — and then calls exactly what the scenario asks for:The harness judged the first tool call outright, and
user-infois not in theexpectallowlist, so correct behaviour scored 0. This is the failure AGENTS.md:150 already warns about: an allowlist naming the tool under test cannot also name every reasonable lookup that precedes it. Fixing tool descriptions against that reading would have been optimising against noise.Two changes to the harness, both narrow:
CONTEXT_TOOLS, currently justuser-info) is answered with a canned result, and the next call is judged. Capped atMAX_CONTEXT_HOPSso a model that only ever gathers context still fails. A tool a scenario explicitly expects is never stubbed, so a future scenario testing the route touser-infostays measurable.Adding
user-infotoexpectwould have been the cheap fix and the wrong one: the judge would pass on the lookup alone and check no arguments at all, scoring 100% while measuring nothing. AGENTS.md now documents both traps.Filtering the answer to the asker
From review, and it turned out to be a real hole rather than a theoretical one.
find-activityreports every user's events by default, so rerouting a first-person question to it means a shared project answers "what did I get done" with collaborators' completions mixed in. AssertinginitiatorIdin the scenario put it at 0% on both models — nothing was asking for it.Sonnet 5 was already passing it unprompted and went to 100% once
find-activity's description named it. Haiku needed the routing rule to name the order of the two calls —user-infofirst for the ID, thenfind-activity— because it was answering in one shot and had no ID to pass. Every Haiku run that callsuser-infofirst passes; the ones that skip it are the remaining failures.Numbers
completed-via-activity, 12 repeats,mainvs this branch, both judged by the harness as it stands here (context hops, andinitiatorIdasserted):Sonnet's "before" is 100% because its routing was never broken — the harness fix alone is what surfaced that, and the argument assertions it already satisfied. Haiku's 67% is the honest 12-repeat figure; smaller samples ran higher, and the residual failure is always the same one, skipping
user-infoand so having no ID to filter by.Splitting the two halves apart, since they moved for different reasons: on routing alone (which tool gets picked, the subject of #564), this branch is 100% on both models against main's 38% for Haiku. On filtering to the asker, 0% → 67% Haiku, 0% → 100% Sonnet.
Full sweep, 10 scenarios x 5 repeats: 100/100 on both models, up from 45/50 —
completed-via-activitywas the only failing scenario, and no other scenario moved.Cost
Token footprint 33,453 → 33,624 of a 35,000 budget: +138 across three tool descriptions and one input field, plus 33 on the
instructionsblock (777 → 810). The budget is a ratchet, so to be explicit — this is a rise, bought with a routing failure fixed on the model that was getting it wrong, and a correctness gap closed on both.Also from review
Both P3 notes applied: the
user-infostub is now tied toUserInfoStructuredwithsatisfies, so a renamed or added field on the real schema breaks the harness instead of drifting; andcurrentLocalTimenow uses thetoLocaleString('en-US', …)format the tool actually emits rather than ISO.Left for later
instructionsblock bullet grew rather than shrank here, since naming the call order is what moved Haiku. Whether the tool descriptions can now carry that alone is worth a measured follow-up.checkdoes not assertobjectType === "task", though the instructions andfind-activityboth ask for it. Both models set it anyway. Tightening it is a separate change, since it would move the baseline.🤖 Generated with Claude Code