pi-harness-runtime 0.2.0 → 0.3.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 +117 -158
- package/harness/agent-handoff.ts +189 -0
- package/harness/blackboard.ts +291 -0
- package/harness/context-window-manager.ts +210 -0
- package/harness/e2e/playwright-runner.ts +252 -0
- package/harness/e2e/test-engine.ts +402 -0
- package/harness/job-state-machine.ts +363 -0
- package/harness/loop-runtime.ts +337 -0
- package/harness/master-planner.ts +327 -0
- package/harness/project-detector/detector.ts +328 -0
- package/harness/repair-engine.ts +340 -0
- package/harness/task-graph.ts +336 -0
- package/index.ts +294 -2
- package/package.json +5 -2
- package/packages/checkpoint/README.md +3 -0
- package/packages/checkpoint/src/checkpoint-manager.ts +38 -0
- package/packages/provider-router/src/provider-router.ts +42 -0
- package/packages/providers/README.md +3 -0
- package/packages/providers/adapters.ts +261 -0
- package/packages/quota-manager/README.md +3 -0
- package/packages/quota-manager/quota-manager.ts +328 -0
- package/packages/runtime/README.md +3 -0
- package/packages/scheduler/README.md +3 -0
- package/packages/scheduler/src/scheduler.ts +50 -0
- package/packages/shared-context/README.md +3 -0
- package/packages/shared-context/src/shared-context.ts +42 -0
- package/packages/tui/README.md +3 -0
- package/packages/types/src/runtime-types.ts +439 -0
- package/packages/worktree/README.md +3 -0
- package/packages/worktree/worktree.ts +293 -0
- package/skills/harness-runtime/SKILL.md +186 -72
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Worktree Manager — RFC-0005
|
|
3
|
+
*
|
|
4
|
+
* Creates isolated workspaces for parallel or recoverable coding tasks.
|
|
5
|
+
*
|
|
6
|
+
* Goals:
|
|
7
|
+
* - avoid branch conflicts
|
|
8
|
+
* - preserve partial work
|
|
9
|
+
* - allow reviewer agent to inspect diffs
|
|
10
|
+
* - allow separate models to work on separate tasks
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { WorktreeInfo } from "../../packages/types/src/runtime-types.ts";
|
|
14
|
+
// @ts-expect-error - Bun has built-in Node.js types
|
|
15
|
+
import { execSync } from "node:child_process";
|
|
16
|
+
// @ts-expect-error - Bun has built-in Node.js types
|
|
17
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
18
|
+
// @ts-expect-error - Bun has built-in Node.js types
|
|
19
|
+
import { join, dirname } from "node:path";
|
|
20
|
+
|
|
21
|
+
export interface WorktreeOptions {
|
|
22
|
+
rootDir: string;
|
|
23
|
+
baseBranch?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CreateWorktreeOptions {
|
|
27
|
+
name: string;
|
|
28
|
+
branch?: string;
|
|
29
|
+
jobId?: string;
|
|
30
|
+
taskId?: string;
|
|
31
|
+
startPoint?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface WorktreeDiff {
|
|
35
|
+
file: string;
|
|
36
|
+
status: "added" | "modified" | "deleted" | "renamed";
|
|
37
|
+
insertions?: number;
|
|
38
|
+
deletions?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class WorktreeManager {
|
|
42
|
+
private readonly rootDir: string;
|
|
43
|
+
private readonly baseBranch: string;
|
|
44
|
+
private readonly metaPath: string;
|
|
45
|
+
|
|
46
|
+
constructor(options: WorktreeOptions) {
|
|
47
|
+
this.rootDir = options.rootDir;
|
|
48
|
+
this.baseBranch = options.baseBranch ?? "main";
|
|
49
|
+
this.metaPath = join(this.rootDir, ".worktrees.json");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a new worktree
|
|
54
|
+
*/
|
|
55
|
+
async create(options: CreateWorktreeOptions): Promise<WorktreeInfo> {
|
|
56
|
+
const name = options.name.replace(/[^a-zA-Z0-9-_]/g, "-");
|
|
57
|
+
const branch = options.branch ?? `worktree/${name}`;
|
|
58
|
+
const path = join(this.rootDir, "worktrees", name);
|
|
59
|
+
|
|
60
|
+
// Ensure directory exists
|
|
61
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
62
|
+
|
|
63
|
+
// Create git worktree
|
|
64
|
+
const startPoint = options.startPoint ?? this.baseBranch;
|
|
65
|
+
const worktreePath = this.execGit([
|
|
66
|
+
"worktree",
|
|
67
|
+
"add",
|
|
68
|
+
"-b",
|
|
69
|
+
branch,
|
|
70
|
+
path,
|
|
71
|
+
startPoint,
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
// Record metadata
|
|
75
|
+
const worktree: WorktreeInfo = {
|
|
76
|
+
name,
|
|
77
|
+
path: worktreePath,
|
|
78
|
+
branch,
|
|
79
|
+
jobId: options.jobId,
|
|
80
|
+
taskId: options.taskId,
|
|
81
|
+
createdAt: new Date().toISOString(),
|
|
82
|
+
status: "active",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
this.saveWorktree(worktree);
|
|
86
|
+
|
|
87
|
+
return worktree;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* List all worktrees
|
|
92
|
+
*/
|
|
93
|
+
async list(): Promise<WorktreeInfo[]> {
|
|
94
|
+
if (!existsSync(this.metaPath)) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const content = readFileSync(this.metaPath, "utf-8");
|
|
100
|
+
return JSON.parse(content) as WorktreeInfo[];
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get worktree by name
|
|
108
|
+
*/
|
|
109
|
+
async get(name: string): Promise<WorktreeInfo | null> {
|
|
110
|
+
const worktrees = await this.list();
|
|
111
|
+
return worktrees.find((w) => w.name === name) ?? null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get worktree by job ID
|
|
116
|
+
*/
|
|
117
|
+
async getByJob(jobId: string): Promise<WorktreeInfo | null> {
|
|
118
|
+
const worktrees = await this.list();
|
|
119
|
+
return worktrees.find((w) => w.jobId === jobId) ?? null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Remove a worktree
|
|
124
|
+
*/
|
|
125
|
+
async remove(name: string, force: boolean = false): Promise<void> {
|
|
126
|
+
const worktree = await this.get(name);
|
|
127
|
+
if (!worktree) {
|
|
128
|
+
throw new Error(`Worktree ${name} not found`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Remove git worktree
|
|
132
|
+
this.execGit(["worktree", "remove", worktree.path, force ? "--force" : ""]);
|
|
133
|
+
|
|
134
|
+
// Remove from metadata
|
|
135
|
+
const worktrees = await this.list();
|
|
136
|
+
const updated = worktrees.filter((w) => w.name !== name);
|
|
137
|
+
this.saveAllWorktrees(updated);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Prune stale worktrees
|
|
142
|
+
*/
|
|
143
|
+
async prune(): Promise<string[]> {
|
|
144
|
+
const output = this.execGit(["worktree", "list", "--porcelain"]);
|
|
145
|
+
const lines = output.split("\n");
|
|
146
|
+
const pruned: string[] = [];
|
|
147
|
+
|
|
148
|
+
// Parse git worktree list
|
|
149
|
+
const worktreePaths: string[] = [];
|
|
150
|
+
for (const line of lines) {
|
|
151
|
+
if (line.startsWith("worktree ")) {
|
|
152
|
+
worktreePaths.push(line.replace("worktree ", "").trim());
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check our tracked worktrees
|
|
157
|
+
const tracked = await this.list();
|
|
158
|
+
for (const worktree of tracked) {
|
|
159
|
+
if (!worktreePaths.includes(worktree.path)) {
|
|
160
|
+
// Git worktree no longer exists
|
|
161
|
+
worktree.status = "abandoned";
|
|
162
|
+
pruned.push(worktree.name);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.saveAllWorktrees(tracked);
|
|
167
|
+
|
|
168
|
+
return pruned;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get diff between worktree and base
|
|
173
|
+
*/
|
|
174
|
+
async getDiff(worktreePath: string): Promise<WorktreeDiff[]> {
|
|
175
|
+
const output = this.execGit(["diff", "--stat", `main...${worktreePath}`], {
|
|
176
|
+
cwd: worktreePath,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const diffs: WorktreeDiff[] = [];
|
|
180
|
+
const lines = output.split("\n");
|
|
181
|
+
|
|
182
|
+
for (const line of lines) {
|
|
183
|
+
if (!line.includes("|")) continue;
|
|
184
|
+
|
|
185
|
+
const [file, stats] = line.split("|").map((s) => s.trim());
|
|
186
|
+
if (!file || !stats) continue;
|
|
187
|
+
|
|
188
|
+
let status: WorktreeDiff["status"] = "modified";
|
|
189
|
+
if (stats.startsWith("+")) status = "added";
|
|
190
|
+
else if (stats.startsWith("-")) status = "deleted";
|
|
191
|
+
|
|
192
|
+
const match = stats.match(/\+(\d+)/);
|
|
193
|
+
const insertions = match ? parseInt(match[1], 10) : undefined;
|
|
194
|
+
|
|
195
|
+
const delMatch = stats.match(/-(\d+)/);
|
|
196
|
+
const deletions = delMatch ? parseInt(delMatch[1], 10) : undefined;
|
|
197
|
+
|
|
198
|
+
diffs.push({ file, status, insertions, deletions });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return diffs;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get uncommitted changes
|
|
206
|
+
*/
|
|
207
|
+
async getUncommitted(worktreePath: string): Promise<WorktreeDiff[]> {
|
|
208
|
+
const output = this.execGit(["diff", "--numstat"], { cwd: worktreePath });
|
|
209
|
+
|
|
210
|
+
const diffs: WorktreeDiff[] = [];
|
|
211
|
+
const lines = output.split("\n");
|
|
212
|
+
|
|
213
|
+
for (const line of lines) {
|
|
214
|
+
const parts = line.split("\t");
|
|
215
|
+
if (parts.length < 3) continue;
|
|
216
|
+
|
|
217
|
+
const [ins, del, file] = parts;
|
|
218
|
+
diffs.push({
|
|
219
|
+
file,
|
|
220
|
+
status: "modified",
|
|
221
|
+
insertions: parseInt(ins, 10) || 0,
|
|
222
|
+
deletions: parseInt(del, 10) || 0,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return diffs;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Mark worktree as merged
|
|
231
|
+
*/
|
|
232
|
+
async markMerged(name: string): Promise<void> {
|
|
233
|
+
const worktrees = await this.list();
|
|
234
|
+
const worktree = worktrees.find((w) => w.name === name);
|
|
235
|
+
if (worktree) {
|
|
236
|
+
worktree.status = "merged";
|
|
237
|
+
this.saveAllWorktrees(worktrees);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Execute git command
|
|
243
|
+
*/
|
|
244
|
+
private execGit(args: string[], options?: { cwd?: string }): string {
|
|
245
|
+
try {
|
|
246
|
+
const cwd = options?.cwd ?? this.rootDir;
|
|
247
|
+
return execSync(["git", ...args].join(" "), {
|
|
248
|
+
cwd,
|
|
249
|
+
encoding: "utf-8",
|
|
250
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
251
|
+
}).trim();
|
|
252
|
+
} catch (error) {
|
|
253
|
+
const e = error as { message?: string };
|
|
254
|
+
throw new Error(`Git command failed: ${e.message ?? error}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Save worktree to metadata
|
|
260
|
+
*/
|
|
261
|
+
private saveWorktree(worktree: WorktreeInfo): void {
|
|
262
|
+
const current = this.listSync();
|
|
263
|
+
const idx = current.findIndex((w) => w.name === worktree.name);
|
|
264
|
+
if (idx >= 0) {
|
|
265
|
+
current[idx] = worktree;
|
|
266
|
+
} else {
|
|
267
|
+
current.push(worktree);
|
|
268
|
+
}
|
|
269
|
+
this.saveAllWorktrees(current);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Save all worktrees
|
|
274
|
+
*/
|
|
275
|
+
private saveAllWorktrees(worktrees: WorktreeInfo[]): void {
|
|
276
|
+
mkdirSync(dirname(this.metaPath), { recursive: true });
|
|
277
|
+
writeFileSync(this.metaPath, JSON.stringify(worktrees, null, 2), "utf-8");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* List worktrees synchronously
|
|
282
|
+
*/
|
|
283
|
+
private listSync(): WorktreeInfo[] {
|
|
284
|
+
if (!existsSync(this.metaPath)) {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
return JSON.parse(readFileSync(this.metaPath, "utf-8")) as WorktreeInfo[];
|
|
289
|
+
} catch {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -1,95 +1,209 @@
|
|
|
1
|
-
|
|
2
|
-
name: harness-runtime
|
|
3
|
-
description: Show Codex-style /usage status for pi — local token tracking + manual provider mirror. Use when the user asks about token usage, API quota, 5h limit, weekly limit, or wants to know how much they've spent.
|
|
4
|
-
---
|
|
1
|
+
# Harness Runtime — pi Extension
|
|
5
2
|
|
|
6
|
-
|
|
3
|
+
**Status:** v0.2.0 | **RFCs:** 18 defined | **Implementation:** Phase 1-6
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
## Overview
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
pi-harness-runtime is a local-first, provider-agnostic AI coding harness runtime for pi.dev. It coordinates multiple AI models to complete software engineering tasks with minimal human intervention.
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
## Core Architecture
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
```
|
|
12
|
+
Human Requirement
|
|
13
|
+
→ /harness start <requirement>
|
|
14
|
+
→ Master Planner (creates task graph)
|
|
15
|
+
→ Loop Runtime (executes tasks)
|
|
16
|
+
→ Provider Router (selects best model)
|
|
17
|
+
→ BlackBoard (coordination)
|
|
18
|
+
→ Checkpoint Manager (resumability)
|
|
19
|
+
→ Quota Manager (avoid exhaustion)
|
|
20
|
+
→ Scheduler (pause/resume)
|
|
21
|
+
→ Repair Engine (auto-fix failures)
|
|
22
|
+
→ /harness status (report)
|
|
23
|
+
```
|
|
21
24
|
|
|
22
|
-
##
|
|
25
|
+
## Commands
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
### Usage Commands
|
|
28
|
+
|
|
29
|
+
| Command | Description |
|
|
30
|
+
|---------|-------------|
|
|
31
|
+
| `/usage` | Show full status (local tracking + provider mirror) |
|
|
32
|
+
| `/usage sync` | Sync provider quota from console.minimax.io |
|
|
33
|
+
| `/usage today` | Today's usage + 5h window |
|
|
34
|
+
| `/usage week` | This week's usage + lifetime |
|
|
35
|
+
| `/usage reset` | Clear provider mirror |
|
|
36
|
+
|
|
37
|
+
### Harness Commands
|
|
38
|
+
|
|
39
|
+
| Command | Description |
|
|
40
|
+
|---------|-------------|
|
|
41
|
+
| `/harness start <requirement>` | Start a new harness job |
|
|
42
|
+
| `/harness status` | Show current job status |
|
|
43
|
+
| `/harness tasks` | List all tasks |
|
|
44
|
+
| `/harness pause` | Pause the current job |
|
|
45
|
+
| `/harness resume` | Resume a paused job |
|
|
46
|
+
| `/harness cancel` | Cancel the current job |
|
|
47
|
+
|
|
48
|
+
## Job State Machine
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
created → planning → queued → running → testing → reviewing
|
|
52
|
+
↓ ↓ ↓ ↓ ↓
|
|
53
|
+
cancelled blocked waiting_human repairing ready_for_client
|
|
54
|
+
↓ ↓
|
|
55
|
+
paused_quota archived
|
|
30
56
|
```
|
|
31
57
|
|
|
32
|
-
|
|
58
|
+
### State Transitions
|
|
59
|
+
|
|
60
|
+
| From | Valid Transitions |
|
|
61
|
+
|------|-----------------|
|
|
62
|
+
| created | planning |
|
|
63
|
+
| planning | queued, cancelled |
|
|
64
|
+
| queued | running, cancelled, waiting_human |
|
|
65
|
+
| running | testing, reviewing, repairing, paused_quota, blocked, waiting_human, cancelled |
|
|
66
|
+
| testing | reviewing, running, repairing, paused_quota, waiting_human, cancelled |
|
|
67
|
+
| reviewing | repairing, running, ready_for_client, paused_quota, waiting_human, cancelled |
|
|
68
|
+
| repairing | running, testing, reviewing, paused_quota, waiting_human, cancelled |
|
|
69
|
+
| paused_quota | running, waiting_human, cancelled |
|
|
70
|
+
|
|
71
|
+
## Task Graph
|
|
72
|
+
|
|
73
|
+
Tasks are organized as a DAG (Directed Acyclic Graph):
|
|
74
|
+
|
|
75
|
+
- Tasks with dependencies are marked `pending` until all dependencies are `done`
|
|
76
|
+
- Tasks without dependencies start as `ready`
|
|
77
|
+
- Only `ready` tasks are picked for execution
|
|
78
|
+
|
|
79
|
+
## Key Components
|
|
80
|
+
|
|
81
|
+
### JobStateMachine (`harness/job-state-machine.ts`)
|
|
82
|
+
|
|
83
|
+
- Manages job lifecycle states
|
|
84
|
+
- Emits events on every transition
|
|
85
|
+
- Auto-checkpoints to disk
|
|
86
|
+
|
|
87
|
+
### TaskGraphManager (`harness/task-graph.ts`)
|
|
88
|
+
|
|
89
|
+
- DAG-based task representation
|
|
90
|
+
- Tracks task status and dependencies
|
|
91
|
+
- Computes topological execution order
|
|
92
|
+
|
|
93
|
+
### MasterPlanner (`harness/master-planner.ts`)
|
|
94
|
+
|
|
95
|
+
- Converts requirements to task graphs
|
|
96
|
+
- Heuristic planner (no LLM needed)
|
|
97
|
+
- LLM-based planner (with provider config)
|
|
98
|
+
|
|
99
|
+
### RepairEngine (`harness/repair-engine.ts`)
|
|
100
|
+
|
|
101
|
+
- Converts failures to repair tasks
|
|
102
|
+
- Retry policy with exponential backoff
|
|
103
|
+
- Auto-escalation after max retries
|
|
104
|
+
|
|
105
|
+
### SharedBlackboard (`harness/blackboard.ts`)
|
|
33
106
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- Real-time, exact, but only counts THIS pi session
|
|
107
|
+
- File-based agent coordination
|
|
108
|
+
- Next-action queue
|
|
109
|
+
- Agent registry and locks
|
|
38
110
|
|
|
39
|
-
|
|
40
|
-
- Stored at `~/.pi/usage-status/mirror.json`
|
|
41
|
-
- Synced via `/usage sync` form
|
|
42
|
-
- Ground truth for TOTAL quota (across all clients)
|
|
111
|
+
### LoopRuntime (`harness/loop-runtime.ts`)
|
|
43
112
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- Divergence warning if local tracking differs from mirror by >5%
|
|
113
|
+
- Core execution loop
|
|
114
|
+
- Picks ready tasks, executes, tests, reviews
|
|
115
|
+
- Handles failures and quota exhaustion
|
|
48
116
|
|
|
49
|
-
|
|
117
|
+
### ContextWindowManager (`harness/context-window-manager.ts`)
|
|
50
118
|
|
|
51
|
-
-
|
|
52
|
-
-
|
|
119
|
+
- Tracks context usage per provider
|
|
120
|
+
- Warning at 80%, critical at 95%
|
|
121
|
+
- Truncation strategy
|
|
53
122
|
|
|
54
|
-
|
|
123
|
+
### AgentHandoffProtocol (`harness/agent-handoff.ts`)
|
|
55
124
|
|
|
56
|
-
|
|
125
|
+
- Clean agent transitions
|
|
126
|
+
- Context transfer
|
|
127
|
+
- Handoff validation
|
|
57
128
|
|
|
129
|
+
### E2ETestEngine (`harness/e2e/test-engine.ts`)
|
|
130
|
+
|
|
131
|
+
- Scenario-based E2E testing
|
|
132
|
+
- Screenshot/video on failure
|
|
133
|
+
- Playwright runner integration
|
|
134
|
+
|
|
135
|
+
### ProjectDetector (`harness/project-detector/detector.ts`)
|
|
136
|
+
|
|
137
|
+
- Auto-detect: Frappe, Next.js, React, Django, Laravel
|
|
138
|
+
- Seed strategy recommendation
|
|
139
|
+
- E2E strategy recommendation
|
|
140
|
+
|
|
141
|
+
## Providers
|
|
142
|
+
|
|
143
|
+
### Provider Adapter (`packages/providers/adapters.ts`)
|
|
144
|
+
|
|
145
|
+
- MiniMax, OpenAI adapters
|
|
146
|
+
- Unified error parsing
|
|
147
|
+
- Quota signal extraction
|
|
148
|
+
|
|
149
|
+
### Quota Manager (`packages/quota-manager/quota-manager.ts`)
|
|
150
|
+
|
|
151
|
+
- Collects signals from API, Playwright, local estimates
|
|
152
|
+
- Tracks 5h, daily, weekly, monthly windows
|
|
153
|
+
- Selects best available provider
|
|
154
|
+
|
|
155
|
+
### Worktree Manager (`packages/worktree/worktree.ts`)
|
|
156
|
+
|
|
157
|
+
- Git worktree per task
|
|
158
|
+
- Isolated workspaces
|
|
159
|
+
- Diff tracking
|
|
160
|
+
|
|
161
|
+
## Usage Example
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// Start a new job
|
|
165
|
+
/harness start Build a REST API with authentication
|
|
166
|
+
|
|
167
|
+
// Monitor progress
|
|
168
|
+
/harness status
|
|
169
|
+
/harness tasks
|
|
170
|
+
|
|
171
|
+
// Pause when quota is low
|
|
172
|
+
/harness pause
|
|
173
|
+
|
|
174
|
+
// Resume when quota resets
|
|
175
|
+
/harness resume
|
|
176
|
+
|
|
177
|
+
// Cancel if needed
|
|
178
|
+
/harness cancel
|
|
58
179
|
```
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
Weekly limit: [████████████████░░░░] 81% left (resets in 2d 13h)
|
|
75
|
-
|
|
76
|
-
③ LOCAL RESET TIMES (derived from your data)
|
|
77
|
-
Local 5h reset: in 3h 12m (oldest request falls out of window)
|
|
78
|
-
Local week reset: in 5d 7h (oldest request falls out of window)
|
|
79
|
-
Local-vs-mirror: -12.4% ⚠️ divergence > 5%
|
|
80
|
-
Burn rate: 11.4% / day → 100% in 2.5 d
|
|
81
|
-
────────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
## Data Directory
|
|
182
|
+
|
|
183
|
+
All harness data is stored in `~/.pi/harness/`:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
~/.pi/harness/
|
|
187
|
+
jobs/
|
|
188
|
+
<job-id>/
|
|
189
|
+
checkpoint.json
|
|
190
|
+
events.jsonl
|
|
191
|
+
task-graph.json
|
|
192
|
+
blackboard/
|
|
193
|
+
repair-tasks.jsonl
|
|
194
|
+
handoffs/
|
|
82
195
|
```
|
|
83
196
|
|
|
84
|
-
##
|
|
197
|
+
## Testing
|
|
198
|
+
|
|
199
|
+
Run tests with:
|
|
85
200
|
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
|
|
89
|
-
- **Idempotent** — running `/usage` repeatedly has no side effects
|
|
90
|
-
- **Privacy-respecting** — all data stays on local disk
|
|
201
|
+
```bash
|
|
202
|
+
pi-harness-runtime@latest
|
|
203
|
+
```
|
|
91
204
|
|
|
92
|
-
## Related
|
|
205
|
+
## Related Skills
|
|
93
206
|
|
|
94
|
-
- `
|
|
95
|
-
-
|
|
207
|
+
- `form-state-persistence-fix` — Step data persistence patterns
|
|
208
|
+
- `frappe-gl-preview` — GL entry preview pattern
|
|
209
|
+
- `frappe-workflow` — Workflow debugging patterns
|