Skip to content

[pull] main from danny-avila:main - #216

Merged
pull[bot] merged 7 commits into
innFactory:mainfrom
danny-avila:main
Sep 7, 2026
Merged

[pull] main from danny-avila:main#216
pull[bot] merged 7 commits into
innFactory:mainfrom
danny-avila:main

Conversation

@pull

@pull pull Bot commented Sep 7, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

danny-avila and others added 7 commits September 7, 2026 07:40
* 🗜️ feat: Summarize-Only Runs for Manual Compaction

Adds `summarizeOnly` to the agent inputs. A summarize-only run requests
summarization outright on its first model step instead of consulting the
configured trigger, and the step after the summary dispatches the
post-summary context usage snapshot and ends the run without ever calling
the model. Hosts use it for user-initiated compaction, where the summary is
the whole response and the snapshot is what the host persists as the
branch's new context baseline.

The summarize node learns a `manual` reason for the request: the default
recency window does not apply to a summary the user asked for, so the whole
history becomes the checkpoint unless the host configured `retainRecent`
explicitly. Summarization that is not enabled ends the run immediately with
no model work, and the request claim is reset per run so a graph can be
reused.

* 🗜️ fix: Fail Closed and Report Correctly on Summarize-Only Runs

Review round 1. A summarize-only run has no reply to fall back on, so every
way it could end without a summary now either preserves history and reports
the failure, or rejects:

- A provider failure that yields the metadata stub keeps the history and
  completes the summary step with an error, as overflow recovery already
  did; a manual compaction must never replace the conversation with a
  message count.
- A summary the graph cannot attempt — summarization disabled, the
  summarizer exhausted, instructions exceeding the budget, or an explicit
  recency window that already covers the whole conversation — rejects with
  `ManualSummarizationSkippedError` instead of ending quietly.
- A `retainRecent` that sets only a token cap keeps the ordinary turn
  default; only an absent `retainRecent` means the whole history.
- The summary rides in state as `manualSummary`, so the Langfuse root
  output reports it rather than the retained tail or the raw graph state.
- In a multi-agent graph, successors of the summarizing agent are no-ops
  and the workflow drains to END, so no chained agent spends a model call
  on the compacted history.

* 🗜️ fix: Route and Report Summarize-Only Runs Through Every Graph Shape

Review round 2.

- The standard workflow's outer state now declares `manualSummary`, as the
  agent subgraph and the multi-agent outer state already did, so a
  single-agent summarize-only run reaches the Langfuse root with its
  summary rather than serialized state.
- Root-output shaping prefers the summary over any assistant message left
  in state: with an explicit recency window that message is an older reply
  the run retained, never its result.
- A multi-agent run also starts at the agent that opted in, so a summarizer
  reachable only through a handoff runs; the workflow's own entry points
  stay reachable and end without a model call.
- The primary model is constructed only once a model call is certain, so a
  summarize-only step neither fails on a primary configuration it never
  invokes nor pays for its tool binding.
- The test seam types the recording summarizer against `initializeModel`'s
  return type directly, without a double assertion.

* 🗜️ fix: Compile a Summarize-Only Run to Its Agent Alone

Review round 3.

- A multi-agent summarize-only run now compiles to the opted-in agent
  alone: no other node, no handoff or direct edge, START → agent → END.
  Keeping the ordinary routing ran a direct-edge target twice (from START
  and again after its predecessor) and could append routing prompts to the
  checkpoint it had just produced. The model-node no-op for successors is
  gone with it.
- `manualSummary` never outlives its run on a checkpointed thread: the
  summarize-only request clears the previous value before the node writes
  the new one, and an ordinary run's first model step clears whatever it
  inherited, so a later run or a failed compaction cannot report an old
  checkpoint as its output.
- PreCompact hooks receive `trigger: 'manual'` for a manual compaction
  instead of the configured token-pressure trigger that was never
  evaluated.

* 📝 docs: Describe the Summarize-Only Workflow as Compiled to Its Agent

* 🗜️ fix: Attribute, Persist, and Terminate a Summarize-Only Run Correctly

Review round 4.

- The run's agent follows the summarizer: `defaultAgentId` resolves to the
  agent that opted in, so root trace identity and per-agent Langfuse
  routing name the agent that actually ran, not the workflow's first.
- The compaction's remove-all now crosses the agent-subgraph boundary. The
  subgraph applies it internally and returns only the retained tail, which
  the outer reducer merged back into the history it already held, leaving a
  checkpointed thread uncompacted. The outer node re-issues the remove-all
  when the run produced a summary; a run that produced none passes through.
- A summarize-only run admits no stop continuation. Its one summarization
  claim is spent, so a blocking Stop hook would start segments that end
  without a model call until the continuation budget turned a successful
  compaction into an error. Stop and StopFinalize hooks still fire.
* refactor: classify the retained tool share with the provider tool taxonomy

`budget.ts` hand-rolled its own answer to "is this message a tool invocation,
a tool result, or mixed content" while `toolResultTypes.ts` already holds the
canonical one, consumed by `format.ts`, `alternation.ts`, `recency.ts` and
`handlers.ts`. Every shape the local classifier lagged behind became a review
finding: v1 standard `tool_call` blocks, `HumanMessage(tool_result)` history,
Anthropic server tools, generic assistant messages. This removes the fifth
classifier instead of patching it.

Lift the message-level walker recency.ts kept private into the taxonomy as
exports: `appendProviderMessageToolCalls` (now reporting how many calls it
recognized), `getProviderToolMessageResultDescriptor`, and a single
`getProviderMessageRole` that normalizes `ChatMessage` roles the converters
accept. recency.ts imports them back unchanged in behaviour.

Redefine tool-only on the taxonomy: every part is blank text, a call
descriptor, an inline provider tool result, or a reasoning block. That closes
the provider asymmetry where Anthropic thinking turns were excluded while
OpenAI reasoning in `additional_kwargs` was counted, admits `server_tool_use`,
`mcp_tool_use` and their results, counts a user turn made only of paired
`tool_result` parts, and counts generic tool-role results. A call id with
conflicting names is ambiguous and attributed to `unknown_tool` rather than
guessed, matching the index's own conflict rule.

The lifted helpers stay internal: `toolResultTypes.ts` is not re-exported.

* fix: pair results the way the wire walkers do and accept approximate counters

Results now consume their call through `consumeProviderToolResultPair`, so a
provider that reuses a call id in a later turn is attributed to the call it
currently answers instead of poisoning the id, and answered calls no longer
accumulate toward the bounded index cap in long tool-heavy contexts. Pending
calls are forgotten at an ordinary user turn, as the recency walker does; a
user turn made only of paired tool results is committed from a candidate copy.

The `TokenCounter` contract promises a `number`, and the pruning path accepts
approximate counters such as `length / 4`. The share now rounds a finite,
non-negative count rather than dropping the whole gauge on the first fraction;
NaN, infinities, negatives and values past the safe range still degrade.

`getProviderMessageRole` learns the aliases the Google converter reads as the
model turn (`ai`, `model`, `supervisor`) and as the user (`human`).

* fix: consume inline provider results and count folded tool history

An assistant turn carrying a provider call and its inline result, such as
`server_tool_use` followed by `web_search_tool_result`, recognized the result
but left the call pending, so a reused id was marked ambiguous and answered
inline exchanges still accumulated toward the bounded index cap. The scan now
pairs and consumes inline results the same way whole-message results are.

A tool-less destination inheriting tool history has each call and its results
folded into one synthetic user turn, and compaction of that fold keeps the
lineage. The role-based scan read those as ordinary conversation, reporting a
zero tool share when folded tool output dominated the prompt. The fold is what
the provenance stamp records, so a user turn whose provenance carries a `tool`
part is counted as retained tool output; per-tool attribution is lost with the
fold. Synthetic turns without tool lineage, such as handoff cues, stay in the
conversation share.

* fix: count Google code-execution exchanges as tool-only invocations

Google's built-in code execution emits an `executableCode` part with no call
id, and its `codeExecutionResult` pairs by adjacency. The scan recognized the
result but treated the code part as visible content, so the whole exchange
reported zero. `isExecutableCodePart` is now exported from the taxonomy and
counts as an invocation; the adjacent result then pairs as before.

The fold JSDoc now states the bounded over-count a folded turn carries when
visible model text was folded alongside its calls.

* fix: round the tool share once after aggregation

Rounding each message's count before summing threw away sub-token amounts
the pruning path keeps: four quarter-token results summed to one token there
and to zero here. Per-message counts now stay raw through aggregation, guarded
only for finiteness, sign and the safe range, and the aggregate and per-tool
totals round once at the end. `apportionTokenCounts` already distributes
fractional per-tool totals to integers against the rounded result share.
* feat(langfuse): promote safe tool observation metadata

* fix(langfuse): preserve observation identity during metadata promotion

* fix(langfuse): record safe metadata from host tool results

* fix(langfuse): mark failed host tool observations as errors

* perf(langfuse): index host tool calls by ID
* perf: consolidate retained tool accounting

* fix: preserve provider-aware tool accounting

* fix: preserve provider roles in recency fallback

* perf: lazily count retained tool messages
* fix: count raw OpenAI tool calls

* fix: traverse raw tool metadata safely

* fix: align raw tool-call measurement

* fix: project descriptor-read tool calls

* fix: bound provider raw tool metadata
* perf: cache active AgentSession log projections

* test: cover cached compaction and continuation transitions
* 🧠 refactor: Centralize Reasoning Block Classification

* fix: Preserve Provider Reasoning Replay Policy
@pull pull Bot locked and limited conversation to collaborators Sep 7, 2026
@pull pull Bot added the ⤵️ pull label Sep 7, 2026
@pull
pull Bot merged commit 945119d into innFactory:main Sep 7, 2026
2 checks passed
@pull
pull Bot deployed to publish September 7, 2026 16:44 Active
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants