Skip to content

refactor: brand the request id - #675

Open
jakedipity wants to merge 5 commits into
nightlyfrom
jakedipity/request-id-newtype
Open

jakedipity wants to merge 5 commits into
nightlyfrom
jakedipity/request-id-newtype

Conversation

@jakedipity

Copy link
Copy Markdown
Collaborator

RequestId becomes a newtype over String instead of a type alias, so a bare string can no longer stand in for a request id. It threads through SSE routing, the tool-event broker, MCP cancellation, HITL approval gates, the session store, and the scratchpad's per-request directory.

The brokers and the cancellation registry key their maps by RequestId rather than String. In mcp/client.rs, rmcp's own RequestId is imported as McpRequestId, so the HTTP request id and the MCP protocol request id are no longer the same type in signatures that take both.

The webhook payload and stored approval records keep their bare-string shape, which serde(transparent) guarantees and a record test now pins.

@jakedipity
jakedipity requested a review from a team as a code owner September 9, 2026 20:11
@greptile-apps

greptile-apps Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

Greptile Summary

The PR replaces bare request-ID strings with a dedicated RequestId newtype while preserving existing serialized record and webhook shapes.

  • Threads the branded identifier through streaming, orchestration, HITL, MCP cancellation, brokers, session storage, and scratchpad paths.
  • Keys request-scoped registries and brokers by RequestId.
  • Aliases rmcp’s protocol identifier as McpRequestId where both identifier domains appear.
  • Adds coverage pinning the persisted request ID to a bare JSON string.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
crates/aura/src/config.rs Defines the branded request identifier and its transparent string representation.
crates/aura/src/mcp/client.rs Separates Aura request identifiers from rmcp protocol request identifiers in MCP operations.
crates/aura/src/request_cancellation.rs Migrates cancellation-registry keys and APIs from strings to RequestId.
crates/aura/src/tool_event_broker.rs Migrates request-scoped tool-event broker keys and calls to the branded identifier.
crates/aura/src/session_store/record.rs Stores RequestId directly and pins its enduring bare-string JSON contract with a focused test.
crates/aura-web-server/src/streaming/handlers.rs Threads the branded request identifier through the web server’s streaming request lifecycle.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    HTTP[HTTP / A2A request] --> RID[RequestId newtype]
    RID --> STREAM[Streaming and SSE routing]
    RID --> HITL[HITL approval gates]
    RID --> BROKERS[Progress and tool-event brokers]
    RID --> CANCEL[Request and MCP cancellation]
    RID --> STORE[Session and approval storage]
    RID --> SCRATCH[Per-request scratchpad]
    STORE -->|serde transparent| JSON[Bare-string persisted shape]
Loading

Reviews (4): Last reviewed commit: "refactor: brand the request id" | Re-trigger Greptile

Comment thread crates/aura/src/session_store/record.rs Outdated
@jakedipity
jakedipity force-pushed the jakedipity/agent-event-schema branch from 14cb706 to 0de55be Compare September 9, 2026 20:27
@jakedipity
jakedipity force-pushed the jakedipity/request-id-newtype branch from d7cdb5a to 7d9cf2a Compare September 9, 2026 20:28
dhable
dhable previously approved these changes Sep 9, 2026
@jakedipity
jakedipity force-pushed the jakedipity/agent-event-schema branch 3 times, most recently from 0a897b9 to d3df8a6 Compare September 10, 2026 17:14
@jakedipity
jakedipity force-pushed the jakedipity/request-id-newtype branch from 7d9cf2a to 4827e82 Compare September 10, 2026 18:11
@jakedipity
jakedipity force-pushed the jakedipity/agent-event-schema branch from d3df8a6 to daccaa7 Compare September 10, 2026 18:15
@jakedipity
jakedipity force-pushed the jakedipity/request-id-newtype branch from 4827e82 to b78a932 Compare September 10, 2026 18:31
@jakedipity
jakedipity force-pushed the jakedipity/agent-event-schema branch from daccaa7 to 55faa90 Compare September 10, 2026 21:24
@jakedipity jakedipity closed this Sep 11, 2026
@jakedipity jakedipity reopened this Sep 11, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 11, 2026

@Shearerbeard Shearerbeard left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

My only feedback here is that lib.rs is to general - if were starting to consolidate code around building our domain types and events lets just call it domain

Shearerbeard
Shearerbeard previously approved these changes Sep 16, 2026

@Shearerbeard Shearerbeard left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This looks good - as we progress we might have to better module classify identifiers a cross application concerns (like RequestId) vs agent specific new types like AgentId. There are probably a few cross cutting concerns to belong in a root domain vs a specific one. Also a good place to start looking for duplicated construction and validation code if it exists (I don't see that here - just the kind of thing I like to think ahead on when designing by types)

@jakedipity
jakedipity force-pushed the jakedipity/agent-event-schema branch 2 times, most recently from 30b2685 to 8c3a670 Compare September 17, 2026 19:07
Base automatically changed from jakedipity/agent-event-schema to nightly September 17, 2026 19:55
@jakedipity
jakedipity dismissed stale reviews from Shearerbeard and dhable September 17, 2026 19:55

The base branch was changed.

Shearerbeard
Shearerbeard previously approved these changes Sep 29, 2026
RequestId is a newtype that only RequestId::generate,
RequestId::for_a2a_task, and RequestId::for_slack_message construct. It
replaces the String alias and the bare request id strings in HITL, MCP
cancellation, the session stores, scratchpad storage, StreamingAgent,
and the web server. A run's id is its request's id.

A parked approval's ApprovalOwner is the request that raised it, the
orchestration run that parked it, or no one for an agent built without
a request. Stored records and the webhook payload keep the bare-string
request_id field.

Signed-off-by: Jacob Hull <jacob@planethull.com>
StreamingRequestHook takes a StreamKey, either the run's own stream
(Run(RequestId)) or an orchestration task attempt (Attempt). Only a Run
stream owns the run's tool-call queue and sends it tool events, and only
an Attempt has a park cell. Both rules were string comparisons that held
because an attempt key never equals a request id.

Signed-off-by: Jacob Hull <jacob@planethull.com>
The HITL gate and RequestApprovalTool read the owner of a live approval
from the run they are bound to, whose id is the request id. They no
longer take a request id of their own. AgentRuntimeConfig.request_id is
removed, along with the request_id parameter of RigBuilder::build_agent,
build_streaming_agent_with_headers, and build_streaming_agent_with_tools
that only fed it.

Agent::stream_prompt_with_timeout and stream_chat_with_timeout build no
run, so approvals raised through them would have no owner. They are
crate-private, which leaves StreamingAgent::stream as the public way to
stream an agent.

The orchestrator parses its run's id once and holds it as a RunId,
along with its persistence's session id. The park guard, the worker
approval scope, and the sweep of a run's parked approvals all read them
there, so worker_scope is synchronous and create_worker builds its scope
through it.

An agent that streams with a request id now owns its approvals under
that id, including one built through AgentBuilder. A gate with no run
bound raises unowned approvals.

Signed-off-by: Jacob Hull <jacob@planethull.com>
A run cancels the approvals it raised, in the registry and the store,
when its event stream drops. AgentRun carries the sweep, and
Agent::stream and the orchestrator factory attach it whenever the HITL
route parks approvals in a registry.

Chat completions, A2A tasks, and Slack runs all stream through
StreamingAgent::stream, so each gets the cleanup. A2A tasks and Slack
runs previously left their approvals decidable until they expired. The
chat handler's RequestResourceGuard and CompletionConfig's
pending_approvals, which only fed it, are removed.

TaskCancelEntry drops its request_id, since cancel() derives it from the
task id.

Signed-off-by: Jacob Hull <jacob@planethull.com>
Agent::cancel_mcp_requests had no callers, and it was the only caller of
McpManager::cancel_all_for_request, so both are removed. Every cancel
goes through cancel_and_close. McpClient::cancel_all_for_request is now
private to it.

Signed-off-by: Jacob Hull <jacob@planethull.com>
@jakedipity
jakedipity force-pushed the jakedipity/request-id-newtype branch from 3ec8a18 to 4be9654 Compare October 7, 2026 01:07
@justintime4tea

Copy link
Copy Markdown
Collaborator

@jakedipity heads-up, there's some overlap between this PR and the #778 stack (#794 envelope, then #796 run id, then #797, which fixes #790). #795 isn't related.

The main collision: #796 and this PR both re-type RunContext::id, just differently. #796 makes it a RunId (a UUID), going off #778's "one id per run" decision (decided by default, no comment on issue/discussion, we can change 🙏 ), so the request id is the run's UUID as a string, and the HITL run-id parsing, the park owner key and the event envelope all share one value. This PR makes it a RequestId that keeps the req_ / a2a_ / slack_ formats. Both can't land as written.

FWIW from dry-run merges: this PR already conflicts with nightly in 7 files because it predates #719, so that rebase happens either way. Against the whole stack it's 23 files. #794 and #795 don't add anything beyond #719's 7.

Here's an end state I'm thinking about. What are your thoughts?

  • One id per run, typed as RunId, so no second request-id type.
  • One place that names a run for each ingress, borrowing your per-ingress constructors. A small web-server function takes an origin (chat, A2A task, Slack message), mints the RunId, and logs the origin next to it, so the traceability your prefixes give moves into the log line instead of the id. Your three constructors would map onto its three origins one to one.
  • Your typed boundaries, just carrying RunId: the stream / cancel_and_close_mcp params, the registry keys, StreamKey, McpRequestId, and ApprovalOwner replacing the empty request id on approvals.
  • Stored approval records accept both the UUID form and the old prefixed forms until the old ones age out.

What I'd change in #796 to meet you halfway (not pushed yet):

  1. Swap the three copies of mint-and-log in the chat, A2A and Slack handlers for that one origin function, logging at info.
  2. Mint a library caller's run id once in the builder, instead of in four separate fallbacks.
  3. Make RunContext::has_id(&str) crate-private. It only exists because the hook key and cancel params are still strings, which your StreamKey and typed params replace, so I don't want it turning into public API you'd just have to delete.
  4. Call out in refactor(agents): name a run by its RunId #796's description why the builders keep their id param: since refactor(agent): separate a prepared agent from its runs #719 the run begins at build time, so begin_run needs it then. It's renamed run_id: Option<RunId>.

I'm deliberately leaving the typed params, registry keys, StreamKey, McpRequestId and ApprovalOwner to you, since that's your work and I don't want to duplicate it.

If this PR lands first:

If my stack (#794) lands first: on top of the #719 rebase you need anyway, you'd:

  • Swap RequestId for RunId, or make it a newtype over it. The origin function covers what your constructors do, and it can move into your domain module if you'd rather it live there.
  • Type the boundaries with RunId, which is mostly your existing changes with the type swapped. That includes fix(agents): refuse a stream under another run's id or a second time #797's StreamClaim::claim, which drops its string compare, and has_id can be deleted.
  • Have RequestId::parse / ApprovalOwner::parse accept the UUID plus the old forms for records written before refactor(agents): name a run by its RunId #796.
  • Keep the builders' run_id param rather than removing it.
  • Keep ApprovalOwner as is. Request and Run would carry the same type with different values until [FEATURE]: A runtime that owns sessions and their runs #780 unifies them.
  • Expect the approval sweep (598095b) and helper removal (4be9654) to carry over mostly as is, with some conflicts in the chat handler around RequestResourceGuard.

I'm leaning toward the stack landing first, since #796 is mergeable now and you need the #719 rebase regardless, but I'm totally fine going the other way if you'd rather land first. Happy to noodle on it together too! 🙏 And if you've got a strong reason to keep the readable prefixes in the id itself, I think that may be worth hashing out on #778 before either of us moves? Thoughts?

@jakedipity

Copy link
Copy Markdown
Collaborator Author

@justintime4tea Thanks for mapping this out. I agree with the end state, and I'm happy for your stack to land first. I owe the #719 rebase either way, and reworking once onto RunId is better than you building #796 around a type you'd then replace.

I had some local changes not pushed remotely:

Once your stack lands, I'll rework this PR into typing the run's boundaries on top of RunId:

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants