Conversation
🌱 graft blast radius3 areas changed → 3 areas can be affected. 5 dependent symbols, depth 2. flowchart TB
A0(("LLM Integrations<br/>2 symbols"))
A1(("Cluster Naming<br/>2 symbols"))
A2(("Pull Request Review<br/>1 symbol"))
classDef reached fill:#D9EDF3,stroke:#3AA7C9,stroke-width:1.5px,color:#0E313C;
class A0,A1,A2 reached;
Who knows this code — 4 people across 6 areas
Ownership is git history over each area's own files, weighted towards recent work (120-day half-life). Merge commits and bots are dropped, and you are dropped from your own PR. A name with no All 5 dependent symbols, grouped by areaLLM Integrations — 2 symbols in 2 files
Cluster Naming — 2 symbols in 2 files
Pull Request Review — 1 symbol in 1 file
Test signal per changed area — 1 ⚠ · 2 ✗Reached = a node under a test path has a resolved edge into the changed symbol. It undercounts anything called indirectly — through a CLI, a spawned process or a dynamic import — so read a low ratio as “look here”, never as a coverage gate.
3 test suites also reference this code3 symbols, kept out of the diagram and the table so they cannot crowd out the areas a reviewer has to look at.
Open the interactive graph → — click an area to see its dependent symbols at file:line. |
Problem
With a local reasoning model behind an OpenAI-compatible endpoint (qwen3.8:27b
on Ollama), every forced-tool call in the deep pass came back empty:
The model spent the entire
maxTokensbudget in its reasoning channel. Ollama'sparser puts that in
message.reasoning, whichfromResponse()does not read, sograft sees an empty message and records a miss — after three minutes of GPU time.
The reasoning text itself shows the work was done; it ends with "Let me write
the final output now."
synthesizeandcruxboth requestmaxTokens: 8192, which is exactly thebudget the reasoning consumes.
Why the existing remedy does not reach this
openai.tsalready knows the fix —isRejectedToolsWithReasoning()retries withreasoning_effort: "none"when a provider rejects tools while reasoning is on:That is reactive, and it needs an HTTP 400. A server that answers 200 with an
empty message never triggers it. This PR adds the proactive form of the same
value.
Change
reasoning_effortis sent on every request when configured, following theexisting env-plus-flag pattern of every other provider option:
GRAFT_REASONING_EFFORT/--reasoning-effort <level>providers.ts, carried throughfactory.ts, applied inopenai.ts(solitellmandorcarouterinherit it — their option types arealiases);
anthropicis untouchedResult
Same call,
GRAFT_REASONING_EFFORT=none:12 nodes, 29 links — from a call that previously returned nothing.
Note on the cast
"none"is not in theReasoningEffortunion of the pinnedopenaiSDK, andthe value is user-supplied, so it is written through an index signature. The
existing reactive path solves the same typing problem differently
(
{ ...attempt, reasoning_effort: "none" } as ChatParams). If you would ratherhave one shape for both, say so and I will fold them into a small helper.