Skip to content

fix(protocol): keep forwarded request headers out of logged ACP params - #553

Open
stephen-wang24 wants to merge 1 commit into
mainfrom
stephen-wang/acp-params-hide-request-headers
Open

stephen-wang24 wants to merge 1 commit into
mainfrom
stephen-wang/acp-params-hide-request-headers

Conversation

@stephen-wang24

@stephen-wang24 stephen-wang24 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

str() of the ACP params models included request, which holds the x-* headers FastACP forwards to the agent. Those headers can carry end-user credentials such as x-user-oauth-credentials. The default, temporal and temporal-openai-agents templates and the Temporal tutorials log the whole params at INFO, so the header values were written to agent stdout on every call.

What changed

  1. Models (src/agentex/protocol/acp.py): request is now Field(repr=False) on CreateTaskParams, SendMessageParams, SendEventParams, CancelTaskParams and InterruptTaskParams. str(params), repr(params) and f"{params}" no longer include it. Attribute access, model_dump(), model_dump_json() and the JSON schema are unchanged, so handlers, the ADK's header forwarding and the Temporal signal payload still get the headers.
  2. Templates and tutorials: the 22 log lines that interpolated the whole params now log the task id, plus the event id in event handlers:
    • templates default (3 lines), temporal (2) and temporal-openai-agents (2)
    • Temporal tutorials 000, 010, 030, 050, 060, 070, 080 and 100
    • the procurement demo
  3. Tests (tests/test_acp_params_repr.py): for each model, a canary header value is absent from str()/repr(), while .request, model_dump(), model_dump_json() and a model_dump() → model_validate() round trip (the Temporal signal path) still carry it.
flowchart LR
  GW["x-* request headers<br/>e.g. x-user-oauth-credentials"] --> ING["FastACP ingress<br/>puts them in params.request"]
  ING --> H["Handler / workflow"]
  H -->|"before: logger.info of the whole params<br/>header values in the log line"| LOG["agent stdout"]
  H -->|"after: task and event ids only;<br/>str of params omits request"| LOG
  H -->|"params.request and model_dump<br/>unchanged"| USE["ADK header forwarding,<br/>Temporal signal payload"]
Loading

Before / After

Behavior Before (main @ 7e421c6) After (this branch)
Stdout of a new default agent after task/create, event/send and task/cancel that carry an x-user-oauth-credentials header the header value is printed on 3 lines, one per handler not printed; each handler logs its task id (and event id)
Stdout of a new temporal agent after the same three calls the header value is printed once, by the RECEIVE_EVENT signal handler not printed
Stdout of an agent built from the old templates (which still log the whole params) once it runs this SDK the header value is printed not printed; the line still prints, without request=
Any print(params) or f"{params}" in agent code shows request={'headers': {...}} with the values leaves request out
Headers available to handler code and the Temporal workflow (params.request) yes yes

Proof

Ran agents scaffolded from main and from this branch with the real agentex agents run. Each got task/create, event/send and task/cancel carrying x-user-oauth-credentials: test-canary-not-a-real-token, and I counted that value in the agent's stdout:

Agent Templates SDK Canary in stdout
default main main 3 lines: task/create, event/send, task/cancel
default this branch this branch 0
temporal main main 1 line: the RECEIVE_EVENT signal handler
temporal this branch this branch 0
default main (old {params} lines) this branch 0
temporal main (old {params} lines) this branch 0

The header still reaches the code. A throwaway probe line in the branch's event handlers compared params.request["headers"]["x-user-oauth-credentials"] with the canary, and it logged True in both the default handler and the temporal workflow.

The event handler's log line, before and after (timestamps replaced by ..., the middle of long lines trimmed to …):

# main, default agent
ACP: ... INFO   - Received task event send rpc: agent=Agent(id='agent-canary', … request={'headers': {'x-user-oauth-credentials': 'test-canary-not-a-real-token'}}

# this branch, default agent
ACP: ... INFO   - Received task event send rpc for task task-branch-default-1793052325, event event-canary-1

# main, temporal agent
WORKER: ... INFO   - Received task message instruction: agent=Agent(id='agent-canary', … request={'headers': {'x-user-oauth-credentials': 'test-canary-not-a-real-token'}}

# this branch, temporal agent
WORKER: ... INFO   - Received task message instruction for task task-branch-temporal-1793052372, event event-canary-1

# main's old temporal template on this branch's SDK: the same line, now ending at the event
WORKER: ... INFO   - Received task message instruction: agent=Agent(id='agent-canary', … content=TextContent(author='user', content='hello', attachments=None, format='plain', style='static', type='text'), created_at=None)

# probe lines, this branch
ACP: ... INFO   - probe: forwarded header reached handler=True
WORKER: ... INFO   - probe: forwarded header reached handler=True
Setup and full run output

Setup, for main and this branch, each with its own venv:

  • Scaffolded a default and a temporal agent with the calls agentex init makes after its prompts.
  • Ran each with agentex agents run --manifest manifest.yaml.
  • Counted the canary in the CLI's stdout, where ACP and worker output both stream. I also counted with newlines removed, in case a line wrapped.

Harness: a stand-in Agentex backend on :5003 (registration and message writes) and the Temporal Java test server on :7233. Redis was not running, so adk.messages.create logs the same Redis stream-publish errors on both sides.

[sdk=main templates=main default] stdout lines: 122, lines with the canary: 3, canary count with newlines removed: 3
[sdk=branch templates=branch default] stdout lines: 122, lines with the canary: 0, canary count with newlines removed: 0
[sdk=main templates=main temporal] stdout lines: 178, lines with the canary: 1, canary count with newlines removed: 1
[sdk=branch templates=branch temporal] stdout lines: 178, lines with the canary: 0, canary count with newlines removed: 0
[sdk=branch templates=main default] stdout lines: 122, lines with the canary: 0, canary count with newlines removed: 0
[sdk=branch templates=main temporal] stdout lines: 178, lines with the canary: 0, canary count with newlines removed: 0
[sdk=branch templates=probe default] stdout lines: 123, lines with the canary: 0, canary count with newlines removed: 0
[sdk=branch templates=probe temporal] stdout lines: 179, lines with the canary: 0, canary count with newlines removed: 0

All handler lines:

# main, default agent
ACP: ... INFO   - Received task create rpc: agent=Agent(id='agent-canary', … request={'headers': {'x-user-oauth-credentials': 'test-canary-not-a-real-token'}}
ACP: ... INFO   - Received task event send rpc: agent=Agent(id='agent-canary', … request={'headers': {'x-user-oauth-credentials': 'test-canary-not-a-real-token'}}
ACP: ... INFO   - Received task cancel rpc: agent=Agent(id='agent-canary', … request={'headers': {'x-user-oauth-credentials': 'test-canary-not-a-real-token'}}

# main, temporal agent (task/create carries no headers on the Temporal path)
WORKER: ... INFO   - Received task create params: agent=Agent(id='agent-canary', … request=None
WORKER: ... INFO   - Received task message instruction: agent=Agent(id='agent-canary', … request={'headers': {'x-user-oauth-credentials': 'test-canary-not-a-real-token'}}

# this branch, default agent
ACP: ... INFO   - Received task create rpc for task task-branch-default-1793052325
ACP: ... INFO   - Received task event send rpc for task task-branch-default-1793052325, event event-canary-1
ACP: ... INFO   - Received task cancel rpc for task task-branch-default-1793052325

# this branch, temporal agent
WORKER: ... INFO   - Received task create for task task-branch-temporal-1793052372
WORKER: ... INFO   - Received task message instruction for task task-branch-temporal-1793052372, event event-canary-1

Printing a params model directly, with the canary in request. Here True means the canary appears in that output, and for .request and dump it means the value equals the forwarded one:

                      ---------- main ----------   ---------- branch --------
model                 str  repr f-str .request dump json   str  repr f-str .request dump json
CreateTaskParams      True True True  True     True True   False False False True    True True
SendMessageParams     True True True  True     True True   False False False True    True True
SendEventParams       True True True  True     True True   False False False True    True True
CancelTaskParams      True True True  True     True True   False False False True    True True
InterruptTaskParams   True True True  True     True True   False False False True    True True

Supporting checks (these show nothing broke, not the change itself):

  • 10 new tests in tests/test_acp_params_repr.py. Against main's acp.py, the 5 str/repr tests fail and the 5 "headers still carried" tests pass. On this branch all 10 pass.
  • Full suite on this commit (./scripts/test): 1774 passed, 1622 skipped on Python 3.12 and on 3.13. main on 3.12 gives 1764 passed with the same 1622 skips.
  • ./scripts/lint (ruff, pyright with 0 errors, import check), ./scripts/check-slim-deps and mypy src/agentex/protocol/acp.py: all clean.
  • This PR's checks: 53 passed, 0 failed, including Test Tutorial Agents for the changed tutorials 000-080 against a real AgentEx server.
  • The JSON schema of the 5 models is byte-identical to main's. git grep over src/, tests/, examples/ and adk/ found no code that reads str() or repr() of a params model.

Upgrade note

Agents scaffolded from older templates keep their own {params} log lines. With this SDK version those lines no longer include request (the old-template rows in the Proof table), but they still print the rest of the params, including user input. Changing them to log ids is up to each agent.

Not verified

  • Deployed agents show the effect only after an agentex-sdk release that includes this change, and after the agent upgrades. A search of an upgraded agent's logs for request= in its handler lines would show it.
  • The end-to-end runs used a stand-in Agentex backend and the Temporal test server, not a real Agentex deployment or Temporal dev server.
  • Tutorial 100 has no manifest.yaml, and the procurement demo is outside examples/tutorials, so CI tests neither. For those two, only python -m py_compile ran.
  • The async-base tutorials that log {params.task} are unchanged. That is the Task model, which has no request field.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no blocking issues were found.

Summary

Keeps forwarded headers out of ACP parameter strings and replaces whole-parameter logs with task and event IDs.

  • ACP params keep forwarded headers out of their text while agent logs show task and event ids.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Forwarded headers"] --> B["params.request"]
  B --> C["Handler access and model_dump(): preserved"]
  B --> D["str() and repr(): request omitted"]
  E["Templates and examples"] --> F["Log task and event IDs only"]
Loading

Reviews (2) · Last reviewed commit: "fix(protocol): keep forwarded request he..." · Reviewed by Greptile

The ACP params models rendered `request` (the forwarded x-* headers,
which can carry end-user credentials) in str() and repr(), and the
default, temporal and temporal-openai-agents templates plus the Temporal
tutorials logged the whole params object at INFO.

- Mark `request` repr=False on CreateTaskParams, SendMessageParams,
  SendEventParams, CancelTaskParams and InterruptTaskParams. Attribute
  access and model_dump() are unchanged, so handlers, the ADK and the
  Temporal signal payload still receive the headers.
- Log task and event ids instead of the whole params in the templates,
  the Temporal tutorials and the procurement demo.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@stephen-wang24

Copy link
Copy Markdown
Contributor Author

@greptileai

@stephen-wang24
stephen-wang24 marked this pull request as ready for review October 27, 2026 00:04

This branch has not been deployed

No deployments
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