# Refine harness state

> Run /refine against the current trajectory, apply evidence-backed harness updates, serialize refine, and use snapshots for rollback.

- 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/src/core/agent-session.ts`

---

---
title: "Refine harness state"
description: "Run /refine against the current trajectory, apply evidence-backed harness updates, serialize refine, and use snapshots for rollback."
---

`/refine` and the bundled `refine` skill plan small create/update/delete edits against the continual harness (prompt notes, memories, skill specs, subagent specs) from the current conversation trajectory, then apply them only after the turn is idle. The base system prompt is never rewritten. Local edits land in the session artifact store; global edits land under the agent harness directory and are logged for cross-session rollback.

## What refine does

Refinement is a host-side subsystem in `AgentSession` and `packages/coding-agent/src/core/refinement/`. It:

1. Reads the trajectory, merged harness overview, and prior refinement history.
2. Emits a JSON proposal of focused harness edits (or an empty `edits` array).
3. Re-loads the target harness file, applies edits with before/after snapshots, persists state, appends a refinement record, and rebuilds the system prompt.

It does **not** edit source files, package new skill packages on disk, or change the trained base system prompt. Harness skill entries are description/reference contracts for Python calls; packaging new code still uses skill authoring tools.

```mermaid
sequenceDiagram
  participant User as User or IPython
  participant Session as AgentSession
  participant Plan as planRefinement
  participant Disk as harness_state.json

  User->>Session: /refine or refine.run
  Note over Session: Schedule only while a turn is active
  Session->>Plan: Background plan (LLM or rollback)
  Plan-->>Session: RefinementPlan + baselineState
  Session->>Session: Wait for agent idle
  Session->>Disk: Re-load target store
  Session->>Disk: apply + saveHarnessState
  Session->>Session: append refinement record, rebuild system prompt
  Session-->>User: refine_complete or refine_failed
```

## Surfaces

| Surface | Entry | Behavior |
| --- | --- | --- |
| Slash command | `/refine [instructions]` | Session command; optional focus text |
| Global scope | `/refine --global [instructions]` | Target global harness store |
| Rollback | `/refine rollback <refinement-id>` | Rebuild inverse edits from recorded snapshots |
| Global rollback | `/refine rollback <id> --global` or `--global` prefix | Same parser; scope resolved from history when possible |
| IPython skill | `await refine.run(...)` / `await refine.status()` | Host bridge; schedules, does not run mid-cell |
| Connection API | `connection.refine({ instructions?, rollbackId?, global? })` | Daemon / in-process / RPC clients |
| Session method | `session.refine(options)` | Plan in background; apply when idle |
| Events | `refine_complete`, `refine_failed` | Session listeners and extension hook on success |

### Slash command

```text
/refine
/refine capture git status before every commit
/refine --global promote error-handling pattern to a reusable skill
/refine rollback refine_20260806123045000
/refine rollback refine_20260806123045000 --global
```

Parser rules (`parseRefineCommandOptions`):

- Optional leading `--global` sets global scope.
- `rollback` without an id errors: `Usage: /refine rollback <refinement-id>`.
- Trailing `--global` after the rollback id is also accepted.
- Any other non-empty remainder is treated as free-text `instructions`.

### Kernel skill (`refine`)

```python
await refine.status()
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)
```

| Call | Returns | Notes |
| --- | --- | --- |
| `status()` | `{ pending, in_flight }` | `pending` = request queued for this turn; `in_flight` = plan or apply active (including serialized background plan) |
| `run(instructions=None, global_=False)` | `{ scheduled: True, note: ... }` or `{ scheduled: False, reason: ... }` | Python keyword is `global_` because `global` is reserved |

Host request types: `refine.status`, `refine.run`. Unknown types throw. Handlers register only when auto-refine is allowed for the session (`rlmDepth === 0` and a local harness directory exists from a persisted session).

## Prerequisites and constraints

- **Root session only for skill/auto-refine:** `rlmDepth === 0`. Subagents do not register `refine.*` host handlers.
- **Persisted session for local scope:** Local refine needs a session artifact (or RLM session) dir. Without it, planning fails with: local harness refinement requires a persisted session; use global refinement instead.
- **Active turn for `refine.run`:** Scheduling while not streaming returns `scheduled: false` with reason `no active turn; refine can only be requested while a turn is running`.
- **One coalesced request per turn:** A second `run` before the turn ends updates instructions/global flags; it does not queue two refinements.
- **Never mid-cell:** Application waits until the turn ends and the agent is idle, then rebuilds the system prompt and continues.
- **Base prompt immutable:** Edits targeting id `base_system_prompt` are rejected.
- **Scope isolation:** One refinement writes only the requested scope store. During local refine, global entries are read-only context; propose a local override instead of updating global ids.

## Workflow

<Steps>
  <Step title="Observe a durable lesson">
    Trigger refine after a repeated failure, reusable tactic, delegation role, or behavior policy. Prefer a focused memory, skill, prompt note, or subagent spec over rewriting large harness sections.
  </Step>
  <Step title="Schedule refinement">
    From chat: `/refine …` or `/refine --global …`. From IPython during a turn: `await refine.run(...)`. Check `await refine.status()` if you need `pending` / `in_flight`.
  </Step>
  <Step title="Plan against trajectory">
    Host runs `planRefinement`: conversation slice (up to 80k chars), harness overview, refinement history, and scope policy. Auto-refine may run a cheaper review gate first (`shouldRefine` + optional instructions). Explicit `/refine` and `refine.run` skip that gate.
  </Step>
  <Step title="Apply when idle">
    Apply re-reads the target `harness_state.json`, strips display prefixes `local:` / `global:` from edit ids, runs `applyRefinementProposal` with a planning baseline (rejects edits whose entries changed during planning), saves state, records history, rebuilds the system prompt, and emits `refine_complete`.
  </Step>
  <Step title="Validate and roll back if needed">
    Confirm harness entries and next-turn behavior. If a refinement is harmful: `/refine rollback <refinement-id>` (or the connection/API equivalent). Rollback rebuilds inverse create/update/delete edits from each applied edit's `before` / `after` snapshot.
  </Step>
</Steps>

## Edit model

### Kinds

| Kind | Purpose | Create/update requirements |
| --- | --- | --- |
| `prompt` | Supplemental behavioral notes only | `title`, `content` |
| `memory` | Durable facts, decisions, failures, preferences | `title`, `content` |
| `skill` | Python REPL skill contract | `title`, `content`, `arguments`, `reference` with `type: "python"`, import, and callable or `call_pattern` |
| `subagent` | Reusable delegation role/spec | `title`, `content` |

Actions: `create` | `update` | `delete`. Delete requires `id`. Create may omit `id` (slug from title). Source on written entries is `"refine"`.

### Proposal JSON shape

The planner must return JSON only:

```json
{
  "summary": "one sentence",
  "rationale": "why these edits are justified by trajectory evidence",
  "expectedOutcome": "what should improve and how to validate it",
  "edits": [
    {
      "action": "create",
      "kind": "memory",
      "title": "Check git status before commit",
      "content": "Always run git status before committing.",
      "path": "git",
      "reason": "Agent committed without checking dirty tree twice"
    }
  ]
}
```

Empty `edits` with a rationale is valid when nothing should change.

### Scope policy

| Scope | Store | Use for |
| --- | --- | --- |
| `local` (default) | `session-artifacts/<session-id>/harness/harness_state.json` | Session progress, temporary blockers, current-run coordination, non-reusable project facts |
| `global` | `~/.prime/agent/harness/harness_state.json` (via `getAgentDir()/harness`) | Stable cross-session lessons, durable preferences, reusable skills/subagents, explicitly project-qualified facts |

Merged overview for prompts overlays local on global; colliding local ids are shown with a `local:` prefix for display only—edits must use bare ids.

## Persistence and snapshots

:::files
~/.prime/agent/
  harness/
    harness_state.json          # global store
    refinements.jsonl           # global refinement results (rollback log)
  sessions/
    <session-id>.jsonl          # custom type prime-agent.refinement
  session-artifacts/
    <session-id>/
      harness/
        harness_state.json      # local store
:::

| Artifact | Contents |
| --- | --- |
| `harness_state.json` | `schema`, `entries.{prompt,memory,skill,subagent}`, `refinements[]` event log |
| Applied edit snapshots | Each applied edit records `before` / `after` harness entry clones |
| Session custom entry | `prime-agent.refinement` → full `RefinementResult` |
| Global history | `refinements.jsonl` append-only for global results |

`saveHarnessState` writes via temp file + rename with mode `0o600` (or existing mode). Corrupt state files load as empty rather than crashing the session.

### Rollback mechanics

Given a target refinement id:

1. History is the merge of global `refinements.jsonl` and session custom entries.
2. `rollbackProposal` walks applied edits in reverse: restore `before` via create/update, or delete if there was only `after`.
3. Apply targets the scope inferred from the result (or `harnessStatePath` for legacy local records pointing at global).
4. Missing local state file fails with a clear path error.
5. Result sets `rollbackOf` to the original id.

## Serialized refine vs interactive refine

| Mode | When | Timing |
| --- | --- | --- |
| Interactive (`serializedRefine: false`) | Default TUI / interactive; daemon worker default | Explicit `refine.run` schedules and runs after turn end; auto-refine often after `agent_end` |
| Serialized (`serializedRefine: true`) | Print/JSON/headless (`appMode !== "interactive" && !== "daemon"`) | Plan may start at assistant `message_end` (overlaps tools); apply runs at `shouldStopAfterTurn` so refine never overlaps the primary model call |

Runtime config field:

```ts
// AgentSessionRuntimeConfig
serializedRefine?: boolean;
```

Serialized checkpoint guarantees (from tests and session code):

- At most one concurrent primary/refine model path at the boundary.
- Explicit `refine.run` takes priority over interval auto-refine and skips the review gate.
- A replacement `refine.run` aborts/invalidates an in-flight serialized plan.
- Failure stamps cooldown and does not synchronously retry the same interval review.
- Pending refine is drained before dispose when possible.
- Prompt/model state from the refined harness is visible on the resumed turn.

## Auto-refine

Settings object `autoRefine` (defaults applied in `SettingsManager.getAutoRefineSettings()`):

| Key | Default | Meaning |
| --- | --- | --- |
| `enabled` | `true` | Master switch |
| `turnInterval` | `25` | Assistant turns between interval reviews (min 1) |
| `compact` | `true` | Allow post-compaction auto-refine |
| `cooldownMs` | `20 * 60_000` | Cooldown after review/failure (min 0) |

Triggers: `turn_interval` and `compact`. Auto-refine review returns:

```json
{
  "shouldRefine": true,
  "rationale": "short reason",
  "instructions": "optional instructions for /refine"
}
```

Default auto scope is **local**. Review should only ask for global when lessons are durable and cross-session. Auto-refine is skipped when `rlmDepth > 0` or there is no local harness dir.

## Result and events

### `RefinementResult`

| Field | Type | Meaning |
| --- | --- | --- |
| `id` | `string` | e.g. `refine_<timestamp>` |
| `summary` | `string` | One-line summary |
| `rationale` | `string` | Evidence justification |
| `expectedOutcome` | `string` | Validation expectation |
| `appliedEdits` | array | Each edit with `applied`, optional `error`, `before`/`after` |
| `harnessStatePath` | `string` | Path written |
| `rollbackOf` | `string?` | Set when this result is a rollback |
| `scope` | `"local" \| "global"?` | Effective store |

### Session events

```ts
{ type: "refine_complete"; result: RefinementResult }
{ type: "refine_failed"; error: string }
```

Extension emit on success: `{ type: "refine_complete", id, summary, appliedEdits, scope }`. Listener/extension failures after a successful persist do not convert success into failure.

### Host schedule responses

```json
// success
{ "scheduled": true, "note": "Refinement runs when the current turn ends; ..." }

// no active turn
{ "scheduled": false, "reason": "no active turn; refine can only be requested while a turn is running" }

// status
{ "pending": true, "in_flight": false }
```

## Failure modes

| Symptom | Likely cause | What to do |
| --- | --- | --- |
| `scheduled: false` / no active turn | `refine.run` outside a streaming turn | Call during tool/IPython work inside a turn, or use `/refine` between turns |
| Local refine requires persisted session | Ephemeral / non-persisted session | Persist the session, or use `--global` / `global_=True` |
| Handlers missing | `rlmDepth > 0` or no local harness dir | Refine from the root session only |
| `Refinement <id> not found` | Unknown rollback id | Check session custom entries / global `refinements.jsonl` |
| Local state file not found on rollback | Artifact deleted or path moved | Restore artifact or accept that local rollback is unavailable |
| Entry changed during planning | Concurrent harness write (e.g. `rlm.harness`) | Retry refine; apply re-reads disk and uses baseline conflict checks |
| Planning JSON truncated / parse error | Model hit output budget | Narrow instructions; retry; refine forces non-reasoning complete for JSON |
| `refine_failed` event | Plan/apply error | Inspect `error` string; interval auto-refine will cool down rather than tight-loop |
| Speculative auto-refine noise | Auto-refine too aggressive | Set `autoRefine.enabled: false` or raise `turnInterval` / `cooldownMs` |

## Configuration and SDK notes

- **BYOK/BYOC:** Refine uses the session's currently selected model and provider credentials (`_getRequiredRequestAuth`). No hosted refine service is required.
- **SDK:** Pass `serializedRefine` through session runtime config when building headless agents that must keep refine off the primary model call path.
- **Model output:** Refine and auto-review use `completeSimple` with capped max tokens (`min(model.maxTokens, 32000)` for refine, `4096` for review) and intentionally non-reasoning completion so final text remains parseable JSON.

## Related pages

<CardGroup>
  <Card title="Continual Harness" href="/continual-harness">
    Durable harness entries, merge rules, immutable base prompt, and store layout.
  </Card>
  <Card title="RLM control plane" href="/rlm-control-plane">
    IPython as control tool, host bridges, and how skills call into the host.
  </Card>
  <Card title="Built-in skills reference" href="/builtin-skills">
    Catalog entry for the refine skill and related host-bridge skills.
  </Card>
  <Card title="Long-running tasks" href="/long-running-tasks">
    Goals, compaction, heartbeats, and autonomous loops that interact with refine boundaries.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, turn boundaries, and where pending refine is consumed.
  </Card>
  <Card title="Session configuration" href="/session-configuration">
    Runtime config keys including `serializedRefine` and related session options.
  </Card>
</CardGroup>
