Skip to content

[api][java][python] Propagate user context from request events to responses - #1352

Merged
wenjin272 merged 7 commits into
apache:mainfrom
HsbcJone:cursor/feat-1350-request-response-context-4e93
Nov 9, 2026
Merged

wenjin272 merged 7 commits into
apache:mainfrom
HsbcJone:cursor/feat-1350-request-response-context-4e93

Conversation

@HsbcJone

@HsbcJone HsbcJone commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Linked issue: #1350

Closes #1350

Purpose of change

An Action can put optional opaque data on a chat, tool, context-retrieval, or decision request and read that same value on the matching response. The value tells that Action's responses apart. It is not prompt text, tool arguments, or a copy of event attachments.

Runtime flow

The caller sets context (Java withContext or the full constructor; Python context=). The built-in action copies it onto the response it sends.

For chat, the action holds the value across retries and, on Java, candidate fallback. A tool round parks it in the tool-request sensory entry and leaves it off the ToolRequestEvent. The tool response resumes that chat call with the parked value. The terminal ChatResponseEvent, success or failure, carries it.

Tool, decision, and retrieval actions copy it onto their one response. A retrieval query error throws and sends no response.

Key decisions

context is optional and untyped, and omitted when null, so an unset request keeps its previous serialized shape.

A chat-loop tool request does not inherit the chat context. That value stays in sensory memory and is stamped only on the terminal chat response.

Java uses withContext because another Object argument would clash with outputSchema. Python takes context on the constructors and response factories.

Behavioral Semantics

Interaction decisions

Request context Outcome Caller-visible result
omitted success attribute absent
set success same value on the response
set chat, decision, or tool failure response same value
set chat retry, tool rounds, Java fallback initial value on the terminal chat response
set retrieval query throws no response; request still holds it
set, plus attachments response the action sends context copied; attachments not copied
two chat requests responses finish in either order each keeps its own value

The value is not written into messages, prompt arguments, tool inputs, the decision request, request_params, or the vector-store query.

Behavioral contracts

  1. Omitting context leaves it off the request and the response.
  2. A set context is returned unchanged on the success response for chat, tool, retrieval, and decision.
  3. Chat and decision failure responses, and a tool response whose calls failed, carry that context.
  4. Concurrent chat requests do not exchange context when tool rounds finish out of order.
  5. The terminal chat response keeps the initial context across retries, tool rounds, and Java model fallback.
  6. Context is not injected into prompts, tool arguments, decision inputs, or retrieval queries.
  7. Event attachments are not copied onto the response.
  8. Context survives event JSON and a JSON checkpoint of the value parked for a chat tool round.
  9. An unknown attribute is still rejected. An empty map is kept. Null clears the attribute.
  10. A request restored from JSON still echoes context.

Failure behavior

Chat provider errors, exhausted retries, and Java fallback exhaustion send a failed ChatResponseEvent that still carries context. Decision provider errors do the same. A failed tool call is recorded on the ToolResponseEvent, which still carries context. A retrieval query error is thrown and no response is sent. Cancellation still sends no tool or chat response. A value that cannot be serialized fails when the event or sensory entry is written, as with any other attribute.

Tests

Contract Tests
1 omittedContextIsAbsentFromTheSerializedEvent, omittedContextStaysOffTheResponse, processToolRequestOmitsContextWhenTheRequestDoes, decision omit case; Python test_omitted_context_*, test_tool_response_omits_context_when_the_request_does, test_retrieval_omits_context_when_the_request_does
2 eachRequestAndResponseRoundTripsItsOwnContext; retrieval, tool, decision, and chat success tests in both languages
3 chat and decision failure tests; processToolRequestEchoesContextOnSuccessAndFailureWithoutInjectingIt; queryFailurePropagatesWithoutAResponseAndKeepsTheRequestContext
4 toolRoundsKeepTheInitialContextAndDoNotCopyItOntoToolRequests; test_tool_rounds_keep_each_request_context
5 chat retry assertions; fallbackSuccessAndExhaustionRetainRequestContext; fallbackThenToolRoundRetainsInitialContext; Python retry and tool-round tests
6 those action tests check prompt args, tool inputs, query text, and decide arguments
7 those tests set an attachment and expect empty response attachments
8 foreignJsonContextSurvivesTheCrossLanguageBoundary and the Python twin; parked-context JSON in the tool-round tests
9 unknownAttributeIsStillRejectedWhenContextIsPresent; emptyContextIsKeptAndNullClearsIt; test_empty_context_is_kept
10 restoredRequestStillEchoesContext; test_restored_request_still_echoes_context

Java mvn -pl api,plan -am test for these classes plus ChatResponseEventTest, DecisionEventTest, BuiltInEventsTest, and CrossLanguageEventSnapshotTest: pass (API 176 run, 14 skipped; plan 156 run). Python pytest of the new tests plus the existing chat, tool, decision, built-in, and snapshot modules: 260 passed, 15 skipped.

Not verified: a full Flink job restore. Python has no executable model router, so fallback is covered on Java. Snapshots that omit context are unchanged.

Implementation invariants

The attribute name is context on all eight events and in the chat action's tool-request sensory entry. It is stored only when non-null, so an older checkpoint without the key resumes with no context. fromEvent allows the key and still rejects every other unknown key. Java ChatModelAction.chat(UUID, String, ...) does not take a context; only the request-event path does. Tool requests created inside a chat loop do not carry the chat context.

API

Compatibility impact

Existing constructors and factories keep their signatures. When context is omitted, serialized attributes are unchanged. withContext, getContext, and the Python context parameter are additive. YAML event-type aliases are unchanged. A caller who sets nothing new sees the same events as before.

Documentation

  • doc-included

Was this patch authored or co-authored using generative AI tooling?

  • Yes

Generated-by: Cursor (Grok 4.7)

@github-actions github-actions Bot added doc-label-missing The Bot applies this label either because none or multiple labels were provided. fixVersion/1.0.0 The feature or bug should be implemented/fixed in the 1.0.0 version. priority/major Default priority of the PR or issue. doc-included Your PR already contains the necessary documentation updates. and removed doc-label-missing The Bot applies this label either because none or multiple labels were provided. labels Nov 4, 2026

@eemario eemario left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for working on this! Reusing the existing tool request state makes sense, and the out-of-order test covers the main use case in #1350. I left two inline comments to address before merging, along with two non-blocking test suggestions.

@cursor
cursor Bot force-pushed the cursor/feat-1350-request-response-context-4e93 branch from 37c3226 to fc8f266 Compare November 5, 2026 03:13
@HsbcJone

HsbcJone commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @eemario — addressed both blocking items on fc8f2663 (rebased onto current main):

  1. Context is deep-copied/snapshotted on attach (Java UserDefinedContext + Python copy_user_context, including nested maps/lists), so later mutation of a reused map cannot leak across requests.
  2. Python Event.from_json no longer coerces JSON context shaped like {"type":"Row",...} into a PyFlink Row on the eight built-in request/response events; other attributes still reconstruct rows as before.

Also added the two non-blocking coverage items (cross-language context snapshots + ActionStateSerde restore path for chat context). CI is re-running.

@eemario

eemario commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Thanks @HsbcJone for addressing the comments! I’ve verified the fixes and the added tests. Nothing blocking from my side. @wenjin272 , could you take a look when you have a chance?

@wenjin272 wenjin272 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for implementing this and adding the cross-language and recovery tests! I left a few inline comments on the API shape, a remaining array-copy issue, and documentation clarity. The user-context values should follow the existing Event serialization and type-restoration rules.

@HsbcJone

HsbcJone commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @wenjin272 — review feedback is addressed on e963cca (logic/docs/rename). Spotless/AOSP wrap fixed in fc43b709; spotless:check plus RequestContextTest / BuiltInEventsTest passed locally.

Per-thread mapping:

  • Array copy: UserDefinedContext.snapshot recursively copies object/primitive arrays (incl. nested in lists). Test nestedArrayIsCopiedSoLaterMutationDoesNotChangeAnEarlierRequest covers String[], int[], nested String[].
  • Rename to userContext: All eight request/response events use userContext / Python user_context; key user_context; accessors getUserContext() / user_context.
  • Drop request-side withContext(): Request events take the map only in the constructor; withUserContext() remains on the four response events.
  • chat.md: Describes app-defined data request→response; links Serialization/Typed Values and User context.
  • events.md serialization: Points at Serialization/Typed Values; JSON may change runtime types; construction copies mutable structure only.
  • events.md opening: Leads with carrying application-defined data; removed “existing callers” / attachments comparison.

@HsbcJone

HsbcJone commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @wenjin272 — addressed your review on e963cca (spotless clean-up on fc43b70):

  1. Nested array deep-copy in UserDefinedContext.snapshot (maps/lists/arrays), with a regression test for String[] mutation after attach.
  2. Renamed to userContext / user_context (Map<String, Object> / dict[str, Any] | None).
  3. Request events take context at construction only; removed withContext() from the four request events; responses keep withUserContext().
  4. Docs: application-defined request→response wording, point at Serialization and Typed Values, dropped migration/attachments comparison.

Inline replies are on each thread. CI is re-running on fc43b70 — happy to adjust further if anything still looks off.

@cursor
cursor Bot force-pushed the cursor/feat-1350-request-response-context-4e93 branch from 7dd8095 to 1124a76 Compare November 6, 2026 06:55
cursoragent and others added 6 commits November 6, 2026 08:49
Optional opaque context on chat, tool, context-retrieval, and decision
requests is echoed on the matching response, including chat failure,
retry, tool rounds, and model fallback. Callers that omit it are unchanged.

Generated-by: Cursor (Grok 4.7)
Co-authored-by: mengxiaopeng <602012854@qq.com>
Copy maps and lists, including nested ones, when context is attached so a
later change to the caller's object does not rewrite an earlier request.
Python keeps context on the built-in request and response events as the
supplied JSON, including an object shaped like a PyFlink Row.

Generated-by: Cursor (Grok 4.7)
Co-authored-by: mengxiaopeng <602012854@qq.com>
Carry application-defined data as a user_context map. Requests take it at
construction; responses keep withUserContext so actions can attach it after
the existing factories. Snapshotting also copies nested arrays.

Generated-by: Cursor (Grok 4.7)
Co-authored-by: mengxiaopeng <602012854@qq.com>
Format the request-context sources so Code Style Check matches google-java-format AOSP.

Generated-by: Cursor (Grok 4.7)
Co-authored-by: mengxiaopeng <602012854@qq.com>
Wrap the ChatModelAction.sendFailure parameters and the ChatRequestEvent
constructor arguments to match google-java-format AOSP.

Generated-by: Cursor (Grok 4.7)
Co-authored-by: mengxiaopeng <602012854@qq.com>
Tool rounds stored user_context as the live object, so a UUID, datetime,
Pydantic model, tuple, or Row raised TypeError before ToolRequestEvent was
sent. Write the Event JSON form and decode it on read, the same conversion
Event.from_json applies, including Row reconstruction.

Generated-by: Cursor (Grok 4.7)
Co-authored-by: mengxiaopeng <602012854@qq.com>
@cursor
cursor Bot force-pushed the cursor/feat-1350-request-response-context-4e93 branch from 1124a76 to a465b44 Compare November 6, 2026 08:56
@HsbcJone

HsbcJone commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on tip a465b44c: Python now parks user_context in sensory memory via Event JSON (model_dump_json / from_json) so UUID/datetime/Pydantic/tuple/Row values pass validate_memory_value and survive checkpoint round-trips; covered by test_tool_round_restores_event_serializable_user_context. Rebased onto main at 64e9e0f (includes #1359). Local: 91 pytest passed.

@wenjin272 would you mind another look when you have a moment?

@wenjin272 wenjin272 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for addressing my comments. LGTM

@wenjin272
wenjin272 merged commit d698f2d into apache:main Nov 9, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc-included Your PR already contains the necessary documentation updates. fixVersion/1.0.0 The feature or bug should be implemented/fixed in the 1.0.0 version. priority/major Default priority of the PR or issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature][api] Propagate user-defined context from request events to response events

4 participants