Skip to content

feat: finish shared runtime extraction - #21

Merged
zhanghanduo merged 9 commits into
mainfrom
refactor/final-shared-runtime
Sep 2, 2026
Merged

zhanghanduo merged 9 commits into
mainfrom
refactor/final-shared-runtime

Conversation

@zhanghanduo

Copy link
Copy Markdown
Collaborator

Summary:

  • move task-local external API, tool, and LLM metering into AgentCore
  • add configurable tool guardrails and tool-call repair middleware
  • share the legacy cooldown fallback engine behind an event hook
  • add a host-configurable auxiliary LLM factory and raw summary execution engine
  • make workflow-default registration merge safely without null blanking
  • correct the documented provider/runtime boundary

Validation:

  • Ruff passed
  • Pyright passed
  • Pytest: 1074 passed

Downstream adoption PRs will pin this immutable commit and retain only product catalog, credentials, session headers, billing/tracing hooks, and composition.

zhanghanduo and others added 9 commits September 2, 2026 18:19
Review fixes for the modules this branch extracted, plus four provider
defects the review surfaced in code the extraction depends on.

Extracted-module fixes:
- tool_call_repair: stop whitespace-stripping literal content args. Every
  string arg was stripped, so file_editor_str_replace lost the indentation
  and trailing newline of old_string/new_string (breaking exact matching)
  and write_file dropped trailing newlines. Adds LITERAL_CONTENT_KEYS.
- tool_call_repair: honour an explicitly empty key_aliases/type_coercions
  as an opt-out instead of falling back to the default table.
- summary: classify permanent 4xx as non-retryable. A bad API key burned
  max_retries attempts per candidate (8 requests, 12s of sleep); now 2
  requests and no backoff. Injectable via `retryable`.
- summary: measure the context-length retry ladder against the original
  content, not the already-shortened text, which abandoned a candidate one
  truncation step early.
- guardrails: read total_tokens, the key providers and UsageMetadata
  actually emit. "total" appears nowhere else in agent_core, so the
  budget-exhausted hint never fired. Both keys are now accepted.
- guardrails: coerce untyped budget config instead of raising ValueError
  out of an advisory check.
- guardrails: tolerate non-JSON tool args when fingerprinting, and pass
  usedforsecurity=False so md5 imports on FIPS builds.
- fallback: skip the backoff sleep after the final attempt, which only
  delayed the degrade and emitted a retry event no retry followed.
- fallback: stop replaying already-yielded stream deltas to the consumer;
  set replay_partial_stream=True for the historical behavior.
- usage_meter: serialize wire counters as ints rather than floats.
- protocols: document the tool-call veto contract. GuardrailsMiddleware
  only marks metadata["blocked"], and nothing in AgentCore dispatches
  tools, so an unchecked flag meant silent non-enforcement. Adds
  BLOCKED_KEY/BLOCK_REASON_KEY and ctx.block()/is_blocked/block_reason.
- export the new classes from the providers/runtime/runtime.loop package
  roots, matching the existing re-export convention.

Provider fixes (behavior changes — see below):
- finish_reason: new shared normalizer. _runaway matches the literal
  "length" and deliberately has no token-count fallback once visible text
  is present, but Anthropic sends stop_reason="max_tokens" and the
  Responses API sends status="incomplete" plus
  incomplete_details.reason="max_output_tokens". Truncation recovery was
  dead for both protocols. Only truncation markers are rewritten;
  tool_use/end_turn/stop pass through unchanged.
- openai_responses: also emit a finish_reason on response.incomplete,
  which the streaming path ignored while hardcoding "stop".
- openai_chat: compute session affinity on every call. A scoped client
  withholds default_query from the SDK because cached clients outlive
  their task, making the per-call path the only source of affinity and
  the only staleness check — but it was gated on extra_headers, so
  chat(messages) pinned no replica at all.
- _streaming: keep the visible answer when a streamed block list carries
  no text block. A text block is only recorded on a content_block_start
  of type text, so a gateway omitting that event returned thinking-only
  blocks and silently dropped the answer.

Three of these change observable behavior rather than only fixing a
latent bug, and matter to downstream repos pinning this commit:
truncation continuation now fires for anthropic/bedrock and responses;
scoped clients now send an affinity query on every request; and
CooldownFallbackLLM.stream no longer duplicates partial output.

test_cooldown_fallback's `assert sleeps == [0.5]` pinned the wasted
post-final-attempt sleep and is updated to the corrected contract.

Validation: Ruff clean, Pyright 0 errors, 1108 passed (was 1074).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@zhanghanduo
zhanghanduo merged commit a9b5272 into main Sep 2, 2026
1 check passed
@zhanghanduo
zhanghanduo deleted the refactor/final-shared-runtime branch September 2, 2026 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant