You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds a third interceptor type — sink — to the working copy of the SEP (docs/sep.md).
What
A sink is a non-blocking, non-mutating interceptor that reacts to a context operation without affecting the interaction. It MUST NOT block execution and MUST NOT modify the payload — it exists to drive concurrent UX, workflow state, or downstream pipelines based on what the agent is doing.
Use cases: avatar/character animation from conversation mood, voice-mode / TTS switching, real-time UI affordances, background context indexing, secondary-agent triggers. The C# reference impl already demonstrates the avatar case end-to-end in AvatarMoodInterceptorSample.
Changes to docs/sep.md:
Type union extended to "validation" | "mutation" | "sink" (all sites)
New Sink section parallel to Validator / Mutator, with SinkResult
Execution model: sink as a third stage (parallel, fire-and-forget) on each phase; Type Behaviors table updated
Sinks exempt from aggregate timeouts, failOpen, and fail-closed requirements
New avatar-mood sink example in discovery (audit-logger stays an audit-mode validator, since compliance logging must also capture rejected operations)
Rationale restructured: separation of the three types, why sink vs mode: "audit", and why "sink" and not "observability" (avoids implying OpenTelemetry overlap)
Sinks are additive to the validator/mutator split and mode: "audit": audit mode disables enforcement while preserving a validator/mutator's interface; sinks are structurally non-enforcing and cannot be promoted to affect the interaction. Implementations MUST reject sinks declared with mode: "audit".
Why here
This re-homes the proposal from Degiorgio/modelcontextprotocol#1 — which targeted the SEP author's personal fork — onto this repo's working copy of the SEP. #21 established docs/sep.md as where the WG maintains and reviews the SEP, so the sink proposal belongs here. Once the WG agrees, it folds upstream into SEP-2624 along with the rest of docs/sep.md.
@PederHP This is looking great, the sink/audit distinction is really clearly argued. One small thing I wanted to check my understanding on:
The Sink section notes that a sink MUST return a SinkResult, while the execution model describes sinks as parallel, fire-and-forget. I might be missing something, but I wasn't totally sure how those fit together in
practice, if the runtime doesn't wait for the sink, when does it collect the SinkResult for ChainExecutionResult.results?
@PederHP This is looking great, the sink/audit distinction is really clearly argued. One small thing I wanted to check my understanding on:
The Sink section notes that a sink MUST return a SinkResult, while the execution model describes sinks as parallel, fire-and-forget. I might be missing something, but I wasn't totally sure how those fit together in practice, if the runtime doesn't wait for the sink, when does it collect the SinkResult for ChainExecutionResult.results?
Yeah, the point of this one just to ensure that the request has been "sinked" - it is not a guarantee that the sink has actually been processed. One could argue that the sender can't really do anything with this - which is true - it is not supposed to be blocking, but it might still be useful in some cases to be able to send an error message back. But purely for observability - not something to build logic around. We could have a void-ish type instead - I wouldn't mind.
Adds a third interceptor type, `sink`, for non-blocking, non-mutating
interceptors that react to context to drive concurrent UX, workflow
state, or downstream pipelines (avatar animation, voice-mode triggers,
real-time UI affordances, background indexing) without affecting the
request/response interaction.
Additive to the validator/mutator split and `mode: "audit"`. Audit mode
disables enforcement while preserving the validator/mutator interface;
sinks are structurally non-enforcing and cannot be promoted to affect
the interaction. Renamed from the original draft's `observability` to
`sink` to avoid implying overlap with OpenTelemetry / general logging.
Re-homed from Degiorgio/modelcontextprotocol#1 (a PR against the SEP
author's personal fork) onto this repo's working copy of the SEP, which
PR #21 established as where the WG maintains and reviews the SEP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review feedback: metrics duplicates what OTel already covers and no
other interceptor result type has an equivalent field. recorded stays,
with explicit async semantics: true is an acknowledgment of acceptance,
false definitively means the event was not recorded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The documentation still subjects sinks to blocking result aggregation, aggregate timeouts, and fail-closed requirements, contradicting their required non-blocking behavior.
This PR reintroduces sinks in the working-copy interceptor SEP, adding a non-blocking, non-mutating type for context-reactive workflows alongside validators and mutators.
Changes:
Defines the sink contract and SinkResult, with audit-logger and avatar-mood examples.
Adds fire-and-forget sink execution to the trust-boundary-aware execution model.
Explains how sinks differ from audit mode and observability tooling.
File
Description
docs/sep.md
Adds sink definitions, execution semantics, examples, and design rationale.
Per review feedback: the sink sections say sinks never block, but the
general rules elsewhere in the SEP still applied to all interceptors.
- Orchestration: sinks are dispatched without awaiting completion and
are excluded from the aggregate timeout; sink results, if collected,
are consumed asynchronously and never delay the operation
- failOpen (interceptor default and invoker override): ignored for sinks
- InterceptorOverrides.timeoutMs: bounds only the sink's own invocation
- Timeout and Cancellation: a timed-out sink is swallowed like any other
sink failure
- Security Best Practices #4 (Fail Secure): scoped to validators and
mutators, with sinks explicitly exempt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqPQYRWhLmScrZ19A8eKJp
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The documentation claims complete compliance logging even though validation failures skip the sink-based logger, leaving rejected operations unlogged.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Sink logger omits rejected operations from compliance auditing
docs/sep.md:590
Changing this logger to a sink means it will not run when a receiving validator rejects a request: the new execution model skips sinks on validation failure. As a result, it no longer satisfies the adjacent description, “Logs all MCP operations for compliance,” and omits rejected attempts even when the logger is healthy. Please label this as best-effort post-validation logging rather than a complete compliance audit, and make clear that rejected-operation auditing must be handled separately.
@PederHP This is looking great, the sink/audit distinction is really clearly argued. One small thing I wanted to check my understanding on:
The Sink section notes that a sink MUST return a SinkResult, while the execution model describes sinks as parallel, fire-and-forget. I might be missing something, but I wasn't totally sure how those fit together in practice, if the runtime doesn't wait for the sink, when does it collect the SinkResult for ChainExecutionResult.results?
The orchestration change now makes this clear - so sink results are read in the background and never delay the operation.
Per review feedback: as a sink, audit-logger would be skipped when a
receiving validator rejects a request, contradicting its "logs all MCP
operations for compliance" description. Every other example in the SEP
(execution ordering, invoker overrides with promotion to active, context
passing) already treats it as an audit-mode validator.
Compliance auditing that must capture rejected operations belongs in an
audit-mode validator; avatar-mood remains the sink example.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MqPQYRWhLmScrZ19A8eKJp
Re the Copilot finding on audit-logger (docs/sep.md:590): fixed in 9a85746 by restoring it as an audit-mode validator, matching main and the other examples in the SEP (execution ordering, invoker overrides, context passing). Compliance logging that must capture rejected operations belongs in the validate stage; avatar-mood remains the sink example.
Clarified explanation of audit mode for validators and mutators.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Peder Holdgaard Pedersen <127606677+PederHP@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a third interceptor type —
sink— to the working copy of the SEP (docs/sep.md).What
A sink is a non-blocking, non-mutating interceptor that reacts to a context operation without affecting the interaction. It MUST NOT block execution and MUST NOT modify the payload — it exists to drive concurrent UX, workflow state, or downstream pipelines based on what the agent is doing.
Use cases: avatar/character animation from conversation mood, voice-mode / TTS switching, real-time UI affordances, background context indexing, secondary-agent triggers. The C# reference impl already demonstrates the avatar case end-to-end in
AvatarMoodInterceptorSample.Changes to
docs/sep.md:"validation" | "mutation" | "sink"(all sites)SinkResultfailOpen, and fail-closed requirementsavatar-moodsink example in discovery (audit-loggerstays an audit-mode validator, since compliance logging must also capture rejected operations)mode: "audit", and why "sink" and not "observability" (avoids implying OpenTelemetry overlap)Sinks are additive to the validator/mutator split and
mode: "audit": audit mode disables enforcement while preserving a validator/mutator's interface; sinks are structurally non-enforcing and cannot be promoted to affect the interaction. Implementations MUST reject sinks declared withmode: "audit".Why here
This re-homes the proposal from
Degiorgio/modelcontextprotocol#1— which targeted the SEP author's personal fork — onto this repo's working copy of the SEP. #21 establisheddocs/sep.mdas where the WG maintains and reviews the SEP, so the sink proposal belongs here. Once the WG agrees, it folds upstream into SEP-2624 along with the rest ofdocs/sep.md.🤖 Generated with Claude Code