Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
59 kB · 1408 lines
TypeScript
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";import { join } from "node:path";import type { AgentMessage, ThinkingLevel } from "@earendil-works/pi-agent-core";import type { Api, Model } from "@earendil-works/pi-ai";import { completeSimple } from "@earendil-works/pi-ai";import { getAgentDir } from "../../config.js";import { realpathIfPresentSync, writeFileAtomicSync } from "../../utils/atomic-file.js";import { serializeConversation } from "../compaction/utils.js";import { convertToLlm } from "../messages.js";import { completeWithProviderRetry, type ProviderRetryPolicy } from "../provider-retry.js";import type { CustomEntry } from "../session-manager.js";import { getAuxiliaryThinkingLevel } from "../thinking-levels.js";
export const REFINEMENT_CUSTOM_TYPE = "prime-agent.refinement";
export const REFINE_SKILL_NAME = "refine";const HARNESS_STATE_DIR_NAME = "harness";const REFINEMENT_HISTORY_FILE_NAME = "refinements.jsonl";const DEFAULT_OVERVIEW_ENTRY_LIMIT = 6;const DEFAULT_OVERVIEW_REFINEMENT_LIMIT = 5;const DEFAULT_OVERVIEW_CONTENT_LIMIT = 180;const DEFAULT_OVERVIEW_DETAIL_BUDGET = Number.POSITIVE_INFINITY;const DEFAULT_OVERVIEW_LISTED_LIMIT = 200;const DEFAULT_OVERVIEW_LISTED_CHAR_BUDGET = 8000;const OVERVIEW_STUB_LINE_LIMIT = 160;const OVERVIEW_STUB_MIN_REMAINDER = 4;const OFFSETLESS_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/;
export type RefinementKind = "prompt" | "memory" | "skill" | "subagent";export type RefinementAction = "create" | "update" | "delete";export type HarnessScope = "local" | "global";
export interface HarnessEntry { id: string; kind: RefinementKind; title: string; content: string; path: string; scope?: HarnessScope; reference: Record<string, unknown>; arguments: Record<string, unknown>; metadata: Record<string, unknown>; source: string; created_at: string; updated_at: string; version: number; violations?: number;}
export interface HarnessRefinementEvent { id: string; trigger: string; changes: string[]; evidence: string; outcome: string; created_at: string;}
export interface HarnessState { schema: number; entries: Record<RefinementKind, Record<string, HarnessEntry>>; refinements: HarnessRefinementEvent[];}
export interface RefinementEdit { action: RefinementAction; kind: RefinementKind; id?: string; title?: string; content?: string; path?: string; reference?: Record<string, unknown>; arguments?: Record<string, unknown>; metadata?: Record<string, unknown>; reason?: string; violations?: number;}
export interface RefinementProposal { summary: string; rationale: string; edits: RefinementEdit[]; expectedOutcome: string;}
export interface AppliedRefinementEdit extends RefinementEdit { id: string; before?: HarnessEntry; after?: HarnessEntry; applied: boolean; error?: string;}
export interface RefinementResult { id: string; summary: string; rationale: string; expectedOutcome: string; appliedEdits: AppliedRefinementEdit[]; harnessStatePath: string; rollbackOf?: string; scope?: HarnessScope;}
export interface RefineOptions { instructions?: string; rollbackId?: string; global?: boolean; retry?: ProviderRetryPolicy;}
export type AutoRefineReason = "turn_interval" | "compact";
export interface AutoRefineReviewContext { reason: AutoRefineReason; turnsSinceLastReview: number;}
export interface AutoRefineReview { shouldRefine: boolean; rationale: string; instructions?: string;}
const REFINEMENT_SYSTEM_PROMPT = `You are Prime Agent's /refine continual harness subsystem.
Your job is to improve the editable continual harness state from the current trajectory.This is similar in spirit to context compaction, but instead of summarizing theconversation you emit precise Create, Update, or Delete edits to reusable state.The continual harness is the persistent, editable set of prompt notes, memories,skills, and subagent specs that lets Prime Agent improve reusable behavioroutside the token history.Use "continual harness" for that persistent artifact layer; keep "RLM" for theruntime, Python REPL kernel, and native call interface that executes those artifacts.
Continual harness components:- prompt: supplemental prompt notes only. The base system prompt is immutable and MUST NOT be rewritten.- memory: durable facts, decisions, failures, preferences, and outcomes.- skill: installed Python REPL skill. Skill create/update edits MUST include a \`reference\` object with \`{"type":"python"}\`, a Python import, and a callable or call pattern; they also MUST include an \`arguments\` object describing accepted inputs, required fields, defaults, and constraints. Use \`{}\` for \`arguments\` only when the Python callable truly needs no external inputs. Include the RLM-native call form \`await <skill_import>(...)\`.- subagent: reusable delegation specs, including purpose, instructions, and when to invoke. Include the RLM-native call form: compose a concise task prompt and spawn with \`handle = await rlm.spawn("sub-task", name="worker", model="<model>")\`; call \`await rlm.models()\` first to inspect available models; name and model are required (thinking= is optional), and admission returns immediately with \`rlm_child_id\`, \`name\`, \`session_dir\`, and \`model\`, never the child's answer. Results arrive only through explicit \`agent_message\` replies or files; children reply with \`await agent_message.send(message, receiver_role="parent")\`. Use \`await rlm.list_subagents()\` to recover direct child handles and \`await agent_message.send(..., receiver_role="child", receiver_name=handle.name)\` for follow-ups. Do not invent wrappers like \`run_subagent(...)\`.
Scope and persistence policy:- The default editable continual harness store is local to the current Prime Agent session. Use it for session-specific progress, active task state, current-run coordination notes, temporary blockers, and project facts that should not affect other sessions.- A caller may explicitly request global refinement. Global edits must be stable cross-session lessons, durable user preferences, reusable skills/subagents, or tool/environment facts that should affect future sessions.- Entry ids in the harness overview may carry a display-only \`local:\` or \`global:\` prefix. Always use the bare id (no prefix) in edits.- All edits in one refinement apply only to the requested scope's store. During a local refinement, global entries are read-only context: never propose update or delete edits for them; create a local entry instead when a session-specific override is genuinely needed.- Project/workspace-specific lessons may be persisted globally only when the title, path, or content explicitly names the project/workspace and the lesson is likely to be reused in future sessions for that project. Prefer local edits when the lesson only belongs in the current conversation.- Use memory for declarative facts and preferences, skill for repeatable procedures exposed as Python calls, prompt for narrow behavioral policy addendums, and subagent for reusable delegation roles.- Create or update the smallest relevant component: repeated delegation roles should become subagent specs, repeated procedures should become skills, durable facts/preferences should become memories, and narrow behavioral policies should become prompt addendums.- When an edit is persisted, include metadata such as \`{"scope":"local"}\` or \`{"scope":"global"}\` when that helps future review understand the intended blast radius.- Violation escalation: When an entry displays repeated violations (\`N violations\` in the overview), do NOT re-word the note again. Repeated violations MUST escalate to an automated guard, validator, or check (such as a runtime guard, test, or check script) instead of another prompt re-wording. Once an automated check or guard is established, prune or delete the redundant prompt note.
Use the trajectory, current continual harness state, and prior refinement history. Prefersmall evidence-backed edits. If prior refinements caused issues, rollback orreplace the faulty editable entries. Never edit source files directly. OutputJSON only with this exact shape:
{ "summary": "one sentence", "rationale": "why these edits are justified by trajectory evidence", "expectedOutcome": "what should improve and how to validate it", "edits": [ { "action": "create|update|delete", "kind": "prompt|memory|skill|subagent", "id": "stable id for update/delete, optional for create", "title": "required for create/update except delete", "content": "required for create/update except delete", "path": "optional grouping path", "reference": {"type": "python", "import": "package.module", "callable": "function_name", "call_pattern": "await function_name(...)"}, "arguments": {"name": {"type": "string", "required": true, "description": "accepted input"}}, "metadata": {}, "reason": "why this edit is useful" } ]}`;
const AUTO_REFINE_REVIEW_SYSTEM_PROMPT = `You are Prime Agent's automatic /refine review gate.
Decide whether this checkpoint should run /refine. Auto /refine writes local continual harness state by default, so approve when the trajectory contains evidence useful to this session's future turns.Reject one-off noise, unsupported hypotheses, and transient tool outputs. Ask for global refinement only for durable cross-session lessons or explicitly project-qualified lessons likely to be reused in future sessions.
Return JSON only:{ "shouldRefine": true|false, "rationale": "short reason", "instructions": "optional concise instructions for /refine if shouldRefine is true"}`;
// These caps apply only with reasoning off; thinking and JSON otherwise share the model's output budget.const REFINEMENT_MAX_OUTPUT_TOKENS = 32_000;const AUTO_REFINE_REVIEW_MAX_OUTPUT_TOKENS = 4_096;const REFINEMENT_CONTEXT_OVERHEAD_TOKENS = 1_024;
const TRUNCATED_JSON_ERROR = "the model stopped before completing its JSON object. This usually means the output budget was exhausted; retry with a smaller request.";
function refinementInputTokenBound(text: string): number { // One token per UTF-8 byte bounds byte-based tokenizers, including dense or unusual text. return Buffer.byteLength(text, "utf8");}
function refinementRequest( model: Model<Api>, systemPrompt: string, conversationText: string, buildPrompt: (conversation: string) => string, outputReserve: number,): { model: Model<Api>; userPrompt: string } { const systemReserve = refinementInputTokenBound(systemPrompt) + REFINEMENT_CONTEXT_OVERHEAD_TOKENS; const inputBudget = model.contextWindow - Math.min(model.maxTokens, outputReserve, Math.floor(model.contextWindow / 2)); let userPrompt = buildPrompt(conversationText); if (systemReserve + refinementInputTokenBound(userPrompt) > inputBudget && conversationText.length > 0) { const promptForLength = (length: number): string => { let start = conversationText.length - length; const first = conversationText.charCodeAt(start); if (first >= 0xdc00 && first <= 0xdfff) start++; return buildPrompt( `[Earlier conversation omitted to fit the model context.]\n${conversationText.slice(start)}`, ); }; let low = 0; let high = conversationText.length; while (low < high) { const length = Math.ceil((low + high) / 2); if (systemReserve + refinementInputTokenBound(promptForLength(length)) <= inputBudget) low = length; else high = length - 1; } userPrompt = promptForLength(low); } const maxTokens = Math.min( model.maxTokens, model.contextWindow - systemReserve - refinementInputTokenBound(userPrompt), ); if (maxTokens <= 0) { throw new Error( "Refinement prompt leaves no room for output in the model's context window; retry with a smaller request.", ); } // Bound the request's model ceiling too: some adapters add thinking tokens before clamping to it. return { model: { ...model, maxTokens }, userPrompt };}
function now(): string { return new Date().toISOString();}
function emptyHarnessState(): HarnessState { return { schema: 1, entries: { prompt: {}, memory: {}, skill: {}, subagent: {}, }, refinements: [], };}
function slug(raw: string, fallback: string): string { const normalized = raw .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, "_") .replace(/^_+|_+$/g, "") .slice(0, 80); return normalized || fallback;}
function cloneEntry(entry: HarnessEntry | undefined): HarnessEntry | undefined { return entry ? JSON.parse(JSON.stringify(entry)) : undefined;}
function objectRecord(value: unknown): Record<string, unknown> | undefined { if (typeof value !== "object" || value === null || Array.isArray(value)) { return undefined; } return value as Record<string, unknown>;}
function normalizeHarnessScope(value: unknown, fallback: HarnessScope): HarnessScope { return value === "global" || value === "local" ? value : fallback;}
export function inferRefinementResultScope(result: RefinementResult): HarnessScope | undefined { if (result.scope) { return result.scope; }
const scopes = new Set<HarnessScope>(); for (const edit of result.appliedEdits) { const scope = edit.after?.scope ?? edit.before?.scope; if (scope) { scopes.add(scope); } } return scopes.size === 1 ? [...scopes][0] : undefined;}
function withDefaultRefinementScope(result: RefinementResult, scope: HarnessScope): RefinementResult { const inferred = inferRefinementResultScope(result); return { ...result, scope: inferred ?? scope };}
export function getGlobalHarnessStateDir(agentDir: string = getAgentDir()): string { return join(agentDir, HARNESS_STATE_DIR_NAME);}
export function getLocalHarnessStateDir(sessionArtifactDir: string | undefined): string | undefined { return sessionArtifactDir ? join(sessionArtifactDir, HARNESS_STATE_DIR_NAME) : undefined;}
export function getHarnessStatePath(harnessStateDir: string = getGlobalHarnessStateDir()): string { return join(harnessStateDir, "harness_state.json");}
export function loadHarnessState( harnessStateDir: string = getGlobalHarnessStateDir(), scope: HarnessScope = "global",): HarnessState { const statePath = getHarnessStatePath(harnessStateDir); if (!existsSync(statePath)) { return emptyHarnessState(); } let parsed: Partial<HarnessState>; try { const raw = JSON.parse(readFileSync(statePath, "utf8")); // loadHarnessState runs on every system-prompt build and before each /refine, so // a corrupt or unreadable (or non-object) state file must degrade to empty rather // than throw and break the session. The next saveHarnessState rewrites it cleanly. if (typeof raw !== "object" || raw === null || Array.isArray(raw)) { return emptyHarnessState(); } parsed = raw as Partial<HarnessState>; } catch { return emptyHarnessState(); } const state = emptyHarnessState(); state.schema = typeof parsed.schema === "number" ? parsed.schema : 1; for (const kind of Object.keys(state.entries) as RefinementKind[]) { const records = parsed.entries?.[kind]; if (records && typeof records === "object") { for (const [id, rawEntry] of Object.entries(records)) { const entry = objectRecord(rawEntry); if (!entry) continue; const violationsRaw = entry.violations; const violations = typeof violationsRaw === "number" && Number.isInteger(violationsRaw) && violationsRaw > 0 ? violationsRaw : undefined; state.entries[kind][id] = { ...(entry as unknown as HarnessEntry), scope: normalizeHarnessScope(entry.scope, scope), reference: objectRecord(entry.reference) ?? {}, arguments: objectRecord(entry.arguments) ?? {}, metadata: objectRecord(entry.metadata) ?? {}, ...(violations !== undefined ? { violations } : {}), }; } } } if (Array.isArray(parsed.refinements)) { state.refinements = parsed.refinements; } return state;}
export function mergeHarnessStates(globalState: HarnessState, localState?: HarnessState): HarnessState { const merged = emptyHarnessState(); merged.schema = Math.max(globalState.schema, localState?.schema ?? 1); for (const kind of Object.keys(merged.entries) as RefinementKind[]) { for (const [id, entry] of Object.entries(globalState.entries[kind])) { const cloned = cloneEntry(entry)!; merged.entries[kind][id] = { ...cloned, scope: normalizeHarnessScope(cloned.scope, "global") }; } for (const [id, entry] of Object.entries(localState?.entries[kind] ?? {})) { const cloned = cloneEntry(entry)!; const scopedEntry = { ...cloned, scope: normalizeHarnessScope(cloned.scope, "local") }; const mergedId = merged.entries[kind][id] ? `${scopedEntry.scope}:${id}` : id; merged.entries[kind][mergedId] = scopedEntry; } } merged.refinements = [...globalState.refinements, ...(localState?.refinements ?? [])]; return merged;}
export function saveHarnessState(harnessStateDir: string, state: HarnessState): string { const statePath = getHarnessStatePath(harnessStateDir); mkdirSync(harnessStateDir, { recursive: true }); const targetPath = realpathIfPresentSync(statePath); const mode = existsSync(targetPath) ? statSync(targetPath).mode & 0o777 : 0o600; writeFileAtomicSync(targetPath, `${JSON.stringify(state, null, 2)}\n`, { mode }); return statePath;}
export function recordHarnessViolation(state: HarnessState, id: string, count = 1): HarnessEntry | undefined { const bareId = id.replace(/^(local|global):/, ""); for (const kind of Object.keys(state.entries) as RefinementKind[]) { const entry = state.entries[kind][bareId]; if (entry) { entry.violations = Math.max(0, (entry.violations ?? 0) + count); entry.updated_at = new Date().toISOString(); return entry; } } return undefined;}
export function getRefinementHistoryPath(harnessStateDir: string = getGlobalHarnessStateDir()): string { return join(harnessStateDir, REFINEMENT_HISTORY_FILE_NAME);}
function isRefinementResult(data: unknown): data is RefinementResult { return typeof data === "object" && data !== null && "id" in data && "appliedEdits" in data;}
/** * Append a global-scope refinement to the cross-session history log so it can be * rolled back from any session. Local-scope refinements are recorded only in the * session JSONL and roll back via their recorded harnessStatePath. */export function appendGlobalRefinement(harnessStateDir: string, result: RefinementResult): string { const historyPath = getRefinementHistoryPath(harnessStateDir); mkdirSync(harnessStateDir, { recursive: true }); appendFileSync(historyPath, `${JSON.stringify(result)}\n`, "utf8"); return historyPath;}
export function loadGlobalRefinementHistory(harnessStateDir: string = getGlobalHarnessStateDir()): RefinementResult[] { const historyPath = getRefinementHistoryPath(harnessStateDir); if (!existsSync(historyPath)) { return []; } const results: RefinementResult[] = []; for (const line of readFileSync(historyPath, "utf8").split("\n")) { const trimmed = line.trim(); if (!trimmed) continue; try { const parsed = JSON.parse(trimmed); if (isRefinementResult(parsed)) { results.push(withDefaultRefinementScope(parsed, "global")); } } catch { // Skip malformed lines so a single bad append cannot break rollback. } } return results;}
/** * Merge global and session refinement history, de-duplicating by id. Session entries * win on conflict so a session that is mid-flight still resolves its own latest result. */export function mergeRefinementHistory( global: readonly RefinementResult[], session: readonly RefinementResult[],): RefinementResult[] { const byId = new Map<string, RefinementResult>(); for (const result of global) { byId.set(result.id, result); } for (const result of session) { const existing = byId.get(result.id); byId.set(result.id, result.scope || !existing?.scope ? result : { ...result, scope: existing.scope }); } return [...byId.values()];}
function collapseWhitespace(text: string): string { return text.replace(/\s+/g, " ").trim();}
function compactText(text: string, maxLength: number): string { const normalized = collapseWhitespace(text); if (normalized.length <= maxLength) { return normalized; } return `${normalized.slice(0, Math.max(0, maxLength - 3))}...`;}
/** Notice body in digest notation: trigger line plus applied edits as `action kind [scope:id] title: content`; rollbacks print via their rollback summaries. */export function formatRefinementNoticeBody(result: RefinementResult): string { const lines = [compactText(result.summary, DEFAULT_OVERVIEW_CONTENT_LIMIT)]; for (const edit of result.appliedEdits) { if (!edit.applied) continue; const entry = edit.after ?? edit.before; const scope = entry?.scope ?? result.scope ?? "local"; lines.push( `- ${edit.action} ${edit.kind} [${scope}:${edit.id}] ${entry?.title ?? edit.id}: ${compactText( entry?.content ?? "", DEFAULT_OVERVIEW_CONTENT_LIMIT, )}`, ); } return lines.join("\n");}
/** * Query terms for relevance-ranked harness digests: term -> weight. * Built by the caller from task signal (goal objective, recent * messages). The ranking is a pure weighted-term overlap over the * entry's searchable fields. */export type HarnessQueryTerms = Map<string, number>;
/** Lowercase a possibly malformed persisted field. */function searchableField(value: unknown): string { return typeof value === "string" ? value.toLowerCase() : "";}
/** CJK ideographs, kana, and Hangul: scripts that do not mark word * boundaries with spaces. */const CJK_TERM_RANGES = "\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af" + "\u{20000}-\u{2a6df}\u{2a700}-\u{2b73f}\u{2b740}-\u{2b81f}" + "\u{2b820}-\u{2ceaf}\u{2ceb0}-\u{2ebef}\u{2ebf0}-\u{2ee5f}" + "\u{2f800}-\u{2fa1f}\u{30000}-\u{3134f}\u{31350}-\u{323af}\u{323b0}-\u{3347f}";const CJK_TERM_PATTERN = new RegExp(`[${CJK_TERM_RANGES}]`, "u");const CJK_TERM_SPLIT = new RegExp(`[${CJK_TERM_RANGES}]+|[^${CJK_TERM_RANGES}]+`, "gu");
/** * Tokenize text into lowercase query terms for harness relevance ranking. * Letters, digits, and combining marks of any script form terms; punctuation only * separate them, so a query like `worktree?` never ranks entries by their * question marks. CJK runs carry no spaces between words, so each run * becomes overlapping bigrams: `修复登录` yields 修复/复登/登录 and still * matches an entry containing 登录故障. Each distinct term is returned once. */export function harnessQueryTerms(text: string): string[] { const terms: string[] = []; // \p{M} keeps combining marks inside their run so mark-heavy scripts // spell whole words (Devanagari किताब stays one run). for (const run of text.toLowerCase().match(/[\p{L}\p{N}\p{M}]+/gu) ?? []) { // Runs break only at CJK boundaries: accented Latin stays whole // (naïve) while spacing-free CJK is cut from adjacent words. for (const segment of run.match(CJK_TERM_SPLIT) ?? []) { if (CJK_TERM_PATTERN.test(segment)) { // Code points, not UTF-16 units, keep astral ideographs whole. const chars = Array.from(segment); if (chars.length === 1) terms.push(segment); else for (let i = 0; i < chars.length - 1; i += 1) terms.push(chars[i] + chars[i + 1]); } else if (segment.length >= 4) { // Short runs are noise (the, and, ids) and are dropped. terms.push(segment); } } } return [...new Set(terms)];}
export interface HarnessOverviewLimits { maxEntriesPerKind?: number; maxContentLength?: number; detailBudget?: number; maxListedEntries?: number; maxListedChars?: number;}
export function isAddressableHarnessId(id: string): boolean { if (id.length === 0) return false; for (const char of id) { const code = char.codePointAt(0) ?? 0; if (code < 0x20 || code === 0x7f || char === "[" || char === "]") { return false; } } return true;}
export function resolvedOverviewListedChars(maxListedChars: number | undefined): number { return resolveOverviewLimit(maxListedChars, DEFAULT_OVERVIEW_LISTED_CHAR_BUDGET, 0);}
function resolveOverviewLimit(value: number | undefined, fallback: number, minimum = 1): number { if (typeof value !== "number" || !Number.isFinite(value)) { return fallback; } const floored = Math.floor(value); return floored >= minimum ? floored : fallback;}
function entryPriority(entry: HarnessEntry): number { const meta = entry.metadata ?? {}; if (meta.pinned === true || (entry as unknown as { pinned?: boolean }).pinned === true) { return Number.POSITIVE_INFINITY; } if (typeof meta.priority === "number" && Number.isFinite(meta.priority)) { return meta.priority; } const directPriority = (entry as unknown as { priority?: number }).priority; if (typeof directPriority === "number" && Number.isFinite(directPriority)) { return directPriority; } return 0;}
function overviewRecency(entry: HarnessEntry): number { const raw = entry.updated_at ?? entry.created_at; const normalized = typeof raw === "string" && OFFSETLESS_TIMESTAMP.test(raw) ? `${raw}Z` : raw; const parsed = typeof normalized === "string" ? Date.parse(normalized) : Number.NaN; return Number.isNaN(parsed) ? Number.NEGATIVE_INFINITY : parsed;}
function overviewTiebreakKey(entry: HarnessEntry): string { return [entry.path ?? "", entry.title ?? "", entry.id ?? "", entry.scope ?? "global"].join("\0");}
function compareOverviewEntries(a: HarnessEntry, b: HarnessEntry): number { // Session-local entries win the digest's detail budget first: they describe the work in // flight, and a large global harness would otherwise push them into the listing-only // tail, where the model loses its own session state at compaction. const aLocal = a.scope === "local"; const bLocal = b.scope === "local"; if (aLocal !== bLocal) { return aLocal ? -1 : 1; } const aPriority = entryPriority(a); const bPriority = entryPriority(b); if (aPriority !== bPriority) { return bPriority > aPriority ? 1 : -1; } const aRecency = overviewRecency(a); const bRecency = overviewRecency(b); if (aRecency !== bRecency) { return bRecency > aRecency ? 1 : -1; } const aVersion = Number.isFinite(a.version) ? a.version : 0; const bVersion = Number.isFinite(b.version) ? b.version : 0; if (aVersion !== bVersion) { return bVersion - aVersion; } const aKey = overviewTiebreakKey(a); const bKey = overviewTiebreakKey(b); if (aKey === bKey) { return 0; } return aKey < bKey ? -1 : 1;}
function overviewHeadline(entry: HarnessEntry): string { const violationCount = typeof entry.violations === "number" && entry.violations > 0 ? entry.violations : 0; const violationText = violationCount > 0 ? `, ${violationCount} violation${violationCount === 1 ? "" : "s"}` : ""; return `- [${entry.scope ?? "global"}:${entry.id}] ${collapseWhitespace(`${entry.title} (${entry.path}, v${entry.version}${violationText})`)}`;}
function overviewStubLine(entry: HarnessEntry): string { const address = `- [${entry.scope ?? "global"}:${entry.id}]`; const room = OVERVIEW_STUB_LINE_LIMIT - address.length - 1; if (room < OVERVIEW_STUB_MIN_REMAINDER) { return address; } const violationCount = typeof entry.violations === "number" && entry.violations > 0 ? entry.violations : 0; const violationText = violationCount > 0 ? `, ${violationCount} violation${violationCount === 1 ? "" : "s"}` : ""; return `${address} ${compactText(`${entry.title} (${entry.path}, v${entry.version}${violationText})`, room)}`;}
function overviewDetailLine(entry: HarnessEntry, maxContentLength: number): string { const argumentsText = entry.kind === "skill" && Object.keys(entry.arguments).length > 0 ? ` args=${compactText(JSON.stringify(entry.arguments), maxContentLength)}` : ""; const referenceText = entry.kind === "skill" && Object.keys(entry.reference).length > 0 ? ` ref=${compactText(JSON.stringify(entry.reference), maxContentLength)}` : ""; return `${overviewHeadline(entry)}${referenceText}${argumentsText}: ${compactText(entry.content, maxContentLength)}`;}
export function selectOverviewEntries( entries: readonly HarnessEntry[], opts: { detailBudget?: number; maxContentLength?: number; reservedRecentSlots?: number; queryTerms?: HarnessQueryTerms; } = {},): { detailed: HarnessEntry[]; listed: HarnessEntry[] } { const detailBudget = resolveOverviewLimit(opts.detailBudget, DEFAULT_OVERVIEW_DETAIL_BUDGET); const maxContentLength = resolveOverviewLimit(opts.maxContentLength, DEFAULT_OVERVIEW_CONTENT_LIMIT); const reservedRecentSlots = resolveOverviewLimit(opts.reservedRecentSlots, DEFAULT_OVERVIEW_ENTRY_LIMIT, 0); const queryTerms = opts.queryTerms; const detailed: HarnessEntry[] = []; const listed: HarnessEntry[] = []; let spent = 0; let budgetExhausted = false; for (const entry of [...entries].sort((a, b) => { if (queryTerms !== undefined && queryTerms.size > 0) { const scoreDifference = scoreHarnessEntryForQuery(b, queryTerms) - scoreHarnessEntryForQuery(a, queryTerms); if (scoreDifference !== 0) return scoreDifference; } return compareOverviewEntries(a, b); })) { if (!budgetExhausted && detailed.length < reservedRecentSlots) { const line = overviewDetailLine(entry, maxContentLength); if (detailed.length === 0 || spent + line.length <= detailBudget) { detailed.push(entry); spent += line.length; continue; } budgetExhausted = true; } listed.push(entry); } return { detailed, listed };}
/** Score one harness entry against query terms: weighted term overlap. */export function scoreHarnessEntryForQuery(entry: HarnessEntry, terms: HarnessQueryTerms): number { if (terms.size === 0) return 0; const title = searchableField(entry.title); const content = searchableField(entry.content); const identifier = `${searchableField(entry.path)} ${searchableField(entry.id)}`; let score = 0; for (const [term, weight] of terms) { // One match per field counts once per term: coverage over distinct // fields matters more than repetition inside a single field. Path // and id form a single identifier slot: the id is often embedded in // the path, so matching both is one signal, not two. let fields = 0; if (title.includes(term)) fields += 1; if (content.includes(term)) fields += 1; if (identifier.includes(term)) fields += 1; if (fields > 0) score += weight * (1 + (fields - 1) * 0.5); } return score;}
export function formatHarnessStateForPrompt( state: HarnessState, options: HarnessOverviewLimits & { maxRefinements?: number; includeIpythonExamples?: boolean; includeShellExamples?: boolean; includeRefineExamples?: boolean; /** Select entries by relevance to these terms instead of * alphabetical order. */ queryTerms?: HarnessQueryTerms; } = {},): string { const maxEntriesPerKind = resolveOverviewLimit(options.maxEntriesPerKind, DEFAULT_OVERVIEW_ENTRY_LIMIT); const maxRefinements = resolveOverviewLimit(options.maxRefinements, DEFAULT_OVERVIEW_REFINEMENT_LIMIT, 0); const maxContentLength = resolveOverviewLimit(options.maxContentLength, DEFAULT_OVERVIEW_CONTENT_LIMIT); const detailBudget = resolveOverviewLimit(options.detailBudget, DEFAULT_OVERVIEW_DETAIL_BUDGET); const maxListedEntries = resolveOverviewLimit(options.maxListedEntries, DEFAULT_OVERVIEW_LISTED_LIMIT, 0); const maxListedChars = resolveOverviewLimit(options.maxListedChars, DEFAULT_OVERVIEW_LISTED_CHAR_BUDGET, 0); const includeIpythonExamples = options.includeIpythonExamples ?? true; const includeRefineExamples = options.includeRefineExamples ?? includeIpythonExamples; const lines = [ "# Continual Harness State", "", "Local continual harness entries belong to this Prime Agent session. Global continual harness entries persist across Prime Agent sessions.", "The continual harness entries below are compact summaries, not full descriptions. Use them as routing/context hints; inspect or refine the underlying continual harness entry only when detail matters.", "Default to local continual harness refinement for current task progress, temporary blockers, and session coordination. Use global continual harness refinement only for stable cross-session lessons, durable user preferences, reusable skills/subagents, or explicitly project-qualified facts.", "Use these continual harness prompt notes, memories, skills, and subagent specs when they are relevant. The base system prompt is immutable; prompt entries below are supplemental notes only.", "", includeRefineExamples ? "When to call `await refine.run()`: after a repeated failure, a reusable tactic emerges, a repeated delegation role should become a subagent spec, a repeated procedure should become a skill, a durable fact/preference should become a memory, a narrow behavioral policy should become a prompt addendum, a user corrects behavior that should persist locally or globally, validation shows a continual harness entry is wrong, or a skill/subagent/memory/prompt note should be created, updated, deleted, or rolled back. Repeated violations on a note must escalate to a check or guard instead of another re-wording. Keep `await refine.run()` continual harness edits small and evidence-backed." : "When to refine the continual harness: after a repeated failure, a reusable tactic emerges, a repeated delegation role should become a subagent spec, a repeated procedure should become a skill, a durable fact/preference should become a memory, a narrow behavioral policy should become a prompt addendum, a user corrects behavior that should persist locally or globally, validation shows a continual harness entry is wrong, or a skill/subagent/memory/prompt note should be created, updated, deleted, or rolled back. Repeated violations on a note must escalate to a check or guard instead of another re-wording. Keep continual harness edits small and evidence-backed.", "", includeIpythonExamples ? "Call contract: read each installed Python skill's SKILL.md and call its documented module function in the Python REPL; do not assume a `.run` entrypoint. Use `<skill_import> ...` in shell when a CLI exists. Continual harness skill entries are Python REPL skills with an explicit Python `reference` and `arguments` contract. Spawn a continual harness subagent spec by composing a concise task prompt and calling `handle = await rlm.spawn('sub-task', name='worker', model='<model>')`; call `await rlm.models()` first to inspect available models; `name` and `model` are required (`thinking=` is optional); admission returns immediately with `rlm_child_id`, `name`, `session_dir`, and `model`, never the child's answer. Results arrive only through explicit `agent_message` replies or files; children reply with `await agent_message.send(message, receiver_role='parent')`. Use `await rlm.list_subagents()` to recover direct child handles and `await agent_message.send(..., receiver_role='child', receiver_name=handle.name)` for follow-ups. Do not invent wrappers such as `call_skill(...)`, `run_subagent(...)`, or named subagent registries." : options.includeShellExamples ? "Call contract: use installed skills as shell commands when available (for example `<skill_import> ...`). Continual harness entries are routing/context hints only in sessions without the Python REPL; do not use Python `await`, `asyncio`, or `rlm` examples unless the prompt also documents a Python kernel." : "Call contract: continual harness entries are routing/context hints only in sessions without the Python REPL or shell access; do not use Python `await`, `asyncio`, `rlm`, or shell skill commands unless the prompt also documents those interfaces.", "", ];
const queryTerms = options.queryTerms; const selections = (Object.keys(state.entries) as RefinementKind[]).map((kind) => { const kindEntries = Object.values(state.entries[kind]); const addressable = kindEntries.filter((entry) => isAddressableHarnessId(entry.id)); return { kind, count: kindEntries.length, unaddressable: kindEntries.length - addressable.length, ...selectOverviewEntries(addressable, { detailBudget, maxContentLength, reservedRecentSlots: maxEntriesPerKind, queryTerms, }), }; });
const totalEntries = selections.reduce((running, selection) => running + selection.count, 0); const stubbedKinds = selections.filter((selection) => selection.listed.length > 0).length; const stubBudget = stubbedKinds > 0 ? Math.floor(maxListedChars / stubbedKinds) : 0;
for (const { kind, count, unaddressable, detailed, listed } of selections) { if (kind === "subagent" && count > 0 && includeIpythonExamples) { lines.push( `${kind}: ${count} (invoke a spec by turning it into a concise task prompt and spawning with \`await rlm.spawn('<task>', name='<worker>', model='<model>')\`; admission returns a child handle, never the answer)`, ); } else { lines.push(`${kind}: ${count}`); } if (queryTerms !== undefined && queryTerms.size > 0 && listed.length > 0) { lines.push("(entries ranked by relevance to the current task; see harness.search)"); } for (const entry of detailed) { lines.push(overviewDetailLine(entry, maxContentLength)); } let spentOnStubs = 0; let stubCount = 0; for (const entry of listed) { if (stubCount >= maxListedEntries || spentOnStubs >= stubBudget) { break; } const line = overviewStubLine(entry); if (spentOnStubs + line.length + 1 > stubBudget) { continue; } lines.push(line); spentOnStubs += line.length + 1; stubCount++; } const overflow = listed.length - stubCount + unaddressable; if (overflow > 0) { lines.push(`- +${overflow} more ${kind} entries`); } lines.push(""); }
if (totalEntries === 0) { lines.push("No saved harness entries yet.", ""); }
lines.push(`recent refinements: ${state.refinements.length}`); const renderableEvents = state.refinements.filter((event) => isAddressableHarnessId(event.id)); for (const event of renderableEvents.slice(-maxRefinements)) { const changes = event.changes.length > 0 ? collapseWhitespace(event.changes.join(", ")) : "no applied edits"; const outcome = event.outcome ? `; outcome: ${compactText(event.outcome, maxContentLength)}` : ""; lines.push(`- [${event.id}] ${compactText(event.trigger, maxContentLength)}: ${changes}${outcome}`); } const refinementOverflow = state.refinements.length - Math.min(renderableEvents.length, maxRefinements); if (refinementOverflow > 0) { lines.push(`- +${refinementOverflow} older refinement events`); }
return lines.join("\n").trim();}
function overviewForPrompt(state: HarnessState): string { const lines: string[] = []; for (const kind of Object.keys(state.entries) as RefinementKind[]) { const entries = Object.values(state.entries[kind]).sort(compareOverviewEntries); lines.push(`${kind}: ${entries.length}`); for (const entry of entries.slice(0, 40)) { const content = entry.content.replace(/\s+/g, " ").slice(0, 240); const argumentsText = entry.kind === "skill" && Object.keys(entry.arguments).length > 0 ? ` args=${JSON.stringify(entry.arguments).slice(0, 240)}` : ""; const referenceText = entry.kind === "skill" && Object.keys(entry.reference).length > 0 ? ` ref=${JSON.stringify(entry.reference).slice(0, 240)}` : ""; lines.push( `- [${entry.scope ?? "global"}:${entry.id}] ${entry.title} (${entry.path}, v${entry.version})${referenceText}${argumentsText}: ${content}`, ); } if (entries.length > 40) { lines.push(`- +${entries.length - 40} more ${kind} entries`); } } return lines.join("\n");}
function historyForPrompt(history: RefinementResult[]): string { if (history.length === 0) { return "No prior refinement history."; } return history .slice(-20) .map((item) => { const edits = item.appliedEdits .map((edit) => `${edit.applied ? "applied" : "failed"} ${edit.action} ${edit.kind}:${edit.id}`) .join(", "); const rollback = item.rollbackOf ? ` rollbackOf=${item.rollbackOf}` : ""; return `[${item.id}]${rollback} ${item.summary}\n${edits}\nExpected outcome: ${item.expectedOutcome}`; }) .join("\n\n");}
/** * Whether a JSON candidate ends mid-value: an unterminated string, or unclosed * objects/arrays. A reply cut off by an exhausted output budget is incomplete in * this sense, while a complete-but-malformed reply is balanced. Brace slicing can * also produce a balanced fragment, so callers treat "balanced" as malformed. */function isIncompleteJson(candidate: string): boolean { let depth = 0; let inString = false; let escaped = false; for (const char of candidate) { if (escaped) { escaped = false; continue; } if (inString) { if (char === "\\") escaped = true; else if (char === '"') inString = false; continue; } if (char === '"') inString = true; else if (char === "{" || char === "[") depth++; else if (char === "}" || char === "]") depth--; } return inString || depth > 0;}
function parseJsonCandidate(candidate: string): unknown { try { return JSON.parse(candidate); } catch (error) { // A truncated reply and a malformed one both fail here, and JSON.parse // describes the fragment rather than the cause. Name the cause instead. if (isIncompleteJson(candidate)) { throw new Error(TRUNCATED_JSON_ERROR); } throw new Error(`the model did not return valid JSON: ${error instanceof Error ? error.message : String(error)}`); }}
function extractJsonObject(text: string): unknown { const trimmed = text.trim(); if (trimmed.startsWith("{") && trimmed.endsWith("}")) { // A reply truncated after a nested closing brace still looks well-formed // here, so this path needs the same diagnosis as the slicing fallback. return parseJsonCandidate(trimmed); } const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/); if (fenced) { return parseJsonCandidate(fenced[1].trim()); } // Brace slicing recovers JSON wrapped in prose. On a reply truncated inside the // edits array it slices to an earlier edit's closing brace, so a failure here // is diagnosed against the original text rather than the balanced fragment. const start = trimmed.indexOf("{"); const end = trimmed.lastIndexOf("}"); if (start !== -1 && end > start) { try { return JSON.parse(trimmed.slice(start, end + 1)); } catch { return parseJsonCandidate(trimmed.slice(start)); } } if (isIncompleteJson(trimmed)) { throw new Error(TRUNCATED_JSON_ERROR); } throw new Error("Refiner did not return a JSON object");}
/** * Normalizes an untrusted refinement proposal while preserving invalid edit * fields for apply-time validation. */export function normalizeRefinementProposal(value: unknown): RefinementProposal { const record = typeof value === "object" && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : {}; const edits = Array.isArray(record.edits) ? record.edits : []; return { summary: typeof record.summary === "string" ? record.summary : "Refined continual harness state", rationale: typeof record.rationale === "string" ? record.rationale : "", expectedOutcome: typeof record.expectedOutcome === "string" ? record.expectedOutcome : "", edits: edits .filter((edit): edit is Record<string, unknown> => typeof edit === "object" && edit !== null) .map((edit) => ({ action: edit.action as RefinementAction, kind: edit.kind as RefinementKind, id: typeof edit.id === "string" ? edit.id : undefined, title: typeof edit.title === "string" ? edit.title : undefined, content: typeof edit.content === "string" ? edit.content : undefined, path: typeof edit.path === "string" ? edit.path : undefined, reference: objectRecord(edit.reference), arguments: objectRecord(edit.arguments), metadata: typeof edit.metadata === "object" && edit.metadata !== null && !Array.isArray(edit.metadata) ? (edit.metadata as Record<string, unknown>) : undefined, reason: typeof edit.reason === "string" ? edit.reason : undefined, })), };}
function parseProposal(text: string): RefinementProposal { const value = extractJsonObject(text); if (typeof value !== "object" || value === null || Array.isArray(value)) { throw new Error("Refiner JSON must be an object"); } return normalizeRefinementProposal(value);}
function validateEdit(edit: RefinementEdit, computedId?: string): string | undefined { if (!["create", "update", "delete"].includes(edit.action)) { return `unsupported action ${String(edit.action)}`; } if (!["prompt", "memory", "skill", "subagent"].includes(edit.kind)) { return `unsupported kind ${String(edit.kind)}`; } if (edit.kind === "prompt" && (edit.id === "base_system_prompt" || computedId === "base_system_prompt")) { return "base system prompt is not editable"; } if (edit.action !== "create" && !edit.id) { return `${edit.action} requires id`; } if (edit.action !== "delete" && (!edit.title || !edit.content)) { return `${edit.action} requires title and content`; } if (edit.action !== "delete" && edit.kind === "skill" && edit.arguments === undefined) { return `${edit.action} skill requires arguments`; } if (edit.action !== "delete" && edit.kind === "skill") { const reference = edit.reference; if (!reference) { return `${edit.action} skill requires python reference`; } if (reference.type !== "python") { return `${edit.action} skill reference.type must be python`; } const hasImport = (typeof reference.import === "string" && reference.import.length > 0) || (typeof reference.python_import === "string" && reference.python_import.length > 0); const hasCallable = (typeof reference.callable === "string" && reference.callable.length > 0) || (typeof reference.call_pattern === "string" && reference.call_pattern.length > 0); if (!hasImport) { return `${edit.action} skill requires python import`; } if (!hasCallable) { return `${edit.action} skill requires callable or call_pattern`; } } return undefined;}
export function applyRefinementProposal( state: HarnessState, proposal: RefinementProposal, options: { id: string; rollbackOf?: string; scope?: HarnessScope; baselineState?: HarnessState },): RefinementResult { const appliedEdits: AppliedRefinementEdit[] = []; const proposalModifiedKeys = new Set<string>(); for (const edit of proposal.edits) { const computedId = edit.id ?? (edit.action === "create" ? slug(edit.title ?? edit.kind, edit.kind) : undefined); const id = computedId ?? ""; const validationError = validateEdit(edit, id); if (validationError) { appliedEdits.push({ ...edit, id, applied: false, error: validationError }); continue; }
const records = state.entries[edit.kind]; const before = cloneEntry(records[id]); const entryKey = `${edit.kind}:${id}`; const baseline = cloneEntry(options.baselineState?.entries[edit.kind][id]); if ( options.baselineState && !proposalModifiedKeys.has(entryKey) && JSON.stringify(before) !== JSON.stringify(baseline) ) { appliedEdits.push({ ...edit, id, before, applied: false, error: "entry changed during refinement planning", }); continue; } if (edit.action === "delete") { if (!before) { appliedEdits.push({ ...edit, id, applied: false, error: "entry not found" }); continue; } delete records[id]; proposalModifiedKeys.add(entryKey); appliedEdits.push({ ...edit, id, before, applied: true }); continue; } if (edit.action === "create" && before) { appliedEdits.push({ ...edit, id, before, applied: false, error: "entry already exists" }); continue; } if (edit.action === "update" && !before) { appliedEdits.push({ ...edit, id, applied: false, error: "entry not found" }); continue; }
const createdAt = before?.created_at ?? now(); const version = before ? before.version + 1 : 1; const violations = edit.violations !== undefined ? edit.violations : before?.violations; const after: HarnessEntry = { id, kind: edit.kind, title: edit.title ?? before?.title ?? id, content: edit.content ?? before?.content ?? "", path: edit.path ?? before?.path ?? "general", scope: before?.scope ?? options.scope ?? "local", reference: edit.reference ?? before?.reference ?? {}, arguments: edit.arguments ?? before?.arguments ?? {}, metadata: edit.metadata ?? before?.metadata ?? {}, source: "refine", created_at: createdAt, updated_at: now(), version, ...(violations !== undefined && violations > 0 ? { violations } : {}), }; records[id] = after; proposalModifiedKeys.add(entryKey); appliedEdits.push({ ...edit, id, before, after: cloneEntry(after), applied: true }); }
const changes = appliedEdits.filter((edit) => edit.applied).map((edit) => `${edit.action} ${edit.kind}:${edit.id}`); state.refinements.push({ id: options.id, trigger: proposal.summary, changes, evidence: proposal.rationale, outcome: proposal.expectedOutcome, created_at: now(), });
return { id: options.id, summary: proposal.summary, rationale: proposal.rationale, expectedOutcome: proposal.expectedOutcome, appliedEdits, harnessStatePath: "", rollbackOf: options.rollbackOf, scope: options.scope, };}
function rollbackProposal(target: RefinementResult): RefinementProposal { const edits: RefinementEdit[] = []; for (const edit of [...target.appliedEdits].reverse()) { if (!edit.applied) continue; if (edit.before) { edits.push({ action: edit.after ? "update" : "create", kind: edit.kind, id: edit.id, title: edit.before.title, content: edit.before.content, path: edit.before.path, reference: edit.before.reference, arguments: edit.before.arguments, metadata: edit.before.metadata, reason: `Rollback ${target.id}`, }); } else if (edit.after) { edits.push({ action: "delete", kind: edit.kind, id: edit.id, reason: `Rollback ${target.id}`, }); } } return { summary: `Rollback refinement ${target.id}`, rationale: `Restores continual harness state snapshots from refinement ${target.id}.`, expectedOutcome: "Faulty refinement edits are reverted.", edits, };}
export function getRefinementHistory(entries: readonly CustomEntry[]): RefinementResult[] { return entries .filter((entry) => entry.customType === REFINEMENT_CUSTOM_TYPE) .map((entry) => entry.data) .filter((data): data is RefinementResult => { return typeof data === "object" && data !== null && "id" in data && "appliedEdits" in data; });}
export interface RefinementPlan { proposal: RefinementProposal; id: string; rollbackOf?: string; rollbackScope?: HarnessScope; /** Target-scope state captured before planning, used to reject conflicting edits at apply time. */ baselineState?: HarnessState;}
/** * Produce a refinement proposal (the LLM pass, or a rollback proposal) without * mutating any harness state. Separated from {@link applyRefinementProposal} so * callers can re-read the harness file immediately before applying — the LLM call * here can take many seconds, during which the kernel or another session may write * the shared `harness_state.json`. *//** Mint a refinement id in the canonical `refine_<timestamp>` format. */export function generateRefinementId(): string { return `refine_${new Date() .toISOString() .replace(/[^0-9]/g, "") .slice(0, 17)}`;}
export async function planRefinement( messages: AgentMessage[], state: HarnessState, history: RefinementResult[], model: Model<any>, apiKey: string, options: RefineOptions = {}, headers?: Record<string, string>, signal?: AbortSignal, thinkingLevel?: ThinkingLevel, sessionId?: string,): Promise<RefinementPlan> { const id = generateRefinementId(); if (options.rollbackId) { const target = history.find((item) => item.id === options.rollbackId); if (!target) { throw new Error(`Refinement ${options.rollbackId} not found`); } const fallbackScope: HarnessScope = options.global ? "global" : "local"; return { proposal: rollbackProposal(target), id, rollbackOf: target.id, rollbackScope: inferRefinementResultScope(target) ?? fallbackScope, }; }
const conversationText = serializeConversation(convertToLlm(messages)).slice(-80_000); const scopeInstruction = options.global ? "Requested refinement scope: global. Only propose stable cross-session continual harness edits, durable user preferences, reusable skills/subagents, or explicitly project-qualified facts that should affect future Prime Agent sessions. Do not persist session-only progress, temporary blockers, or current-run coordination globally." : "Requested refinement scope: local. Prefer local continual harness edits for current task progress, temporary blockers, current-run coordination, and project facts that are not clearly reusable across Prime Agent sessions. Global entries in the overview are read-only context: do not propose update or delete edits for them; create a local entry instead if an override is needed."; const buildPrompt = (conversation: string): string => [ `<current_harness_state>\n${overviewForPrompt(state)}\n</current_harness_state>`, `<refinement_history>\n${historyForPrompt(history)}\n</refinement_history>`, `<conversation>\n${conversation}\n</conversation>`, `<scope_policy>\n${scopeInstruction}\n</scope_policy>`, options.instructions ? `<user_refine_instructions>\n${options.instructions}\n</user_refine_instructions>` : "", "Return only JSON edits. If no useful edit is justified, return an empty edits array with a rationale.", ] .filter(Boolean) .join("\n\n"); const reasoning = getAuxiliaryThinkingLevel(model, thinkingLevel); const { model: requestModel, userPrompt } = refinementRequest( model, REFINEMENT_SYSTEM_PROMPT, conversationText, buildPrompt, reasoning === "off" ? REFINEMENT_MAX_OUTPUT_TOKENS : model.maxTokens, ); const maxTokens = reasoning === "off" ? Math.min(requestModel.maxTokens, REFINEMENT_MAX_OUTPUT_TOKENS) : requestModel.maxTokens;
const response = await completeWithProviderRetry( () => completeSimple( requestModel, { systemPrompt: REFINEMENT_SYSTEM_PROMPT, messages: [{ role: "user", content: [{ type: "text", text: userPrompt }], timestamp: Date.now() }], }, { reasoning, maxTokens, signal, apiKey, headers, sessionId, }, ), { policy: options.retry, signal }, );
if (response.stopReason === "error") { throw new Error(`Refinement failed: ${response.errorMessage || "Unknown error"}`); } if (response.stopReason === "length") { throw new Error(`Refinement failed: ${TRUNCATED_JSON_ERROR}`); }
const text = response.content .filter((content): content is { type: "text"; text: string } => content.type === "text") .map((content) => content.text) .join("\n"); return { proposal: parseProposal(text), id };}
function parseAutoRefineReview(text: string): AutoRefineReview { const value = extractJsonObject(text); if (typeof value !== "object" || value === null || Array.isArray(value)) { throw new Error("Auto-refine review JSON must be an object"); } const record = value as Record<string, unknown>; return { shouldRefine: record.shouldRefine === true, rationale: typeof record.rationale === "string" ? record.rationale : "No rationale provided.", instructions: typeof record.instructions === "string" ? record.instructions : undefined, };}
export async function reviewAutoRefine( messages: AgentMessage[], state: HarnessState, history: RefinementResult[], model: Model<any>, apiKey: string, context: AutoRefineReviewContext, headers?: Record<string, string>, signal?: AbortSignal, thinkingLevel?: ThinkingLevel, retry?: ProviderRetryPolicy, sessionId?: string,): Promise<AutoRefineReview> { const conversationText = serializeConversation(convertToLlm(messages)).slice(-40_000); const buildPrompt = (conversation: string): string => [ `<trigger>${context.reason}; ${context.turnsSinceLastReview} assistant turns since last auto-refine review</trigger>`, `<current_harness_state>${overviewForPrompt(state)}</current_harness_state>`, `<refinement_history>${historyForPrompt(history)}</refinement_history>`, `<conversation>${conversation}</conversation>`, "Return shouldRefine=true when the trajectory contains evidence useful to this session's future turns. Prefer local harness edits for current task progress, temporary blockers, and current-run coordination. Ask for global refinement only for durable cross-session lessons or explicitly project-qualified facts likely to be reused in future sessions.", ].join("\n\n"); const reasoning = getAuxiliaryThinkingLevel(model, thinkingLevel); const { model: requestModel, userPrompt } = refinementRequest( model, AUTO_REFINE_REVIEW_SYSTEM_PROMPT, conversationText, buildPrompt, reasoning === "off" ? AUTO_REFINE_REVIEW_MAX_OUTPUT_TOKENS : model.maxTokens, ); const maxTokens = reasoning === "off" ? Math.min(requestModel.maxTokens, AUTO_REFINE_REVIEW_MAX_OUTPUT_TOKENS) : requestModel.maxTokens; const response = await completeWithProviderRetry( () => completeSimple( requestModel, { systemPrompt: AUTO_REFINE_REVIEW_SYSTEM_PROMPT, messages: [{ role: "user", content: [{ type: "text", text: userPrompt }], timestamp: Date.now() }], }, { reasoning, maxTokens, signal, apiKey, headers, sessionId, }, ), { policy: retry, signal }, ); if (response.stopReason === "error") { throw new Error(`Auto-refine review failed: ${response.errorMessage || "Unknown error"}`); } if (response.stopReason === "length") { throw new Error(`Auto-refine review failed: ${TRUNCATED_JSON_ERROR}`); } const text = response.content .filter((content): content is { type: "text"; text: string } => content.type === "text") .map((content) => content.text) .join("\n"); return parseAutoRefineReview(text);}
export async function refineHarness( messages: AgentMessage[], state: HarnessState, history: RefinementResult[], model: Model<any>, apiKey: string, options: RefineOptions = {}, headers?: Record<string, string>, signal?: AbortSignal, thinkingLevel?: ThinkingLevel, sessionId?: string,): Promise<RefinementResult> { const plan = await planRefinement( messages, state, history, model, apiKey, options, headers, signal, thinkingLevel, sessionId, ); return applyRefinementProposal(state, plan.proposal, { id: plan.id, rollbackOf: plan.rollbackOf, scope: plan.rollbackScope ?? (options.global ? "global" : "local"), });}