Skip to content

fix(coding-agent): make continual harness state reachable from the prompt - #889

Open
Hotragn wants to merge 1 commit into
PrimeIntellect-ai:mainfrom
Hotragn:fix/819-harness-overview-reachability
Open

fix(coding-agent): make continual harness state reachable from the prompt#889
Hotragn wants to merge 1 commit into
PrimeIntellect-ai:mainfrom
Hotragn:fix/819-harness-overview-reachability

Conversation

@Hotragn

@Hotragn Hotragn commented Aug 7, 2026

Copy link
Copy Markdown

Fixes #819.

Two defects compound so that a spawned child has no reachable path to the continual harness state its own system prompt was built from. Each is the workaround for the other, so they are fixed together.

A. The prompt overview kept the alphabetically-first entries

formatHarnessStateForPrompt sorted every kind by [path, title, id] and kept the first 6. Which lessons reached the model was decided by how their paths happened to be spelled — a memory written seconds ago under memory/w/... lost its slot to 48 older entries under memory/a/.... The options object already accepted maxEntriesPerKind/maxContentLength, but neither production call site in system-prompt.ts passed them and no setting reached them, so the caps were hardcoded in practice.

  • Entries are now ranked by recency: updated_at, falling back to created_at when it is unparseable, then version, then the old [path, title, id] comparison purely to break exact ties. Selection and render order are both recency-first, so the newest lesson is nearest the model and the output stays deterministic for a given state.
  • A new harnessOverview setting (maxEntriesPerKind, maxContentLength) reaches both call sites through SettingsManager.getHarnessOverviewSettings() and BuildSystemPromptOptions. Values are clamped in formatHarnessStateForPrompt (0-500 entries, 40-4000 chars) so a bad settings value cannot blow up or collapse the prompt.
  • overviewForPrompt, the refiner's own input, had the same defect in a milder form: an unordered 40-entry head slice. It now uses the same ranking, so a truncated list still shows the refiner the entries most likely to need an edit.

The overflow line was - +42 more memory entries while the surrounding prompt instructed the model to "inspect the underlying continual harness entry only when detail matters". It named a number and no way to act on it. It now names the call:

- +42 more memory entries not shown: read them with `rlm.harness.list("memory")` for this session's local store and `rlm.harness.list("memory", global_=True)` for the cross-session global store, or raise `harnessOverview.maxEntriesPerKind` in settings.json

Sessions without IPython get the settings half only, matching the existing includeIpythonExamples gating.

B. rlm.harness was silent about the global store

rlm.harness resolves to the session-local store. For an rlm()-spawned child that store is new and empty, so overview() returns memory: 0 — while the same child's system prompt was built from the merged global + local state and lists global entries. Nothing in the output mentioned that a global store existed, how many entries it held, or that global_=True reads it.

A local overview now ends with:

global store (not listed above): /home/u/.prime/agent/harness/harness_state.json
  prompt: 0, memory: 40, skill: 0, subagent: 0, refinements: 0
  read it with overview(global_=True), list(<kind>, global_=True), or get(<kind>, 'global:<id>'); pass include_global=True to overview()/list() for the merged view this session's system prompt was built from

overview() and list() also accept include_global=True, which returns the same union the host merges to build the prompt — local entries shadow global entries on id collision, matching mergeHarnessStates. The pointer is emitted only when a distinct global store resolves, so a global store never points at itself and a local store aliased onto the global file stays quiet.

Running the issue's repro B verbatim against this branch:

Harness state (local): /tmp/child-harness/harness_state.json
...
memory: 0
refinements: 0
global store (not listed above): /tmp/global-harness/harness_state.json
  prompt: 0, memory: 40, skill: 0, subagent: 0, refinements: 0
  read it with overview(global_=True), list(<kind>, global_=True), ...

Deliberately not in scope

The issue's third observation — parent-local entries never reach children, while the prompt recommends local scope for session coordination — is a scope-inheritance design change rather than a reachability fix, and would need its own decision about whether a child sees a read-only view of its parent's local store. Left for a separate issue.

Tests

packages/coding-agent/test/suite/regressions/819-harness-overview-reachability.test.ts (7 cases): the issue's repro A (the newest entry now renders, first, out of 49); created_at fallback and tie-break determinism; the overflow hint in both IPython and non-IPython prompts; clamping of negative, NaN, and oversized budgets; SettingsManager round-trip; buildSystemPrompt honoring the option; and an end-to-end case that writes a global harness file, boots a session with harnessOverview.maxEntriesPerKind: 12, and asserts the live session.systemPrompt renders all 12.

prime-agent-runtime/test/test_harness.py (3 cases): the local overview names the global store and its counts; include_global=True merges with local shadowing while global_=True still reads global alone; and no pointer is emitted when no distinct global store exists.

Verified: npm run check clean; prime-agent-runtime 38/38; test/refinement.test.ts, test/system-prompt.test.ts, test/settings-manager.test.ts pass except one pre-existing Windows-only path-separator assertion in refinement.test.ts that also fails on main.

Note

Make continual harness state reachable from the system prompt via recency ranking and global store pointers

  • Harness overview entries in the system prompt are now ranked by recency (most-recently-updated first) instead of alphabetical path order, using new helpers in refinement.ts.
  • Overflow lines now include explicit rlm.harness.list / rlm.harness.overview read instructions so agents know how to access withheld entries.
  • rlm.harness.overview() and rlm.harness.list() in harness.py gain an include_global=True parameter that merges the global peer store, with local entries shadowing global ones.
  • Local overviews now append a pointer to the global store (path, per-kind counts, and read guidance) when a distinct global store exists.
  • New harnessOverview.maxEntriesPerKind and harnessOverview.maxContentLength settings are exposed via SettingsManager and forwarded through buildSystemPrompt, with clamping deferred to the formatter.

Macroscope summarized 47027e3.

…ompt

The system-prompt overview rendered an alphabetical head slice of 6 entries
per kind, so which lessons the model saw was decided by how their paths
happened to be spelled, and neither production call site could raise the cap.
Rank entries by recency (updated_at, then created_at, then version) so the
newest lessons survive truncation, and read the cap and body clip from a new
`harnessOverview` setting. The same ranking now feeds the refiner's own
overview, which had an unordered 40-entry head slice.

The overflow line named withheld entries by count alone while the prompt told
the model to "inspect the underlying continual harness entry" — with no call
that reads them. It now names that call.

On the kernel side, `rlm.harness` resolves to the session-local store, which
is empty in a freshly spawned child even though the child's system prompt was
built from the merged global + local state. A local overview now names the
global store, its per-kind counts, and how to read it, and `overview()` and
`list()` accept `include_global=True` for the merged view the prompt was
built from.

fixes PrimeIntellect-ai#819
zhengr pushed a commit to zhengr/prime-agent that referenced this pull request Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant