0xPlaygrounds / 0xPlaygrounds/rig
feat(agent): expose correlated per-invocation progress for native and MCP tools
- Dominant language
- Rust
- Stars
- 8.6k
- Forks
- 959
- Avg merge
- 4h 32m
- Merged PRs (30d)
- 117
Description
- [x] I have looked for existing issues (including closed) about this
## Feature Request
Expose a host-facing progress channel for one concrete tool invocation, shared by native and MCP tools.
Rig already gives hosts a stable `internal_call_id` around tool-call lifecycle events, but the executing tool cannot publish intermediate progress that the host can correlate with that invocation. A host can therefore show requested/running/completed states, but it cannot faithfully show updates such as “scanned 42/100 files” or “downloaded 8 MiB”.
This is different from streamed tool-call argument deltas: the requested API reports progress while the tool itself is executing.
### Motivation
Without a public per-invocation progress contract, downstream hosts must either show an indefinite spinner, infer progress from unrelated events, or correlate by tool name/arguments. The latter two approaches are incorrect when identical tools run concurrently or recursively.
The same gap exists for MCP. MCP can carry a progress token on the request and send progress notifications, but Rig does not currently project those notifications into a host-facing event associated with the Rig invocation.
Related work provides useful pieces but not the complete contract:
- [#1311](https://github.com/0xPlaygrounds/rig/pull/1311) added `internal_call_id` for lifecycle correlation.
- [#1906](https://github.com/0xPlaygrounds/rig/issues/1906) discusses host-facing tool metadata including `emits_progress` and MCP progress tokens, but does not provide a progress transport.
- [#2094](https://github.com/0xPlaygrounds/rig/issues/2094) discusses call-scoped state under concurrency, but not live tool-to-host events.
- [#2121](https://github.com/0xPlaygrounds/rig/pull/2121) contains an operation-backend `ProgressSink`; it is not a general progress channel for arbitrary `Tool`/`McpTool` dispatch.
- The closed [#2122](https://github.com/0xPlaygrounds/rig/pull/2122) explicitly listed a general tool-progress event channel as remaining work.
### Proposal
Add an additive, runtime-neutral progress contract with these properties.
#### Correlated event
Expose a normalized event whose exact shape is open for design, but which carries at least:
```rust
pub struct ToolProgress {
pub internal_call_id: String,
pub sequence: u64,
pub message: Option,
pub completed: Option,
pub total: Option,
}
```
It must support both determinate and indeterminate progress. Sequence values must be monotonic within one invocation so hosts can discard stale updates.
#### Native tools
The per-call execution context/extensions supplied to a native tool should expose:
1. the stable `internal_call_id` for that dispatch; and
2. a host-installed, concurrency-safe `ToolProgressSink` or reporter bound to that invocation.
Progress is host-only observation and must not be appended to the model-visible tool result.
#### MCP tools
For an MCP call, Rig should:
1. allocate or project a matching MCP progress token without overwriting unrelated caller metadata;
2. route matching MCP progress notifications into the same host-facing progress channel; and
3. preserve the same `internal_call_id` used by normal Rig lifecycle events.
MCP servers that do not emit progress remain valid tools and must not receive fabricated percentage updates.
#### Semantics
- Native, MCP, parallel, nested, and duplicate-name calls remain independently correlated.
- Streaming and non-streaming agent runs expose the same progress semantics.
- Updates after completion, failure, denial, or cancellation are ignored or rejected deterministically.
- Progress observation does not alter the tool result or agent control flow.
### Acceptance criteria
- A native tool can emit multiple updates during execution and the host receives them with the matching `internal_call_id`.
- Two concurrent identical calls cannot cross-update.
- Real MCP progress notifications are forwarded through the same public API.
- A non-progress MCP tool emits no synthetic progress.
- Cancellation and all terminal tool states handle late updates deterministically.
- Tests cover native, MCP, concurrency, nested calls, cancellation, and streaming/non-streaming parity.
### Alternatives
- **Downstream-only progress registries:** cannot generically bind Rig's private invocation lifecycle to arbitrary native and MCP tools, and every host would rebuild the same fragile bridge.
- **A running spinner:** useful as lifecycle UI, but it is not tool-reported progress.
- **Tool-name or argument correlation:** incorrect for concurrent or recursive identical calls.
- **Operation-backend-only progress:** useful for command/code backends, but it does not cover the general `Tool` and `McpTool` execution boundary.
Contributor guide
Research direction
The issue describes adding a progress channel for tool invocations. Start by examining the existing tool dispatch and lifecycle code, likely in the agent module, to understand the `internal_call_id` flow. Look at the `ToolProgressSink` from PR #2121 and the MCP tool handling. The acceptance criteria list specific scenarios to test, so writing tests for native tools, MCP tools, and concurrency will be key to verifying the implementation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- ai-infra-agents, backend
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100