Remove the main agent's ReAct turn cap; fix dark-mode legibility in the tool-call panel - #106
Merged
Merged
Conversation
added 2 commits
August 16, 2026 02:58
…ts up front
A fixed round ceiling bounds the wrong axis. What a long task exhausts is
context, not rounds, and any number picked here is at once too low for
report-scale work and too high to catch a genuine runaway. Both things the cap
was supposed to buy are already covered elsewhere: the chat-run watchdog reaps
silent and immortal runs on wall-clock and output, and compaction handles
context exhaustion.
The main agent is therefore unbounded; _UNBOUNDED_ITERS is a loop backstop for
AgentScope's int-typed ReActConfig, not a budget. Bounded budgets survive only
where the bound is a deliberate contract: sub-agents, turbo's quick-lookup cap,
a custom agent's own max_iters, a published profile's turn budget, and the
CHAT_MAIN_MAX_ITERS opt-in for operators who do want the main loop fenced.
Where a budget does exist, state it up front instead of only shouting at the
end. The wrap-up reminder can merely ask a loop that already burned its rounds
to salvage something; a static hint changes how the rounds get spent, and names
parallel fan-out as the lever, since a round is one reasoning step rather than
one tool call. The hint is byte-stable for a given budget so it stays
prefix-cache friendly, and it is never rendered for the unbounded main agent,
where a false scarcity claim would make the model cut work short.
Profile defaults follow: max_react_turns now defaults to the sentinel 0
("unbounded"), a missing key resolves to it rather than an invented ceiling,
and validation accepts the sentinel while still rejecting out-of-band values.
…s strip Three independent root causes, all invisible in light mode. The tool-call area rendered as black text on white. highlight.js ships a fixed light theme, and tool.css had zero dark overrides, so both the code block and its surrounding chrome stayed pinned to light surfaces. Hard-coded literals and `var(--token, #fff)` fallbacks were the mechanism: the fallback silently wins wherever the token is not defined, which makes a light value survive the theme switch. Those call sites now resolve through theme tokens, and a code-highlight.css layer re-tints the highlight.js palette for the dark theme. The status strip's "completed" grey sat too close to the background to read, and the running-state shimmer pulsed the wrong way: the gradient was built as "slate-400 base plus slate-600 highlight", creating its highlight by darkening text. That works over a light surface and inverts over a dark one, leaving the highlight at under 2:1 contrast. Dark mode now runs the gradient dark-to-light so the brightest point is the highlight. The shimmering title also collapsed into a solid grey block, because the dark override set `background`, and the shorthand resets `background-clip` back to `border-box` — the very property clipping the gradient into the glyphs. It now sets `background-image` and restates the clip.
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.
Two unrelated fixes, one backend and one frontend.
Remove the main agent's ReAct turn cap
A fixed round ceiling bounds the wrong axis. What a long task exhausts is context, not rounds, and any number picked is simultaneously too low for report-scale work and too high to catch a genuine runaway. Both guarantees the cap was supposed to provide already live elsewhere:
CHAT_RUN_INACTIVITY_TIMEOUT_SEC/CHAT_RUN_MAX_AGE_SEC/CHAT_RUN_HARD_MAX_AGE_SECreap silent and immortal runs regardless of round count);So the main agent is now unbounded.
_UNBOUNDED_ITERSexists only because AgentScope'sReActConfigneeds an int; it is a loop backstop, not a budget.Bounded budgets survive wherever the bound is a deliberate contract: sub-agents, turbo's quick-lookup cap, a custom agent's own
max_iters, a published profile's turn budget, and theCHAT_MAIN_MAX_ITERSopt-in for operators who do want the main loop fenced.Where a budget exists, it is now stated up front. The existing wrap-up reminder can only ask a loop that already burned its rounds to salvage something one tool call at a time; a static hint instead changes how the rounds get spent. It names parallel fan-out as the lever, because a round is one reasoning step rather than one tool call — the budget binds serial round-trips, never total tool calls. The hint is byte-stable for a given budget so it stays prefix-cache friendly, and it is deliberately never rendered for the unbounded main agent, where a false scarcity claim would make the model cut work short.
Profile defaults follow:
max_react_turnsdefaults to the sentinel0(UNBOUNDED_REACT_TURNS), a missing key resolves to that sentinel rather than an invented ceiling, and validation accepts the sentinel while still rejecting out-of-band values.Fix dark-mode legibility in the tool-call panel and status strip
Three independent root causes, none of them visible in light mode.
Tool-call area rendered black-on-white. highlight.js ships a fixed light theme and
tool.csscarried zero dark overrides, so the code block and its surrounding chrome both stayed on light surfaces. The mechanism was hard-coded literals andvar(--token, #fff)fallbacks — the fallback silently wins wherever the token is undefined, keeping a light value alive across the theme switch. Those call sites now resolve through theme tokens, and a newcode-highlight.csslayer re-tints the highlight.js palette for dark.Status strip unreadable and shimmering backwards. The "completed" grey sat too close to the background, and the running-state gradient was built as "slate-400 base + slate-600 highlight" — it creates its highlight by darkening the text, which works over a light surface and inverts over a dark one, leaving the highlight below 2:1 contrast. Dark mode now runs the gradient dark-to-light so the brightest point really is the highlight.
Shimmering title collapsed into a solid grey block. The dark override used the
backgroundshorthand, which resetsbackground-clipback toborder-box— precisely the property clipping the gradient into the glyphs. It now setsbackground-imageand restates the clip.Testing
test_iter_budget_middleware.py: the hint states the actual number, leads with the parallel-fan-out strategy, and is byte-stable for a given budget; an unbounded profile validates while an out-of-band one does not.test_policies_and_memory_ops.py: the built-in profile stays unbounded, and a profile published without an explicit turn budget does too.