Skip to content

Tier 3 agent runner: streaming, compaction, steering#20

Merged
inhaq merged 3 commits into
pi-native-agent-runnerfrom
pi-agent-tier3
Jun 15, 2026
Merged

Tier 3 agent runner: streaming, compaction, steering#20
inhaq merged 3 commits into
pi-native-agent-runnerfrom
pi-agent-tier3

Conversation

@inhaq

@inhaq inhaq commented Jun 15, 2026

Copy link
Copy Markdown
Owner

This pull request was created by @kiro-agent on behalf of @inhaq 👻

Comment with /kiro fix to address specific feedback or /kiro all to address everything.
Learn about Kiro Web


Implements all of Tier 3 from the pi-harness deep-dive on the native agent runner. Stacked on #18 (base = pi-native-agent-runner), so this diff is just the Tier 3 commit; retarget to main once #18 merges.

All three features compose at runtime and are exercised offline against pi's faux provider (no network/API key).

G — Sub-step activity streaming

The actor's inner tool calls now surface live instead of a single opaque result.

  • RunRequest gains an onEvent sink + a vendor-neutral RunnerActivity type; PiAgentRunner forwards turn_start / tool_start / tool_end from pi's agent events.
  • The role layer (runnerRoles) enriches each activity with the role + runner identity and threads onEvent into every runner.run() call; createRoles exposes onActivity; session wires it to store.recordEvent("runner_activity").
  • Because the server's tapStoreEvents already fans every recorded event to the SSE hub, a monitor sees tool calls live with no server.ts change.

D — Context compaction

Long inner loops no longer blow the context window.

  • New compaction profile option wires pi's transformContext + convertToLlm. When the transcript grows past budget it summarizes the older middle via pi's generateSummary and keeps the prompt + recent tail.
  • findCompactionCut snaps the retained tail back to a turn-start assistant so a toolResult is never orphaned. Both helpers are exported for direct testing.
  • Best-effort: any summary failure (or nothing safe to cut) returns the transcript unchanged, so compaction can never break a run.

E — Steering

A human/supervisor can nudge a running task instead of only cancelling.

  • RunRequest.steering hands the caller a steer(text) bound to the live loop; PiAgentRunner injects it via agent.steer, taking effect after the current turn. meta.steerCount reports injections.

Testing

  • runnerRoles.test.ts (+3): activity forwarded enriched with role/runner identity for actor and critic; no onEvent passed when no sink configured.
  • piAgentRunner.test.ts (+3): real tool activity emitted via onEvent; mid-run steering proven to reach the next turn's context (got-steer); zero steers when no source.
  • piAgentCompaction.test.ts (new, 6): cut snaps to a turn boundary; summarize-and-keep over threshold; no-op under threshold; too-short transcript untouched.
  • npm run typecheck clean; full suite 197 tests green.

Notes / follow-ups

  • Compaction defaults to enabled but only fires when the context is actually large (pi's shouldCompact vs the model's window); thresholdTokens forces it (mainly for tuning/tests).
  • Steering is wired at the runner seam; threading a steering source through roles → session → a server /nudge endpoint (to drive it from the UI) is the natural next wiring step.
  • Activity events flow as the existing SSE event type with data.type = "runner_activity"; a small frontend filter would render them as a live tool feed.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added real-time activity streaming to observe runner progress, including tool execution and conversation turn events during role operations
    • Introduced context compaction to automatically manage long conversation histories and reduce token consumption
    • Added live steering capability to inject guidance and direct model behavior mid-execution
    • Enhanced observability with new activity event types for improved monitoring and debugging

Three pi-harness Tier 3 features for the native agent runner.

G) Sub-step activity streaming. RunRequest gains an onEvent sink + a
vendor-neutral RunnerActivity type; PiAgentRunner forwards turn_start /
tool_start / tool_end from pi's agent events. The role layer
(runnerRoles) enriches each activity with role + runner identity and
threads onEvent into every runner.run() call; createRoles exposes
onActivity; session wires it to store.recordEvent(runner_activity),
which the server's store tap already fans to the SSE hub - so a monitor
sees the actor's tool calls live with no server.ts change.

D) Context compaction. PiAgentRunner gains a compaction option and wires
pi's transformContext + convertToLlm: when the transcript grows past
budget it summarizes the older middle via pi's generateSummary and keeps
the prompt + recent tail. findCompactionCut snaps the retained tail back
to a turn-start assistant so a toolResult is never orphaned. Exported for
direct testing.

E) Steering. RunRequest.steering hands the caller a steer(text) bound to
the live loop; PiAgentRunner injects it via agent.steer so a human can
nudge a running task (taking effect after the current turn) instead of
only cancelling. meta.steerCount reports injections.

Tests: runner-level activity emission + role enrichment/threading;
compaction cut + summarize/keep + no-trigger + too-short (vs faux); mid-
run steering proven to reach the next turn's context. Full suite 197
green; typecheck clean.
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • '.*'

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e90c4e9-4a22-4654-bcc9-b41e58834255

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pi-agent-tier3

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/piAgentCompaction.test.ts (1)

89-103: ⚡ Quick win

Add a test for summarize-failure best-effort fallback.

Line 89-103 covers the success path well, but the contract that compaction failure must return the original transcript unchanged is not directly tested. Please add a deterministic faux-provider failure case and assert no compaction is applied (same reference or equivalent untouched transcript).

Suggested test addition
+  it("keeps the original transcript when summary generation fails", async () => {
+    // no response queued -> summary generation should fail in faux provider path
+    const transform = createCompactionTransform(opts({ thresholdTokens: 1 }), faux.getModel(), "k");
+    const msgs = transcript();
+    const out = await transform!(msgs);
+    expect(out).toBe(msgs); // best-effort fallback
+    expect(out.find((m) => m.role === "compactionSummary")).toBeUndefined();
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/piAgentCompaction.test.ts` around lines 89 - 103, The test file
currently only covers the success path for the compaction transformation but
lacks coverage for the failure fallback behavior. Add a new test case that
configures the faux provider to return an error response (using a method like
setResponses with an error condition or similar failure state), then call
createCompactionTransform with transcript() and assert that the returned output
is either the same reference as the input or an equivalent untouched transcript
with no compaction applied, demonstrating that the transform gracefully falls
back to the original content when summarization fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/session.ts`:
- Around line 155-164: The onActivity callback is dropping the Promise returned
by store.recordEvent(), which can result in unhandled promise rejections. In the
arrow function that calls store.recordEvent (within the ternary operator in the
onActivity assignment), add explicit error handling to the Promise by chaining a
.catch() method to handle any rejection failures. This makes the fire-and-forget
intent explicit while ensuring that any persistence failures are caught and do
not cause unhandled rejection errors.

---

Nitpick comments:
In `@test/piAgentCompaction.test.ts`:
- Around line 89-103: The test file currently only covers the success path for
the compaction transformation but lacks coverage for the failure fallback
behavior. Add a new test case that configures the faux provider to return an
error response (using a method like setResponses with an error condition or
similar failure state), then call createCompactionTransform with transcript()
and assert that the returned output is either the same reference as the input or
an equivalent untouched transcript with no compaction applied, demonstrating
that the transform gracefully falls back to the original content when
summarization fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d831c5f4-4d62-47f9-85e5-01286ede7028

📥 Commits

Reviewing files that changed from the base of the PR and between 197b50e and 27703bb.

📒 Files selected for processing (9)
  • src/adapters/roleBindings.ts
  • src/adapters/runnerRoles.ts
  • src/observability/events.ts
  • src/runners/agentRunner.ts
  • src/runners/piAgentRunner.ts
  • src/session.ts
  • test/piAgentCompaction.test.ts
  • test/piAgentRunner.test.ts
  • test/runnerRoles.test.ts

Comment thread src/session.ts
Comment on lines +155 to +164
const onActivity: ((e: RunnerActivityEvent) => void) | undefined =
store && sessionId
? (e) =>
store.recordEvent({
sessionId,
at: e.at,
type: EVENT_TYPES.runnerActivity,
data: e as unknown as Record<string, unknown>,
})
: roleOpts.onActivity;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle runner_activity persistence failures explicitly.

This callback drops the Promise from store.recordEvent(...). If persistence rejects, the rejection can go unhandled and destabilize the process under stricter unhandled-rejection settings. Make this fire-and-forget path explicit and catch failures.

Suggested patch
     const onActivity: ((e: RunnerActivityEvent) => void) | undefined =
       store && sessionId
-        ? (e) =>
-            store.recordEvent({
-              sessionId,
-              at: e.at,
-              type: EVENT_TYPES.runnerActivity,
-              data: e as unknown as Record<string, unknown>,
-            })
+        ? (e) => {
+            void store
+              .recordEvent({
+                sessionId,
+                at: e.at,
+                type: EVENT_TYPES.runnerActivity,
+                data: e as unknown as Record<string, unknown>,
+              })
+              .catch((err) => {
+                opts.log?.(`failed to persist runner_activity: ${String((err as Error)?.message ?? err)}`);
+              });
+          }
         : roleOpts.onActivity;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onActivity: ((e: RunnerActivityEvent) => void) | undefined =
store && sessionId
? (e) =>
store.recordEvent({
sessionId,
at: e.at,
type: EVENT_TYPES.runnerActivity,
data: e as unknown as Record<string, unknown>,
})
: roleOpts.onActivity;
const onActivity: ((e: RunnerActivityEvent) => void) | undefined =
store && sessionId
? (e) => {
void store
.recordEvent({
sessionId,
at: e.at,
type: EVENT_TYPES.runnerActivity,
data: e as unknown as Record<string, unknown>,
})
.catch((err) => {
opts.log?.(`failed to persist runner_activity: ${String((err as Error)?.message ?? err)}`);
});
}
: roleOpts.onActivity;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/session.ts` around lines 155 - 164, The onActivity callback is dropping
the Promise returned by store.recordEvent(), which can result in unhandled
promise rejections. In the arrow function that calls store.recordEvent (within
the ternary operator in the onActivity assignment), add explicit error handling
to the Promise by chaining a .catch() method to handle any rejection failures.
This makes the fire-and-forget intent explicit while ensuring that any
persistence failures are caught and do not cause unhandled rejection errors.

inhaq and others added 2 commits June 15, 2026 06:56
The onActivity (and the identical onRunnerCall) sink dropped the Promise
from store.recordEvent, so a rejected persistence could surface as an
unhandled rejection and destabilize the run under strict settings. Make
the fire-and-forget intent explicit with void and catch failures, logging
via opts.log.

Addresses CodeRabbit review on PR #20.

Co-authored-by: Inzimam Ul Haq <27832433+inhaq@users.noreply.github.com>
Restacks Tier 3 onto the updated base (bash opt-in + maxTurns-before-
increment from PR #18 review). Resolves the piAgentRunner.ts turn_start
handler conflict by enforcing the turn cap before incrementing while
keeping the Tier 3 streaming emits (turn_start / tool_start / tool_end).

Co-authored-by: Inzimam Ul Haq <27832433+inhaq@users.noreply.github.com>
@inhaq inhaq merged commit 63b838f into pi-native-agent-runner Jun 15, 2026
2 checks passed
@inhaq inhaq deleted the pi-agent-tier3 branch June 15, 2026 08:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant