Skip to content

feat(runtime_context): pass Request to extension point (EVO-1626 followup)#13

Open
marcelogorutuba wants to merge 5 commits into
developfrom
marcelo/evo-1626-followup-pass-request-to-runtime-context
Open

feat(runtime_context): pass Request to extension point (EVO-1626 followup)#13
marcelogorutuba wants to merge 5 commits into
developfrom
marcelo/evo-1626-followup-pass-request-to-runtime-context

Conversation

@marcelogorutuba

@marcelogorutuba marcelogorutuba commented Jun 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds an optional request argument to run_agent / StandardRunner.run_agent and threads it from the chat/a2a routes down to the runtime_context extension-point call site.
  • At the call site, prefer request over metadata so consumers that resolve the tenant from X-Evo-Tenant-Id actually see the header. Falls back to metadata when no request is provided (internal callers, tests).
  • Community default is unchanged — the no-op impl ignores its argument either way.

Why

Before this patch, runtime_context.current_context_id(metadata) was called with metadata=None on every chat/a2a request, so the enterprise consumer in evo-enterprise-licensing-python (EVO-1626) had no way to observe X-Evo-Tenant-Id end-to-end.

Validation

  • Syntax check on all 4 touched files passes (ast.parse).
  • The only EP call site (src/services/adk/runners/standard_runner.py:101) keeps a single-line behavior change behind the existing log line.

Changed Files

  • src/services/adk/runners/standard_runner.py
  • src/services/adk/agent_runner.py
  • src/api/chat_routes.py
  • src/api/a2a_routes.py

Linked Issue

  • EVO-1626 (followup; functional wiring for the consumer shipped in evolution-foundation/evo-enterprise-licensing-python#1)

Summary by Sourcery

Thread the incoming request object through agent execution so the runtime context extension point can use it when resolving context IDs.

Bug Fixes:

  • Allow runtime context resolution to access request headers such as tenant ID by passing the request into the extension point.

Enhancements:

  • Extend agent runner and standard runner signatures to accept an optional request parameter and prefer it over metadata when resolving runtime context IDs.

…owup)

Previously runtime_context.current_context_id was called with the
metadata dict, which is always None on the chat/a2a code paths. As a
result, an enterprise consumer that resolves tenant from the
X-Evo-Tenant-Id header could never observe it.

Thread an optional `request` argument from the chat and a2a routes down
through agent_runner -> StandardRunner.run_agent and prefer it over
metadata when present at the runtime_context call site. Falls back to
metadata when no request is provided (e.g. internal callers), so the
default community impl that ignores the argument is unaffected.

Refs: EVO-1626
@sourcery-ai

sourcery-ai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Threads an optional request object from the chat and A2A API routes through the agent runner into the StandardRunner, and uses it as the primary input to the runtime_context extension point, falling back to metadata when no request is provided.

Sequence diagram for passing request to runtime_context extension point

sequenceDiagram
    actor Client
    participant ChatRoutes as chat_routes
    participant A2aRoutes as a2a_routes
    participant AgentRunner as agent_runner
    participant StandardRunner as StandardRunner
    participant RuntimeContext as runtime_context

    Client->>ChatRoutes: chat(request)
    ChatRoutes->>AgentRunner: run_agent(db, session_id, files, metadata, user_id, request)
    AgentRunner->>StandardRunner: run_agent(db, session_id, files, metadata, user_id, request)
    StandardRunner->>RuntimeContext: current_context_id(request)

    Client->>A2aRoutes: handle_message_send(request)
    A2aRoutes->>AgentRunner: run_agent(db, session_id, files, metadata, user_id, request)
    AgentRunner->>StandardRunner: run_agent(db, session_id, files, metadata, user_id, request)

    alt request is None
        StandardRunner->>RuntimeContext: current_context_id(metadata)
    end
Loading

File-Level Changes

Change Details Files
Add optional request parameter to agent execution APIs and thread it through to the StandardRunner.
  • Extend run_agent function signature to accept an optional request argument.
  • Pass the request argument from the API route handlers into run_agent.
  • Propagate the request argument from run_agent to StandardRunner.run_agent.
src/services/adk/agent_runner.py
src/api/a2a_routes.py
src/api/chat_routes.py
src/services/adk/runners/standard_runner.py
Use request as the preferred input for runtime_context.current_context_id, falling back to metadata.
  • Update StandardRunner.run_agent to pass request to runtime_context.current_context_id when available.
  • Retain existing behavior for callers that do not supply a request by falling back to metadata.
src/services/adk/runners/standard_runner.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • Consider tightening the type for the new request parameter (e.g., Request or Optional[Request]) instead of Any in run_agent and StandardRunner.run_agent so downstream consumers get better type safety and editor support.
  • To reflect that request can be omitted, it may be clearer to type it as Optional[...] = None rather than Any = None in both the function signatures and any related call sites.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider tightening the type for the new `request` parameter (e.g., `Request` or `Optional[Request]`) instead of `Any` in `run_agent` and `StandardRunner.run_agent` so downstream consumers get better type safety and editor support.
- To reflect that `request` can be omitted, it may be clearer to type it as `Optional[...] = None` rather than `Any = None` in both the function signatures and any related call sites.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

marcelogorutuba and others added 4 commits June 8, 2026 11:34
…ardRunner (EVO-1626)

Code review follow-up on PR #13: with_context is sync by contract, so it
cannot keep the consumer's tenant binding alive across the awaits between
the FastAPI handler and the next SQLAlchemy transaction. Add a
companion async-only sibling helper so consumers can scope the binding
per request.

- EP 1.0.0 -> 1.1.0: new optional bind_context(context_id) ->
  AsyncContextManager[None] surfaced on the runtime_context module.
  Community default is a no-op async CM. Consumer impls participate by
  exposing a bind_context method (duck-typed); when absent the shim
  returns the no-op.
- StandardRunner.run_agent wraps the request body in
  async with runtime_context.bind_context(context_id) if context_id
  else nullcontext(). Community path is unchanged (no-op CM);
  enterprise path keeps app.current_tenant_id set across every await so
  the consumer's before begin listener can issue set_config(...) on
  each transaction.
- EXTENSION_POINTS.md: documents the new optional method, explains why
  it has to be async, and bumps the changelog.

No enterprise code lives in community — the shim is a pure extension
point default, mirroring how with_context, current_context_id and the
other EPs already work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Round-2 review M2-1: extend the runner-hook mirror with 4 tests
covering the new async with bind_context wrap in StandardRunner.

- test_default_bind_is_noop_async_cm: community default works as
  async CM (no consumer registered).
- test_null_branch_when_context_id_is_none: nullcontext() supports
  async with on Python 3.10+.
- test_consumer_bind_context_is_invoked: when a consumer exposes
  bind_context, the shim delegates and enter/exit are tracked.
- test_consumer_bind_context_resets_on_exception: exception in body
  still drives __aexit__ → finally cleanup.

10 passed (4 new).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ity (EVO-1626)

Community must remain neutral about what consumers do with the runtime
context EP. Replace tenant/enterprise-specific wording in comments and
docs with generic context-binding language. No behavior change; the EP
is still a generic per-request context primitive whose meaning is
defined by whichever consumer is registered.

- runtime_context.py: docstrings now talk about "context binding" /
  "consumers", not "tenant" / "enterprise".
- standard_runner.py: wrap comment talks about "operational context",
  no more app.current_tenant_id reference (that was implementation
  detail of one specific consumer).
- EXTENSION_POINTS.md: "Why bind_context is async-only" rationale
  reworded to be consumer-agnostic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Round-3 polish:

- L3-1: remove "for EVO-1626" from TestBindContextWrap docstring. Ticket
  refs in code rot — context belongs in PR descriptions, not source.
- L3-2: declare bind_context() -> AbstractAsyncContextManager[None] so
  type checkers (mypy/pyright) can validate async with callers, and
  to match the return-type annotation style of with_context and
  current_context_id in the same module.

10 passed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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