Skip to content

Commit e3615f6

Browse files
tercelclaude
andcommitted
spec: add streaming execution protocol section
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9cd7332 commit e3615f6

File tree

1 file changed

+38
-0
lines changed

1 file changed

+38
-0
lines changed

PROTOCOL_SPEC.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3884,6 +3884,44 @@ This enables framework-specific context creation (e.g., extracting Identity from
38843884

38853885
The lifecycle is: request arrives → ContextFactory.create_context(request) → Executor.call(module_id, inputs, context) → response.
38863886

3887+
#### Streaming Execution Protocol
3888+
3889+
Modules MAY support incremental output by implementing a `stream` method alongside the required `execute` method:
3890+
3891+
```
3892+
stream(inputs, context) → AsyncIterable<Record>
3893+
```
3894+
3895+
**Semantics:**
3896+
3897+
- Each yielded record is a partial result chunk; the framework does not prescribe chunk structure.
3898+
- The complete result is the shallow merge of all yielded chunks (left-to-right object spread).
3899+
- `execute()` MUST remain implemented as the non-streaming fallback.
3900+
- Module descriptors SHOULD declare `annotations.streaming = true` when `stream()` is provided.
3901+
3902+
**Executor.stream() pipeline:**
3903+
3904+
1. Steps 1–6 identical to `call()`: context creation, safety checks, module lookup, ACL, input validation, before-middleware.
3905+
2. If module lacks `stream()`: fall back to `call()`, yield single chunk, return.
3906+
3. Iterate `module.stream(inputs, context)`, yield each chunk to caller.
3907+
4. After all chunks: validate accumulated output against `output_schema`, run after-middleware on accumulated result.
3908+
3909+
**Cross-language signatures:**
3910+
3911+
| Language | Executor method signature |
3912+
|------------|------------------------------------------------------------------------------------|
3913+
| TypeScript | `async *stream(moduleId, inputs?, context?): AsyncGenerator<Record<string, unknown>>` |
3914+
| Python | `async def stream(module_id, inputs?, context?) -> AsyncIterator[dict[str, Any]]` |
3915+
3916+
**MCP bridge behavior:**
3917+
3918+
When bridging `Executor.stream()` to MCP, implementations SHOULD use the standard `notifications/progress` mechanism:
3919+
3920+
1. Client includes `_meta.progressToken` in the `tools/call` request to opt into streaming.
3921+
2. Server calls `Executor.stream()` and for each yielded chunk, sends `notifications/progress` with `message` containing the JSON-serialized chunk.
3922+
3. The final `CallToolResult` contains the complete accumulated result.
3923+
4. If the client does not provide `progressToken`, the bridge accumulates internally and returns an atomic result.
3924+
38873925
### 11.3 Cross-language Implementation Requirements
38883926

38893927
| Requirement | Python | Rust | Go | Java | TypeScript |

0 commit comments

Comments
 (0)