Skip to content

v0.3.52

Latest

Choose a tag to compare

@abi-chatterjee abi-chatterjee released this 31 Jul 06:57
c8135ee

Jiva v0.3.52 — Chat-Mode Tool-Call Logging & Recursive Argument Truncation

Release Date: July 29, 2026


Summary

Chat mode's WorkerAgent now logs tool-call arguments the same way Code
mode's CodeAgent has since v0.3.51 — closing a gap where the v0.3.51 fix
never got carried over to Chat mode. Since Chat mode has no "built-in vs
MCP" distinction (every tool, including filesystem ops, goes through
mcpManager.getClient()), this one fix covers every MCP server's tools
generically. Also replaces the argument truncator with a recursive version
so nested MCP tool schemas can't produce invalid JSON in the log. Also
fixes a real, longstanding bug (present since v0.3.41) where a batch of
parallel tool calls could get left half-answered, causing frequent, hard
API failures mid-run in both Code and Chat mode. A second, separate source
of the same API failure — in-loop context compaction splitting a tool call
away from its result — is also fixed, alongside a higher, now-configurable
compaction threshold.


Bug Fixes

Incomplete parallel tool-call groups causing hard API failures

Before: CodeAgent and WorkerAgent's tool-execution loops iterate
response.toolCalls, which can hold several calls the model issued
together as one parallel batch. Both had break statements (doom-loop
detection in both; CodeAgent's "15 consecutive read_file calls" nudge) that
exited the loop early without ever sending a tool-result message for the
call that triggered the break, or for any calls still queued later in the
same batch. The next API call then sent a conversation history with a
partially-answered tool_calls array, which providers that validate
parallel tool-calling strictly reject outright with invalid_tool_messages: incomplete parallel tool-call group: tool call 'X' has no tool response
— and every retry hit the identical error, since the retry logic re-sent
the same malformed history without fixing the actual gap. In practice this
surfaced as a Deep Run that worked fine for a while, then collapsed with
repeated API error (400) and produced nothing.

After: Both loops now push a stub tool-role response (via the
existing formatToolResult() helper) for the triggering call and every
call still queued after it in the batch before pushing the nudge/stop
message and breaking — every tool_call_id the model issues is always
answered before the next request goes out.


Context compaction could orphan a tool_call from its result

Symptom: A separate cause of the same invalid_tool_messages family of
API failures above — "tool message tool_call_id 'X' does not match any tool call in the preceding assistant messages" — surfaced in Code mode
specifically on wide parallel tool-call batches.

Root cause: CodeAgent's in-loop compaction (compactInLoopMessages)
kept the last 10 messages verbatim and summarised everything before that
away — a raw positional cut with no awareness of where a tool-call/
tool-result boundary fell. A parallel batch wider than 10 messages could
straddle that cut: the assistant message declaring the calls got
summarised away while some of its own trailing tool-result messages
survived in the kept tail, orphaning them.

Fix: Compaction now groups messages into "rounds" (an assistant/user
message plus the tool-result messages immediately following it,
src/code/rounds.ts) and always cuts on round boundaries, never mid-round
— works the same whether or not the assistant message carries a
structured tool_calls array (i.e. also correct for Harmony-mode
history). As a second line of defence, a new ensureToolResultPairing()
(src/models/harmony.ts) runs immediately before every model call in both
CodeAgent and WorkerAgent, silently repairing (and logging) any orphaned
or unanswered tool call that slips through regardless of cause.


New Features

Chat mode tool-call argument logging

Before: WorkerAgent's per-tool-call log line was a bare
[Worker] Tool: <name> with no arguments at all, unlike CodeAgent,
which has logged full args since v0.3.51.

After: src/core/worker-agent.ts's log line now matches CodeAgent's
shape: ` [Worker] Tool: ${toolName} ${formatToolCallArgs(args)}`,
using the same formatToolCallArgs() helper from src/utils/logger.ts.
The log call was also moved to after toolCall.function.arguments is
parsed (it previously ran before parsing, when args wasn't yet
available). No per-tool-name special-casing was needed — because Chat mode
routes every tool call, including filesystem operations, through
mcpManager.getClient(), this single change covers every MCP server's
tools.

Configurable compaction threshold, raised default

CodeAgent's in-loop compaction fired once the prompt exceeded a fixed
90,000-token threshold, and — separately — could only ever fire once per
chat() turn, no matter how many iterations followed. That once-per-turn
cap meant the threshold needed generous headroom to survive whatever a
long tool-heavy turn (up to maxIterations, default 50) might add
afterwards with no further compaction possible.

Compaction can now re-trigger later in the same turn once growth crosses
the threshold again (compactedThisIteration, reset every iteration
instead of once per turn). With that safety net in place, the default is
raised to 100,000 tokens (~78% of a 128K context window, matching
DualAgent's existing equivalent default). It's also now overridable —
via codeMode.compactionThreshold in jiva config for the CLI, or
JIVA_CODE_COMPACTION_THRESHOLD for the HTTP interface — for models with
a larger context window. Set to 0 to disable in-loop compaction
entirely.

Recursive argument truncation

Before: formatToolCallArgs() only truncated string values at the
top level of the arguments object, then hard-cut the final serialized JSON
string at a fixed 2000-character length as a fallback. That was safe for
today's flat, top-level built-in tool arguments, but a deeply nested MCP
tool schema could have its structure cut off mid-token by the length-based
fallback, producing invalid JSON in the log.

After: src/utils/logger.ts replaces the shallow truncation with a new
recursive truncateValue() that walks objects and arrays at any nesting
depth, truncating string leaves wherever they occur (still 500 chars per
value), capping arrays at 20 entries and objects at 50 keys (each with a
...(N more items/keys) marker), and bailing out at depth 10 with
'[max depth exceeded]'. formatToolCallArgs() now simply returns
JSON.stringify(truncateValue(args)) — the result is always valid JSON
regardless of how deeply nested the input is, since truncation happens
before serialization instead of by slicing the serialized string.


Files Changed

File Change
src/core/worker-agent.ts Tool-call log line now includes formatToolCallArgs(args); log call moved after argument parsing. Tool-call loop now indexed and stubs tool-result responses for calls abandoned by the doom-loop break; ensureToolResultPairing() runs before every model call
src/code/agent.ts Tool-call loop now indexed and stubs tool-result responses for calls abandoned by the doom-loop or excessive-reads-nudge break; ensureToolResultPairing() runs before every model call; compaction rewritten to cut on round boundaries (compactInLoopMessages); compaction can re-trigger per iteration (compactedThisIteration); default threshold raised 90,000 → 100,000, overridable via compactionThreshold
src/code/rounds.ts NewgroupMessagesIntoRounds() / splitRoundsForCompaction(), the round-boundary grouping logic that compactInLoopMessages now cuts on
src/models/harmony.ts New ensureToolResultPairing() — repairs orphaned/unanswered tool calls in message history, logging when it does
src/core/config.ts New optional codeMode.compactionThreshold field
src/interfaces/cli/index.ts Passes codeModeConfig.compactionThreshold through to CodeAgent
src/interfaces/http/session-manager.ts New JIVA_CODE_COMPACTION_THRESHOLD env var passed through to CodeAgent
src/utils/logger.ts New recursive truncateValue() (depth-aware string/array/object truncation) replaces the old top-level-only truncation + fixed-length JSON-string cut; formatToolCallArgs() now truncates before serializing instead of after

Upgrade

npm install -g jiva-core@0.3.52

No breaking changes. One new optional config field: codeMode.compactionThreshold
(jiva config) / JIVA_CODE_COMPACTION_THRESHOLD (HTTP interface env var) —
existing configs are unaffected and pick up the new 100,000-token default
automatically.