# Continual Harness

> Durable harness state (prompts, memories, skill specs, subagent specs), refine boundaries, immutable base prompt, and rollback snapshots.

- Repository: PrimeIntellect-ai/prime-agent
- GitHub: https://github.com/PrimeIntellect-ai/prime-agent
- Human docs: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1
- Complete Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/llms-full.txt

## Source Files

- `packages/coding-agent/skills/refine/SKILL.md`
- `packages/coding-agent/skills/refine/src/refine/__init__.py`
- `packages/coding-agent/test/suite/agent-session-refine-skill.test.ts`
- `packages/coding-agent/test/suite/agent-session-serialized-refine.test.ts`
- `packages/coding-agent/README.md`

---

---
title: "Continual Harness"
description: "Durable harness state (prompts, memories, skill specs, subagent specs), refine boundaries, immutable base prompt, and rollback snapshots."
---

The continual harness is Prime Agent’s persisted, editable ledger for reusable behavior outside the token history: prompt notes, memories, skill call contracts, subagent specs, and refinement events. It lives as `rlm.harness` / `get_harness_state()` in the IPython kernel and as `harness_state.json` on disk; host `/refine`, the `refine` skill, and kernel CRUD all read and write the same store. It is not a second execution engine—runtime still runs through the TypeScript host, IPython, and `rlm(...)` recursion.

## What the harness stores

| Kind | Role | Typical content |
|------|------|-----------------|
| `prompt` | Supplemental prompt notes only | Narrow behavioral policy addendums |
| `memory` | Durable facts and preferences | Decisions, failures, outcomes, user preferences |
| `skill` | Reusable Python call description | `reference` + `arguments` contract for an installed or planned callable |
| `subagent` | Reusable delegation role | Purpose, instructions, when to spawn via `await rlm("…")` |

Each entry is a `HarnessEntry` with stable fields: `id`, `kind`, `title`, `content`, `path` (grouping, default `general`), `scope` (`local` \| `global`), `reference`, `arguments`, `metadata`, `source`, `created_at`, `updated_at`, `version`.

Skill create/update edits require a Python `reference` (`type: "python"`, import, and `callable` or `call_pattern`) and an `arguments` object (use `{}` only when the callable needs no inputs). Subagent specs document the native spawn form: admission via `handle = await rlm("sub-task")` returns `rlm_child_id`, `name`, `session_dir`, and `model`—not the child’s answer; results come from `agent_message` or files.

```text
HarnessState (schema: 1)
├── entries
│   ├── prompt:   { id → HarnessEntry }
│   ├── memory:   { id → HarnessEntry }
│   ├── skill:    { id → HarnessEntry }
│   └── subagent: { id → HarnessEntry }
└── refinements[]  (id, trigger, changes, evidence, outcome, created_at)
```

## Local vs global scope

| Scope | Default store | Blast radius |
|-------|---------------|--------------|
| `local` | Session artifact dir: `…/session-artifacts/<session-id>/harness/harness_state.json` | Current session only |
| `global` | `~/.prime/agent/harness/` (or `PRIME_AGENT_CODING_AGENT_DIR` / `PI_CODING_AGENT_DIR`) | Cross-session |

- Local is the default for refinement and for session-specific progress, blockers, and current-run coordination.
- Global is for stable cross-session lessons, durable preferences, reusable skills/subagents, or explicitly project-qualified facts.
- One refinement applies only to the requested scope’s store. During a local refine, global entries are read-only context (no update/delete of global ids; create a local entry for overrides).
- Merged overview ids may show display prefixes `local:` / `global:`; edit payloads always use bare ids.

Local refinement requires a **persisted** session (local harness directory). Non-persistent sessions must use global refinement or cannot refine locally.

:::files
~/.prime/agent/
  harness/
    harness_state.json
    refinements.jsonl          # global refinement history for rollback
  session-artifacts/
    <root-session-id>/
      harness/
        harness_state.json     # local session harness
  sessions/
    <root-session-id>.jsonl    # session custom entries include prime-agent.refinement
:::

Python path resolution also honors `RLM_HARNESS_STATE_DIR` / `RLM_GLOBAL_HARNESS_STATE_DIR` and, for local, `RLM_SESSION_DIR/harness/` when those env vars are set by the host.

## Immutable base system prompt

The base RLM system prompt is **not** editable through the harness. Prompt-kind entries are supplemental notes only; refine guidance explicitly forbids rewriting the base prompt. After a successful apply, the session rebuilds `_baseSystemPrompt` and reassigns `agent.state.systemPrompt` so the next turn sees the updated compact harness overview without changing the immutable base template.

Custom `customPrompt` options still compose with harness injection when `harnessState` is passed into `buildSystemPrompt`.

## How state reaches the model

`formatHarnessStateForPrompt` injects a compact **Continual Harness State** section into the system prompt: counts and short summaries per kind (default 6 entries per kind, 180-character content clips), recent refinements, and call-contract guidance for IPython vs shell-only sessions. Full entry bodies stay on disk; the overview is a routing/context hint.

Kernel and host both degrade corrupt `harness_state.json` to empty state rather than failing the session; the next successful save rewrites a clean file.

## Mutation surfaces

### User and host: `/refine`

Session slash command (also available in Agents View and RPC):

| Form | Effect |
|------|--------|
| `/refine` | Plan and apply evidence-backed create/update/delete edits (local by default) |
| `/refine <instructions>` | Focus the planner with free-text instructions |
| `/refine --global …` | Target the global harness store |
| `/refine rollback <refinement-id>` | Invert a prior refinement from recorded before/after entry snapshots |
| `/refine rollback <id> --global` | Rollback routing with global flag (path/scope still resolved from history) |

RPC wire shape: `{ type: "refine", instructions?, rollbackId?, global? }`.

### Agent: `refine` skill (IPython)

The bundled `refine` skill is a thin host bridge over the same host implementation as `/refine`. It never runs mid-cell.

```python
await refine.status()
# → {"pending": bool, "in_flight": bool}

await refine.run()
await refine.run("create a memory about always checking git status before committing")
await refine.run("promote the error-handling pattern to a global skill", global_=True)
# → {"scheduled": True, "note": "..."}  or  {"scheduled": False, "reason": "..."}
```

| Constraint | Behavior |
|------------|----------|
| Active turn required | `refine.run` while not streaming returns `scheduled: false` (`no active turn…`) |
| Schedule timing | Runs when the current turn ends; system prompt rebuilds; agent resumes |
| One request per turn | A second `run` before turn end **updates** instructions (and keeps prior `global` if not re-specified) |
| Serialized mode | A new request can abort/invalidate an in-flight background plan and replace pending options |

### Kernel: `rlm.harness` CRUD

The runtime exposes explicit methods (host bootstrap asserts these exist):

- `create_memory` / `update_memory` / `delete_memory`
- `create_skill` / `update_skill` / `delete_skill`
- `create_subagent` / `update_subagent` / `delete_subagent`
- `create_prompt_note` / `update_prompt_note` / `delete_prompt_note`
- `record_refinement`
- `get_harness_state(global_=…)` / `overview(…)`

The Python store reloads when on-disk mtime changes so host `/refine` writes are not overwritten by a stale in-kernel cache.

### Harness skill entry vs installed skill package

| Surface | What it is |
|---------|------------|
| Installed skill | Real package under project/personal skills paths with `SKILL.md` (and usually Python sources) |
| Continual harness `skill` entry | Persisted description of a reusable Python call (`reference` + `arguments`) |

`/refine` can create or update harness skill **descriptions** after a repeated procedure emerges. Packaging new executable functionality still uses skill creation (`skill-creator` / install paths), not refine alone.

## Refine lifecycle

```mermaid
sequenceDiagram
  participant User as User or refine.run
  participant Session as AgentSession
  participant Planner as planRefinement
  participant Disk as harness_state.json
  participant Prompt as system prompt

  User->>Session: /refine or scheduled refine
  Note over Session: Planning is backgrounded<br/>(does not block new turns)
  Session->>Disk: load state + history
  Session->>Planner: trajectory + harness overview
  Planner-->>Session: RefinementPlan (or rollback proposal)
  Note over Session: Apply waits for agent idle,<br/>event queue, compaction
  Session->>Disk: re-read target store
  Session->>Disk: apply create/update/delete
  Session->>Disk: saveHarnessState (atomic tmp+rename)
  Session->>Session: append prime-agent.refinement
  Session->>Prompt: rebuild base+harness overview
  Session-->>User: refine_complete (or refine_failed)
```

**Plan vs apply**

1. **Plan** (`planRefinement`): LLM emits JSON `{ summary, rationale, expectedOutcome, edits[] }`, or builds a rollback proposal from history. Conversation slice is truncated (~80k chars). Output budget is `min(model.maxTokens, 32000)`.
2. **Apply** (`applyRefinementProposal`): Re-reads the target store immediately before mutation so concurrent kernel writes during the long LLM pass are not clobbered. Baseline-aware conflict check rejects edits when an entry changed under the planner (`entry changed during refinement planning`).
3. **Persist**: Atomic write (`*.tmp` → rename, mode preserved or `0o600`). Global refinements also append to `refinements.jsonl`. Every result is stored as a session custom entry `prime-agent.refinement`.

**Serialization**

- Concurrent public `refine()` calls wait on in-flight plan/apply.
- Only the apply critical section blocks new turn entry (`_waitForRefineIdle`); planning does not.
- Headless/print/json/rpc paths can enable **serialized refine**: at most one primary+refine model request at a time; checkpoint may run refine once per interval inside the agent loop.

## Rollback snapshots

Rollback is not a full-file restore of arbitrary history; it inverts a recorded `RefinementResult`:

1. Locate refinement by id in merged history (global `refinements.jsonl` + session custom entries; session wins on id conflict).
2. Build reverse edits from each **applied** edit’s `before` / `after` snapshots (delete ↔ recreate, update ↔ restore fields).
3. Apply into the store resolved from `harnessStatePath` / scope (legacy records without scope honor the recorded path).
4. Missing local state file for a local rollback raises a clear error naming the path.

Unapplied edits (validation failures) are skipped in the reverse pass.

## Auto-refine

Settings (`autoRefine` on session settings; resolved by `getAutoRefineSettings()`):

| Key | Default | Meaning |
|-----|---------|---------|
| `enabled` | `true` | Auto review/refine loop on (set `false` to opt out) |
| `turnInterval` | `25` | Assistant turns between auto-refine reviews (min 1; non-numeric → default) |
| `compact` | `true` | Also consider refine after compaction checkpoints |
| `cooldownMs` | `20 * 60_000` | Minimum ms between auto-refine reviews (min 0) |

Auto-refine uses a separate reviewer JSON gate (`shouldRefine`, `rationale`, optional `instructions`) and **defaults to local** harness edits. Instructions push empty edits over speculative memories and avoid global promotion unless explicitly requested. Provider credentials still come from the host’s BYOK/BYOC auth path—no fixed model vendor is required.

## Events and ACP

Session events include `refine_complete` (with `RefinementResult`) and `refine_failed`. In ACP mode, refinement status is not a native ACP field; it travels in the namespaced `_meta` envelope (`ai.primeintellect.prime-agent`) as refinement status metadata that vanilla ACP clients ignore.

## Common errors and constraints

| Situation | Result |
|-----------|--------|
| Local refine without persisted session | Error: local harness requires persisted session; use global |
| `refine.run` outside active turn | `{ scheduled: false, reason: "no active turn…" }` |
| Invalid `instructions` / `global` types | Host throws type errors |
| Rollback id missing | `Refinement ${id} not found` |
| Entry race during plan→apply | Edit fails: `entry changed during refinement planning` |
| Skill missing `reference` / `arguments` | Validation error on apply |
| Truncated refine JSON | Explicit budget-exhausted style error (not opaque parse-only failure) |
| Corrupt harness file | Load as empty; next save rewrites |

## When to refine

Use refine after: repeated failures, reusable tactics, repeated delegation roles → subagent specs, repeated procedures → skill entries, durable facts/preferences → memory, narrow policies → prompt notes, user corrections that should persist, or validation that shows a harness entry is wrong. Prefer the smallest component and evidence-backed edits; empty `edits` is better than speculative state.

## Related pages

<CardGroup>
  <Card title="Refine harness state" href="/refine-harness">
    Run /refine, apply evidence-backed updates, serialize refine, and roll back with snapshots.
  </Card>
  <Card title="RLM control plane" href="/rlm-control-plane">
    Persistent IPython control tool, prompt-as-variable context, and rlm(...) subagents.
  </Card>
  <Card title="Skills model" href="/skills-model">
    Installed Python skill packages vs harness skill entries, collision rules, and scopes.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, session-scoped vs durable state, and artifact layout.
  </Card>
  <Card title="Built-in skills" href="/builtin-skills">
    Catalog including refine, goal, compact, heartbeat, and related host bridges.
  </Card>
  <Card title="Session configuration" href="/session-configuration">
    autoRefine and other session config keys, defaults, and reload behavior.
  </Card>
</CardGroup>
