pi-harness-runtime 0.2.0 → 0.3.1

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
@@ -1,207 +1,182 @@
1
1
  # Pi Harness Runtime
2
2
 
3
- **Codex-style `/usage` status for pi coding agent: local token tracking + manual provider mirror.**
4
-
5
- [![npm version](https://img.shields.io/npm/v/pi-harness-runtime?style=for-the-badge)](https://www.npmjs.com/package/pi-harness-runtime)
3
+ [![Beta](https://img.shields.io/badge/version-beta-orange?style=for-the-badge)](https://github.com/ManotLuijiu/pi-harness-runtime)
4
+ [![npm downloads](https://img.shields.io/npm/dm/pi-harness-runtime?style=for-the-badge)](https://www.npmjs.com/package/pi-harness-runtime)
6
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT)
7
- [![Platform](https://img.shields.io/badge/Platform-macOS%20|20|Linux-blue?style=for-the-badge)]()
6
+ [![Platform](https://img.shields.io/badge/Platform-macOS%20|Linux-blue?style=for-the-badge)]()
8
7
 
9
- ## Why Pi Harness Runtime
8
+ > ⚠️ **⚠️ Beta Notice — Not Production Ready ⚠️**
9
+ > This project is in active development. Core features work but E2E testing is not yet implemented.
10
+ > **148+ downloads** and counting — we appreciate the interest! Help us test by filing issues.
10
11
 
11
- **3-Source Honest Tracking** Codex-style `/status` for pi: local-tracked tokens + manually-mirrored provider quota + derived projections. No fake precision, no scraping, no password storage.
12
+ **Autonomous AI coding harness for pi: local token tracking + provider mirror + task orchestration.**
12
13
 
13
- **Zero Config** — Works immediately. Auto-tracks every assistant message. Run `/usage sync` once a day to mirror provider quota from `https://platform.minimax.io/console/usage`.
14
+ ## Two Flavors
14
15
 
15
- **Local-First Privacy** — All data stays in `~/.pi/usage-status/`. No telemetry, no remote calls, no credential storage. The provider mirror is a 5-second manual entry.
16
+ ### 1. `/usage` Token Tracking (stable ✅)
16
17
 
17
- **Derived Insights** — Auto-computes reset times from your data (no provider API needed), burn rate projections, local-vs-mirror divergence warnings.
18
+ Codex-style usage tracking with local tokens + provider mirror.
18
19
 
19
- ## Install
20
+ ### 2. `/harness` — Autonomous Coding Loop (beta 🔨)
20
21
 
21
- ```bash
22
- pi install npm:pi-harness-runtime
23
- ```
22
+ Give the runtime a requirement. It keeps working, survives interruption, and returns with code ready for review.
24
23
 
25
- Or install locally for development:
24
+ ## Install
26
25
 
27
26
  ```bash
28
- git clone https://github.com/ManotLuijiu/pi-harness-runtime.git
29
- ln -s pi-harness-runtime ~/.pi/agent/extensions/pi-harness-runtime
30
- pi reload
27
+ pi install npm:pi-harness-runtime
31
28
  ```
32
29
 
33
- **Important**: Symlink the **directory** into `~/.pi/agent/extensions/`. Pi's loader scans that directory and reads `package.json`'s `pi.extensions` field for multi-file extensions. Don't symlink a single file (relative imports break) or symlink into `node_modules/` (pi doesn't auto-scan there).
34
-
35
- No API keys required. No build step (Bun runs `.ts` directly).
36
-
37
30
  Requires Pi v0.37.3+.
38
31
 
39
- ## Quick Start
32
+ ## Usage Commands
40
33
 
41
34
  ```bash
42
- /usage # show full status (model, local tracking, provider mirror)
43
- /usage sync # open form to mirror provider-side quota
44
- /usage today # focused: today's usage + 5h window
45
- /usage week # focused: this week's usage + lifetime totals
46
- /usage reset # clear provider mirror (force re-sync)
35
+ /usage # show full status (local tracking + provider mirror)
36
+ /usage sync # mirror provider quota from console.minimax.io
37
+ /usage today # today's usage + 5h window
38
+ /usage week # this week's usage + lifetime totals
39
+ /usage reset # clear provider mirror
47
40
  ```
48
41
 
49
- ## Commands
42
+ ## Harness Commands
50
43
 
51
- ### `/usage`
44
+ ```bash
45
+ /harness start <requirement> # Start a new harness job (beta)
46
+ /harness status # Show current job status
47
+ /harness tasks # List all tasks
48
+ /harness pause # Pause when quota low
49
+ /harness resume # Resume after quota reset
50
+ /harness cancel # Cancel job
51
+ ```
52
52
 
53
- Show Codex-style usage status. Renders three sections:
53
+ ## Example Session
54
54
 
55
55
  ```
56
- Codex-style usage status for pi
57
- ────────────────────────────────────────────────────────────────
58
- Model: minimax/MiniMax-M3
59
- Directory: ~/frappe-bench/apps/thai_business_suite
60
-
61
- LOCAL TRACKED (ground truth we count this)
62
- This session: $0.17 · 142k tokens · 17 requests
63
- This 5h: 384k tokens · 23 requests · $0.04
64
- This week: 1.2M tokens · 67 requests · $0.13
65
- Lifetime: 4592 requests · $81.61
66
-
67
- ② PROVIDER MIRROR (you enter from console.minimax.io)
68
- Last sync: 2 min ago [fresh]
69
- Provider: minimax
70
- 5h limit: [████████░░░░░░░░░░░░] 18% left (resets in 4h 54m)
71
- Weekly limit: [████████████████░░░░] 81% left (resets in 2d 13h)
72
-
73
- ③ LOCAL RESET TIMES (derived from your data)
74
- Local 5h reset: in 3h 12m (oldest request falls out of window)
75
- Local week reset: in 5d 7h (oldest request falls out of window)
76
- Local-vs-mirror: -12.4% ⚠️ divergence > 5%
77
- Burn rate: 11.4% / day → 100% in 2.5 d
78
- ────────────────────────────────────────────────────────────────
56
+ /harness start Build a REST API with JWT authentication
57
+
58
+ # Runtime creates task graph:
59
+ # task-001: Analyze requirements → ready
60
+ # task-002: Implement API → depends on task-001
61
+ # task-003: Write tests depends on task-002
62
+ # task-004: Code review depends on task-003
63
+
64
+ /harness status
65
+ # Job: job-123, Status: running
66
+ # Tasks: 1/4 done, 1 running
67
+
68
+ # When quota runs low:
69
+ /harness pause
70
+ # Job paused. Resume when quota resets.
71
+
72
+ # When quota resets:
73
+ /harness resume
74
+ # Job resumed. Continue working.
75
+
76
+ # When all tasks complete:
77
+ /harness status
78
+ # Job: job-123, Status: ready_for_client
79
+ # Tasks: 4/4 done ✓
79
80
  ```
80
81
 
81
- ### `/usage sync`
82
-
83
- Open a 6-prompt form to mirror provider-side quota:
82
+ ## Architecture
84
83
 
85
84
  ```
86
- 5h used % (0-100) for minimax: [18]
87
- 5h resets in (hours): [4]
88
- 5h resets in (minutes, 0-59): [56]
89
- Weekly used % (0-100): [72]
90
- Weekly resets in (days, 0-7): [2]
91
- Weekly resets in (hours, 0-23): [13]
85
+ pi-harness-runtime/
86
+ ├── index.ts # Extension entry point
87
+ ├── harness/
88
+ │ ├── job-state-machine.ts # 14-state lifecycle machine
89
+ │ ├── task-graph.ts # DAG-based task management
90
+ │ ├── master-planner.ts # Requirement task graph
91
+ │ ├── loop-runtime.ts # Core execution loop
92
+ │ ├── repair-engine.ts # Auto-fix failures
93
+ │ ├── blackboard.ts # Agent coordination
94
+ │ ├── context-window-manager.ts # Context tracking
95
+ │ ├── agent-handoff.ts # Clean agent transitions
96
+ │ ├── e2e/
97
+ │ │ ├── test-engine.ts # E2E test runner (coming soon)
98
+ │ │ └── playwright-runner.ts # Browser automation
99
+ │ └── project-detector/
100
+ │ └── detector.ts # Auto-detect project type
101
+ ├── packages/
102
+ │ ├── providers/adapters.ts # MiniMax, OpenAI adapters
103
+ │ ├── quota-manager/ # Quota signal collection
104
+ │ └── worktree/ # Git worktree per task
105
+ └── skills/
106
+ └── harness-runtime/
107
+ └── SKILL.md # Skill documentation
92
108
  ```
93
109
 
94
- Saves to `~/.pi/usage-status/mirror.json` and updates the next `/usage` output.
95
-
96
- ### `/usage today`
97
-
98
- Focused view: today's usage + 5h window. Quick check before starting a long session.
99
-
100
- ### `/usage week`
101
-
102
- Focused view: this week's usage + lifetime totals. Good for end-of-week review.
103
-
104
- ### `/usage reset`
105
-
106
- Clear the provider mirror. Local usage log is preserved. Asks for confirmation.
107
-
108
- ## How It Works
110
+ ## Job State Machine
109
111
 
110
- ### Data Sources (3-source model)
111
-
112
- 1. **Local tracked** — every assistant message is logged to `~/.pi/usage-status/usage.jsonl`
113
- - Auto-tracked via `message_end` event
114
- - Contains: timestamp, model, input/output/cache tokens, cost
115
- - Real-time, exact, but only counts THIS pi session
116
-
117
- 2. **Provider mirror** — manually entered from `https://platform.minimax.io/console/usage`
118
- - Stored at `~/.pi/usage-status/mirror.json`
119
- - Synced via `/usage sync` form
120
- - Ground truth for TOTAL quota (across all clients)
121
-
122
- 3. **Derived** — burn rate, reset times, divergence
123
- - Local reset time = oldest request in window + window duration
124
- - Burn rate = mirror weekly % / elapsed days
125
- - Divergence warning if local tracking differs from mirror by >5%
126
-
127
- ### Files Written
128
-
129
- | Path | Contents |
130
- |---|---|
131
- | `~/.pi/usage-status/usage.jsonl` | Append-only usage log |
132
- | `~/.pi/usage-status/mirror.json` | Manual provider mirror |
133
- | (none — pure functions) | CLI helpers in `cli.ts` |
134
-
135
- Override location with `PI_USAGE_DIR` env var (useful for testing).
136
-
137
- ### What's NOT Stored
138
-
139
- - Your password (we don't ask for it, ever)
140
- - Provider session tokens (we don't read browser cookies)
141
- - Telemetry or remote calls (all local)
142
- - Auto-send quota (you always confirm any sync)
143
-
144
- ## Why Manual Mirror?
112
+ ```
113
+ created → planning → queued → running → testing → reviewing
114
+ ↓ ↓ ↓ ↓ ↓
115
+ cancelled blocked waiting_human repairing ready_for_client
116
+ ↓ ↓
117
+ paused_quota archived
118
+ ```
145
119
 
146
- Most AI providers (MiniMax, Anthropic, OpenAI) don't expose rate limit headers publicly. Two alternatives exist:
120
+ ## Key Features
147
121
 
148
- | Approach | Pros | Cons |
149
- |---|---|---|
150
- | **Manual mirror** (this) | Zero security risk, 5 sec/day | 1 manual entry |
151
- | **Browser cookie extraction** (pi-web-access does for Gemini Web) | Fully automatic | ~400 LOC platform-specific code, fragile to auth changes |
152
- | **HTML scraping** (assumes cookies work) | Once cookies extracted, parsing is easy | Breaks if page redesigns |
122
+ - **Resumable**: Every state change is checkpointed to disk
123
+ - **Quota-aware**: Detects quota exhaustion, pauses, resumes after reset
124
+ - **Provider-agnostic**: MiniMax, OpenAI, Claude adapters
125
+ - **Task DAG**: Dependencies tracked, topological execution
126
+ - **Auto-repair**: Failure classification + retry with exponential backoff
127
+ - **E2E testing**: Scenario-based Playwright integration (coming soon)
128
+ - **Project detection**: Auto-detects Frappe, Next.js, React, Django, Laravel
153
129
 
154
- We chose the manual approach because:
130
+ ## Data Directory
155
131
 
156
- 1. MiniMax's UI shows reset times like "Resets in 4 hr 56 min" — manual entry is genuinely 5 seconds
157
- 2. Cookie extraction has ongoing maintenance burden
158
- 3. Password storage is a security anti-pattern
132
+ All data stored locally in `~/.pi/`:
159
133
 
160
- If you find yourself needing to sync more than 3x/day, we may add browser cookie extraction as a future enhancement.
134
+ ```
135
+ ~/.pi/
136
+ ├── usage-status/ # /usage data
137
+ │ ├── usage.jsonl
138
+ │ └── mirror.json
139
+ └── harness/ # /harness data
140
+ └── jobs/
141
+ └── <job-id>/
142
+ ├── checkpoint.json
143
+ ├── events.jsonl
144
+ ├── task-graph.json
145
+ ├── blackboard/
146
+ └── repair-tasks.jsonl
147
+ ```
161
148
 
162
149
  ## Safety Properties
163
150
 
164
- - ✅ **No auto-tracking of other clients** — local data is just this pi session
165
- - ✅ **No scraping** provider mirror is manual
166
- - ✅ **No fabrication** divergence warning if local and mirror disagree
167
- - ✅ **Idempotent** running `/usage` repeatedly has no side effects
168
- - ✅ **Privacy-respecting** all data stays on local disk
169
- - ✅ **No credentials stored** — passwords, tokens, cookies: none
151
+ - ✅ **Local-first**: All data stays on disk
152
+ - ✅ **No credentials stored**: No passwords, tokens, or cookies
153
+ - ✅ **Human-on-the-loop**: Clear intervention points
154
+ - ✅ **Checkpointed**: Resume from any state
155
+ - ✅ **Idempotent**: Safe to run multiple times
170
156
 
171
- ## Architecture
157
+ ## Testing
172
158
 
173
- ```
174
- pi-harness-runtime/
175
- ├── index.ts # entry — exports default (pi: ExtensionAPI)
176
- ├── tracker.ts # UsageTracker — JSONL writer/reader
177
- ├── mirror.ts # MirrorStore — JSON read/write + freshness check
178
- ├── windows.ts # WindowAggregator — pure aggregation functions
179
- ├── renderer.ts # StatusRenderer — Codex-style progress bars
180
- ├── sync-form.ts # /usage sync form handling
181
- ├── cli.ts # shared pure helpers (paths, formatting, IO)
182
- ├── skills/
183
- │ └── harness-runtime/
184
- │ └── SKILL.md # bundled skill (auto-loaded)
185
- ├── test/ # node --test
186
- │ ├── cli.test.mjs
187
- │ ├── tracker.test.mjs
188
- │ ├── mirror.test.mjs
189
- │ ├── windows.test.mjs
190
- │ ├── renderer.test.mjs
191
- │ └── sync-form.test.mjs
192
- ├── package.json # "pi" field declares extensions + skills
193
- ├── README.md
194
- ├── CHANGELOG.md
195
- ├── LICENSE # MIT
196
- └── .gitignore
159
+ ```bash
160
+ bun test # 131+ tests passing
197
161
  ```
198
162
 
199
- ## Related
163
+ ## Roadmap
200
164
 
201
- - [pi-web-access](https://github.com/nicobailon/pi-web-access) web search + URL fetching for pi (inspiration for this package's structure)
202
- - [context-mode](https://github.com/MiniMax-AI/context-mode) context-window tracking (complementary)
203
- - [pi-coding-agent](https://github.com/earendil-works/pi-coding-agent) the underlying pi agent
165
+ - [x] Core harness infrastructure (job state machine, task graph)
166
+ - [x] Master planner
167
+ - [x] Repair engine
168
+ - [x] Shared blackboard
169
+ - [ ] E2E test engine integration
170
+ - [ ] MiniMax Web automatic quota detection
171
+ - [ ] Claude adapter
172
+ - [ ] Production stress testing
204
173
 
205
174
  ## License
206
175
 
207
176
  MIT © 2026 MooCoding
177
+
178
+ ## Related
179
+
180
+ - [pi-coding-agent](https://github.com/earendil-works/pi-coding-agent) — Underlying pi agent
181
+ - [context-mode](https://github.com/MiniMax-AI/context-mode) — Context window tracking
182
+ - [pi-web-access](https://github.com/nicobailon/pi-web-access) — Web search for pi
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Agent Handoff Protocol — RFC-0012
3
+ *
4
+ * Clean handoff between agents with context transfer.
5
+ * Ensures continuity when switching agents mid-task.
6
+ */
7
+
8
+ import type {
9
+ HandoffContext,
10
+ HandoffEvent,
11
+ } from "../packages/types/src/runtime-types.ts";
12
+ import { writeJson, readJson } from "../cli.ts";
13
+ // @ts-expect-error - Bun has built-in Node.js types
14
+ import { join } from "node:path";
15
+
16
+ export interface HandoffData {
17
+ fromAgent: string;
18
+ toAgent: string;
19
+ taskId: string;
20
+ taskSummary: string;
21
+ contextFiles: string[];
22
+ recentHistory: string[];
23
+ currentState: Record<string, unknown>;
24
+ }
25
+
26
+ export class AgentHandoffProtocol {
27
+ private readonly rootDir: string;
28
+
29
+ constructor(rootDir: string) {
30
+ this.rootDir = rootDir;
31
+ }
32
+
33
+ /**
34
+ * Create a handoff context for switching agents
35
+ */
36
+ createHandoff(
37
+ jobId: string,
38
+ taskId: string,
39
+ fromAgent: string,
40
+ toAgent: string,
41
+ currentState?: Record<string, unknown>,
42
+ ): HandoffContext {
43
+ const events = this.loadHandoffHistory(jobId, taskId);
44
+
45
+ return {
46
+ jobId,
47
+ taskId,
48
+ fromAgent,
49
+ toAgent,
50
+ sharedFiles: [],
51
+ taskHistory: events,
52
+ summary: this.generateSummary(taskId, currentState),
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Record a handoff event
58
+ */
59
+ recordHandoff(context: HandoffContext, result?: string): void {
60
+ const path = join(
61
+ this.rootDir,
62
+ "jobs",
63
+ context.jobId,
64
+ "handoffs",
65
+ `${context.taskId}.json`,
66
+ );
67
+ const event: HandoffEvent = {
68
+ ts: new Date().toISOString(),
69
+ agentId: context.toAgent,
70
+ action: "handoff_received",
71
+ result,
72
+ };
73
+ context.taskHistory.push(event);
74
+ writeJson(path, context);
75
+ }
76
+
77
+ /**
78
+ * Generate handoff prompt for the receiving agent
79
+ */
80
+ generateHandoffPrompt(context: HandoffContext): string {
81
+ const lines = [
82
+ `## Agent Handoff`,
83
+ ``,
84
+ `**From Agent:** ${context.fromAgent}`,
85
+ `**To Agent:** ${context.toAgent}`,
86
+ `**Task:** ${context.taskId}`,
87
+ ``,
88
+ `### Task History`,
89
+ ];
90
+
91
+ for (const event of context.taskHistory) {
92
+ lines.push(`- [${event.ts}] ${event.agentId}: ${event.action}`);
93
+ if (event.result) {
94
+ lines.push(` Result: ${event.result}`);
95
+ }
96
+ }
97
+
98
+ lines.push(``);
99
+ lines.push(`### Summary`);
100
+ lines.push(context.summary);
101
+
102
+ if (context.sharedFiles.length > 0) {
103
+ lines.push(``);
104
+ lines.push(`### Shared Files`);
105
+ for (const file of context.sharedFiles) {
106
+ lines.push(`- ${file}`);
107
+ }
108
+ }
109
+
110
+ return lines.join("\n");
111
+ }
112
+
113
+ /**
114
+ * Validate handoff readiness
115
+ */
116
+ validateHandoff(context: HandoffContext): {
117
+ valid: boolean;
118
+ issues: string[];
119
+ } {
120
+ const issues: string[] = [];
121
+
122
+ if (!context.summary) {
123
+ issues.push("Task summary is empty");
124
+ }
125
+
126
+ if (context.taskHistory.length === 0) {
127
+ issues.push("No task history recorded");
128
+ }
129
+
130
+ // Check for recent handoffs
131
+ const recentHandoffs = context.taskHistory.filter((h) => {
132
+ const age = Date.now() - Date.parse(h.ts);
133
+ return age < 5 * 60 * 1000; // 5 minutes
134
+ });
135
+
136
+ if (recentHandoffs.length > 3) {
137
+ issues.push(
138
+ `Too many recent handoffs (${recentHandoffs.length}). Possible ping-pong.`,
139
+ );
140
+ }
141
+
142
+ return { valid: issues.length === 0, issues };
143
+ }
144
+
145
+ /**
146
+ * Load handoff history for a task
147
+ */
148
+ private loadHandoffHistory(jobId: string, taskId: string): HandoffEvent[] {
149
+ const path = join(
150
+ this.rootDir,
151
+ "jobs",
152
+ jobId,
153
+ "handoffs",
154
+ `${taskId}.json`,
155
+ );
156
+ const data = readJson(path) as HandoffContext | null;
157
+ return data?.taskHistory ?? [];
158
+ }
159
+
160
+ /**
161
+ * Generate a summary of the task state
162
+ */
163
+ private generateSummary(
164
+ taskId: string,
165
+ currentState?: Record<string, unknown>,
166
+ ): string {
167
+ if (!currentState) {
168
+ return `Task ${taskId} requires continuation. Check task files for current state.`;
169
+ }
170
+
171
+ const lines = [`Task ${taskId} is in progress.`];
172
+
173
+ if (currentState.filesModified) {
174
+ lines.push(
175
+ `Files modified: ${(currentState.filesModified as string[]).join(", ")}`,
176
+ );
177
+ }
178
+
179
+ if (currentState.lastAction) {
180
+ lines.push(`Last action: ${currentState.lastAction}`);
181
+ }
182
+
183
+ if (currentState.blockers) {
184
+ lines.push(`Blockers: ${currentState.blockers}`);
185
+ }
186
+
187
+ return lines.join("\n");
188
+ }
189
+ }