Skip to content

Prime Agent integration: session-scoped ACP state and isolated inline RLM runners #317

Description

@qiangu2020

Summary

I integrated billion-context-pi@0.1.58 (acp-kernel@0.0.55) with prime-agent@0.9.3 and validated the result against Prime's real extension loader and inline RLM runtime.

Prime's session model differs from the host assumptions that billion-context-pi normally sees. A working integration therefore needed a thin adapter, several session-boundary fixes in a small vendor fork, and one host-side fix in Prime's inline-child runtime.

This issue documents what was integrated and why. It is related to #269, but is not a duplicate: #269 covers models ignoring compression nudges; this report focuses on host adapters, custom-message turn boundaries, inherited child state, and extension-runner isolation.

Prime-specific runtime differences

  1. Prime exposes persisted entries through SessionManager.getBranch(), while the plugin expects buildContextEntries().
  2. Initial and forwarded RLM tasks are stored as custom_message entries with customType: "agent_message", not only as ordinary role: "user" messages.
  3. Inline RLM children need the parent's existing compression blocks and refs, but must start with fresh nudge/cadence state.
  4. Parent, child, sibling, and nested sessions can run in one process, so process-global nudge bookkeeping crosses session boundaries.
  5. Prime created a child SessionManager, but its inline child previously reused the parent Agent's context transform and provider hooks.
  6. A nudge is advisory. Long agentic runs can ignore it and keep growing, which is the failure mode in Title: Context never auto-compresses when the model ignores the nudge — climbs 75%→95%+ on long agentic tasks (no enforcement path) #269.

Integration implemented

1. Thin Prime extension bridge

The Prime wrapper:

  • adapts getBranch() to buildContextEntries();
  • loads the tested vendor build directly;
  • sets delegate: false, because Prime already has native RLM delegation;
  • avoids installing a second universal proxy/context transform.

This keeps one ACP transformation layer and prevents two delegation systems from competing for the same session.

2. Pinned, reproducible vendor integration

The integration is based on upstream 0.1.58 with a small Prime-specific patch set. Prime resolves it as a local package instead of silently continuing to load an older pinned npm version.

This was necessary because a host adapter alone cannot change child state derivation or the host's extension-runner wiring, and modifying a transient global node_modules copy would be lost on the next install.

3. Shared Prime-aware turn boundary

The fork uses one boundary predicate for message projection, turn IDs, retry/outcome scope, and nudge accounting:

  • ordinary role: "user" messages begin a turn;
  • non-empty custom_message + customType: "agent_message" entries also begin a turn;
  • UI/control custom messages do not;
  • synthetic provider-throttle and delegate messages do not.

Without this, multiple real Prime turns can collapse to the session ID, and failed/no-op compression results from an earlier turn can incorrectly block a later one.

4. Per-session injection and runtime state

I replaced process-global nudge deduplication with a per-session injection ledger that records actual injections, not model responses.

  • Ordinary nudge budget: one injection per real turn.
  • Emergency nudge budget: bounded separately (up to three per real turn).
  • Session start/shutdown clears only that session's runtime state.
  • Auto-compress episode, dead-range, and throttle tracking are also session-scoped.

This prevents a child startup from clearing its parent, or a sibling's turn key from suppressing a valid injection.

5. Inherit structure; reset child-local cadence

When deriving a child state, the integration inherits:

  • compression blocks;
  • message refs;
  • statistics and absorbed records needed for recovery.

It does not inherit stale nudge baselines or tier cadence stamps. Existing child sidecars receive a one-time migration marker so resume does not reset healthy cadence repeatedly.

This fixes the observed case where a fresh child had a smaller context than the inherited parent baseline, produced negative “growth”, and remained blocked: T1 (cadence).

6. Bounded server-side auto-compression backstop

The local integration keeps nudges as the primary path but adds a bounded fallback:

  • trigger after three ignored over-threshold nudges, or at a hard threshold around 95%;
  • aim for roughly 80% context usage;
  • choose at most five viable ranges;
  • require at least 5,000 estimated net tokens saved;
  • cap automatic runs per session;
  • apply cooldown and range-fingerprint deduplication;
  • preserve reversible ACP blocks for decompress.

This follows the motivation of #269/#270 without treating repeated reminder injection as enforcement.

7. Isolated Prime inline-child extension runner

On the host side, Prime's inline RLM child now owns a child-specific extensionRunnerRef. The child's:

  • transformContext uses its own runner;
  • before_provider_request hook uses its own runner;
  • after_provider_response hook uses its own runner;
  • AgentSession is explicitly bound to that runner ref.

This host-side change is required because the plugin factory cannot override a child Agent that has already captured the parent's callbacks. A new child session file alone is not sufficient isolation.

Why these boundaries matter

Before the integration, I could see internal decisions reporting nudge=active while no new nudge reached the model. Fresh children could inherit a larger cadence baseline than their own context, remain cadence-blocked, and count an old turn's compression result as current. Reusing the parent's context callback also meant a child that looked separate on disk could still execute extension logic through the parent's runner.

The key design rule that fixed these cases was:

Compression structure may be inherited, but scheduling state, injection budgets, and extension runners belong to the concrete session runtime.

Validation

The installed integration passed:

  • TypeScript typecheck;
  • production build;
  • full vendor test suite: 610 tests total, 606 passed, 0 failed, 4 skipped;
  • Prime's real resource-loader smoke test with no extension diagnostics;
  • registration of compress, decompress, search_context, and acp_status, with no duplicate delegate tool;
  • parent/inline-child isolation tests (different runner refs and independent cadence/runtime state);
  • inherited-block + fresh-cadence tests;
  • old-child-sidecar one-time migration and resume tests;
  • repeated same-turn nudge suppression and next-turn reactivation tests;
  • ignored-nudge automatic compression plus reversible decompress tests.

Possible upstream seams

Would you be open to focused PRs for generic parts of this integration?

  1. A host-supplied or shared isTurnBoundary(entry) / entry-normalization hook.
  2. An explicit distinction between inherited structural state and child-local scheduling state.
  3. Session-keyed injection/runtime bookkeeping throughout the extension.
  4. A documented adapter contract for hosts whose child runtime is not created by the Pi coding agent.
  5. Coordination with Title: Context never auto-compresses when the model ignores the nudge — climbs 75%→95%+ on long agentic tasks (no enforcement path) #269 on a bounded auto-compression backstop, rather than creating a separate enforcement design.

I can split these into small changes so Prime-specific host code does not leak into the general plugin. This report intentionally omits local paths, session IDs, account details, and raw logs.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions