@bongos/core 1.19.861 → 1.19.862
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/.bongos-core.json +34 -24
- package/docs/adr/0264-the-ten-working-areas.md +2 -0
- package/docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md +178 -0
- package/docs/adr/0308-an-eleventh-working-area.md +89 -0
- package/docs/adr/README.md +2 -0
- package/docs/module-api-changelog.md +3 -1
- package/docs/research/cursor-competitive-analysis.md +14 -8
- package/modules/lifecycle/db-ship.js +40 -3
- package/modules/provisioning/core-upgrade.js +3 -2
- package/{migrations/core_242_core_version_snapshot.sql → modules/provisioning/migrations/provisioning_019_core_version_snapshot.sql} +30 -6
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/provision-core-upgrade.js +2 -2
- package/src/module-api.js +1 -1
- package/tests/claim_resolve_worktree_lock.mjs +57 -1
- package/tests/core_upgrade_runner.mjs +2 -2
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.862",
|
|
6
|
+
"core_contract": "1.19.862",
|
|
7
|
+
"source_commit": "ce0983547b33999b6262c9c586d0b7f9fe970689",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-18T00:41:20.268Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 500,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
14
|
"functional_verbatim": 2280,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2806,
|
|
20
|
+
"tree_sha256": "3c3f933893a61cda70824e5d11211bd2d7483c501eb5a5a2e17bfea72de33382",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -1812,7 +1812,7 @@
|
|
|
1812
1812
|
{
|
|
1813
1813
|
"path": "docs/adr/0264-the-ten-working-areas.md",
|
|
1814
1814
|
"mode": "0000644",
|
|
1815
|
-
"sha256": "
|
|
1815
|
+
"sha256": "a958e59b3cbcc35a0aa2a89151f8af00cc4fd32f5f65335ef102310fe84cfac6"
|
|
1816
1816
|
},
|
|
1817
1817
|
{
|
|
1818
1818
|
"path": "docs/adr/0265-work-with-no-goal-and-the-order-the-cut-needs.md",
|
|
@@ -2024,10 +2024,20 @@
|
|
|
2024
2024
|
"mode": "0000644",
|
|
2025
2025
|
"sha256": "00d74b19e9574186fbe354e410f6acd9e84db60f11a550905e95ac9071b89aa6"
|
|
2026
2026
|
},
|
|
2027
|
+
{
|
|
2028
|
+
"path": "docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md",
|
|
2029
|
+
"mode": "0000644",
|
|
2030
|
+
"sha256": "12159ae1337eec3d86f847104d9fa75eea7683cedd0fd5d8c0e3f8c4cdf63da5"
|
|
2031
|
+
},
|
|
2032
|
+
{
|
|
2033
|
+
"path": "docs/adr/0308-an-eleventh-working-area.md",
|
|
2034
|
+
"mode": "0000644",
|
|
2035
|
+
"sha256": "feab2773d4b8954874ed42bf922bc74d1b9c37e7d5ffef13c5c3cf58fd36507a"
|
|
2036
|
+
},
|
|
2027
2037
|
{
|
|
2028
2038
|
"path": "docs/adr/README.md",
|
|
2029
2039
|
"mode": "0000644",
|
|
2030
|
-
"sha256": "
|
|
2040
|
+
"sha256": "f19111877404016a28176ac0375e28713dad9d8895b5d637622497b3f461546c"
|
|
2031
2041
|
},
|
|
2032
2042
|
{
|
|
2033
2043
|
"path": "docs/api-reference.md",
|
|
@@ -2592,7 +2602,7 @@
|
|
|
2592
2602
|
{
|
|
2593
2603
|
"path": "docs/module-api-changelog.md",
|
|
2594
2604
|
"mode": "0000644",
|
|
2595
|
-
"sha256": "
|
|
2605
|
+
"sha256": "191ec7b680ce527f312660fc8cc1505803adb992febf7deac050d43abdc987dd"
|
|
2596
2606
|
},
|
|
2597
2607
|
{
|
|
2598
2608
|
"path": "docs/modules-contract.md",
|
|
@@ -2867,7 +2877,7 @@
|
|
|
2867
2877
|
{
|
|
2868
2878
|
"path": "docs/research/cursor-competitive-analysis.md",
|
|
2869
2879
|
"mode": "0000644",
|
|
2870
|
-
"sha256": "
|
|
2880
|
+
"sha256": "9dc5f219a1b1f114705eb31ac18926cfe98126197bbb5974e34ab931ce45819d"
|
|
2871
2881
|
},
|
|
2872
2882
|
{
|
|
2873
2883
|
"path": "docs/research/llm-model-agnostic-and-cheaper-grader.md",
|
|
@@ -3669,11 +3679,6 @@
|
|
|
3669
3679
|
"mode": "0000644",
|
|
3670
3680
|
"sha256": "7cc5e9681fd62486d445a80f8ac4f0a8cc7fbe4a8ffacba7d7ffcfd262e8440c"
|
|
3671
3681
|
},
|
|
3672
|
-
{
|
|
3673
|
-
"path": "migrations/core_242_core_version_snapshot.sql",
|
|
3674
|
-
"mode": "0000644",
|
|
3675
|
-
"sha256": "e52821124b99bd844c8323f2de0221db3fafd92c0c6e2227838cadf7d987676a"
|
|
3676
|
-
},
|
|
3677
3682
|
{
|
|
3678
3683
|
"path": "migrations/core_243_cli_sessions_never_expire.sql",
|
|
3679
3684
|
"mode": "0000644",
|
|
@@ -5677,7 +5682,7 @@
|
|
|
5677
5682
|
{
|
|
5678
5683
|
"path": "modules/lifecycle/db-ship.js",
|
|
5679
5684
|
"mode": "0000644",
|
|
5680
|
-
"sha256": "
|
|
5685
|
+
"sha256": "fc65aa01818dcd8c74d013e074ff9b6c71c34537230fc844e6a23b69cc93e22f"
|
|
5681
5686
|
},
|
|
5682
5687
|
{
|
|
5683
5688
|
"path": "modules/lifecycle/db-tasks.js",
|
|
@@ -6422,7 +6427,7 @@
|
|
|
6422
6427
|
{
|
|
6423
6428
|
"path": "modules/provisioning/core-upgrade.js",
|
|
6424
6429
|
"mode": "0000644",
|
|
6425
|
-
"sha256": "
|
|
6430
|
+
"sha256": "b66e81df0685777b9c6f92260d372fab3b37a81d39dbbf5d9183537634ec3e2b"
|
|
6426
6431
|
},
|
|
6427
6432
|
{
|
|
6428
6433
|
"path": "modules/provisioning/credential-preflight.js",
|
|
@@ -6524,6 +6529,11 @@
|
|
|
6524
6529
|
"mode": "0000644",
|
|
6525
6530
|
"sha256": "0fec45fe6188b76d625127ed60dabeda90dc02389709b3d2274c4477b33bb33d"
|
|
6526
6531
|
},
|
|
6532
|
+
{
|
|
6533
|
+
"path": "modules/provisioning/migrations/provisioning_019_core_version_snapshot.sql",
|
|
6534
|
+
"mode": "0000644",
|
|
6535
|
+
"sha256": "9d530c724f49392e5eed301b498631be5bfa096eca344cd7b26b88b5fc9210d3"
|
|
6536
|
+
},
|
|
6527
6537
|
{
|
|
6528
6538
|
"path": "modules/provisioning/module.json",
|
|
6529
6539
|
"mode": "0000644",
|
|
@@ -7917,12 +7927,12 @@
|
|
|
7917
7927
|
{
|
|
7918
7928
|
"path": "package-lock.json",
|
|
7919
7929
|
"mode": "0000644",
|
|
7920
|
-
"sha256": "
|
|
7930
|
+
"sha256": "669729fd3145c8a17880f92490b18fa7e76d6416c780e1420808be4f8c583bab"
|
|
7921
7931
|
},
|
|
7922
7932
|
{
|
|
7923
7933
|
"path": "package.json",
|
|
7924
7934
|
"mode": "0000644",
|
|
7925
|
-
"sha256": "
|
|
7935
|
+
"sha256": "0a41309fd1fc04ef03267da5ba55d9580489ab6f598b34ef4a14d9dd79906404"
|
|
7926
7936
|
},
|
|
7927
7937
|
{
|
|
7928
7938
|
"path": "public-docs/index.html",
|
|
@@ -8827,7 +8837,7 @@
|
|
|
8827
8837
|
{
|
|
8828
8838
|
"path": "scripts/gds/provision-core-upgrade.js",
|
|
8829
8839
|
"mode": "0000644",
|
|
8830
|
-
"sha256": "
|
|
8840
|
+
"sha256": "fef10e02d3b1d605c1b30fd553b1cf8069159d8c2cbf6b0e4b48ec32359562a9"
|
|
8831
8841
|
},
|
|
8832
8842
|
{
|
|
8833
8843
|
"path": "scripts/gds/provision-disconnect.js",
|
|
@@ -9867,7 +9877,7 @@
|
|
|
9867
9877
|
{
|
|
9868
9878
|
"path": "src/module-api.js",
|
|
9869
9879
|
"mode": "0000644",
|
|
9870
|
-
"sha256": "
|
|
9880
|
+
"sha256": "b16a023fc2c2dcf233402a8e3b72214d9c8217f5df7d6f5b0925f064a6c172e5"
|
|
9871
9881
|
},
|
|
9872
9882
|
{
|
|
9873
9883
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10572,7 +10582,7 @@
|
|
|
10572
10582
|
{
|
|
10573
10583
|
"path": "tests/claim_resolve_worktree_lock.mjs",
|
|
10574
10584
|
"mode": "0000644",
|
|
10575
|
-
"sha256": "
|
|
10585
|
+
"sha256": "063794948bb45c600781bdce3f31d590991c4628824fd57c15f916f9e0b49fc2"
|
|
10576
10586
|
},
|
|
10577
10587
|
{
|
|
10578
10588
|
"path": "tests/claimable_blocker_status.mjs",
|
|
@@ -10727,7 +10737,7 @@
|
|
|
10727
10737
|
{
|
|
10728
10738
|
"path": "tests/core_upgrade_runner.mjs",
|
|
10729
10739
|
"mode": "0000644",
|
|
10730
|
-
"sha256": "
|
|
10740
|
+
"sha256": "5f9ae4144ab85ee197f417003a71df1d78398ca11b6c8bd3dc96de9b4cd5e1fb"
|
|
10731
10741
|
},
|
|
10732
10742
|
{
|
|
10733
10743
|
"path": "tests/cosmos_token_contrast.mjs",
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
- **Goal:** [#1000086](https://cloudbongos.com/builders#/goal/1000086) — Strict versioning, criterion `sv-cutover-proven-live`.
|
|
7
7
|
- **Decided by:** the owner and the full team, in a manual goal review on 2026-09-07.
|
|
8
8
|
- **Builds on:** [ADR 0250](0250-strict-versioning-the-version-boundary-is-the-scope-gate.md) (the version boundary is the scope gate — this is the scope that boundary will hold), [ADR 0086](0086-goal-scoped-work-hierarchy.md) (the goal tier).
|
|
9
|
+
- **Amended by:** [ADR 0308](0308-an-eleventh-working-area.md) — an **eleventh** area (Autobongos, [goal 1000119](https://cloudbongos.com/builders#/goal/1000119)) was admitted on 2026-09-17. The table in §1 is ten of eleven; read 0308 beside it.
|
|
9
10
|
|
|
10
11
|
## 1. The decision
|
|
11
12
|
|
|
@@ -26,6 +27,7 @@ these ten.
|
|
|
26
27
|
| 8 | **Platform credit economy** | Scott | Real money reaches users for completed work, without fail. Users hold platform balances, fund projects and pay each other with no fees. Bongos banks project funds and delivers them on cash-out. |
|
|
27
28
|
| 9 | **Platform analytics** | Lars | Bongos gathers as much user data as it can to improve itself — session transcripts as an efficiency signal, plus every user action and work delivery across the ecosystem through the Bongos API. |
|
|
28
29
|
| 10 | **Security** | all except Nils and Scott | Ranks are enforced. The Bongos server is not hackable by Claude's latest model. Red-teaming is consistent. |
|
|
30
|
+
| 11 | **Autobongos** | Lars | *Added 2026-09-17 by [ADR 0308](0308-an-eleventh-working-area.md).* A project owner points idle compute at goals they have allowlisted, and it builds unattended inside their subscription limits — stopping rather than guessing whenever a human is needed, and reporting what it did without being asked. |
|
|
29
31
|
|
|
30
32
|
## 2. Why ten, and why now
|
|
31
33
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# ADR 0307 — Autobongos: a deterministic supervisor, agentic workers
|
|
2
|
+
|
|
3
|
+
**Status:** accepted · **Date:** 2026-09-17 · **Task:** [task 1003900](https://cloudbongos.com/builders#/task/1003900) (goal 1000119 — BONGOS-V2)
|
|
4
|
+
|
|
5
|
+
**Related:** [ADR 0153](0153-sequential-chain-runner.md) (the halt matrix and the halt-don't-skip rule this reuses wholesale) · [ADR 0024](0024-multi-agent-system-architecture.md) (the MAS the autonomy gate belongs to) · [ADR 0055](0055-server-mediated-branch-publish.md) (server-mediated publish — why the machine holds no push credential) · [ADR 0043](0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md) (the protected-path set the fence reuses) · [ADR 0308](0308-an-eleventh-working-area.md) (the working area this work lives in)
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The owner has an always-on Windows PC and a Claude subscription whose capacity
|
|
10
|
+
is mostly unused overnight. The ask was an orchestration loop that works the task
|
|
11
|
+
queue unattended inside goals the owner has allowlisted.
|
|
12
|
+
|
|
13
|
+
Most of that loop already exists, wired to the wrong trigger:
|
|
14
|
+
|
|
15
|
+
- **`scripts/gds/sequence.js`** resolves a chain and evaluates a ten-reason halt
|
|
16
|
+
matrix — entirely from facts on the task row, no model judgement (ADR 0153).
|
|
17
|
+
- **An open blocker linked to a task makes `claimTask` throw `OPEN_BLOCKER`**
|
|
18
|
+
(`modules/lifecycle/db-claims.js`). That is already a "put this work on hold"
|
|
19
|
+
state, and `/blocker-review` is already the owner's morning queue.
|
|
20
|
+
- **`modules/grading/grader-subagent.js`** is a proven headless `claude` spawn
|
|
21
|
+
seam, already Windows-aware (it routes through `cmd.exe` and hides the console).
|
|
22
|
+
- **`.claude/scheduled-tasks/overnight-builder/`** runs this loop today.
|
|
23
|
+
|
|
24
|
+
Two things are genuinely missing. The trigger is a cron schedule rather than the
|
|
25
|
+
subscription's own capacity, so a run either wastes a fresh window or dies
|
|
26
|
+
half-way through a spent one. And the **supervisor is itself a Claude session**,
|
|
27
|
+
which spends a full context boot per night on work that is entirely mechanical.
|
|
28
|
+
|
|
29
|
+
## The decision
|
|
30
|
+
|
|
31
|
+
### 1. The supervisor is a program; only the worker is a session
|
|
32
|
+
|
|
33
|
+
Picking, claiming, metering, verifying and recording are deterministic and stay
|
|
34
|
+
in code. One task at a time is handed to one headless session. This is the whole
|
|
35
|
+
architectural difference from `overnight-builder`, and it is what makes the loop
|
|
36
|
+
auditable: every branch the supervisor takes is a branch someone can read.
|
|
37
|
+
|
|
38
|
+
### 2. The gauge is the statusline's `rate_limits`, and it cannot be read cold
|
|
39
|
+
|
|
40
|
+
Claude Code feeds a statusLine hook `rate_limits.five_hour.{used_percentage,
|
|
41
|
+
resets_at}` and `rate_limits.seven_day.{…}` — the only structured read of
|
|
42
|
+
remaining subscription capacity. Anthropic's own note on the field says it is
|
|
43
|
+
*"only present for subscribers after first API response"*, so there is no way to
|
|
44
|
+
ask "how much is left" before starting. The mechanism is therefore a statusline
|
|
45
|
+
script that **persists both windows on every session**, and a reader the
|
|
46
|
+
supervisor consults between tasks. A gauge file older than its freshness window
|
|
47
|
+
is *unknown*, never *headroom*.
|
|
48
|
+
|
|
49
|
+
The reserve is time-aware, because the runner and the owner draw on the **same**
|
|
50
|
+
five-hour window: near zero overnight, a real floor during the owner's working
|
|
51
|
+
hours. The seven-day window is the binding one across a heavy week, and is paced
|
|
52
|
+
against rather than spent greedily.
|
|
53
|
+
|
|
54
|
+
### 3. One session per task, not a batch
|
|
55
|
+
|
|
56
|
+
The original sketch pulled "a string of sequential tasks estimated to fit the
|
|
57
|
+
remaining limits". Rejected, for three reasons. The estimate does not exist —
|
|
58
|
+
tasks carry human-minutes, not token spend. A successor cannot be claimed until
|
|
59
|
+
its predecessor ships (ADR 0153 §2), so "walk a string" and "take the next one"
|
|
60
|
+
produce the identical order anyway. And because each ship merges to main, a
|
|
61
|
+
**fresh session per task is also more correct** than a long one holding a
|
|
62
|
+
checkout that goes stale behind main.
|
|
63
|
+
|
|
64
|
+
So v1 **meters rather than estimates**: work one task, re-read the gauge, decide
|
|
65
|
+
again. The estimate becomes a learned number once the ledger has data.
|
|
66
|
+
|
|
67
|
+
### 4. Completion is read from the ledger, never from the session
|
|
68
|
+
|
|
69
|
+
A session exiting is not a task shipping, and the result envelope makes the
|
|
70
|
+
confusion easy: a worker that stopped dead needing a decision returns
|
|
71
|
+
`subtype: "success"`, structurally identical to one that finished. This project's
|
|
72
|
+
worst incidents are all this shape — branches never pushed, tasks parked at
|
|
73
|
+
`confirmed` with no code, a ship that timed out *after* its merge landed.
|
|
74
|
+
|
|
75
|
+
After every worker the supervisor reads the task's status back from Bongos and
|
|
76
|
+
confirms the artifact. Anything not actually shipped releases the claim and files
|
|
77
|
+
a blocker, which also removes the task from the claimable pool so the loop does
|
|
78
|
+
not pick it straight back up.
|
|
79
|
+
|
|
80
|
+
### 5. There is no terminal stop — only speeds
|
|
81
|
+
|
|
82
|
+
On an always-on machine a stopped daemon is silently worthless. Three straight
|
|
83
|
+
failures drop the runner to a **probing** state: no new work, a growing wait,
|
|
84
|
+
then exactly one task; a success returns it to full speed. Failures are
|
|
85
|
+
classified, because the right response differs — transient (instance
|
|
86
|
+
unreachable, grader outage, rejected push) heals by waiting; systemic (expired
|
|
87
|
+
token, full disk) will not, but cheap probing means it resumes the moment someone
|
|
88
|
+
fixes the cause; and a queue where *every* task halts is not a runner failure at
|
|
89
|
+
all, and is reported as an unworkable queue. A new signal — main moved, a blocker
|
|
90
|
+
resolved, the allowlist changed — jumps the remaining wait.
|
|
91
|
+
|
|
92
|
+
The only true stop is the **server-side** kill switch.
|
|
93
|
+
|
|
94
|
+
### 6. The fence, and where it is enforced
|
|
95
|
+
|
|
96
|
+
The allowlist of goals lives in the database and is edited from the hall, never
|
|
97
|
+
in a file on the machine — a file there is editable by anything running there,
|
|
98
|
+
and the premise is a machine nobody is watching. It combines with the existing
|
|
99
|
+
protected-path exclusion (ADR 0043), so permission, migration, infrastructure and
|
|
100
|
+
deploy-pipeline work never reaches an unattended worker.
|
|
101
|
+
|
|
102
|
+
The machine holds **no push credential at all** (the dev boxes were built this
|
|
103
|
+
way deliberately — ADR 0055), so the only road to main is the server-mediated
|
|
104
|
+
ship pipeline and its server-side grade. That grade is the *only* review between
|
|
105
|
+
an unattended worker and main, so **a grader outage stops the runner** rather
|
|
106
|
+
than downgrading it to shipping unreviewed.
|
|
107
|
+
|
|
108
|
+
### 7. Workers run with blanket `bypassPermissions`
|
|
109
|
+
|
|
110
|
+
`--permission-prompt-tool` exists and is print-mode only, so routing every
|
|
111
|
+
permission request back to the supervisor was available and was **rejected**: it
|
|
112
|
+
round-trips every tool call through one process, so a slow or crashed supervisor
|
|
113
|
+
stalls every worker, and it buys a thousand weak gates in place of the one that
|
|
114
|
+
matters. The boundary that matters is the push to main, and §6 already holds it.
|
|
115
|
+
|
|
116
|
+
### 8. Gate before, record after — and no conversation in between
|
|
117
|
+
|
|
118
|
+
This is the posture the owner chose, and it is the one part that was **tested
|
|
119
|
+
before it was decided**.
|
|
120
|
+
|
|
121
|
+
A worker was given an ambiguous task and told, in plain terms, to stop and ask
|
|
122
|
+
rather than guess. It did not. It settled the retry count, the backoff base,
|
|
123
|
+
which status codes to retry and whether to retry network errors — four genuine
|
|
124
|
+
judgement calls — and wrote them into the file. That is not misbehaviour; it is
|
|
125
|
+
what a competent engineer does without interrupting anyone. But it means **a
|
|
126
|
+
design that waits for workers to raise their hands will see very few hands, while
|
|
127
|
+
the decisions still get made.**
|
|
128
|
+
|
|
129
|
+
So:
|
|
130
|
+
|
|
131
|
+
- **Before** — the halt matrix refuses ambiguous work at *pick* time, mechanically
|
|
132
|
+
(thin spec, protected path, `kind='decision'`, needs a migration). Those tasks
|
|
133
|
+
never reach a worker.
|
|
134
|
+
- **After** — the worker's structured verdict must include the calls it made that
|
|
135
|
+
its task description did not determine. They land in the morning digest, where
|
|
136
|
+
the owner reviews them sitting on a branch behind a grader.
|
|
137
|
+
- **In between** — nothing. Mid-flight injection is the exception path for hard
|
|
138
|
+
blocks, not the main road.
|
|
139
|
+
|
|
140
|
+
Handing a decision back to a live worker *does* work and was verified — pinning
|
|
141
|
+
the session id at spawn and resuming it later returns the session with its
|
|
142
|
+
context intact, and cheaply, because the context comes back from cache. That
|
|
143
|
+
capability is kept for hard blocks and deliberately not built into a
|
|
144
|
+
question-answering apparatus.
|
|
145
|
+
|
|
146
|
+
## What this deliberately does not build
|
|
147
|
+
|
|
148
|
+
- **Mid-flight question answering** — no question tiering, no citation-enforced
|
|
149
|
+
answering agent, no resume caps. Removed on the owner's instruction as
|
|
150
|
+
unneeded complexity, and §8 is why it is not load-bearing.
|
|
151
|
+
- **Portability.** v1 targets one machine and one instance. Designing for hosts
|
|
152
|
+
we do not have, against numbers nobody has measured, would fix the metering
|
|
153
|
+
design before there is data to fix it with.
|
|
154
|
+
- **Per-task cost estimation.** See §3 — metering first, estimates later.
|
|
155
|
+
|
|
156
|
+
## Alternatives rejected
|
|
157
|
+
|
|
158
|
+
- **A budget-capped worker** (`--max-budget-usd`). Caps API dollars, which on a
|
|
159
|
+
subscription is not the constraint. The real runaway risk is a session spending
|
|
160
|
+
the whole window on one task, so the bound is **wall-clock**.
|
|
161
|
+
- **Extending `overnight-builder`.** It walks the whole backlog by priority on a
|
|
162
|
+
cron, supervised by a model. This is limit-triggered, allowlist-scoped and
|
|
163
|
+
supervised by a program. Different trigger, different scope, different cost.
|
|
164
|
+
- **A terminal stop after N failures.** The first draft of this loop had one; on
|
|
165
|
+
an always-on host it is indistinguishable from the machine being off. §5.
|
|
166
|
+
|
|
167
|
+
## Consequences
|
|
168
|
+
|
|
169
|
+
- The supervisor is testable the way a program is testable. Its decisions are not
|
|
170
|
+
a transcript.
|
|
171
|
+
- The runner competes with the owner for the same five-hour window, so the
|
|
172
|
+
reserve is a product decision, not a politeness.
|
|
173
|
+
- Every declared decision in the digest is a sentence the task description should
|
|
174
|
+
have contained. The list is a spec-quality signal, and acting on it lowers
|
|
175
|
+
future halts.
|
|
176
|
+
- The Windows host has **no precedent in this repo** — all prior autonomy ran on
|
|
177
|
+
Linux droplets, and that infrastructure is being torn down under goal 1000091.
|
|
178
|
+
It is the bucket most likely to overrun.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# ADR 0308 — An eleventh working area: Autobongos
|
|
2
|
+
|
|
3
|
+
**Status:** accepted · **Date:** 2026-09-17 · **Task:** [task 1003900](https://cloudbongos.com/builders#/task/1003900) (goal 1000119 — BONGOS-V2)
|
|
4
|
+
|
|
5
|
+
**Amends:** [ADR 0264](0264-the-ten-working-areas.md) (the ten working areas, held until 5,000 builders)
|
|
6
|
+
|
|
7
|
+
**Related:** [ADR 0307](0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md) (what the area actually builds) · [ADR 0250](0250-strict-versioning-the-version-boundary-is-the-scope-gate.md) (the version boundary that had to be reopened to admit it) · [ADR 0086](0086-goal-scoped-work-hierarchy.md) (the goal tier)
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
[ADR 0264](0264-the-ten-working-areas.md) collapsed thirty-eight open goals into
|
|
12
|
+
**ten working areas, one goal each, held until the platform has 5,000 builders**.
|
|
13
|
+
Ten was chosen so that every area has a single owner who can hold its whole shape
|
|
14
|
+
in their head, and the horizon is a scale question rather than a calendar one.
|
|
15
|
+
|
|
16
|
+
That ADR is nine days old. Adding to it needs a better argument than "here is
|
|
17
|
+
something else we want to build", because a scope that grows by exception is the
|
|
18
|
+
failure ADR 0264 exists to stop.
|
|
19
|
+
|
|
20
|
+
## The decision
|
|
21
|
+
|
|
22
|
+
**Autobongos becomes working area 11** — [goal 1000119](https://cloudbongos.com/builders#/goal/1000119) on BONGOS-V2.
|
|
23
|
+
|
|
24
|
+
> **11 · Autobongos** — *owner: Lars.* At 5,000 builders: a project owner points
|
|
25
|
+
> idle compute at goals they have allowlisted, and it builds unattended inside
|
|
26
|
+
> their subscription limits — stopping rather than guessing whenever a human is
|
|
27
|
+
> needed, and reporting what it did without being asked.
|
|
28
|
+
|
|
29
|
+
## Why this is an axis and not a feature of an existing area
|
|
30
|
+
|
|
31
|
+
The obvious home was **area 6** (Governor / Builder / Artist / Ideator
|
|
32
|
+
experience), whose 5,000-builder state already says *"Agents are designed to aid
|
|
33
|
+
all these roles."* It was considered and rejected, for one reason:
|
|
34
|
+
|
|
35
|
+
**Area 6 is about a human's experience of the platform. This area has no human in
|
|
36
|
+
it.** Its whole subject is what the system does across the hours when nobody is
|
|
37
|
+
watching — which limits it may spend, which fences hold without supervision, how
|
|
38
|
+
it recovers from failure with nobody at the keyboard, and what it owes the owner
|
|
39
|
+
the next morning. Those questions do not get better answers by being asked
|
|
40
|
+
alongside "how do we keep the Artist's surface from breaking into the Ideator's".
|
|
41
|
+
They get less attention, because they compete with work that has a user in front
|
|
42
|
+
of it.
|
|
43
|
+
|
|
44
|
+
Area 4 (core distribution) was the other candidate and is a worse fit still — it
|
|
45
|
+
would file autonomous building as a shipping-cadence tool, under a goal about how
|
|
46
|
+
core updates reach instances.
|
|
47
|
+
|
|
48
|
+
## It reopens a closed goal rather than inventing a new one
|
|
49
|
+
|
|
50
|
+
[Goal 1000028 — *Autonomy groundwork*](https://cloudbongos.com/builders#/goal/1000028)
|
|
51
|
+
(*"safe unattended routines — guardrails first, then arm the safe set"*, criteria
|
|
52
|
+
`gcarry-auto-guardrails` and `gcarry-auto-armed`) was archived in the 38→10 cut
|
|
53
|
+
**with no successor**. Goal 1000119 carries `succeeds_goal_id: 1000028`, so the
|
|
54
|
+
lineage is in the ledger rather than in this prose: the eleventh area is a
|
|
55
|
+
reopening, not a new axis nobody had thought of.
|
|
56
|
+
|
|
57
|
+
That is also the honest reading of what happened. ADR 0264 §3 left three goals
|
|
58
|
+
*"not covered by the review"*, one of them explicitly marked **"needs an owner
|
|
59
|
+
decision: fold, keep as an eleventh, or close."** The eleventh slot was a live
|
|
60
|
+
question in the ADR itself, not a door this one forces.
|
|
61
|
+
|
|
62
|
+
## What it cost to admit, and why that is recorded
|
|
63
|
+
|
|
64
|
+
BONGOS-V2 is `building`, so its goal set is **closed** ([ADR 0250](0250-strict-versioning-the-version-boundary-is-the-scope-gate.md)):
|
|
65
|
+
`POST /goals` refuses with `version_not_planning`. An Archon may reopen it, but
|
|
66
|
+
only by writing an `admit_reason`, which is stored against the goal with who
|
|
67
|
+
admitted it and when.
|
|
68
|
+
|
|
69
|
+
Goal 1000119 therefore carries a permanent admission record. This is the gate
|
|
70
|
+
working, and it is the answer to "does an eleventh area mean the cut did not
|
|
71
|
+
hold": the cut held well enough that adding to it left evidence.
|
|
72
|
+
|
|
73
|
+
## What this does not decide
|
|
74
|
+
|
|
75
|
+
- **The ten-area discipline stands.** This is one exception with a written
|
|
76
|
+
reason, not a return to a board that grows on request. The 5,000-builder
|
|
77
|
+
horizon is unchanged, and areas 1–10 are untouched.
|
|
78
|
+
- **It does not settle ADR 0264 §3's other two open rows** — goal 1000065
|
|
79
|
+
(community surface) and goal 1000088 (repo cleanup) are still open questions.
|
|
80
|
+
- **It does not give the area a second goal.** One goal per area is the rule that
|
|
81
|
+
makes ten legible, and eleven is only legible under the same rule.
|
|
82
|
+
|
|
83
|
+
## Consequences
|
|
84
|
+
|
|
85
|
+
- The board is twelve rows: eleven areas plus maintenance.
|
|
86
|
+
- ADR 0264's §1 table is now incomplete on its own, so it links forward to this
|
|
87
|
+
record. A reader who finds 0264 first is not left with a stale ten.
|
|
88
|
+
- The next area proposed has a precedent to argue against, which is the point of
|
|
89
|
+
writing the reasoning down rather than just the outcome.
|
package/docs/adr/README.md
CHANGED
|
@@ -398,3 +398,5 @@ This keeps the decision history honest and traceable.
|
|
|
398
398
|
| 0304 | [**Routine reads run without a prompt; anything a flag can turn into a write is exact-form** ([task 1003992](https://cloudbongos.com/builders#/task/1003992), goal 1000095). The shipped `.claude/settings.json` pre-allowed six commands, so every `gh pr view` or `git fetch` asked a non-technical builder to approve a command they cannot evaluate. The read-only subset is now pre-allowed — `git status`, `gh pr view/checks/list`, `gh run view/list` and the gds CLIs audited read-only under every flag as prefix rules; `git branch` and `git fetch` as exact forms only, because a flag turns either into a write or an exec — and the instance starter mirrors it rule for rule. `git diff/log/show` get NO rule: the harness already runs their read-only forms unprompted with a flag check, and a prefix rule switched that check off (`--output=<path>` writes anywhere, `diff --no-index` reads deny-listed files — both reproduced in review). A test pins the whole allow-list, that no rule covers a command git-guard blocks, and that no rule covers a write dressed as a read. Rejected: `Bash(git:*)`, prefix-plus-`ask` for diff/log/show (argument patterns are evaded by quoting), prefix rules for fetch/branch, `bypassPermissions` everywhere.](0304-routine-reads-run-without-a-prompt-and-anything-a-flag-can-turn-into-a-write-is-exact-form.md) | GDS / sessions / permissions |
|
|
399
399
|
| 0305 | [**A recipe lives with what it documents** ([task 1002156](https://cloudbongos.com/builders#/task/1002156), goal 1000091, from idea 536 / idea 537). Part A of the 2026-07-10 architecture review, held until the owner confirmed direction. The binary core-vs-instance split in `isPublishable()` has collapsed to one side: all **36** files in `docs/recipes/` and all **4** in `docs/research/` publish, and the denylist rows that once held the instance-local half name files that no longer exist. Worse, it is producing wrong docs — four published recipes document the dev-box that goal 1000091 is deleting, and none of the nine removal tasks covers them ([task 1004013](https://cloudbongos.com/builders#/task/1004013)). The cause is not neglect: **nothing structurally ties a recipe to what it documents**. So the rule is ownership, not portability — a recipe lives in the same deletable unit as its subject. Three classes: core methodology stays in `docs/recipes/`; feature-coupled moves to `modules/<key>/recipes/` and dies with its module; instance-operational stays behind the publish gate. `scripts/` is unchanged (it is the CLI, not documentation with a portability question); `docs/research/` becomes instance-local (situated artifacts, and the likeliest brand-name leak). **The thesis is rejected in its literal form**: a shared recipe is not a NEW asset class parallel to modules but a directory *inside* one, reusing the manifest attribution, submit→review→accept, security accept-gate, default-off enablement and orphaning policy that ADR 0107/0135 already ship — `modules/ui-design/docs/design-contract.md` is the undesigned precedent. Rejected: a parallel registry (a second manifest, queue, attribution model and orphaning policy, to distribute Markdown; Supermarket/Galaxy distribute executables with dependency resolution, which is what a module already is); classify-and-keep-the-binary-split (treats the symptom, restores the local half only until the next module ships a recipe); per-recipe portability front-matter (no build step reads it, no deletion touches it); deleting `docs/research/` (live inputs to open goals). Decides direction only — no file moves; the sweep and a fitness check for a recipe outliving its module are follow-on.](0305-a-recipe-lives-with-what-it-documents.md) | modules / upstreaming |
|
|
400
400
|
| 0306 | [**Instance supervision is a seam, with systemd as the default** ([task 1003997](https://cloudbongos.com/builders#/task/1003997), goal 1000106, F13 from the task-1002718 QA walk). `provision-units.js` hardcoded `/etc/systemd/system` in three path functions and the runner reached for `systemctl` inline at fifteen call sites, so a standup was impossible on any plane without systemd — the self-host image runs `USER node`, installs no `sudo` and boots no systemd. **A directory knob does not fix this**: pointing the unit path somewhere writable still emits `daemon-reload` into a namespace where systemd is not PID 1. What varies is not where the unit file goes, it is what supervises the process. Chosen: four operations (install+enable, install backup schedule, restart, remove) move behind `scripts/gds/provision-process.js`, taking the `{ exec, writeFile, sudoP, log }` io the runbook already threads, so dry-run, remote-ssh and logging behaviour carry over untouched (ADR 0130). Selection is explicit (`PROVISION_PROCESS_MANAGER`, an unknown value throws) → probed (`/run/systemd/system`, the `sd_booted(3)` check) → supervisord. **The live box is unchanged by construction** — it boots systemd, and the systemd implementation emits byte-identical commands; the untouched composition assertions in `tests/provision.mjs` are the proof. Rejected: systemd `--user` units (fix an unprivileged VM we do not have, while a plain container has no user bus either). Consequences: isolation is restated per manager (systemd `User=` → supervisord `user=`, ADR 0281) or it silently drops; supervisord has no timers, so it installs no backup schedule and prints the `crontab` line instead of re-implementing cron; the seam is two implementations wide, not speculatively N. **This removes one of four container blockers** — ingress ([task 1004017](https://cloudbongos.com/builders#/task/1004017)), per-instance identity ([task 1004018](https://cloudbongos.com/builders#/task/1004018)) and the hardcoded backup-dir owner ([task 1002719](https://cloudbongos.com/builders#/task/1002719)) remain, so task 1003997's done-when was narrowed to the seam rather than claiming a self-host story that is not there.](0306-instance-supervision-is-a-seam-with-systemd-as-the-default.md) | infra / provisioning |
|
|
401
|
+
| 0307 | [**Autobongos: a deterministic supervisor, agentic workers** ([task 1003900](https://cloudbongos.com/builders#/task/1003900), goal 1000119). An always-on machine works the queue unattended inside owner-allowlisted goals. Most of the loop already existed wired to the wrong trigger — `sequence.js`'s ten-reason halt matrix (ADR 0153), an open blocker as the hold state, the Windows-aware headless spawn seam, `overnight-builder` — but the trigger was cron rather than capacity, and the **supervisor was itself a Claude session**. Decided: the supervisor is a **program**, only the worker is a session; the gauge is the statusLine's `rate_limits.five_hour`/`seven_day`, which per Anthropic's own note is absent until a session's first API response and so **cannot be read cold** (a statusline script persists it, a stale file is *unknown*, never *headroom*); **one session per task, metering not estimating** (a successor cannot be claimed until its predecessor ships, so a batch produces the same order while risking a half-done window, and a fresh session avoids a checkout going stale behind main); **completion is read from the ledger, never the session** (a worker that stopped needing a decision returns `subtype: success`, identical to one that finished); **no terminal stop, only speeds** (three failures → a probing state that self-heals; the only true stop is the server-side kill switch); the machine holds **no push credential**, so a grader outage stops the run rather than shipping unreviewed. The load-bearing finding was **tested before it was decided**: a worker told plainly to stop and ask instead settled four judgement calls and wrote them to disk — competent, and fatal to any design that waits for raised hands. Hence **gate before, record after, nothing in between**: the halt matrix refuses ambiguous work at pick time, and the worker's verdict must declare what the spec did not determine. Rejected: per-call `--permission-prompt-tool` (a thousand weak gates, and one slow supervisor stalls every worker), `--max-budget-usd` (caps dollars; on a subscription the constraint is capacity, so the bound is wall-clock), extending `overnight-builder`, and a terminal stop. Deliberately not built: mid-flight question answering, portability, per-task estimation.](0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md) | autonomy / sessions |
|
|
402
|
+
| 0308 | [**An eleventh working area: Autobongos** ([task 1003900](https://cloudbongos.com/builders#/task/1003900), goal 1000119). Amends [ADR 0264](0264-the-ten-working-areas.md), which nine days earlier collapsed 38 goals into **ten** areas held until 5,000 builders. Area 11 is Autobongos ([goal 1000119](https://cloudbongos.com/builders#/goal/1000119), owner Lars). **Area 6 was the obvious home and was rejected**: area 6 is about a human's experience of the platform, and this area has no human in it — which limits to spend, which fences hold unsupervised, how it recovers with nobody at the keyboard. Those get less attention next to work with a user in front of it, not better answers. It **reopens** goal 1000028 (*Autonomy groundwork*), archived in the 38→10 cut with no successor and carried as `succeeds_goal_id`, so the lineage is in the ledger rather than the prose — and ADR 0264 §3 had already left "fold, keep as an eleventh, or close" as a live owner question. Admitting it cost something recorded: BONGOS-V2 is `building`, so its goal set is closed (ADR 0250) and `POST /goals` refuses `version_not_planning` — an Archon reopened it with a stored `admit_reason`, which is the cut holding well enough that adding to it left evidence. Does not decide: the ten-area discipline stands (one exception, written reason, 5,000-builder horizon unchanged), 0264 §3's other two open rows, or a second goal for the area.](0308-an-eleventh-working-area.md) | methodology / goals |
|
|
@@ -2195,6 +2195,8 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2195
2195
|
1.19.860 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2196
2196
|
landed since 1.19.859 with no explicit bump. run 35281454119. (task 1002620)
|
|
2197
2197
|
1.19.861 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2198
|
-
landed since 1.19.860 with no explicit bump. run
|
|
2198
|
+
landed since 1.19.860 with no explicit bump. run 35290990147. (task 1002620)
|
|
2199
|
+
1.19.862 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2200
|
+
landed since 1.19.861 with no explicit bump. run 35292190396. (task 1002620)
|
|
2199
2201
|
---------------------------------------------------------------------------
|
|
2200
2202
|
```
|
|
@@ -79,9 +79,9 @@ Full bibliography in [§10](#10-bibliography). Our positioning is from a direct
|
|
|
79
79
|
This is the most important section for understanding why Cursor does what it does. The picture is now well-sourced (no longer contested):
|
|
80
80
|
|
|
81
81
|
- **Inference is largely a pass-through.** When a user selects a specific frontier model, "usage is drawn from the API pool at that model's API rate" ([cursor.com/docs](https://cursor.com/docs/models-and-pricing), ✅ 2-1) — so the $20 Pro sub roughly *buys $20 of model usage*. Margin can't come from reselling tokens.
|
|
82
|
-
- **Cursor ran negative gross margins.** Through 2025 it spent an estimated **$0.40–$0.70 of model-inference cost per $1 of revenue** (Anthropic + OpenAI API costs), making structural gross margin **~30–50% vs the 80%+ of normal software** — the "token tax" that "locks AI-agent gross margins ~30 points below the SaaS baseline" ([Contrary Research](https://research.contrary.com/company/cursor);
|
|
82
|
+
- **Cursor ran negative gross margins.** Through 2025 it spent an estimated **$0.40–$0.70 of model-inference cost per $1 of revenue** (Anthropic + OpenAI API costs), making structural gross margin **~30–50% vs the 80%+ of normal software** — the "token tax" that "locks AI-agent gross margins ~30 points below the SaaS baseline" ([Contrary Research](https://research.contrary.com/company/cursor); TechTimes (2026-06-01, article 317542 — link lost); Yahoo Finance — "high costs and thin margins threatening AI coding startups" (link lost)). Contrary quotes the criticism directly: Cursor charges "lower or equivalent pricing to Anysphere's own costs of accessing foundation models."
|
|
83
83
|
- **The owned model is the margin fix — and it's partly working.** Composer (Nov 2025) + cheaper models like Kimi moved Cursor to **slight gross-margin profitability — positive on large enterprise, but still losing money on individual developer accounts** ([market reporting via search, 2026](https://mktclarity.com/blogs/news/is-cursor-profitable)). Internal telemetry reportedly showed Composer handling **~half of autocomplete requests** while matching Claude Sonnet on common tasks. 📄
|
|
84
|
-
- **Forward pressure:** Cursor reportedly forecasts ending **2026 at a >$6B annualized run-rate** (
|
|
84
|
+
- **Forward pressure:** Cursor reportedly forecasts ending **2026 at a >$6B annualized run-rate** (letsdatascience — link lost) — i.e., it expects to roughly triple again. 📄 (forecast; treat as a target, not a fact.)
|
|
85
85
|
|
|
86
86
|
### Revenue & ARR trajectory ✅
|
|
87
87
|
|
|
@@ -189,7 +189,7 @@ For an AI-first **orchestration** platform, rank threats by *"does it manage aut
|
|
|
189
189
|
|
|
190
190
|
| Competitor | What it is | Threat to an *orchestration* platform vs Cursor |
|
|
191
191
|
|---|---|---|
|
|
192
|
-
| **GitHub Copilot coding agent** | Issue→PR autonomous agent inside the dominant SCM ([docs](https://docs.github.com/copilot/concepts/agents/coding-agent/about-coding-agent),
|
|
192
|
+
| **GitHub Copilot coding agent** | Issue→PR autonomous agent inside the dominant SCM ([docs](https://docs.github.com/copilot/concepts/agents/coding-agent/about-coding-agent), noqta.tn — link lost) | **MORE.** Autonomous, task-scoped, lands a PR through review gates — orchestration-shaped *and* riding GitHub's distribution + enterprise relationships. |
|
|
193
193
|
| **Anthropic Claude Code** | Terminal-native autonomous coding agent ([mindstudio](https://www.mindstudio.ai/blog/claude-code-vs-openai-codex-comparison)) | **MORE — and special.** Closest to "agents author the code," *and it is Cloud Bongos's own substrate.* The most-competitive system is the one you're built on. It also showed (Jan 2026) it will police who resells its access. |
|
|
194
194
|
| **OpenAI Codex** | Cloud autonomous agent, parallel tasks | **MORE / EQUAL.** Same orchestration encroachment from the other frontier lab. |
|
|
195
195
|
| **Devin / Cognition** | Autonomous "AI software engineer" + agent-management UI | **MORE.** The most direct *conceptual* competitor to "agents own tasks end-to-end," with its own session/management layer. |
|
|
@@ -217,6 +217,12 @@ For an AI-first **orchestration** platform, rank threats by *"does it manage aut
|
|
|
217
217
|
|
|
218
218
|
## 10. Bibliography
|
|
219
219
|
|
|
220
|
+
> **On the "link lost" markers below.** A public-mirror scrubbing pass overwrote the URL
|
|
221
|
+
> paths of several sources in this file's source text. Where the article could be
|
|
222
|
+
> re-identified with confidence against the claim it supports, the link has been restored;
|
|
223
|
+
> where it could not, the citation text is kept and the bare URL dropped rather than
|
|
224
|
+
> replaced with a plausible-looking guess.
|
|
225
|
+
|
|
220
226
|
**Verified (✅ — adversarially confirmed in-harness and/or confirmed against a named source in the 2026-06-22 main-loop round):**
|
|
221
227
|
- Cursor — Models & Pricing (primary): https://cursor.com/docs/models-and-pricing
|
|
222
228
|
- Cursor — Series D announcement (primary): https://cursor.com/blog/series-d
|
|
@@ -231,15 +237,15 @@ For an AI-first **orchestration** platform, rank threats by *"does it manage aut
|
|
|
231
237
|
- TechCrunch — Cursor in talks to raise $2B at $50B as enterprise growth surges (2026-04-17): https://techcrunch.com/2026/04/17/<redacted>/
|
|
232
238
|
|
|
233
239
|
**Sourced (📄 — reputable, single-pass; cite with care):**
|
|
234
|
-
- TechTimes — AI-agent economics / token-tax 30 points below SaaS
|
|
235
|
-
- Yahoo Finance — high costs and thin margins threatening AI coding startups
|
|
240
|
+
- TechTimes — AI-agent economics / token-tax 30 points below SaaS (2026-06-01, article 317542) — link lost
|
|
241
|
+
- Yahoo Finance — high costs and thin margins threatening AI coding startups — link lost
|
|
236
242
|
- Market Clarity — is Cursor profitable (enterprise-positive / individual-negative margins): https://mktclarity.com/blogs/news/is-cursor-profitable
|
|
237
|
-
- TheRouter — Cursor Bugbot, Composer 2.5, model block-list governance
|
|
238
|
-
- ValueAdd VC — how a code editor became a $9B company
|
|
243
|
+
- TheRouter — Cursor Bugbot, Composer 2.5, model block-list governance — link lost
|
|
244
|
+
- ValueAdd VC — how a code editor became a $9B company — link lost
|
|
239
245
|
- GetPanto — Cursor AI statistics 2026: https://www.getpanto.ai/blog/cursor-ai-statistics
|
|
240
246
|
- Wikipedia — Anysphere / Cursor (company): https://en.wikipedia.org/wiki/Anysphere · https://en.wikipedia.org/wiki/Cursor_(company)
|
|
241
247
|
- MindStudio — Claude Code vs OpenAI Codex: https://www.mindstudio.ai/blog/claude-code-vs-openai-codex-comparison
|
|
242
|
-
- GitHub — About Copilot coding agent: https://docs.github.com/copilot/concepts/agents/coding-agent/about-coding-agent · noqta.tn (autonomous PR)
|
|
248
|
+
- GitHub — About Copilot coding agent: https://docs.github.com/copilot/concepts/agents/coding-agent/about-coding-agent · noqta.tn (autonomous PR) — link lost
|
|
243
249
|
- Coder — Compare Cursor / "is the IDE dead": https://coder.com/solutions/agents/compare-cursor · https://coder.com/blog/is-the-ide-dead-the-rise-of-agentic-ai-in-software-development
|
|
244
250
|
- MarkTechPost — AI coding agents & dev platforms 2026: https://www.marktechpost.com/2026/06/10/<redacted>/
|
|
245
251
|
|
|
@@ -53,6 +53,24 @@ const { mostRecentClaimHolderId, sealSessionLog, staleClaimHourPredicate } = req
|
|
|
53
53
|
// creator_session_id (a web/unbound claim per #616, or one pre-dating the column),
|
|
54
54
|
// and a resolver with no session context at all.
|
|
55
55
|
//
|
|
56
|
+
// claimTreeIdentity — WHICH of a claim row's two worktree names identifies the
|
|
57
|
+
// working TREE (task 1004031). Pure + exported for the same reason the decision
|
|
58
|
+
// below is: the bug was never in the comparison, it was in which field reached
|
|
59
|
+
// it, so that choice has to be testable on its own.
|
|
60
|
+
//
|
|
61
|
+
// requested_worktree_name — what the caller asked for. The tree's identity,
|
|
62
|
+
// and what `detectWorktreeName()` sends back at resolve time.
|
|
63
|
+
// worktree_name — the DISAMBIGUATED name. V3.R51 appends `-2` when
|
|
64
|
+
// another ACTIVE claim holds the requested one, so it is unique across live
|
|
65
|
+
// claims but names no directory on disk.
|
|
66
|
+
//
|
|
67
|
+
// Comparing the disambiguated name refuses every ship from a tree whose name
|
|
68
|
+
// ever collided. Falls back for rows written before requested_worktree_name
|
|
69
|
+
// existed, where the stored name is the only one there is.
|
|
70
|
+
function claimTreeIdentity(claim = {}) {
|
|
71
|
+
return claim.requested_worktree_name ?? claim.worktree_name ?? null;
|
|
72
|
+
}
|
|
73
|
+
|
|
56
74
|
// Pure + exported so the decision is unit-testable with no DB and no route —
|
|
57
75
|
// the same shape as shouldRefuseSessionClaim. Returns { refuse, reason }.
|
|
58
76
|
function shouldRefuseClaimResolve({
|
|
@@ -109,7 +127,7 @@ async function resolveClaim({ claimId, builderId, outcome, notesMd, valueSummary
|
|
|
109
127
|
return withTx(async (client) => {
|
|
110
128
|
const { rows: claimRows } = await client.query(
|
|
111
129
|
`SELECT c.id, c.task_id, c.builder_id, c.released_at, c.claimed_at,
|
|
112
|
-
c.worktree_name, c.creator_session_id,
|
|
130
|
+
c.worktree_name, c.requested_worktree_name, c.creator_session_id,
|
|
113
131
|
t.credits_reward, t.title
|
|
114
132
|
FROM claims c JOIN tasks t ON t.id = c.task_id
|
|
115
133
|
WHERE c.id = $1 FOR UPDATE`,
|
|
@@ -125,10 +143,26 @@ async function resolveClaim({ claimId, builderId, outcome, notesMd, valueSummary
|
|
|
125
143
|
// session, is the hazard boundary. Skips: force=true, an unbound/web claim,
|
|
126
144
|
// or a resolver with no session context. `reason` distinguishes the two
|
|
127
145
|
// refusal shapes so the CLI can say which one happened.
|
|
146
|
+
//
|
|
147
|
+
// WHICH NAME (task 1004031). The row stores two, and they answer different
|
|
148
|
+
// questions (the same distinction db-claims.js draws at the insert):
|
|
149
|
+
// `worktree_name` is the DISAMBIGUATED name — V3.R51 appends a `-2` when
|
|
150
|
+
// another ACTIVE claim already holds the requested one, so it is unique
|
|
151
|
+
// across live claims but is NOT the name of any directory on disk.
|
|
152
|
+
// `requested_worktree_name` is what the caller asked for: the identity of
|
|
153
|
+
// the working TREE. The resolver sends `detectWorktreeName()`, the real
|
|
154
|
+
// directory basename — so comparing against the disambiguated name refuses
|
|
155
|
+
// every ship from a tree whose name ever collided, and names a `<tree>-2`
|
|
156
|
+
// directory that has never existed. Reproduced live on task 1003900.
|
|
157
|
+
// db-claims.js already records this exact hazard for the one-claim-per-tree
|
|
158
|
+
// guard ("keying on the STORED name was the hole"); this is the same hole,
|
|
159
|
+
// one check over. Fall back to `worktree_name` for rows written before
|
|
160
|
+
// `requested_worktree_name` existed, where it is the only name there is.
|
|
161
|
+
const claimTree = claimTreeIdentity(claim);
|
|
128
162
|
const resolveLock = shouldRefuseClaimResolve({
|
|
129
163
|
claimSessionId: claim.creator_session_id,
|
|
130
164
|
resolverSessionId,
|
|
131
|
-
claimWorktree:
|
|
165
|
+
claimWorktree: claimTree,
|
|
132
166
|
resolverWorktree,
|
|
133
167
|
force,
|
|
134
168
|
});
|
|
@@ -136,7 +170,9 @@ async function resolveClaim({ claimId, builderId, outcome, notesMd, valueSummary
|
|
|
136
170
|
throw {
|
|
137
171
|
code: 'CLAIM_SESSION_MISMATCH',
|
|
138
172
|
reason: resolveLock.reason,
|
|
139
|
-
|
|
173
|
+
// Name the tree the builder can actually `cd` into, not the suffixed
|
|
174
|
+
// label — the old text sent them to a directory that cannot exist.
|
|
175
|
+
claimWorktree: claimTree,
|
|
140
176
|
};
|
|
141
177
|
}
|
|
142
178
|
|
|
@@ -1349,6 +1385,7 @@ async function mergeTask({ taskId, intoTaskId, reason }, deps = {}) {
|
|
|
1349
1385
|
}
|
|
1350
1386
|
|
|
1351
1387
|
module.exports = {
|
|
1388
|
+
claimTreeIdentity,
|
|
1352
1389
|
mergeTask,
|
|
1353
1390
|
abandonTask,
|
|
1354
1391
|
confirmTask,
|
|
@@ -200,7 +200,8 @@ function makeCoreUpgradeHandler({ provisioning, pool, parseId, validateOrRespond
|
|
|
200
200
|
/**
|
|
201
201
|
* What the control shows BEFORE anything is pressed — step one of the two-step (D4).
|
|
202
202
|
*
|
|
203
|
-
* Reads the snapshot the control-plane runner writes (migration
|
|
203
|
+
* Reads the snapshot the control-plane runner writes (migration provisioning_019),
|
|
204
|
+
* because
|
|
204
205
|
* neither fact is reachable from here: the served version lives on the box's loopback
|
|
205
206
|
* and the available versions need a blocking `npm view`. The web tier asks; it does not
|
|
206
207
|
* go and look.
|
|
@@ -234,7 +235,7 @@ function previewFromSnapshot(inst, snap, openIntent) {
|
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
async function loadPreview(pool, provisioning, inst) {
|
|
237
|
-
const { rows } = await pool.query('SELECT * FROM
|
|
238
|
+
const { rows } = await pool.query('SELECT * FROM provisioning_core_version_snapshot WHERE instance_id = $1', [inst.id]);
|
|
238
239
|
const openIntent = provisioning.getOpenIntent ? await provisioning.getOpenIntent(pool, inst.id) : null;
|
|
239
240
|
return previewFromSnapshot(inst, rows[0], openIntent);
|
|
240
241
|
}
|
|
@@ -1,5 +1,32 @@
|
|
|
1
|
-
--
|
|
2
|
-
-- (task 1003159, ADR 0293 D3 + D6).
|
|
1
|
+
-- provisioning_019_core_version_snapshot.sql — what the box serves and what it could
|
|
2
|
+
-- take (task 1003159, ADR 0293 D3 + D6; MOVED here from core by task 1004030).
|
|
3
|
+
--
|
|
4
|
+
-- WHY THIS LIVES IN THE MODULE NOW. It arrived as migrations/core_242_core_version_snapshot.sql
|
|
5
|
+
-- and took a foreign key on provisioning_instances — a CORE table depending on a MODULE
|
|
6
|
+
-- one, which ADR 0083's one-way rule forbids. That is not only a layering complaint: it
|
|
7
|
+
-- BROKE every fresh database. scripts/migrate.sh applies the merged set sorted by
|
|
8
|
+
-- BASENAME, so every `core_*` file runs before every `provisioning_*` one; core_242
|
|
9
|
+
-- therefore always reached a database where provisioning_instances did not exist yet,
|
|
10
|
+
-- errored, and took migrate.sh out with exit 3 at migration 147 of ~242. Deterministic,
|
|
11
|
+
-- and invisible on any database that already had the table — which is why production
|
|
12
|
+
-- never saw it and a new contributor's first `bongos dev` did.
|
|
13
|
+
--
|
|
14
|
+
-- Owning it here fixes both halves at once: the dependency is now module→module, and
|
|
15
|
+
-- provisioning_001_tables.sql sorts before this file, so the table it references exists
|
|
16
|
+
-- by the time this runs. The FK and its ON DELETE CASCADE are kept deliberately —
|
|
17
|
+
-- disconnect.js deletes the instance row and documents that its children go with it.
|
|
18
|
+
--
|
|
19
|
+
-- RENAMED to the provisioning_ prefix because a module may only create objects inside
|
|
20
|
+
-- its own namespace (fitness.js check 11, ADR 0083 §Decision #5). The old
|
|
21
|
+
-- core_version_snapshot table is left untouched on databases that already applied
|
|
22
|
+
-- core_242: it is a CACHE, nothing reads it after this lands, and core migrations here
|
|
23
|
+
-- have no DROP TABLE precedent worth inventing for one small table.
|
|
24
|
+
--
|
|
25
|
+
-- NO DATA IS CARRIED OVER, on purpose. Every field is an observation the control-plane
|
|
26
|
+
-- runner rewrites on its next tick, and a missing row is a first-class state the reader
|
|
27
|
+
-- already handles: loadPreview passes rows[0] === undefined straight through and
|
|
28
|
+
-- previewFromSnapshot reports reading:'no_answer' rather than rendering "up to date"
|
|
29
|
+
-- over a reading it does not have.
|
|
3
30
|
--
|
|
4
31
|
-- THE PROBLEM THIS SOLVES IS A LAYERING ONE, not a storage one. The deploy control has
|
|
5
32
|
-- to show the owner two facts before they press anything: the version the box is
|
|
@@ -32,7 +59,7 @@
|
|
|
32
59
|
|
|
33
60
|
BEGIN;
|
|
34
61
|
|
|
35
|
-
CREATE TABLE IF NOT EXISTS
|
|
62
|
+
CREATE TABLE IF NOT EXISTS provisioning_core_version_snapshot (
|
|
36
63
|
instance_id bigint PRIMARY KEY REFERENCES provisioning_instances(id) ON DELETE CASCADE,
|
|
37
64
|
-- What 127.0.0.1/version answered. NULL means the box did not answer — a real and
|
|
38
65
|
-- different state from "answered with an old version", and the control must not
|
|
@@ -48,7 +75,4 @@ CREATE TABLE IF NOT EXISTS core_version_snapshot (
|
|
|
48
75
|
observed_at timestamptz NOT NULL DEFAULT now()
|
|
49
76
|
);
|
|
50
77
|
|
|
51
|
-
INSERT INTO schema_migrations (version) VALUES ('core_242_core_version_snapshot')
|
|
52
|
-
ON CONFLICT (version) DO NOTHING;
|
|
53
|
-
|
|
54
78
|
COMMIT;
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.862",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.862",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.862",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -251,7 +251,7 @@ async function coreUpgradeInstance(inst, deps, intent) {
|
|
|
251
251
|
*
|
|
252
252
|
* Runs on every drain tick for each upgradable instance. Both facts it records are ones
|
|
253
253
|
* the web tier cannot reach for itself — the served version lives on loopback, the
|
|
254
|
-
* available versions need a blocking `npm view` (migration
|
|
254
|
+
* available versions need a blocking `npm view` (migration provisioning_019 says why at length)
|
|
255
255
|
* — so this is the runner reporting on behalf of a process that may only ask.
|
|
256
256
|
*
|
|
257
257
|
* BEST-EFFORT, ALWAYS. A registry hiccup or a box that will not answer must never fail a
|
|
@@ -280,7 +280,7 @@ async function snapshotCoreVersions(inst, deps) {
|
|
|
280
280
|
? channelMod.resolveChannelTarget({ installed: served, channel: channelMod.DEFAULT_CHANNEL, available })
|
|
281
281
|
: null;
|
|
282
282
|
await db.query(
|
|
283
|
-
`INSERT INTO
|
|
283
|
+
`INSERT INTO provisioning_core_version_snapshot (instance_id, served_version, available, recommended, observed_at)
|
|
284
284
|
VALUES ($1, $2, $3::jsonb, $4, now())
|
|
285
285
|
ON CONFLICT (instance_id) DO UPDATE
|
|
286
286
|
SET served_version = EXCLUDED.served_version, available = EXCLUDED.available,
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.862'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -33,7 +33,7 @@ import { test } from 'node:test';
|
|
|
33
33
|
import { createRequire } from 'node:module';
|
|
34
34
|
|
|
35
35
|
const require = createRequire(import.meta.url);
|
|
36
|
-
const { shouldRefuseClaimResolve } = require('../modules/lifecycle/db-ship.js');
|
|
36
|
+
const { shouldRefuseClaimResolve, claimTreeIdentity } = require('../modules/lifecycle/db-ship.js');
|
|
37
37
|
|
|
38
38
|
// ── 1) the bug this task exists to fix ──────────────────────────────────────────
|
|
39
39
|
test('re-auth mid-claim: same worktree, NEW session → allowed', () => {
|
|
@@ -255,3 +255,59 @@ test('an empty or malformed details payload does not trigger the retry', () => {
|
|
|
255
255
|
assert.equal(isUnknownFieldRejection(400, null, 'resolver_worktree'), false);
|
|
256
256
|
assert.equal(isUnknownFieldRejection(400, { error: 'flat_string_error' }, 'resolver_worktree'), false);
|
|
257
257
|
});
|
|
258
|
+
|
|
259
|
+
// ── 4) task 1004031: WHICH of the two stored names reaches the comparison ───────
|
|
260
|
+
//
|
|
261
|
+
// The guard above was never wrong. The caller was: it passed the DISAMBIGUATED
|
|
262
|
+
// `worktree_name` (V3.R51 appends `-2` when another ACTIVE claim already holds
|
|
263
|
+
// the requested name) while the resolver sends the real directory basename. A
|
|
264
|
+
// tree whose name had ever collided could therefore never ship, and the refusal
|
|
265
|
+
// named a `<tree>-2` directory that has never existed on disk. Reproduced live
|
|
266
|
+
// on task 1003900, where two full grader panels were spent against the wall.
|
|
267
|
+
|
|
268
|
+
test('a disambiguated claim resolves against the tree the builder is actually in', () => {
|
|
269
|
+
const claim = { requested_worktree_name: 'goal-1000086-completion', worktree_name: 'goal-1000086-completion-2' };
|
|
270
|
+
assert.equal(claimTreeIdentity(claim), 'goal-1000086-completion',
|
|
271
|
+
'the -2 suffix names no directory; the requested name is the tree identity');
|
|
272
|
+
|
|
273
|
+
const d = shouldRefuseClaimResolve({
|
|
274
|
+
claimSessionId: 'sess-1',
|
|
275
|
+
resolverSessionId: 'sess-1',
|
|
276
|
+
claimWorktree: claimTreeIdentity(claim),
|
|
277
|
+
resolverWorktree: 'goal-1000086-completion',
|
|
278
|
+
});
|
|
279
|
+
assert.equal(d.refuse, false, 'a name collision with someone else must not strand this ship');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('the pre-fix behaviour is what it refused — proving the regression is real', () => {
|
|
283
|
+
const d = shouldRefuseClaimResolve({
|
|
284
|
+
claimSessionId: 'sess-1',
|
|
285
|
+
resolverSessionId: 'sess-1',
|
|
286
|
+
claimWorktree: 'goal-1000086-completion-2', // the disambiguated name, as passed before the fix
|
|
287
|
+
resolverWorktree: 'goal-1000086-completion',
|
|
288
|
+
});
|
|
289
|
+
assert.equal(d.refuse, true, 'guard rails: this is the comparison that produced the live strand');
|
|
290
|
+
assert.equal(d.reason, 'worktree_mismatch');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('a row written before requested_worktree_name existed still uses its stored name', () => {
|
|
294
|
+
assert.equal(claimTreeIdentity({ worktree_name: 'legacy-tree' }), 'legacy-tree');
|
|
295
|
+
assert.equal(claimTreeIdentity({ requested_worktree_name: null, worktree_name: 'legacy-tree' }), 'legacy-tree');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('a web/unbound claim names no tree at all', () => {
|
|
299
|
+
assert.equal(claimTreeIdentity({}), null);
|
|
300
|
+
assert.equal(claimTreeIdentity({ requested_worktree_name: null, worktree_name: null }), null);
|
|
301
|
+
assert.equal(claimTreeIdentity(), null, 'called with nothing must not throw');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('two genuinely different trees are STILL refused after the fix', () => {
|
|
305
|
+
const d = shouldRefuseClaimResolve({
|
|
306
|
+
claimSessionId: 'sess-1',
|
|
307
|
+
resolverSessionId: 'sess-1',
|
|
308
|
+
claimWorktree: claimTreeIdentity({ requested_worktree_name: 'tree-a', worktree_name: 'tree-a-2' }),
|
|
309
|
+
resolverWorktree: 'tree-b',
|
|
310
|
+
});
|
|
311
|
+
assert.equal(d.refuse, true, 'the fix must not widen the guard into letting the wrong branch ship');
|
|
312
|
+
assert.equal(d.reason, 'worktree_mismatch');
|
|
313
|
+
});
|
|
@@ -341,7 +341,7 @@ await t('the snapshot records the served version, the registry list and what the
|
|
|
341
341
|
const queries = [];
|
|
342
342
|
const d = makeDeps({ served: '1.19.760', available: ['1.19.758', '1.19.760', '1.19.762'], queries });
|
|
343
343
|
await R.snapshotCoreVersions(inst(), d);
|
|
344
|
-
const w = queries.find((q) => /
|
|
344
|
+
const w = queries.find((q) => /provisioning_core_version_snapshot/.test(q.sql));
|
|
345
345
|
assert.ok(w, 'it wrote the snapshot');
|
|
346
346
|
assert.equal(w.params[1], '1.19.760', 'the served version');
|
|
347
347
|
assert.deepEqual(JSON.parse(w.params[2]), ['1.19.758', '1.19.760', '1.19.762']);
|
|
@@ -352,7 +352,7 @@ await t('a box that does not answer still writes a snapshot — a NULL reading i
|
|
|
352
352
|
const queries = [];
|
|
353
353
|
const d = makeDeps({ served: null, queries });
|
|
354
354
|
await R.snapshotCoreVersions(inst(), d);
|
|
355
|
-
const w = queries.find((q) => /
|
|
355
|
+
const w = queries.find((q) => /provisioning_core_version_snapshot/.test(q.sql));
|
|
356
356
|
assert.ok(w);
|
|
357
357
|
assert.equal(w.params[1], null);
|
|
358
358
|
assert.equal(w.params[3], null, 'and nothing is recommended against a version we could not read');
|