Skip to content

feat(adk): replace forced tool call with response_format fallback for… - #1145

Open
N3kox wants to merge 125 commits into
alpha/10from
fix/automemory
Open

feat(adk): replace forced tool call with response_format fallback for…#1145
N3kox wants to merge 125 commits into
alpha/10from
fix/automemory

Conversation

@N3kox

@N3kox N3kox commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

… topic selection

What type of PR is this?

Check the PR title.

  • This PR title match the format: <type>(optional scope): <description>
  • The description of this PR title is user-oriented and clear enough for others to understand.
  • Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. User docs repo

(Optional) Translate the PR title into Chinese.

(Optional) More detailed description for this PR(en: English/zh: Chinese).

en:
zh(optional):

(Optional) Which issue(s) this PR fixes:

(optional) The PR that updates user documentation:

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.11765% with 44 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (alpha/10@a4afa59). Learn more about missing BASE report.

Files with missing lines Patch % Lines
adk/middlewares/automemory/automemory.go 71.26% 22 Missing and 3 partials ⚠️
adk/middlewares/automemory/utils.go 73.97% 17 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             alpha/10    #1145   +/-   ##
===========================================
  Coverage            ?   82.05%           
===========================================
  Files               ?      194           
  Lines               ?    31343           
  Branches            ?        0           
===========================================
  Hits                ?    25719           
  Misses              ?     3810           
  Partials            ?     1814           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shentongmartin
shentongmartin force-pushed the alpha/10 branch 2 times, most recently from af155cf to a4afa59 Compare July 21, 2026 04:03
Comment thread adk/middlewares/automemory/automemory.go
Comment thread adk/middlewares/automemory/automemory.go
Change-Id: I09b9754cf51f10fe662fb6cb33935ad92a4d1656
…mance

- Add HumanReadableSerializer that produces human-readable JSON output
- Add GobSerializer for comparison benchmarks
- Refactor serialization_test.go to use table-driven tests for both serializers
- Add comprehensive benchmarks comparing InternalSerializer, HumanReadableSerializer, and GobSerializer

Performance improvements over InternalSerializer:
- 30-68% faster marshal/unmarshal operations
- 17-49% less memory allocation
- 30-76% fewer allocations
- 33-59% smaller serialized output size

The HumanReadableSerializer uses standard JSON encoding with type annotations
only for interface{} fields, making the output both human-readable and
type-preserving for round-trip serialization.~

Change-Id: Ifeae12484fc73b74067a0ac3a496e73e0674b975
Change-Id: Iecbcfd8ab5905e61df9a5578e8d3c99387dace85
Change-Id: I5ac5d8b60be92a28d1e57929f03fa4201649fbd0
Change-Id: I318ff16a6bd81bb57b38791eeea17b3385b1fde4
Change-Id: Iefb047967d4cb4796edd363e7e6ba0a9c4f49148
Change-Id: I3de2ba37914fe779cd880b38ce067acf5fbde7c8
The runner previously wrote the interrupt checkpoint inline, before the
session event persister had flushed. This risks a checkpoint that
references events not yet durable in the SessionStore.

Introduce deferredRunnerCheckpoint: on the interrupt/cancel path the
checkpoint payload is captured but not written until finalize() confirms
persister.closeAndWait succeeded. If event persistence failed, the
checkpoint write is skipped entirely (fail-closed).

Also adds regression tests for checkpoint ordering invariants and the
InMemoryStore checkpoint round-trip.

Change-Id: Ib29263add4a261513f1349a9173a913c74ee65ef
Resume previously assumed the agent_tool interrupt state was always the
JSON envelope (agentToolInterruptState) introduced for SessionID-based
event filtering. Pre-envelope checkpoints stored raw gob bridge bytes,
so json.Unmarshal failed outright and resume errored.

Try the JSON envelope first; on parse failure or empty BridgeCheckpoint,
treat the raw bytes as the legacy bridge checkpoint and synthesize a
fresh childSessionID. Pre-envelope checkpoints predate session
persistence and have no parent-session filter to coordinate with, so
the synthesized ID is harmless.

Un-skip the v0.7.37, v0.8.2, v0.8.3, v0.8.4 compat fixtures so the
resume path is now exercised for real on-disk legacy bytes.

Change-Id: I5000424ba028e9fb9fe3843ea9673a3dd23a7860
Merge AfterCursor and PageToken into a single `After` field in
LoadEventsOptions. Rename NextPageToken to `Next` in LoadEventsResult.
Rename afterEventCursor to afterCursor in LoadLatestTurnEnd returns.

One concept, one name: the caller seeds After from LoadLatestTurnEnd,
then passes res.Next back as After on subsequent pages.

Change-Id: Icd0960940b7f69938e2b4a5062d220c709bc0e66
The Options suffix implies a functional-options pattern; Request better
reflects the struct-pointer parameter style and pairs with LoadEventsResult.
Also removes dead variable in conformance test.

Change-Id: Ia28d8a1f92f4307182508dfba56e2b3c454acfa4
…ializer

Correct the tool-call middleware ordering in chatmodel.go doc comments
(cancelMonitor wraps around user handlers, not inside). Add architectural
doc comment to newTypedInvokableAgentToolRunner explaining why AgentTool
lacks its own SessionStore. Remove the unused GobSerializer type alias
from schema/serialization.go (already aliased internally). Update
conformance test variable names to match LoadEventsRequest rename.

Change-Id: I2251e190c9d343dbd1e57cbebfc236840be4a0fa
…igurable page size

AppendEvents failures in the session persister now retry with exponential
backoff (default 3 retries, 50ms initial delay, 2x multiplier, 25% jitter)
before latching the error. This prevents transient store failures from
causing irrecoverable session log corruption.

Also extracts the hard-coded page-size=100 in reconstructFromEventLog and
replayTailEvents into the configurable LoadPageSize field on
SessionPersistenceConfig (default 100, preserving current behavior).

New config fields on SessionPersistenceConfig:
- MaxFlushRetries (default 3, set to -1 to disable)
- FlushRetryInitialBackoff (default 50ms)
- LoadPageSize (default 100)

Change-Id: I407b6038fd61df74420a5ddd16ce8ec0f8860948
…preservation

When a SessionStore is configured, the Runner now reuses the exact tool
list from the previous turn's TurnEndState to feed the model, ensuring
byte-exact prompt cache hits across turns. The ToolSearch middleware
skips its initialization strip logic when it detects pre-seeded tool
infos via the new ToolInfosPreSeededKey RunLocalValue.

Users can opt out with WithRefreshToolInfos() when tools have genuinely
changed between turns.

Change-Id: I95585e105d41689f08116325478f21552482b79b
Change-Id: Id35944259eaee9254bc9ffa7288fe6bc43bc021b
…ion mode

When sessions are enabled, TurnEndState.Messages carries the previous
turn's system message. The Runner prepends this history to the new input,
then defaultGenModelInput unconditionally prepends a fresh system message,
causing duplication. Strip the leading system message from history before
prepending the fresh instruction so that dynamic SessionValues are always
re-evaluated without duplicating the system prompt.

Change-Id: Id14acc6aa5f2941ea64c4de82393a36bcea2fb36
…to single file

Merge three scattered test files (human_readable_test.go,
human_readable_edge_test.go, schema/toolinfo_humanreadable_test.go) into
one unified file in internal/serialization. Replace schema.ToolInfo usage
with a local mock type to eliminate the circular dependency, and remove
duplicate test cases that exercised identical code paths.

Change-Id: I0b1fa9d001201382917abb8bf4b3bd220ffdf13d
Reduce SessionStore from 4 methods to 2 (AppendEvents + LoadEvents) by
merging TurnEndState into the event log as a SessionEvent variant. This
eliminates duplicate message storage and unifies reconstruction into a
single reverse-scan algorithm.

- Rewrite session/in_memory_store.go with forward/reverse pagination
- Remove SaveTurnEnd/LoadLatestTurnEnd from all test mocks
- Replace encodeTurnEndState/decodeTurnEndState with encodeSessionEvent
- Replace reconstructFromEventLog with reconstructSessionState

Change-Id: I979e88727dd33bfaa6983241b7e90b7bbbe040e0
Replace the opaque integer-index cursor with the per-event UUIDv4
event_id, giving SSE consumers a stable identity for Last-Event-ID
resume and de-duplication. AppendEvents becomes idempotent
(first-write-wins on duplicate event_id) so persister retries no longer
double-write. Introduce ErrInvalidEventID and ErrEventIDOutOfRange
sentinels with isProtocolError classification, so the persister
fail-fasts protocol violations while still retrying infrastructure
errors. Stores treat event_id as an opaque non-empty string; UUIDv4 is
the Runner allocation convention, not a store-enforced format.

Change-Id: I70c277af5505d57c7354e8056372c1021d6009eb
Allocate EventID once at the AgentEvent emission boundary (execCtx.send)
so user-land stream consumers and persisted SessionStore records share
the same identity. SSE adapters can now use AgentEvent.EventID as the
SSE id: line, and clients reconnecting with Last-Event-ID can pass that
value directly to SessionStore.LoadEvents(After: ...) without going
through the store.

toSessionEvent reuses event.EventID instead of minting a new UUID, with
a defensive fallback for test fixtures that construct events directly.
makeInputSessionEvent is unchanged (no upstream AgentEvent).

Change-Id: I525675f948aef55d6afc0dcdc46bd7e255a68311
Persist lifecycle, model span, retry/failover, and tool observation events through the managed session log while preserving the EventID identity contract between live AgentEvents and stored SessionEvents.

Add focused coverage for replay boundaries, timeline exposure, retry/failover observability, model usage metadata, gob compatibility, and persistence guards.

Change-Id: Id78c137b40265324d315237067a82e8ea1ffc8ef
Change-Id: I84f8155f66807d6e229741b9f5b3f6345f58f1d5
Change-Id: Id53b725579b6e7ceaeb9f4edd3c65a11e092f8e1
Delay loaded checkpoint abandonment until fresh-turn agent preparation succeeds, and fail before execution if checkpoint deletion fails.

Fold durable attack coverage into normal ADK test suites and remove the standalone attack test file.

Change-Id: I43fa3cd8d25dbd4ea3d1ca4c845191fa5a9c503d
shentongmartin and others added 23 commits July 22, 2026 17:27
…nt model (#1106)

* refactor(adk): introduce SessionEventVariant and simplify session event model

- Replace SessionEvent pointer in TypedAgentEvent with SessionEventVariant sum type
- SessionEventVariant carries either materialized SessionEvent or MessageStreamRef for streaming
- Move SessionID from durable SessionEvent to live SessionEventVariant metadata
- Remove EventID and Timestamp from TypedAgentEvent (already in SessionEvent/MessageStreamRef)
- Simplify store interface: pass sessionID as first-class arg, remove AppendEventsRequest
- Rename UserObservation/UserInterrupt/AgentInterrupt to Cancel/Interrupt
- Remove SessionRunStateRescheduled (only running and idle remain)

Change-Id: Iab036b8ac7c445fde8cb54215aefab12f51eef1c

* fix(adk): resolve dead code in toSessionEventChecked and add doc comments

- Remove unused SessionEvent construction in fallback path of toSessionEventChecked
- Add doc comments for MessageStreamRef, CancelEvent, and SessionEventVariant methods

Change-Id: I4ba1ff5faf2abfa13ddc59fafb065a73a17d0345

* fix(adk): snapshot leading system message before genModelInput

- deep copy leading system message before calling GenModelInput to detect
  in-place mutations (Extra map, Content, etc.) that would otherwise be
  missed by sameSystemMessage comparison
- remove dead code in streaming-complete persistence branch (persistMV /
  persistOutput were constructed and immediately discarded)
- add regression tests for in-place Extra and Content mutation in GenModelInput
- document SessionEventVariant invariant and turn_end legacy compatibility

Change-Id: Id8d6d3f91e013642a7573502ae4ad562239cbc07

* refactor(adk): replace turn_end bypass with generic unknown-kind tolerance

replace hardcoded "turn_end" compatibility shim with a general mechanism
that tolerates any unrecognized event kind carrying no
payload:

- add knownSessionEventKinds set and isKnownSessionEventKind helper
- add countActiveSessionEventPayloads helper for structural check
- tolerate unknown kinds with zero recognized payloads (forward/backward compat)
- known kinds with missing/wrong payloads still error as before

Change-Id: I48a74dc546a3c3b5a1f21a6a9a23ccae32aaf7fe

* chore(adk): remove unreachable gob registrations for SessionEventVariant and MessageStreamRef

checkpoint sanitizers strip SessionEventVariant before gob encoding,
and the store serializer only encodes *SessionEvent[M]. add a comment
explaining why these types are not registered.

Change-Id: I55e2726a95ed3e31abc28719b8ed4c2f8106114e

* test(adk): align variant serialization test with durable payload

Change-Id: I0b9607028aebc61976529082b67f2d9751196563
…ng (#1107)

* feat(adk): background-task manager with subagent/filesystem/deep wiring

Introduce a shared, domain-agnostic background-task engine and wire it into the
subagent and filesystem middlewares and the deep prebuilt agent.

adk/backgroundtask (engine):
- Manager tracks foreground/background/auto-background runs under one task-ID
  space; Run blocks with an optional foreground budget, then either completes,
  auto-backgrounds (Config.ShouldAutoBackground), or times out.
- RunStream + StreamWorkFunc forward a run's output to the caller in real time
  during the foreground phase, then on auto-background inject a generic notice
  and drain the rest into the task result.
- Cancellation records a reason on Task.Error and the foreground caller reports
  StatusCanceled (not a StatusFailed ctx error).
- Task ids are TaskType_base62(int64), where the int64 packs a ms timestamp, a
  per-ms sequence (spinning to the next ms on overflow), and random low bits —
  unique within a process and self-describing by type.
- Optional OutputStore persists completed results (filesystem.Backend satisfies
  it directly); WaitForTask/WaitAllDone for lifecycle waits.

adk/middlewares/backgroundtask (control tools):
- Injects task_output/task_stop once, bound to a Config{Manager}. task_output
  supports CC-aligned block/timeout inputs.

adk/middlewares/subagent + filesystem:
- subagent agent tool and filesystem execute tool route through a shared Manager
  when configured, gaining run_in_background; the streaming execute tool streams
  its foreground output via RunStream. filesystem.Shell now documents the
  ctx-cancellation contract.

adk/prebuilt/deep:
- deep.New accepts a Manager, wiring it into the top-level subagent + filesystem
  middlewares and injecting the control tools once; sub-agents stay
  foreground-only. Replaces the old task_tool with the subagent middleware.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* feat: adjust background.Manager

* refactor(adk): clarify background task events

* feat: simplify done check

* feat: reduce one goroutine for direct run_in_background

* refactor: enchance background task with direct run_in_background

* refactor(adk): make background task output file worker-owned

The background-task Manager previously declared an output file globally and
wrote it once at completion, which broke its "interim output" promise and made
the file redundant with Task.Result. Move output-file ownership to the launching
adapters (execute / agent tools): the Manager only records RunInput.OutputFile,
while the worker writes — shell runs tee interim output as it streams, sub-agent
runs append their final result.

- backgroundtask: drop Config.OutputStore/OutputDir and persistOutput; add
  RunInput.OutputFile (path only, Manager never writes)
- filesystem: add Appender optional interface + AppendRequest (InMemoryBackend
  implements it); output files require an Appender, no rewrite fallback
- bundle Manager + output config into a nested BackgroundConfig across the
  filesystem, subagent, and deep configs
- name output files after the launching tool-call id (matching Task.ToolUseID),
  with a uuid fallback when absent
- task_output's formatTask points at the file when present instead of inlining
  the result

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* feat: support mark output file

* fix: golangci-lint

* refactor(adk): hand WorkFunc a TaskInfo and key output-file failures by id

The launcher needs the Manager-assigned task id at write time to report an
output-file write failure, but the id is generated inside createTask, after the
work closure is already built. Pass a TaskInfo (read-only snapshot of
creation-time identity) as an explicit WorkFunc/StreamWorkFunc parameter so the
work receives the id directly; MarkOutputFileUnreliable then keys by id (O(1)
map lookup) instead of scanning all tasks by output-file path.

Also make the failed-write reporting honest: when a write fails, neither the
partial file nor the in-memory Result is the authoritative complete output
(Result may be empty while the task runs, or a partial projection of the file
for sub-agent runs). formatTask and the OutputFileErr doc no longer claim Result
is always the full copy.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
…rated leading system messages (#1121)

* fix(schema): remove gob registration for map[string]any and []any

Registering map[string]any / []any with custom _eino_ prefixed names in
init() causes gob to panic when other libraries (e.g. go-openapi/spec)
register the same types with their default names, since gob enforces
one name per concrete type globally.

These registrations were added preemptively in commit 318c253 (model
timeout feature) so that GobSerializer could round-trip nested maps/
slices inside Extra/MetaData fields. However:
- No eino-internal code puts nested map[string]any / []any into Extra
- All existing tests pass without them
- The conflict with third-party libraries is a real init-time failure

GobSerializer will still fail at runtime if users store nested
map[string]any / []any in Extra, but that is a runtime error surface
rather than an unconditional init-time panic.

Change-Id: Id4594751cb0a7ffb4666e07749ee2104f04a0267

* refactor(adk): remove session turn ids

Use committed idle event IDs as rollback boundaries and remove runner-side turn ID generation, checkpointing, and session event stamping.

Change-Id: Ica138789eae5cc901a9af23572d5f6aee801b416

* fix(adk): stop persisting runtime generated leading system messages in session events

Generated leading system messages from GenModelInput are runtime model
input scaffolding, not durable conversation history. They should be
recalculated on each run via applyBeforeAgent -> GenModelInput rather
than reconstructed from SessionEventStore.

- Add runtime provenance marker (_eino_adk_runtime_generated_system_message)
  to distinguish generated vs caller-supplied leading system messages
- Remove syncLeadingSystemMessageSessionEvent from all 3 run paths
  (no-tools, message ReAct, agentic ReAct)
- Summarization Middleware strips marked messages from MessagesReplaced
  event payload while preserving them in runtime state
- Caller-supplied leading system messages (unmarked) remain durable

Change-Id: I4a9ddd895b1d5fd32ad15345e3f7d43417342f4d

* chore: format code

Change-Id: I4d8b7e4d78f688255142a1823462ca8864d8468d

* refactor(adk): remove TurnLoop managed resume mode

Remove the redundant managed interrupt resume mode where business
interrupts kept the TurnLoop alive and waited for explicit Resume().
Business interrupts now always exit with *InterruptError and persist
a checkpoint when Store + CheckpointID are configured, consistent with
the normal interrupt-exit path.

Restored checkpoint resume via GenResume remains intact.

Deleted:
- TurnLoopInterruptMode type and constants
- InterruptMode and ResumeWaitTimeout from TurnLoopConfig
- TurnLoop.Resume() method and sentinel errors
- turnLoopPendingResumeSource enum and managed-only fields/helpers
- Managed parking loop in takePendingResume
- Managed-mode branch in run() and proxy iterator
- Resume-wait watcher and timeout logic
- InterruptContexts from turnLoopCheckpoint
- ~2000 lines of managed-mode tests and helpers

Change-Id: I4cc772012939ed8d0c768d00b9472ea8887f937f

* fix(adk): use json canonical comparison for model context tool equality

reflect.DeepEqual incorrectly reports tool change when persisted ToolInfo
numbers decoded as float64 differ from runtime int values, causing
redundant model context events every model call. Fall back to comparing
canonical JSON form to keep semantic equivalence across persist/reload.

Change-Id: I3baffe7decdf0d30217ccf45db354dc01a7779f5

* refactor(adk): skip persisting ModelContext session event

Change-Id: I7ebc6ceb603b4d236c1978a5db2e64e6d5afb413
…#1091)

* chore: ensure that tool calls match tool results and correct comments

* chore: adjust condition
Change-Id: Iabc283f15ab65dc8100ace045344a4a24574eea9
Allow the summarization middleware to reuse the main conversation's
cached prompt instead of rebuilding a fresh input, avoiding a full
prompt-cache invalidation on every summarize call.
… turn (#1128)

* fix(adk): stop forwarding backgrounded sub-agent events to the parent turn

A managed sub-agent run that is backgrounded (explicit run_in_background or
auto-moved at the foreground deadline) runs detached and outlives the turn
that launched it. It kept forwarding the inner agent's events to the parent
turn's event generator, which the turn closes on end — injecting events into a
stream the user has stopped watching, and racing a send against that close.

Introduce a lifecycle signal (backgroundtask TaskInfo.Backgrounded) that closes
when a task moves to the background, and an AgentTool option
(WithAgentToolParentForwardUntil) that bounds parent forwarding by it. The
subagent middleware wires the two together, so forwarding stops the moment a run
detaches; foreground runs are unaffected. The gated path uses a non-panicking
send by definition (it may outlive its consumer); the plain foreground path
keeps a panicking send so an unexpected closed-generator send still surfaces.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* refactor(adk): rename AgentTool event-forward options for self-documentation

Make the two halves of AgentTool event forwarding read as a pair: the target
(where inner events go) and the bound (how long they go there).

- withAgentToolEventGenerator     -> withAgentToolEventForwardTarget
- withTypedAgentToolEventGenerator -> withTypedAgentToolEventForwardTarget
- WithAgentToolParentForwardUntil  -> WithAgentToolEventForwardUntil
- agentToolOptions.gatedForward/forwardParentUntil ->
  eventForwardGated/eventForwardUntil

Behavior is unchanged; this is a naming-only refactor.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* refactor(adk): consolidate event-forward gate into internal/agenttool.ForwardGate

Move the event-forwarding gate option to a self-documenting ForwardGate
struct in internal/agenttool, replacing the split across agentToolOptions
and the exported WithAgentToolEventForwardUntil. Also remove the AgentEvent
type-specialization wrapper (withAgentToolEventForwardTarget) in favor of
the generic withTypedAgentToolEventForwardTarget.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
#1140)

fix(adk): preserve ToolInfos and DeferredToolInfos in BeforeModelRewriteState
* feat: change appender to sreamable

* refactor(adk): expose append sessions as writers

* feat(adk): stream subagent events to task output

* refactor(adk): tighten event receiver contract

* refactor(adk): persist materialized agent events as jsonl

* refactor(adk): strip only root message extra

* feat(adk): preview streaming shell startup output

* test(adk): support go 1.18 atomic APIs

* perf(adk): make in-memory append linear

* docs(adk): clarify background preview completion

* refactor(adk): unify pumped stream forwarding

* refactor(adk): clarify foreground timeout naming

* refactor(adk): enforce append-writer Close contract in in-memory backend

Make the InMemoryBackend append handle strictly honor the AppendOpener
contract: Close is now idempotent and records a closed state, and a Write
or WriteString after Close is rejected with io.ErrClosedPipe without
mutating the file. Document the write-after-close behavior on the
AppendOpener interface so other backends follow suit, and add a regression
test covering idempotent Close and post-close writes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(adk): clarify streaming foreground-timeout and preview timing

Document that RunStream's foreground timeout and BackgroundStartupPreviewMs
windows are measured from when the StreamWorkFunc returns its reader, not
from the RunStream call, since RunStream invokes the work synchronously and
starts the timers only afterward. Blocking initialization done before the
reader is returned is therefore not bounded by either window, so streaming
work must return its reader promptly and push blocking init into the
producer goroutine. Note the Run vs RunStream difference on ForegroundTimeoutMs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(adk): make sub-agent background output encoder pluggable

Replace the hardcoded JSONL envelope for managed sub-agent output files
with a pluggable subagent.AgentEventFormat[M] encoder on the sub-agent
BackgroundConfig. It encodes one AgentEvent into a single line (framework
appends the newline); returning an empty line skips the event, so a caller
can filter down to a final-result-only file. When nil, a built-in default
encoder writes {agent_name, message} per line with the message's root Extra
stripped — the event kind is read from the message's own role/tool_calls,
so no invented type taxonomy is introduced.

Keep the format concern inside the subagent middleware: the format hint is
surfaced only through the tool's own background-run message, so the
backgroundtask manager and task_output stay format-agnostic (drop the shared
metadata key and formatTask rendering). deep.BackgroundConfig exposes only
shared knobs (Manager, OutputDir) and no longer needs to be generic; a custom
encoder is available by composing the subagent middleware directly.

Also report elapsed duration instead of an absolute (zone-ambiguous)
completion timestamp in task_output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(adk): treat short writes to the sub-agent output file as errors

io.WriteString falls back to Write([]byte) for a non-StringWriter, and a
misbehaving writer can report n < len with a nil error. Convert that short
write into io.ErrShortWrite so a truncated line marks the output file
unreliable instead of being trusted by task_output as authoritative.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(adk): preserve session envelope when copying agent events

copyTypedAgentEvent dropped SessionEventVariant, so the per-receiver copies
that AgentTool forwards lost the child-session stamp applied by
stampAgentToolSessionEvent — leaving the parent unable to filter child/tool
events out of its own session. Copy the variant (and its Event /
MessageStreamRef, which are value/one-level structs) so each forwarded copy
carries the stamp. Nested Message payloads remain shared, matching the
existing copy convention. Add a test asserting a receiver sees the stamped
envelope.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(adk): expose subagent BackgroundConfig as a typed alias

Rename the generic subagent background config to TypedBackgroundConfig[M] and
add BackgroundConfig = TypedBackgroundConfig[*schema.Message], mirroring the
existing Config = TypedConfig[*schema.Message] pattern so the common case
writes BackgroundConfig{} instead of BackgroundConfig[*schema.Message]{};
generic callers (deep) use TypedBackgroundConfig[M].

Also reframe the output-file contract: the file is line-oriented (one line
per event, format defined by EventFormat). JSONL {agent_name, message} is
documented as the default encoder's output specifically, not a property of
the file — a custom EventFormat may emit any per-line text. No type field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(adk): align foreground-timeout terminology and RunStream timing wording

Rename lingering "foreground budget" comments to "foreground timeout" in the
filesystem execute tool to match the manager, and tighten the RunStream doc
to say the forwarding goroutine starts the timers after work returns its
reader. Comment-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…#1147)

* perf(schema): assemble streamed string fields with pre-sized builders

ConcatAgenticMessages and its downstream concat helpers assembled streamed
string fields (reasoning text/signature, assistant text, media Base64 data,
function/MCP tool arguments, tool-result text, OpenAI refusal reason and
indexed reasoning content) with in-loop `+=` concatenation. That produced a
fresh full-history intermediate string per chunk, making a single concat pass
O(n^2) in the number of chunks and generating heavy allocation and GC churn —
observed as the dominant CPU cost in production profiles.

Replace every such path with a single pre-sized allocation: sum the fragment
lengths, then Grow a strings.Builder once and WriteString each fragment (no
reflection, no unsafe), mirroring the existing ConcatMessages implementation.
The FunctionToolResult content merge is restructured to flatten all chunks
once and collapse adjacent text runs in a single pass instead of repeatedly
copying the accumulated slice. The OpenAI refusal path now builds a fresh
OutputRefusal, which also fixes an input-chunk mutation.

Public APIs, output content, ordering, error behavior, and extension/Extra
merge semantics are unchanged. Benchmarks (16/64/256/1024 chunks x 8/128 byte
payloads, ReportAllocs) show constant ~2 allocs/op regardless of chunk count
and up to -99.8% time and bytes at large chunk counts (geomean -95.9% time,
-98.1% bytes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf(schema): drop per-index length map in reasoning extension concat

Compute each index's total length in the final build loop instead of
maintaining a separate indexToLen map, removing one map allocation and
N hashed map writes per stream. Behavior is unchanged: the builder is
still pre-sized to the exact byte total.

Also re-split a tool-result test fragment ("hel" -> "he"/"llo ") to
avoid a false-positive typos check while preserving the merge-across-
chunks assertion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Change-Id: I3a51986eb27032bc0f96bbc9e83ed627519768ea
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants