@hecer/yoke 1.1.1 → 1.2.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.
Files changed (41) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +24 -3
  4. package/README.md +87 -21
  5. package/bench/README.md +46 -14
  6. package/bench/RESULTS.md +108 -11
  7. package/bench/analyze-routing-study.mjs +96 -0
  8. package/bench/fixtures/routing-queue/.yoke/config.yaml +30 -0
  9. package/bench/fixtures/routing-queue/.yoke/prd.yaml +24 -0
  10. package/bench/fixtures/routing-queue/bench-verify.mjs +9 -0
  11. package/bench/fixtures/routing-queue/package.json +9 -0
  12. package/bench/fixtures/routing-queue/src/task-queue.mjs +1 -0
  13. package/bench/fixtures/routing-queue/tests/STORY-1.test.mjs +68 -0
  14. package/bench/fixtures/routing-queue/tests/STORY-2.test.mjs +72 -0
  15. package/bench/results/routing-queue-codex-routing-off-2026-08-01T21-44-00.json +46 -0
  16. package/bench/results/routing-queue-codex-routing-on-2026-08-01T21-50-31.json +109 -0
  17. package/bench/results/yoke-codex-study-codex-routing-off-2026-08-02T07-58-56.json +65 -0
  18. package/bench/results/yoke-codex-study-codex-routing-off-2026-08-02T08-08-48.json +65 -0
  19. package/bench/results/yoke-codex-study-codex-routing-off-2026-08-02T08-34-45.json +65 -0
  20. package/bench/results/yoke-codex-study-codex-routing-on-2026-08-02T07-51-59.json +168 -0
  21. package/bench/results/yoke-codex-study-codex-routing-on-2026-08-02T08-18-59.json +168 -0
  22. package/bench/results/yoke-codex-study-codex-routing-on-2026-08-02T08-25-25.json +168 -0
  23. package/bench/results/yoke-large-codex-routing-off-2026-08-01T22-58-01.json +54 -0
  24. package/bench/results/yoke-large-codex-routing-on-2026-08-01T23-09-39.json +133 -0
  25. package/bench/run-large.mjs +188 -0
  26. package/bench/run.mjs +42 -21
  27. package/dist/agents/providers.js +19 -2
  28. package/dist/agents/telemetry.js +46 -7
  29. package/dist/cli.js +7 -5
  30. package/dist/loop/decision.js +3 -1
  31. package/dist/loop/loop.js +10 -0
  32. package/dist/loop/reporter.js +9 -0
  33. package/dist/loop/run-command.js +33 -5
  34. package/dist/loop/runner.js +68 -9
  35. package/dist/retrofit/config.js +21 -0
  36. package/dist/review/command.js +2 -2
  37. package/dist/routing/registry.js +92 -0
  38. package/dist/routing/router.js +195 -0
  39. package/dist/setup/command.js +23 -1
  40. package/gemini-extension.json +1 -1
  41. package/package.json +6 -4
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "bench-routing-queue",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "node bench-verify.mjs"
8
+ }
9
+ }
@@ -0,0 +1 @@
1
+ // Benchmark fixture: implementation intentionally left for the coding agent.
@@ -0,0 +1,68 @@
1
+ import test from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { TaskQueue } from '../src/task-queue.mjs'
4
+
5
+ const makeQueue = () => {
6
+ let now = 1_000
7
+ const queue = new TaskQueue({ clock: () => now, maxAttempts: 2, baseDelayMs: 100 })
8
+ return { queue, setNow: value => { now = value } }
9
+ }
10
+
11
+ test('enqueue validates type and creates stable immutable snapshots', () => {
12
+ const { queue } = makeQueue()
13
+ assert.throws(() => queue.enqueue({ type: '', payload: {} }), /type/i)
14
+ const task = queue.enqueue({ type: 'email', payload: { to: 'a@example.test' } })
15
+ assert.equal(task.status, 'queued')
16
+ assert.equal(task.priority, 0)
17
+ assert.equal(task.createdAt, 1_000)
18
+ assert.throws(() => { task.status = 'dead' }, TypeError)
19
+ task.payload.to = 'changed@example.test'
20
+ assert.equal(queue.get(task.id).payload.to, 'a@example.test')
21
+ })
22
+
23
+ test('idempotency keys return the original task without growing the queue', () => {
24
+ const { queue } = makeQueue()
25
+ const first = queue.enqueue({ type: 'email', payload: { n: 1 }, idempotencyKey: 'order-7' })
26
+ const second = queue.enqueue({ type: 'email', payload: { n: 2 }, idempotencyKey: 'order-7' })
27
+ assert.equal(second.id, first.id)
28
+ assert.equal(second.payload.n, 1)
29
+ assert.equal(queue.list().length, 1)
30
+ })
31
+
32
+ test('claim selects priority first and FIFO for ties', () => {
33
+ const { queue, setNow } = makeQueue()
34
+ const low = queue.enqueue({ type: 'low', payload: {}, priority: 1 })
35
+ setNow(1_001)
36
+ const highFirst = queue.enqueue({ type: 'high-a', payload: {}, priority: 5 })
37
+ setNow(1_002)
38
+ const highSecond = queue.enqueue({ type: 'high-b', payload: {}, priority: 5 })
39
+ assert.equal(queue.claim('worker-1', { leaseMs: 50 }).id, highFirst.id)
40
+ assert.equal(queue.claim('worker-2', { leaseMs: 50 }).id, highSecond.id)
41
+ assert.equal(queue.claim('worker-3', { leaseMs: 50 }).id, low.id)
42
+ assert.equal(queue.claim('worker-4'), null)
43
+ })
44
+
45
+ test('claim records lease ownership and complete enforces it', () => {
46
+ const { queue } = makeQueue()
47
+ const original = queue.enqueue({ type: 'work', payload: {} })
48
+ const claimed = queue.claim('worker-a', { leaseMs: 250 })
49
+ assert.equal(claimed.attempts, 1)
50
+ assert.equal(claimed.workerId, 'worker-a')
51
+ assert.equal(claimed.leaseUntil, 1_250)
52
+ assert.throws(() => queue.complete(original.id, 'worker-b', 42), /worker|owner|lease/i)
53
+ const done = queue.complete(original.id, 'worker-a', { ok: true })
54
+ assert.equal(done.status, 'completed')
55
+ assert.deepEqual(done.result, { ok: true })
56
+ assert.throws(() => queue.complete(original.id, 'worker-a', {}), /state|active|completed/i)
57
+ })
58
+
59
+ test('get/list snapshots cannot mutate internal state', () => {
60
+ const { queue } = makeQueue()
61
+ const task = queue.enqueue({ type: 'work', payload: { nested: { value: 1 } } })
62
+ const listed = queue.list()
63
+ listed[0].payload.nested.value = 9
64
+ listed.length = 0
65
+ assert.equal(queue.get(task.id).payload.nested.value, 1)
66
+ assert.equal(queue.list().length, 1)
67
+ assert.equal(queue.get('missing'), null)
68
+ })
@@ -0,0 +1,72 @@
1
+ import test from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { TaskQueue } from '../src/task-queue.mjs'
4
+
5
+ const makeQueue = (options = {}) => {
6
+ let now = 1_000
7
+ const queue = new TaskQueue({ clock: () => now, maxAttempts: 2, baseDelayMs: 100, ...options })
8
+ return { queue, now: () => now, advance: ms => { now += ms } }
9
+ }
10
+
11
+ test('fail requeues with exponential backoff and respects availability', () => {
12
+ const { queue, now, advance } = makeQueue({ maxAttempts: 3 })
13
+ const task = queue.enqueue({ type: 'work', payload: {} })
14
+ queue.claim('worker')
15
+ const first = queue.fail(task.id, 'worker', new Error('temporary'))
16
+ assert.equal(first.status, 'queued')
17
+ assert.equal(first.availableAt, now() + 100)
18
+ assert.equal(queue.claim('early'), null)
19
+ advance(100)
20
+ queue.claim('worker')
21
+ const second = queue.fail(task.id, 'worker', 'again')
22
+ assert.equal(second.availableAt, now() + 200)
23
+ })
24
+
25
+ test('failure at maxAttempts moves a task to dead letters', () => {
26
+ const { queue, advance } = makeQueue({ maxAttempts: 2 })
27
+ const task = queue.enqueue({ type: 'work', payload: {} })
28
+ queue.claim('worker')
29
+ queue.fail(task.id, 'worker', 'first')
30
+ advance(100)
31
+ queue.claim('worker')
32
+ const dead = queue.fail(task.id, 'worker', new Error('final'))
33
+ assert.equal(dead.status, 'dead')
34
+ assert.match(dead.error, /final/)
35
+ assert.equal(queue.claim('other'), null)
36
+ })
37
+
38
+ test('reapExpired requeues only expired active leases', () => {
39
+ const { queue, advance } = makeQueue()
40
+ const first = queue.enqueue({ type: 'a', payload: {}, priority: 2 })
41
+ queue.enqueue({ type: 'b', payload: {}, priority: 1 })
42
+ queue.claim('worker-a', { leaseMs: 50 })
43
+ queue.claim('worker-b', { leaseMs: 500 })
44
+ advance(51)
45
+ assert.equal(queue.reapExpired(), 1)
46
+ assert.equal(queue.get(first.id).status, 'queued')
47
+ assert.equal(queue.claim('worker-c').id, first.id)
48
+ })
49
+
50
+ test('list filters by status and type and stats reports every state', () => {
51
+ const { queue } = makeQueue({ maxAttempts: 1 })
52
+ const completed = queue.enqueue({ type: 'email', payload: {} })
53
+ queue.claim('a')
54
+ queue.complete(completed.id, 'a', 'ok')
55
+ const dead = queue.enqueue({ type: 'email', payload: {} })
56
+ queue.claim('b')
57
+ queue.fail(dead.id, 'b', 'bad')
58
+ queue.enqueue({ type: 'report', payload: {} })
59
+ assert.equal(queue.list({ type: 'email' }).length, 2)
60
+ assert.equal(queue.list({ status: 'dead' }).length, 1)
61
+ assert.deepEqual(queue.stats(), { total: 3, queued: 1, active: 0, completed: 1, dead: 1 })
62
+ })
63
+
64
+ test('invalid identifiers, owners, and transitions are rejected', () => {
65
+ const { queue } = makeQueue()
66
+ assert.throws(() => queue.claim(''), /worker/i)
67
+ assert.throws(() => queue.fail('missing', 'worker', 'x'), /task|missing|unknown/i)
68
+ const task = queue.enqueue({ type: 'work', payload: {} })
69
+ assert.throws(() => queue.fail(task.id, 'worker', 'x'), /state|active|queued/i)
70
+ queue.claim('owner')
71
+ assert.throws(() => queue.fail(task.id, 'intruder', 'x'), /worker|owner|lease/i)
72
+ })
@@ -0,0 +1,46 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "fixtureVersion": "routing-queue@1",
4
+ "runner": "codex",
5
+ "sampleLabel": "routing-ab-controlled-baseline",
6
+ "permissionProfile": "unsafe",
7
+ "yokeVersion": "1.1.1",
8
+ "fixture": "routing-queue",
9
+ "routing": "off",
10
+ "startedAt": "2026-08-01T21:44:00.963Z",
11
+ "wallClockMs": 299787,
12
+ "exitCode": 0,
13
+ "finalState": "complete",
14
+ "verdict": "completed",
15
+ "blocker": null,
16
+ "conflicts": 0,
17
+ "iterations": 2,
18
+ "finalTestsPass": true,
19
+ "progress": {
20
+ "passed": 2,
21
+ "total": 2
22
+ },
23
+ "usageAvailable": true,
24
+ "modelAvailable": true,
25
+ "requestedParentModel": "gpt-5.6-sol",
26
+ "tokens": {
27
+ "inputTokens": 579301,
28
+ "outputTokens": 9103
29
+ },
30
+ "modelCalls": [],
31
+ "stories": [
32
+ {
33
+ "id": "STORY-1",
34
+ "durationMs": 173400,
35
+ "iterations": 1,
36
+ "finalTestsPass": true
37
+ },
38
+ {
39
+ "id": "STORY-2",
40
+ "durationMs": 125693,
41
+ "iterations": 1,
42
+ "finalTestsPass": true
43
+ }
44
+ ],
45
+ "srcLoc": 133
46
+ }
@@ -0,0 +1,109 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "fixtureVersion": "routing-queue@1",
4
+ "runner": "codex",
5
+ "sampleLabel": "routing-ab-controlled-routed",
6
+ "permissionProfile": "unsafe",
7
+ "yokeVersion": "1.1.1",
8
+ "fixture": "routing-queue",
9
+ "routing": "on",
10
+ "startedAt": "2026-08-01T21:50:32.408Z",
11
+ "wallClockMs": 283134,
12
+ "exitCode": 0,
13
+ "finalState": "complete",
14
+ "verdict": "completed",
15
+ "blocker": null,
16
+ "conflicts": 0,
17
+ "iterations": 2,
18
+ "finalTestsPass": true,
19
+ "progress": {
20
+ "passed": 2,
21
+ "total": 2
22
+ },
23
+ "usageAvailable": true,
24
+ "modelAvailable": true,
25
+ "requestedParentModel": "gpt-5.6-sol",
26
+ "tokens": {
27
+ "inputTokens": 542788,
28
+ "outputTokens": 5486,
29
+ "calls": [
30
+ {
31
+ "role": "orchestrator",
32
+ "provider": "codex",
33
+ "requestedModel": "gpt-5.6-sol",
34
+ "inputTokens": 19065,
35
+ "outputTokens": 28,
36
+ "durationMs": 22124
37
+ },
38
+ {
39
+ "role": "worker",
40
+ "provider": "codex",
41
+ "inputTokens": 272894,
42
+ "outputTokens": 3201,
43
+ "durationMs": 124464
44
+ },
45
+ {
46
+ "role": "orchestrator",
47
+ "provider": "codex",
48
+ "requestedModel": "gpt-5.6-sol",
49
+ "inputTokens": 19046,
50
+ "outputTokens": 30,
51
+ "durationMs": 19208
52
+ },
53
+ {
54
+ "role": "worker",
55
+ "provider": "codex",
56
+ "inputTokens": 231783,
57
+ "outputTokens": 2227,
58
+ "durationMs": 102089
59
+ }
60
+ ]
61
+ },
62
+ "modelCalls": [
63
+ {
64
+ "role": "orchestrator",
65
+ "provider": "codex",
66
+ "requestedModel": "gpt-5.6-sol",
67
+ "inputTokens": 19065,
68
+ "outputTokens": 28,
69
+ "durationMs": 22124
70
+ },
71
+ {
72
+ "role": "worker",
73
+ "provider": "codex",
74
+ "inputTokens": 272894,
75
+ "outputTokens": 3201,
76
+ "durationMs": 124464
77
+ },
78
+ {
79
+ "role": "orchestrator",
80
+ "provider": "codex",
81
+ "requestedModel": "gpt-5.6-sol",
82
+ "inputTokens": 19046,
83
+ "outputTokens": 30,
84
+ "durationMs": 19208
85
+ },
86
+ {
87
+ "role": "worker",
88
+ "provider": "codex",
89
+ "inputTokens": 231783,
90
+ "outputTokens": 2227,
91
+ "durationMs": 102089
92
+ }
93
+ ],
94
+ "stories": [
95
+ {
96
+ "id": "STORY-1",
97
+ "durationMs": 148000,
98
+ "iterations": 1,
99
+ "finalTestsPass": true
100
+ },
101
+ {
102
+ "id": "STORY-2",
103
+ "durationMs": 122288,
104
+ "iterations": 1,
105
+ "finalTestsPass": true
106
+ }
107
+ ],
108
+ "srcLoc": 112
109
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "fixtureVersion": "yoke-codex-study@1",
4
+ "runner": "codex",
5
+ "sampleLabel": "codex-only-pair1-off",
6
+ "permissionProfile": "unsafe",
7
+ "yokeVersion": "1.1.1",
8
+ "fixture": "yoke-codex-study",
9
+ "routing": "off",
10
+ "startedAt": "2026-08-02T07:58:58.592Z",
11
+ "wallClockMs": 561860,
12
+ "exitCode": 0,
13
+ "finalState": "complete",
14
+ "verdict": "completed",
15
+ "blocker": null,
16
+ "conflicts": 0,
17
+ "iterations": 2,
18
+ "finalTestsPass": true,
19
+ "progress": {
20
+ "passed": 2,
21
+ "total": 2
22
+ },
23
+ "usageAvailable": true,
24
+ "modelAvailable": true,
25
+ "requestedParentModel": "gpt-5.6-sol",
26
+ "tokens": {
27
+ "inputTokens": 3255382,
28
+ "cachedInputTokens": 3050240,
29
+ "cacheWriteInputTokens": 0,
30
+ "outputTokens": 17468,
31
+ "reasoningOutputTokens": 7383
32
+ },
33
+ "modelCalls": [],
34
+ "tokenBreakdown": {
35
+ "inputTokens": 3255382,
36
+ "cachedInputTokens": 3050240,
37
+ "freshInputTokens": 205142,
38
+ "cacheWriteInputTokens": 0,
39
+ "outputTokens": 17468,
40
+ "reasoningOutputTokens": 7383
41
+ },
42
+ "stories": [
43
+ {
44
+ "id": "STUDY-1",
45
+ "durationMs": 345519,
46
+ "iterations": 1,
47
+ "finalTestsPass": true
48
+ },
49
+ {
50
+ "id": "STUDY-2",
51
+ "durationMs": 215666,
52
+ "iterations": 1,
53
+ "finalTestsPass": true
54
+ }
55
+ ],
56
+ "srcLoc": 5274,
57
+ "sourceFiles": 61,
58
+ "finalVerification": {
59
+ "buildExitCode": 0,
60
+ "testExitCode": 0,
61
+ "finalTestCount": 580,
62
+ "originalAcceptanceTestsExitCode": 0,
63
+ "originalAcceptanceTestCount": 6
64
+ }
65
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "fixtureVersion": "yoke-codex-study@1",
4
+ "runner": "codex",
5
+ "sampleLabel": "codex-only-pair2-off",
6
+ "permissionProfile": "unsafe",
7
+ "yokeVersion": "1.1.1",
8
+ "fixture": "yoke-codex-study",
9
+ "routing": "off",
10
+ "startedAt": "2026-08-02T08:08:51.009Z",
11
+ "wallClockMs": 579139,
12
+ "exitCode": 0,
13
+ "finalState": "complete",
14
+ "verdict": "completed",
15
+ "blocker": null,
16
+ "conflicts": 0,
17
+ "iterations": 2,
18
+ "finalTestsPass": true,
19
+ "progress": {
20
+ "passed": 2,
21
+ "total": 2
22
+ },
23
+ "usageAvailable": true,
24
+ "modelAvailable": true,
25
+ "requestedParentModel": "gpt-5.6-sol",
26
+ "tokens": {
27
+ "inputTokens": 2027874,
28
+ "cachedInputTokens": 1887744,
29
+ "cacheWriteInputTokens": 0,
30
+ "outputTokens": 14662,
31
+ "reasoningOutputTokens": 6087
32
+ },
33
+ "modelCalls": [],
34
+ "tokenBreakdown": {
35
+ "inputTokens": 2027874,
36
+ "cachedInputTokens": 1887744,
37
+ "freshInputTokens": 140130,
38
+ "cacheWriteInputTokens": 0,
39
+ "outputTokens": 14662,
40
+ "reasoningOutputTokens": 6087
41
+ },
42
+ "stories": [
43
+ {
44
+ "id": "STUDY-1",
45
+ "durationMs": 346078,
46
+ "iterations": 1,
47
+ "finalTestsPass": true
48
+ },
49
+ {
50
+ "id": "STUDY-2",
51
+ "durationMs": 232165,
52
+ "iterations": 1,
53
+ "finalTestsPass": true
54
+ }
55
+ ],
56
+ "srcLoc": 5259,
57
+ "sourceFiles": 61,
58
+ "finalVerification": {
59
+ "buildExitCode": 0,
60
+ "testExitCode": 0,
61
+ "finalTestCount": 578,
62
+ "originalAcceptanceTestsExitCode": 0,
63
+ "originalAcceptanceTestCount": 6
64
+ }
65
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "fixtureVersion": "yoke-codex-study@1",
4
+ "runner": "codex",
5
+ "sampleLabel": "codex-only-pair3-off",
6
+ "permissionProfile": "unsafe",
7
+ "yokeVersion": "1.1.1",
8
+ "fixture": "yoke-codex-study",
9
+ "routing": "off",
10
+ "startedAt": "2026-08-02T08:34:47.242Z",
11
+ "wallClockMs": 589501,
12
+ "exitCode": 0,
13
+ "finalState": "complete",
14
+ "verdict": "completed",
15
+ "blocker": null,
16
+ "conflicts": 0,
17
+ "iterations": 2,
18
+ "finalTestsPass": true,
19
+ "progress": {
20
+ "passed": 2,
21
+ "total": 2
22
+ },
23
+ "usageAvailable": true,
24
+ "modelAvailable": true,
25
+ "requestedParentModel": "gpt-5.6-sol",
26
+ "tokens": {
27
+ "inputTokens": 2279164,
28
+ "cachedInputTokens": 2147584,
29
+ "cacheWriteInputTokens": 0,
30
+ "outputTokens": 16008,
31
+ "reasoningOutputTokens": 6536
32
+ },
33
+ "modelCalls": [],
34
+ "tokenBreakdown": {
35
+ "inputTokens": 2279164,
36
+ "cachedInputTokens": 2147584,
37
+ "freshInputTokens": 131580,
38
+ "cacheWriteInputTokens": 0,
39
+ "outputTokens": 16008,
40
+ "reasoningOutputTokens": 6536
41
+ },
42
+ "stories": [
43
+ {
44
+ "id": "STUDY-1",
45
+ "durationMs": 376161,
46
+ "iterations": 1,
47
+ "finalTestsPass": true
48
+ },
49
+ {
50
+ "id": "STUDY-2",
51
+ "durationMs": 212311,
52
+ "iterations": 1,
53
+ "finalTestsPass": true
54
+ }
55
+ ],
56
+ "srcLoc": 5253,
57
+ "sourceFiles": 61,
58
+ "finalVerification": {
59
+ "buildExitCode": 0,
60
+ "testExitCode": 0,
61
+ "finalTestCount": 580,
62
+ "originalAcceptanceTestsExitCode": 0,
63
+ "originalAcceptanceTestCount": 6
64
+ }
65
+ }
@@ -0,0 +1,168 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "fixtureVersion": "yoke-codex-study@1",
4
+ "runner": "codex",
5
+ "sampleLabel": "codex-only-pair1-on",
6
+ "permissionProfile": "unsafe",
7
+ "yokeVersion": "1.1.1",
8
+ "fixture": "yoke-codex-study",
9
+ "routing": "on",
10
+ "startedAt": "2026-08-02T07:52:01.670Z",
11
+ "wallClockMs": 383099,
12
+ "exitCode": 0,
13
+ "finalState": "complete",
14
+ "verdict": "completed",
15
+ "blocker": null,
16
+ "conflicts": 0,
17
+ "iterations": 2,
18
+ "finalTestsPass": true,
19
+ "progress": {
20
+ "passed": 2,
21
+ "total": 2
22
+ },
23
+ "usageAvailable": true,
24
+ "modelAvailable": true,
25
+ "requestedParentModel": "gpt-5.6-sol",
26
+ "tokens": {
27
+ "inputTokens": 1561371,
28
+ "cachedInputTokens": 1436672,
29
+ "cacheWriteInputTokens": 0,
30
+ "outputTokens": 9340,
31
+ "reasoningOutputTokens": 2010,
32
+ "calls": [
33
+ {
34
+ "role": "orchestrator",
35
+ "provider": "codex",
36
+ "requestedModel": "gpt-5.6-sol",
37
+ "requestedReasoningEffort": "low",
38
+ "inputTokens": 19164,
39
+ "cachedInputTokens": 11008,
40
+ "cacheWriteInputTokens": 0,
41
+ "outputTokens": 30,
42
+ "reasoningOutputTokens": 0,
43
+ "durationMs": 12390
44
+ },
45
+ {
46
+ "role": "worker",
47
+ "provider": "codex",
48
+ "profile": "codex-luna",
49
+ "requestedModel": "gpt-5.6-luna",
50
+ "requestedReasoningEffort": "low",
51
+ "inputTokens": 570526,
52
+ "cachedInputTokens": 528640,
53
+ "cacheWriteInputTokens": 0,
54
+ "outputTokens": 4335,
55
+ "reasoningOutputTokens": 343,
56
+ "durationMs": 171211
57
+ },
58
+ {
59
+ "role": "orchestrator",
60
+ "provider": "codex",
61
+ "requestedModel": "gpt-5.6-sol",
62
+ "requestedReasoningEffort": "low",
63
+ "inputTokens": 19161,
64
+ "cachedInputTokens": 11008,
65
+ "cacheWriteInputTokens": 0,
66
+ "outputTokens": 32,
67
+ "reasoningOutputTokens": 0,
68
+ "durationMs": 12716
69
+ },
70
+ {
71
+ "role": "worker",
72
+ "provider": "codex",
73
+ "profile": "codex-luna",
74
+ "requestedModel": "gpt-5.6-luna",
75
+ "requestedReasoningEffort": "low",
76
+ "inputTokens": 952520,
77
+ "cachedInputTokens": 886016,
78
+ "cacheWriteInputTokens": 0,
79
+ "outputTokens": 4943,
80
+ "reasoningOutputTokens": 1667,
81
+ "durationMs": 182866
82
+ }
83
+ ]
84
+ },
85
+ "modelCalls": [
86
+ {
87
+ "role": "orchestrator",
88
+ "provider": "codex",
89
+ "requestedModel": "gpt-5.6-sol",
90
+ "requestedReasoningEffort": "low",
91
+ "inputTokens": 19164,
92
+ "cachedInputTokens": 11008,
93
+ "cacheWriteInputTokens": 0,
94
+ "outputTokens": 30,
95
+ "reasoningOutputTokens": 0,
96
+ "durationMs": 12390
97
+ },
98
+ {
99
+ "role": "worker",
100
+ "provider": "codex",
101
+ "profile": "codex-luna",
102
+ "requestedModel": "gpt-5.6-luna",
103
+ "requestedReasoningEffort": "low",
104
+ "inputTokens": 570526,
105
+ "cachedInputTokens": 528640,
106
+ "cacheWriteInputTokens": 0,
107
+ "outputTokens": 4335,
108
+ "reasoningOutputTokens": 343,
109
+ "durationMs": 171211
110
+ },
111
+ {
112
+ "role": "orchestrator",
113
+ "provider": "codex",
114
+ "requestedModel": "gpt-5.6-sol",
115
+ "requestedReasoningEffort": "low",
116
+ "inputTokens": 19161,
117
+ "cachedInputTokens": 11008,
118
+ "cacheWriteInputTokens": 0,
119
+ "outputTokens": 32,
120
+ "reasoningOutputTokens": 0,
121
+ "durationMs": 12716
122
+ },
123
+ {
124
+ "role": "worker",
125
+ "provider": "codex",
126
+ "profile": "codex-luna",
127
+ "requestedModel": "gpt-5.6-luna",
128
+ "requestedReasoningEffort": "low",
129
+ "inputTokens": 952520,
130
+ "cachedInputTokens": 886016,
131
+ "cacheWriteInputTokens": 0,
132
+ "outputTokens": 4943,
133
+ "reasoningOutputTokens": 1667,
134
+ "durationMs": 182866
135
+ }
136
+ ],
137
+ "tokenBreakdown": {
138
+ "inputTokens": 1561371,
139
+ "cachedInputTokens": 1436672,
140
+ "freshInputTokens": 124699,
141
+ "cacheWriteInputTokens": 0,
142
+ "outputTokens": 9340,
143
+ "reasoningOutputTokens": 2010
144
+ },
145
+ "stories": [
146
+ {
147
+ "id": "STUDY-1",
148
+ "durationMs": 184822,
149
+ "iterations": 1,
150
+ "finalTestsPass": true
151
+ },
152
+ {
153
+ "id": "STUDY-2",
154
+ "durationMs": 197354,
155
+ "iterations": 1,
156
+ "finalTestsPass": true
157
+ }
158
+ ],
159
+ "srcLoc": 5252,
160
+ "sourceFiles": 61,
161
+ "finalVerification": {
162
+ "buildExitCode": 0,
163
+ "testExitCode": 0,
164
+ "finalTestCount": 578,
165
+ "originalAcceptanceTestsExitCode": 0,
166
+ "originalAcceptanceTestCount": 6
167
+ }
168
+ }