@zachjxyz/moxie 0.3.5 → 0.3.8

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
@@ -66,7 +66,7 @@ Dependencies:
66
66
 
67
67
  Agents (from .moxie/config.toml):
68
68
  [OK] claude — 2.1.91 (Claude Code)
69
- [OK] codexcodex-cli 0.118.0
69
+ [OK] gpt-gwopenai/gpt-5.4 (AI Gateway, key stored)
70
70
 
71
71
  Project:
72
72
  [OK] .moxie/ directory found
@@ -151,7 +151,7 @@ Each phase uses round-robin rotation with randomized order. Agents independently
151
151
 
152
152
  Quorum after claude's turn:
153
153
  claude pending .moxie/phases/rfc/claude-041126-142611.md
154
- codex pending .moxie/phases/rfc/codex-041126-141850.md
154
+ gpt-gw pending -
155
155
  ── 0/2 agents reached quorum
156
156
  ```
157
157
 
@@ -161,6 +161,7 @@ Quorum after claude's turn:
161
161
  - After each turn, the agent marks whether it agrees with the current state (`reached: true`)
162
162
  - If any agent finds issues, it resets all agents to `reached: false`
163
163
  - A phase completes when all healthy agents simultaneously agree
164
+ - Agents are told how many turns remain — as the countdown decreases, they prioritize substantive issues over minor nitpicks
164
165
 
165
166
  ```
166
167
  ╔════════════════════════════════════════════════════════════╗
@@ -172,6 +173,8 @@ Quorum after claude's turn:
172
173
 
173
174
  ### Safeguards
174
175
 
176
+ **Turn pressure** — Every agent prompt includes the current turn count and how many remain. As rounds run low, agents naturally triage harder — accepting minor incompletes rather than resetting quorum over stylistic concerns.
177
+
175
178
  **Dead agent detection** — If an agent fails 2 consecutive turns (timeout, empty output, quota exhaustion), it's automatically removed from rotation. Quorum adjusts to the remaining healthy agents.
176
179
 
177
180
  ```
@@ -207,6 +210,39 @@ moxie agents List configured agents
207
210
  moxie doctor Check dependencies, agents, and project health
208
211
  ```
209
212
 
213
+ ## Cost tracking
214
+
215
+ `moxie cost` shows token usage per phase and agent:
216
+
217
+ ```
218
+ Token usage by phase:
219
+
220
+ RFC:
221
+ claude-gw 950,980 tokens
222
+ gpt-gw 505,430 tokens
223
+ qwen-gw 271,317 tokens
224
+ TOTAL 1,727,727 tokens
225
+
226
+ Grand total:
227
+ 1,727,727 tokens
228
+ ```
229
+
230
+ For gateway agents, moxie also pulls actual USD costs from the [Vercel AI Gateway Custom Reporting API](https://vercel.com/docs/ai-gateway/capabilities/custom-reporting) (requires Pro or Enterprise plan, currently in beta):
231
+
232
+ ```
233
+ Gateway cost (via Vercel AI Gateway):
234
+ claude-gw $ 5.38 (1,121,678 input + 26,964 output tokens)
235
+ gpt-gw $ 0.17 (32,021 input + 457 output tokens)
236
+ qwen-gw $ 1.27 (349,598 input + 12,723 output tokens)
237
+ TOTAL $ 6.82
238
+
239
+ Dashboard: https://vercel.com/~/ai
240
+ ```
241
+
242
+ Each `moxie init` generates a unique run ID. All gateway requests are tagged with the run ID plus `project:moxie`, `phase:<name>`, and `agent:<name>`, so costs are scoped to the exact session — no date range issues for overnight or multi-day runs.
243
+
244
+ If the reporting API is unavailable, `moxie cost` shows token counts only — you can always check your spend in the [AI Gateway dashboard](https://vercel.com/docs/ai-gateway) directly.
245
+
210
246
  ## Config
211
247
 
212
248
  After `moxie init`, your `.moxie/config.toml` looks like:
@@ -217,6 +253,7 @@ path = "spec.md"
217
253
 
218
254
  [gateway]
219
255
  endpoint = "https://ai-gateway.vercel.sh"
256
+ run_id = "run-20260411-204514-93675"
220
257
 
221
258
  [agents.claude]
222
259
  command = "claude --dangerously-skip-permissions --effort max --output-format json -p"
@@ -235,38 +272,6 @@ turn_timeout = 900
235
272
 
236
273
  Edit this file to change agents, adjust timeouts, or add custom agent commands.
237
274
 
238
- ## Cost tracking
239
-
240
- `moxie cost` shows token usage per phase and agent:
241
-
242
- ```
243
- Token usage by phase:
244
-
245
- RFC:
246
- claude 12,655,300 tokens (+1 unknown)
247
- codex 8,535,058 tokens
248
- qwen 2,484,379 tokens (+2 unknown)
249
- TOTAL 23,674,737 tokens
250
-
251
- Grand total:
252
- 23,674,737 tokens
253
- ```
254
-
255
- For gateway agents, moxie also pulls actual USD costs from the [Vercel AI Gateway Custom Reporting API](https://vercel.com/docs/ai-gateway/capabilities/custom-reporting) (requires Pro or Enterprise plan, currently in beta):
256
-
257
- ```
258
- Gateway cost (via Vercel AI Gateway):
259
- claude-gw $4.82 (1,245,890 input + 389,201 output tokens)
260
- gpt-gw $3.17 (987,654 input + 234,567 output tokens)
261
- TOTAL $7.99
262
-
263
- Dashboard: https://vercel.com/~/ai
264
- ```
265
-
266
- Gateway requests are automatically tagged with `project:moxie`, `phase:<name>`, and `agent:<name>` so you can filter and drill down in the [AI Gateway dashboard](https://vercel.com/docs/ai-gateway).
267
-
268
- If you're on the free tier or the reporting API is unavailable, `moxie cost` shows token counts only — you can always check your spend in the AI Gateway dashboard directly.
269
-
270
275
  ## Platform support
271
276
 
272
277
  | Platform | Status |
package/bin/moxie CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  set -euo pipefail
18
18
 
19
- MOXIE_VERSION="0.3.5"
19
+ MOXIE_VERSION="0.3.8"
20
20
  # Resolve symlinks (npm installs bin as a symlink)
21
21
  _self="$0"
22
22
  while [ -L "$_self" ]; do
package/lib/agents.sh CHANGED
@@ -271,9 +271,10 @@ dispatch_agent() {
271
271
 
272
272
  # Gateway agent: dispatch via Node.js script
273
273
  if _is_gateway_agent "$agent"; then
274
- local model endpoint api_key
274
+ local model endpoint api_key run_id
275
275
  model=$(_agent_model "$agent")
276
276
  endpoint=$(toml_get "$MOXIE_CONFIG" "gateway.endpoint" "https://ai-gateway.vercel.sh")
277
+ run_id=$(toml_get "$MOXIE_CONFIG" "gateway.run_id" "")
277
278
  api_key=$(gateway_get_key "vercel-ai-gateway") || {
278
279
  echo "ERROR: Gateway API key not found. Run 'moxie init' to configure." >&2
279
280
  return 1
@@ -285,6 +286,7 @@ dispatch_agent() {
285
286
  GATEWAY_MODEL="$model" \
286
287
  GATEWAY_PHASE="$phase" \
287
288
  GATEWAY_AGENT="$agent" \
289
+ GATEWAY_RUN_ID="$run_id" \
288
290
  timeout "$timeout_secs" \
289
291
  node "$MOXIE_ROOT/lib/gateway-agent.mjs" "$(cat "$prompt_file")" || {
290
292
  local rc=$?
@@ -18,6 +18,7 @@ const MAX_TURNS = parseInt(process.env.GATEWAY_MAX_TURNS || '50', 10);
18
18
  const IDLE_TIMEOUT_MS = parseInt(process.env.GATEWAY_IDLE_TIMEOUT || '120000', 10);
19
19
  const PHASE = process.env.GATEWAY_PHASE || 'unknown';
20
20
  const AGENT_NAME = process.env.GATEWAY_AGENT || 'unknown';
21
+ const RUN_ID = process.env.GATEWAY_RUN_ID || '';
21
22
  const CWD = process.cwd();
22
23
 
23
24
  if (!API_KEY) { process.stderr.write('ERROR: GATEWAY_API_KEY not set\n'); process.exit(1); }
@@ -310,7 +311,7 @@ function chatCompletion(messages) {
310
311
  stream_options: { include_usage: true },
311
312
  providerOptions: {
312
313
  gateway: {
313
- tags: [`project:moxie`, `phase:${PHASE}`, `agent:${AGENT_NAME}`],
314
+ tags: [`project:moxie`, `phase:${PHASE}`, `agent:${AGENT_NAME}`, ...(RUN_ID ? [`run:${RUN_ID}`] : [])],
314
315
  user: 'moxie',
315
316
  },
316
317
  },
@@ -7,18 +7,34 @@ import https from 'https';
7
7
  import http from 'http';
8
8
 
9
9
  const API_KEY = process.env.GATEWAY_API_KEY;
10
+ const RUN_ID = process.env.GATEWAY_RUN_ID || '';
10
11
  const endpoint = process.argv[2] || 'https://ai-gateway.vercel.sh';
11
- const startDate = process.argv[3];
12
- const endDate = process.argv[4];
12
+ const startDate = process.argv[3] || '';
13
+ const endDate = process.argv[4] || '';
13
14
 
14
15
  if (!API_KEY) { process.stderr.write('ERROR: GATEWAY_API_KEY not set\n'); process.exit(1); }
15
- if (!startDate || !endDate) { process.stderr.write('ERROR: start_date and end_date required\n'); process.exit(1); }
16
16
 
17
17
  const url = new URL(`${endpoint}/v1/report`);
18
- url.searchParams.set('start_date', startDate);
19
- url.searchParams.set('end_date', endDate);
20
18
  url.searchParams.set('group_by', 'tag');
21
- url.searchParams.set('tags', 'project:moxie');
19
+
20
+ if (RUN_ID) {
21
+ // Filter by run ID — captures the full session regardless of dates
22
+ url.searchParams.set('tags', `run:${RUN_ID}`);
23
+ // Still need dates — use a wide window
24
+ const now = new Date();
25
+ const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
26
+ const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
27
+ url.searchParams.set('start_date', thirtyDaysAgo.toISOString().slice(0, 10));
28
+ url.searchParams.set('end_date', tomorrow.toISOString().slice(0, 10));
29
+ } else if (startDate && endDate) {
30
+ // Fallback: filter by date range + project tag
31
+ url.searchParams.set('start_date', startDate);
32
+ url.searchParams.set('end_date', endDate);
33
+ url.searchParams.set('tags', 'project:moxie');
34
+ } else {
35
+ process.stderr.write('ERROR: Either GATEWAY_RUN_ID or start_date/end_date required\n');
36
+ process.exit(1);
37
+ }
22
38
 
23
39
  const isHttps = url.protocol === 'https:';
24
40
  const mod = isHttps ? https : http;
package/lib/phases.sh CHANGED
@@ -425,9 +425,12 @@ HEADER
425
425
 
426
426
  # Gateway section (if any gateway models selected)
427
427
  if [ ${#SELECTED_GATEWAY_INDICES[@]} -gt 0 ]; then
428
- cat >> "$config_file" <<'GATEWAY'
428
+ local run_id
429
+ run_id="run-$(date +%Y%m%d-%H%M%S)-$$"
430
+ cat >> "$config_file" <<GATEWAY
429
431
  [gateway]
430
432
  endpoint = "https://ai-gateway.vercel.sh"
433
+ run_id = "$run_id"
431
434
 
432
435
  GATEWAY
433
436
  fi
@@ -805,13 +808,18 @@ _phase_is_complete() {
805
808
  # Shuffle AGENT_NAMES array in-place using Fisher-Yates.
806
809
  # Compatible with Bash 3.2 (no $RANDOM % n bias issues for small arrays).
807
810
  _shuffle_agents() {
808
- local i len tmp j
811
+ local i len tmp tmp_cmd j
809
812
  len=${#AGENT_NAMES[@]}
810
813
  for (( i = len - 1; i > 0; i-- )); do
811
814
  j=$(( RANDOM % (i + 1) ))
815
+ # Swap names
812
816
  tmp="${AGENT_NAMES[$i]}"
813
817
  AGENT_NAMES[$i]="${AGENT_NAMES[$j]}"
814
818
  AGENT_NAMES[$j]="$tmp"
819
+ # Swap commands to keep in sync
820
+ eval "tmp_cmd=\"\$AGENT_CMD_$i\""
821
+ eval "AGENT_CMD_$i=\"\$AGENT_CMD_$j\""
822
+ eval "AGENT_CMD_$j=\"$tmp_cmd\""
815
823
  done
816
824
  }
817
825
 
@@ -1272,13 +1280,44 @@ _run_phase() {
1272
1280
  fi
1273
1281
  fi
1274
1282
 
1275
- # Hydrate prompt with agent name and project dir
1283
+ # Hydrate prompt with agent name, project dir, and tiered turn pressure
1284
+ local remaining=$(( max_rounds - round ))
1285
+ local pct_remaining=$(( (remaining * 100) / max_rounds ))
1286
+ local turn_pressure=""
1287
+
1288
+ if [ "$pct_remaining" -gt 60 ]; then
1289
+ # Early: full rigor, gentle awareness
1290
+ turn_pressure="This is turn $round of $max_rounds ($remaining turns remaining). Focus on thoroughness and correctness. Flag all issues you find — there is time to resolve them."
1291
+ elif [ "$pct_remaining" -gt 30 ]; then
1292
+ # Middle: triage mode
1293
+ turn_pressure="This is turn $round of $max_rounds ($remaining turns remaining). Quorum has not been reached. Prioritize issues that would cause real problems in downstream phases. Do not reset quorum over stylistic preferences, speculative concerns, or minor incompletes that agents can work around. If the draft is substantively correct, reach quorum."
1294
+ elif [ "$pct_remaining" -gt 0 ]; then
1295
+ # Late: urgency
1296
+ turn_pressure="WARNING: This is turn $round of $max_rounds — only $remaining turn(s) remaining. If quorum is not reached by turn $max_rounds, the best draft will be force-accepted AS-IS including any unresolved issues. Only reset quorum if you find a factual error that would directly cause a build failure. Accept minor gaps — they can be addressed in later phases. Reaching quorum NOW with a good-enough draft is better than running out of turns with a perfect draft that was never agreed upon."
1297
+ else
1298
+ # Final turn
1299
+ turn_pressure="FINAL TURN: This is turn $round of $max_rounds — there are no more turns after this. If you do not reach quorum now, the best available draft will be force-accepted with all current issues unresolved. Set reached: true unless there is a critical factual error that makes the draft unusable for downstream phases."
1300
+ fi
1301
+
1276
1302
  local prompt_file
1277
1303
  prompt_file=$(mktemp)
1278
1304
  sed -e "s|{{AGENT_NAME}}|$current|g" \
1279
1305
  -e "s|{{PROJECT_DIR}}|$(pwd)|g" \
1306
+ -e "s|{{TURN}}|$round|g" \
1307
+ -e "s|{{MAX_ROUNDS}}|$max_rounds|g" \
1308
+ -e "s|{{REMAINING}}|$remaining|g" \
1280
1309
  "$prompt_template" > "$prompt_file"
1281
1310
 
1311
+ # Replace multiline pressure text (sed can't do multiline easily, use temp file)
1312
+ python3 -c "
1313
+ import sys
1314
+ with open('$prompt_file', 'r') as f:
1315
+ content = f.read()
1316
+ content = content.replace('{{TURN_PRESSURE}}', '''$turn_pressure''')
1317
+ with open('$prompt_file', 'w') as f:
1318
+ f.write(content)
1319
+ " 2>/dev/null
1320
+
1282
1321
  # Run
1283
1322
  dispatch_logged "$current" "$prompt_file" "$turn_timeout" "$log_dir" "$round" "$csv" "$phase"
1284
1323
  local dispatch_rc=$?
package/lib/tokens.sh CHANGED
@@ -200,36 +200,13 @@ _show_gateway_cost() {
200
200
  local api_key
201
201
  api_key=$(gateway_get_key "vercel-ai-gateway" 2>/dev/null) || return
202
202
 
203
- # Find earliest timestamp across all CSV files to determine date range
204
- local start_date
205
- start_date=$(python3 -c "
206
- import csv, glob, datetime
207
- earliest = None
208
- for f in glob.glob('$MOXIE_DIR/phases/*/token-usage.csv'):
209
- with open(f) as fh:
210
- for row in csv.DictReader(fh):
211
- ts = row.get('timestamp', '')
212
- if len(ts) >= 13:
213
- try:
214
- dt = datetime.datetime.strptime(ts[:13], '%m%d%y-%H%M%S')
215
- if earliest is None or dt < earliest:
216
- earliest = dt
217
- except ValueError:
218
- pass
219
- if earliest:
220
- print(earliest.strftime('%Y-%m-%d'))
221
- else:
222
- print(datetime.date.today().strftime('%Y-%m-%d'))
223
- " 2>/dev/null)
224
-
225
- local end_date
226
- end_date=$(date +"%Y-%m-%d")
227
-
228
- [ -z "$start_date" ] && return
203
+ # Get run_id for filtering (if available, use it; otherwise fall back to dates)
204
+ local run_id
205
+ run_id=$(toml_get "$MOXIE_CONFIG" "gateway.run_id" "")
229
206
 
230
207
  # Call the reporting API
231
208
  local cost_json
232
- cost_json=$(GATEWAY_API_KEY="$api_key" node "$MOXIE_ROOT/lib/gateway-cost.mjs" "$gw_endpoint" "$start_date" "$end_date" 2>/dev/null) || {
209
+ cost_json=$(GATEWAY_API_KEY="$api_key" GATEWAY_RUN_ID="$run_id" node "$MOXIE_ROOT/lib/gateway-cost.mjs" "$gw_endpoint" 2>/dev/null) || {
233
210
  echo ""
234
211
  echo "Gateway cost:"
235
212
  echo " (unavailable — check your API key or AI Gateway dashboard)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zachjxyz/moxie",
3
- "version": "0.3.5",
3
+ "version": "0.3.8",
4
4
  "description": "Run multiple AI coding agents through spec-driven phases with quorum convergence. Supports CLI agents (Claude, Codex, Qwen, Aider, Goose, Amp, Cline, Roo) and Vercel AI Gateway models.",
5
5
  "bin": {
6
6
  "moxie": "bin/moxie"
@@ -76,6 +76,10 @@ Only when EVERY agent has independently verified and agreed:
76
76
 
77
77
  Do NOT write the FINAL file unless ALL agents show `reached: true` in the ledger.
78
78
 
79
+ ## Turn Pressure
80
+
81
+ {{TURN_PRESSURE}}
82
+
79
83
  ## Rules
80
84
 
81
85
  - ALWAYS verify against actual source code, not other audits
@@ -64,6 +64,10 @@ When ALL build phases are `"complete"`:
64
64
 
65
65
  Do NOT write the FINAL file unless ALL agents show `reached: true` in the ledger.
66
66
 
67
+ ## Turn Pressure
68
+
69
+ {{TURN_PRESSURE}}
70
+
67
71
  ## Rules
68
72
 
69
73
  - Complete phases in order — never skip
@@ -53,6 +53,10 @@ Write `.moxie/phases/fix/FIX-FINAL-{MMDDyy-HHmmss}.md` with:
53
53
 
54
54
  Do NOT write the FINAL file unless ALL agents show `reached: true` in the ledger.
55
55
 
56
+ ## Turn Pressure
57
+
58
+ {{TURN_PRESSURE}}
59
+
56
60
  ## Rules
57
61
 
58
62
  - Fix highest severity issues first
@@ -57,6 +57,10 @@ self-contained implementation plan. This is the canonical build spec.
57
57
 
58
58
  Do NOT write the FINAL file unless ALL agents show `reached: true` in the ledger.
59
59
 
60
+ ## Turn Pressure
61
+
62
+ {{TURN_PRESSURE}}
63
+
60
64
  ## Rules
61
65
 
62
66
  - All file paths must be accurate against current source
@@ -80,6 +80,10 @@ If ALL agents (every agent in the config) show `reached: true`:
80
80
  - Copy the final RFC to `.moxie/spec.md` (this becomes the canonical spec for all later phases)
81
81
  - Set ledger `status` to `"rfc_final"`
82
82
 
83
+ ## Turn Pressure
84
+
85
+ {{TURN_PRESSURE}}
86
+
83
87
  ## Rules
84
88
 
85
89
  - Be EXHAUSTIVE — this RFC drives the entire pipeline. Missing details here mean gaps downstream.