pi-continual-harness 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -440,7 +440,9 @@ Then `"my-proposer"` is selectable via `/refine --proposer my-proposer` or
440
440
  - **Boundary delivery for auto-refine**: the steering prompt is persisted as a
441
441
  structural entry with `{ entries, continue: true }` at `turn_end` (no
442
442
  synthetic user message, no follow-up-scheduling side effects); manual
443
- `/refine` still steers via a user message.
443
+ `/refine` still steers via a user message. Each new boundary also omits
444
+ prior auto-refine request drafts from future provider context (pi ≥ 0.87
445
+ context edits; `autoRefine.omitStaleDrafts: false` to keep them).
444
446
 
445
447
  Open extension points (see `docs/ROADMAP.md`):
446
448
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-continual-harness",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Online self-improvement layer for pi. A unified, in-trajectory harness-state store (prompt notes, memory, skill descriptions, sub-agent specs) with a manual /refine that proposes evidence-backed structured CRUD deltas. Composes with pi-reflect (offline) and pi-mem (storage); owns only the online optimizer layer. Inspired by Continual Harness (arXiv 2605.09998) and ACE (arXiv 2510.04618).",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -75,6 +75,39 @@ export function buildRefineDraft(steeringMessage: string): {
75
75
  return { type: "custom_message", customType: AUTO_REFINE_ENTRY, content: steeringMessage, display: true };
76
76
  }
77
77
 
78
+ /** Structural slice of pi's ProjectedSessionEntry (sourceEntry + messages). */
79
+ export interface ProjectedLike {
80
+ sourceEntry?: { type?: string; customType?: string; id?: string };
81
+ messages?: unknown[];
82
+ }
83
+
84
+ /** A context_edit boundary draft (pi ≥ 0.87): omit one entry from future
85
+ * provider context — raw history, usage, and UI history stay untouched. */
86
+ export interface OmissionDraft {
87
+ type: "context_edit";
88
+ targetId: string;
89
+ replacement: null;
90
+ }
91
+
92
+ /**
93
+ * Context-edit drafts omitting every LIVE prior auto-refine request from
94
+ * future provider context (pi ≥ 0.87 ContextEditEntry). Invariant: at most
95
+ * ONE auto-refine request is ever model-visible — the newest. Already-omitted
96
+ * drafts surface in the projection with empty `messages` and are skipped, so
97
+ * the operation is idempotent and never stacks redundant edits. Raw history
98
+ * (and the HTML export) keeps every draft: /tree rollback still applies.
99
+ */
100
+ export function buildOmissionDrafts(projected: Iterable<ProjectedLike>): OmissionDraft[] {
101
+ const drafts: OmissionDraft[] = [];
102
+ for (const p of projected) {
103
+ const src = p.sourceEntry;
104
+ if (src?.type !== "custom_message" || src.customType !== AUTO_REFINE_ENTRY) continue;
105
+ if ((p.messages?.length ?? 0) === 0) continue; // already omitted → skip
106
+ if (typeof src.id === "string") drafts.push({ type: "context_edit", targetId: src.id, replacement: null });
107
+ }
108
+ return drafts;
109
+ }
110
+
78
111
  /** Subscribe to turn_end and run /refine on the configured cadence. */
79
112
  export function registerAutoRefine(pi: ExtensionAPI): void {
80
113
  pi.on("turn_end", async (event, ctx) => {
@@ -118,7 +151,19 @@ export function registerAutoRefine(pi: ExtensionAPI): void {
118
151
  pi.sendUserMessage(result.steeringMessage);
119
152
  return;
120
153
  }
121
- return { entries: [...event.entries, buildRefineDraft(result.steeringMessage)], continue: true };
154
+ // Hygiene (pi ≥ 0.87 context edits): omit every LIVE prior auto-refine
155
+ // request from future provider context, so at most one draft (the new
156
+ // one) is ever model-visible. Already-omitted drafts are skipped by
157
+ // buildOmissionDrafts; raw history and /tree rollback are untouched.
158
+ // Kill-switch: autoRefine.omitStaleDrafts: false.
159
+ const omissions =
160
+ config.autoRefine?.omitStaleDrafts === false
161
+ ? []
162
+ : buildOmissionDrafts(event.context?.contextEntries ?? []);
163
+ return {
164
+ entries: [...event.entries, ...omissions, buildRefineDraft(result.steeringMessage)],
165
+ continue: true,
166
+ };
122
167
  }
123
168
  } catch (err) {
124
169
  ctx.ui.notify(`Auto-refine failed: ${(err as Error).message}`, "error");
package/src/config.ts CHANGED
@@ -62,6 +62,11 @@ export interface HarnessConfig {
62
62
  * trajectory contains a durable correction; skip the refine when it says
63
63
  * no. Soft-fails to the plain cadence on classifier errors. Default false. */
64
64
  gate?: boolean;
65
+ /** Boundary-delivery hygiene (pi ≥ 0.87 context edits, default true): omit
66
+ * prior auto-refine request drafts from future provider context so at most
67
+ * one (the newest) is ever model-visible. Raw history and /tree rollback
68
+ * are untouched; set false to keep every draft in context. */
69
+ omitStaleDrafts?: boolean;
65
70
  };
66
71
  /** Classifier model for the cheap gated paths (pi ≥ 0.99): "provider/id" or
67
72
  * a bare id of a classifier catalog entry (e.g. "typesafe/jev-latest" or a
@@ -92,7 +97,7 @@ export const DEFAULT_CONFIG: HarnessConfig = {
92
97
  durableScope: "global",
93
98
  autoImport: false,
94
99
  remindRefine: { enabled: false, everyTurns: DEFAULT_EVERY_TURNS },
95
- autoRefine: { enabled: false, everyTurns: DEFAULT_AUTO_EVERY_TURNS, commit: false, gate: false },
100
+ autoRefine: { enabled: false, everyTurns: DEFAULT_AUTO_EVERY_TURNS, commit: false, gate: false, omitStaleDrafts: true },
96
101
  classifier: { model: undefined },
97
102
  virtualBinding: "virtual",
98
103
  proposer: "steering",
@@ -125,6 +130,7 @@ function mergeConfig(over: Partial<HarnessConfig>): HarnessConfig {
125
130
  everyTurns: over.autoRefine?.everyTurns ?? DEFAULT_AUTO_EVERY_TURNS,
126
131
  commit: over.autoRefine?.commit ?? false,
127
132
  gate: over.autoRefine?.gate === true,
133
+ omitStaleDrafts: over.autoRefine?.omitStaleDrafts !== false,
128
134
  },
129
135
  classifier: {
130
136
  model: typeof over.classifier?.model === "string" && over.classifier.model ? over.classifier.model : undefined,
package/src/index.ts CHANGED
@@ -139,3 +139,14 @@ export {
139
139
  type ClassifyBoolResult,
140
140
  type ClassifyFn,
141
141
  } from "./classify.js";
142
+
143
+ // Auto-refine boundary helpers (pi ≥ 0.87, 0.12.0–0.13.0): the steering draft
144
+ // and the stale-draft context-edit omissions. Re-exported for tests and
145
+ // companion packages without reaching into internal paths.
146
+ export {
147
+ AUTO_REFINE_ENTRY,
148
+ buildOmissionDrafts,
149
+ buildRefineDraft,
150
+ type OmissionDraft,
151
+ type ProjectedLike,
152
+ } from "./auto-refine.js";