#17666: feat(plugins): expose steerSession and isSessionStreaming on plugin API
stale
size: S
Cluster:
Tool and Plugin Enhancements
---
## Summary
Add two new methods to `OpenClawPluginApi` for agent-to-agent steering:
- `steerSession(sessionKey, text)`: inject a message into an active streaming session between tool calls. Returns `true` if queued.
- `isSessionStreaming(sessionKey)`: check if a session is currently streaming.
These wrap the internal `queueEmbeddedPiMessage`/`isEmbeddedPiRunStreaming` functions with sessionKey→sessionId resolution, so plugins don't need to know internal session IDs.
Use case: multi-agent plugins can steer agents that are actively streaming when another agent posts to a shared channel, solving the stale-context problem without workarounds.
- **Problem:** Plugins have no way to inject messages into an actively streaming agent session. The internal `queueEmbeddedPiMessage()` exists but is not exposed through the plugin API. Multi-agent plugins (e.g. agent swarms where agents communicate through shared channels) cannot notify a streaming agent about new messages from other agents, causing stale-context issues where the agent completes its response without seeing the latest input.
- **Why it matters:** Without steer access, the only workarounds are: (1) waiting for the agent to finish and then sending a follow-up (high latency), or (2) building a full A2A transport layer that duplicates internal OpenClaw functionality. Direct steer access enables real-time agent-to-agent coordination with zero additional infrastructure.
- **What changed:** Two new methods added to `OpenClawPluginApi` type and implemented in `createPluginRegistry`. A `resolveSessionId()` helper bridges the gap between canonical session keys (what plugins know) and internal session IDs (what the embedded runner uses). No existing methods, types, or behavior were modified.
- **What did NOT change (scope boundary):** No changes to the embedded runner itself, session store format, queue modes, or any existing plugin API methods. The `queueEmbeddedPiMessage` semantics are preserved exactly — steer
only works when the session is actively streaming and not compacting.
## Change Type (select all)
- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [ ] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [ ] Integrations
- [x] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- Related: N/A (no existing issue; motivated by flock multi-agent plugin development)
## User-visible / Behavior Changes
None — these are plugin API additions only. No existing behavior, defaults, or config are affected. Plugins that don't call the new methods see zero difference.
## Security Impact (required)
- New permissions/capabilities? **Yes** — plugins can now inject text into other agents' streaming sessions.
- Secrets/tokens handling changed? **No**
- New/changed network calls? **No** — purely in-process; no network involved.
- Command/tool execution surface changed? **No** — steered text goes through the same `activeSession.steer()` path as existing inbound messages.
- Data access scope changed? **No** — plugins already have access to `config` which contains agent IDs and session keys.
- **Risk + mitigation:** A malicious plugin could steer arbitrary sessions. Mitigation: (1) plugin loading is already trust-gated (only admin-configured plugins load), (2) `queueEmbeddedPiMessage` internally validates the session is streaming and not compacting before accepting, (3) the steered text follows the same processing path as any other inbound message — no privilege escalation.
## Repro + Verification
### Environment
- OS: Linux 6.17.0-14 (x64)
- Runtime/container: Node v22.22.0
- Model/provider: N/A (API surface change, no model interaction)
- Integration/channel: N/A
- Relevant config: Any OpenClaw config with plugins enabled
### Steps
1. Create a plugin that calls `api.steerSession("agent:coder:discord:channel:123", "new message from reviewer")`
2. Have the target agent actively streaming (processing a prompt)
3. Observe the steered text is injected between tool calls
### Expected
- `steerSession` returns `true`, agent sees injected message in its next tool-call boundary
- `isSessionStreaming` returns `true` while agent is streaming, `false` otherwise
- Both return `false` gracefully when session doesn't exist or isn't streaming
### Actual
- Not yet verified with live plugin integration (see Human Verification)
## Evidence
- [x] Trace/log snippets
`queueEmbeddedPiMessage` emits diagnostic logs on success/failure:
```
queue message failed: sessionId=xxx reason=no_active_run
queue message failed: sessionId=xxx reason=not_streaming
queue message failed: sessionId=xxx reason=compacting
```
`resolveSessionId` returns `undefined` when session key not found → both methods return `false` without side effects.
## Human Verification (required)
- **Verified scenarios:** Import resolution correct (all imported symbols exist in their source modules). Type compatibility confirmed — `steerSession` and `isSessionStreaming` signatures match `OpenClawPluginApi` type. `loadSessionStore` returns `Record<string, SessionEntry>` where `SessionEntry.sessionId` is the key used by `ACTIVE_EMBEDDED_RUNS`.
- **Edge cases checked:** Missing session key → `resolveSessionId` returns `undefined` → methods return `false`. Session exists but not streaming → `queueEmbeddedPiMessage` returns `false`. Session compacting → `queueEmbeddedPiMessage` returns `false`.
- **What you did NOT verify:** Full build (pnpm install + tsc) — deps not installed in local checkout. Live end-to-end test with an actual plugin calling the API during streaming.
## Compatibility / Migration
- Backward compatible? **Yes** — additive API only; no existing signatures changed.
- Config/env changes? **No**
- Migration needed? **No**
## Failure Recovery (if this breaks)
- **How to disable/revert quickly:** Revert the single commit. Or: plugins simply don't call the new methods — they are opt-in.
- **Files/config to restore:** `src/plugins/types.ts`, `src/plugins/registry.ts`
- **Known bad symptoms:** If `loadSessionStore` or `resolveDefaultSessionStorePath` throws for an unexpected agent ID, `steerSession`/`isSessionStreaming` would throw up to the calling plugin. Low risk — these functions are battle-tested in the existing codebase.
## Risks and Mitigations
- **Risk:** `loadSessionStore` reads from disk on every call (with TTL cache). High-frequency `steerSession` calls could add I/O pressure.
- **Mitigation:** `loadSessionStore` already has mtime-based caching with `structuredClone`. In practice, steer calls happen at most once per tool-call boundary per channel post — well within cache TTL.
- **Risk:** Session store key format mismatch — if a plugin passes a non-canonical key, `resolveSessionId` silently returns `undefined`.
- **Mitigation:** This is the correct fail-safe behavior (returns `false`). Documented in JSDoc that the key must be canonical.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR adds two new methods to the `OpenClawPluginApi` for agent-to-agent session steering:
- `steerSession(sessionKey, text)` — injects a message into an active streaming session between tool calls, returning `true` if successfully queued
- `isSessionStreaming(sessionKey)` — checks if a session is currently streaming
Both methods use a new private `resolveSessionId` helper that bridges canonical session keys (what plugins know) to internal session IDs (what the embedded runner uses) via `resolveAgentIdFromSessionKey` → `resolveDefaultSessionStorePath` → `loadSessionStore`.
- The implementation correctly handles all failure paths: missing session key, non-existent session, session not streaming, and session currently compacting — all return `false` without side effects
- `loadSessionStore` performs synchronous disk I/O but has TTL-based mtime caching (default 45s), appropriate for the expected call frequency (once per tool-call boundary)
- No existing methods, types, or behavior were modified — this is a purely additive change
- No tests were added for the new functionality
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge — additive API surface only, with correct fail-safe behavior on all error paths.
- Score of 4 reflects that the implementation is clean, type-safe, and follows existing codebase patterns. The resolution chain (session key → agent ID → store path → session store → sessionId) correctly handles all edge cases. All imported functions have been verified to not throw unexpected exceptions. The only reason this isn't a 5 is the absence of unit tests for the new methods, and the fact that end-to-end verification with an actual plugin has not been performed.
- No files require special attention. Both changes are straightforward and well-contained.
<sub>Last reviewed commit: 34f4f02</sub>
<!-- greptile_other_comments_section -->
<sub>(4/5) You can add custom instructions or style guidelines for the agent [here](https://app.greptile.com/review/github)!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#16558: feat(plugins): add sessions.spawn and rateLimit to plugin runtime
by Zephyr-Blessed · 2026-02-14
71.6%
#18911: feat(plugins): Add registerStreamFnWrapper and updatePluginConfig APIs
by John-Rood · 2026-02-17
71.4%
#16044: plugin-sdk: expose onAgentEvent + onSessionTranscriptUpdate via Plu...
by scifantastic · 2026-02-14
68.4%
#6582: fix(steer): prevent cross-thread reply contamination in queue mode
by NSEvent · 2026-02-01
67.6%
#7516: feat(sessions): Auto-inject From:/To: identity headers in agent-to-...
by RusDyn · 2026-02-02
67.5%
#5525: Add plugin hook to resolve canonical RoomKey for session identity a...
by peteclt92 · 2026-01-31
66.6%
#11732: feat(plugins): add injectMessages to before_agent_start hook
by antra-tess · 2026-02-08
66.2%
#20072: feat(sessions_spawn): add sessionKey param to reuse sub-agent sessions
by Be1Human · 2026-02-18
65.5%
#20078: feat(session): Add channelGroups config(optional config) for shared...
by demarlik01 · 2026-02-18
65.5%
#19073: feat(voice-call): streaming TTS, barge-in, silence filler, hangup, ...
by odrobnik · 2026-02-17
64.9%