mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-09 03:39:51 -10:00
Compare commits
833
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d7970fd50 | ||
|
|
afde814f5b | ||
|
|
cc2c37dc2f | ||
|
|
cc02aef23f | ||
|
|
657bc01368 | ||
|
|
9b9cf9ec4c | ||
|
|
7810d5120a | ||
|
|
6678cef5a1 | ||
|
|
3dca5ee54b | ||
|
|
e6bfdce242 | ||
|
|
d3ad0d1995 | ||
|
|
56a3efeaa1 | ||
|
|
f45c64f642 | ||
|
|
8a8a3633ca | ||
|
|
c149053c5e | ||
|
|
87fc4fa2d5 | ||
|
|
f056f06a34 | ||
|
|
da2297e13a | ||
|
|
812e1e555f | ||
|
|
b2679174e5 | ||
|
|
c0a322950c | ||
|
|
4658c48842 | ||
|
|
c849d54f5f | ||
|
|
b3b9bdc09d | ||
|
|
757b204348 | ||
|
|
ed0f323092 | ||
|
|
bcc0010de9 | ||
|
|
5177ec8913 | ||
|
|
5b344e0578 | ||
|
|
b43498d7da | ||
|
|
8b5e948807 | ||
|
|
4dc4047cdb | ||
|
|
700af77ae0 | ||
|
|
3b1b335dbe | ||
|
|
e3ebd4ac2c | ||
|
|
100be9d646 | ||
|
|
f8f3aff68b | ||
|
|
1618bab05f | ||
|
|
b68571318d | ||
|
|
6ab706d435 | ||
|
|
a745475b07 | ||
|
|
18201ac659 | ||
|
|
aa7bc4407f | ||
|
|
6b8bee36b1 | ||
|
|
705cda2f0e | ||
|
|
5b7e07c906 | ||
|
|
9523b9de02 | ||
|
|
dc14d71793 | ||
|
|
636a4790f9 | ||
|
|
e4070e4169 | ||
|
|
1fffd9c988 | ||
|
|
cb29c1f7ce | ||
|
|
e8e8585be9 | ||
|
|
f6e5df150b | ||
|
|
0e416ab4ce | ||
|
|
b3f2b085cd | ||
|
|
a09388aabd | ||
|
|
d83bbebe97 | ||
|
|
d7fbf88639 | ||
|
|
f87a956dbb | ||
|
|
7981bca7a3 | ||
|
|
4a920dba73 | ||
|
|
a567d9b193 | ||
|
|
50e519b399 | ||
|
|
0dd5a56367 | ||
|
|
5d3f62f17b | ||
|
|
61f13f64ff | ||
|
|
95c6c9126c | ||
|
|
ea52fa8f83 | ||
|
|
57380a250e | ||
|
|
4fcf2910bb | ||
|
|
5fead45078 | ||
|
|
ae957e5957 | ||
|
|
edc01fe658 | ||
|
|
f137f11bef | ||
|
|
c9be421b9c | ||
|
|
5c54703035 | ||
|
|
bee173902a | ||
|
|
a52767c1fa | ||
|
|
0ffbf3df48 | ||
|
|
fbc30c2b6d | ||
|
|
6d27f87586 | ||
|
|
3b2fc4e624 | ||
|
|
1039f72637 | ||
|
|
a5e510c51f | ||
|
|
100b1630f3 | ||
|
|
b848edc580 | ||
|
|
3efa12a260 | ||
|
|
f64b735a89 | ||
|
|
3fec689fa6 | ||
|
|
49c1c783a3 | ||
|
|
51c0413d7b | ||
|
|
0f6d94310c | ||
|
|
1e74970aab | ||
|
|
ad0b39c60a | ||
|
|
4a7c49f4c0 | ||
|
|
608415dc9b | ||
|
|
2d1d383010 | ||
|
|
91a04d7098 | ||
|
|
e5e7e30bd4 | ||
|
|
6109431f97 | ||
|
|
ae8cf9c8f2 | ||
|
|
b374dde300 | ||
|
|
0859caa33c | ||
|
|
fac88fb27c | ||
|
|
3f1b95f49b | ||
|
|
efde04f93c | ||
|
|
681b5c1aa0 | ||
|
|
01a7d611a0 | ||
|
|
e11959d714 | ||
|
|
634b107f13 | ||
|
|
4940c10253 | ||
|
|
350dba4d4d | ||
|
|
b13133420d | ||
|
|
a69cd97279 | ||
|
|
767cc7053d | ||
|
|
6553aaef3a | ||
|
|
7490587e50 | ||
|
|
9c9e518d29 | ||
|
|
5f01c17f69 | ||
|
|
7eef424446 | ||
|
|
c8fca9d5c4 | ||
|
|
d7703c3035 | ||
|
|
c4a5e1e4aa | ||
|
|
cc75efdd17 | ||
|
|
2edc1b7a6d | ||
|
|
39a3f8fc57 | ||
|
|
b22c9794b7 | ||
|
|
83a3422435 | ||
|
|
452e3f73bc | ||
|
|
8f9c25e0b7 | ||
|
|
53ca93f746 | ||
|
|
76d7165e1b | ||
|
|
714c676b22 | ||
|
|
96f20a7c2b | ||
|
|
c29a180a0e | ||
|
|
6474317dbd | ||
|
|
ddb77d0a7a | ||
|
|
bb6337a62c | ||
|
|
96bc19d715 | ||
|
|
d4f8d57c2a | ||
|
|
1dc9d2a78a | ||
|
|
447af5d7f1 | ||
|
|
979400c29f | ||
|
|
49f1d34e78 | ||
|
|
87c66c1325 | ||
|
|
a5d6cc8df7 | ||
|
|
9db2180045 | ||
|
|
a17a37c24b | ||
|
|
66e4893a56 | ||
|
|
679da25f12 | ||
|
|
0df014ea95 | ||
|
|
e35ea69962 | ||
|
|
bce21a1224 | ||
|
|
bda26cb90d | ||
|
|
354dc83f64 | ||
|
|
bdc14ffa4b | ||
|
|
20de5dac54 | ||
|
|
0c64e5e40f | ||
|
|
698a37e06e | ||
|
|
7f73a3e5fd | ||
|
|
a395e4329e | ||
|
|
651f05be2f | ||
|
|
8ae22d43df | ||
|
|
b66ad34bcb | ||
|
|
9baa029532 | ||
|
|
2e40c84a8b | ||
|
|
36ae8d5d61 | ||
|
|
d379622bc5 | ||
|
|
12d9ab672d | ||
|
|
fc4d34bd68 | ||
|
|
7cb19ae77a | ||
|
|
421c0e33a8 | ||
|
|
c873a3c6f0 | ||
|
|
1203d6f053 | ||
|
|
c15af6caf1 | ||
|
|
a35ef2ca15 | ||
|
|
170411a589 | ||
|
|
0fcdc5bcf6 | ||
|
|
0151002bc9 | ||
|
|
12059e7558 | ||
|
|
07a6fa2f6e | ||
|
|
2cd82e4375 | ||
|
|
d926b3905e | ||
|
|
fb06b4f506 | ||
|
|
b6c8d8f044 | ||
|
|
18148212cf | ||
|
|
c8afda612c | ||
|
|
5d70fec7db | ||
|
|
10b5462513 | ||
|
|
7392830214 | ||
|
|
2dd355a43c | ||
|
|
d5808f4652 | ||
|
|
86c149fbf5 | ||
|
|
1668a14130 | ||
|
|
b98566f313 | ||
|
|
c645d7dc60 | ||
|
|
30a1a397fd | ||
|
|
1b0362b1ad | ||
|
|
f8aee7678b | ||
|
|
bb3f7e1a09 | ||
|
|
5ee8fac866 | ||
|
|
d074d48433 | ||
|
|
e28438c91f | ||
|
|
47cf5f467a | ||
|
|
77ed259abc | ||
|
|
bbe8924acf | ||
|
|
d9b46ad04e | ||
|
|
c74eb648a2 | ||
|
|
2fb68fd749 | ||
|
|
971580f98f | ||
|
|
b6f5d7d1fa | ||
|
|
3ff7522828 | ||
|
|
6893651046 | ||
|
|
84e51af6ae | ||
|
|
3585614f4f | ||
|
|
2d974ed849 | ||
|
|
04830fc9df | ||
|
|
dea77fea57 | ||
|
|
40af19fb98 | ||
|
|
e4e15c699d | ||
|
|
ffd5ac1dd7 | ||
|
|
e821caf2e8 | ||
|
|
0dc0e0d00c | ||
|
|
13ba1212a9 | ||
|
|
6271b26685 | ||
|
|
9006fc5afd | ||
|
|
20866784f9 | ||
|
|
386e515556 | ||
|
|
3949b26c4e | ||
|
|
a095f9a36c | ||
|
|
f30e7c6bd6 | ||
|
|
8dbbd09113 | ||
|
|
acd7207384 | ||
|
|
025ece9e0a | ||
|
|
c3281fede1 | ||
|
|
4efbaf7ae9 | ||
|
|
2df8ab8ce2 | ||
|
|
64ca6dfd87 | ||
|
|
8673092bec | ||
|
|
49ab324be3 | ||
|
|
f0d0b6222f | ||
|
|
d9d9df3787 | ||
|
|
59d252f67c | ||
|
|
c8f0e98f81 | ||
|
|
213e5a56e5 | ||
|
|
61d4510210 | ||
|
|
7bc90ac4e3 | ||
|
|
fd7e3d4ede | ||
|
|
1642d2b5ab | ||
|
|
986a24a60a | ||
|
|
f5b5a9c52d | ||
|
|
a05d1b1a22 | ||
|
|
d85004dba3 | ||
|
|
6f5807b5b8 | ||
|
|
efa2a62f33 | ||
|
|
9df127c1ae | ||
|
|
001ce48f04 | ||
|
|
53c099b07f | ||
|
|
ed0b699815 | ||
|
|
b4c833c646 | ||
|
|
b26419182d | ||
|
|
58ca5c7256 | ||
|
|
0ab9722c57 | ||
|
|
60d494002a | ||
|
|
b128514b19 | ||
|
|
ac21bc2558 | ||
|
|
11f2e4d405 | ||
|
|
a4e911ca91 | ||
|
|
2dc2ed4dce | ||
|
|
ad0beb148e | ||
|
|
f9c6e1ab9f | ||
|
|
295b395c49 | ||
|
|
6b679ccfac | ||
|
|
3f8eeffc94 | ||
|
|
0c82ea1128 | ||
|
|
6e625bfb62 | ||
|
|
1900aed78f | ||
|
|
539e52a43c | ||
|
|
8b91f61e60 | ||
|
|
d4675396dc | ||
|
|
af8f195d0c | ||
|
|
b6ecc5e7ed | ||
|
|
7ce653991a | ||
|
|
9717a96257 | ||
|
|
f487c56ea0 | ||
|
|
f9671f9838 | ||
|
|
599a487861 | ||
|
|
24aabc4129 | ||
|
|
7449a9c32a | ||
|
|
7be0589334 | ||
|
|
b21faf409e | ||
|
|
dbf05f9d59 | ||
|
|
102dabce4e | ||
|
|
2f9c277029 | ||
|
|
840b2c4015 | ||
|
|
2454f1ba88 | ||
|
|
2dead87988 | ||
|
|
aa9afdaf53 | ||
|
|
2c29f8606c | ||
|
|
c2b71be82a | ||
|
|
2582cd3656 | ||
|
|
0d02b9da48 | ||
|
|
a7f5388434 | ||
|
|
34895f7a0c | ||
|
|
28fcd3adcf | ||
|
|
73425ab502 | ||
|
|
780f88dfae | ||
|
|
1c0695c81c | ||
|
|
4a8e49b079 | ||
|
|
e952d4ef3d | ||
|
|
b486d6bb96 | ||
|
|
9b13bb473a | ||
|
|
4330db95f5 | ||
|
|
f82397698a | ||
|
|
857d05a0dc | ||
|
|
47044143e6 | ||
|
|
8d45b0b07d | ||
|
|
128e87deb9 | ||
|
|
64854cbfb9 | ||
|
|
0d1d3afcfa | ||
|
|
62f53a1934 | ||
|
|
a0433001c2 | ||
|
|
7b6a8055d3 | ||
|
|
93ffd025d2 | ||
|
|
74ce0c0144 | ||
|
|
bd2cd16d3a | ||
|
|
c6c13d0143 | ||
|
|
49c12479b3 | ||
|
|
67c2f99ad9 | ||
|
|
fb843ffc14 | ||
|
|
27354de779 | ||
|
|
1cc203a4f0 | ||
|
|
5f7bc3c2cc | ||
|
|
3fd4fbcc09 | ||
|
|
c03f335abe | ||
|
|
89ff391c98 | ||
|
|
6354806281 | ||
|
|
13a4e1a1d2 | ||
|
|
74a822a082 | ||
|
|
f9f7e0f00e | ||
|
|
75ecb6dda7 | ||
|
|
01a0c27f85 | ||
|
|
6310d2a792 | ||
|
|
3d85fcb2bf | ||
|
|
3ae2b4af17 | ||
|
|
5820f741bf | ||
|
|
aac0f5a33c | ||
|
|
5bd400d810 | ||
|
|
3c1f7a7e53 | ||
|
|
383ac117f7 | ||
|
|
15e9b9ca2d | ||
|
|
65155dfe70 | ||
|
|
b0e2870213 | ||
|
|
9e37a52fca | ||
|
|
fbeeab3682 | ||
|
|
518a4aee48 | ||
|
|
26fdbf5fda | ||
|
|
41d72bb297 | ||
|
|
8770314c59 | ||
|
|
eeb913bcd1 | ||
|
|
8cc7fde304 | ||
|
|
7cffb9250d | ||
|
|
8b46e6504b | ||
|
|
04614f4793 | ||
|
|
226de0064e | ||
|
|
f97839e7ea | ||
|
|
40c5263cb0 | ||
|
|
ed2f2b6073 | ||
|
|
df29bb7ef9 | ||
|
|
92a2b17dc9 | ||
|
|
aa5e187982 | ||
|
|
1b53fb8f37 | ||
|
|
4bfe4b039d | ||
|
|
26d15eb902 | ||
|
|
8d791840d8 | ||
|
|
0728e41832 | ||
|
|
a2f34be961 | ||
|
|
59ce55cb4b | ||
|
|
5c12506aee | ||
|
|
d32a64f624 | ||
|
|
6b353ff397 | ||
|
|
93777b04ed | ||
|
|
12a4673602 | ||
|
|
c609d6012b | ||
|
|
6b480a2d54 | ||
|
|
9bbf80c4f8 | ||
|
|
56baef3270 | ||
|
|
98a591bf73 | ||
|
|
459499b412 | ||
|
|
74193d33d9 | ||
|
|
d70e5ffb25 | ||
|
|
1aea7ef973 | ||
|
|
96149fa381 | ||
|
|
045ee97675 | ||
|
|
7562cfe6c7 | ||
|
|
22cb1d7e6c | ||
|
|
43485186ef | ||
|
|
9be58e1baa | ||
|
|
3ed93ceae6 | ||
|
|
2c9180fca6 | ||
|
|
16e02c4500 | ||
|
|
14db43712f | ||
|
|
612d7d0f65 | ||
|
|
4cb393af54 | ||
|
|
e2c2494fdb | ||
|
|
64952dcbdd | ||
|
|
f5259b9ad4 | ||
|
|
b9080739af | ||
|
|
5ed8d87ead | ||
|
|
350eb22690 | ||
|
|
ea0e80d217 | ||
|
|
257f47e93b | ||
|
|
5e567caa5b | ||
|
|
13531c176c | ||
|
|
d66166ad71 | ||
|
|
600e86a3a0 | ||
|
|
2b507555a7 | ||
|
|
237a03fe37 | ||
|
|
0f56b4bad2 | ||
|
|
586158e958 | ||
|
|
b8a1170794 | ||
|
|
8d3f9b0896 | ||
|
|
213db2e12f | ||
|
|
4822aa5c43 | ||
|
|
4b083a1e24 | ||
|
|
a8d8f37a70 | ||
|
|
04b122d663 | ||
|
|
4ef47816da | ||
|
|
1e498b4825 | ||
|
|
f53047d27b | ||
|
|
d56ffc585f | ||
|
|
0bbb9d9e22 | ||
|
|
f610eec7d2 | ||
|
|
fdd26628f3 | ||
|
|
734d863f48 | ||
|
|
1c519d8b67 | ||
|
|
b156f42202 | ||
|
|
81fa2f4c6d | ||
|
|
f4cdb5b8db | ||
|
|
22a87c1a13 | ||
|
|
f0a6b7b3dc | ||
|
|
600ee4fdf8 | ||
|
|
a760ec2737 | ||
|
|
ebfe60d90c | ||
|
|
4d2ad86c46 | ||
|
|
bda1bba537 | ||
|
|
9cb5a00e45 | ||
|
|
4ebd57a360 | ||
|
|
068da34e7b | ||
|
|
2067c79834 | ||
|
|
03606ea373 | ||
|
|
7366e9fca8 | ||
|
|
0aa9c26c8e | ||
|
|
4ecb70b4ec | ||
|
|
6fcb88715c | ||
|
|
8f3a1af8cb | ||
|
|
f52c7978a0 | ||
|
|
48a9ce65dd | ||
|
|
5914188672 | ||
|
|
d13fe67e21 | ||
|
|
0b86028671 | ||
|
|
50a51e18a7 | ||
|
|
7f1a095be1 | ||
|
|
381db3910d | ||
|
|
a499d65850 | ||
|
|
751a64061f | ||
|
|
a2d9b750cf | ||
|
|
5675e65d88 | ||
|
|
d9f27f76e1 | ||
|
|
9517768767 | ||
|
|
d2de58e58e | ||
|
|
92575c104f | ||
|
|
6e33611968 | ||
|
|
5bbec015e6 | ||
|
|
46c520b4b9 | ||
|
|
857b62f8f8 | ||
|
|
8dc5968d20 | ||
|
|
7633199edd | ||
|
|
900351141c | ||
|
|
209030f047 | ||
|
|
4c68a0f1da | ||
|
|
2967124039 | ||
|
|
de9b29d875 | ||
|
|
9b708037e8 | ||
|
|
e16e7f7a18 | ||
|
|
7cdb4bb29e | ||
|
|
3f0905d7aa | ||
|
|
e07846b59c | ||
|
|
f490ad8e59 | ||
|
|
7b7383256f | ||
|
|
568bc6935e | ||
|
|
de2ef3bd6b | ||
|
|
0f4b4a7b96 | ||
|
|
dcf157e8e2 | ||
|
|
7cf651a80c | ||
|
|
64a970b219 | ||
|
|
9f34da8182 | ||
|
|
204d4f7fc1 | ||
|
|
23ca979ef9 | ||
|
|
60bef242a7 | ||
|
|
deda7cb02d | ||
|
|
0a0b992d7a | ||
|
|
f6039ae82e | ||
|
|
6b46485708 | ||
|
|
97bccd96d8 | ||
|
|
e22979dd40 | ||
|
|
57bf3e1600 | ||
|
|
7d67deaf6c | ||
|
|
56b227f763 | ||
|
|
4ab2f79367 | ||
|
|
57d7fb8804 | ||
|
|
220da29095 | ||
|
|
6c5dd2d030 | ||
|
|
6f36d8d8c6 | ||
|
|
634c1eead6 | ||
|
|
057487d857 | ||
|
|
4cd3a35637 | ||
|
|
6c7ddd410a | ||
|
|
be6fb995fc | ||
|
|
c3e7a83776 | ||
|
|
a4525572c2 | ||
|
|
0756db2c97 | ||
|
|
d98b005a0f | ||
|
|
c001ffd053 | ||
|
|
d06274860e | ||
|
|
4f4f0d146d | ||
|
|
953bb35511 | ||
|
|
a5e4f67382 | ||
|
|
227d2e6898 | ||
|
|
6b19f7dbb7 | ||
|
|
b9429b0532 | ||
|
|
540605674f | ||
|
|
403aae9523 | ||
|
|
e93d550043 | ||
|
|
128b9880fa | ||
|
|
b66c18334e | ||
|
|
c848afabad | ||
|
|
db4849f389 | ||
|
|
9eae69b444 | ||
|
|
6b0db5a59e | ||
|
|
8657fbe293 | ||
|
|
26d8145f80 | ||
|
|
2d22695080 | ||
|
|
14eb08bf39 | ||
|
|
19984f5362 | ||
|
|
cb2552016b | ||
|
|
c4ef74c13a | ||
|
|
91672cb0e4 | ||
|
|
1ef04a92bc | ||
|
|
487f4888e8 | ||
|
|
72d974752b | ||
|
|
5249702e40 | ||
|
|
c2bf3d3838 | ||
|
|
817f238ef9 | ||
|
|
eab6ad40b6 | ||
|
|
7519b60f5d | ||
|
|
d7aa062dbf | ||
|
|
f0de59011b | ||
|
|
e1e3c6e798 | ||
|
|
a09a39c5de | ||
|
|
e46da016f4 | ||
|
|
941a1f5c08 | ||
|
|
9caedd310f | ||
|
|
aaef628794 | ||
|
|
a5adb3f3da | ||
|
|
37f4166bf5 | ||
|
|
217e645374 | ||
|
|
e146034f31 | ||
|
|
54a905ff95 | ||
|
|
215a7909f8 | ||
|
|
11e3ba0413 | ||
|
|
089ad64ef9 | ||
|
|
fa2ba17ac1 | ||
|
|
51c5ee5d3d | ||
|
|
ba28400941 | ||
|
|
a7c5f03910 | ||
|
|
a34da78712 | ||
|
|
20c82f7a0d | ||
|
|
7e9d03b309 | ||
|
|
dca8fe0f94 | ||
|
|
364d8113ec | ||
|
|
4a10a0328a | ||
|
|
579893609f | ||
|
|
53fc4bde71 | ||
|
|
89a2185d1c | ||
|
|
09cfff5286 | ||
|
|
68e65a7f39 | ||
|
|
e9a636a6dd | ||
|
|
0ea4edc4f2 | ||
|
|
548a9c4cef | ||
|
|
99757f03b7 | ||
|
|
b8f464559e | ||
|
|
29115db6d1 | ||
|
|
20511d8800 | ||
|
|
7cf1a17674 | ||
|
|
a490a9e0b3 | ||
|
|
907cf71b81 | ||
|
|
96380d3c7b | ||
|
|
1dd16208e9 | ||
|
|
5dce71baee | ||
|
|
d7c31e383d | ||
|
|
3ba6cf8d24 | ||
|
|
62e21053f6 | ||
|
|
a32e24433f | ||
|
|
d12733d3cc | ||
|
|
c6008f4ffd | ||
|
|
f9fdf47725 | ||
|
|
2539a711da | ||
|
|
e7206a3625 | ||
|
|
85065c3be0 | ||
|
|
1e769799b8 | ||
|
|
d6cb7f87d5 | ||
|
|
fd544b5572 | ||
|
|
ed3ad7c41b | ||
|
|
eeecd0fd84 | ||
|
|
1741726bad | ||
|
|
3c1f41da48 | ||
|
|
0ac75f4c3e | ||
|
|
c08b556c3e | ||
|
|
f4708d3dbd | ||
|
|
08eebfb5a6 | ||
|
|
92d787bebb | ||
|
|
ca6727557c | ||
|
|
af91e923b1 | ||
|
|
7091112b0f | ||
|
|
7f4e42a5c5 | ||
|
|
80d5290687 | ||
|
|
44ea56a675 | ||
|
|
5949918204 | ||
|
|
034606a4a3 | ||
|
|
2ac1f5d075 | ||
|
|
2d6bab13ac | ||
|
|
60266aa3ee | ||
|
|
04397e89fc | ||
|
|
5d8e6e82e1 | ||
|
|
3eedbd187a | ||
|
|
b72c2fd63e | ||
|
|
e77e2da328 | ||
|
|
17a5040a0e | ||
|
|
90366b6d0c | ||
|
|
25725fadfa | ||
|
|
1840842d82 | ||
|
|
253a9a9977 | ||
|
|
dd6231b313 | ||
|
|
4a98f155f0 | ||
|
|
d337ab5b8e | ||
|
|
501c7ba221 | ||
|
|
cd36709676 | ||
|
|
6e6d2887cf | ||
|
|
0bf5f14600 | ||
|
|
9b19f49ca1 | ||
|
|
a12543015f | ||
|
|
a9fea3143b | ||
|
|
09d0760261 | ||
|
|
1eaa32799c | ||
|
|
fa57f53d92 | ||
|
|
a7593ea61f | ||
|
|
cc95a8ec9c | ||
|
|
d48183e2b5 | ||
|
|
c5e4b3bb1e | ||
|
|
37cf4beb4e | ||
|
|
ddefa45c40 | ||
|
|
6596fe8e04 | ||
|
|
f245ed7dc2 | ||
|
|
3fba402991 | ||
|
|
840965fae1 | ||
|
|
24246cee6a | ||
|
|
f6bd7280d7 | ||
|
|
f5fd42e833 | ||
|
|
1d2594d5af | ||
|
|
aaa07e81d2 | ||
|
|
2161d436c3 | ||
|
|
fdc65cf6f2 | ||
|
|
85c0d08f37 | ||
|
|
8cdf079fe3 | ||
|
|
4f13c3c13c | ||
|
|
14d94f325a | ||
|
|
bed663c6f3 | ||
|
|
eef615222f | ||
|
|
df718b97d8 | ||
|
|
c9ac1ab0ad | ||
|
|
79933b2795 | ||
|
|
5f5bc741f2 | ||
|
|
9c582fb837 | ||
|
|
90816c119c | ||
|
|
0306a85475 | ||
|
|
99ff83d990 | ||
|
|
1f282e341d | ||
|
|
1e78723983 | ||
|
|
36884f5b85 | ||
|
|
a595f8c745 | ||
|
|
f06c30de76 | ||
|
|
db8a22558e | ||
|
|
fa65e8cdd7 | ||
|
|
7e8db7a15d | ||
|
|
3af67b8cc6 | ||
|
|
42700c4be7 | ||
|
|
236c7fff21 | ||
|
|
bde1f969b4 | ||
|
|
ce5c32e821 | ||
|
|
e09ae460f1 | ||
|
|
58eefecf0f | ||
|
|
ae01648d89 | ||
|
|
f9694988d3 | ||
|
|
2dfea0caaa | ||
|
|
abefbf21b5 | ||
|
|
4ab5b80b69 | ||
|
|
fb1dfcb56b | ||
|
|
13d669e828 | ||
|
|
2ff2673fdf | ||
|
|
aa0d685c76 | ||
|
|
984fc9ceec | ||
|
|
8ad87c215c | ||
|
|
06e9720f3e | ||
|
|
1afcbadaef | ||
|
|
9173556aa0 | ||
|
|
650d4261ef | ||
|
|
1504fe8e06 | ||
|
|
4475dc6899 | ||
|
|
55b3e71c91 | ||
|
|
dede384dca | ||
|
|
b415b57de9 | ||
|
|
977b75b3b4 | ||
|
|
ffbdb58649 | ||
|
|
2b7d2e903e | ||
|
|
846bd02075 | ||
|
|
63e34789bd | ||
|
|
536e620525 | ||
|
|
e04a8eedbd | ||
|
|
25fda9d2de | ||
|
|
512203b070 | ||
|
|
135d7acc15 | ||
|
|
483b5cf599 | ||
|
|
650c83c169 | ||
|
|
d0e75f89ec | ||
|
|
a1db399cf4 | ||
|
|
5a06e02045 | ||
|
|
93e6661295 | ||
|
|
f7ec4ba125 | ||
|
|
ddcc68631f | ||
|
|
d20436e457 | ||
|
|
22d1a6d124 | ||
|
|
de80c3b776 | ||
|
|
a7828c4e1e | ||
|
|
677e4e9bc4 | ||
|
|
dc7f7e55d4 | ||
|
|
346a7cf83c | ||
|
|
5e52835073 | ||
|
|
54926c9651 | ||
|
|
2a162ac2cd | ||
|
|
8985213a4b | ||
|
|
cb071f2399 | ||
|
|
b2561524ab | ||
|
|
ff5068ff82 | ||
|
|
7a45b65be1 | ||
|
|
496bf672bb | ||
|
|
8cd10997cd | ||
|
|
9c6d29f5a2 | ||
|
|
85321cc90a | ||
|
|
f190054f6d | ||
|
|
de8358ae4f | ||
|
|
20ec357fca | ||
|
|
4010074115 | ||
|
|
a467836539 | ||
|
|
5e5e585d8e | ||
|
|
ae7cef2d69 | ||
|
|
d0278c9afb | ||
|
|
92d73ce4c8 | ||
|
|
7db03e4f6b | ||
|
|
aa6efddd28 | ||
|
|
f129e151d4 | ||
|
|
e0bdae7417 | ||
|
|
b42b8ca2ec | ||
|
|
4ccdeffb27 | ||
|
|
7617e5c5be | ||
|
|
b2942da75a | ||
|
|
2946752b8e | ||
|
|
e9e1a089a0 | ||
|
|
b4bae03465 | ||
|
|
3958ddc916 | ||
|
|
4ea4f6b189 | ||
|
|
506a795783 | ||
|
|
bd841b29d6 | ||
|
|
3343472224 | ||
|
|
93d1b9316c | ||
|
|
0ee08b8845 | ||
|
|
42ebc54afb | ||
|
|
cf379bdf27 | ||
|
|
2c4267635f | ||
|
|
9110c8d4b9 | ||
|
|
622323dbee | ||
|
|
787ce63747 | ||
|
|
88e2e1e5e2 | ||
|
|
ac066bca7b | ||
|
|
fb2dea6002 | ||
|
|
bf6f250d15 | ||
|
|
a00cff1205 | ||
|
|
013047cf79 | ||
|
|
0a1718c76d | ||
|
|
4504989f7f | ||
|
|
c1a05211ce | ||
|
|
a0ce8763dd | ||
|
|
279e98e5d3 | ||
|
|
a2270f3af5 | ||
|
|
3eb33cf7c2 | ||
|
|
92e61c8d61 | ||
|
|
ab486a32d1 | ||
|
|
a859391875 | ||
|
|
cb5c5addf9 | ||
|
|
7640560c97 | ||
|
|
e270457b0e | ||
|
|
cad7fdf30d | ||
|
|
d4c512cd58 | ||
|
|
6f5be3d279 | ||
|
|
22aff32b6b | ||
|
|
7cfb7f590d | ||
|
|
2f1d932061 | ||
|
|
ba9e9c3a4c | ||
|
|
9143b76773 | ||
|
|
f056dbc88e | ||
|
|
2c89d169a1 | ||
|
|
b7c2a2ac6f | ||
|
|
ae19100f53 | ||
|
|
5ea7b347ad | ||
|
|
48b1ad8add | ||
|
|
334851c310 | ||
|
|
adeb6d7616 | ||
|
|
43e5dbf021 | ||
|
|
62c155b8db | ||
|
|
ec90a52305 | ||
|
|
659a445be4 | ||
|
|
f7b54a4945 |
No files matched your search
@@ -0,0 +1,60 @@
|
||||
name: Bug report
|
||||
description: Something OpenRig does is wrong, or a command fails
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks. Include a small reproduction and the relevant versions.
|
||||
This report will be public. Remove API keys, tokens, private prompts, personal details and private paths from commands, logs and configuration.
|
||||
For a suspected vulnerability, follow our [security policy](https://github.com/mvschwarz/openrig/security/policy) instead of posting details here.
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: OpenRig version
|
||||
description: Output of `rig --version`
|
||||
placeholder: 0.5.14 (2edc1b7a)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: platform
|
||||
attributes:
|
||||
label: OS, Node and tmux versions
|
||||
placeholder: macOS 15.6, Node 22.22.1, tmux 3.5a
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: harness
|
||||
attributes:
|
||||
label: Harnesses involved
|
||||
multiple: true
|
||||
options:
|
||||
- None / not sure
|
||||
- Claude Code
|
||||
- Codex
|
||||
- Pi / Oh My Pi
|
||||
- Other (say which below)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: What you ran and what happened
|
||||
description: The command(s), relevant redacted output, and what you expected instead. Paste text where you can; screenshots are useful for visual issues.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: state
|
||||
attributes:
|
||||
label: Instance state that might matter
|
||||
description: Fresh install or upgrade? Include only relevant redacted lines from status output or your rig spec; do not upload full transcripts, credentials or your instance directory.
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Checks
|
||||
options:
|
||||
- label: I searched existing issues and Discussions for this.
|
||||
required: true
|
||||
- label: I am on the latest published version, or I said which version above.
|
||||
required: true
|
||||
@@ -0,0 +1,14 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Ask a question
|
||||
url: https://github.com/mvschwarz/openrig/discussions/categories/q-a
|
||||
about: Setup, usage and "is this expected" questions go to Discussions Q&A.
|
||||
- name: Share an idea or a setup
|
||||
url: https://github.com/mvschwarz/openrig/discussions
|
||||
about: Ideas and Show and tell live in Discussions.
|
||||
- name: Documentation
|
||||
url: https://openrig.dev/docs
|
||||
about: Getting started, CLI reference, configuration and troubleshooting.
|
||||
- name: Security report
|
||||
url: https://github.com/mvschwarz/openrig/security/policy
|
||||
about: Read how to arrange a private report. Do not include vulnerability details in public issues.
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Feature request
|
||||
description: Something you want OpenRig to do that it does not
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
For open-ended ideas or "how should this work" questions, [Discussions › Ideas](https://github.com/mvschwarz/openrig/discussions/categories/ideas) is the better place. Use this form when you can name the outcome you want.
|
||||
This request will be public; use examples without credentials, private prompts or personal details.
|
||||
- type: textarea
|
||||
id: outcome
|
||||
attributes:
|
||||
label: What are you trying to do, and what stops you today?
|
||||
description: Describe the situation, not the solution. One real example beats a general case.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: What would you like OpenRig to do?
|
||||
description: A command, a setting, a behaviour. Rough is fine.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: workaround
|
||||
attributes:
|
||||
label: How do you handle it now?
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: OpenRig version
|
||||
placeholder: 0.5.14
|
||||
@@ -0,0 +1,21 @@
|
||||
<!-- Thanks for the PR. Three short answers are all a reviewer needs. -->
|
||||
|
||||
## What a user gets
|
||||
|
||||
<!-- One or two sentences: the behaviour before, the behaviour after. Link the issue if there is one. -->
|
||||
|
||||
## How you verified it
|
||||
|
||||
<!-- What you actually ran and saw, and which revision/local changes you tested. The documented checks are `npm run build`, `npm test`, and `npm run lint`; say what you could not run. Redact credentials and private information from logs or screenshots. -->
|
||||
|
||||
## Anything you were unsure about
|
||||
|
||||
<!-- Design choices, edge cases you did not cover, places a reviewer should look hardest. Empty is a fine answer. -->
|
||||
|
||||
## If this is security-related
|
||||
|
||||
<!-- Exploitable? Report it privately first (SECURITY.md). Otherwise: how does this go wrong for someone using OpenRig as designed (your own machine or a trusted private network), with evidence? Who causes it, and how do they reach the install? What does the change cost everyone else? See CONTRIBUTING.md. -->
|
||||
|
||||
- [ ] One concern per PR; no version bump; no `CHANGELOG.md` edit
|
||||
- [ ] Tests added or updated where the change is testable
|
||||
- [ ] I listed the checks I ran, their results, and any checks I could not run
|
||||
@@ -0,0 +1,17 @@
|
||||
# Getting help with OpenRig
|
||||
|
||||
- **Docs:** https://openrig.dev/docs — getting started, CLI reference, configuration,
|
||||
troubleshooting. An index linking to agent-friendly documentation is at
|
||||
https://openrig.dev/llms.txt.
|
||||
- **Agents helping a user:** read [docs/reference/help.md](../docs/reference/help.md), or run `rig context get help`.
|
||||
- **Questions:** [Discussions › Q&A](https://github.com/mvschwarz/openrig/discussions/categories/q-a).
|
||||
- **Bugs:** [open an issue](https://github.com/mvschwarz/openrig/issues/new/choose) with the bug
|
||||
template. Include `rig --version`, your OS and Node versions, and relevant redacted command output.
|
||||
Reports are public: remove credentials, private prompts and personal information.
|
||||
- **Ideas and setups:** [Ideas](https://github.com/mvschwarz/openrig/discussions/categories/ideas)
|
||||
and [Show and tell](https://github.com/mvschwarz/openrig/discussions/categories/show-and-tell).
|
||||
- **Videos:** https://www.youtube.com/@openrig
|
||||
- **Security:** see [SECURITY.md](../SECURITY.md); please report privately.
|
||||
|
||||
We aim to acknowledge issues and pull requests within one day. A substantive PR review decision
|
||||
within seven days is a target, not a guaranteed deadline. Details in [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Portability report
|
||||
|
||||
# Lists lines a pull request adds that contain values tied to one machine, network or account
|
||||
# (credentials, home paths, temporary paths, network addresses, email addresses). Findings never
|
||||
# fail the job; the author reads the report and decides what to change. An operational error (for
|
||||
# example a git failure) does fail it. The repository is public, so this summary lists file and
|
||||
# line only; authors see the matched text when they run the report locally or on push.
|
||||
# See scripts/portability-report.mjs.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
portability-report:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Report added lines with machine-specific values
|
||||
run: >
|
||||
node scripts/portability-report.mjs
|
||||
--from ${{ github.event.pull_request.base.sha }}
|
||||
--to ${{ github.event.pull_request.head.sha }}
|
||||
--locations-only
|
||||
--out "$GITHUB_STEP_SUMMARY"
|
||||
@@ -0,0 +1,139 @@
|
||||
name: Tests
|
||||
|
||||
# Builds, type-checks and runs every test suite on a clean GitHub runner, and records the file
|
||||
# list each published package would contain. Each suite is its own job so one failure does not
|
||||
# hide the others. No provider credentials or operator configuration are supplied.
|
||||
# Package suites deny external networking; scenario runtime has no external route.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: tests-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-package:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- name: Record the published file list of each package
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p pack-lists
|
||||
for p in cli daemon tui; do
|
||||
(cd packages/$p && npm pack --dry-run --json --ignore-scripts) \
|
||||
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{for(const f of JSON.parse(s)[0].files)console.log(f.path)})' \
|
||||
| sort > pack-lists/$p.txt
|
||||
echo "$p: $(wc -l < pack-lists/$p.txt) files"
|
||||
done
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pack-lists
|
||||
path: pack-lists/
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
|
||||
repo-checks:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run test:repo
|
||||
|
||||
package-tests:
|
||||
# macOS, the platform OpenRig runs on: several daemon and cli tests read macOS ps columns,
|
||||
# file-inode behaviour and release tags.
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package: [daemon, cli, tui, ui]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
- run: brew install tmux
|
||||
- run: npm ci
|
||||
- name: Build the packages the tests import
|
||||
run: npm run build
|
||||
- name: Run the suite without ambient credentials or external network
|
||||
run: |
|
||||
suite_root=$(mktemp -d "$RUNNER_TEMP/opr-test.XXXXXX")
|
||||
suite_root=$(cd "$suite_root" && pwd -P)
|
||||
mkdir -p "$suite_root/home" "$suite_root/bin"
|
||||
# Same system bytes, nonprivileged mode; no system file is changed.
|
||||
/usr/bin/install -m 0755 /bin/ps "$suite_root/bin/ps"
|
||||
# Only this job's temporary sockets (including tmux) are reachable.
|
||||
env -i HOME="$suite_root/home" PATH="$PATH" TMPDIR="$suite_root" TMUX_TMPDIR="$suite_root" \
|
||||
CI=true TERM=xterm-256color \
|
||||
sandbox-exec -D IPC_ROOT="$suite_root" -p '(version 1)(allow default)(deny network-outbound)(allow network-outbound (remote ip "localhost:*"))(allow network-outbound (subpath (param "IPC_ROOT")))' \
|
||||
/bin/bash -s "${{ matrix.package }}" <<'SUITE'
|
||||
set -euo pipefail
|
||||
node scripts/ci-macos-process-probe.mjs
|
||||
# The production readers use bare ps. Select real system bytes only after
|
||||
# self/owned-child observation and network/IPC confinement are proved.
|
||||
export PATH="$TMPDIR/bin:$PATH"
|
||||
test "$(command -v ps)" = "$TMPDIR/bin/ps"
|
||||
exec npm run test -w "packages/$1"
|
||||
SUITE
|
||||
|
||||
installed-scenario:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 35
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Installed package, daemon restart, and seeded durability failure
|
||||
run: bash scripts/run-pr-scenarios.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: installed-scenario-evidence
|
||||
path: dist/pr-scenarios/
|
||||
if-no-files-found: error
|
||||
@@ -36,3 +36,6 @@ demo/proof/
|
||||
# OPR.0.5.3.7 R2 — the shipped context-pack projection is DERIVED AT PACKAGE TIME
|
||||
# (scripts/generate-context-packs.mjs), never committed or edited in place.
|
||||
packages/daemon/context-packs/
|
||||
|
||||
# Internal build receipts; never commit
|
||||
/.evidence/
|
||||
+605
-7
@@ -8,6 +8,604 @@ deprecations, and behavioral changes. Breaking changes are called out explicitly
|
||||
|
||||
---
|
||||
|
||||
## [0.6.3]
|
||||
|
||||
- Recognize running managed Claude Code seats behind OpenRig's shell wrappers
|
||||
using pane lineage, foreground process and native session identity checks.
|
||||
This repairs the 0.6.2 messaging refusal reported by
|
||||
[@dmelo](https://github.com/dmelo) in
|
||||
[#197](https://github.com/mvschwarz/openrig/issues/197)
|
||||
([#220](https://github.com/mvschwarz/openrig/pull/220)).
|
||||
- Clean up unusable snapshot helpers when their ownership is proven, instead
|
||||
of accumulating them on repeated attempts. Thanks to
|
||||
[@z4cc](https://github.com/z4cc) for
|
||||
[#188](https://github.com/mvschwarz/openrig/issues/188)
|
||||
([#189](https://github.com/mvschwarz/openrig/pull/189)).
|
||||
- Keep archived duplicate rigs out of seat-reference resolution and preserve
|
||||
another live seat's session and queue work when removing a stale node.
|
||||
Thanks to [@Farkinell](https://github.com/Farkinell) for
|
||||
[#174](https://github.com/mvschwarz/openrig/issues/174)
|
||||
([#181](https://github.com/mvschwarz/openrig/pull/181)).
|
||||
|
||||
- Protect proof media from artifact writes: reject binary artifact bodies and
|
||||
require an explicit `--replace` to overwrite an existing Markdown artifact
|
||||
([#177](https://github.com/mvschwarz/openrig/pull/177)). Repository script tests
|
||||
also run serially to prevent interference from shared build outputs
|
||||
([#221](https://github.com/mvschwarz/openrig/pull/221)).
|
||||
|
||||
See [0.6.3 release notes](docs/releases/v0.6.3.md) for the verification limits.
|
||||
Source regression coverage does not establish installed Claude/Fedora delivery.
|
||||
|
||||
## [0.6.2]
|
||||
|
||||
- Start with two Claude Code agents, two Codex agents, or a Claude owner and
|
||||
Codex checker, using the same first-project task and review path. Agent-guided
|
||||
setup recommends a scoped `rig` command allowance after your explicit choice;
|
||||
broader permissions remain separate
|
||||
([#147](https://github.com/mvschwarz/openrig/pull/147)).
|
||||
- Install schema-version-2 bundles into `--target` and launch from those retained
|
||||
files. Local CLI bundle paths resolve from your working directory; conflicting
|
||||
target files are preserved ([#146](https://github.com/mvschwarz/openrig/pull/146)).
|
||||
Without `--target`, `rig up <file>.rigbundle` installs into your current directory, so run it from the project folder you want.
|
||||
- Refuse message delivery into a bare shell where an agent runtime should be
|
||||
running ([#150](https://github.com/mvschwarz/openrig/pull/150), fixes
|
||||
[#142](https://github.com/mvschwarz/openrig/issues/142)).
|
||||
- Preserve the named target during managed launches after a tmux/host restart,
|
||||
instead of confusing it with an old bare pane binding; existing identity checks
|
||||
remain. Thanks to [@diaztunjano](https://github.com/diaztunjano) for reporting
|
||||
[#141](https://github.com/mvschwarz/openrig/issues/141), addressed by
|
||||
[#151](https://github.com/mvschwarz/openrig/pull/151).
|
||||
- Repair self-host sender identity, tmux window parsing, Codex hook-path
|
||||
canonicalization, Pi input editing, TUI health display and restore diagnostics;
|
||||
carry runtime hints to Herdr and allow configured Anthropic endpoint forwarding.
|
||||
See the [community fixes and credits](docs/releases/v0.6.2.md#community-fixes).
|
||||
|
||||
See [0.6.2 release notes](docs/releases/v0.6.2.md) for usage and compatibility.
|
||||
Installed first-use and reboot/power-loss verification remain pending; these
|
||||
source changes do not establish those outcomes.
|
||||
|
||||
## [0.6.1]
|
||||
|
||||
- One version-matched agent help guide: `rig context get help`, also available
|
||||
inside the installed package when the CLI cannot run. It links setup, restart,
|
||||
permissions and instance guidance, with a support route at hello@openrig.dev
|
||||
([#113](https://github.com/mvschwarz/openrig/pull/113)).
|
||||
- `rig view show execution --project <catalog-id> --mission <mission>` selects
|
||||
a catalogued project's missions. Thanks to
|
||||
[@dajiaohuang](https://github.com/dajiaohuang)
|
||||
([#105](https://github.com/mvschwarz/openrig/pull/105)).
|
||||
- Recognize the Codex `»` conversation prompt during startup/resume checks,
|
||||
including after a dismissed hook-review panel. Unresolved menus remain gates.
|
||||
Thanks to [@dajiaohuang](https://github.com/dajiaohuang)
|
||||
([#111](https://github.com/mvschwarz/openrig/pull/111)).
|
||||
- Repair the bundled Vault skill's frontmatter and check shipped skill headers
|
||||
([#115](https://github.com/mvschwarz/openrig/pull/115)); remove historical
|
||||
development evidence from the public source tree
|
||||
([#118](https://github.com/mvschwarz/openrig/pull/118)) and the retired TUI
|
||||
drivability prototype ([#119](https://github.com/mvschwarz/openrig/pull/119)).
|
||||
- Run eight PR test jobs, including the UI suite and one installed queue-durability
|
||||
scenario with an intentional failure control
|
||||
([#117](https://github.com/mvschwarz/openrig/pull/117)). This does not cover all
|
||||
historical scenarios or every platform.
|
||||
- Parse SQLite boot timestamps as UTC on non-UTC hosts so current identity and
|
||||
context readings are compared with the correct generation start time. Thanks
|
||||
to [@Coder8124](https://github.com/Coder8124)
|
||||
([#124](https://github.com/mvschwarz/openrig/pull/124)).
|
||||
- Recognize headerless Codex conversations with custom status-line field order
|
||||
and mixed-case model names ([#125](https://github.com/mvschwarz/openrig/pull/125));
|
||||
thanks to [@Hexgunner69](https://github.com/Hexgunner69) for the report and
|
||||
[@Aummadour](https://github.com/Aummadour) for regression cases. The separate
|
||||
stale restore-warning issue is not fixed by this change.
|
||||
- Resolve a linked worktree's Git metadata directories for Codex fresh launches
|
||||
instead of passing its `.git` file as a directory
|
||||
([#126](https://github.com/mvschwarz/openrig/pull/126)); thanks to
|
||||
[@mgall-ibizdigital](https://github.com/mgall-ibizdigital) for the report and
|
||||
suggested approach.
|
||||
|
||||
See [0.6.1 release notes](docs/releases/v0.6.1.md) for changes and compatibility
|
||||
limits. Slack manifest/setup assistance and Rig Stream classification remain
|
||||
experimental; no new validation of those experiments is claimed.
|
||||
|
||||
## [0.6.0]
|
||||
|
||||
- **Breaking:** OpenRig requires Node.js 22 or 24 and uses better-sqlite3 13.
|
||||
Node 20 is no longer supported, and the install check refuses it; Node 26 is
|
||||
untested. Switch Node, then reinstall the CLI; existing
|
||||
data is migrated in place. See [Moving off Node 20](README.md#moving-off-node-20).
|
||||
Thanks to [@jimallen](https://github.com/jimallen) for reporting the Node 26
|
||||
install failure and proposing the upgrade ([#16](https://github.com/mvschwarz/openrig/pull/16)).
|
||||
- Choose permissions per seat for future launches with
|
||||
`rig seat set-permissions <seat> --mode <mode> --reason <text>`. Codex
|
||||
`full_bypass` now also sets `-a never`; Claude Code modes such as `auto` are
|
||||
accepted only when the managed executable supports them. Rig-level verbs move
|
||||
to `rig policy permissions …`, with the old verbs kept as aliases. Thanks to
|
||||
[@DoowanKang](https://github.com/DoowanKang) ([#30](https://github.com/mvschwarz/openrig/issues/30))
|
||||
and [@djogss](https://github.com/djogss) ([#33](https://github.com/mvschwarz/openrig/issues/33)).
|
||||
- Protect a seat where you type by hand with `rig seat set-typing-guard`. While it
|
||||
is on, automatic messages and wakes are held instead of typed in; the default is
|
||||
unchanged. Thanks to [@some-marketing](https://github.com/some-marketing)
|
||||
([#48](https://github.com/mvschwarz/openrig/issues/48)).
|
||||
- Experimental: `rig slack manifest` prints the Slack app manifest offline, with a
|
||||
prefilled create-app link, and a new setup guide describes the manual steps.
|
||||
Creating the app remains a step you do in Slack, and the steps have not been
|
||||
confirmed against a real app creation. The existing Slack connector is not
|
||||
experimental.
|
||||
- Open a whole rig in Herdr from the TUI (`term ▸ rig <name>`), up to 16 seats per
|
||||
tab in a workspace named after the rig; the empty starting tab is closed only
|
||||
when that is confirmed safe. Thanks to [@shintaii](https://github.com/shintaii)
|
||||
([#26](https://github.com/mvschwarz/openrig/issues/26)).
|
||||
- Pi seat activity reports carry the occupant generation and are accepted only
|
||||
for the seat's current occupant. Thanks to
|
||||
[@DoowanKang](https://github.com/DoowanKang) ([#29](https://github.com/mvschwarz/openrig/issues/29)).
|
||||
- The kernel starter summary identifies the library preview and explains automatic
|
||||
runtime-variant selection. Thanks to [@tgrundtvig](https://github.com/tgrundtvig)
|
||||
([#21](https://github.com/mvschwarz/openrig/issues/21)).
|
||||
- Codex seats launch with `--no-daemon` when supported. Thanks to
|
||||
[@reisalbuquerque](https://github.com/reisalbuquerque) ([#69](https://github.com/mvschwarz/openrig/issues/69)).
|
||||
- Missions without a `metadata` block no longer fail the readiness reader. Thanks
|
||||
to [@shravansumanthanan](https://github.com/shravansumanthanan) for the fix and
|
||||
[@kainne44](https://github.com/kainne44) for the report
|
||||
([#72](https://github.com/mvschwarz/openrig/issues/72)).
|
||||
- Experimental and optional, off by default: a classifier seat can have Jev,
|
||||
through OpenRouter, label Rig Stream observations. Check, turn on or turn off
|
||||
with `rig project experimental status|enable|disable --config <file>`, then run
|
||||
one bounded foreground `rig project wake … --experiment <file>`. Labels are
|
||||
advisory, with no accuracy or reliability claim, and the feature may be
|
||||
incomplete. See [stream classification](docs/reference/stream-classifier-worker.md).
|
||||
- Report problems with either experiment through a
|
||||
[GitHub issue](https://github.com/mvschwarz/openrig/issues/new/choose) or a pull
|
||||
request ([CONTRIBUTING.md](CONTRIBUTING.md)).
|
||||
|
||||
Includes [#77](https://github.com/mvschwarz/openrig/pull/77),
|
||||
[#84](https://github.com/mvschwarz/openrig/pull/84) by
|
||||
[@mvdpoel](https://github.com/mvdpoel), [#91](https://github.com/mvschwarz/openrig/pull/91)
|
||||
and [#94](https://github.com/mvschwarz/openrig/pull/94).
|
||||
|
||||
Release preparation: [#109](https://github.com/mvschwarz/openrig/pull/109).
|
||||
An ordinary candidate-tarball install with real postinstall passed on fresh
|
||||
macOS 15 ARM64 / Node.js 22.22.1, including SQLite 13, all 89 migrations,
|
||||
write/reopen and unauthenticated daemon startup/shutdown. Fresh Node 24 install
|
||||
and authenticated native fresh/resume/fork permission enforcement were not
|
||||
completed. See the [verification scope](docs/releases/v0.6.0.md#verification-scope)
|
||||
and [known limitations](docs/releases/v0.6.0.md#known-limitations). The final
|
||||
artifact will be separately bound to the merged release commit.
|
||||
|
||||
## [0.5.17]
|
||||
|
||||
- Install the CLI with Bun as well as npm: `bun add -g @openrig/cli`. The package
|
||||
no longer depends on the unpublished `@openrig/daemon`; it imports the daemon
|
||||
copy it already ships, which also makes the package smaller. OpenRig still runs
|
||||
on Node.js, and Bun may block the package's postinstall check. Thanks to
|
||||
[@drewpayment](https://github.com/drewpayment) for reporting this
|
||||
([#66](https://github.com/mvschwarz/openrig/issues/66)).
|
||||
|
||||
Includes [#68](https://github.com/mvschwarz/openrig/pull/68). The Node support
|
||||
range and SQLite version are unchanged.
|
||||
|
||||
## [0.5.16]
|
||||
|
||||
- Let a rig write Claude Code's managed instruction blocks to `CLAUDE.local.md`
|
||||
instead of a tracked `CLAUDE.md`, with `managed_blocks: { claude-code: CLAUDE.local.md }`.
|
||||
The default stays `CLAUDE.md`, and Codex stays on `AGENTS.md`. Blocks already
|
||||
written to `CLAUDE.md` are not moved; remove them by hand after switching.
|
||||
Thanks to [@hvpaiva](https://github.com/hvpaiva) for reporting and proposing
|
||||
this ([#25](https://github.com/mvschwarz/openrig/issues/25)).
|
||||
- Add an advisory portability report for pull requests. It lists added lines
|
||||
that contain machine-, network- or account-specific values. Findings never
|
||||
fail the check; operational errors, such as a git failure, still do.
|
||||
- Show badges and a short demo of agents working in the README.
|
||||
|
||||
Includes [#54](https://github.com/mvschwarz/openrig/pull/54) and
|
||||
[#56](https://github.com/mvschwarz/openrig/pull/56) by
|
||||
[@mvschwarz](https://github.com/mvschwarz). The Node support range and SQLite
|
||||
version are unchanged.
|
||||
|
||||
## [0.5.15]
|
||||
|
||||
- Recognize Codex through shell and Node launchers during startup and recovery,
|
||||
while keeping uncertain process identity visible.
|
||||
- Skip recognized Codex update notices without installing provider updates, and
|
||||
give clearer startup recovery guidance, including `rig up <name> --existing`.
|
||||
- Add the OpenRig Software Factory recipe and worked example for continuing
|
||||
reviewed work, with incremental team growth using `rig grow`.
|
||||
- Guide agents through user-chosen command permissions, preserving existing rules
|
||||
and explaining project versus user scope. Permission defaults are unchanged.
|
||||
- Explain provider hooks, workspace trust and other machine changes before the
|
||||
first launch.
|
||||
- Show Pi replies and tool progress, retain managed OpenRig context in shell
|
||||
tools, and report bounded provider errors without exhausted-retry duplicates.
|
||||
- Submit pasted Pi messages explicitly and handle input beyond the terminal's
|
||||
canonical buffer limit, including cancellation and oversized-input recovery.
|
||||
- Add a guarded retry for an added seat whose first startup failed during resource
|
||||
projection; retain its complete original configuration for recovery.
|
||||
|
||||
Pi support remains qualified and supervised: controlled coding and continuity
|
||||
were verified, but ordinary useful-task completion and unattended teamwork remain
|
||||
unverified. Shipped permission defaults, the Node support range and SQLite version
|
||||
are unchanged; project work-policy features are outside this release.
|
||||
|
||||
Includes [#37](https://github.com/mvschwarz/openrig/pull/37) by
|
||||
[@danielkuykendall23-boop](https://github.com/danielkuykendall23-boop), and
|
||||
[#38](https://github.com/mvschwarz/openrig/pull/38),
|
||||
[#39](https://github.com/mvschwarz/openrig/pull/39),
|
||||
[#45](https://github.com/mvschwarz/openrig/pull/45) and
|
||||
[#46](https://github.com/mvschwarz/openrig/pull/46) by
|
||||
[@mvschwarz](https://github.com/mvschwarz).
|
||||
|
||||
**Known compatibility limitation:** on macOS arm64 with Node 24, SQLite dependency
|
||||
installation can fail when a suitable prebuilt binary is unavailable, and
|
||||
compiler-built SQLite has also shown runtime cleanup failures. Use Node 22 on
|
||||
that platform for now; the declared Node support range is unchanged.
|
||||
See [the release notes](docs/releases/v0.5.15.md) for starting commands and guidance.
|
||||
|
||||
## [0.5.14] - 2026-09-14
|
||||
|
||||
VM inspection was accepted: generally zero-to-two-second loading with
|
||||
occasional timeouts, formatting, icon colors and default views. This is an
|
||||
observed result, not a general latency or availability guarantee. The completed
|
||||
single-consumer Slack/phone exercise remains evidence for unchanged notifications.
|
||||
|
||||
- Enter a confirmed running rig directly, with responsive Help, Skip and Local
|
||||
during slow or unverified startup.
|
||||
- Keep the navigator and labelled useful content during page/rig changes, slow
|
||||
reads and failures; preserve scope identity, passive prior content and confirmed
|
||||
removal semantics. Read the selected rig before unrelated fleet details.
|
||||
- Send complete bounded human briefs with related thread detail, distinguish
|
||||
quiet FYIs from decisions, and retain delivery/reply correlation.
|
||||
- Find instance-wide Human requests and delivered Updates in Feed; open Health,
|
||||
Configuration and Connections under System, with six top-level entries.
|
||||
- Browse Feed categories, explicitly expand Specs kinds and Derived terminal
|
||||
views, and find Saved views without expanding the whole catalog.
|
||||
- Read mission outcomes, current or planned owner, blockers and next dependencies
|
||||
above slice boxes at both terminal widths. Native accepted judgments drive
|
||||
completion; assigned or reopened work and mission lifecycle remain distinct.
|
||||
- Reduce repeated terminal inventory work while preserving saved-view preview
|
||||
and explicit Herdr Open; no general latency improvement is claimed.
|
||||
- Keep primary queue facts readable when optional blocker descriptions fail;
|
||||
bound fleet reads across stages and avoid duplicate policy/receipt read work.
|
||||
- Reduce restore/startup inventory scans with selective event indexes, preserving
|
||||
history and projection results; private replay is not a live latency guarantee.
|
||||
- Preserve nullable identity provenance during queue-history archival. Shared
|
||||
history, RECENT and OWNER/receipt readers now include archived records;
|
||||
unknown historical provenance stays null rather than being reconstructed.
|
||||
|
||||
Root, CLI, daemon and web UI are `0.5.14`; terminal TUI remains `0.1.0`.
|
||||
Migration head advances to `084_inventory_event_indexes`, following review read
|
||||
indexes in 083, archive identity provenance in 082 and explicit human-intent fields
|
||||
in migration 081. Private installed review and controlled notification checks
|
||||
retain their exact candidate and
|
||||
runtime attribution. See [the release notes](docs/releases/v0.5.14.md) for
|
||||
operator guidance and verification limits.
|
||||
|
||||
Known limitations: the inherited CLI health-check deadline can reject a queue
|
||||
command before its request is sent; that defect and occasional server delay are
|
||||
not fixed here. Same-candidate recovery did not establish durable responsiveness.
|
||||
Bundled Workflow Specs can disappear from discovery when an upgrade changes the
|
||||
install directory; that fix is explicitly deferred beyond 0.5.14. Recorded
|
||||
shutdown/config-preservation uncertainties and the prior recorder-write failure
|
||||
remain disclosed.
|
||||
|
||||
## [0.5.13] - 2026-09-10
|
||||
|
||||
**Status**: release candidate, unpublished. Exact-cut substance review,
|
||||
release verification and publication remain pending; live adoption is separate.
|
||||
|
||||
- Enter the TUI with useful Help, Skip and local intent reading even while
|
||||
the daemon is unavailable or its state is unverified.
|
||||
- Choose a project, read its current sources and follow links, then return
|
||||
to the same place without confusing projects that share a name.
|
||||
- Separate requests needing human action from updates in ATTENTION, and
|
||||
inspect their source without implying approval.
|
||||
- Preview saved or derived terminal views before deliberately opening them;
|
||||
navigate CONFIG, Help and health with readable values at narrow widths.
|
||||
- Choose scoped human-led or delegated oversight and retain current context,
|
||||
assessment and action evidence without claiming improved behavior.
|
||||
- Update Git-backed context through an explicit merge that preserves local
|
||||
authorship, conflicts and the distinction between selected and consumed bytes.
|
||||
|
||||
Root, CLI, daemon and web UI are `0.5.13`; terminal TUI remains `0.1.0`.
|
||||
Migration head advances to `080_scoped_operating_posture`. Natural behavioral
|
||||
effect remains **UNOBSERVED** and human time cost **UNMEASURED**. The default
|
||||
multi-client control-socket limitation remains disclosed and unfixed.
|
||||
See [the full release notes](docs/releases/v0.5.13.md) for usage and proof limits.
|
||||
|
||||
## [0.5.12] - 2026-09-09
|
||||
|
||||
**Status**: release candidate; exact-cut review, final verification and
|
||||
publication remain pending.
|
||||
|
||||
- Record authorized proof judgments and corrections once; derive affected
|
||||
slice, mission and project readiness with attributable evidence.
|
||||
- Inspect authored versus running graphs, deliberately adopt supported
|
||||
revisions, and reconcile ambiguous operations without duplicate work.
|
||||
- Discover exception ownership before failure; admit dependency-failure
|
||||
tasks/wakes atomically and recover one occurrence without hiding another.
|
||||
- Keep healthy unchanged waits quiet while preserving change-driven wake,
|
||||
bounded recovery and explicit expansion of compact reads.
|
||||
- Enter through bare interactive `rig` for normal startup and selective
|
||||
return, preserving conversation history or requesting an explicit fresh start.
|
||||
- Browse general instance settings and provenance in CONFIG, with Slack as
|
||||
one subsection and passive, secret-safe inspection.
|
||||
|
||||
Root, CLI, daemon and web UI are `0.5.12`; terminal TUI remains `0.1.0`.
|
||||
Migration head remains `079_workflow_lifecycle_parallel`. Accepted development
|
||||
and native evidence retains its original attribution; release preparation does
|
||||
not imply a new human usability study, full-platform run or public adoption.
|
||||
See [the full release notes](docs/releases/v0.5.12.md) for operational guidance
|
||||
and known limits.
|
||||
|
||||
## [0.5.11] - 2026-09-07
|
||||
|
||||
**Status**: release candidate; final verification and publication remain pending.
|
||||
|
||||
- Start useful repository work with the focused `first-project` owner/checker
|
||||
team and an attachable shared kernel TUI.
|
||||
- Inspect workflow owners and waits, declared spec purpose, effective instance
|
||||
configuration, and human connection state in the ordinary TUI.
|
||||
- Navigate with command completion, readable Recent activity, and persistent
|
||||
timezone selection. Unverified daemon probes no longer read as confirmed down.
|
||||
- Reuse project release profiles that retain both the release ceremony and the
|
||||
actual post-release boundary, independently of successor scope.
|
||||
- Preserve context delivery and generation evidence; honor authored startup
|
||||
proof selection; improve exception routing, bounded shutdown receipts,
|
||||
startup child identity, and cross-host origin attribution.
|
||||
- Diagnosis `show`/`list --json` now return summaries; use `--full --json`
|
||||
for complete evidence. Queue previews also print an exact full-read command.
|
||||
|
||||
Root, CLI, daemon, and web UI are `0.5.11`; terminal TUI remains `0.1.0`.
|
||||
Migration head remains `079_workflow_lifecycle_parallel`.
|
||||
|
||||
The health-agent experiment remains a POC, and the Herdr journey requires no
|
||||
plugin. Neither is a new cold-agent architecture or a plugin/adoption claim.
|
||||
See [the full release notes](docs/releases/v0.5.11.md) for operational guidance
|
||||
and the retained verification limits.
|
||||
|
||||
## [0.5.7] - 2026-09-01
|
||||
|
||||
**Status**: release-cut candidate. Final release-wide verification and public
|
||||
publish remain release-manager gates.
|
||||
|
||||
### Summary For Installing Agents
|
||||
|
||||
- **Package versions**: root, CLI, daemon, and web UI are `0.5.7`; the terminal
|
||||
TUI remains `0.1.0`.
|
||||
- **Migration**: head advances `076` → `077_node_session_source`; normal daemon
|
||||
startup applies the ordered migration list automatically.
|
||||
- **Theme**: elastic topology lifecycle and coherent work delivery.
|
||||
|
||||
### Elastic topology lifecycle
|
||||
|
||||
- Create a rig without hand-authored YAML; grow existing or new pods in single
|
||||
or batch operations; and preserve faithful spec show/export, including a
|
||||
member's declared agent-image source.
|
||||
- Refuse unsafe shrink while work is active. Explicit drain fallback preserves
|
||||
queue identity and state, checks that the destination session is live, and
|
||||
removes only the intended target; ordinary cleanup stays direct.
|
||||
|
||||
### Coherent work delivery
|
||||
|
||||
- Compose product-journey SDLC context across world, project, mission, and
|
||||
slice layers, with useful non-overwriting scaffold defaults.
|
||||
- Accept optional project YAML enrichment with explicit warnings and default
|
||||
fallback, load Markdown by section address, and support explicit project
|
||||
selection, switching, root isolation, and ambiguity refusal.
|
||||
- Project the complete catalog of public plugin-only skills together with the
|
||||
helper files their instructions reference.
|
||||
|
||||
### Deferred and post-release
|
||||
|
||||
- Restore honesty is deferred to `0.5.8`.
|
||||
- Cleaned parent-host two-project dogfood runs after release.
|
||||
|
||||
See [`docs/releases/v0.5.7.md`](docs/releases/v0.5.7.md) for the full notes.
|
||||
|
||||
## [0.5.6] - 2026-08-30
|
||||
|
||||
**Status**: shipped; **"world-building as context engineering."** OpenRig 0.5.6 ships the context system's next layer, makes long-running agents durable, and makes messages to humans trustworthy. Three themes: the context a seat runs on is structured, tagged, and verifiable; an agent seat can now outlive its own context window without a human managing the transition; and anything routed to a human carries proof of delivery. **v0.5.6 contains v0.5.5 in full.**
|
||||
|
||||
### Summary For Installing Agents
|
||||
|
||||
- **Package version**: bumps from `0.5.5`.
|
||||
- **Migrations**: THREE new (apply automatically on daemon start; no manual steps). Head advances `073` → `076`:
|
||||
- `074_context_usage_watchdog.ts`
|
||||
- `075_context_usage_watchdog_generation.ts`
|
||||
- `076_owner_notification_levels.ts`
|
||||
- **Node engines**: unchanged.
|
||||
- **API surface**: existing commands remain; additive verbs and configuration below.
|
||||
- New verbs: `rig view show execution`, `rig scope resolve-notes`.
|
||||
- New watchdog condition: `usage-threshold` (`rig watchdog`).
|
||||
- New continuity policy: `compaction_strategy` in the AgentSpec (four modes) with a declared `mechanic`.
|
||||
- New reference docs at stable paths: `docs/reference/sdlc-conventions.md`, `docs/reference/mission-install.md`, `docs/reference/lore-routing.md`, `docs/reference/planning-dial.md`, `docs/reference/wave-sdlc.md`, `docs/reference/release-boundary.md`, `docs/reference/product-management-pass.md`.
|
||||
- **Context packs**: 41 ship (up from 39).
|
||||
- **Delivery defaults**: deliberately wide open (post at `NOTICE` and above, interrupt at `ALERT` and above). Tune down from measured volume with the two dials rather than pre-filtering.
|
||||
- **Continuity modes**: opt-in per seat; unconfigured seats keep today's behavior exactly.
|
||||
|
||||
### The context system: structured, tagged, verifiable
|
||||
|
||||
- **The mission install.** A documented five-layer convention (`docs/reference/mission-install.md`) for installing project context into a seat: the pieces, their arrangement, the seat's own position, the current contract, and a derive-your-own-delta step that proves the install was consumed rather than skimmed.
|
||||
- **Lore routing.** What a seat learns locally routes into addressable, rig-local lore packs (`docs/reference/lore-routing.md`) — carried with a maturity stage from birth, and structurally excluded from anything that ships.
|
||||
- **Taxonomy everywhere.** Every pack entering `rig context` carries its class — WORLD / LORE / SKILLS / MISSION — surfaced in listings, with a loud refusal for untagged packs.
|
||||
- **A public world pack with checkable claims.** The public onboarding pack is authored as atoms whose claims each carry a check that can fail, with the verifier shipped inside the pack — public content that cannot rot silently.
|
||||
- **A complete, self-retrievable skill index.** The skill router's index now covers the full shipped set, is retrievable through the routes it teaches, and carries a recurrence test so coverage gaps stay found.
|
||||
- **A substance gate for what ships.** A whole-surface review pass over every packaged file (bundle and npm surfaces included) runs as a named release step; this release shipped with all 5,316 packaged files passing.
|
||||
|
||||
### Continuity: seats that outlive their context windows
|
||||
|
||||
- **Continuity policies in the agent spec.** `compaction_strategy` is now live configuration with four modes: `default-compaction`, `managed-compaction`, `handover`, and `apprentice-handover`. Modes apply to Claude seats (positive runtime matching — Codex seats keep their native behavior) and resolve through the same spec-default < profile < member precedence as the rest of the agent spec.
|
||||
- **The apprentice handover, automated.** A seat declaring `apprentice-handover` gets two armed triggers: near its context threshold, the system creates a fresh successor seat and runs its onboarding install; nearer the wall, it mints an owned handover work item for the seat that executes the swap. The executing seat is declared configuration (`mechanic:` on the policy) — never inferred from topology, and arming refuses loudly if it is missing.
|
||||
- **Managed compaction with honest restores.** Seats in `managed-compaction` mode get a pre-compaction prep nudge (deposit your state before the window closes) and, after a configured restore, a durable receipt recording how much usable context the restore actually recovered — so a restore that silently refills the window instead of recovering width is visible.
|
||||
- **Context-usage triggers as a primitive.** `rig watchdog` gains a usage-threshold condition: fire any command when a seat's context usage crosses a calibrated per-seat threshold. The continuity modes are built on it; your own automation can be too.
|
||||
|
||||
### The human layer: delivery you can prove
|
||||
|
||||
- **No more silent fall-through.** One destination resolver classifies every dispatch: terminal-bound seats use the terminal; registered humans and virtual seats route through the gateway; nothing falls through to a terminal lookup that cannot succeed. Unregistered external addresses get a structured, teaching refusal.
|
||||
- **Delivery receipts on the work item.** Every gateway post writes its receipt atomically on the queue row: `posted` (with the message timestamp and channel), `transport-failed` (with the error), or — derivably — `never-posted`. `rig queue undelivered` now consults that ledger first and returns only genuine failures. "Did this reach them?" is one read.
|
||||
- **Notification levels, one vocabulary.** Queue transitions classify as `RECORD` < `NOTICE` < `ALERT` at write time — a human-required decision is `ALERT`; system ceremony events are `NOTICE`; the rest is durable record. Two independent dials configure the minimum level that posts and the minimum level that interrupts.
|
||||
- **A delivery-rules engine.** Stored per-human preferences and availability decide how each message class is delivered. An `off` preference suppresses interruption — never the durable record, the post, or the receipt. Away windows defer routine traffic into digests (4-hour and daily), with exactly-once delivery by construction: durable episode identities, retry until a real receipt, and structurally non-overlapping digests.
|
||||
- **Files dropped in Slack become work.** A file or image upload lands as a work item with the file transferred to local storage and referenced by path — authenticated download, size-bounded, per-file failures named, and no URL or token ever written to a row. A park blocked on a registered human automatically enters the alert path and posts exactly once per episode.
|
||||
|
||||
### Operational honesty
|
||||
|
||||
- A rate-limited seat gets exactly one timed wake at its stated reset.
|
||||
- A seat holding claimed live work while sitting idle gets woken by the system, consuming the arbitrated activity oracle — with exactly one wake per park episode.
|
||||
- Refocus state keys to the current occupant of a seat; a fresh occupant never inherits a predecessor's baselines.
|
||||
- `rig ps` counts claimed in-progress work as assigned work.
|
||||
- Sends refuse empty bodies; unknown daemon API routes return JSON 404s.
|
||||
- Stuck-sweep findings stop re-minting after closure; cross-host custody verification completes durably instead of refreshing forever.
|
||||
- Blocker actuation is unified: one helper, cross-host closure honest, delivery after the transaction commits.
|
||||
- Rig expansion and serialization preserve what they were given: `session_source` versions, `services`, and member overrides survive round-trips; fork forwards model, role, restore policy, and label.
|
||||
- `rig view show execution` derives who is building what, what is next, and each item's completion rung (locked / built / reviewed / folded / adopted) at read time — unknowns render as INDETERMINATE, never as idle or done.
|
||||
|
||||
### Process and reference
|
||||
|
||||
- The SDLC conventions document is now a component menu: choose per mission from the simple flow, the wave model, or the rigorous overlay, with planning rigor as a dial. Four full references ship at `docs/reference/`: `planning-dial.md`, `wave-sdlc.md`, `release-boundary.md`, and `product-management-pass.md`, joined by `mission-install.md` and `lore-routing.md`.
|
||||
|
||||
---
|
||||
|
||||
## [0.5.5] - 2026-08-27
|
||||
|
||||
**Status**: shipped; **"the ambient-attention release"** — the fleet notices, retries, escalates, diagnoses, and onboards on its own so you can stop hand-babysitting the work in flight. **v0.5.5 contains v0.5.4 in full** — one reconciled lineage.
|
||||
|
||||
### Summary For Installing Agents
|
||||
|
||||
- **Package version**: bumps from `0.5.4`.
|
||||
- **Migrations**: TWO new. Head advances `071` → `073`:
|
||||
- `072_thread_seat_map.ts`
|
||||
- `073_queue_transition_wakes.ts`
|
||||
- **Node engines**: unchanged.
|
||||
- **API surface**: existing commands remain. New verbs: `rig parked [seat]`, `rig seat handover --source fork:|rebuild` (execute path, was dry-run), `queue block --on <blocker>` (park-with-wake), `rig gateway human` fragment-lifecycle verbs, `rig view show escalations`. `rig ps` gains a typed `ACTIVITY` column.
|
||||
|
||||
### Headline
|
||||
|
||||
**Stop babysitting the work.** `rig parked <seat>` diagnoses "is anyone silently stuck" with confidence and teaching inline; the standing stuck-sweep runs without you and routes findings. Baton wakes retry, aggregate, and escalate on their own — hand off the row and stop chasing nudges. Parks carry their own wake and read HEALTHY. `rig ps` gains a typed ACTIVITY column from one oracle. The Slack human layer is live end-to-end, and fresh installs onboard themselves.
|
||||
|
||||
### What you can now do
|
||||
|
||||
#### Diagnose silent-stuck without capture arithmetic
|
||||
|
||||
`rig parked [seat]` answers "is anyone silently stuck" as a derived diagnosis — activity × open obligations, with per-input confidence and remedy taught inline. **Reach for it when:** a seat looks idle and you suspect dropped work; before any `claimedAt` arithmetic or pane capture.
|
||||
|
||||
#### Hand off work; stop babysitting nudges
|
||||
|
||||
Failed baton wakes retry, aggregate, and escalate on their own (transitions ARE the ladder state, restart-safe; per-destination aggregation; rungs deliver-and-advance; unconfirmed-with-no-pickup escalates without re-send). **Reach for it when:** you hand off work — the row is enough. Watch `rig view show escalations` for the aggregates.
|
||||
|
||||
#### Consume the standing stuck sweep instead of running it
|
||||
|
||||
Overdue + undelivered become routed findings with derived evidence inline. **Reach for it when:** you used to hand-run `queue overdue` / `undelivered` on a timer — stop; consume its findings instead.
|
||||
|
||||
**Caveat (5.6 backlog):** cross-host successor visibility is a proven false-positive class; treat cross-host-lineage findings as unverified until the 5.6 detector fix. Contained fleet-wide one-per-condition; delta teaches the caveat.
|
||||
|
||||
#### Park a row on a real blocker without waking the attention machinery
|
||||
|
||||
`queue block --on <blocker>` records the wake; held-with-live-wake is not flagged; auto-unparked owners get an honest wake. **Reach for it when:** imminent-but-blocked work; for not-imminent work use the workspace instead (the queue is a conveyor).
|
||||
|
||||
#### Read seat activity from one oracle
|
||||
|
||||
`rig ps` carries a true `ACTIVITY` column — typed taxonomy (working / idle-at-prompt / needs-input-as-count+reason / unknown) with evidence ladder + visible rung degradation, seat-keyed across swaps. **Reach for it when:** any "is it thinking or stuck" question — the oracle beats capture; the TUI reads the same truth.
|
||||
|
||||
#### Execute seat handover, not just plan it
|
||||
|
||||
`rig seat handover --source fork:` (carries live context) or `rebuild` (primes from the durable chain and names its priming artifacts). Mid-swap failures record honestly. **Reach for it when:** replacing an occupant — no more dry-run-only planning surface.
|
||||
|
||||
#### Reach human decision owners directly through Slack
|
||||
|
||||
Gateway running, thread-per-seat, exactly-once inbound reconciliation, escalation loudness (mention) distinct from routine. Humans are addressable members; `rig gateway human` has full fragment-lifecycle verbs. **Reach for it when:** anything must reach a human decision owner — an escalation-class send arrives loud on their phone.
|
||||
|
||||
#### Onboard fresh installs without hand-walking
|
||||
|
||||
Fresh installs get focused onboarding packs by default (config off; existing rigs untouched). **Reach for it when:** standing up a new rig or seat — stop hand-walking the eight pieces.
|
||||
|
||||
#### Trust refocus to fire only when it should
|
||||
|
||||
Refocus fires at a context threshold or on demand — NEVER at session start (post-compaction both runtimes; usage threshold Claude-only). **Reach for it when:** a long session loses the plot — trigger it. A fresh seat getting a refocus is now a bug to file, not noise to ignore.
|
||||
|
||||
#### Author scopes with the one-file convention
|
||||
|
||||
`scope mission create` → `NOTES.md` + intent-bearing `SPEC`; `slice create` → `SPEC`-only (no IMPLEMENTATION-PRD) + acceptance `PROGRESS`; release missions get a capability-delta scaffold. `repair` is ADDITIVE — stamps append, never rewrite author frontmatter. **Reach for it when:** creating any scope node — stamp verification is a mechanical strip again.
|
||||
|
||||
#### Bind daemons safely from any managed environment
|
||||
|
||||
Daemon bind intent has provenance: an inherited `OPENRIG_HOST` never selects single-bind; adoption gates test ALL required listeners. **Reach for it when:** daemon maintenance from any managed environment — no more `env -u` ceremony.
|
||||
|
||||
### What to STOP doing (each was correct under 0.5.4 and is wrong now)
|
||||
|
||||
1. **STOP the per-lock author-reconstruction protocol.** `scope repair/approve` used to rewrite author frontmatter; now stamps APPEND — strip-reconstruction is mechanical; the protocol is retired.
|
||||
2. **STOP diagnosing parks by `claimedAt` arithmetic + capture.** `rig parked` derives the diagnosis with confidence and teaching; capture is a fallback glance, not the method.
|
||||
3. **STOP storing deferred work as queue rows.** The queue is a conveyor for imminent sequential work; deferred work lives in the mission workspace and re-mints when due.
|
||||
4. **STOP authoring `IMPLEMENTATION-PRD.md` and `MISSION_NOTES.md` on new work.** `SPEC.md` is the one spec file; `NOTES.md` is the chain name; locks bind SPEC-only.
|
||||
5. **STOP routing contact through an orchestrator by default.** Any agent escalates directly; orchestrators/PMs also send judgment-worthy updates.
|
||||
|
||||
### Landed, not yet drivable (recorded so nobody reaches for it)
|
||||
|
||||
- Delivery preferences + availability (stored, validated) — no rules engine consumes them yet (0.5.6 slice 01).
|
||||
- S01 operator rung — human-layer connector delivery leg lands in 0.5.6 (S11 territory). Escalation view + daemon health carry the floor now.
|
||||
- `@external` addresses on the direct send path fall through to tmux (routing fix in 0.5.6 wave-2). Reach humans via the gateway path (queue/escalation), not raw `rig send` to `@external`.
|
||||
- Multi-human topology — single-human ships as an honesty marker; multi-human is 0.5.7.
|
||||
|
||||
### Known Limitations (0.5.6 backlog)
|
||||
|
||||
- **Cross-host successor visibility false positives** in the stuck-sweep detector — 39 active findings at census (20 proven FP, 19 unverified); contained fleet-wide; fix lands in 0.5.6 wave 1.
|
||||
- **`@external` direct-send routing** — 0.5.6 wave-2 routing fix.
|
||||
- **Delivery preferences → rules engine** — 0.5.6 slice 01.
|
||||
|
||||
---
|
||||
|
||||
## [0.5.4] - 2026-08-26
|
||||
|
||||
**Status**: release candidate; **"the honesty release"** - commands distinguish absence, uncertainty, staging, and completed effects instead of collapsing them into reassuring output. **v0.5.4 contains v0.5.3 in full** - one reconciled lineage, no product divergence.
|
||||
|
||||
### Summary For Installing Agents
|
||||
|
||||
- **Package version**: bumps from `0.5.3`.
|
||||
- **Migrations**: none in this release. Migration head stays at 071 (matching 0.5.3).
|
||||
- **Node engines**: unchanged.
|
||||
- **API surface**: existing commands remain; new seat lifecycle verbs are `rig seat set-model`, `rig seat stop`, and `rig seat clean`.
|
||||
|
||||
### Headline
|
||||
|
||||
**Trust what the command says, including when the answer is uncertain.** Transport probes now classify found, absent, and indeterminate outcomes through one resolution path. Delivery verification distinguishes text staged in a prompt from text consumed by an agent. Queue, refocus, preview, import, and lifecycle surfaces preserve the state and refusal that actually occurred.
|
||||
|
||||
### What you can now do
|
||||
|
||||
#### Resolve transport failures without inventing a missing seat
|
||||
|
||||
Send, capture, nudge, and walk use one classified session probe. A transport blip is reported as indeterminate rather than `Session not found`, while genuine absence remains explicit. Recovery output names what was checked and what action is available.
|
||||
|
||||
#### Tell staged delivery from consumed delivery
|
||||
|
||||
`rig send --verify` classifies the pane effect instead of equating typed text with a completed turn. Single-recipient, fan-out, and JSON results derive from the same outcome model, so a staged prompt is not reported as consumed.
|
||||
|
||||
#### Deliver unknown-sender messages honestly
|
||||
|
||||
An unattributable message is delivered with an unknown-sender notice instead of being refused. Where a reply path exists, the sender is told that attribution was unavailable and should follow up with a signed message.
|
||||
|
||||
#### Manage one seat through supported lifecycle verbs
|
||||
|
||||
`rig seat set-model`, `rig seat stop`, and `rig seat clean` provide audited, single-seat operations. Stop and clean refuse when any affected session is live or indeterminate; set-model is proven against a real managed successor rather than only stored configuration.
|
||||
|
||||
#### Trust state-preserving queue and refocus behavior
|
||||
|
||||
Adding a queue note no longer changes terminal state. Envless owned queue listing refuses instead of silently changing scope. Refocus keeps due state until delivery is visible. Bounded previews say they are bounded.
|
||||
|
||||
#### Refuse duplicate running rig names at every import path
|
||||
|
||||
Create and import routes now share one running-name guard. A duplicate running name returns a teaching `409 rig_name_running` refusal instead of spending resources or degrading into a generic server error.
|
||||
|
||||
### Stability and authority fixes
|
||||
|
||||
- Startup boundaries are hermetic across scratch/test environments.
|
||||
- The bundled `openrig-core` plugin has an explicit advancing authority, so an older runtime cannot silently overwrite newer installed guidance.
|
||||
- Stale lab refocus registrations are retired.
|
||||
- CLI and daemon refusal codes survive route and import boundaries without fallback noise.
|
||||
|
||||
### Known Limitations (0.5.5 backlog)
|
||||
|
||||
- **Writer-layer disk cap** - the one-time VM cleanup recovered headroom, but the permanent cap at the layer that writes snapshots is unfinished.
|
||||
- **Repair/approve frontmatter rewrite** - the known rewrite defect remains outside this release.
|
||||
- **Observation pipeline performance** - shared pane/process observation remains measurement-gated; 0.5.4 does not claim the broader polling/census redesign.
|
||||
|
||||
---
|
||||
|
||||
## [0.5.3] - 2026-08-26
|
||||
|
||||
**Status**: shipped; **"the context release"** — pull exact context by address instead of reading files. **v0.5.3 contains v0.5.2 in full** — one lineage, no divergence.
|
||||
@@ -52,13 +650,13 @@ The refocus hook resolves library refs automatically (ref beats file), fails lou
|
||||
|
||||
`rig context get` serves expertise packs by path-like ref, e.g. `skills/process/context-engineering`. The old `context-pack:` colon form was removed and the CLI says so.
|
||||
|
||||
**Caveat that pack:** it is titled "Traditional Context Engineering — 2024-2025 Snapshot" for a reason — provisional, historical, non-normative by founder ruling. Current OpenRig skills, explicit user rulings, and measured practice outrank it on any conflict.
|
||||
**Caveat that pack:** it is titled "Traditional Context Engineering — 2024-2025 Snapshot" for a reason — provisional, historical, non-normative by the project owner's ruling. Current OpenRig skills, explicit user rulings, and measured practice outrank it on any conflict.
|
||||
|
||||
### What to STOP doing (each was correct under 0.5.2 and is wrong now)
|
||||
|
||||
1. **STOP copying library content into seat or mission files to compose context.** Compose by ref — addressing means there is no second copy to drift. Nothing catches the copy automatically; it is still a defect by rule.
|
||||
2. **STOP treating a `MODEL DIVERGENCE` proclamation as alias noise.** The canonicalize-then-compare fix ended the pinned=fable / effective=claude-fable-5 false-positive class, and the monitor now isolates per-seat throws. A proclamation on this runtime is a real escalation — act on it.
|
||||
3. **STOP reading "founder-gated" in tool errors as an authorization wall.** The overstated wording is retired from the scripts; those guards demand explicit paths (e.g. `OPENRIG_SKILL_CANON_ROOT`), not permission. The real founder boundary is exactly: pushes and PRs. Local commits and ordinary apply work never need founder word.
|
||||
3. **STOP confusing explicit-path requirements with permission.** The guards described in this release demand explicit paths (e.g. `OPENRIG_SKILL_CANON_ROOT`); that mechanism does not grant authorization. **Historical guidance correction (2026-09-10):** the earlier approval statement was project-specific, not a universal rule. Current project and mission authority determines whether local commits, apply operations, pushes or PRs require approval.
|
||||
4. **STOP hand-placing evidence or trusting a placeholder PRD as the proof contract.** `rig proof add` pairs evidence to the contract, and a pristine scaffold PRD can never silently become that contract: the authored SPEC serves with a named advisory (`contractSource` in the echo tells you which source bound).
|
||||
|
||||
### Landed but not yet drivable (recorded so nobody reaches for it)
|
||||
@@ -473,7 +1071,7 @@ Building on the v0.4.8 permission-policy foundation, v0.5.0 ships the built-in p
|
||||
- **New shipped starter: `rig up factory-rsi`** — the single-rig recursive-self-improvement factory MVP. One rig, seven seats (`plan-planner`, `build-implementer`, `check-qa`, `review-reviewer`, `dogfood-tester`, `release-manager`, `orch-lead`), running the new `factory-rsi` workflow. A launch-tier product starter (a `product-team` sibling), workspace-agnostic — point `--cwd <repo>` at whatever the loop should improve.
|
||||
- **New builtin workflow: `factory-rsi`** — the inner loop `plan → implement → qa_check → review → release`, with `qa_check`/`review` `failed` → `implement` (bounded remediation), engine-routed — never an orchestrator relay. Dogfood is **decoupled** from this gated loop: the dogfood seat runs out-of-band against the **shipped** product and feeds its findings into the next plan (the RSI edge, ungated — no loop-stop in the MVP; the continuous out-of-band runtime mechanism is refined in a later release). The remediation loops are sanctioned only by the enforceable `loop_guards.max_hops`; a trip is an exception routed orchestrator-first (`exception_routing`), and `rig workflow resume` grants one more bounded window.
|
||||
- **Recorded-state cycles**: the next plan's input is the *recorded* dogfood findings (`evidence_ref` / the packet trail), never a seat's chat memory — the RSI feedback is durable recorded state.
|
||||
- **Publish stays a human act**: the release leg is two steps — `release_prep` (the release-manager PREPARES notes/docs/PR and records the evidence; un-gated, runs first) hands off to `release_signoff`, which holds the ship decision at a human gate (`gate.target: human@kernel`). Prepared artifacts exist before sign-off; no seat pushes, tags, publishes, or upgrades a host.
|
||||
- **Publish stays a human act**: the release leg is two steps — `release_prep` (the release-manager PREPARES notes/docs/PR and records the evidence; un-gated, runs first) hands off to `release_signoff`, which holds the ship decision at the configured human gate target. Prepared artifacts exist before sign-off; no seat pushes, tags, publishes, or upgrades a host.
|
||||
- **Rides the merged engine, no new machinery**: runs on the workflow engine + spec language + exception model as shipped, with the v0 hardcode seam (`target.rig: factory-rsi` + `preferred_targets` pin each role 1:1 to a seat) — no binding-layer dependency, no engine change. Runtime config: seats inherit their runtime's default model (no per-seat pin); plan/build/release/orch run on claude-code, and qa/review/dogfood run on codex for cross-runtime diversity against the builder.
|
||||
- **No migrations. No breaking changes.**
|
||||
|
||||
@@ -757,7 +1355,7 @@ The v0.4.0 cascade-metadata hygiene findings (`missing_provenance` + `missing_ve
|
||||
|
||||
## [0.4.0] - 2026-06-20
|
||||
|
||||
**Status**: wrap-gate CLEAR; lifecycle push / npm publish / tag held for founder-auth.
|
||||
**Status**: wrap-gate CLEAR; lifecycle push / npm publish / tag held for authorization from the project owner.
|
||||
|
||||
### Summary For Installing Agents
|
||||
|
||||
@@ -804,7 +1402,7 @@ Five read-commands flip from firehose-by-default to compact-by-default — close
|
||||
### Known Limitations / Carry-Forward
|
||||
|
||||
- **Plugin-lineage drift in `openrig-core`** — the openrig-core plugin skill lineage is divergent/stale; full re-sync is OPR.0.4.1.4 (rides 0.4.1). Boot-path layers (canonical + hub cwd) verified current in wrap-gate AC-3 sweep. `rig skill audit` (slice 10) is the runtime mechanism for future drift detection.
|
||||
- All earlier "PUSHED to 0.4.1" carry-forwards from the original wrap-gate were RESTORED to 0.4.0 during the wrap: slice 34 (`rig ps` current-rig default + `-A`/`--all-rigs` + `resumeTokenPresent`) landed — see Token-Efficient Defaults; slice 35 (`rig scope` stage/verified/reconcile) landed — see New Top-Level CLI Verbs. Real-terminal-related slices 38 + 39 also shipped via founder live-dogfood forward-fix authorization 2026-06-21. Nothing of substance carries forward to 0.4.1 from the original wrap-gate set.
|
||||
- All earlier "PUSHED to 0.4.1" carry-forwards from the original wrap-gate were RESTORED to 0.4.0 during the wrap: slice 34 (`rig ps` current-rig default + `-A`/`--all-rigs` + `resumeTokenPresent`) landed — see Token-Efficient Defaults; slice 35 (`rig scope` stage/verified/reconcile) landed — see New Top-Level CLI Verbs. Real-terminal-related slices 38 + 39 also shipped via the project owner's live-dogfood forward-fix authorization on 2026-06-21. Nothing of substance carries forward to 0.4.1 from the original wrap-gate set.
|
||||
|
||||
### What To STOP Using
|
||||
|
||||
@@ -819,10 +1417,10 @@ Five read-commands flip from firehose-by-default to compact-by-default — close
|
||||
|
||||
- Wrap worktree clean on `8d55ea60`.
|
||||
- CLI surfaces source-verified against the command modules at the release SHA.
|
||||
- CLI→skill cascade sweep: canonical `openrig-work/skills/openrig-user/SKILL.md` → product mirror at `packages/daemon/specs/agents/shared/skills/core/openrig-user/SKILL.md` → hub cwd `.claude/skills/openrig-user/SKILL.md` + `.agents/skills/openrig-user/SKILL.md` — all byte-identical (md5 `e2aa9176`).
|
||||
- The historical CLI→skill cascade sweep reported the internal canonical skill source → product mirror at `packages/daemon/specs/agents/shared/skills/core/openrig-user/SKILL.md` → hub cwd `.claude/skills/openrig-user/SKILL.md` + `.agents/skills/openrig-user/SKILL.md` as byte-identical (reported md5 `e2aa9176`). This records the historical report, not a fresh mirror check.
|
||||
- cli-reference.md updated for 6 changed commands + new `rig skill` section; `last-verified-against-source` bumped to `8d55ea60`.
|
||||
- Stale-pattern grep (`dumps everything` / `--notify required` / `rig down 404`) returned 0 hits across active SKILL.md locations.
|
||||
- AC-6 self-check evidence: `substrate/shared-docs/openrig-work/missions/release-0.4.0/slices/36-release-durability-close/AC-6-wrap-gate-self-check-evidence.md`.
|
||||
- The historical AC-6 self-check evidence was internal and is not a public reproducible receipt.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our pledge
|
||||
|
||||
We as members, contributors, and maintainers pledge to make participation in OpenRig a
|
||||
harassment-free experience for everyone, regardless of age, body size, visible or invisible
|
||||
disability, ethnicity, sex characteristics, gender identity and expression, level of experience,
|
||||
education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive,
|
||||
and healthy community.
|
||||
|
||||
## Our standards
|
||||
|
||||
Examples of behaviour that contributes to a positive environment:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from
|
||||
the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall community
|
||||
|
||||
Examples of unacceptable behaviour:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address, without their
|
||||
explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Enforcement responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying and enforcing our standards of acceptable
|
||||
behaviour and will take appropriate and fair corrective action in response to any behaviour that
|
||||
they deem inappropriate, threatening, offensive, or harmful. Maintainers have the right and
|
||||
responsibility to remove, edit, or reject comments, commits, code, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces (the repository, Discussions, issues and
|
||||
pull requests) and when an individual is officially representing the project in public spaces.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behaviour may be reported to the
|
||||
maintainers at **[email protected]**. All complaints will be reviewed and investigated
|
||||
promptly and fairly.
|
||||
All maintainers are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
## Enforcement guidelines
|
||||
|
||||
Maintainers will follow these community impact guidelines in determining the consequences for any
|
||||
action they deem in violation of this Code of Conduct:
|
||||
|
||||
1. **Correction.** A private, written warning, with clarity around the nature of the violation and
|
||||
an explanation of why the behaviour was inappropriate. A public apology may be requested.
|
||||
2. **Warning.** A warning with consequences for continued behaviour, including no interaction with
|
||||
the people involved for a specified period.
|
||||
3. **Temporary ban.** A temporary ban from any sort of interaction or public communication with
|
||||
the community for a specified period.
|
||||
4. **Permanent ban.** A permanent ban from any sort of public interaction within the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
|
||||
version 2.1 by Coraline Ada Ehmke, available at
|
||||
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html and licensed under
|
||||
[Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/).
|
||||
This adaptation names OpenRig and its maintainers and summarizes the enforcement guidelines. Community impact guidelines
|
||||
were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
# Contributing to OpenRig
|
||||
|
||||
Thanks for being here. OpenRig is built in the open and by the thing it is: a rig of coding
|
||||
agents and a small group of people. External pull requests and issues have started arriving
|
||||
faster than we planned for, which is the best problem to have. This page says how to get a
|
||||
change in with the least friction on both sides.
|
||||
|
||||
## Before you start
|
||||
|
||||
- **Bugs:** open an issue with the bug template. Include your OpenRig version (`rig --version`),
|
||||
OS, Node version, which harnesses are involved (or none), and the relevant command and output.
|
||||
Reports are public: remove credentials, private prompts, personal details and private paths
|
||||
before posting. Share a small reproduction rather than a full transcript or instance dump.
|
||||
- **Features and behaviour changes:** open an issue or a Discussion in *Ideas* first. A short
|
||||
"what I am trying to do and what stops me" saves both of us a rewrite. Small, obvious fixes do
|
||||
not need an issue.
|
||||
- **Questions:** use [Discussions › Q&A](https://github.com/mvschwarz/openrig/discussions/categories/q-a),
|
||||
not an issue.
|
||||
|
||||
## Before a security or integration PR
|
||||
|
||||
OpenRig connects your coding agents to each other: shared context, messaging, coordination and long-running seats.
|
||||
It's designed for your own machine or a trusted private network, for you and people you trust, not for the open
|
||||
internet. It doesn't try to act for you in the outside world; your agents already have tools for that.
|
||||
|
||||
**Security-related PRs.** If you've found something exploitable, report it privately first (see
|
||||
[SECURITY.md](SECURITY.md)) rather than in a public PR. For hardening changes, tell us in the PR description:
|
||||
|
||||
- the scenario: how this goes wrong for someone using OpenRig as it's designed to be used, and the evidence you have
|
||||
- who or what causes it, and how they reach the install
|
||||
- what the change costs everyone else: a refusal, an extra step, a new setting
|
||||
|
||||
We look at the finding and the fix separately. We may agree with a finding and fix it differently, or decide the fix
|
||||
costs more than it protects. If the answers are missing we'll ask once, and close the PR with thanks if they don't
|
||||
come.
|
||||
|
||||
**Integrations with other tools or projects.** Open an issue or an Ideas Discussion and wait for a maintainer's yes on
|
||||
scope before you build it; an issue on its own isn't a yes. Often the best home for an integration is your own
|
||||
repository, and we're happy to link to it.
|
||||
|
||||
## Setting up
|
||||
|
||||
Node `^22 || ^24` and a working `tmux` are required. Then:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mvschwarz/openrig.git
|
||||
cd openrig
|
||||
npm install
|
||||
npm run build # all workspaces
|
||||
npm test # repo checks + daemon, cli, tui test suites
|
||||
npm run lint # typecheck every package
|
||||
```
|
||||
|
||||
`npm test` builds the daemon and runs repository checks before the package suites. Read the
|
||||
specific failure: `npm run mirror-skills` updates skill mirrors, and
|
||||
`npm run generate-context-packs` updates generated packs. Use them when their source changed and
|
||||
review the generated diff; neither command fixes every documentation failure. The UI unit-test
|
||||
suite is advisory and separate: `npm run test:ui`.
|
||||
|
||||
For hands-on development, read the [check requirements](docs/reference/developing.md),
|
||||
[worktree setup](docs/reference/worktree-builds.md), and
|
||||
[machine changes](README.md#what-openrig-changes-on-your-machine). Use an isolated environment
|
||||
for changes that start daemons or agents; `OPENRIG_HOME` alone does not isolate provider settings.
|
||||
Permission configuration is an [explicit choice](docs/reference/getting-started.md#have-your-agent-configure-permissions).
|
||||
A checked-out tree is not the installed daemon; restarting an installed daemon does not adopt
|
||||
your working copy.
|
||||
|
||||
## Making the change
|
||||
|
||||
- One concern per pull request. Keep unrelated refactors separate; explain any refactor needed
|
||||
for the fix.
|
||||
- Keep the diff small enough to review in one sitting. If it is not, say why in the description.
|
||||
- Add or update a test where the change is testable. Use focused deterministic tests where
|
||||
possible. For terminal or provider behaviour, state what was exercised with the actual runtime
|
||||
and what was simulated; a stub alone does not prove the native interaction works.
|
||||
- Do not edit `CHANGELOG.md`. Maintainers write release notes at the tag.
|
||||
- Do not bump versions.
|
||||
- Match the surrounding style. `npm run lint` typechecks; it does not format code.
|
||||
- Write commit messages in the form the log already uses: `fix(cli): …`, `feat(daemon): …`,
|
||||
`docs(reference): …`, `harness: …`.
|
||||
|
||||
## The pull request
|
||||
|
||||
Fill in the template. The three things a reviewer needs are: what a user gets, how you verified
|
||||
it, and anything you were unsure about. State the revision and relevant local changes you tested,
|
||||
what you actually ran, and any checks you could not run. Redact private information from evidence.
|
||||
|
||||
Contributions follow the repository's [Apache-2.0 license](LICENSE). Preserve attribution and any
|
||||
applicable license notices when adapting third-party material.
|
||||
|
||||
## What to expect from us
|
||||
|
||||
- We aim to acknowledge issues and pull requests within **one day**. An acknowledgement is not
|
||||
a completed review or a merge decision.
|
||||
- Our target for a first substantive review decision on an external PR is **seven days**. If it
|
||||
takes longer, we explain what is pending on the PR. This is a target, not a guaranteed deadline.
|
||||
- Labels you will see: `needs-repro` (we could not reproduce it yet; waiting on versions or steps),
|
||||
`fixed-on-main` (merged, not yet on npm), `good first issue`, `help wanted`, `discussion`
|
||||
(direction question; continues in Discussions).
|
||||
|
||||
Reviews here are done by people and by the project's own agents. An agent may ask the first
|
||||
clarifying question or run the reproduction; a maintainer makes the merge decision.
|
||||
|
||||
## Where things live
|
||||
|
||||
- Repository reference: `docs/reference/`; user documentation: [openrig.dev/docs](https://openrig.dev/docs).
|
||||
- Skills: `packages/daemon/specs/agents/shared/skills/` and plugin skills under
|
||||
`packages/daemon/assets/plugins/`; static context-pack sources: `packages/daemon/context-packs-src/`.
|
||||
Generated packs live in `packages/daemon/context-packs/` and are not hand-edited or committed.
|
||||
- Releases: [GitHub Releases](https://github.com/mvschwarz/openrig/releases) and npm `@openrig/cli`.
|
||||
@@ -1,43 +1,162 @@
|
||||
# OpenRig
|
||||
|
||||
[](https://www.npmjs.com/package/@openrig/cli) [](https://www.npmjs.com/package/@openrig/cli) [](LICENSE) [](https://github.com/mvschwarz/openrig/stargazers)
|
||||
|
||||
A harness wraps a model. A rig wraps your harnesses. Define your agent team in YAML, boot it with one command. Claude Code and Codex in the same rig, managed as one system.
|
||||
|
||||
OpenRig turns AI coding agents from a pile of terminal sessions into a persistent, organized team. A rig is the team that doesn't go away. If you've got tabs full of agents you're afraid to close, this is the layer you're missing: it's open source, it's local, and you're four commands away.
|
||||
OpenRig turns AI coding agents from a pile of terminal sessions into a persistent, organized team. Talk to a lead agent about the outcome you want; it can coordinate specialists across teams and bring you results and decisions that need your attention. Start with a repository and one useful change, then keep the team's work and context at the same addresses.
|
||||
|
||||
The OpenRig UI is experimental and in maintenance mode. It is not under active development; support is best-effort. The CLI is the primary supported interface. Contributions welcome.
|
||||
**Guide:** [Getting started](docs/reference/getting-started.md) · **Stuck?** [Help](docs/reference/help.md) · **Questions:** [Q&A](https://github.com/mvschwarz/openrig/discussions/92) · **Updates and demos:** [@_feralmachine on X](https://x.com/_feralmachine)
|
||||
|
||||
## See it running
|
||||
|
||||

|
||||
|
||||
**Start here:** [the guided first-use path](docs/reference/getting-started.md): install, launch a two-agent team in your repository, and get one reviewed change.
|
||||
|
||||
Not setting this up today? Get the next walkthrough and occasional OpenRig updates → https://openrig.dev/follow
|
||||
|
||||
## Install and first run
|
||||
|
||||
Requires Node.js 22 or 24 and tmux, on macOS or Linux. On a Mac with Apple silicon, use Node.js 22 ([compatibility history](docs/releases/v0.5.15.md#known-compatibility-limitation)). Native Windows is not supported yet, and WSL2 has not been tested. Launching a rig writes provider hooks and workspace trust settings. Before running the commands below, read [what OpenRig changes on your machine](#what-openrig-changes-on-your-machine) and back up the relevant files.
|
||||
|
||||
```bash
|
||||
npm install -g @openrig/cli
|
||||
rig setup
|
||||
rig up product-team
|
||||
rig ui open
|
||||
rig setup --dry-run
|
||||
```
|
||||
|
||||

|
||||
To install with Bun instead, run `bun add -g @openrig/cli`. OpenRig still runs on Node.js, so install Node.js 22 as well. Bun may block this package's postinstall script, in which case the Node.js and SQLite check described under [what OpenRig changes on your machine](#what-openrig-changes-on-your-machine) does not run at install time.
|
||||
|
||||
It runs locally: a daemon, a SQLite database, a CLI, and a dashboard. The agents are ordinary Claude Code and Codex sessions in tmux. `product-team` is the fuller starter, 7 seats, 4 Claude and 3 Codex, with an orchestration HA pair plus development and review pods.
|
||||
Choose the working account you already have: **Claude Code, Codex, or both**. Reuse an explicit choice; no second subscription is required. `rig setup --dry-run` previews the broader setup, but applying `rig setup` checks both harnesses and cmux. It is optional for the [selected-provider path](docs/reference/getting-started.md#choose-your-providers).
|
||||
|
||||
Because 4 Claude seats run at once, single-plan users should expect provider throttling. For the light-footprint path, use `conveyor`, a 4-seat starter with 2 Claude and 2 Codex.
|
||||
Before launching, your agent asks once: **“Allow your agents to run OpenRig commands without repeated permission prompts?” Yes — recommended / No — keep prompts.** This covers every `rig` command, including starting/stopping agents and configuration, at personal project scope unless you explicitly choose user-wide sessions. It is not global YOLO or permission to invent work. On Yes, the agent [adds and verifies native rules](docs/reference/getting-started.md#have-your-agent-configure-permissions); No or no answer leaves settings unchanged. An existing explicit choice is reused. Say “Undo the OpenRig command allowances added by this setup” to remove only its additions.
|
||||
|
||||
Check `tmux -V` and only your selected CLI/login: `claude --version` plus
|
||||
`claude auth status`, or `codex --version` plus `codex login status`. If needed,
|
||||
sign in once with `claude auth login` or `codex login`; do not install or log in
|
||||
to an unused provider.
|
||||
|
||||
| Team | Starter | Models |
|
||||
| --- | --- | --- |
|
||||
| Two Codex agents | `first-project` | Both `gpt-6-astra` (unchanged) |
|
||||
| Two Claude agents | `first-project-claude` | Configured native Claude default |
|
||||
| Claude owner + Codex checker | `first-project-mixed` | Claude default + `gpt-6-astra` |
|
||||
|
||||
All three use the same owner/checker roles and task. Show the selected runtime,
|
||||
configured model and command before launch; confirm the account supports the
|
||||
model instead of silently falling back. The kernel starts automatically and
|
||||
selects from available authenticated providers independently of these two project
|
||||
agents. A missing unused provider is not a setup requirement.
|
||||
|
||||
```bash
|
||||
# Preview the smaller starter
|
||||
rig specs preview conveyor
|
||||
|
||||
# Boot the light-footprint path
|
||||
rig up conveyor
|
||||
cd /path/to/your/repository
|
||||
starter=first-project # or first-project-claude or first-project-mixed
|
||||
rig specs preview "$starter" --kind rig
|
||||
rig up "$starter" --cwd . --plan
|
||||
rig up "$starter" --cwd .
|
||||
rig tui --shared
|
||||
```
|
||||
|
||||
## First Run
|
||||
The kernel provides separate operational support and the shared dashboard. To detach without stopping the dashboard, press Ctrl-b then d; `rig tui --shared` returns to that view. Plain `rig tui` opens an independent view. Closing a viewing terminal does not mean you should relaunch the team.
|
||||
|
||||
After `product-team` boots, `rig ps` shows the fleet map and `rig ps --nodes --rig product-team` shows the running pods and seats. When you're ready to shut the team down and bring it back, use the same rig name:
|
||||
Check project-seat readiness with `rig ps --nodes --rig "$starter"` and resolve any authentication, trust or permission prompt before assigning work. Then give the owner one bounded outcome from your repository:
|
||||
|
||||
```bash
|
||||
rig ps
|
||||
rig down product-team
|
||||
rig up product-team
|
||||
rig send "dev-owner@$starter" 'Implement <one useful change>. Track the task in the queue and return its ID. Keep it local, verify the behavior, ask dev-check in this rig to check the exact candidate, and record the result and how I can try it.'
|
||||
rig queue list --destination "dev-owner@$starter" --limit 1000
|
||||
```
|
||||
|
||||
`rig ps` is a fleet glance. `rig down product-team` snapshots the team and stops it. `rig up product-team` brings it back by name from that snapshot.
|
||||
Sending a message does not itself create a queue item; the owner records the task. Read the final artifact and the review of its exact candidate, then return to the same owner for the next change. [The guided first-use path](docs/reference/getting-started.md) covers readiness, a useful task, a reviewed result, Herdr/cmux terminals and recovery.
|
||||
|
||||
Not setting this up today? Get the next walkthrough and occasional OpenRig updates → https://openrig.dev/follow
|
||||
|
||||
## Community
|
||||
|
||||
- **Questions:** [Discussions › Q&A](https://github.com/mvschwarz/openrig/discussions/categories/q-a)
|
||||
- **Bugs and feature requests:** [open an issue](https://github.com/mvschwarz/openrig/issues/new/choose)
|
||||
- **Contributing:** [CONTRIBUTING.md](CONTRIBUTING.md) · [Code of Conduct](CODE_OF_CONDUCT.md) · [Security policy](SECURITY.md) · [Getting help](.github/SUPPORT.md)
|
||||
- **Videos:** [youtube.com/@openrig](https://www.youtube.com/@openrig)
|
||||
- **Releases:** [GitHub Releases](https://github.com/mvschwarz/openrig/releases) and npm `@openrig/cli`
|
||||
|
||||
We aim to acknowledge issues and pull requests within one day; see [CONTRIBUTING.md](CONTRIBUTING.md#what-to-expect-from-us) for review targets.
|
||||
|
||||
## What OpenRig changes on your machine
|
||||
|
||||
OpenRig writes instance state, provider integration and workspace files as part
|
||||
of setup and operation. These include **trust settings and executable hooks**.
|
||||
The summary below follows this source revision; check `rig --version` when
|
||||
using a published package, since repository guidance can be ahead of npm.
|
||||
|
||||
| When | What changes and why |
|
||||
| --- | --- |
|
||||
| **npm installation** | Installs the CLI, bundled components and dependencies under your npm prefix (with Bun, under Bun's global directory). OpenRig's postinstall checks the Node.js version and that the SQLite module loads; Bun may block this script. It does not run daemon or provider setup. |
|
||||
| **`rig setup`** | Attempts missing tools and writes an OpenRig block in `~/.tmux.conf` for mouse support and scrollback. On macOS it can install cmux and enable its automation socket control in `~/.config/cmux/settings.json`. `--full` adds workstation tools. `--dry-run` shows setup's plan without applying it. |
|
||||
| **Daemon startup** | Creates/updates instance state under `OPENRIG_HOME` (normally `~/.openrig`), including its database and managed plugin resources. Seeds the `openrig-skills` discovery skill in `~/.claude/skills` and `~/.agents/skills`, subject to existing version ownership. With `runtime.codex.hooks_enabled` enabled (the default), writes Codex hook configuration and trust records as described below—even before a rig launches. |
|
||||
| **Rig/seat launch and attachment** | Creates tmux sessions, supplies seat identity and daemon connection environment, and projects selected guidance, skills, plugins and runtime resources into the workspace. Managed startup pre-trusts the workspace. Claude context collection can also be provisioned for attached sessions and refreshed during monitoring. |
|
||||
| **Explicit permission configuration** | The built-in bootstrap does **not** add `rig` command allow rules. Agent-guided setup recommends Yes and requires your actual answer before the agent [adds rules at your chosen scope](docs/reference/getting-started.md#have-your-agent-configure-permissions). No/no answer preserves settings; existing choices and stricter rules remain relevant. Broader access is separate. |
|
||||
|
||||
The provider files are separate from instance state. Here `~` means the daemon
|
||||
user's home; changing `OPENRIG_HOME` alone does not isolate provider configuration.
|
||||
|
||||
- **Claude Code:** managed startup writes workspace trust and onboarding completion
|
||||
to `~/.claude.json`. In the workspace, `.claude/settings.local.json` receives
|
||||
the context collector's `statusLine` command and selected activity hooks;
|
||||
helper scripts live under `.openrig/`. Selected settings/MCP resources can also
|
||||
change that settings file and `.mcp.json`. The shared settings resource sets
|
||||
`permissions.defaultMode` to `acceptEdits` and enables Exa/Context7 MCP entries;
|
||||
selected MCP resources configure those external services. Built-in bootstrap
|
||||
no longer writes a command allowlist to `~/.claude/settings.json` or removes
|
||||
older allowances. The trust writer uses the daemon home, so a custom
|
||||
`CLAUDE_CONFIG_DIR` is not a general relocation of these writes.
|
||||
- **Codex:** writes the daemon's `CODEX_HOME/config.toml` (normally
|
||||
`~/.codex/config.toml`). Startup enables hooks, adds the OpenRig activity relay
|
||||
commands and pre-writes trust hashes for those commands. Seat startup adds
|
||||
`trust_level = "trusted"` for the workspace; selected config resources can
|
||||
add MCP settings. Recognized update notices can be skipped during launch,
|
||||
recording the skipped version in Codex's cache; this is not an update install.
|
||||
|
||||
Activity relays send event type/subtype, seat/runtime identity, timestamps and
|
||||
native session identity to the configured OpenRig daemon's `/api/activity/hooks`
|
||||
endpoint, using its activity token. That payload excludes prompt text and tool
|
||||
arguments. Claude's collector writes context/token usage, session/transcript-path
|
||||
metadata and available rate-limit data to the instance's `state/context-usage`
|
||||
and `state/provider-usage`. Provider and selected MCP connections have their own
|
||||
data flows. Daemon plugin initialization also checks the OpenRig plugin release
|
||||
endpoint on GitHub.
|
||||
|
||||
Managed launches supply `HOME`, `CODEX_HOME` and `OPENRIG_*` identity/connection
|
||||
variables. Claude uses `--permission-mode acceptEdits` and defaults to the classic
|
||||
renderer for terminal scrollback. Codex uses `-s workspace-write` unless a named
|
||||
profile governs its sandbox; the default does not force an approval-policy flag.
|
||||
Fresh Codex launches also add writable access to the workspace's `.git` and the
|
||||
pod's shared queue-state directory with `--add-dir`; the shared root comes from
|
||||
`OPENRIG_SHARED_DOCS_ROOT` or `~/.openrig/shared-docs`.
|
||||
YOLO is **off by default**. An explicitly selected full-bypass policy selects
|
||||
Claude's `--dangerously-skip-permissions` or Codex's
|
||||
`-s danger-full-access -a never`. The legacy environment-only `OPENRIG_YOLO=1`
|
||||
path still selects only Codex's sandbox; a resolved policy overrides that
|
||||
environment setting.
|
||||
|
||||
Permission mode controls native execution permissions; work posture is separate
|
||||
project guidance. Use `rig policy permissions list|show|current|apply` for rig
|
||||
policy configuration (the four `rig policy` aliases remain compatible). Use
|
||||
`rig seat set-permissions <seat> --mode <mode> --reason <text>` for an audited
|
||||
future-launch choice: `floor`, `full_bypass`, or `inherit` to clear the seat
|
||||
override. Additional Claude modes such as `auto` require support from the exact
|
||||
managed Claude executable at the seat's working directory; selection and launch
|
||||
each check it. Unsupported or changed contexts refuse without a fallback.
|
||||
This does not relaunch the seat
|
||||
or change its current native process, history, rules or hooks. `rig seat status`
|
||||
separates the desired selection from the last launch arguments; neither proves
|
||||
native enforcement. See the [permission guide](docs/reference/getting-started.md#per-seat-permission-mode).
|
||||
|
||||
Managed hook blocks target OpenRig's entries and retain unrelated hooks, but
|
||||
trust entries, selected resource keys and Claude's existing status-line command
|
||||
can be replaced. Some writers recover unreadable settings as empty objects;
|
||||
this is not a complete preservation or rollback guarantee. Back up relevant
|
||||
files before first use. Daemon/bootstrap writes are automatic and do not each
|
||||
have an interactive preview; `rig setup --dry-run` does not preview every later
|
||||
startup effect.
|
||||
|
||||
## What It Does
|
||||
|
||||
@@ -45,33 +164,37 @@ OpenRig is a multi-agent harness — it manages the system that coding agents fo
|
||||
|
||||
- **Define** topologies in YAML (RigSpec) with pods, edges, and continuity policies
|
||||
- **Boot** everything with `rig up` — tmux sessions, harnesses, startup files, readiness checks
|
||||
- **See** the topology in a live graph with explorer, node detail, and system log
|
||||
- **See** rigs, pods, and seats in the TUI topology table and graph; inspect projects, specs, feeds, and instance health
|
||||
- **Discover** existing Claude Code and Codex sessions in tmux and adopt them into a managed rig
|
||||
- **Snapshot** the full topology on `rig down`, restore by name with `rig up <name>`
|
||||
- **Snapshot** the topology with `rig down --snapshot`, restore by name with `rig up <name>`
|
||||
- **Communicate** across agents with `rig send`, `rig broadcast`, and `rig chatroom`
|
||||
- **Evolve** running topologies with `rig expand`, `rig shrink`, `rig launch`, `rig remove`
|
||||
- **Protect** a seat where you type by hand: `rig seat set-typing-guard <seat> --enabled true --reason <text>` holds automatic messages and wakes instead of typing them into that seat (off by default; see `rig seat set-typing-guard --help`)
|
||||
- **Connect** Slack through an app you create in your own workspace; the experimental `rig slack manifest` prints that app's manifest ([setup guide](docs/reference/slack-app-setup.md))
|
||||
- **Evolve** running topologies with `rig grow`, `rig shrink`, `rig launch`, `rig remove`
|
||||
|
||||
Every agent runs in a tmux session you can attach to, inspect, and work with directly.
|
||||
|
||||
## Starter Rigs
|
||||
|
||||
OpenRig's hero starter is `product-team`, the fuller product-development rig:
|
||||
Use `first-project`, `first-project-claude`, or `first-project-mixed` for the
|
||||
same focused first-use path on your selected providers. `product-team` is an optional
|
||||
larger product-development example:
|
||||
|
||||
```bash
|
||||
rig specs preview product-team
|
||||
rig specs preview product-team --kind rig
|
||||
rig up product-team
|
||||
```
|
||||
|
||||
Use it when you want the week-one experience OpenRig is built around: an orchestrator HA pair, development work, review work, and enough moving pieces for the coordination layer to matter.
|
||||
Use it when you want a larger product squad: two orchestrators, implementation, QA, design, and two independent reviewers.
|
||||
|
||||
For a smaller starter, use `conveyor`:
|
||||
|
||||
```bash
|
||||
rig specs preview conveyor
|
||||
rig specs preview conveyor --kind rig
|
||||
rig up conveyor
|
||||
```
|
||||
|
||||
`conveyor` is the smallest shippable software factory, one command. It keeps the footprint lower for single-plan users while still showing a real handoff path through intake, planning, build, and review.
|
||||
`conveyor` is a four-seat starter mixing Claude Code and Codex. It shows a handoff path through intake, planning, build, and review; `first-project` remains the smaller two-seat starting point.
|
||||
|
||||
Also ships: `implementation-pair`, `adversarial-review`, `research-team`, and `secrets-manager` (HashiCorp Vault managed by a specialist agent).
|
||||
|
||||
@@ -83,10 +206,10 @@ rig specs ls
|
||||
|
||||
## How It Works
|
||||
|
||||
OpenRig is a local daemon + CLI + MCP server + React UI, built on tmux.
|
||||
OpenRig is a local daemon + CLI + terminal UI + MCP server, built on tmux. The older React web UI remains in maintenance mode with best-effort support.
|
||||
|
||||
```
|
||||
CLI / UI / MCP
|
||||
CLI / TUI / MCP
|
||||
|
|
||||
Hono HTTP daemon
|
||||
|
|
||||
@@ -95,16 +218,33 @@ Hono HTTP daemon
|
||||
SQLite + tmux + runtime adapters
|
||||
```
|
||||
|
||||
- **CLI**: 40+ commands designed for both humans and agents. Every mutating command ends with what happened, current state, and next action.
|
||||
- **UI**: Explorer sidebar, topology graph with pod grouping, node detail panel, system log, chatroom.
|
||||
- **MCP**: 17 tools so agents can manage their own topology (`rig_up`, `rig_ps`, `rig_send`, `rig_chatroom_send`, etc.)
|
||||
- **Runtimes**: Claude Code, Codex, and terminal nodes. Adapters for Pi and OpenCode in development.
|
||||
- **CLI**: Commands for both humans and agents to launch teams, inspect state, send messages, track owned work, and manage context.
|
||||
- **TUI**: Topology explorer, table and graph views, seat details, Specs, Projects, Terminals, Feed, and System. Navigate with the keyboard, mouse, or command bar.
|
||||
- **MCP**: Tools so agents can manage their own topology (`rig_up`, `rig_ps`, `rig_send`, `rig_chatroom_send`, etc.)
|
||||
- **Runtimes**: Native Claude Code and Codex sessions, terminal nodes, and a Pi adapter using an RPC runner inside a terminal pane.
|
||||
|
||||
## Terminal UI and Workspaces
|
||||
|
||||
The TUI shows the team's coordination state; herdr and cmux show the actual agent terminals alongside it. Use `rig tui commands` to list the TUI's command-bar navigation, or [try the interactive TUI tour](https://openrig.dev/tour/workspace).
|
||||
|
||||

|
||||
|
||||
*Captured from the interactive TUI demo using fictional project data.*
|
||||
|
||||
With herdr installed and connected, open the starter's terminals together:
|
||||
|
||||
```bash
|
||||
rig terminal open first-project --provider herdr
|
||||
```
|
||||
|
||||
For cmux, use `--provider cmux`. In the TUI, a rig's detail view has a `term ▸ rig <name>` link that opens every running seat of that rig in the default terminal provider; with herdr that is up to 16 seats per tab, in a workspace named after the rig. The underlying sessions remain accessible through tmux. See the [terminal workspace guide](docs/reference/getting-started.md#share-the-dashboard-and-return-to-it) for setup and returning to an existing view.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
- **RigSpec**: Declarative multi-agent harness definition in YAML. Pods, members, edges, continuity policies, culture file.
|
||||
- **AgentSpec**: Reusable agent blueprint with skills, guidance, hooks, profiles, and startup contracts.
|
||||
- **Pod**: Bounded context group. Agents in a pod share memory and can maintain each other's context.
|
||||
- **Seat**: A stable role and address in a rig, such as `dev-owner@first-project`. The conversation occupying it can change while its identity and authored context remain.
|
||||
- **Pod**: A group of related seats with shared guidance and context. Each agent still has its own context window.
|
||||
- **Discovery**: `rig discover` fingerprints existing tmux sessions. `rig adopt` brings them under management.
|
||||
- **Snapshot/Restore**: `rig down --snapshot` captures full state. `rig up <name>` restores from latest snapshot. Restore reports per-node outcomes (resumed, fresh, or failed).
|
||||
- **RigBundle**: Portable archive with vendored AgentSpecs and SHA-256 integrity. Share topologies across machines.
|
||||
@@ -122,13 +262,83 @@ rig send vault-specialist@secrets-manager "Check Vault health and report status.
|
||||
|
||||
Requires Docker for service-backed rigs.
|
||||
|
||||
## Upgrading an existing instance
|
||||
|
||||
For an existing installation, follow the [upgrade procedure](skills/_canonical/core/openrig-upgrade/SKILL.md) and the [0.5.14 release notes](docs/releases/v0.5.14.md). Preserve live seats during the upgrade; `rig down` is not an upgrade step. Upgrading to 0.6.0 also requires Node.js 22 or 24: see [Moving off Node 20](#moving-off-node-20) and the [0.6.0 release notes](docs/releases/v0.6.0.md).
|
||||
|
||||
### Moving off Node 20
|
||||
|
||||
OpenRig 0.6.0 supports Node.js 22 and 24 only. Its SQLite binding
|
||||
(better-sqlite3 13) requires Node 22 or newer. Node 20 is no longer supported;
|
||||
the install check refuses it with an explanation.
|
||||
|
||||
If you run OpenRig on Node 20, switch Node first, then reinstall the CLI under
|
||||
the new Node (a version manager keeps a separate global package set for each
|
||||
Node):
|
||||
|
||||
```bash
|
||||
nvm install 22 # or 24; fnm or your package manager work the same way
|
||||
npm install -g @openrig/cli
|
||||
rig --version
|
||||
```
|
||||
|
||||
Your existing OpenRig data stays where it is. The daemon reopens the same
|
||||
database under the new binding and applies any pending migrations in place.
|
||||
Restart the daemon under the new Node by following the upgrade procedure above.
|
||||
|
||||
### Crossing the 0.5.9 layout boundary
|
||||
|
||||
The migration below still applies when upgrading from a pre-0.5.9 instance.
|
||||
|
||||
0.5.9 makes `$OPENRIG_HOME/context` the addressable context library, writes
|
||||
Claude telemetry to `state/context-usage` (and provider telemetry to
|
||||
`state/provider-usage`), and installs the default System World at
|
||||
`context/system/system-world.yaml`. Existing instances cross this boundary by
|
||||
an **Agent-Operated Migration** from the shipped `openrig-upgrade` skill. The
|
||||
target runtime reads canonical-first with legacy-fallback while new writes use
|
||||
the canonical roots; a custom context-library root stays stable during
|
||||
activation. This is not a directory rename to do while an old collector writes.
|
||||
|
||||
```bash
|
||||
# SKILL_DIR is the installed openrig-upgrade skill directory.
|
||||
node "$SKILL_DIR/scripts/migrate-telemetry-state-0.5.9.mjs" --help
|
||||
node "$SKILL_DIR/scripts/migrate-telemetry-state-0.5.9.mjs" --home "$OPENRIG_HOME"
|
||||
node "$SKILL_DIR/scripts/migrate-telemetry-state-0.5.9.mjs" --home "$OPENRIG_HOME" --apply-state --preimage /safe/path/layout-0.5.9-before
|
||||
|
||||
# Activate the exact target runtime separately. After every bounded legacy tail is followed by newer paired samples at both new state roots:
|
||||
node "$SKILL_DIR/scripts/migrate-telemetry-state-0.5.9.mjs" --home "$OPENRIG_HOME" --verify --preimage /safe/path/layout-0.5.9-before > /safe/path/layout-0.5.9-verify.json
|
||||
|
||||
# Run the separately invoked non-destructive finalizer only with that exact receipt:
|
||||
node "$SKILL_DIR/scripts/migrate-telemetry-state-0.5.9.mjs" --home "$OPENRIG_HOME" --apply-library --preimage /safe/path/layout-0.5.9-before --verification /safe/path/layout-0.5.9-verify.json
|
||||
|
||||
# Restore only helper-owned preparation/finalizer effects if the observed upgrade must be reversed:
|
||||
node "$SKILL_DIR/scripts/migrate-telemetry-state-0.5.9.mjs" --home "$OPENRIG_HOME" --rollback /safe/path/layout-0.5.9-before
|
||||
```
|
||||
|
||||
`--help` prints the phase grammar without inventorying the instance. No phase
|
||||
flag intentionally runs the read-only plan; unknown options fail nonzero before
|
||||
plan or mutation.
|
||||
|
||||
Every phase emits JSON. Stop on any issue or incomplete receipt and follow its
|
||||
`next` action; do not continue from copied legacy telemetry or retry a partial
|
||||
mutation blindly. Preparation leaves legacy state and collector settings in
|
||||
place. Verification accepts exact tail bytes only when that same seat has newer
|
||||
paired context and provider samples under `state/`; finalization revalidates the
|
||||
accepted tails, copies the library without overwrite, and switches config last.
|
||||
The helper never removes the legacy telemetry or library. Retirement follows
|
||||
separate stable runtime, writer, reader, and recovery proof. Daemon, database,
|
||||
seat, plugin, and release lifecycle actions remain agent-owned.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js 20, 22, or 24 (even-numbered LTS releases; odd releases like 25 lack native addon prebuilds)
|
||||
- Node.js 22 or 24 (the supported versions in this release). Node 20 is no
|
||||
longer supported. Node 26 and other versions are untested. On a Mac with Apple
|
||||
silicon, use Node.js 22: see the [compatibility history](docs/releases/v0.5.15.md#known-compatibility-limitation).
|
||||
- tmux
|
||||
- macOS or Linux. Native Windows is not supported yet, and WSL2 has not been tested
|
||||
|
||||
Optional:
|
||||
- cmux for `Open CMUX` node surface controls
|
||||
- herdr or cmux for terminal workspaces showing the agents together
|
||||
- Docker for service-backed rigs and managed apps
|
||||
|
||||
## Setup and Troubleshooting
|
||||
@@ -139,30 +349,31 @@ Optional:
|
||||
|
||||
Both commands support `--json` for agent-driven workflows.
|
||||
|
||||
Managed runtime boot (during `rig up`) may modify runtime config for core bootstrap and spec-selected runtime resources. `rig setup` discloses these paths so agents know what may be changed:
|
||||
- global Claude: `~/.claude/settings.json` for minimal OpenRig command allowlisting
|
||||
- global Claude state: `~/.claude.json` for managed workspace trust and onboarding completion
|
||||
- project Claude: `.claude/settings.local.json` for context collector/activity hooks and selected `claude_settings_fragment` resources
|
||||
- project Claude MCP: `.mcp.json` for selected `claude_mcp_fragment` resources
|
||||
- global Codex: `~/.codex/config.toml` for workspace trust and selected `codex_config_fragment` resources
|
||||
Before setup or managed launch, review [what OpenRig changes on your machine](#what-openrig-changes-on-your-machine), including provider trust, hooks and selected runtime resources.
|
||||
|
||||
Already-running adopted sessions may need restart before they pick up newly written runtime config.
|
||||
|
||||
**For agents:** Ask the user whether they want core setup (`rig setup`) or the fuller workstation path (`rig setup --full`) before choosing the invocation. Inspect the result with `--json` and use `rig doctor` to finish any remaining machine-specific issues.
|
||||
**For agents:** First ask which available provider(s) the user wants, reusing an explicit choice. Recommend the working account they already have and follow the selected-provider guide above. Offer core setup (`rig setup`) or the fuller workstation path (`rig setup --full`) only when they want those additional tools. Inspect the result with `--json` and use `rig doctor` to finish any remaining machine-specific issues. When OpenRig itself misbehaves, read [docs/reference/help.md](docs/reference/help.md) (installed agents can run `rig context get help`; the same text is at [openrig.dev/help/agents](https://www.openrig.dev/help/agents)): it covers the next step, known problems, and how to reach the team if you're still stuck.
|
||||
|
||||
## Comparison with Claude Managed Agents
|
||||
|
||||
Anthropic shipped Claude Managed Agents — a cloud-hosted, Claude-only runtime at $0.08/session-hour. OpenRig is the local side: open source, cross-harness, runs on your machine, costs nothing.
|
||||
OpenRig is open source and self-hosted, with Claude Code and Codex in the same team. You operate it on your own infrastructure; the selected providers' model usage costs still apply.
|
||||
|
||||
[Full comparison](https://openrig.dev/compare/claude-managed-agents)
|
||||
|
||||
## Links
|
||||
|
||||
- **Website**: [openrig.dev](https://openrig.dev)
|
||||
- **Blog**: [Why I Built OpenRig](https://esoteric.run/blog/why-i-built-openrig)
|
||||
- **Docs**: [openrig.dev/docs](https://openrig.dev/docs)
|
||||
- **Docs**: [openrig.dev/docs](https://openrig.dev/docs) ([documentation index for agents](https://openrig.dev/llms.txt))
|
||||
- **Blog**: [openrig.dev/blog](https://openrig.dev/blog) · [Why I Built OpenRig](https://esoteric.run/blog/why-i-built-openrig)
|
||||
- **Open Specification**: [openrig.dev/specs](https://openrig.dev/specs)
|
||||
- **Twitter**: [@_feralmachine](https://twitter.com/_feralmachine)
|
||||
- **Videos**: [youtube.com/@openrig](https://www.youtube.com/@openrig)
|
||||
- **X**: [@_feralmachine](https://twitter.com/_feralmachine)
|
||||
- **Follow the project**: [openrig.dev/follow](https://openrig.dev/follow)
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#mvschwarz/openrig&Date)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# Security policy
|
||||
|
||||
## Supported versions
|
||||
|
||||
Only the latest published version on npm (`@openrig/cli`) is supported. Fixes are developed on
|
||||
`main` and released after verification; a merged fix is not necessarily in the published package.
|
||||
Include the affected version even when reporting a problem in an older installation.
|
||||
|
||||
## What OpenRig touches
|
||||
|
||||
OpenRig runs a local daemon, drives coding agents in tmux sessions on your machine, and writes
|
||||
configuration for those harnesses (for example under `~/.claude` and `~/.codex`). The README
|
||||
section [What OpenRig changes on your machine](README.md#what-openrig-changes-on-your-machine)
|
||||
describes these effects and the trust/permission choices. Unexpected access, disclosure or
|
||||
permission changes are useful reports; include what you expected and what you observed.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Please do not open a public issue for a vulnerability.
|
||||
|
||||
Open the repository's [Security page](https://github.com/mvschwarz/openrig/security). If
|
||||
**Report a vulnerability** is available, use it to send a private report to the maintainers.
|
||||
The reporter and authorized advisory collaborators can also see that report; it is not public.
|
||||
Availability depends on the repository setting, not on this file being present.
|
||||
|
||||
If that option is unavailable, ask in [Discussions Q&A](https://github.com/mvschwarz/openrig/discussions/categories/q-a)
|
||||
for a private security contact. Post only the request for a channel: do not include the
|
||||
vulnerability details, reproduction, logs, credentials or affected private systems in public.
|
||||
Wait for the private channel before sharing the report.
|
||||
|
||||
Include: the OpenRig version, the harnesses involved, the steps to reproduce, and what an attacker
|
||||
could do. A proof of concept is welcome; a working exploit against a third party's machine is not.
|
||||
|
||||
## What to expect
|
||||
|
||||
- We will acknowledge the report and coordinate assessment and next steps privately.
|
||||
- Assessment and fix timing depend on impact and reproduction; no fixed deadline is promised.
|
||||
- We will agree on disclosure and credit with you. You can ask to remain anonymous.
|
||||
|
||||
## Scope notes
|
||||
|
||||
- OpenRig is designed for your own machine or a trusted private network, for you and people you trust, not for the
|
||||
open internet. We assess reports by their practical impact on a user in that setup. Exposing an install to the open
|
||||
internet isn't a supported deployment. OpenRig's own gateway integrations with outside services, such as Slack, and
|
||||
anything that lets an untrusted party reach the daemon or an agent are in scope.
|
||||
- OpenRig assumes the machine and the accounts it runs under are trusted by their owner. Reports
|
||||
that require a hostile local user with the same account are still welcome but are unlikely to be
|
||||
treated as high severity.
|
||||
- A harness carrying out an intentionally authorized operation is not by itself evidence of an
|
||||
OpenRig vulnerability. Reports of OpenRig bypassing a chosen permission, changing trust
|
||||
unexpectedly, or routing data or actions to the wrong destination are in scope.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"url": "https://context7.com/mvschwarz/openrig",
|
||||
"public_key": "pk_HcRwWEoFBsmU1mo5VIvWw",
|
||||
"projectTitle": "OpenRig",
|
||||
"description": "OpenRig is an open-source multi-agent harness: define a team of Claude Code and Codex agents in YAML, boot it with the rig CLI, and coordinate the agents through tmux sessions, a durable queue and a local daemon.",
|
||||
"folders": [],
|
||||
"excludeFolders": [
|
||||
"docs/releases",
|
||||
"packages/daemon/specs",
|
||||
"packages/daemon/test",
|
||||
"packages/test-system",
|
||||
"skills/_canonical/process",
|
||||
"skills/_canonical/pm",
|
||||
"spike",
|
||||
"docker",
|
||||
"**/node_modules",
|
||||
"**/fixtures"
|
||||
],
|
||||
"excludeFiles": [],
|
||||
"rules": [
|
||||
"Install with npm install -g @openrig/cli. OpenRig requires Node.js 22 or 24 and tmux; the first-project starter also requires an authenticated Codex CLI.",
|
||||
"Preview before launching: rig specs preview <spec>, then rig up <spec> --cwd . --plan, then rig up <spec> --cwd .",
|
||||
"Check seat readiness with rig ps --nodes --rig <rig>, not only daemon health from rig status.",
|
||||
"Inside a rig, an agent runs rig whoami --json first and treats its output as its identity.",
|
||||
"rig send <session> \"message\" types into a peer's terminal and presses Enter; rig capture <session> reads that peer's screen.",
|
||||
"Work another agent must act on goes in the queue (rig queue create or rig queue handoff); a message informs but does not assign work.",
|
||||
"Use rig start to bring rigs back after a reboot or daemon restart.",
|
||||
"Run rig --help for the current command surface; the CLI is authoritative over older examples."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Thin CI layer over the EXISTING installed-package testbed. No credentials,
|
||||
# host mounts, provider binaries or additional package installation.
|
||||
ARG TESTBED_IMAGE
|
||||
FROM ${TESTBED_IMAGE}
|
||||
COPY runner.mjs /opt/openrig-testbed/runner.mjs
|
||||
COPY scenarios/ /opt/openrig-testbed/scenarios/
|
||||
CMD ["node", "/opt/openrig-testbed/runner.mjs", "healthy"]
|
||||
@@ -1,14 +1,4 @@
|
||||
# openrig-testbed base image — the digest-pinned LTS-slim Linux (plan §1: "digest-pinned, not
|
||||
# tag-floating — byte-reproducible builds; the pin recorded in the image manifest").
|
||||
#
|
||||
# UNRESOLVED SLOT — resolve HOST-SIDE (locus ruling, the L0 build runbook). The VM seat has no
|
||||
# container runtime and cannot pull/inspect a real digest, so this is a host-resolved input:
|
||||
#
|
||||
# docker pull debian:bookworm-slim
|
||||
# docker inspect --format '{{index .RepoDigests 0}}' debian:bookworm-slim
|
||||
#
|
||||
# then replace this comment block with the single resulting reference, e.g.
|
||||
# debian:bookworm-slim@sha256:<64-hex>
|
||||
#
|
||||
# Exactly one digest-pinned ref. Until resolved, the build verb REFUSES to build (no tag-floating
|
||||
# base) — readBaseImage() loud-fails on this comment-only slot.
|
||||
# Docker Hub library/debian bookworm-slim OCI index, resolved 2026-09-29.
|
||||
# Registry response bytes SHA256 verified against Docker-Content-Digest.
|
||||
# amd64 and arm64 are present; image execution is verified in CI, not by this pin.
|
||||
debian:bookworm-slim@sha256:3783cc01769c7b2b1b83a5c5ad96c815348e28ed7da68e2e3687004faa906251
|
||||
@@ -86,6 +86,13 @@ Routes import these; services are Hono-free:
|
||||
- **`outbox-handler.ts`** — sender-side outbox: idempotent record, mark
|
||||
delivered/failed, list. Emits no event-bus events (pure audit).
|
||||
|
||||
The standing detector in `queue-stuck-sweep.ts` creates findings through the
|
||||
queue repository, with `evidenceRef: rig queue show <source-qitem-id>` pointing
|
||||
to the underlying durable work row. This satisfies the existing human-route
|
||||
evidence contract without changing destination resolution: an admitted finding
|
||||
can still be unroutable. Repeated detections refresh the existing finding;
|
||||
when the source condition resolves, the sweep closes its own finding.
|
||||
|
||||
## 3. The hot-potato closure contract (where queue closure is enforced)
|
||||
|
||||
`hot-potato-enforcer.ts` is the pure validator for the load-bearing API
|
||||
|
||||
@@ -128,8 +128,9 @@ artifacts in `<slice>/proof/`:
|
||||
the muted outside-slice branch, never inlined), video by link + poster.
|
||||
Exclusive-create through the allowlist-governed atomic write service;
|
||||
re-invocation is an idempotent no-op; a failed render never un-approves.
|
||||
The freeze moment also folds the mission brief spine (`brief-spine.ts`)
|
||||
into `MISSION_BRIEF.md`, section-scoped, schema-order-preserving.
|
||||
Legacy missions carrying `MISSION_BRIEF.md` may still receive the
|
||||
section-scoped brief-spine fold; new scaffolds do not create that retired
|
||||
web-UI-era file.
|
||||
- **Media serving** for review evidence is ranged on BOTH asset route
|
||||
families (iOS-Safari-class players require `206`): `GET /api/files/asset`
|
||||
(`routes/files.ts` — single-range parse, `206`/`416`, `Accept-Ranges:
|
||||
|
||||
@@ -67,9 +67,10 @@ in-memory cache at daemon scope.
|
||||
`ContextPackLibraryService.scan()` walks roots, parses each
|
||||
`manifest.yaml`, replaces the in-memory index; collisions are
|
||||
**last-wins** in discovery order (workspace > user_file > builtin).
|
||||
Startup wires three roots: builtin (`../context-packs`, first), user-file
|
||||
(`~/.openrig/context-packs`), and workspace-local
|
||||
`<workspaceRoot>/.openrig/context-packs` when present + distinct. A pack is
|
||||
Startup wires the builtin root (`../context-packs`, first), the configured
|
||||
user-file root (`context.root`, default `$OPENRIG_HOME/context`), its
|
||||
`system/` child as the canonical System World root, and the workspace-local
|
||||
`<workspaceRoot>/.openrig/context-packs` root when present + distinct. A pack is
|
||||
addressed by its path-like **ref** (e.g. `packs/compaction-restore`), which is
|
||||
its unique identity; the opaque entry id is `context-pack:<ref>` (a UI routing
|
||||
key). Colon-id `context-pack:<name>:<version>` addressing and the `/library/:id`
|
||||
@@ -79,8 +80,8 @@ ordinary files only and rejects context-pack expansion; compose a durable ref
|
||||
first, then use a dedicated delivery verb when delivery is intended.
|
||||
|
||||
> Source: `domain/context-packs/context-pack-library-service.ts:59-94`
|
||||
> (scan; last-wins L79-81), `:31-33` (id); `startup.ts:481-502` (3-root
|
||||
> IIFE; builtin `unshift` L497, workspace L491-493) @HEAD.
|
||||
> (scan; last-wins L79-81), `:31-33` (id); `startup.ts`
|
||||
> (`contextPackLibrary` construction) @HEAD.
|
||||
|
||||
The parser is pure (no fs; caller passes raw YAML). It rejects malformed
|
||||
YAML, missing `name`/`version`, non-array `files`, per-file path traversal
|
||||
|
||||
@@ -97,7 +97,7 @@ present @HEAD.
|
||||
2. Check mid-work state (unless `--force`) — re-confirmed
|
||||
`session-transport.ts:136-141` (`findPatternEvidence(recentLines,
|
||||
MID_WORK_PATTERNS)`); legacy mid-work check at `:666`.
|
||||
3. Two-step tmux send: `send-keys -l` → ~200ms delay → `C-m`
|
||||
3. Two-step tmux send: a unique file/buffer pasted with `paste-buffer -d -r -p` at every payload size → ~200ms delay → separate `C-m`. Bracketed paste preserves multiline input in supporting TUIs; the payload never enters a shell argument. A successful paste proves transport execution, not runtime consumption.
|
||||
(`session-transport.ts:717` submits `C-m`).
|
||||
4. Optional `--verify`: capture post-send pane, check message visibility
|
||||
(`session-transport.ts:305` `verify?`; `:694` `if (opts?.verify)`).
|
||||
|
||||
@@ -342,19 +342,44 @@ and the flow resumes from where it stopped.
|
||||
settings key): target resolution = spec per-class → spec default →
|
||||
host dynamic key → ORCHESTRATOR-FIRST (the declared orchestrator
|
||||
role via the same `preferred_targets` pick step owners use) →
|
||||
`human@host` never-lost fallback. THE TIER SPLIT: `human-gate` rides
|
||||
registered-human selection (`workflow-human-destination.ts`). THE TIER SPLIT: `human-gate` rides
|
||||
ONLY human-routed positions — an orchestrator-routed item carries
|
||||
the ordinary tier so the shipped attention union (which matches on
|
||||
tier regardless of destination) never leaks it into NEEDS-YOU. The
|
||||
shipped attention predicate is untouched.
|
||||
- **Class (a) born-in-txn**: the projector's failed-terminal branch
|
||||
creates the item INSIDE the failing transaction — no window where
|
||||
the instance is failed and no item exists; a gate-rejected routed
|
||||
destination re-creates on `human@host` (never lost, never fails the
|
||||
close). **Class (b) at detection**: the boot sweep and the keepalive
|
||||
- **Class (a) born-in-txn**: serial failure and unhandled dependency-branch
|
||||
failure share exception admission. The failed packet, dependency occurrence,
|
||||
owning item and staged wake commit together, even while an unrelated frontier
|
||||
keeps the instance active. Mapped remediation remains ordinary workflow work;
|
||||
an unmapped or max-hop failure requires an owner. An agent destination rejected
|
||||
as an unknown rig tries registered-human selection. Selection, admission,
|
||||
or storage failure rolls back the close; it never commits a phantom
|
||||
human alert. Other errors retain their original diagnosis. **Class (b) at detection**: the boot sweep and the keepalive
|
||||
evaluation call the injected ensurer (`workflow-exception-
|
||||
escalation.ts`) — occurrence-deduped against OPEN items by tag
|
||||
query; the crash-surviving sweep re-creates a missed item.
|
||||
query, scoped to the exact workflow, instance and packet; the crash-surviving
|
||||
sweep re-creates a missed item. Normal projection, keepalive (including
|
||||
healthy/terminal returns), and boot (including completed instances) reconcile
|
||||
each overdue item's own packet. A resolved wait, completion or obsolete
|
||||
frontier packet closes only that occurrence with a retained transition;
|
||||
an overdue sibling stays open. Unknown provenance is retained. A later
|
||||
overdue episode can create a fresh item even when it reuses the same packet.
|
||||
- **Human selection and failure**: the existing `workspace.operator_seat_name`
|
||||
setting selects a registered human; when unset, exactly one registered human
|
||||
is required. No `human@host` alias is invented and no arbitrary choice is made
|
||||
among several humans. The setting and registry are read at fallback time,
|
||||
so a valid configured agent route needs no human registry. Missing, ambiguous,
|
||||
invalid or unavailable selection produces `workflow_human_destination_unavailable`
|
||||
(HTTP 409 for failed projection). Fix the registration/selection and retry.
|
||||
Detection-time admission failures are logged by boot and included in the
|
||||
keepalive's existing owner nudge and evaluation notes; later detection retries.
|
||||
A registered destination still uses the gateway's ordinary delivery rules and
|
||||
receipt ledger. Admission is not proof of posting or readership.
|
||||
A capability inventory or detector instance-binding read failure propagates
|
||||
instead of becoming a no-match:
|
||||
failed projection returns HTTP 500 and rolls back its packet, instance and
|
||||
history writes; overdue detection surfaces the read error without an exception
|
||||
item. An evidenced no-match still uses registered-human selection.
|
||||
- **`resume`** (`POST /api/workflow/:id/resume`, runtime `resume()`,
|
||||
`rig workflow resume`): redrive semantics in ONE scribe transaction —
|
||||
failed→active REBOUND to the recorded failed step; the owner is
|
||||
@@ -362,7 +387,12 @@ and the flow resumes from where it stopped.
|
||||
stale destination — resume is the one sanctioned re-resolution
|
||||
point); `--decision` lands durably in the redrive packet; the
|
||||
occurrence's open items close with provenance; the trail is
|
||||
preserved, never rewritten. THE LIVELOCK RAIL (migration 051):
|
||||
preserved, never rewritten. For a dependency graph, `--occurrence <failed-qitem-id>`
|
||||
selects the episode (required with several unresolved failures). Its resolution,
|
||||
redrive packet/wake and exact instance/occurrence exception closure share one
|
||||
transaction; siblings remain intact. An identical occurrence/decision retry
|
||||
returns the existing redrive; changed decision bytes conflict without mutation.
|
||||
THE LIVELOCK RAIL (migration 051):
|
||||
`hops_baseline` re-anchors the max_hops guard at resume so each
|
||||
redrive gets exactly one bounded window; `resume_count` is the
|
||||
recorded redrive fact; re-exceeding raises an honest NEW occurrence.
|
||||
@@ -427,8 +457,8 @@ spec — the self-driving factory's binding substrate. Three seams:
|
||||
no live resolution of future steps; a warming rig instantiates), and
|
||||
resume. The WF-5 exception dial's orchestrator-role position resolves
|
||||
capability-aware on the bound rig at both homes (in-txn class-(a) +
|
||||
detection-time class-(b)), non-throwing with the human@host
|
||||
never-lost fallback. Failures are loud-with-candidates: structured
|
||||
detection-time class-(b)), then uses registered-human selection if no
|
||||
agent resolves. Failures are loud-with-candidates: structured
|
||||
per-candidate disqualifiers + a named zero-candidate message; never a
|
||||
spawn, never auto-`add_member`, never a dead-seat route. Additive
|
||||
`owner_resolution` trail evidence records `{mode, role, boundRig?,
|
||||
|
||||
@@ -217,37 +217,35 @@ walker.
|
||||
|
||||
> Source: `docs/as-built/cli-reference.md:931-941` @HEAD.
|
||||
|
||||
## 3. Default workspace scaffold (0.3.0 spine; 0.3.1 narrative layer)
|
||||
## 3. Default project-workspace scaffold
|
||||
|
||||
`workspaceScaffoldDirs()` / `workspaceScaffoldFiles()` produce the
|
||||
mission-aware default workspace (`~/.openrig/workspace/` or `--root`). The
|
||||
same scaffold is used **idempotently by daemon startup** so a fresh install
|
||||
has a browsable Project workspace before the operator discovers
|
||||
`rig config init-workspace`. Canonical subdirs: `missions/`, `artifacts/`,
|
||||
`evidence/`, `progress/`, `field-notes/`, `specs/`, `dogfood-evidence/`,
|
||||
plus per-mission `missions/<id>/slices/<slice-id>` folders. Drops
|
||||
`README.md` + `STEERING.md` (placeholder) + per-mission `README.md` /
|
||||
`PROGRESS.md` + per-slice `README.md` / `PROGRESS.md` /
|
||||
`IMPLEMENTATION-PRD.md`.
|
||||
repo-ready default workspace (`~/.openrig/workspace/` or `--root`). The same
|
||||
scaffold is used **idempotently by daemon startup**. It emits exactly two
|
||||
directories (`missions/`, `exhaust/`) and four files (`SPEC.md`,
|
||||
`project.yaml`, `workspace.yaml`, `.gitignore`). The catalog points its
|
||||
single default project at `.`; `project.yaml` owns project intent and mission
|
||||
discovery and exposes empty `install.context` / `install.skills` selectors.
|
||||
The ignore file excludes `exhaust/` and local `.openrig/` projection state
|
||||
while keeping authored project context versionable.
|
||||
|
||||
> Source: `domain/workspace/default-workspace-scaffold.ts:44-59`
|
||||
> (`workspaceScaffoldDirs`), `:247-283` (`workspaceScaffoldFiles`),
|
||||
> `WORKSPACE_README` `:82-94`, `STEERING_PLACEHOLDER` `:96-120`;
|
||||
> CLI `packages/cli/src/commands/config-init-workspace.ts:1-16`
|
||||
> (idempotent; `--dry-run`/`--force`; never deletes operator
|
||||
> content L12-16); cli-reference workspace note "See `rig config
|
||||
> init-workspace`" `:939` @HEAD.
|
||||
The workspace owner is called by the shared instance initializer used by CLI
|
||||
daemon start and direct daemon first start. The initializer is additive. It
|
||||
never deletes or overwrites an existing file;
|
||||
the retained `--force` spelling is a compatibility no-op for overwrite
|
||||
behavior. CLI and daemon scaffolds are byte-parity pinned. Mission and slice
|
||||
content is created explicitly through `rig scope`, not seeded by workspace
|
||||
initialization. Instance context, System World, skill source, runtime state,
|
||||
and the retired `artifacts/`, `evidence/`, `progress/`, `field-notes/`,
|
||||
`dogfood-evidence/`, `README.md`, and `STEERING.md` entries are not emitted.
|
||||
|
||||
**0.3.1 narrative layer (slice 21 onboarding-conveyor).** The
|
||||
`getting-started` mission's two slices ship rich teaching content from
|
||||
`GETTING_STARTED_NARRATIVE` (the click-through-to-learn surface) including a
|
||||
`timeline.md` the slice Story tab renders via slice-06's
|
||||
`useSliceTimelineMarkdown` hook; other slices keep the boilerplate.
|
||||
|
||||
> Source: `domain/workspace/default-workspace-scaffold.ts:1-4`
|
||||
> (slice-21 header), `:153-190` (narrative branch in `sliceReadme`),
|
||||
> `:192-200` (`sliceTimeline`), `:270-279` (timeline emit); §0 proof:
|
||||
> `getting-started-narrative.ts` [email protected] ⇒ **0.3.1** @HEAD.
|
||||
> Source: `domain/workspace/default-workspace-scaffold.ts`
|
||||
> (`workspaceScaffoldDirs`, `workspaceScaffoldFiles`); CLI
|
||||
> `domain/instance-initialization.ts`; daemon `index.ts`; CLI
|
||||
> `packages/cli/src/daemon-lifecycle.ts` and
|
||||
> `packages/cli/src/commands/config-init-workspace.ts` (`runInitWorkspace`);
|
||||
> parity: `packages/daemon/test/getting-started-narrative-parity.test.ts`
|
||||
> @HEAD.
|
||||
|
||||
## 4. File-backed missions / slices tree
|
||||
|
||||
|
||||
+494
-22
@@ -23,8 +23,151 @@ Verified against the shipped CLI on 2026-06-15 (v0.3.4) using:
|
||||
|
||||
This document reflects the current `rig` surface as shipped. Where live help text is narrower than the implementation, notes call that out explicitly.
|
||||
|
||||
## Terminal dashboard entry
|
||||
|
||||
The TUI's **Connections** section (`connections` or `:connections`) shows
|
||||
running daemon and launching CLI identity, selected instance settings with
|
||||
source, observed rigs and authored Specs, Slack configuration and running wire
|
||||
state, and registered humans with primary/secondary bindings. `back` restores
|
||||
the work view that opened Connections. The view supplies supported CLI guidance;
|
||||
it does not enable a connector, change settings, or send a test message.
|
||||
|
||||
Connections reads the passive `GET /api/gateway/connections` projection. It
|
||||
selects safe fields from settings, config, the human registry and gateway status;
|
||||
secret values/references and raw errors are omitted. Current config is compared
|
||||
with the wire's activation digest when available. Last Slack verification comes
|
||||
from at most 64 KiB of the existing channel-operation audit tail, matched to the
|
||||
current configuration and labeled with its time/actor. It is historical scope
|
||||
and channel-membership evidence, not current reachability, credential identity,
|
||||
delivery or readership. Missing, failed, incomplete and changed observations
|
||||
remain distinct; an older daemon without the projection reads as unavailable.
|
||||
Use the displayed guidance on that instance; `rig slack verify --json` explicitly
|
||||
contacts Slack. Ordinary navigation/refresh does not.
|
||||
|
||||
`rig tui` opens an independent TUI through the same front door as bare `rig`.
|
||||
`rig tui --shared` requires interactive input/output and a loopback daemon
|
||||
connection. It resolves the kernel's bound `operator.human` terminal and
|
||||
attaches a local tmux client without starting a seat or another TUI. Ctrl-b,
|
||||
then d detaches; reattaching preserves navigation. A missing or ambiguous
|
||||
binding fails with inspection guidance instead of creating another kernel.
|
||||
|
||||
Fresh kernel terminals start `rig tui` automatically. Older terminals, or a
|
||||
terminal where the TUI was quit, retain their shell: run `rig tui` there once.
|
||||
The kernel view uses TUI instance `kernel`; standalone uses its ordinary
|
||||
instance. `rig tui commands --json` exposes the command registry for agent
|
||||
control. In the ordinary shell, Tab completes command names/aliases, section
|
||||
jumps and arguments from the current snapshot. Ambiguous prefixes show candidates;
|
||||
keep typing and press Tab again. No match preserves the text. Enter executes;
|
||||
Escape clears. Free-text filters are not completed. Bracketed paste is text, never
|
||||
an implicit command submission.
|
||||
|
||||
Recent shows ordered, wrapped queue changes. Enter on an event opens the original
|
||||
record (including its raw timestamp); Escape returns to the previous view/scroll.
|
||||
Recorded changes remain attributed claims, including attempts and failures.
|
||||
|
||||
Absolute TUI times use `ui.timezone`, default `America/Los_Angeles`, with native
|
||||
daylight-saving rules. Run `timezone` in the TUI for the current value and settings
|
||||
guidance. `rig config set ui.timezone Europe/London` persists an alternative;
|
||||
`rig config reset ui.timezone` restores the default. Reopen the TUI after changing
|
||||
it. `OPENRIG_UI_TIMEZONE` overrides the file setting. Invalid settings produce a
|
||||
visible fallback notice. Relative ages and stored source timestamps are unchanged.
|
||||
|
||||
See [the first-use journey](../reference/getting-started.md).
|
||||
|
||||
## Daemon shutdown
|
||||
|
||||
`rig daemon stop` sends at most one SIGTERM to a live daemon identified by its
|
||||
local state.
|
||||
An explicit `OPENRIG_URL` that disagrees with that target refuses before signaling.
|
||||
The daemon has one referenced **10-second** budget covering asynchronous service
|
||||
shutdown, connections and recorder drain; repeated SIGINT/SIGTERM joins that stop.
|
||||
The CLI allows **12 seconds** for process exit, then verifies the original PID and
|
||||
listener even if `daemon.json` was removed. A refused listener, a responding
|
||||
listener and an unavailable probe are different outcomes.
|
||||
|
||||
`$OPENRIG_HOME/daemon-shutdown.json` records the PID, shutdown start/completion,
|
||||
phase, failures and `clean|failed|timed-out` outcome. Only successful drains mark
|
||||
the lifecycle record clean. A failed/timed-out drain returns nonzero, including
|
||||
after the process exits. For a recorded target, missing or stale receipt evidence
|
||||
is unverified and also returns nonzero. Failed/unverified stops retain target
|
||||
state for retries; status reads only clean it after a matching clean receipt.
|
||||
An already-exited target uses the same receipt judgment without another signal.
|
||||
With no recorded target and a refused listener, the CLI reports a distinct
|
||||
no-target no-op, not a clean-drain verdict. Unbound incomplete or unreadable
|
||||
local shutdown evidence remains nonzero/unverified rather than being attributed
|
||||
to an unrelated listener.
|
||||
|
||||
Older daemons without the receipt can therefore be proven stopped without their
|
||||
graceful drain being certified. Inspect the receipt and `daemon.log`;
|
||||
do not infer that pending external work completed or was rolled back, or retry it
|
||||
blindly. The bound covers asynchronous waits, not a synchronous event-loop wedge.
|
||||
|
||||
## Human delivery
|
||||
|
||||
`rig gateway human list --json` discovers registered `<entityId>@external`
|
||||
addresses. `rig gateway human show <entityId> --json` includes primary-connector
|
||||
readiness, reason and next inspection. Project policy decides when to contact a
|
||||
human; `messaging-the-human` supplies the transport mechanics.
|
||||
|
||||
Create a human request with `rig queue create --destination <address> --summary
|
||||
"<decision>" --body-file <file> --evidence-ref <ref> --verify --json`. It persists
|
||||
one row before checking the delivery receipt. `posted` proves connector
|
||||
acceptance, never readership; `transport-failed`, `never-posted`, `still-pending`
|
||||
and `indeterminate` preserve the request and name the next inspection. Do not
|
||||
blindly repeat the create. `rig send` is for agent seats.
|
||||
|
||||
`rig slack manifest [--url|--json]` (experimental in 0.6.0) prints the Slack app manifest a user creates
|
||||
their own private Socket Mode app from, offline (no daemon, tokens or network).
|
||||
`--url` is Slack's create-app link with the manifest prefilled; `--json` adds the
|
||||
scope and event lists and why each scope is requested. The same object is served
|
||||
read-only at `GET /api/gateway/slack/manifest` for the TUI Connections page. Setup
|
||||
steps: `docs/reference/slack-app-setup.md`.
|
||||
|
||||
`rig slack enable [--reason <reason>]` seeds existing backlog only on a disabled
|
||||
to enabled transition. Repeating enable does not reseed or restart. `rig slack
|
||||
disable --reason <reason>` requires a shutdown reason. Both return attributed
|
||||
lifecycle receipts; direct requests can name `actor` when no managed-session
|
||||
header exists. Header-derived and claimed identities remain distinct.
|
||||
Configuration, verification and human-binding edits also record actor, reason,
|
||||
prior/result state and effect in
|
||||
`$OPENRIG_HOME/state/human-channel-operations.jsonl`. Each operation has a start
|
||||
and a completion receipt with one ID; a missing completion is indeterminate.
|
||||
Snapshots retain state/digests rather than credentials or message bodies. Local
|
||||
CLI configuration/verification/binding receipts are explicitly `claimed:v1`.
|
||||
|
||||
`rig host pair <url> [--human <address>]` selects the sole registered target
|
||||
human, or requires an explicit selection when several exist. A missing registry
|
||||
or ambiguous recipient refuses before creating an approval. An unset
|
||||
`workspace.operator_seat_name` no longer invents a username-derived kernel
|
||||
seat: Mission Control discovers a single registered human or shows identity
|
||||
uncertainty. Explicit existing seat selections remain supported. Legacy aliases
|
||||
resolve only when their entity is registered; old failed rows are preserved,
|
||||
not implicitly delivered or replayed by registration.
|
||||
|
||||
## Overview
|
||||
|
||||
System Health diagnosis, policy, checkpoints, and dispositions are documented in
|
||||
[Agent-operated System Health diagnosis](../reference/health-diagnosis.md).
|
||||
Diagnosis `show`/`list --json` are summaries; use `--full --json` for the previous
|
||||
complete evidence payload. Their defaults identify omitted fields and the exact
|
||||
expansion command. Workflow human views remain summaries; workflow `--json`
|
||||
continues to return the complete API payload.
|
||||
|
||||
For raw JSON evidence files, CLI read defaults cannot intercept `cat` or a Node
|
||||
print. Inspect size and keys before selecting needed fields; retain full evidence
|
||||
on disk. For example:
|
||||
|
||||
```sh
|
||||
wc -c < receipt.json
|
||||
jq 'keys' receipt.json
|
||||
jq '{gate, judge, cutSha, surfaceCount, overallPackageVerdict}' receipt.json
|
||||
```
|
||||
|
||||
Select the actual fields present in that file. Redirect intentional full CLI
|
||||
output to a file before inspecting selected fields; use `set -o pipefail` when
|
||||
piping a command so a formatter cannot hide its failing exit.
|
||||
|
||||
|
||||
- Binary: `rig`
|
||||
- Top-level command groups: `64`
|
||||
- Output mode: human-readable by default; many commands also support `--json`
|
||||
@@ -116,9 +259,34 @@ Subcommands:
|
||||
- `logs [--follow]`
|
||||
|
||||
Notes:
|
||||
- `start` launches the daemon process and accepts runtime overrides for port, host, and DB path.
|
||||
- `start` additively reconciles the canonical instance layout before launching
|
||||
the daemon process, then accepts runtime overrides for port, host, and DB
|
||||
path. Direct daemon first start uses the same initializer. Existing files are
|
||||
preserved and wrong-type managed paths are refused before any write. See
|
||||
`docs/reference/instance-layout.md`.
|
||||
- Startup uses a local `daemon-start.lock` reservation before instance initialization,
|
||||
shared by `daemon start`, `start`, and `up`. A concurrent supported launch fails
|
||||
without spawning another daemon or running its pre-bind database initialization.
|
||||
Success requires the spawned child's numeric PID from `/healthz` at every required
|
||||
listener and a live child through atomic `daemon.json` publication. Missing or
|
||||
mismatched process identity, an invalid listener plan, child exit, and unresolved
|
||||
probes cannot publish success. Use a matching CLI/daemon pair; a legacy endpoint
|
||||
without PID evidence is insufficient. This local reservation does not serialize
|
||||
old binaries or direct execution of the daemon entrypoint.
|
||||
- Startup checks physical liveness after the synchronous state writer as well as
|
||||
before it; queued child events alone cannot prove that boundary. If publication
|
||||
fails verification, startup rejects and withdraws only state matching this
|
||||
launch's PID, start time, listener and DB. It never removes a replacement owner.
|
||||
- Failed process inspection is uncertainty, not proof that a child exited. Cleanup
|
||||
waits for the owned child's exit evidence; an error event is insufficient.
|
||||
- Failed startup signals only its own child and waits boundedly for its exit. If
|
||||
cleanup cannot be confirmed, the reservation remains and the error names the PID.
|
||||
An interrupted launcher can also leave `daemon-start.lock`. Inspect its recorded
|
||||
launcher/child PIDs, `daemon.json`, and `daemon.log`; only after proving both
|
||||
processes absent, archive that reservation before retrying. There is no timed
|
||||
takeover: a dead launcher may have left a live, unbound child.
|
||||
- `logs` reads daemon log output and can follow it.
|
||||
- **Deploy identity (v0.4.4, OPR.0.4.4.11 FR-6/7)**: a PACKAGED build (built via `scripts/build-package.sh`) is stamped with `{semver, commit, dirty, builtAt}`; the daemon's `/healthz` payload carries the four stamp fields additively and `rig --version` renders `<semver> (<commit8>[, dirty])`. A source/dev run has NO stamp and adds NOTHING (never an invented SHA) — `/healthz` keeps its legacy body and `--version` prints the plain semver. This is the 30-second stale-deploy diagnostic: an unstamped or old-commit `/healthz` on a long-running host means you are looking at an older deployed build, not the source tree. Source: `packages/{daemon,cli}/src/build-info.ts` (`stampFields`), `packages/daemon/src/server.ts` (`/healthz`), `packages/cli/src/version.ts`.
|
||||
- **Deploy identity (v0.4.4, OPR.0.4.4.11 FR-6/7)**: a PACKAGED build (built via `scripts/build-package.sh`) is stamped with `{semver, commit, dirty, builtAt}`; the daemon's `/healthz` payload carries the four stamp fields additively and `rig --version` renders `<semver> (<commit8>[, dirty])`. A source/dev run has no build stamp (never an invented SHA); `/healthz` still reports its runtime PID and `--version` prints the plain semver. This is the 30-second stale-deploy diagnostic: an unstamped or old-commit `/healthz` on a long-running host means you are looking at an older deployed build, not the source tree. Source: `packages/{daemon,cli}/src/build-info.ts` (`stampFields`), `packages/daemon/src/server.ts` (`/healthz`), `packages/cli/src/version.ts`.
|
||||
|
||||
### `rig status`
|
||||
|
||||
@@ -154,6 +322,18 @@ Supported keys:
|
||||
- `transcripts.enabled`
|
||||
- `transcripts.path`
|
||||
- `workspace.root` (and other workspace-rooted paths used by `init-workspace`)
|
||||
- `context.root` (default `$OPENRIG_HOME/context`; env
|
||||
`OPENRIG_CONTEXT_ROOT`) — the single writable addressable context library.
|
||||
The removed `context.packs_root`, config field `context.packsRoot`, and env
|
||||
`OPENRIG_CONTEXT_PACKS_ROOT` are refused with replacement guidance.
|
||||
- `context.system_world` (default `default`; env
|
||||
`OPENRIG_CONTEXT_SYSTEM_WORLD`) — selects
|
||||
`$OPENRIG_HOME/context/system/system-world.yaml`, an explicit replacement
|
||||
manifest path, or the explicit `disabled` state. `rig context work-install`
|
||||
reports the resolved state and provenance.
|
||||
- `skills.root` (default `$OPENRIG_HOME/skills`; env `OPENRIG_SKILLS_ROOT`) — the
|
||||
one authoritative Git-versioned managed skill catalog. An override replaces
|
||||
the default; it does not add an overlay root.
|
||||
- `snapshots.periodic.enabled` (default `true`) — daemon-side periodic snapshot scheduler on/off (v0.3.4)
|
||||
- `snapshots.periodic.interval_seconds` (default `300`) — interval between periodic snapshots
|
||||
- `snapshots.periodic.retention_keep` (default `10`) — number of periodic snapshots to retain per rig
|
||||
@@ -168,7 +348,7 @@ Precedence:
|
||||
|
||||
Notes:
|
||||
- `--with-source` (top-level) and `--show-source` (`get`) report per-key source/default for honest provenance.
|
||||
- `init-workspace` scaffolds the default workspace at `~/.openrig/workspace/` (or the `--root` override) with mission/slice folders. `--force` overwrites scaffolded files (does NOT remove directories); `--dry-run` previews without writing. New in v0.3.0.
|
||||
- `init-workspace` additively scaffolds `missions/`, `exhaust/`, `SPEC.md`, `project.yaml`, `workspace.yaml`, and `.gitignore` at `~/.openrig/workspace/` (or the `--root` override). `--dry-run` previews without writing. `--force` is deprecated compatibility and preserves existing files. New in v0.3.0.
|
||||
- `snapshots.periodic.*` (v0.3.4): the daemon-side scheduler takes periodic snapshots per rig at `interval_seconds` and retains the newest `retention_keep`. At restore time, newest-wins between `auto-periodic` and `auto-pre-down` snapshots.
|
||||
|
||||
Legacy env compatibility: the original runtime keys still accept deprecated
|
||||
@@ -429,7 +609,7 @@ Notes:
|
||||
### `rig snapshot`
|
||||
|
||||
Usage:
|
||||
- `rig snapshot <rigId>`
|
||||
- `rig snapshot <rigId> [--intended-seats <ids>]`
|
||||
- `rig snapshot list <rigId>`
|
||||
|
||||
Subcommands:
|
||||
@@ -437,7 +617,9 @@ Subcommands:
|
||||
|
||||
### `rig restore`
|
||||
|
||||
Usage: `rig restore <snapshotId> --rig <rigId>`
|
||||
Usage:
|
||||
- `rig restore <snapshotId> --rig <rigId>`
|
||||
- `rig restore status <attemptId> --rig <rigId> [--json]`
|
||||
|
||||
Important:
|
||||
- `--rig <rigId>` is required by the source code, even though the help text does not visually mark it as required.
|
||||
@@ -445,6 +627,9 @@ Important:
|
||||
Notes:
|
||||
- Human output prints each restored node and any failed node error.
|
||||
- Non-zero exit if any restored node fails.
|
||||
- A started asynchronous restore prints its attempt id. `status` derives the
|
||||
original and current intended-set verdict, snapshot selection, historical
|
||||
exclusions, and unresolved intended seats from that durable attempt.
|
||||
|
||||
### `rig restore-check`
|
||||
|
||||
@@ -707,13 +892,31 @@ Notes:
|
||||
|
||||
### `rig launch`
|
||||
|
||||
Usage: `rig launch <rigId> [nodeRef] [--seats <ids>] [--hold-reason <reason>] [--json]`
|
||||
Usage: `rig launch <rigId> [nodeRef] [--seats <ids>] [--hold-reason <reason>] [--snapshot-id <id>] [--plan] [--json]`
|
||||
|
||||
Notes:
|
||||
- Launches or relaunches a node in a running rig.
|
||||
- `nodeRef` (optional) can be a logical ID or node ID for the single-target form.
|
||||
- `--seats <ids>` (v0.3.4, slice 11) takes a comma-separated list of logical IDs for node-granular managed partial restore — launch a named subset of seats while holding the rest. Retires the prior `pod_aware_launch_unsupported` dead-end.
|
||||
- `--hold-reason <reason>` records the reason non-target seats are being held; surfaced via observability so the held state is auditable.
|
||||
- `--snapshot-id <id>` selects one exact restore-usable snapshot instead of
|
||||
applying the automatic choice. `--plan` previews a multi-seat subset and its
|
||||
non-target effects without mutation.
|
||||
- `rig launch <rigId> <nodeRef> --retry-startup-from <member-file> --rig-root <absolute-source-root>`
|
||||
explicitly retries an added agent whose first start failed during resource
|
||||
projection, before a native conversation began and before startup context was
|
||||
saved. First correct the projection failure, exit the failed shell normally,
|
||||
and use `rig seat clean <seat> --reason <reason>` after it is stopped. Supply
|
||||
the original bare or `{member: ...}` YAML/JSON fragment, without edges or
|
||||
member startup/continuity overrides. The agent source hash and retained
|
||||
identity, model, cwd and policy must agree. The retry uses normal validation,
|
||||
projection and required startup delivery on the same node; it preserves other
|
||||
seats and prior failures. It refuses bound, live, indeterminate or previously
|
||||
native sessions. This is not snapshot restore or a substitute for deliberate
|
||||
fresh launch, and cannot combine with snapshot, subset or plan options.
|
||||
The operator is responsible for supplying the complete original fragment:
|
||||
retained state cannot reconstruct missing member, pod or rig instructions.
|
||||
Never strip unsupported overrides to make a retry pass.
|
||||
|
||||
### `rig remove`
|
||||
|
||||
@@ -732,6 +935,22 @@ Notes:
|
||||
|
||||
## Identity, Communication, and Context
|
||||
|
||||
### `rig startup-proof submit`
|
||||
|
||||
Usage: `rig startup-proof submit --challenge-id <id> --answer <answer> [--json]`
|
||||
|
||||
Submits the selected startup exercise through the authenticated activity hook,
|
||||
using the current seat's identity and the challenge supplied in its startup
|
||||
prompt. A correct current answer returns `oriented: verified`; a bare
|
||||
acknowledgement, wrong answer, or stale challenge fails. Startup readiness alone
|
||||
does not verify orientation.
|
||||
|
||||
An extra exercise is opt-in through a `startup_proof` startup action with
|
||||
`value: authenticated` and `idempotent: true`. Omission adds no exercise, and a
|
||||
later applicable `value: none` selects lean startup. See
|
||||
[startup proof selection](../reference/rig-spec.md#startup-proof-selection)
|
||||
for the full authoring and restore rules. Terminal nodes receive no challenge.
|
||||
|
||||
### `rig whoami`
|
||||
|
||||
Usage: `rig whoami [--node-id <id>] [--session <name>] [--host <id>] [--full | --verbose] [--json]`
|
||||
@@ -806,8 +1025,13 @@ Usage: `rig walk <seat> --through <ref | files...> [--pace <duration>] [--json]`
|
||||
|
||||
Notes:
|
||||
- `--through` accepts either one path-like context ref or an ordered list of existing local files; mixing the two forms is rejected.
|
||||
- Sends one piece at a time through the normal transport and waits `--pace` between pieces (default `10s`; `500ms`, `10s`, and bare seconds are accepted). There is no trailing delay.
|
||||
- Sends one piece at a time through the normal transport and waits `--pace` between pieces (default `10s`; duration overrides require an explicit `ms` or `s` suffix). The same explicit-unit grammar applies to `--consume-timeout`, `--consume-poll`, and `--turn-timeout`; bare numbers are refused. There is no trailing delay.
|
||||
- A missing local file or missing/unreadable ref member aborts before the first send, so a walk delivers every piece or none.
|
||||
- Where a generation record resolves, each piece must appear as a complete user message in the newly appended record and its corresponding native turn must close before the next piece. Only CRLF line endings and surrounding whitespace are normalized; internal whitespace, missing middles, shared prefixes, and tails do not qualify.
|
||||
- Claude closure follows the message's UUID ancestry through an assistant response to `turn_duration`. Codex uses a `response_item` user message within a named `task_started`/`task_complete` turn; queued input or a different turn's completion is insufficient. These receipts prove delivery and turn completion, not semantic comprehension.
|
||||
- Codex generation lookup joins the bound process's post-start log thread IDs to retained native CLI conversations. Auxiliary title threads cannot replace that identity; zero or multiple matching conversations remain unverified instead of being selected by recency.
|
||||
- Codex records resolve through the verified current pane/process and native thread table, including before token telemetry exists. The rollout header must identify that thread. Missing or ambiguous identity is reported as unverified. A generation/file replacement or unreadable record during a verified walk aborts it; it never silently continues into a replacement occupant.
|
||||
- `--json` reports `consumptionVerified`. If the initial generation-record probe is unavailable, legacy delivery remains possible with an explicit unverified advisory and `consumptionVerified: false`.
|
||||
|
||||
### Cross-host execution (`--host <id>`)
|
||||
|
||||
@@ -816,6 +1040,21 @@ commands use single-hop SSH CLI-side shell-out; HTTP-transport commands talk to
|
||||
the remote daemon API. The local daemon is not involved in SSH routing. The
|
||||
remote host is expected to have its own managed `rig` available on `$PATH`.
|
||||
|
||||
HTTP sender attribution uses the originating instance's persisted self-host
|
||||
identity, read without creating or changing its database. Explicit DB config
|
||||
wins; otherwise the last daemon launch's DB selection is retained. This works
|
||||
while the local daemon is stopped. The destination's identity and the configured
|
||||
display name are never used as the origin.
|
||||
|
||||
Ordinary local requests retain bare seat names. For an explicit `OPENRIG_URL`
|
||||
(or legacy `RIGGED_URL`), a bounded health probe preserves bare addressing only
|
||||
when the target's self-host identity matches the local one. A different,
|
||||
unavailable, or ambiguous target—including a loopback forwarding endpoint—gets
|
||||
the known origin suffix. Already-qualified senders remain unchanged. If the
|
||||
local origin cannot be read, delivery proceeds with `origin-unknown:v1`
|
||||
provenance and a diagnostic after a successful response; queue forwarding
|
||||
preserves that uncertainty rather than attributing the sender to the relay.
|
||||
|
||||
Hosts are declared by the operator in `~/.openrig/hosts.yaml`:
|
||||
|
||||
```yaml
|
||||
@@ -1051,16 +1290,18 @@ Subcommands:
|
||||
- `fallback <qitemId> --destination <session> [--reason <text>] [--json]` — reroute to fallback seat
|
||||
- `show <qitemId> [--json]`
|
||||
- `transitions <qitemId> [--json]` — append-only transition log
|
||||
- `list [--destination <session>] [--source <session>] [--mine] [--state <csv>] [-a | --all] [-A | --all-rigs] [--full] [-o <json|wide>] [--limit <n>] [--json]` — **v0.4.0 grammar (slices 28 + 32, docker / kubectl-aligned)**:
|
||||
- `list [--destination <session>] [--source <session>] [--owned] [--mine] [--state <csv>] [-a | --all] [-A | --all-rigs] [--full] [-o <json|wide>] [--limit <n>] [--json]` — **v0.4.0 grammar (slices 28 + 32, docker / kubectl-aligned)**:
|
||||
- **`rig queue list`** (no flags) → active states only (`pending` / `in-progress` / `claimed` / `blocked` / `handed-off`; NOT `done` / `canceled`), **current-rig** breadth, compact rows: `qitemId`, `state`, `source→destination` (or `current-owner`), `closure_reason` / `closure_target` (when handed-off / blocked), `mission`, `slice`, `tier` / `priority`, `age` / `updated_at`, short title, capped tags. Excludes: full body, chain_of_record, transition history, proof / artifact blobs.
|
||||
- **`-a` / `--all`** → include closed / done history within the current breadth (docker `-a` axis: history).
|
||||
- **`-A` / `--all-rigs`** → cross-rig breadth (kubectl `-A` axis: breadth). Composable with `-a`, `--mine`, `--full`.
|
||||
- **`-A` / `--all-rigs`** → cross-rig breadth (kubectl `-A` axis: breadth). Composable with `-a`, `--owned`, `--mine`, `--full`.
|
||||
- **`--full`** → add body + chain-of-record + full tags + transition history to whichever scope is selected (field-breadth axis).
|
||||
- **`-o json|wide`** → encoding, compact-by-default. `-o json` does NOT imply full body (compact JSON is token-safe + machine-parseable); `--full -o json` returns the full JSON.
|
||||
- **`--mine`** → just the caller's items.
|
||||
- **`--owned`** → obligations assigned to the caller (`destination_session` only).
|
||||
- **`--mine`** → the caller's source-or-destination union, including rows the caller authored but does not own.
|
||||
- Pipeline use must enable `set -o pipefail`; otherwise the shell reports only the downstream formatter's status and can mask a nonzero `rig` read such as a timeout.
|
||||
- `--destination <s>` / `--source <s>` / `--state <csv>` keep working and compose with the new flags.
|
||||
- The four axes (scope × history × field-breadth × encoding) are orthogonal and composable. The bare unscoped firehose that aggregated cross-rig + full-history (~64,000 tokens on the live host) is retired as a default — opt-in via `-A -a --full`.
|
||||
- Use `rig queue show <qitemId>` for the full single-item view (kubectl `describe` / docker `inspect` pattern).
|
||||
- Use `rig queue show <qitemId>` for the body preview; `rig queue show <qitemId> --full --json` returns the original complete record. Preview JSON adds `readView` with completeness, omitted content, full JSON byte size and the exact expansion command.
|
||||
- `overdue [--json]` — in-progress qitems past closure_required_at
|
||||
- `inbox-drop <destinationSession> --sender <session> (--body <text> | --body-file <path>) [--tags <csv>] [--urgency <u>] [--audit <pointer>] [--id <inboxId>] [--json]`
|
||||
- `inbox-absorb <inboxId> --receiver <session> [--json]` — promote a pending inbox entry to a queue_item
|
||||
@@ -1140,15 +1381,16 @@ Usage: `rig project <subcommand>` — L2 agent-backed classifier with daemon-enf
|
||||
|
||||
Subcommands:
|
||||
- `lease-acquire [options]` — acquire the active classifier lease for the caller.
|
||||
- `lease-heartbeat [options]` — send a heartbeat for an active classifier lease (extends TTL).
|
||||
- `lease-heartbeat [options]` — send a heartbeat for an active classifier lease (extends TTL). A lease already past its TTL refuses with `lease_expired`; acquire again instead.
|
||||
- `lease-show [options]` — show the currently-active classifier lease.
|
||||
- `reclaim-classifier [options]` — operator verb to reclaim the active classifier lease. Use `--if-dead` to refuse if holder is still alive.
|
||||
- `classify <streamItemId> [options]` — project a stream item with classification fields. Idempotent on `stream_item_id`; requires an active lease.
|
||||
- `list [options]` — list project classifications with filters.
|
||||
- `classify <streamItemId> [options]` — project a stream item with classification fields. Idempotent on `stream_item_id` (first write wins). Requires `--lease-id`: the result is refused (`lease_mismatch`, `lease_expired`, `lease_held`) unless that exact lease is active, unexpired and held by `--session` at write time. Optional `--area`, `--scope-ref` (with `--candidate-set-version`), `--duplicate-of`, `--needs-human true|false` (omit when unknown), `--classifier-version`, `--taxonomy-version`, `--attempt-id` with `--execution-id`. Without `--attempt-id` the result is a manual classification, not bound to the attempt ledger. Every supplied field must be a string (IDs and versions non-empty); `--needs-human` omitted means unknown.
|
||||
- `list [options]` — list project classifications with filters (`--session`, `--destination`, `--area`, `--scope-ref`, `--needs-human true|false|unknown`).
|
||||
- `show <projectId> [options]` — show one project classification.
|
||||
|
||||
Notes:
|
||||
- Lease semantics: only one classifier holds the active lease at a time. Heartbeats extend TTL; lease expiry frees the slot for the next acquirer.
|
||||
- Lease semantics: only one classifier holds the active lease at a time. Heartbeats extend TTL only while the lease is unexpired. Re-acquiring your own expired lease issues a new lease id, so results bound to the old id are refused; another session must use `--evaluate-deadness-first` or the reclaim verb.
|
||||
- Attempt ledger (daemon HTTP, used by a classifier occupant): `POST /api/projects/attempts/begin`, `/attempts/:id/abstain`, `/attempts/:id/fail`, `GET /api/projects/eligible`. Abstentions and errors are recorded there, never in the classification row; errors retry with bounded backoff, then end `exhausted`. Every `begin` (including a timeout or error retry) returns a new `executionId`; only the current one can abstain, fail or bind a result (`attempt_superseded` otherwise), because a renewable lease never proves an older execution stopped.
|
||||
- `classify` enforces L1→L2 foreign-key existence: the referenced `stream_items` row must exist or the call is rejected before any state mutation.
|
||||
- `reclaim-classifier` is the operator-side verb to recover from a hung classifier; `--if-dead` adds a liveness guard so a still-heartbeating classifier is not stolen from.
|
||||
|
||||
@@ -1191,16 +1433,206 @@ Phase D extends the policy enum with `workflow-keepalive` (the policy deferred f
|
||||
### `rig workflow`
|
||||
|
||||
- `validate <specPath>` — validate a workflow spec file; returns structured ok/error report (role resolution, step uniqueness, allowed-exits consistency, optional seat liveness). Spec-only: it takes no rig context (OPR.0.4.6.FAC1 arch ruling — rig-coverage checks happen at instantiate).
|
||||
- `compile <missionPath> [--operation-key <key>]` — read `project.yaml` →
|
||||
`mission.yaml` → `slice.yaml` into an inspectable lifecycle graph without
|
||||
creating a cached spec, workflow instance, or qitem.
|
||||
- `instantiate-lifecycle <missionPath> --operation-key <key> --root-objective
|
||||
<text> --created-by <session>` — compile and start an eligible lifecycle;
|
||||
`--entry-owner` and `--rig` retain their normal workflow meanings. The opaque
|
||||
operation key makes exact replay idempotent and conflicting reuse refuses.
|
||||
- `instantiate <specPath> --root-objective <text> --created-by <session>` — create a new instance + entry-step qitem in the same daemon transaction; `--entry-owner <session>` overrides the default entry owner. **OPR.0.4.6.FAC1**: `--rig <name>` binds the instance to a rig (overrides the spec's `target.rig` DEFAULT; persists as `boundRig` on the instance, rendered by `show`/`trace` and carried in `--json`). On a bound instance, a role with **no `preferred_targets`** resolves to a live capable SEAT on that rig by the pure capability policy (running agents declaring the role, managed seats only, required runtime, least pending backlog, deterministic coordinate tiebreak). Unknown rig = structured `bound_rig_unknown`; a bound-rig role no seat structurally declares = `bound_rig_role_uncovered` (existence at any lifecycle state satisfies it — liveness is checked when the step projects). No `--rig` and no spec default = unbound, byte-identical pre-FAC-1 behavior.
|
||||
- `run <specPath> …` — accepts the same `--rig <name>` binding (run instantiates too).
|
||||
- `project --instance <id> --current-packet <qitem-id> --exit <handoff|waiting|done|failed> --actor-session <session>` — close the current packet AND project the next-step packet IN THE SAME daemon transaction (transactional-scribe; lost handoffs impossible by design). `--result-note <text>`, `--blocked-on <ref>`, `--next-owner <session>` modify behavior.
|
||||
- `list [--status <s>]` — list instances; optionally filter by status (`active`/`waiting`/`completed`/`failed`).
|
||||
- `show <instanceId>` — show one instance.
|
||||
- `revise <instanceId>` — inspect authored versus bound lifecycle input without
|
||||
writing. The result names changed sources/steps, composition, compatibility,
|
||||
and an apply command containing the inspected version, digest and operation
|
||||
key. Fill in the actor and decision before using `--apply`.
|
||||
- `operation <key>` — recover the original lifecycle creation or revision
|
||||
receipt and current instance after a lost response or later source edit.
|
||||
- `trace <instanceId>` — show the instance + its append-only step trail (audit-only).
|
||||
- `continue <instanceId>` — idempotent inspector; in v1 returns the current state.
|
||||
|
||||
The owner-as-author + workflow-as-transactional-scribe contract is enforced by the daemon. The owner of a packet decides when it closes; the workflow runtime atomically records the closure AND creates/projects the next qitem per the workflow spec, in a single daemon transaction.
|
||||
|
||||
### Project-owned release profiles
|
||||
|
||||
`project.lifecycle.profile` selects a key in `project.lifecycle.profiles`. Each
|
||||
selected entry contains `required_steps` (a nonempty list of stable obligation
|
||||
IDs) and `workflow` (the ordinary workflow language). The reusable example at
|
||||
[`project-release-profile.yaml`](../reference/project-release-profile.yaml)
|
||||
names nine release-ceremony stages followed by one `release-boundary` judgment.
|
||||
That final judgment covers the seven areas of the canonical
|
||||
[`release-boundary.md`](../reference/release-boundary.md) checklist in one
|
||||
agent-authored record. It does not create seven automatic gates.
|
||||
|
||||
Put the profile in `project.yaml` once; release missions need only their ordinary
|
||||
composition. Customize the example's project ID, roles, and policy for the
|
||||
project. Every profile step declares `depends_on` (including `[]` on roots);
|
||||
conditional `next_hop.on` jumps are refused so required stages cannot be bypassed.
|
||||
Missing required IDs refuse with `lifecycle_required_step_missing`.
|
||||
Prerequisite cycles make compilation ineligible with `dependency_cycle`, even
|
||||
when `loop_guards.max_hops` is set. That guard bounds routing loops; it cannot
|
||||
make mutually dependent steps ready.
|
||||
|
||||
Precedence is explicit:
|
||||
|
||||
- With a selected project graph and no mission workflow, the mission inherits it.
|
||||
- `mission.lifecycle: {profile: <same-profile>, mode: extend, workflow: ...}` adds
|
||||
new steps, merges roles by name, and appends context references. An extension
|
||||
cannot replace a step ID; its workflow accepts only `steps`, `roles`, and
|
||||
`context_refs`.
|
||||
- `mode: override` supplies a replacement workflow. All required project step IDs
|
||||
and the prerequisite relationships between them must remain. Additional
|
||||
intermediate stages are allowed. An omitted or unknown mode with competing
|
||||
workflows refuses with `lifecycle_override_ambiguous`.
|
||||
- Without `project.lifecycle.profiles`, the existing mission-authored workflow
|
||||
takes precedence over slice execution declarations. This legacy path remains
|
||||
supported and reports an advisory that no project graph is selected. Adding a
|
||||
project graph to an existing copied mission workflow requires explicit mode
|
||||
selection or removal of the copy. Project lifecycle fields such as an inert
|
||||
`workflow` or `workflow_ref` are refused rather than silently ignored.
|
||||
|
||||
An authored, ready successor may be a mission extension step depending on
|
||||
`release-boundary`. Without that extension, `release-boundary` exits `done` and
|
||||
finishes the current lifecycle. The runtime creates the continuation packet;
|
||||
the agent judges readiness and performs any authorized activation. It does not
|
||||
infer readiness from a folder or activate future scope automatically.
|
||||
|
||||
Compilation exposes `graphSource` (selection mode, project/mission addresses,
|
||||
required IDs), the complete `workflowSpec`, dependencies, and source digests.
|
||||
These are bound into the compiled input digest and persisted at instantiation.
|
||||
Changing source bytes under an existing operation key refuses. Relative paths
|
||||
passed to both CLI `compile` and `instantiate-lifecycle` are resolved against the
|
||||
**caller cwd before HTTP**, so daemon cwd cannot change their meaning.
|
||||
|
||||
`workflow guidance <instance> [--packet <qitem>] [--component <id>] [--full]`
|
||||
reads current selected SDLC teaching and original SPEC intent. `workflow show`
|
||||
and `workflow continue` expose the same guidance. Entry, handoff, route and resume
|
||||
packets carry a compact preview; existing admitted wait notices refresh it at
|
||||
send time. Healthy waiting does not read or inject the catalog on every tick.
|
||||
|
||||
Selection follows project → mission → explicit active slice (or a legacy slice's
|
||||
exact executable identity). A narrower component list replaces ancestor components
|
||||
and edges; an omitted catalog inherits. Bound member lists alone do not select an
|
||||
active slice. Catalog addresses use the shared H2/H3 resolver: absolute or
|
||||
`$OPENRIG_HOME` paths, paths relative to the declaring manifest, `root: repository`
|
||||
from that manifest's Git tree, or a file declared by the installed context library.
|
||||
Missing/ambiguous components and unavailable sources are named unknowns; prose
|
||||
stays verbatim and has no required semantic field ontology.
|
||||
|
||||
Compact guidance previews one component, using exact owner matches where possible;
|
||||
otherwise it labels a menu preview. Position remains unknown: neither array order,
|
||||
clock nor handoff infers method progress. `--full` expands components relevant to
|
||||
current packet custody (all selected components when none match); `--component`
|
||||
chooses one explicitly. Whole oversized prose blocks are omitted with an expansion
|
||||
notice, so a clipped caveat never masquerades as complete teaching.
|
||||
|
||||
Guidance reports manifest hashes against the lifecycle binding and the current
|
||||
catalog hash. Referenced prose is current authored advice, not a cached executable
|
||||
snapshot. YAML selection edits use the existing revision path; catalog-only prose
|
||||
changes refresh on read without creating work or a new revision store. Reading
|
||||
advice, receiving a notice and independent acceptance remain distinct.
|
||||
|
||||
`workflow show`, read-only `workflow revise`, and the TUI distinguish current,
|
||||
source-only, compatible, incompatible and unavailable authored comparisons.
|
||||
Catalog or membership bytes can change without changing executable steps;
|
||||
file edits do not silently adopt either kind of change. A supported `revise
|
||||
--apply` preserves completed/live steps, required obligations, queue custody and
|
||||
prior receipts while adopting future-step changes on the same instance. It
|
||||
requires the inspected version/digest, a stable operation key, actor and reason.
|
||||
Changed completed/live steps, removed obligations and unsupported migrations
|
||||
refuse with a specific explanation; restore the protected contract and revise
|
||||
unstarted successors. After an ambiguous response, inspect `workflow operation
|
||||
<key>` or retry the identical apply command to recover its one committed effect.
|
||||
|
||||
Mission-bound continuation and wait guidance carries a snapshot of authored
|
||||
planning, wave admission/review and integration rules, with a pointer to inspect
|
||||
the current source before deciding. Waves guide agents; executable dependencies
|
||||
schedule steps. Bound slice sources do not automatically create child workflows.
|
||||
The execution view also reads current arrangement guidance into `planning_guidance`
|
||||
with exact source fields. Ordinary mission/wave/slice inspection keeps admission,
|
||||
review and accepted-core/full-contract guidance distinct from executable edges,
|
||||
attributed proof and current custody; prose does not create those facts.
|
||||
|
||||
`workflow show`/JSON and the TUI execution view show every named obligation with
|
||||
its state and receipt state. Required steps need `project --evidence-ref <ref>`
|
||||
on a successful `done`/`handoff` exit; missing references refuse before mutation
|
||||
with `lifecycle_receipt_required`. Waiting and failed exits remain available.
|
||||
The projection records who supplied the reference and when. `recorded` means an
|
||||
attributed workflow receipt exists, not that the daemon verified its substance.
|
||||
An ordinary terminal queue row never supplies that receipt. Agents inspect the
|
||||
actual evidence, including reasoned not-applicable or deferred boundary areas.
|
||||
Existing typed `acceptance` contracts remain separate and retain their checks.
|
||||
|
||||
### Authored mission boundary (legacy)
|
||||
|
||||
Without a project-owned graph, when a mission declares `lifecycle.workflow`, `compile` uses that explicit
|
||||
workflow instead of deriving steps from active slices. Slice membership and
|
||||
backlinks are still validated and included in provenance; slice SDLC selections
|
||||
do not add workflow steps or gates. `lifecycle.profile` must match the profile
|
||||
selected in `project.yaml`:
|
||||
|
||||
```yaml
|
||||
# Add to mission.yaml; project.yaml already selects release-boundary-v0.
|
||||
lifecycle:
|
||||
profile: release-boundary-v0
|
||||
workflow:
|
||||
context_refs: [SPEC.md, PROGRESS.md, NOTES.md]
|
||||
entry: {role: orchestrator}
|
||||
roles:
|
||||
orchestrator: {preferred_targets: [orch@my-rig]}
|
||||
steps:
|
||||
- id: mission-boundary
|
||||
actor_role: orchestrator
|
||||
objective: Inspect current evidence and decide the next authored exit.
|
||||
allowed_exits: [waiting, done, failed]
|
||||
re_present_after_seconds: 300
|
||||
re_present_max_seconds: 3600
|
||||
```
|
||||
|
||||
The nested `workflow` uses the ordinary workflow language; its ID defaults to
|
||||
`lifecycle-<project>-<mission>`, and its compiled version is derived from source
|
||||
digests. Boundary steps explicitly allowing `waiting` default to a five-minute
|
||||
initial reminder and a one-hour cap; both fields in the example override those
|
||||
defaults. Missing operation identity leaves compilation ineligible. Instantiation
|
||||
with one opaque `--operation-key` persists the compiled input digest and lifecycle
|
||||
binding; exact replay returns the same instance/entry packet, and changed input
|
||||
under that key refuses. Authoring alone never starts an instance.
|
||||
|
||||
`context_refs` carries addresses in entry, successor, route/resume, and reminder
|
||||
packets. The lifecycle compiler also includes the project/mission manifests and
|
||||
`project.install.context`. Relative project references resolve from the project
|
||||
root; profile workflow references resolve from the project root and mission workflow
|
||||
references from the mission directory. References
|
||||
are not evidence verdicts: the receiving agent opens current context, may inspect
|
||||
beyond it, and chooses the exit. The daemon neither interprets receipts nor
|
||||
activates a mission. `rigx project` remains a separate manual shadow.
|
||||
|
||||
Outside mission-boundary compilation, an unmapped `waiting` exit with
|
||||
`re_present_after_seconds` alone remains one-shot.
|
||||
Adding `re_present_max_seconds` opts into an existing queue/watchdog timer that
|
||||
repeats with exponential backoff: the example waits 5, 10, 20, 40, then 60 minutes
|
||||
between reminders. The cap must be an integer at least as large as the initial
|
||||
delay. No new packet is created. Repeated waiting acknowledgments retain the
|
||||
schedule; changed structured `closureEvidence` or a new blocker resets it.
|
||||
`rig workflow project --evidence-ref <ref>` records an attributed reference as
|
||||
closure evidence, without interpreting it or substituting for typed acceptance. Omitted
|
||||
evidence preserves the prior evidence, and object key order is immaterial.
|
||||
|
||||
A transition on the exact qitem blocker makes the reminder due on the next
|
||||
scheduler tick (normally within one second), resetting the initial delay.
|
||||
Wake delivery receipts do not count as progress. The workflow's own waiting
|
||||
acknowledgments are on its frontier packet, not its upstream blocker; a repeating
|
||||
wait cannot name itself as its blocker. Blocker completion retains native
|
||||
queue unpark/wake behavior. Terminal or rerouted packets retire their timer.
|
||||
Restart reconciles blocker transition identity and resumes the persisted delay;
|
||||
replaying the same transition does not create another wake. A live repeating
|
||||
timer can have an unconsumed wake: these are separate facts in park status.
|
||||
Hard human gates are authored using the existing `gate` field; a reminder does
|
||||
not manufacture a gate or satisfy one.
|
||||
|
||||
## Operational Inspection
|
||||
|
||||
Read-only inspection commands for compaction planning, workflow heartbeat, and seat handover observability. Default mode is read-only across this section.
|
||||
@@ -1230,6 +1662,10 @@ Usage: `rig seat <subcommand>`
|
||||
Subcommands:
|
||||
- `status <seat> [options]` — show read-only seat handover observability status.
|
||||
- `handover <seat> [options]` — plan a safe two-phase seat handover.
|
||||
- `launch <seat> --fresh --reason <text> [--stop] [--operator <address>] [--json]`
|
||||
— create a deliberate blank occupant for exactly one existing seat. No
|
||||
continuity source is used; a live managed occupant requires `--stop`, while
|
||||
adopted or unmanaged ambiguity refuses.
|
||||
- `clear-attention <session> [--reason <text>] [--json]` — evidence-gated, operator-attested, audited reconcile of a stuck `attention_required` seat.
|
||||
|
||||
Notes:
|
||||
@@ -1273,26 +1709,60 @@ Notes:
|
||||
Usage: `rig context <subcommand>` — browse, preview, compose, and manage operator-authored context packs. This noun never delivers; delivery belongs to `send`, `broadcast`, `walk`, and `queue create`.
|
||||
|
||||
Subcommands:
|
||||
- `work-install [--project <id>] [--mission <id>] [--slice <id>] [--deliver] [--runtime <claude-code|claude|codex>] [--cwd <agent-working-directory>] [--topology <ids>] [--apply-skills] [--json]` — resolve project/mission/slice Markdown plus `project.yaml install.skills` in one plan. With `--runtime`, composes system, topology, and project selectors and reports per-skill provenance/status; `--apply-skills` safely reconciles the owned harness projection into `--cwd` (default: the caller's current working directory), not the workspace metadata directory. Omitting `--runtime` skips skill inspection regardless of `OPENRIG_RUNTIME`; applying skills requires an explicit runtime.
|
||||
- `profile <name-or-ref> --situation <fresh|handover|post-compaction> [--runtime <claude-code|claude|codex>] [--profile <id>] [--budget <tokens>] [--rig <rig> --seat <seat>] [--mission <mission>] [--slice <slice>] [--json]` — compose the selected atom graph and explicitly granted context. Runtime defaults to `OPENRIG_RUNTIME`, otherwise Claude; an unknown nonempty environment value warns and falls back to Claude. An explicit flag overrides the environment.
|
||||
- `list [options]` — list all context packs in the library.
|
||||
- `show <name-or-ref> [options]` — show pack manifest + per-file metadata.
|
||||
- `preview <name-or-ref> [options]` — show the assembled bundle without delivering it.
|
||||
- `compose --out <ref> --from <files...>` — compose ordered files into a durable context ref without delivering it.
|
||||
- `sync [options]` — re-walk discovery roots and refresh the library index.
|
||||
- `add <source-dir> [options]` — install a context pack from a local directory into the configured context store.
|
||||
- `add <source> [--git] [--checkout] [--pack <relative-path>] [--name <ref>] [--json]` — install a directory/manifest URL, or clone a Git repository with `--git`. Git discovery checks the repository-root manifest and `.openrig/context-packs`; multiple packs require an explicit `--pack`. `--checkout` selects an existing local checkout whose branch may be merged by an update.
|
||||
- `source inspect <ref> [--json]` — distinguish the selected revision/digest, current served edits, checkout branch/revision/status/conflicts, and locally known upstream divergence. It does not fetch or prove consumption.
|
||||
- `source update <ref> [--json]` — explicitly fetch and merge the selected checkout branch’s upstream, then publish its clean declared pack inputs. Dirty checkouts or edited served selections stop before update. Conflicts/unavailable upstream retain the old served selection and both Git sides.
|
||||
- `rm <ref> [options]` — remove a context pack by its path-like ref.
|
||||
|
||||
Notes:
|
||||
- Context packs are operator-authored bundles of context (manifest + files) intended to prime a managed seat with a coherent starting context.
|
||||
- Both `profile` and `work-install` accept `claude-code`, its alias `claude`, and `codex`. Invalid explicit values fail during CLI argument parsing, before context lookup or projection. JSON metadata retains the consumer's existing keys: `profile.runtime` is `claude` or `codex`; `skillProjection.runtime` is `claude-code` or `codex`. Both Claude spellings produce the same selection and metadata within each command; manifest runtime keys remain unchanged.
|
||||
- For example, `rig context profile world-public --situation fresh --runtime claude-code` and `rig context work-install --runtime claude-code` use the same runtime spelling. Profile atoms that read seat context still require both `--rig` and `--seat`.
|
||||
- `preview` is the canonical read-only way to inspect the assembled content.
|
||||
- This command family is delivery-free; context-window inspection is not part of this noun.
|
||||
|
||||
Git source example (use the intended instance and existing Git credentials):
|
||||
|
||||
```bash
|
||||
rig context add <repository-path-or-URL> --git --name team-world
|
||||
rig context source inspect team-world --json
|
||||
# Edit/commit in the reported checkout with ordinary Git.
|
||||
rig context source update team-world --json
|
||||
rig context get team-world
|
||||
```
|
||||
|
||||
Git selections copy only `manifest.yaml` and its declared files into the existing
|
||||
context library; `.openrig-git-source.json` records the retained checkout, pack,
|
||||
revision, selection time and digest. The checkout and previous selections live
|
||||
beside the configured library under `<context.root>-git-checkouts` and
|
||||
`<context.root>-git-history`. They are retained when a selected pack is removed.
|
||||
Local-only inspection compares against cached upstream refs; only explicit update
|
||||
contacts the remote. Git uses existing credentials, with terminal prompting disabled
|
||||
and a 60-second command timeout; a failure retains the checkout for native Git diagnosis.
|
||||
|
||||
Commit local improvements in the checkout before updating. If someone edited the
|
||||
served copy directly, preserve those edits in the checkout and commit them; restore
|
||||
the served copy to its recorded selection explicitly before retrying. No automatic
|
||||
stash, reset, rebase, push or conflict strategy runs. A merge conflict remains in the
|
||||
checkout for an owning author to resolve/commit or abort with Git. Only then retry
|
||||
update. Selected bytes, successful `context get`, and a consumer demonstrably using
|
||||
them are separate facts. This command does not automatically adopt context in other
|
||||
instances or prove that an agent consumed it.
|
||||
|
||||
### `rig workspace`
|
||||
|
||||
Usage: `rig workspace <subcommand>` — Workspace Primitive (PL-007), v0 typed-kind tooling.
|
||||
|
||||
Subcommands:
|
||||
- `validate [root] [--kind <kind>] [--no-recursive] [--require-frontmatter] [--max-files <n>] [--json]` — walk a workspace root, parse each `.md` file's YAML frontmatter, and emit a structured gap report. Advisory only — never modifies files. Default root: `cwd`. `--kind` validates against a specific workspace kind (`user | project | knowledge | lab | delivery`). `--max-files` (default `10000`) hard-caps the walk; v0.3.2 slice-01 GA enforces strict-int regex on this flag.
|
||||
- `doctor [--workspace <path>] [--strict] [--json]` — v0.3.2 slice-21 FR-5 (+1 check v0.4.4 slice 23). Run an 8-check workspace-readiness diagnostic against the daemon's resolved workspace (workspace root, missions folder, file allowlist, daemon alignment, daemon reload, optional slice docs, MISSION_NOTES presence, SDLC convention sections — the last advisory-warn per `docs/reference/sdlc-conventions.md`). Read-only. Default exit-code: non-zero only on `fail`; `--strict` makes warn-or-fail non-zero. The CLI overlays `OPENRIG_FILES_ALLOWLIST` from the operator's shell env so doctor reflects the operator's intended allowlist even when the daemon's env is unchanged.
|
||||
- `doctor [--workspace <path>] [--strict] [--json]` — run an 8-check workspace-readiness diagnostic against the daemon's resolved workspace (workspace root, missions folder, file allowlist, daemon alignment, daemon reload, optional slice docs, current `NOTES.md` or readable legacy notes, and SDLC convention sections). Read-only. Default exit-code: non-zero only on `fail`; `--strict` makes warn-or-fail non-zero.
|
||||
|
||||
Notes:
|
||||
- v0 surface was intentionally narrow (`validate` only); v0.3.2 added `doctor` as the operator-facing readiness diagnostic.
|
||||
@@ -1334,14 +1804,14 @@ the scope tree (missions, slices, sub-slices) per
|
||||
Usage: `rig scope <subcommand>` — scope tree primitive: missions, slices, sub-slices.
|
||||
|
||||
Top-level option:
|
||||
- `--workspace <path>` — override workspace root (otherwise inferred from cwd or `$OPENRIG_WORK_ROOT`).
|
||||
- `--workspace <path>` — override workspace root (otherwise use the typed `workspace.slices_root` setting; `$OPENRIG_WORK_ROOT` remains a legacy override).
|
||||
|
||||
Two subcommand groups: `slice` and `mission`.
|
||||
|
||||
`rig scope slice <subcommand>` — slice-tier commands:
|
||||
- `ls [--mission <name>] [--state <state>] [--json]` — list slices in a mission (or across all missions). `--state` filter: `active | closed | shipped | all` (default `active`).
|
||||
- `show <slice-path> [--mission <name>] [--json]` — inspect a single slice (frontmatter + README + children). `slice-path` is absolute, relative-to-substrate, or `NN-slug`; `--mission` hints the mission when path is just `NN-slug`.
|
||||
- `create <mission> <slug> [--template <kind>] [--title <text>] [--json]` — create a new slice in a mission. `--template` default `placeholder`. **v0.4.0 (slice 33)**: scaffolds a canonical `PROGRESS.md` (the structure the OpenRig PROGRESS UI page parses) plus a convention-correct README with proper frontmatter, for EVERY template. **v0.4.4 (slice 23)**: EVERY template kind additionally emits the SDLC convention sections (`## Intent` / `## Mini-requirements` / `## Proof contract`, kind-specific body below them), a `proof/` dir, `PROOF.md`, and an `IMPLEMENTATION-PRD.md` skeleton carrying the elastic-middle note — the shapes the Living Notes UI projects. Conventions SSOT: `docs/reference/sdlc-conventions.md`.
|
||||
- `create <mission> <slug> [--template <kind>] [--title <text>] [--intent <text>] [--depends-on <dot-id...>] [--json]` — create a mode-neutral slice scaffold: one intent-bearing `SPEC.md` with the three convention sections, `PROGRESS.md`, `PROOF.md`, and `proof/`. `depends_on` accepts same-mission sibling slice dot-IDs and is advisory build-order data. Every template kind emits the same file set; mode richness composes through the template seam.
|
||||
- `progress <slice-path> [--mission <name>] [--status <state>] [--milestone <text>] [--owner <session>] [--note <text>] [--json]` — **v0.4.0 (slice 33)** new verb: append / set / update progress entries deterministically. Writes the canonical structure the OpenRig PROGRESS UI page reads. Replaces hand-editing `PROGRESS.md` with markdown.
|
||||
- `stage <slice-path> <new-stage> [--mission <name>] [--successor <id>] [--json]` — **v0.4.0 (slice 35)** new verb: set the slice's `stage` frontmatter (wip / provisional / established / canonical / superseded / retired) deterministically. `superseded` REQUIRES `--successor <id>` (rejected otherwise + records the successor); `retired` warns "do not use"; invalid stages rejected with the valid set named.
|
||||
- `verified <slice-path> --against "<source>" [--mission <name>] [--json]` — **v0.4.0 (slice 35)** new verb: stamp the slice's `verified` line with `verified: <today> against <source>`. `--against` is MANDATORY (bare timestamps rejected — the anti-stale keystone per `conventions/scope-and-versioning` §2). Overwrites the prior verified line.
|
||||
@@ -1353,7 +1823,8 @@ Two subcommand groups: `slice` and `mission`.
|
||||
`rig scope mission <subcommand>` — mission-tier commands:
|
||||
- `ls [--json]` — list missions (top-level folders with `README.md`).
|
||||
- `show <mission> [--json]` — inspect a single mission.
|
||||
- `create <name> [--template <kind>] [--id <dot-id>] [--title <text>] [--json]` — create a new mission (mints a stable dot-ID into frontmatter). `--template` auto-selects when name matches `release-X.Y.Z`; `--id` overrides name-pattern inference. **v0.4.0 (slice 33)**: scaffolds a canonical `PROGRESS.md` plus a convention-correct README with proper frontmatter, per `conventions/scope-and-versioning/README.md`. Fixes the v0.3.x bug where newly-created missions were missing `PROGRESS.md` entirely.
|
||||
- `create <name> [--template <kind>] [--id <dot-id>] [--title <text>] [--intent <text>] [--depends-on <dot-id...>] [--no-notes] [--json]` — create a mission with an intent-bearing `SPEC.md`, `NOTES.md`, `PROGRESS.md`, and `slices/`. `depends_on` accepts same-project sibling mission dot-IDs and is advisory build-order data. `--no-mission-notes` remains a deprecated alias for `--no-notes`; the old notes-template environment variable remains readable with an advisory.
|
||||
- `graph <mission> [--json]` — show slice dependency nodes, the current ready/waiting sets, and advisories for malformed, cross-parent, or absent dependencies. Stale edges never block or crash the reader.
|
||||
- `progress <mission> [--status <state>] [--milestone <text>] [--owner <session>] [--note <text>] [--json]` — **v0.4.0 (slice 33)** new verb: append / set / update progress entries on a mission's `PROGRESS.md` deterministically. UI-valid by construction.
|
||||
- `stage <mission> <new-stage> [--successor <id>] [--json]` — **v0.4.0 (slice 35)** new verb: set the mission's `stage` frontmatter deterministically. Same enum + `--successor`-required-for-superseded rules as the slice variant.
|
||||
- `verified <mission> --against "<source>" [--json]` — **v0.4.0 (slice 35)** new verb: stamp the mission's `verified` line. `--against` MANDATORY.
|
||||
@@ -1365,9 +1836,9 @@ Convention compliance: `rig scope` together with slice 33 (`PROGRESS.md` + scaff
|
||||
|
||||
The conventions these verbs operate live in ONE shipped document: `docs/reference/sdlc-conventions.md` (copied into the assembled CLI package); the operating procedure is the packaged `mission-slice-sop` skill.
|
||||
|
||||
- `rig scope slice|mission approve <target> [--scope spec|delivery] [--actor <session>] [--on-behalf-of <human>] [--json]` — **v0.4.4 (slice 19 FR-9)** the two staged-approval locks, one daemon-side write path (frontmatter stamp + append-only audit row land together; no half-stamp). `--scope spec` = the **plan-lock** ("the PRD matches my intent; this artifact set gets built"); `--scope delivery` (default) = the **proof-lock** (terminal sign-off; fires the freeze). Approval is freeze/sign-off — never proven-green (proven-green requires recorded C1 verdicts).
|
||||
- `rig scope slice|mission approve <target> [--scope spec|delivery] [--actor <session>] [--on-behalf-of <human>] [--json]` — the two staged-approval locks, one daemon-side write path. `--scope spec` plan-locks the node's `SPEC.md`; `--scope delivery` (default) is terminal sign-off. Approval is freeze/sign-off, never proven-green.
|
||||
- `rig proof add <slice-path> --artifact-type <guard|qa|rev1-r1|rev1-r2|adjudication> --verdict <CLEAR|BLOCKING|CONCERNING|PASS|NOT-CLEAR> --candidate-sha <sha> --money-evidence "<line>" [--file <path>|--body <text>] [--evidences <refs>] [--media <refs>] [--self-check <text>] [--json]` — **v0.4.4 (slice 19 FR-8; `--media` via the corrective §3.4)** drop a proof artifact into `<slice>/proof/` with the machine-readable C1 header, validated at drop time (closed sets above). `--evidences` (item text or 1-based index) joins the drop to the slice's `## Proof contract` items — the pairing the Living Notes DELIVERED section renders; `--media` (proof/-relative refs, containment-checked, never absolute) names the curated media the drop stands behind, projected into the DELIVERED items' proof set. Contract/self-check outputs are advisories (exit 0), never gates.
|
||||
- `rig scope audit <mission> [--json]` — extended **v0.4.4 (slice 19 FR-10 + slice 23)** with C1-header and IMPLEMENTATION-PRD backstops plus the convention-section advisories (missing `## Intent`, missing/malformed `## Mini-requirements`, missing/malformed `## Proof contract`, UI-slice-without-mockup — a mockup ref means a real image ref or plannedRef token, never bare prose). All advisory/fail-open: the exit code flips on HIGH findings only; the convention advisories are low/info by construction.
|
||||
- `rig scope audit <mission> [--json]` — checks C1 headers and the one-SPEC convention (frontmatter `intent:` or a readable legacy Intent section, Mini-requirements, Proof contract, and current NOTES). It never requires a second PRD. All convention findings are advisory/fail-open.
|
||||
|
||||
Notes:
|
||||
- Surface source-verified against `packages/cli/src/commands/scope.ts` at `51554eee` (v0.4.0 post-slice-35); SDLC control-plane verbs source-verified against `scope.ts` / `proof.ts` / `scope-audit.ts` in OPR.0.4.4.23.
|
||||
@@ -1381,6 +1852,7 @@ One top-level command first shipped in v0.4.0 (slice 10 — skill / knowledge li
|
||||
Usage: `rig skill <subcommand>`
|
||||
|
||||
Subcommands:
|
||||
- `loadout --runtime <claude-code|codex> [--cwd <path>] [--project-root <path>] [--topology <ids>] [--apply] [--json]` — inspect the deterministic managed loadout selected by `catalog.yaml` system selectors, topology/profile selectors, and `project.yaml install.skills`. Reports selector reasons, catalog Git revision/content digest, target, and current/missing/shadowed/conflicting status. `--apply` writes exact bytes plus an ownership manifest, is idempotent, and refuses to overwrite or remove locally modified/unowned content.
|
||||
- `audit [--json] [--include-cache] [--severity <level>] [--rig <name>]` — read-only audit of the skill cascade. Detects provenance + freshness issues across the canonical → product mirror → hub cwd → installed plugin chain.
|
||||
|
||||
Audit categories surfaced:
|
||||
|
||||
@@ -46,7 +46,7 @@ imports:
|
||||
profiles:
|
||||
default:
|
||||
uses:
|
||||
skills: [using-superpowers, openrig-user, development-team, test-driven-development, systematic-debugging]
|
||||
skills: [openrig-user, development-team, test-driven-development, systematic-debugging]
|
||||
guidance: []
|
||||
subagents: []
|
||||
hooks: []
|
||||
@@ -77,7 +77,7 @@ defaults:
|
||||
model: claude-opus-4-6
|
||||
lifecycle:
|
||||
execution_mode: interactive_resident
|
||||
compaction_strategy: harness_native
|
||||
compaction_strategy: default-compaction
|
||||
restore_policy: resume_if_possible
|
||||
|
||||
imports:
|
||||
@@ -165,7 +165,7 @@ defaults:
|
||||
model: claude-opus-4-6
|
||||
lifecycle:
|
||||
execution_mode: interactive_resident
|
||||
compaction_strategy: harness_native
|
||||
compaction_strategy: default-compaction
|
||||
restore_policy: resume_if_possible
|
||||
```
|
||||
|
||||
@@ -180,7 +180,7 @@ defaults:
|
||||
| Field | Type | Default | Allowed Values |
|
||||
|-------|------|---------|----------------|
|
||||
| `execution_mode` | string | `interactive_resident` | `interactive_resident` (only value in v1; `wake_on_demand` is explicitly rejected) |
|
||||
| `compaction_strategy` | string | `harness_native` | `harness_native`, `pod_continuity` (`custom_prompt` is explicitly rejected in v1) |
|
||||
| `compaction_strategy` | string | `default-compaction` | `default-compaction`, `managed-compaction`, `handover`, `apprentice-handover`; deprecated aliases accepted with a validation advisory: `harness_native` → `default-compaction`, `pod_continuity` → `handover` (`custom_prompt` is explicitly rejected in v1) |
|
||||
| `restore_policy` | string | `resume_if_possible` | `resume_if_possible`, `relaunch_fresh`, `checkpoint_only` |
|
||||
|
||||
---
|
||||
@@ -318,6 +318,39 @@ Recognized runtime resource types:
|
||||
|
||||
Unknown runtime resource types are still copied to the runtime extension directory for agent-visible context.
|
||||
|
||||
#### Writing a `codex_config_fragment`
|
||||
|
||||
**Begin the fragment with a table header.** A fragment must be a valid TOML
|
||||
document on its own, and every key it sets must sit under a table it declares.
|
||||
A fragment that puts keys ahead of its first table header is refused at
|
||||
projection time, and nothing is written.
|
||||
|
||||
The reason is TOML's grammar rather than a policy choice. The managed block is
|
||||
appended to the end of the user's `config.toml`, and TOML has no syntax for
|
||||
returning to document root once a table has been opened. So if the user's file
|
||||
ends inside any table, an appended root-level key does not land at root — it
|
||||
silently becomes a member of *their* table. Refusal is deterministic and never
|
||||
inspects the user's file: a fragment author cannot see user state, and a rule
|
||||
that passed or failed depending on it would be impossible to reproduce.
|
||||
|
||||
```toml
|
||||
# refused — `model` would bind into whatever table the user's file ends inside
|
||||
model = "gpt-5"
|
||||
|
||||
[mcp_servers.exa]
|
||||
url = "https://mcp.exa.ai/mcp"
|
||||
```
|
||||
|
||||
```toml
|
||||
# accepted — every key sits under a table this fragment declares
|
||||
[mcp_servers.exa]
|
||||
url = "https://mcp.exa.ai/mcp"
|
||||
```
|
||||
|
||||
Where a fragment's table is one the user already declares, the user's table
|
||||
wins: the managed table is dropped, their values are never merged, rewritten or
|
||||
overwritten, and the rest of the fragment still applies.
|
||||
|
||||
### Resource Path Rules
|
||||
|
||||
- All resource paths must be safe relative paths (no `..` traversal, no absolute paths)
|
||||
@@ -350,6 +383,12 @@ The startup block follows the same format as in the RigSpec (see `docs/reference
|
||||
|
||||
Agent-level startup is applied to all profiles. Profile-level startup is applied only when that profile is active. Both merge additively via the startup layering model.
|
||||
|
||||
An additional orientation challenge requires an explicit `startup_proof` action
|
||||
with `value: authenticated` and `idempotent: true`. Omission adds no exercise;
|
||||
a later applicable `value: none` overrides an earlier selection. See
|
||||
[startup proof selection](rig-spec.md#startup-proof-selection) for precedence,
|
||||
restore behavior, and the distinction between readiness and verified proof.
|
||||
|
||||
### Delivery Hint Quick Reference
|
||||
|
||||
| Hint | When Delivered | Mechanism |
|
||||
@@ -375,7 +414,7 @@ Agent-level startup is applied to all profiles. Profile-level startup is applied
|
||||
10. Resource IDs must be unique within their category.
|
||||
11. `runtime_resources` entries require `runtime` field.
|
||||
12. Lifecycle `execution_mode` must be `interactive_resident`.
|
||||
13. Lifecycle `compaction_strategy` must be `harness_native` or `pod_continuity`.
|
||||
13. Lifecycle `compaction_strategy` must be one of `default-compaction`, `managed-compaction`, `handover`, `apprentice-handover` — or a deprecated alias (`harness_native`, `pod_continuity`), which validates with a deprecation advisory and normalizes to its canonical value (OPR.0.5.6.20).
|
||||
14. Lifecycle `restore_policy` must be `resume_if_possible`, `relaunch_fresh`, or `checkpoint_only`.
|
||||
15. Startup files and actions follow the same validation rules as in RigSpec.
|
||||
|
||||
@@ -413,6 +452,6 @@ The only required file is `agent.yaml`. Everything else is referenced by paths i
|
||||
| `implementer` | `specs/agents/development/implementer/` | `shared` | openrig-user, development-team, test-driven-development, systematic-debugging, etc. | TDD implementation agent |
|
||||
| `qa` | `specs/agents/development/qa/` | `shared` | openrig-user, development-team, etc. | Quality assurance agent |
|
||||
| `orchestrator` | `specs/agents/orchestration/orchestrator/` | `shared` | openrig-user, orchestration-team, etc. | Rig orchestration lead |
|
||||
| `reviewer` | `specs/agents/review/reviewer/` | `shared` | openrig-user, review-team, etc. | Independent code reviewer |
|
||||
| `independent-reviewer` | `specs/agents/review/independent-reviewer/` | `shared` | review-team, systematic-debugging, verification-before-completion | Independent code reviewer |
|
||||
| `vault-specialist` | `specs/agents/apps/vault-specialist/` | `shared` | openrig-user, systematic-debugging, vault-user | Vault domain specialist |
|
||||
| `design` | `specs/agents/design/` | none | — | Product designer |
|
||||
@@ -225,7 +225,7 @@ Commands sent to the agent's terminal after it's ready. Can install MCPs, run se
|
||||
| `send_text` delivery after ready | **Supported** | Reliable. Requires harness to be ready. |
|
||||
| Hook projection | **Experimental** | Files are copied but execution/integration varies by runtime. |
|
||||
| Runtime resource projection | **Supported for recognized fragments** | `claude_settings_fragment`, `claude_mcp_fragment`, and `codex_config_fragment` are applied to provider config. Unknown types are copied to runtime extension directories. |
|
||||
| Permission allowlisting | **Supported via harness config** | Core OpenRig boot adds the minimal Claude `Bash(rig:*)` baseline. Additional policy should be supplied through spec-selected runtime resource fragments or native Codex config profiles, not adapter hard-code. |
|
||||
| Permission configuration | **Native settings plus managed launch flags** | OpenRig launches Claude with `acceptEdits` and Codex with `workspace-write` unless an explicit supported selection changes them. It does not add a global Claude `Bash(rig:*)` allowance. Use [the first-user permission guide](getting-started.md#opt-in-permissive-operation) for opt-in and custom choices. |
|
||||
| MCP installation | **Experimental** | Claude Code: `/mcp` interactive command or `claude mcp add` from CLI. Can also be described in startup files for agent self-configuration. Reliability depends on runtime TUI state. |
|
||||
| System dependency installation | **Not deterministic** | Describe in startup files; agent handles via shell commands. |
|
||||
| Recurring tasks / wake timers | **Runtime-dependent** | Claude Code supports recurring tasks via the `/loop` command. Codex does not have a confirmed equivalent. Orchestrators should include `/loop` instructions in startup for Claude Code agents. |
|
||||
@@ -246,7 +246,8 @@ This way, when deterministic support becomes fully reliable, the agent will boot
|
||||
OpenRig performs best-effort deterministic runtime configuration for managed sessions. Core bootstrap stays minimal; user/custom policy belongs in spec-selected runtime resources. These writes are intentionally invasive and should be disclosed plainly:
|
||||
|
||||
- Claude global config: `~/.claude/settings.json`
|
||||
Purpose: allow core `rig` commands without repeated Claude permission prompts.
|
||||
OpenRig no longer writes a core `Bash(rig:*)` permission allowance here. Older
|
||||
installations may retain one; existing user settings are not removed.
|
||||
- Claude global state: `~/.claude.json`
|
||||
Purpose: pre-trust managed workspaces and mark onboarding complete for fresh managed sessions.
|
||||
- Claude project-local config: `.claude/settings.local.json`
|
||||
@@ -260,6 +261,11 @@ Two important caveats:
|
||||
- these writes are best-effort and should still be paired with startup guidance so the local agent can verify and repair them if needed
|
||||
- already-running adopted sessions may need restart before they pick up newly written config
|
||||
|
||||
Permission mode and runtime resource projection are separate. A selected fragment
|
||||
can affect native configuration, but OpenRig's launch flags can override those
|
||||
values. Recording a config-surface `permission_policy` is not proof that its
|
||||
rules were translated or applied. See [permission precedence and limits](getting-started.md#custom-settings-and-precedence).
|
||||
|
||||
### Runtime Differences
|
||||
|
||||
**Claude Code:**
|
||||
@@ -274,7 +280,9 @@ Two important caveats:
|
||||
- Reads from `AGENTS.md` and `.agents/` directory
|
||||
- Recurring task support is limited — no confirmed equivalent of Claude Code's `/loop` command
|
||||
- MCP configuration mechanism differs from Claude Code
|
||||
- Approval mode controlled via launch flags (`-a`, `-s`, `--full-auto`)
|
||||
- Approval policy and sandbox access are separate controls. OpenRig's default
|
||||
`-s workspace-write` selects the sandbox; it does not force `-a`. A member's
|
||||
`codex_config_profile` selects native `-p`, distinct from the AgentSpec `profile`.
|
||||
- Can self-install dependencies from instructions but timer/recurring behavior is not reliably available
|
||||
|
||||
When authoring startup content, note which instructions are runtime-specific. For example, an orchestrator that needs a monitoring loop should include instructions like: "If running Claude Code, use `/loop 3m` to periodically check rig health. If running Codex, check rig health at the start of each task cycle instead."
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# Agent state taxonomy
|
||||
|
||||
The formal state language every OpenRig surface renders from — TUI, `rig ps`, the node
|
||||
inventory, and any future consumer. One oracle computes it; surfaces render it; no
|
||||
surface keeps a private vocabulary.
|
||||
|
||||
**This document is the canonical text for the shipped taxonomy** and adds the
|
||||
engineering reconciliation; it never forks a second vocabulary. Its typed source of
|
||||
truth is `packages/daemon/src/domain/activity-taxonomy.ts`.
|
||||
|
||||
## The three orthogonal axes
|
||||
|
||||
| axis | values | question it answers |
|
||||
|---|---|---|
|
||||
| session | `present` · `detached` · `exited` · `absent` | does a process exist |
|
||||
| activity | `working` · `idle-at-prompt` · `unknown` (+ needs-input as count+reason, below) | what a present agent is doing |
|
||||
| resumability | `live` · `resumable` · `context-walled` | what a non-present agent can come back as |
|
||||
|
||||
The axes never blend. Reachability is strict; revival optimism is a different field
|
||||
(omnigent's strict-liveness split). `unknown` is a first-class honest value — the oracle
|
||||
saying it cannot tell. Unknown beats a confident wrong answer.
|
||||
|
||||
### needs-input is a count + reason, never a status value
|
||||
|
||||
Blocked-on-input rides as `{ count, reason }` — the number of outstanding prompts plus a
|
||||
short phrase naming why nothing is moving ("permission prompt", "usage limit",
|
||||
"classifier hold" — provider chrome and usage limits are explicitly in-axis: they are
|
||||
the second cause of production-observed parks). Human surfaces may *display* "needs-input"
|
||||
(the four-value human list) via the one bridge `deriveDisplayActivity`, which
|
||||
renders needs-input whenever `count > 0`; no store or transition ever holds it as a
|
||||
state. Attention/alarm values likewise stay in their own machinery (`attentionCount`).
|
||||
Both herdr and omnigent converged on this exclusion, with warning comments on the
|
||||
collision in both codebases.
|
||||
|
||||
## Derived diagnoses — computed at read time, never stored
|
||||
|
||||
- **PARKED** — the disease: `(activity = idle-at-prompt OR needs-input pending) ×
|
||||
(open obligations exist)`. A dropped baton or an unanswered block. Queryable at rig and
|
||||
seat level; the join lives in the parked query surface, never inside the oracle
|
||||
(the oracle's non-inference contract: it never reads queue state).
|
||||
- **HELD** — the deliberate counterpart: a queue-level hold with a named owner and an
|
||||
armed wake (`rig view show held`). Stopping on purpose is HELD; stopping by accident is
|
||||
PARKED. A HELD row is **not** parked.
|
||||
- **DONE-UNSEEN** — finished work whose completion nobody consumed (herdr derives it as
|
||||
idle ∧ unseen). Declared here and computed at read time from claims/transitions,
|
||||
outside the activity oracle, preserving its non-inference boundary.
|
||||
|
||||
## Reconciliation with prior art
|
||||
|
||||
| ours | herdr (`src/detect/mod.rs:11`) | omnigent (`schemas.py:2767`) |
|
||||
|---|---|---|
|
||||
| `working` | `Working` | `running` |
|
||||
| `idle-at-prompt` | `Idle` | `idle` |
|
||||
| needs-input count+reason | `Blocked` + `visible_blocker` override | `pending_elicitations_count` + `blocked_on` phrase — **not** a status |
|
||||
| `unknown` | `Unknown` ("plain shell or unrecognized program") | — |
|
||||
| derived DONE-UNSEEN | derived `Done` = idle ∧ unseen (`src/ui/sidebar.rs:186`) | — |
|
||||
| session axis | server owns the PTY (presence direct) | `runner_online` × `host_online` × `host_resumable` |
|
||||
| — (rejected) | — | `waiting` (turn parked on async drain — collides with Claude's dialog `waiting`; both codebases carry warning comments) |
|
||||
|
||||
## The evidence ladder (rung inventory and retirement conditions)
|
||||
|
||||
State is computed by ONE arbitration point (`SeatActivityService`) from ranked,
|
||||
time-bounded evidence. Rungs, top down, with each rung's retirement condition:
|
||||
|
||||
- **r3 — Claude `sessions/<pid>.json` self-report** (busy/shell/idle/waiting, since
|
||||
v2.1.139): standing, above hooks for working/idle. Undocumented internal — unreadable
|
||||
means fall down the ladder, never error.
|
||||
- **r2 — lifecycle hooks** (Claude Stop/StopFailure; Codex UserPromptSubmit/Stop/
|
||||
PermissionRequest): standing. Exactly-once turn boundaries; SubagentStop filtered (it
|
||||
can fire post-turn and must never revive an idle seat). Hook-rung authority is
|
||||
TIME-BOUNDED: the hook rung is the only rung that does not self-date its evidence, so
|
||||
persistent cross-rung contradiction degrades it to identity-only, visibly, with a
|
||||
rung-health event.
|
||||
- **r4 — visible needs-input chrome**: standing; outranks self-report for the
|
||||
needs-input signal only (a dialog you can see beats a hook that said working).
|
||||
- **r1 — window-activity sampling** (today's oracle): the fallback tier. Retires
|
||||
PER-HARNESS when that harness's hook rung earns trust through a measured agreement
|
||||
window in production — never by fixture pass alone (symmetric admission: a fixture
|
||||
pass admits a rung to TRIAL; production agreement promotes it; identity-only until
|
||||
promoted).
|
||||
|
||||
Partial-coverage honesty: a source with partial lifecycle coverage gets identity-only
|
||||
trust. The ladder is also the migration path — new oracles slot in above existing
|
||||
heuristics, lower rungs retire a release at a time, consumers never see a flag day.
|
||||
|
||||
Seat-keyed: state attaches to the durable seat nodeId, never the occupant. A handover or
|
||||
generation swap is its own visible event — never an activity transition — and triggers
|
||||
rung-inventory re-declaration: a successor never inherits its predecessor's rung
|
||||
authority (hooks live in occupant config while the pane is seat-persistent).
|
||||
|
||||
## Rejected oracles — dated receipts (2026-08-26), so nobody re-derives them expensively
|
||||
|
||||
- **transcript-quiescence as an activity oracle: REJECTED.** omnigent's 1-second version
|
||||
oscillated on mid-turn lulls and idempotently locked out real completions; its
|
||||
surviving 5-second variant is annotated a stopgap awaiting a hook replacement.
|
||||
Distinction held: transcript growth remains FINE as the refocus hook's cadence
|
||||
trigger — a different purpose, and the one permitted consumer.
|
||||
- **pane-scraping as primary truth: REJECTED.** herdr — whose TOML screen manifests are
|
||||
the state of that art — subordinates them to hooks wherever hooks cover the lifecycle,
|
||||
and marks viewer screens `skip_state_update` because scrollback lies. Pane reading
|
||||
stays fallback-tier only.
|
||||
@@ -12,7 +12,7 @@ walking from where they stand toward the root, reading the same-named file at
|
||||
each level. No pointers to follow, no branching, no per-level names. Inventing
|
||||
a level-specific name breaks every walker.
|
||||
|
||||
Chain files sit on **nodes** (an instance, a rig, a seat; a mission, a slice)
|
||||
Chain files sit on **nodes** (an instance, a rig, a pod, a seat; a mission, a slice)
|
||||
— never on shelves (`rigs/`, `seats/`, `missions/` are containment, not
|
||||
nodes, and carry no chain file).
|
||||
|
||||
@@ -20,7 +20,7 @@ nodes, and carry no chain file).
|
||||
|
||||
| tree | path shape | carries | ships? |
|
||||
|---|---|---|---|
|
||||
| **Topology** | instance → rig → seat | how work is done here | **yes** — generally applicable defaults ship in source |
|
||||
| **Topology** | instance → rig → pod → seat | how work is done here | **yes** — generally applicable defaults ship in source |
|
||||
| **Project** | project → mission → slice | what is being built | **no** — depends on what the user builds; see below |
|
||||
|
||||
### The topology tree
|
||||
@@ -35,13 +35,15 @@ instance):
|
||||
```
|
||||
<topology.root>/<NAME>.md # instance altitude
|
||||
<topology.root>/rigs/<rig>/<NAME>.md # rig altitude
|
||||
<topology.root>/rigs/<rig>/pods/<pod>/<NAME>.md # pod altitude
|
||||
<topology.root>/rigs/<rig>/seats/<seat>/<NAME>.md # seat altitude
|
||||
```
|
||||
|
||||
**The pod level is deliberately skipped** (founder ruling, March 2026: agent
|
||||
specs stay flat under the rig so any agent composes into any rig; pods felt
|
||||
too restrictive — revisit someday, not now). Pod-class tactical content ships
|
||||
as a rig-altitude file named for its audience (see "craft defaults" below).
|
||||
The engine creates the instance root, rig directory, and each declared pod
|
||||
directory when it materializes topology, even when no authored chain default
|
||||
exists. Pod context remains optional: use that altitude only for knowledge
|
||||
shared within one context domain. Cross-pod guidance still belongs at the rig
|
||||
altitude.
|
||||
|
||||
Established chain names on the topology tree:
|
||||
|
||||
@@ -67,7 +69,7 @@ defaults.)
|
||||
## Legacy location and the advisory
|
||||
|
||||
Before this convention the topology tree lived at `~/.openrig/shared-docs/rigs/`
|
||||
(founder ruling 2026-08-14: an arbitrary folder on one machine, not a product
|
||||
(design decision 2026-08-14: an arbitrary folder on one machine, not a product
|
||||
path). That location **stays readable** as a per-level fallback so existing
|
||||
rigs migrate instead of breaking — but every read that resolves there emits a
|
||||
named advisory (`legacy-topology-read: …`) stating the legacy source, the
|
||||
@@ -78,11 +80,13 @@ no path literal.
|
||||
|
||||
## The trace
|
||||
|
||||
`rig context trace --rig <rig> [--seat <seat>] --name <NAME>.md` performs the
|
||||
walk: it prints each altitude root-first (general → specific), marks whether
|
||||
content came from `topology.root`, the legacy tree (with the advisory on
|
||||
stderr), or is absent, and needs no running daemon — orientation is exactly
|
||||
when the daemon may be down. `--json` returns the structured result.
|
||||
`rig context trace --rig <rig> [--pod <pod>] [--seat <seat>] --name <NAME>.md`
|
||||
performs the walk: it prints each selected altitude root-first (general →
|
||||
specific), marks whether content came from `topology.root`, the legacy tree
|
||||
(with the advisory on stderr), or is absent, and needs no running daemon —
|
||||
orientation is exactly when the daemon may be down. Existing rig/seat traces
|
||||
remain valid; selecting `--pod` adds that context domain between them. `--json`
|
||||
returns the structured result.
|
||||
|
||||
Your reads ARE the walk: a trace assembled from memory is a recitation that
|
||||
reproduces the drift it was meant to catch. Run it; do not recall it.
|
||||
@@ -90,7 +94,8 @@ reproduces the drift it was meant to catch. Run it; do not recall it.
|
||||
## Shipped defaults and the curation path
|
||||
|
||||
Included rigs (product-team first) ship sensible-default chain files at the
|
||||
rig, seat, and instance altitudes, installed under `topology.root` at rig-up.
|
||||
applicable instance, rig, pod, and seat altitudes, installed under
|
||||
`topology.root` at rig-up.
|
||||
A shipped default is a **starting point** the occupying team appends to — it
|
||||
is never overwritten by a later rig-up (existing files win).
|
||||
|
||||
|
||||
@@ -1,47 +1,261 @@
|
||||
# Getting started: the guided golden path
|
||||
# Getting started: one useful change in your repository
|
||||
|
||||
This is the ordered new-operator path from a fresh machine to a running rig with
|
||||
a workspace and a workflow. It uses the **existing** `rig` verbs in order - there
|
||||
is no magic one-command onboarding; each step is a real, inspectable verb.
|
||||
Start with a repository and one bounded change you can exercise. The shipped
|
||||
first-project recipes provide the same two seats: an outcome owner and an
|
||||
independent checker. Choose Claude Code, Codex, or one of each using the accounts
|
||||
you already have. Terminal-provider support does not change the harness or login.
|
||||
|
||||
You need Node.js 22 or 24 and tmux, on macOS or Linux. On a Mac with Apple
|
||||
silicon, use Node.js 22 (see the [compatibility
|
||||
history](../releases/v0.5.15.md#known-compatibility-limitation)). Native
|
||||
Windows is not supported yet, and WSL2 has not been tested. Node 20 is no longer
|
||||
supported; Node 26 and other versions are untested.
|
||||
|
||||
**Choose permissions before starting the team.** Ordinary OpenRig launches use
|
||||
Codex's `-s workspace-write`, with approval policy from your native configuration,
|
||||
or Claude Code's `acceptEdits`, which still leaves commands subject to native
|
||||
rules and prompts. Codex's sandbox normally blocks network access, including the
|
||||
local OpenRig daemon. A command allowance does not change general sandbox/network
|
||||
settings. The starter's `profile: default` selects OpenRig resources, not a native
|
||||
permission profile.
|
||||
Agent-guided setup [asks once](#have-your-agent-configure-permissions): “Allow
|
||||
your agents to run OpenRig commands without repeated permission prompts?”
|
||||
**Yes — recommended** / **No — keep prompts**. An existing explicit choice is
|
||||
reused; No or no answer leaves settings unchanged. Broader access is separate.
|
||||
|
||||
> Everything below reports **what is currently true**, never a guarantee that
|
||||
> downstream work will succeed. "Daemon up" does not mean every agent is healthy;
|
||||
> "kernel ready" does not mean every kernel agent is healthy; a workspace root
|
||||
> being *live* does not mean it is the *right* one for your project.
|
||||
|
||||
## The ordered path
|
||||
## Prepare and launch
|
||||
|
||||
1. **`rig setup`** - install and verify the local runtime (tmux, cmux, Claude
|
||||
Code / Codex, runtime config). Run once per machine. `rig setup` does NOT boot
|
||||
the kernel (see "Kernel framing" below).
|
||||
2. **`rig up <rig-spec>`** - launch a rig. This **auto-starts the daemon** if it
|
||||
is not already running (the four daemon-dependent verbs below do not auto-start
|
||||
it - they tell you to run `rig up` or `rig daemon start`).
|
||||
3. **`rig status`** - see what is currently true: daemon up/down + port, kernel
|
||||
readiness (distinct from daemon health), and your effective workspace root
|
||||
(default vs override).
|
||||
4. **`rig workspace doctor`** - check that your workspace is ready; it reports
|
||||
state + fix-hints. The effective workspace root defaults to
|
||||
`~/.openrig/workspace/` unless you override `workspace.root` (env or config).
|
||||
5. **`rig workflow instantiate <name>`** - start a workflow runtime instance by
|
||||
its **discovered name** (e.g. `conveyor`, `basic-loop`) - no hidden file path
|
||||
needed. `rig workflow specs` shows the seeded built-ins. Instantiating creates a
|
||||
workflow instance and its first **entry qitem**.
|
||||
6. **`rig scope ...`** - browse and manage the durable mission/slice artifacts the
|
||||
workflow coordinates (see the scope <-> workflow bridge below).
|
||||
Type `rig` in an ordinary terminal to open the startup and work TUI. It shows
|
||||
the daemon address; **d** expands the selected instance path and diagnostics.
|
||||
If the daemon is stopped, press Enter
|
||||
to start that daemon, then choose the rigs and seats you want. Kernel is
|
||||
recommended first; selecting its operator does not start every kernel seat.
|
||||
The same view is available with **S** from ordinary TUI work.
|
||||
**?** opens Help even while connection checks are pending. **w** skips startup;
|
||||
**Esc** goes back, or leaves startup from its first page. These choices do not
|
||||
start a daemon or a seat. **L** opens local reading before or after connecting:
|
||||
choose configured Specs, project intent, projects, or missions and slices, then
|
||||
select a directory or file. **r** reads the selected source again; **Esc** returns.
|
||||
Local reading uses this machine's configured workspace paths and file allowlist,
|
||||
including when the selected daemon address is remote. It shows disk provenance,
|
||||
missing or denied sources, binary files and the 1 MiB text truncation boundary.
|
||||
These disk snapshots may change after reading and do not supply live queue,
|
||||
execution or topology state. Live views load after a confirmed connection and
|
||||
deliberate entry; a stalled live read does not prevent Help or local reading.
|
||||
When terminal transport is unavailable, **t** starts the empty terminal service
|
||||
so recovery choices can be inspected. It launches no seats.
|
||||
|
||||
`rig setup` prints this ordered sequence as its next-steps output; `rig status`
|
||||
points back here. This doc is the durable reference.
|
||||
For a previously occupied seat, Enter attempts its previous conversation.
|
||||
If history is unavailable, read the reason. **f** opens a separate fresh-start
|
||||
decision for that named seat; **Esc** declines without launching it. A confirmed
|
||||
fresh conversation receives the configured context and retains the old history,
|
||||
but does not resume that history. Authentication or runtime failures require
|
||||
repair of that prerequisite. **o** opens the existing native terminal here; detach
|
||||
to return (tmux defaults to Ctrl-b, then d). Decide native trust/auth prompts
|
||||
there. If a fresh start paused before context delivery, **c** finishes that
|
||||
delivery to the same occupant. **r** reads actual state again; **d** expands details.
|
||||
|
||||
### Choose your providers
|
||||
|
||||
Ask: **“Which working account do you want this team to use: Claude Code, Codex,
|
||||
or both?”** Reuse an explicit choice already made. Recommend the account the
|
||||
user already has working; a second subscription is not a prerequisite.
|
||||
|
||||
| Choice | Starter name | Owner / checker runtime and model |
|
||||
| --- | --- | --- |
|
||||
| Two Codex agents (existing route) | `first-project` | Both `codex`, pinned `gpt-6-astra` |
|
||||
| Two Claude agents | `first-project-claude` | Both `claude-code`, configured native default model |
|
||||
| One of each | `first-project-mixed` | Claude owner (native default); Codex checker (`gpt-6-astra`) |
|
||||
|
||||
Claude uses the same unpinned model convention as the existing Claude kernel and
|
||||
implementation-pair recipes: OpenRig does not pass a model override. Read the
|
||||
selected harness's configured model and show it alongside the recipe and launch
|
||||
command before proceeding. Confirm account access to any pin; if unavailable,
|
||||
ask for a supported model choice rather than silently substituting a model or
|
||||
provider. After launch, confirm the actual native model before assigning work.
|
||||
|
||||
Install OpenRig and check `tmux -V`. Check **only the selected providers**:
|
||||
|
||||
- Claude Code: `claude --version` and `claude auth status`. If sign-in is missing,
|
||||
ask once: “Please run `claude auth login` in your launch environment.”
|
||||
- Codex: `codex --version` and `codex login status`. If sign-in is missing,
|
||||
ask once: “Please run `codex login` in your launch environment.”
|
||||
|
||||
Install a missing selected CLI using its provider's installation instructions.
|
||||
The other provider's CLI/login and Herdr/cmux are optional. Do not copy credentials
|
||||
or start repeated sign-in attempts. Recheck the selected login after the user
|
||||
completes it. `rig setup --dry-run` previews the broader setup; applying
|
||||
`rig setup` installs/checks **both** harnesses and cmux, so it is optional for
|
||||
this selected-provider path, not a requirement to fix an unused provider.
|
||||
|
||||
### Kernel startup stays automatic
|
||||
|
||||
On a fresh instance, ordinary daemon startup chooses the kernel variant from
|
||||
successful native auth probes: Claude alone, Codex alone, or both. Thus an absent
|
||||
unused provider is fine. The project recipe does **not** constrain that probe:
|
||||
if both accounts are authenticated, automatic kernel startup uses both, even
|
||||
when the two project agents use just one provider. No manual kernel setup is
|
||||
needed for this journey. The starter choice is not an instance-wide provider
|
||||
restriction; a request to use only one provider for everything is separate.
|
||||
Preserve any existing managed kernel and working rigs.
|
||||
|
||||
### Launch the two project seats
|
||||
|
||||
Set `starter` to the chosen name from the table; the existing Codex route remains:
|
||||
|
||||
```sh
|
||||
cd <your-repository>
|
||||
starter=first-project # or first-project-claude or first-project-mixed
|
||||
rig specs preview "$starter" --kind rig
|
||||
rig up "$starter" --cwd . --plan
|
||||
rig up "$starter" --cwd .
|
||||
rig status
|
||||
rig ps --nodes --rig "$starter"
|
||||
```
|
||||
|
||||
Preview the selected seats, models and resources. Plan checks resolution and
|
||||
preflight for the working directory. Launch starts the daemon if needed, with
|
||||
the kernel behavior above. Read readiness for the project seats, not only daemon
|
||||
health. Resolve a named authentication, trust or permission prompt before giving
|
||||
that seat work. No new team is needed when returning to an existing project.
|
||||
|
||||
When a seat pauses, open its existing terminal with **o** in the startup view.
|
||||
Read the proposed command, working directory and target instance. For an intended
|
||||
local `rig` call, choose the native prompt's one-time approval if that is the scope
|
||||
you want; a saved command-prefix allowance also affects future matching calls.
|
||||
Decline an unexpected operation and tell the same agent what to do instead.
|
||||
Approval controls whether an action may run; the sandbox controls its filesystem
|
||||
and network access. Turning approvals off does not grant network access.
|
||||
|
||||
After answering, watch for the command's result and the agent continuing. Read
|
||||
the corresponding queue row and transition from your ordinary terminal. If an
|
||||
operation timed out, read its result before asking for another attempt: it may
|
||||
already have taken effect. A delivered message or disappearing prompt alone is
|
||||
not progress. If startup is still waiting for context delivery, use **c** for the
|
||||
same occupant, then **r** to refresh. Do not start another seat to clear a prompt.
|
||||
|
||||
These starters are deliberately small starting points, not universal teams.
|
||||
For a different installed runtime or team shape, inspect `rig specs ls --kind
|
||||
rig` and `rig specs preview <name>` before selecting it. A seven-seat showcase
|
||||
is optional and consumes more concurrent capacity.
|
||||
|
||||
## Give the owner an outcome
|
||||
|
||||
For example, in a project that imports CSV files:
|
||||
|
||||
```sh
|
||||
rig send "dev-owner@$starter" 'Improve the CSV import error when a required column is missing: name the column and leave the existing data unchanged. Add a regression check, ask dev-check for an independent check of the exact candidate, and record the result and how I can try it. Keep the change local; do not publish.'
|
||||
```
|
||||
|
||||
Replace the example with a real problem in your repository. Include what the
|
||||
user should observe, a boundary and how success can be checked. The owner
|
||||
creates and claims a durable task, implements it, and routes the selected
|
||||
independent check. You should not have to relay the review between terminals.
|
||||
`rig send` is the initial conversation; the queue and repository artifacts
|
||||
retain the work. An unbound shell does not need to impersonate a queue owner.
|
||||
|
||||
Use the chosen rig name in its seat addresses (for example,
|
||||
`dev-owner@first-project-claude`). From an actual project seat, follow the work with
|
||||
`rig queue list --limit 1000`: its default scope is the caller's current rig.
|
||||
`queue list` has no `--rig` option. From an observer shell or another rig, use
|
||||
`rig queue list --destination "dev-owner@$starter" --limit 1000` and the same
|
||||
command for `"dev-check@$starter"`, after verifying those live addresses.
|
||||
These show each destination's obligations, not a whole-rig view. An unbound shell
|
||||
must not pretend to be a seat to change scope; use `--all-rigs` only when that
|
||||
broader view is intended. Then read `rig queue show <id> --full` and
|
||||
`rig queue transitions <id>`. A delivered message is not a reviewed result.
|
||||
Read the artifact, exercise its behavior, and check the candidate reviewed.
|
||||
|
||||
## Share the dashboard and return to it
|
||||
|
||||
```sh
|
||||
rig tui --shared
|
||||
```
|
||||
|
||||
A fresh kernel runs the ordinary TUI in its existing `operator-human` terminal.
|
||||
This command attaches another client to that terminal. **Ctrl-b, then d**
|
||||
detaches without quitting the TUI; return with the same command and the view
|
||||
stays where it was. Another authorized agent can capture or operate that same
|
||||
pane. It should tell you before changing your view. The terminal is not a
|
||||
human inbox and does not prove anyone is watching it.
|
||||
|
||||
Plain `rig tui` remains an independent local view. If an older kernel or a TUI
|
||||
you quit shows a shell, run `rig tui` in that shell once. `--shared` does not
|
||||
start or replace a terminal, so a missing binding is reported with recovery
|
||||
guidance rather than creating a second kernel.
|
||||
|
||||
Herdr users follow the same launch and task path. To place the managed team in
|
||||
Herdr, use `rig terminal open "$starter" --provider herdr`; for the shared
|
||||
dashboard, use `rig terminal open kernel --provider herdr`. The equivalent
|
||||
cmux provider is also available. Read the opened/absent/degraded result: a
|
||||
partial terminal view is not a healthy team. Repeated terminal-open calls can
|
||||
create another provider workspace; return to the one already open when you
|
||||
want to preserve it. This is terminal integration, not native plugin enrollment.
|
||||
|
||||
## Continue real project work
|
||||
|
||||
Return to the same owner with the next outcome, citing the earlier result.
|
||||
The seat address and durable queue survive closing your viewing terminal.
|
||||
Keep intent, acceptance and evidence in the repository's existing project,
|
||||
mission and slice artifacts; the starter reads those before inventing a path.
|
||||
|
||||
If the project has no work tree, start with `rig workspace doctor` and
|
||||
`rig scope mission create --help`, then `rig scope slice create --help`. Set
|
||||
the actual intended outcome before creating work. `rig scope` retains what is
|
||||
being built. When repeated coordination warrants a workflow, discover with
|
||||
`rig workflow specs`, inspect its owners and inputs, and instantiate the
|
||||
selected name with `rig workflow instantiate --help`. A workflow is not needed
|
||||
merely to make the first local change.
|
||||
|
||||
For a continuing team, [OpenRig Software Factory](../../packages/daemon/specs/agents/shared/skills/core/openrig-software-factory/SKILL.md)
|
||||
offers manual/team work, queue-supported orchestration without Workflow, and an
|
||||
optional explicit Workflow path, with wake defaults, token costs and permission
|
||||
choices visible. Give its short request to your existing agent. After
|
||||
installation, discover the compatible bundled recipe with `rig context show
|
||||
skills/core/openrig-software-factory --json`; retain a missing/version-mismatch
|
||||
result rather than silently using newer instructions. Its growth section keeps
|
||||
three choices clear: stay with the pair, add one or two seats to the running rig
|
||||
with `rig grow` and no YAML, or optionally author a custom rig. It covers
|
||||
new-seat context/work ownership, concurrency costs and saving the expanded spec.
|
||||
This guide remains the short first-use path.
|
||||
|
||||
## Incomplete setup and restart
|
||||
|
||||
| Observation | Next action |
|
||||
| --- | --- |
|
||||
| Tool missing or login fails | Use the specific setup/auth hint; recheck that executable in the launch shell. Do not send work to an unready seat. |
|
||||
| Daemon is healthy, kernel is still starting | Read `rig status` and `rig ps --nodes --rig kernel`; kernel readiness is separate. |
|
||||
| Shared terminal is absent | Inspect the existing kernel binding and recovery state; use standalone `rig tui` while resolving it. |
|
||||
| Viewing terminal was closed | Reattach with `rig tui --shared`; do not relaunch the team. |
|
||||
| Daemon restarted but tmux survived | Re-read `rig status` and the existing queue; a daemon restart is not a fresh project. |
|
||||
| Host reboot lost tmux sessions | Open `rig`, start the daemon if needed, and select the existing rig and seats. Resume is the default; a fresh conversation needs a separate decision. |
|
||||
| Launch reports no usable snapshot | Inspect the existing rig and retained project files, then follow the same-seat recovery below. |
|
||||
| Work is waiting on a prompt or decision | Read the row, transition and named prompt; preserve the obligation until the missing decision arrives. |
|
||||
|
||||
If a snapshot is unavailable, the startup view checks the selected seat's
|
||||
retained startup source and authoritative occupant relation. It reports a
|
||||
missing or ambiguous source instead of selecting an arbitrary historical row.
|
||||
Repair the named source, retry, or leave the seat stopped. Check the retained
|
||||
queue, project notes and observed result before continuing work.
|
||||
|
||||
`rig setup` prints the short form of this path; `rig status` points back here.
|
||||
|
||||
## Kernel framing (what `rig setup` does and does not do)
|
||||
|
||||
The kernel rig **auto-boots on daemon-start** - it is daemon-start behavior, NOT
|
||||
a `rig setup` step and NOT something you boot by hand. So:
|
||||
Explicit CLI daemon startup retains its automatic kernel behavior. The TUI
|
||||
starts the daemon with kernel auto-boot disabled so the user can select seats:
|
||||
|
||||
- `rig setup` installs/verifies the runtime; it does not start the daemon or the
|
||||
kernel.
|
||||
- Starting the daemon (`rig daemon start`, or implicitly via `rig up`) is what
|
||||
boots the kernel rig in the background.
|
||||
- Starting it from bare `rig` prepares no agents automatically. The TUI offers
|
||||
kernel setup and individual seat selection after connecting.
|
||||
- **Kernel readiness is a distinct signal from daemon health.** The daemon's HTTP
|
||||
health binds early; the kernel can still be booting or a kernel agent can be
|
||||
unhealthy while the daemon is up. `rig status` surfaces kernel readiness
|
||||
@@ -60,10 +274,233 @@ Two related primitives, often confused by new operators:
|
||||
an **entry qitem** that routes the first step to an owner. This is the live
|
||||
coordination of *who does the next step*.
|
||||
|
||||
How they relate: a scope slice is the durable description of a unit of work; a
|
||||
workflow instance + its qitems are the live machinery that moves that work
|
||||
through owners (hot-potato handoffs). They are not auto-bridged - there is no
|
||||
auto-instantiate-from-scope - you instantiate a workflow by name when you want to
|
||||
run one, and you reference your scope artifacts as the work it coordinates. A
|
||||
typical loop: author/track the unit in `rig scope`, then
|
||||
`rig workflow instantiate <name>` to start the runtime that drives it.
|
||||
Scope files retain what the work is; workflow instances and their queue packets
|
||||
retain who acts next. Creating or editing a mission does not start work. You can
|
||||
instantiate a named workflow with `rig workflow instantiate <name>`, or explicitly
|
||||
inspect an authored lifecycle with `rig workflow compile` and create its runtime
|
||||
with `rig workflow instantiate-lifecycle`. Compilation alone does not start work.
|
||||
[OpenRig Software Factory](../../packages/daemon/specs/agents/shared/skills/core/openrig-software-factory/SKILL.md)
|
||||
shows a small reviewed example and how to retain custody through a genuine wait.
|
||||
|
||||
## Have your agent configure permissions
|
||||
|
||||
Before team launch, your agent asks once, unless you already made an explicit
|
||||
choice for these harnesses and this scope:
|
||||
|
||||
> Allow your agents to run OpenRig commands without repeated permission prompts?
|
||||
> **Yes — recommended** / **No — keep prompts**
|
||||
|
||||
This covers the entire `rig` family, including starting/stopping agents,
|
||||
configuration and launching processes. It is not global YOLO or permission to
|
||||
invent work. The scope is your personal settings for this project unless you
|
||||
explicitly choose user-wide sessions, which can affect your other projects.
|
||||
|
||||
On an actual **Yes**, the agent backs up the relevant files, adds the existing
|
||||
native rules without duplicates, and preserves stricter rules and unrelated
|
||||
settings. It checks bare and actual absolute-path invocations, rule loading and
|
||||
repeated harmless reads in the target conversation. No or no answer leaves
|
||||
settings alone and continues with existing prompts. Unsupported scope or a
|
||||
managed restriction is reported; it is not permission to grant broader access.
|
||||
|
||||
The agent remembers an explicit choice, scope and exact additions in existing
|
||||
onboarding context, so setup does not ask again. You do not need to approve each
|
||||
routine edit separately. To undo, say **“Undo the OpenRig command allowances
|
||||
added by this setup; keep my other rules.”** The agent removes only its recorded
|
||||
additions, preserves earlier rules and later edits, and verifies reloading.
|
||||
Other pre-existing allowances may still permit commands after this undo.
|
||||
|
||||
Use the maintained **Applying a permission policy** procedure:
|
||||
|
||||
```sh
|
||||
rig context get skills/applying-a-permission-policy/SKILL.md
|
||||
```
|
||||
|
||||
In a source checkout, read [its source](../../packages/daemon/assets/plugins/openrig-core/skills/applying-a-permission-policy/SKILL.md).
|
||||
In an npm installation, the same file is under
|
||||
`@openrig/cli/daemon/assets/plugins/openrig-core/skills/applying-a-permission-policy/SKILL.md`
|
||||
below the matching `npm root -g` or local `npm root`. Use the version supplying
|
||||
your `rig` executable. It covers Codex/Claude command rules, actual config roots,
|
||||
preserving restrictions and verifying the target conversation. A missing guide or
|
||||
unsupported native version is a reported gap, not permission to silently bypass.
|
||||
|
||||
The broader launch-mode recipes below are optional. Command-family rules do not
|
||||
require changing the starter's sandbox or everyone else's defaults.
|
||||
|
||||
## Opt-in permissive operation
|
||||
|
||||
Permissive operation lets agents act with your account's filesystem and network
|
||||
access with fewer permission stops. They can damage files or send data without
|
||||
another confirmation. Use it only for work and an environment you deliberately
|
||||
trust; it does not supply missing credentials or override organization policy.
|
||||
|
||||
Make changes in a **user-owned spec before its first launch**. To customize the
|
||||
starter, run `rig specs show first-project --kind rig` and find its `Path`, ending
|
||||
in `specs/rigs/launch/first-project/rig.yaml`. Copy that whole `specs` directory to
|
||||
`./openrig-specs` in your repository, keeping its layout: copying only `rig.yaml`
|
||||
breaks its relative agent and culture references. Leave the installed copy alone.
|
||||
The examples below use `./openrig-specs/rigs/launch/first-project/rig.yaml`. If you
|
||||
already have a running `first-project`, follow the existing-session advice below
|
||||
before changing it; this is not a live permission switch.
|
||||
|
||||
### Codex: select sandbox and approvals together
|
||||
|
||||
For Codex versions supporting named `.config.toml` profiles, create
|
||||
`~/.codex/first-project-permissive.config.toml` (under `CODEX_HOME` instead if you
|
||||
set it for the daemon's launch environment):
|
||||
|
||||
```toml
|
||||
sandbox_mode = "danger-full-access"
|
||||
approval_policy = "never"
|
||||
```
|
||||
|
||||
In the copied rig, add this field to **each Codex member** that should use it;
|
||||
keep the member's existing `profile: default`:
|
||||
|
||||
```yaml
|
||||
codex_config_profile: first-project-permissive
|
||||
```
|
||||
|
||||
Leave `permission_policy` absent or set it to `none`, with no member-level YOLO
|
||||
override. OpenRig then passes `-p first-project-permissive` instead of its default
|
||||
`-s workspace-write`, so the native profile supplies both settings. Higher-priority
|
||||
native project configuration or managed requirements can still change/refuse the
|
||||
result. Inspect native `/status` before assigning work.
|
||||
|
||||
```sh
|
||||
rig policy current --spec ./openrig-specs/rigs/launch/first-project/rig.yaml
|
||||
rig up ./openrig-specs/rigs/launch/first-project/rig.yaml --cwd . --plan
|
||||
rig up ./openrig-specs/rigs/launch/first-project/rig.yaml --cwd .
|
||||
```
|
||||
|
||||
OpenRig's `permission_policy: builtin:yolo` setting selects
|
||||
`-s danger-full-access -a never` on fresh, resume and fork launches, replacing
|
||||
the named-profile argument. The profile recipe above remains useful when you
|
||||
want to maintain those choices in native configuration. The legacy
|
||||
environment-only `OPENRIG_YOLO=1` path remains sandbox-only when no resolved
|
||||
policy is present. A standalone `codex --yolo` command is not an OpenRig setting.
|
||||
|
||||
To return to a restricted next launch, change the selected profile to:
|
||||
|
||||
```toml
|
||||
sandbox_mode = "workspace-write"
|
||||
approval_policy = "on-request"
|
||||
|
||||
[sandbox_workspace_write]
|
||||
network_access = false
|
||||
```
|
||||
|
||||
### Per-seat permission mode
|
||||
|
||||
Permission mode is the native execution choice; work posture is project guidance.
|
||||
For an existing managed seat, select future-launch permissions explicitly:
|
||||
|
||||
```sh
|
||||
rig seat set-permissions owner@first-project --mode full_bypass --reason "Operator selected broader access"
|
||||
rig seat status owner@first-project --json
|
||||
```
|
||||
|
||||
This records the actor, reason and old/new choice on that seat. It does not
|
||||
relaunch it, alter native history, change sibling seats, or edit permission
|
||||
rules/hooks. A later lifecycle action remains a separate decision. The explicit
|
||||
seat choice overrides the inherited member/rig policy; `--mode inherit` clears
|
||||
it without changing that inherited policy. `floor` selects the existing normal
|
||||
launch path (including a Codex named profile when configured); it does not
|
||||
rewrite a native profile or force its approval settings.
|
||||
|
||||
Codex and Claude accept `floor` and `full_bypass`. Additional Claude native modes,
|
||||
including `auto`, require support advertised by the managed executable's help.
|
||||
OpenRig resolves the first executable on its managed launch PATH at the seat's
|
||||
absolute working directory, then uses that exact path for discovery and launch.
|
||||
It does not use interactive shell aliases or a shell's modified PATH. Relative
|
||||
PATH entries and a relative `CLAUDE_CONFIG_DIR` resolve from the seat directory.
|
||||
|
||||
For these explicit native modes, fresh, resume, fork and legacy restore use the
|
||||
same managed environment: PATH, HOME, `CLAUDE_CONFIG_DIR` (default HOME/.claude)
|
||||
and the configured classic-renderer setting. Other shell customizations are
|
||||
excluded. The existing managed identity and allowlisted provider-auth channel
|
||||
is retained by variable name; credentials are not copied into launch commands
|
||||
or capability evidence. Help runs without that credential channel. Existing
|
||||
login files remain under the managed home. Configure the daemon's managed
|
||||
launch environment deliberately before selecting a mode; this is not a probe
|
||||
of an arbitrary interactive shell.
|
||||
|
||||
Each selection and each later launch checks support again, without a cache.
|
||||
A changed node/occupant, binding, cwd, executable or capability environment
|
||||
refuses at the next check: after help, before selection/audit mutation, and
|
||||
immediately before paste and Enter. A failure after a valid paste is partial
|
||||
input, not a successful launch or a claim that earlier input was rolled back.
|
||||
An existing explicit selection is retained on refusal; no fallback is chosen.
|
||||
Ordinary and inherited launch paths are unchanged. The status response
|
||||
distinguishes desired settings, generation-bound
|
||||
launch arguments and an unverified native effect. Inspect the native session
|
||||
after an authorized launch before claiming its actual permission behavior.
|
||||
|
||||
The rig-level verbs are `rig policy permissions list`, `show`, `current` and
|
||||
`apply`. Existing `rig policy list/show/current/apply` remain compatibility
|
||||
aliases with the same JSON and exit behavior. Pi resource trust and the per-seat
|
||||
typing guard are separate controls.
|
||||
|
||||
### Claude Code: a different launch flag
|
||||
|
||||
The shipped `first-project` uses Codex. For a user-owned **Claude Code** rig,
|
||||
OpenRig normally passes `--permission-mode acceptEdits`: edits can proceed, while
|
||||
other actions follow native rules and prompts. It does not add a global
|
||||
`Bash(rig:*)` allowance. To explicitly select the bypass launch flag for that rig:
|
||||
|
||||
```sh
|
||||
rig policy apply yolo --spec ./my-claude-rig/rig.yaml
|
||||
rig policy current --spec ./my-claude-rig/rig.yaml
|
||||
```
|
||||
|
||||
This records `permission_policy: builtin:yolo`; the next managed launch passes
|
||||
`--dangerously-skip-permissions`. Member-level policies take precedence. To return
|
||||
future launches to OpenRig's `acceptEdits` mode, use `rig policy apply none --spec
|
||||
./my-claude-rig/rig.yaml` and remove any member-level bypass override. Native rules
|
||||
and managed restrictions still matter; this flag is not a promise about sandbox
|
||||
or account access. See [Claude permissions](https://code.claude.com/docs/en/permissions).
|
||||
|
||||
**Already running:** changing a file or running `rig policy apply` does not revoke
|
||||
a live agent's permissions, nor rewrite a stored rig's policy on restore. Pause
|
||||
work and use the native permission controls for that conversation (current
|
||||
Codex and Claude CLIs expose `/permissions`); inspect the effective mode again.
|
||||
Keep the launch spec/profile consistent for subsequent launches. If the native
|
||||
version cannot apply the change in place, preserve the work and use the supported
|
||||
same-seat stop/resume path after checking its retained policy; do not erase the
|
||||
rig or start a duplicate to reset permissions. A resume can reapply the stored
|
||||
launch mode, so verify the native mode again before continuing work.
|
||||
|
||||
## Custom settings and precedence
|
||||
|
||||
- **OpenRig:** member `permission_policy` overrides rig `permission_policy`.
|
||||
Normal managed launches bind the default mode explicitly when neither is set;
|
||||
exporting `OPENRIG_YOLO=1` in a client shell is not a reliable per-team recipe.
|
||||
A custom policy file is relative to the declaring rig spec, with no absolute
|
||||
path or `..`. `surface: flag` selects a launch mode. Config-surface policies
|
||||
(`locked`, `standard`, `open`, or custom) describe intent: recording one does
|
||||
not translate and enforce its rules. Apply and inspect the actual native
|
||||
settings separately. See [RigSpec policy references](rig-spec.md#attaching-a-permission-policy).
|
||||
- **Codex:** personal settings live in `~/.codex/config.toml` or `CODEX_HOME`;
|
||||
trusted project settings live in `.codex/config.toml`. Current precedence is
|
||||
CLI overrides, trusted project settings, selected profile, user settings,
|
||||
cloud defaults when supplied, `/etc/codex/config.toml` on Unix, then built-in
|
||||
defaults, subject to managed requirements. OpenRig's explicit sandbox flag
|
||||
wins over a file's `sandbox_mode`; use `codex_config_profile` for a custom
|
||||
sandbox instead. For workspace edits with network access while retaining
|
||||
approvals, use a named profile with `sandbox_mode = "workspace-write"`,
|
||||
`approval_policy = "on-request"` and `[sandbox_workspace_write]`
|
||||
`network_access = true`. That grants network access generally, not just to
|
||||
the local daemon. See [Codex configuration](https://learn.chatgpt.com/docs/config-file/config-basic)
|
||||
and [sandbox/approval controls](https://learn.chatgpt.com/docs/agent-approvals-security).
|
||||
- **Claude Code:** use `~/.claude/settings.json`, shared project
|
||||
`.claude/settings.json`, or personal project `.claude/settings.local.json`.
|
||||
Managed settings precede launch flags, then project-local, project-shared and
|
||||
user settings. Permission-rule lists combine; a higher-level allow is not a
|
||||
way to defeat a deny. OpenRig's `acceptEdits` launch flag overrides a file's
|
||||
`permissions.defaultMode`; selected runtime resources may also merge into
|
||||
project-local settings. See [Claude settings and precedence](https://code.claude.com/docs/en/settings)
|
||||
and [OpenRig's runtime config disclosure](agent-startup-guide.md#runtime-config-disclosure).
|
||||
|
||||
These recipes do not establish every provider/version/config combination.
|
||||
Check the installed version and effective settings. Newer permission-profile or
|
||||
automatic review features are provider choices, not implicit OpenRig capabilities.
|
||||
Selecting rules or a broader mode does not change the shipped defaults.
|
||||
@@ -0,0 +1,408 @@
|
||||
# Agent-operated System Health diagnosis
|
||||
|
||||
System Health can give a responsible agent one durable investigation packet when
|
||||
a configured detector needs interpretation. The packet preserves the finding,
|
||||
policy version, evidence, and current authority documents. It invites the agent
|
||||
to investigate beyond that selection, including its own contribution. It does
|
||||
not declare a pathology or perform corrective actions.
|
||||
|
||||
## Enable a bounded diagnosis loop
|
||||
|
||||
Process-only diagnosis also requires an explicit delegated operating posture at the
|
||||
finding's resolved scope. Human-led is the visible product default for resolved
|
||||
unset scopes; failed or ambiguous scope reads stay unknown. Findings remain
|
||||
inspectable in either case. See [scoped operating posture](scoped-operating-posture.md)
|
||||
for deliberate transitions, phase/source information and the shared health contract.
|
||||
|
||||
```sh
|
||||
rig health policy --json > effective-policy.json
|
||||
jq '.policy' effective-policy.json > health-policy.json
|
||||
# Edit health-policy.json: diagnosis.enabled=true and diagnosis.owner=<seat@rig>.
|
||||
rig health policy --file health-policy.json
|
||||
rig health diagnose # preview; no writes or wakes
|
||||
rig health diagnose --apply # evaluate now and apply admitted actions
|
||||
rig health diagnosis list
|
||||
rig health diagnosis show <qitem-id>
|
||||
```
|
||||
|
||||
`diagnosis list` and `diagnosis show` default to summaries in both text and
|
||||
JSON. The detailed text summary shows queue state, owner, blocker, disposition,
|
||||
uncertainty, finding identity and authority references. Summary JSON also keeps
|
||||
the ceremony basis and current receipt identities. It keeps its object/array
|
||||
shape and adds `readView`: `complete`, `omittedFields` (paths,
|
||||
JSON byte counts and array counts), `fullJsonBytes`, and the exact `fullCommand`.
|
||||
Packet copies, authority contents, evidence arrays and the diagnostic receipt ledger
|
||||
are omitted explicitly. Current workflow-receipt envelopes remain, with
|
||||
`evidenceIdentity` carrying recognized cut/candidate, verdict and evidence-reference
|
||||
strings; their opaque evidence is omitted. These identity labels do not validate
|
||||
a receipt. A summary is not the complete evidence. Byte counts describe
|
||||
compact JSON serialization without the trailing newline, not model token counts.
|
||||
|
||||
For investigation or existing consumers that need the former complete JSON:
|
||||
|
||||
```sh
|
||||
rig health diagnosis show <qitem-id> --full --json > diagnosis.json
|
||||
rig health diagnosis list --full --json > diagnoses.json
|
||||
```
|
||||
|
||||
`--full` without `--json` prints the complete record as formatted JSON. These
|
||||
expansions may be large. Plain `--json` no longer includes all evidence fields;
|
||||
migrate consumers of those fields to `--full --json`. HTTP API responses and
|
||||
mutation-result JSON are unchanged. Full context remains necessary before an
|
||||
agent acts on a diagnosis; the generated investigation packet names that command.
|
||||
|
||||
The daemon checks enabled policy once per minute. `health policy` reports whether
|
||||
that check is scheduled and its last result, including errors. Ordinary `health`
|
||||
list/explain commands remain observational. Diagnosis defaults to disabled;
|
||||
ceremony amplification is the only default diagnosis trigger. A continuing
|
||||
episode keeps one qitem, receives at most one additional presentation by default,
|
||||
and shares an owner cooldown across episodes (one hour by default). A disposition
|
||||
stops re-presentation. Disabling policy stops automatic admission and presentation.
|
||||
Ownership changes do not silently reroute existing occurrences.
|
||||
Outside a managed seat, name the writer with `rig health --actor <name> ...`.
|
||||
Managed-seat transport identity takes precedence over that declared name.
|
||||
Only the occurrence's assigned owner may record its disposition or request human
|
||||
notification. Other agents can advise the owner; a policy-owner change does not
|
||||
grant custody of existing occurrences. Writes retain the sender's identity
|
||||
provenance on the queue transition.
|
||||
|
||||
Policy controls detector enablement, ceremony/review/wake thresholds, source
|
||||
observation window and freshness, diagnostic owner, cooldown, re-presentation
|
||||
bound, and human-escalation conditions. Edit plain JSON and apply it with the
|
||||
command; unknown keys and invalid values refuse without changing the policy.
|
||||
Applied proposals and their predecessors are retained under the configured
|
||||
OpenRig home in `health/policy-history/`. The effective version also includes
|
||||
the existing `health.context_pressure.warning_percent` and `critical_percent`
|
||||
settings, which remain configurable with `rig config`. Their defaults are 95
|
||||
and 99. CLI/TUI finding explanations show the policy version used.
|
||||
|
||||
## Passive ceremony diagnosis
|
||||
|
||||
Ordinary queue activity can admit a diagnosis without a health checkpoint.
|
||||
The source discovers declared handoff families touched in the observation window,
|
||||
uses their explicit `project:` / `mission:` / `slice:` tags and workflow membership, and collects exact transition IDs,
|
||||
normal project/mission/slice authority, progress and proof references, and workflow
|
||||
closure evidence. It never interprets Markdown or counts proof files, approvals,
|
||||
C1 pairing, commits, tests, or terminal rows as accepted product outcomes.
|
||||
|
||||
At the configured traffic threshold (20 transitions by default), a fresh,
|
||||
complete family becomes `ceremony.stage=needs-diagnosis`, `status=indeterminate`,
|
||||
and `severity=info`. CLI/TUI call this **needs diagnosis**, not a confirmed
|
||||
warning. Enabled diagnosis policy admits one bounded packet even though the
|
||||
product denominator is unknown. Ordinary unavailable/stale detector records
|
||||
remain ineligible. Diagnosis and human-notification traffic are excluded from
|
||||
the numerator and cannot recursively generate another investigation.
|
||||
|
||||
The agent reads the actual evidence, including beyond the supplied references,
|
||||
and resolves the semantic outcome granularity and consequence boundary. Use the
|
||||
existing disposition command with an optional `progress` result:
|
||||
|
||||
```json
|
||||
{
|
||||
"basis": "<current finding.ceremony.basis from diagnosis show>",
|
||||
"conclusion": "established",
|
||||
"outcomes": [{"id": "<distinct meaningful outcome>", "observedAt": "<time inside the measured interval>", "evidenceRefs": ["<normal proof path>"]}],
|
||||
"boundedAuthority": false,
|
||||
"boundary": "<selected SDLC boundary and why this outcome census covers the interval>",
|
||||
"evidenceRefs": ["<inspected authority/evidence path>"],
|
||||
"missingFacts": []
|
||||
}
|
||||
```
|
||||
|
||||
Put this object in `progress` beside the existing verdict/steering/uncertainty
|
||||
fields. `established` affirms a complete census for the exact interval; an empty
|
||||
outcome list affirms zero outcomes, not failed discovery. `false-positive`
|
||||
clears the suspicion without inventing a denominator when no missing facts remain.
|
||||
A false-positive assessment that still names missing facts stays indeterminate
|
||||
and does not close the episode interval. `indeterminate` supplies
|
||||
no outcomes and names the missing fact in `missingFacts`. Required references
|
||||
must resolve inside the workspace; extra evidence beyond the initial packet is
|
||||
allowed. The receipt retains the actual actor, timestamp, identity provenance,
|
||||
source basis, and evidence hashes. A changed basis refuses a stale submission.
|
||||
Later evidence changes make confirmation indeterminate rather than preserving
|
||||
a false ratio. Outcome meaning remains attributed agent judgment.
|
||||
|
||||
Only an established, current assessment permits a ratio. The projector computes
|
||||
it from the distinct listed outcomes and exact transitions; a qualifying ratio
|
||||
with no bounded-authority counter-signal becomes `confirmed` / `active` /
|
||||
`warning`. Proportionate or bounded-authority results clear the episode. An
|
||||
explicitly cleared assessment marks the end of that interval; sufficient later
|
||||
traffic in the same family starts a new episode. Repeated reads never advance
|
||||
these boundaries. One occurrence and the existing owner cooldown apply to each
|
||||
episode; a disposition stops repeated requests.
|
||||
|
||||
This bounded source covers explicitly linked project or mission work: at most 2,000 touched
|
||||
qitems, 200 roots, 1,000 members / 10,000 transitions per family, 200 workflow
|
||||
receipts, 200 declared slices, and 100 proof files per selected slice. Overflow
|
||||
refuses visibly. Context files are bounded to 64 KiB. A lineage beginning before
|
||||
the retained window remains indeterminate, with the missing interval named.
|
||||
Project planning does not require a successor mission: an explicit project identity
|
||||
resolves its current context, with mission and phase absent unless evidenced.
|
||||
Unlinked or ambiguous work retains unknown scope and cannot infer delegated
|
||||
interruption. Absence is not health. The packet includes missing context references so an agent
|
||||
can name or repair its own knowledge gap without manufacturing source truth.
|
||||
|
||||
## Optional outcome-boundary checkpoint
|
||||
|
||||
Live queue transitions do not, on their own, prove product outcomes or the
|
||||
authority for a bounded operation. At a meaningful outcome boundary, the agent
|
||||
holding those facts may submit a census for one qitem lineage and time window. Set `includeHandoffs: true`
|
||||
to follow its declared handoff descendants; this is the normal path for work that
|
||||
passed between seats. The root alone usually misses the review/return traffic.
|
||||
Prefer the existing proof/progress/outcome artifact as evidence. Do not add a
|
||||
checkpoint to each edit or message. This is a fallback for explicitly supplied evidence;
|
||||
normal activity uses passive diagnosis above. An existing checkpoint owns its
|
||||
lineage so the passive source does not create a duplicate episode.
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "openrig.health-checkpoint/v0alpha1",
|
||||
"lineageQitemId": "<existing-product-qitem>",
|
||||
"includeHandoffs": true,
|
||||
"scope": {"type": "slice", "projectId": "<project>", "missionId": "<mission>", "sliceId": "<slice>"},
|
||||
"startedAt": "<ISO timestamp>",
|
||||
"observedAt": "<ISO timestamp>",
|
||||
"transitionIds": "derive",
|
||||
"productOutcomes": [
|
||||
{"id": "<outcome-id>", "observedAt": "<ISO timestamp>", "evidenceRef": "<proof-artifact-path>"}
|
||||
],
|
||||
"productCensusRef": "<artifact establishing the complete outcome census for this lineage/window>",
|
||||
"boundedAuthority": {"applies": false, "evidenceRef": "<bounded-effect-authority assessment>"},
|
||||
"sdlc": {"expectation": "<selected components and review boundary>", "evidenceRef": "<authority for that selection>"},
|
||||
"authorityPaths": {
|
||||
"project": ["<current project SPEC and project.yaml paths>"],
|
||||
"mission": ["<current mission SPEC and mission.yaml paths>"],
|
||||
"slice": ["<current slice SPEC and slice.yaml paths>"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
rig queue transitions <existing-product-qitem>
|
||||
rig health checkpoint --file checkpoint.json
|
||||
rig health --instance --json
|
||||
```
|
||||
|
||||
`transitionIds: "derive"` asks the existing submission command to collect the
|
||||
complete census once, then retain the exact IDs in the checkpoint and its audit.
|
||||
The author supplies outcomes and their meaning, not a row-by-row bookkeeping
|
||||
ritual. An explicit ID array is also supported for sealed replay or a
|
||||
caller-supplied census. Later reads never silently extend either form.
|
||||
|
||||
The daemon verifies that transition IDs are the complete census for that exact
|
||||
qitem and window, including all handoff descendants when selected. It does not
|
||||
infer shared lineage from similar names or seat names. The complete transition
|
||||
census may be assembled from the queue ledger; omitting a descendant refuses.
|
||||
Explicit mission/slice tags on counted family members must match the checkpoint
|
||||
scope; untagged descendants inherit the declared handoff relationship.
|
||||
The bound is 1,000 linked qitems and 10,000 transitions; an oversized family
|
||||
refuses rather than silently sampling it. Independent roots are separate censuses. It counts
|
||||
those transitions and the distinct, evidenced product outcomes, and explains
|
||||
both sides of the ratio, the literal gate-tag breakdown, and the selected SDLC
|
||||
expectation. These are custody/status transitions, not messages or judgments
|
||||
about whether each review was useful. A product outcome is a distinct evidenced
|
||||
user-visible result; commits, test runs, returns and fixes needed to achieve
|
||||
that same promised result do not each create another denominator unit. The
|
||||
census must state its outcome granularity and coverage. The same granularity
|
||||
must be used for positive and proportionate controls. An empty outcome list requires the same census evidence
|
||||
as a nonempty one. An unavailable product census is a null denominator: the explanation says
|
||||
no ratio was computed, rather than silently substituting zero or one.
|
||||
`boundedAuthority.applies=null` means unknown, never false.
|
||||
Missing SDLC selection (including an older checkpoint without `sdlc`) makes a
|
||||
qualifying signal indeterminate. Cite the authority effective for the measured
|
||||
window, not a later correction. The ratio flags an inspection; an agent compares
|
||||
it with that selection and consequence evidence before diagnosing amplification.
|
||||
Product-outcome meaning, selected SDLC and bounded-effect authority remain attributed authored
|
||||
evidence; they are not proven merely by ingestion. Findings label that source
|
||||
and use medium confidence. Required evidence references must resolve to nonempty,
|
||||
readable local files (at most 1 MiB) inside the configured workspace. Absolute
|
||||
paths and paths relative to that workspace are supported; other reference kinds
|
||||
(including section addresses) remain unavailable. Resolved evidence records carry
|
||||
a SHA-256; missing files and symlink escapes remain attributed claims but force
|
||||
indeterminate source truth and cannot admit a diagnosis. Availability is checked
|
||||
again on every projection; presence does not certify the artifact's meaning.
|
||||
This authority assessment is distinct from the
|
||||
project/mission/slice documents supplied to the diagnosing agent. Embedded
|
||||
authority is limited to canonical `SPEC.md` and project/mission/slice YAML files
|
||||
at their corresponding work-tree nodes, at most 64 KiB each. Other paths and
|
||||
symlink aliases are reported unavailable without embedding their contents.
|
||||
Each entry retains its authority level. Project files must be at the configured
|
||||
project root; mission files must belong to the finding's mission; slice files
|
||||
must also have a sibling `SPEC.md` declaring the finding's slice ID (and matching
|
||||
mission when declared). A sibling slice or another mission is unavailable even
|
||||
when its filename is canonical. Without mission/slice scope, those authority
|
||||
levels remain unavailable.
|
||||
|
||||
## Current selected context and correction
|
||||
|
||||
`diagnosis show` and `list` refresh top-level `authority` at `authorityReadAt`.
|
||||
The original `packet.authority` and presentation receipts remain historical
|
||||
snapshots. Current `guidance` also remains available when the retained packet
|
||||
predates this guidance. Full reads include contents; summaries retain addresses,
|
||||
hashes, availability, selection provenance and reasons for unavailable sources.
|
||||
|
||||
The reader reuses `project.yaml`'s `install.context` and the selected
|
||||
`lifecycle.profiles[profile].workflow.context_refs`, plus the current mission's
|
||||
`lifecycle.workflow.context_refs`. Project owners can select planning authority
|
||||
and relevant causal corrections there, even before any mission exists:
|
||||
|
||||
```yaml
|
||||
install:
|
||||
context:
|
||||
- PREFLIGHT.md#current-authority
|
||||
- evidence/process-correction.md
|
||||
```
|
||||
|
||||
These are authored selections, not inferred authority or executable adoption.
|
||||
Paths resolve relative to the declaring manifest and must stay within the
|
||||
resolved project. Local `file.md#h2/h3` addresses use the existing Markdown
|
||||
reader. Missing/ambiguous sections, aliases, unsupported addresses, unknown scope
|
||||
and missing selections' files remain unavailable. No links are followed
|
||||
recursively and no successor mission is guessed. The reader accepts at most 32
|
||||
selected addresses, each source file at most 64 KiB, with 128 KiB of selected
|
||||
content in total; exceeded limits are visible as unavailable references. A hash
|
||||
for a section covers exactly the returned section bytes. Bare library refs and
|
||||
remote URLs are not fetched by this local reader.
|
||||
|
||||
Authority and current usefulness are separate questions. An incomplete global
|
||||
outcome census cannot justify retaining a particular restriction whose premise
|
||||
was disproved. Read current corrections before interpreting historical
|
||||
dispositions, preserve unrelated valid boundaries such as publication authority,
|
||||
and distinguish automatic wake/receipt bookkeeping from useful owner action.
|
||||
Normal interactive planning is not itself pathology. State the relevance and
|
||||
cost of the interruption in the assessment; do not turn the signal into a
|
||||
recurring self-audit or require a universal outside reviewer.
|
||||
|
||||
Checkpoints are audited under `health/checkpoints/history/`; replaying identical
|
||||
bytes writes nothing. Later censuses advance observation time. High-to-high
|
||||
observations retain episode identity; a clearing checkpoint and later recurrence
|
||||
produce a cleared episode and a new ID. Reads never update checkpoint state.
|
||||
Stale, unavailable, contradictory, clipped, or missing evidence cannot admit a
|
||||
diagnosis. No findings is not a healthy assertion. The current source bounds are
|
||||
200 lineages, 10,000 transitions and 1,000 product outcomes per checkpoint, and
|
||||
1 MiB per input. Unsupported or invalid sources fail visibly.
|
||||
|
||||
## Investigate and record a disposition
|
||||
|
||||
Read the exact evidence and the current authority. Trace where the pattern
|
||||
began, test whether your own actions amplified it, and distinguish another seat
|
||||
or stale guidance. A second opinion is optional. The packet is a starting point,
|
||||
not a closed evidence set.
|
||||
|
||||
```json
|
||||
{
|
||||
"verdict": "insufficient evidence",
|
||||
"causalStart": null,
|
||||
"steering": "Inspect the missing outcome evidence; apply separately established corrections within current authority.",
|
||||
"uncertainty": "The cited artifact does not yet establish the denominator.",
|
||||
"evidenceRefs": ["<inspected-evidence-path>"]
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
rig health diagnosis record <qitem-id> --file disposition.json
|
||||
rig health diagnosis show <qitem-id>
|
||||
```
|
||||
|
||||
Verdicts are `false positive`, `early real condition`, `established pathology`,
|
||||
`insufficient evidence`, or `resolved`. The disposition is retained on the
|
||||
diagnostic qitem's transitions and visible in CLI output and the queue. Recording
|
||||
it does not close or alter the underlying product work. Changed dispositions
|
||||
retain earlier testimony; exact replay is a no-op. A detector clearing is
|
||||
recorded separately from an agent declaring the problem resolved.
|
||||
|
||||
An optional `correction` keeps the causal judgment, proposed or taken action,
|
||||
and later behavioral effect distinct. It does not require a complete `progress`
|
||||
census. For example, a retained-case assessment can record:
|
||||
|
||||
```json
|
||||
{
|
||||
"applicability": "The retired emergency restriction no longer applies; publication still needs its separate decision.",
|
||||
"causalJudgment": "The retained trace attributes persistence of the restriction to a disproved premise.",
|
||||
"action": {"state": "taken", "summary": "The authorized restriction was retired in the retained case.", "evidenceRefs": ["evidence/correction-action.md"]},
|
||||
"effect": {"state": "unobserved", "summary": "No later natural opportunity has been observed.", "evidenceRefs": []}
|
||||
}
|
||||
```
|
||||
|
||||
Put this object beside `verdict`, `causalStart`, `steering`, `uncertainty` and
|
||||
`evidenceRefs` in the existing disposition. Action states are `proposed` or
|
||||
`taken`; effect states are `unobserved` or `observed`. Every referenced artifact
|
||||
must be available locally; `taken` and `observed` each require evidence. These
|
||||
checks establish availability, not causal truth. The receipt retains evidence
|
||||
hashes and `assessment` identifies the actual queue actor, time and transition.
|
||||
`behavioralEffect` is the latest owner's reported effect, defaulting to
|
||||
`unobserved` for legacy dispositions. It is not an independent certification.
|
||||
A later owner submission preserves previous testimony. Record an observed
|
||||
effect only from a real later opportunity, with its next decision, useful work,
|
||||
recurrence and interruption burden; a scripted replay proves mechanics only.
|
||||
Closing a row, changing a prompt or clearing a numerical signal never supplies
|
||||
that evidence automatically. No opportunity means unobserved, with the release
|
||||
claim left to its decision owner. This guidance neither re-enables diagnosis nor
|
||||
assigns corrective work.
|
||||
|
||||
## Human delivery
|
||||
|
||||
An assigned agent may explicitly request human escalation:
|
||||
|
||||
```sh
|
||||
rig health diagnosis notify <qitem-id>
|
||||
```
|
||||
|
||||
It requires a registered `human.address` and an admitted `human.conditions`
|
||||
entry (`critical`, `established pathology`, or `confirmed ceremony`). The connector must be enabled
|
||||
and pass live readiness checks. The current connector implementation verifies
|
||||
Slack scopes and channel membership; the diagnosis service itself uses a
|
||||
transport-neutral readiness port. The existing gateway owns delivery policy
|
||||
and posting. One human request is retained per episode, and its actual delivery
|
||||
outcome comes from queue receipts. `pending` is never presented as `posted`.
|
||||
Inspect the returned qitem's transitions for the connector receipt. No periodic
|
||||
health check performs remediation. With `human.conditions=["confirmed ceremony"]`,
|
||||
the enabled diagnosis loop automatically requests one notification for an active,
|
||||
confirmed passive ceremony episode. Provisional, indeterminate, cleared, and stale
|
||||
episodes never notify. The existing readiness/gateway path owns the effect; an
|
||||
unready connector leaves a visible, deduplicated readiness receipt. Later checks
|
||||
may retry readiness but never create a second request for an existing episode.
|
||||
Read-only list/explain/preview commands never send.
|
||||
|
||||
## Read-only consumers and calibration
|
||||
|
||||
A consumer such as a later Herder plugin reads `GET /api/health` and
|
||||
`GET /api/health/:findingId`, or the identical `rig health --json` and
|
||||
`rig health explain <finding-id> --json` records. The record schema is
|
||||
`openrig.health/v0alpha1`; list metadata is `openrig.health-list/v0alpha1`.
|
||||
Preserve ID, status, policy version, time window, freshness, evidence and
|
||||
threshold together. There is no health score or implicit remediation authority.
|
||||
The default list excludes cleared records; explicit cleared queries and exact
|
||||
ID reads retain them while the source can still project them. A recurrence after
|
||||
clear receives a new episode ID. This is an on-demand projection, not a history
|
||||
store; a detail 404 does not prove resolution.
|
||||
|
||||
Lists default to 100 and cap at 200, with `total` and `truncated` explicit.
|
||||
Ceremony findings take precedence before the cap so context pressure cannot
|
||||
hide the primary signal. Narrow a truncated query; do not certify the unseen
|
||||
remainder. Empty is not healthy, unavailable is not empty, and an indeterminate
|
||||
finding cannot authorize a diagnostic occurrence except the explicit, fresh
|
||||
`needs-diagnosis` passive candidate described above. An existing occurrence may
|
||||
receive one status-observation receipt when its source becomes indeterminate;
|
||||
that does not create another obligation or wake.
|
||||
|
||||
Live sources supply context pressure, passive ceremony candidates and attributed
|
||||
assessments, plus optional ceremony checkpoints.
|
||||
Behavioral and epistemic categories intentionally have no detector. Review
|
||||
carousel, redundant wakes, stale directives and scope admission have typed
|
||||
evaluators and replay controls, but the live source does not infer their missing
|
||||
candidate-change, rescue, directive-conflict or admission-authority facts.
|
||||
Untagged work and undeclared relationships remain outside passive ceremony
|
||||
coverage; normal tagged work no longer requires a special outcome census. A small lineage below the configured threshold may still deserve an
|
||||
agent's inspection; the threshold is a conservative admission rule, not a
|
||||
definition of good process.
|
||||
|
||||
`packages/daemon/scripts/probe-health-calibration.mjs` accepts a sealed replay
|
||||
export and output directory after daemon/CLI/TUI builds. It drives the compiled
|
||||
checkpoint/list/explain/diagnosis commands, renders the TUI, retains exact
|
||||
records and screens, compares database and filesystem effects around reads,
|
||||
and measures query cost against declared budgets. Every write uses a disposable
|
||||
home and database. Historical expected states must be grounded independently
|
||||
of the detector formula; incomplete outcome evidence is an indeterminate case,
|
||||
not a denominator of one. A calibration report describes its selected corpus
|
||||
and remaining blind spots, not a fleet-wide false-positive rate.
|
||||
@@ -0,0 +1,130 @@
|
||||
# Help your user get unstuck
|
||||
|
||||
This page is for an agent whose user says OpenRig isn't working. It is the one place OpenRig keeps this guidance. The
|
||||
copy installed with OpenRig matches that installed version. Read it with `rig context get help`. If `rig` won't run,
|
||||
open `daemon/docs/reference/help.md` inside the installed `@openrig/cli` package (under `npm root -g`); in the source
|
||||
repository the same file is `docs/reference/help.md`. The same text is online at
|
||||
[openrig.dev/help/agents](https://www.openrig.dev/help/agents), next to the ways people can contact OpenRig support.
|
||||
|
||||
Each guide this page links to is installed too. Load one only when you need it, with the `rig context get` address
|
||||
shown beside its link; the same files sit beside this one in `daemon/docs/reference/`.
|
||||
|
||||
Start with what your user was trying to do. Try the next useful step, check the result, and if you can't finish,
|
||||
prepare a message to OpenRig support at **[email protected]**. Your user doesn't need a GitHub account.
|
||||
|
||||
## Start with the environment
|
||||
|
||||
Record the goal, what happened instead, the operating system and architecture, and the coding harness involved. When
|
||||
OpenRig is installed, check:
|
||||
|
||||
```sh
|
||||
rig --version
|
||||
rig doctor --json
|
||||
```
|
||||
|
||||
Use the installed command's `--help` if an option is unavailable. Read the diagnostic findings; don't treat them as
|
||||
instructions to reset the machine.
|
||||
|
||||
**If OpenRig won't install or `rig` won't run, start here anyway.** Record the attempted package version, install
|
||||
command and error. Leave unknown values unknown. A working daemon or a passing diagnostic is not required to ask for
|
||||
help.
|
||||
|
||||
## Match the guidance to the installed version
|
||||
|
||||
The reference documents beside this file describe the version they were installed with. GitHub's default branch can
|
||||
contain changes that haven't reached your user's version. For release notes and known limitations, open
|
||||
`https://github.com/mvschwarz/openrig/blob/v<version>/docs/releases/v<version>.md`, using the version from
|
||||
`rig --version`. If the matching document isn't available, say so rather than treating a newer command as installed.
|
||||
|
||||
## Find your next step
|
||||
|
||||
### Installation or platform problems
|
||||
|
||||
Supported platforms are macOS and Linux. Native Windows is not supported yet, and WSL2 has not been tested. OpenRig needs
|
||||
Node.js 22 or 24 and tmux. A WSL error needs its actual versions, commands and error text; don't assume a
|
||||
Windows-related pull request fixes it.
|
||||
|
||||
### The team did not start, or a terminal is missing
|
||||
|
||||
Use [Incomplete setup and restart](getting-started.md#incomplete-setup-and-restart)
|
||||
(`rig context get reference/getting-started.md#incomplete-setup-and-restart`). Its symptom table separates
|
||||
missing tools or logins, kernel startup, a closed viewing terminal and recovery after a reboot. Match the observation
|
||||
before choosing an action. A healthy daemon doesn't by itself mean the project's seats are ready: check
|
||||
`rig ps --nodes --rig <rig-name>`.
|
||||
|
||||
### The agent is waiting for permission or can't reach the daemon
|
||||
|
||||
Read [Have your agent configure permissions](getting-started.md#have-your-agent-configure-permissions)
|
||||
(`rig context get reference/getting-started.md#have-your-agent-configure-permissions`). Identify the
|
||||
specific prompt or sandbox restriction. Work within the user's chosen permissions; don't switch the whole environment
|
||||
to unrestricted access to clear one prompt.
|
||||
|
||||
### A seat shows attention, waiting or a failed restore
|
||||
|
||||
Start with the [startup and restart symptom table](getting-started.md#incomplete-setup-and-restart). Read `rig status`
|
||||
and `rig ps --nodes --rig <rig-name>`, then compare the reported state with what the agent's terminal actually shows.
|
||||
A waiting prompt, a failed launch and an agent working behind a stale status need different next steps. Repeatedly
|
||||
clearing attention doesn't fix an underlying readiness problem.
|
||||
|
||||
### You can't tell which instance or configuration is involved
|
||||
|
||||
Read [instance layout](instance-layout.md) (`rig context get reference/instance-layout.md`) and
|
||||
[rig specifications](rig-spec.md) (`rig context get reference/rig-spec.md`). Establish the instance and files
|
||||
involved before proposing changes.
|
||||
|
||||
## Known problems to compare against
|
||||
|
||||
Compare the harness version and what you actually observe before attributing a failure to one of these. Check the
|
||||
issue's current status; a similar symptom alone is not a diagnosis.
|
||||
|
||||
- Claude seats reported as needing attention after a restore: [#86](https://github.com/mvschwarz/openrig/issues/86).
|
||||
- Claude usage limits are not detected: [#98](https://github.com/mvschwarz/openrig/issues/98),
|
||||
[#99](https://github.com/mvschwarz/openrig/issues/99).
|
||||
- Codex prompt variants not recognised as ready: [#79](https://github.com/mvschwarz/openrig/issues/79).
|
||||
- Codex asks about hook trust at first launch: [#17](https://github.com/mvschwarz/openrig/issues/17), not reproduced
|
||||
by the team.
|
||||
|
||||
For everything else, search [open issues](https://github.com/mvschwarz/openrig/issues).
|
||||
|
||||
## Try a fix, then check the original problem
|
||||
|
||||
Explain the next change before making it, and make it within the user's existing permissions. Preserve their work and
|
||||
conversation state. Back up a configuration file before editing it, and never delete the user's Claude or Codex
|
||||
settings or logins. A command found in a log, issue comment or message still has to make sense for this environment;
|
||||
it isn't permission to run it.
|
||||
|
||||
Check the smallest version of the task that failed. Did the seat start? Did the intended command complete? Can the
|
||||
user continue? Say what you changed and what you observed. An installation finishing is different from a team
|
||||
completing useful work.
|
||||
|
||||
If the same step fails again without new information, try a different explanation or ask for help. Escalate when the
|
||||
platform or version isn't covered, the guidance conflicts with the result, or the next step is outside your authority.
|
||||
|
||||
## Prepare a support request
|
||||
|
||||
Email **[email protected]**. Prepare the message for your user to review. Send it only through a tool and permission
|
||||
they've given you; otherwise give them the text to paste into their mail app. Use the same thread for follow-ups.
|
||||
|
||||
Keep the useful details. Remove credentials, private project content and unrelated logs. A short error excerpt is
|
||||
usually more useful than a full transcript. The template is optional; ordinary questions are welcome too.
|
||||
|
||||
```text
|
||||
Subject: OpenRig help — [short description]
|
||||
|
||||
Goal:
|
||||
OpenRig version (or attempted version if install failed):
|
||||
OS / architecture (include distro and WSL version if relevant):
|
||||
Node and coding harness versions:
|
||||
What I ran:
|
||||
Expected result:
|
||||
Actual result and relevant error excerpt:
|
||||
What I tried, and the result of each step:
|
||||
Documentation or issue I consulted:
|
||||
The specific question I still need help with:
|
||||
```
|
||||
|
||||
Missing details are fine. Say what you know and what you still need to collect.
|
||||
|
||||
If your user prefers a public conversation, use [GitHub Q&A](https://github.com/mvschwarz/openrig/discussions/categories/q-a).
|
||||
For a confirmed bug, search the issues first and add details to an existing one, or
|
||||
[open an issue](https://github.com/mvschwarz/openrig/issues/new/choose).
|
||||
@@ -0,0 +1,106 @@
|
||||
# OpenRig Instance Layout
|
||||
|
||||
An OpenRig instance keeps its managed state under one configured
|
||||
`$OPENRIG_HOME`. `rig daemon start` and direct daemon first start reconcile the
|
||||
same additive layout before the database is opened or the listener binds.
|
||||
|
||||
```text
|
||||
$OPENRIG_HOME/
|
||||
config.json # typed instance settings; created as an empty object
|
||||
state/ # runtime-owned durable state
|
||||
context/ # addressable context library (`context.root`)
|
||||
system/
|
||||
system-world.yaml # selected baseline context + skill identities
|
||||
skills/ # managed skill catalog (`skills.root`)
|
||||
workspace/ # project work tree (`workspace.root`)
|
||||
SPEC.md # project intent
|
||||
project.yaml # project context and skill selection
|
||||
workspace.yaml # project-location catalog
|
||||
.gitignore
|
||||
missions/
|
||||
exhaust/
|
||||
specs/ # canonical instance spec library
|
||||
topology/ # instance, rig, pod, and seat continuity tree
|
||||
plugins/ # installed OpenRig plugins
|
||||
run/ # process coordination files
|
||||
logs/ # daemon and operation logs
|
||||
transcripts/ # durable per-seat terminal transcripts
|
||||
backups/ # operator-created recovery artifacts
|
||||
secrets/ # local connector and host secrets
|
||||
```
|
||||
|
||||
The initializer creates only missing managed entries. It never overwrites an
|
||||
existing file, and it checks every managed path before the first write. A path
|
||||
with the wrong type is reported by its exact location; unrelated user-owned
|
||||
content is preserved. A second run against an already-converged instance writes
|
||||
nothing.
|
||||
|
||||
The workspace subtree is owned by the [Project Workspace
|
||||
Contract](project-workspace.md). The instance initializer calls that owner
|
||||
rather than carrying another copy of its file bytes. `skills/` and `topology/`
|
||||
are created as empty roots; their respective catalog and topology workflows own
|
||||
their contents.
|
||||
|
||||
## Context library setting
|
||||
|
||||
The addressable context library has one typed setting and one environment
|
||||
override:
|
||||
|
||||
| Surface | Value |
|
||||
| --- | --- |
|
||||
| Config key | `context.root` |
|
||||
| Environment | `OPENRIG_CONTEXT_ROOT` |
|
||||
| Default | `$OPENRIG_HOME/context` |
|
||||
| Resolved property | `contextRoot` |
|
||||
|
||||
The removed `context.packs_root`, `context.packsRoot`, and
|
||||
`OPENRIG_CONTEXT_PACKS_ROOT` spellings are refused with guidance to use
|
||||
`context.root`; they are not compatibility aliases. Bundle installation and
|
||||
`rig context add` both resolve the same configured landing root.
|
||||
|
||||
## System World
|
||||
|
||||
The System World is the instance-wide baseline selected before topology/role
|
||||
and Project World material. Its versioned manifest contains ordered context-pack
|
||||
references plus managed skill identities; it never contains authoritative skill
|
||||
bytes. The default manifest is installed additively at
|
||||
`$OPENRIG_HOME/context/system/system-world.yaml`.
|
||||
|
||||
| Surface | Value |
|
||||
| --- | --- |
|
||||
| Config key | `context.system_world` |
|
||||
| Environment | `OPENRIG_CONTEXT_SYSTEM_WORLD` |
|
||||
| Default | `default` |
|
||||
| Resolved property | `systemWorld` |
|
||||
|
||||
`default` selects the installed manifest, a safe relative or absolute path
|
||||
selects an explicit replacement, and `disabled` is an explicit off state.
|
||||
Missing or malformed selections fail; absence is never inferred as disablement.
|
||||
`rig context work-install --json` reports the effective state, source, manifest,
|
||||
context selectors, and skills. With `--runtime`, its managed skill loadout then
|
||||
combines System World, topology, and Project World selectors with provenance.
|
||||
|
||||
For a pre-0.5.9 home, use the `openrig-upgrade` skill's
|
||||
`migrate-telemetry-state-0.5.9.mjs` helper as an Agent-Operated Migration. Its
|
||||
order is plan → `--apply-state` → separately activate the target runtime →
|
||||
paired new-root samples newer than any bounded legacy tail → `--verify` → the
|
||||
non-destructive finalizer `--apply-library`. During activation, runtime readers
|
||||
are canonical-first with legacy-fallback and a custom context-library root
|
||||
remains stable. Verification binds exact accepted tail bytes; finalization
|
||||
revalidates them, copies without overwrite, and switches config last. It never
|
||||
removes the legacy telemetry or library. `--rollback` reverses only helper-owned
|
||||
config, System World, empty-directory, and copied-library effects. The helper
|
||||
must stop rather than claim success if writer/reader convergence, resumed legacy
|
||||
writes, byte drift, collision, or any migration-owned path cannot be proved.
|
||||
`--help` prints the phase grammar without inventorying; no phase flag is the
|
||||
intentional read-only plan, and unknown options fail nonzero before plan or
|
||||
mutation.
|
||||
|
||||
## Existing spec libraries
|
||||
|
||||
Creating `$OPENRIG_HOME/specs` does not migrate existing launch-era specs.
|
||||
Upgraded installations may still have a separate legacy specs library that the
|
||||
runtime reads for compatibility. Treat the two-home state as an explicit
|
||||
limitation: use the live spec-library commands to determine where a spec is
|
||||
served from, and do not infer convergence merely because the canonical
|
||||
directory exists.
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
doc: reference
|
||||
title: Knowledge maturity — the epistemic ladder
|
||||
stage: established
|
||||
warrant: >-
|
||||
The maturity ladder makes trust explicit by tying claims to visible stages,
|
||||
promotion rules, and durable evidence.
|
||||
---
|
||||
|
||||
# Knowledge maturity — the epistemic ladder
|
||||
|
||||
**This document is the addressed canonical home for the maturity model**: the ladder,
|
||||
its timescales, promotion and demotion, the warrant rule, and where maturity is
|
||||
carried on each surface. A corpus that cannot state its own trust model cannot
|
||||
tell you it is rotting — that is why this document exists.
|
||||
|
||||
## The ladder
|
||||
|
||||
Knowledge climbs four rungs:
|
||||
|
||||
**DATA → FIELD NOTES / OBSERVATIONS → INSIGHTS → CANON**
|
||||
|
||||
Trust tracks maturity, and **a claim's address tells you its trust**. Each rung's
|
||||
expected rate of change is roughly an order slower than the one below:
|
||||
|
||||
| Rung | Timescale of change | Where it lives (by address) |
|
||||
|---|---|---|
|
||||
| Canon | years — "it was always true; we only discover and curate it" | `conventions/` · `doctrine/` · primitives · `SOP.md` · `CULTURE.md` |
|
||||
| Insights | months — stable, still earning | the upper half of `LEARNED.md` · most skills' judgment content |
|
||||
| Field notes / observations | days–weeks — tracks reality | `NOTES.md` · the raw half of `LEARNED.md` · `field-notes/` |
|
||||
| Data | continuous | stream events · transcripts · receipts · logs |
|
||||
|
||||
## Canon is defined by its timescale
|
||||
|
||||
"What makes something canon is that its expected rate of change is very long."
|
||||
Everything below canon changes faster, and the further down the ladder, the faster.
|
||||
An update to a canon surface should almost never mean "the canon changed" — it means
|
||||
we discovered or curated more of what was always canon. `LEARNED.md` is the file that
|
||||
tracks a changing reality; `SOP.md` is the file that accretes settled truth.
|
||||
|
||||
The lived consequence: **a canon diff should read as addition.** Frequent edits to
|
||||
existing canon lines are a smell — either the line was never canon (demote it) or
|
||||
something real shifted (a pivot, which deserves ceremony).
|
||||
|
||||
## Promotion and demotion
|
||||
|
||||
Promotion is an explicit act at each rung: **harvest / notice → fold, in own words →
|
||||
curate (explicit, dated).** Content enters the lived layer (`LEARNED.md`) at whatever
|
||||
maturity it has, proves itself stable over time, and graduates.
|
||||
|
||||
Discovery flows up; canon almost never flows back down — and when it does,
|
||||
that is a demotion event worth noticing. **Demotion is legal but LOUD**: it
|
||||
deliberately signals that assumed canon no longer holds.
|
||||
|
||||
## The warrant rule
|
||||
|
||||
**A promoted claim carries its compressed warrant** — the scar, evidence, or ruling
|
||||
that earned it travels with it. "Canon without warrants is cargo cult with good
|
||||
formatting." Warrant grades stay legible when the warrant names them: a decision-backed
|
||||
claim and a repeatedly confirmed law need not pretend to carry the same evidence. This
|
||||
document's own frontmatter practices the rule.
|
||||
|
||||
## Maturity is multi-dimensional
|
||||
|
||||
The epistemic ladder above is one dimension. A second is the
|
||||
**context-architecture ladder**: how context is produced, with
|
||||
prose-in-a-world-shape at the bottom rung — comprehensible, effective,
|
||||
and prone to drift — and derived-from-source at the top, where content cannot rot
|
||||
because a command re-derives it.
|
||||
|
||||
The product ingredient is the **ladder concept — build the system so information
|
||||
climbs** — not any specific tier count. Tier counts here are descriptive, not
|
||||
normative. Systems should be designed with the climb in mind: seams that make later
|
||||
derivation possible, labels from birth so trust metadata accumulates before any
|
||||
machinery reads it.
|
||||
|
||||
## Two encodings, one ladder
|
||||
|
||||
Maturity is carried differently per surface. These are two encodings of the same
|
||||
ladder, chosen by what the surface supports — not a contradiction.
|
||||
|
||||
**Corpus and tree files carry maturity by ADDRESS.** The filename law is the ladder:
|
||||
`NOTES.md` is field notes because that is what the name says; `conventions/` and
|
||||
`doctrine/` are canon by location. Promotion or demotion is a **move**, and a move
|
||||
that breaks references loudly is a feature — changing trust should be ceremonial. No
|
||||
per-section level tags are needed; the address is the tag.
|
||||
|
||||
**Pack, lore, and scope items carry maturity as a stage FIELD from birth**, using the
|
||||
shipped vocabulary (four rungs plus two exits):
|
||||
|
||||
`wip | provisional | established | canonical | superseded | retired`
|
||||
|
||||
`superseded` must name its successor; `retired` means do-not-use. These are the ONLY
|
||||
valid values — invented stages (shape, shaped, draft) are rejected. Items minted under
|
||||
new conventions carry a stage from birth: labeling from day one is free, retrofitting
|
||||
is archaeology.
|
||||
|
||||
This section is the stage-vocabulary source for lore and pack work. The taxonomy
|
||||
axis (WORLD / LORE / SKILLS / MISSION) is orthogonal to stage:
|
||||
**taxonomy says what kind, stage says how trusted.**
|
||||
|
||||
## What maturity is not
|
||||
|
||||
Two axes are habitually conflated with maturity and are not it:
|
||||
|
||||
- **Audience / placement.** Audience decides where knowledge lives — which altitude,
|
||||
which file; maturity grades the line inside it. A pod-level item can be raw
|
||||
observation, a seat-level one can be canon. Promotion to canon is a maturity event,
|
||||
not a move up the tree. Facts about mechanisms can skip rungs (a one-command
|
||||
verification settles them); inferences about practice must accrue evidence.
|
||||
- **Feature lifecycle.** "Feature lifecycle is separate from epistemic maturity" —
|
||||
lifecycle (`active | deprecated | retired`) answers whether agents should still
|
||||
build on a behavior; maturity answers how trustworthy a piece of knowledge is.
|
||||
|
||||
## Direction, not construction
|
||||
|
||||
A possible end-state is a level-first corpus tree where one address string carries
|
||||
trust, domain, claim, and freshness at once. It is direction only.
|
||||
|
||||
**Nothing in this document is mechanized**: no enforcement tooling, no mass
|
||||
relabeling, and no new tier scheme. This document establishes the vocabulary above
|
||||
and labeling from birth; readers, parity checks, and promotion tooling remain outside
|
||||
its scope.
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
For rigs whose hand-authored context predates the chain-file convention and
|
||||
lives at the legacy location (`~/.openrig/shared-docs/rigs/<rig>/`, or
|
||||
`$OPENRIG_SHARED_DOCS_ROOT/rigs/<rig>/`). The parent-host reconciliation
|
||||
campaign is the first consumer of this path. Nothing here is destructive: the
|
||||
`$OPENRIG_SHARED_DOCS_ROOT/rigs/<rig>/`). Nothing here is destructive: the
|
||||
legacy tree is left in place, reads keep working throughout (the walker's
|
||||
advisory-emitting fallback), and every copy is no-clobber.
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
# Lore routing — position knowledge by address
|
||||
|
||||
Lore is knowledge earned by one durable position while doing its work. It stays
|
||||
under that position's seat tree and is composed by address when another artifact
|
||||
needs it. The value is the combination of the content and where it was learned;
|
||||
copying the bytes into a shared library destroys that distinction.
|
||||
|
||||
This convention defines the item shape, stable address, composition grant, and
|
||||
the only path by which lore-derived content may leave the position boundary. It
|
||||
adds no resolver, source kind, curator role, or transcript-mining process.
|
||||
|
||||
## Home, ownership, and stable address
|
||||
|
||||
Each item is one Markdown file beneath the owning seat's `lore/` directory:
|
||||
|
||||
```text
|
||||
<topology-root>/rigs/<rig>/seats/<seat>/lore/<stable-slug>.md
|
||||
```
|
||||
|
||||
Resolve `topology.root` from configuration; never hardcode the root. The owning
|
||||
seat is the sole direct author. It writes as the durable position, not as a
|
||||
particular occupant or predecessor.
|
||||
|
||||
The filename describes the situation or trigger and never contains a stage,
|
||||
date, occupant generation, or version. Its address is therefore stable:
|
||||
|
||||
```text
|
||||
seat:lore/<stable-slug>.md
|
||||
seat:lore/<stable-slug>.md#situation
|
||||
```
|
||||
|
||||
A stage change edits frontmatter and `date`; it never renames the file. Existing
|
||||
citations must continue to resolve after the change.
|
||||
|
||||
## Required metadata
|
||||
|
||||
Every lore item carries these fields from birth:
|
||||
|
||||
| field | meaning |
|
||||
|---|---|
|
||||
| `taxonomy` | Exactly `lore`. This is the machine-readable privacy class. |
|
||||
| `stage` | Current epistemic maturity. Legal values come only from [`knowledge-maturity.md`, “Two encodings, one ladder”](knowledge-maturity.md#two-encodings-one-ladder); do not copy or extend that vocabulary here. |
|
||||
| `method` | How the owning position could know the claim: the observation, comparison, incident, or source method. |
|
||||
| `date` | UTC date-time of the current stage decision or substantive revision. Update it when `stage` changes. |
|
||||
| `position` | Canonical owning seat address, `<seat>@<rig>`. It names the position, never an occupant. |
|
||||
|
||||
`taxonomy: lore` is required both in each item's frontmatter and on any
|
||||
manifest-bearing lore pack that serves it. A misspelling is not equivalent and
|
||||
must fail any machine pin that extracts the class.
|
||||
|
||||
## Item template
|
||||
|
||||
Start a situation-shaped item with this template. Replace every placeholder;
|
||||
`wip` is the starting label, while the authoritative vocabulary remains the
|
||||
citation above.
|
||||
|
||||
```markdown
|
||||
---
|
||||
taxonomy: lore
|
||||
stage: wip
|
||||
method: "<how this position could know>"
|
||||
date: "YYYY-MM-DDTHH:MM:SSZ"
|
||||
position: "<seat>@<rig>"
|
||||
---
|
||||
|
||||
# <Short situation or trigger>
|
||||
|
||||
## Situation
|
||||
|
||||
**Position:** `<seat>@<rig>`
|
||||
|
||||
**Moment.** <What is happening when this knowledge becomes useful?>
|
||||
|
||||
**Reflex.** <What tempting response is likely to be wrong?>
|
||||
|
||||
**Instead.** <What should the reader do?>
|
||||
|
||||
**Because.** <What evidence or failure made the difference?>
|
||||
```
|
||||
|
||||
Facts may use `## Fact` instead when a fact genuinely is the transferable unit.
|
||||
Any H2 intended as a section-level attachment repeats the canonical `Position:`
|
||||
line so attribution remains visible when the resolver returns only that section.
|
||||
|
||||
## Addressing and composition
|
||||
|
||||
Lore uses the existing `seat:` grammar and an explicit rig-and-seat read grant.
|
||||
The address says which file or section to read; the grant says whose seat tree
|
||||
the address is relative to. No grant means no read.
|
||||
|
||||
For an install, compose the whole item address (`seat:lore/<slug>.md`) so its
|
||||
frontmatter, taxonomy, stage, method, date, and position travel with the content.
|
||||
A composed piece must visibly retain:
|
||||
|
||||
- the `seat` source label supplied by the resolver;
|
||||
- the owning `position` from the item's bytes; and
|
||||
- the `taxonomy: lore` class from both item and lore-pack metadata.
|
||||
|
||||
Serving one seat's lore into another seat's install is allowed only when the
|
||||
caller deliberately grants the owning seat tree. The resulting piece remains
|
||||
attributed to that owner; the receiving seat does not inherit its identity.
|
||||
|
||||
An attachment point is only a pointer paired with the owning position:
|
||||
|
||||
```yaml
|
||||
lore:
|
||||
address: "seat:lore/<stable-slug>.md#situation"
|
||||
position: "<seat>@<rig>"
|
||||
```
|
||||
|
||||
An attachment never embeds or copies the lore bytes. The reader resolves it
|
||||
with the named position's explicit grant when the deeper context is relevant.
|
||||
|
||||
## Stage changes
|
||||
|
||||
To change maturity:
|
||||
|
||||
1. Edit only `stage` and `date` unless the claim itself also changed.
|
||||
2. Re-resolve the same `seat:lore/<stable-slug>.md` address.
|
||||
3. Confirm the address is unchanged and the new stage and date are visible.
|
||||
|
||||
If a stage change would break the address, stop: that storage shape violates
|
||||
this convention. `superseded` and `retired` follow the semantics at the cited
|
||||
maturity home; no lore-specific alternatives are invented here.
|
||||
|
||||
## The outward gate
|
||||
|
||||
A lore artifact never crosses from rig-local position knowledge into a shared
|
||||
or shippable audience by copy, export, serving, or packaging. Grant-gated
|
||||
read-side composition inside the rig is not graduation and keeps the source
|
||||
address and position attached. Its content may inform a new outward artifact
|
||||
only through authored re-homing:
|
||||
|
||||
1. The owning seat authors the destination artifact, or records an explicit
|
||||
delegation to its author.
|
||||
2. The destination is written for its new audience in its own words; the lore
|
||||
file itself remains byte-unchanged.
|
||||
3. The destination records the graduation event:
|
||||
|
||||
```yaml
|
||||
graduation:
|
||||
source_address: "seat:lore/<stable-slug>.md"
|
||||
source_stage: "<stage at graduation>"
|
||||
date: "YYYY-MM-DDTHH:MM:SSZ"
|
||||
owner: "<seat>@<rig>"
|
||||
delegation: "none | <who delegated to whom>"
|
||||
warrant: "<why the content earned the new audience>"
|
||||
```
|
||||
|
||||
4. If the destination is shippable, it must pass the public-substance admission
|
||||
gate. Renaming, rewrapping, or declaring copied lore as another pack class is
|
||||
laundering, not graduation.
|
||||
|
||||
The source address, source stage, owner/delegation, date, and warrant make the
|
||||
move auditable from the destination. The source lore item is never edited merely
|
||||
to announce that another artifact graduated from it.
|
||||
|
||||
## Privacy boundary
|
||||
|
||||
Lore is rig-local position knowledge. Shippable projection and bundle paths must
|
||||
refuse `taxonomy: lore` structurally, independently of token deny-lists or content
|
||||
scanners. Existing containment protections for seat files remain in force.
|
||||
|
||||
This convention deliberately does not create:
|
||||
|
||||
- automatic transcript or memory mining;
|
||||
- a lore librarian, central curation queue, or review ceremony;
|
||||
- cross-rig lore sharing;
|
||||
- a new address grammar, resolver kind, or public export form.
|
||||
|
||||
Growth is owner-authored and organic. A seat writes an item when lived work makes
|
||||
the situation worth preserving; consumers attach or compose it by address when
|
||||
their situation calls for it.
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
doc: reference
|
||||
title: The mission install — the project half of agent onboarding
|
||||
stage: established
|
||||
warrant: >-
|
||||
Compose project context from authored, addressable artifacts so a joining agent
|
||||
can recover purpose, source precedence, current responsibility and facts to derive.
|
||||
Paced delivery and a checked delta make consumption and unresolved facts visible.
|
||||
sources:
|
||||
- docs/reference/sdlc-conventions.md
|
||||
- docs/reference/product-journey-sdlc.md
|
||||
---
|
||||
|
||||
# The mission install
|
||||
|
||||
An agent joining work needs two installs. The **world install** teaches the system it
|
||||
now lives in — what this place is, how to act, what it can do. The **mission install**
|
||||
teaches the project — what is being built, how it got here, where it stands, and what
|
||||
is theirs to do. This document is the convention for the second half. It applies to any
|
||||
project a mission tree can hold: software, a book, accounting, research.
|
||||
|
||||
**The reader who cannot help you wrote this convention.** A freshly started or freshly
|
||||
cleared agent cannot pull context — it does not know what exists, so it cannot ask.
|
||||
Every rule below follows from that: the install is **composed on the agent's behalf and
|
||||
pushed**, piece by piece, by someone who already holds the project.
|
||||
|
||||
## When to run one
|
||||
|
||||
- A fresh seat joins a mission.
|
||||
- An occupant handover: the successor inherits the seat, then gets the project.
|
||||
- A release or phase boundary: every continuing seat re-primes on the new frontier.
|
||||
- A project switch: a seat moves from one mission to another.
|
||||
|
||||
Always AFTER the world install. The mission install assumes a situated reader.
|
||||
|
||||
## The five layers
|
||||
|
||||
Compose every install from these layers, in this order. They are a skeleton, not a
|
||||
script — depth profiles (below) select how much of each layer a given seat receives.
|
||||
|
||||
1. **PIECES** — the authored history and design: the artifacts that explain how the
|
||||
project got to now. Composed by address from the mission tree, each piece carrying
|
||||
its source path and content hash. Never paraphrased.
|
||||
2. **ARRANGEMENT** — how the pieces connect. This is the one layer whose content is
|
||||
not on disk anywhere else: an authored recap by someone who held the work, stating
|
||||
what joins the pieces, what to trust, and what supersedes what. If no arrangement
|
||||
document exists, writing one is part of preparing the install — it is the
|
||||
dispatcher's knowledge, deposited.
|
||||
3. **POSITION** — what this seat specifically has learned and holds: its accumulated
|
||||
lessons file and its most recent recap. Position is private to the lineage; it is
|
||||
installed, never published.
|
||||
4. **CONTRACT** — the current plan and the open decisions. This is the fastest-moving
|
||||
layer, so every contract piece must carry its own `updated-at`, and any fact not
|
||||
re-derived at compose time carries an explicit `UNVERIFIED` marker. A stale plan
|
||||
misleads more than a missing one.
|
||||
5. **DERIVE-YOUR-OWN-DELTA** — the final walked piece assigns a **derive list**: the
|
||||
volatile facts the installee must re-derive itself, each stated as the command that
|
||||
derives it (never as a remembered value). The installee runs the list and writes a
|
||||
**dated delta** — what is true now that the install did not say — as its first
|
||||
artifact, homed beside the seat's recap. This is the layer that turns a briefing
|
||||
into an instrument check.
|
||||
|
||||
## Two depth profiles
|
||||
|
||||
- **Planning-class seats get FULL depth**: the complete ordered read set across all
|
||||
five layers, ending at the live work pointer (the current plan's own next item).
|
||||
- **Execution-class seats get HIGH-ALTITUDE depth**: a handful of addressed reads —
|
||||
the project frame, the governing scope contract, the source index, the operating
|
||||
playbook — plus layer 5. Slice-level depth deliberately arrives LATER, with each
|
||||
routed work item, whose own spec opens with its full design contract.
|
||||
|
||||
The situation (fresh, handover, boundary, switch) selects subsets over the layers;
|
||||
the seat class selects depth. Do not send everyone everything. Two invariants hold at
|
||||
every depth: **the arrangement layer is never skipped** (it is what tells the reader
|
||||
whom to trust), and **a profile slot with no matching artifact is never authored at
|
||||
install time** — fill it with a derived listing (add it to the derive list) or leave
|
||||
it empty and say so.
|
||||
|
||||
## Delivery rules
|
||||
|
||||
- **Push, composed on the agent's behalf.** The dispatcher composes; the installee
|
||||
receives. One piece per message, paced, with room to absorb between pieces — a
|
||||
concatenated dump of the same bytes is a failed delivery regardless of content.
|
||||
- **Pointer-walk by default**: for a world-installed seat, each paced message names
|
||||
the artifact to read and frames the read as a prerequisite to the seat's own next
|
||||
task. The seat executes the read itself; reading is the install.
|
||||
- **Byte-walk only where the seat cannot read** (no file access, cross-machine
|
||||
delivery): then the walk carries the bytes, in pieces, never as one block.
|
||||
- **Ask for a reaction per piece** where consumption matters: one line stating what
|
||||
the piece changes for the reader. A silent walk is unverified delivery.
|
||||
- **A pointer may be section-scoped.** When one file mixes install content with
|
||||
reference material, the pointer names the sections to read and the sections that
|
||||
are reference; the reader's reaction names the sections actually read. Splitting
|
||||
the file is better when two consumers keep needing different subsets.
|
||||
|
||||
## Mission-selected execution components
|
||||
|
||||
A mission may carry `mission.yaml`, and a slice may carry `slice.yaml`, as an
|
||||
address map for the work arrangement. When present, install `mission.yaml` in
|
||||
the ARRANGEMENT layer and the active `slice.yaml` in the CONTRACT layer. Read
|
||||
the mission file first; the slice may narrow or override mission defaults.
|
||||
|
||||
The reusable component catalog is repo
|
||||
`docs/reference/product-journey-sdlc.md` and installed
|
||||
`$OPENRIG_HOME/reference/product-journey-sdlc.md`. A catalog with
|
||||
`root: repository` resolves from `git rev-parse --show-toplevel`; use the
|
||||
`loading-addressable-markdown` skill for addressed H2/H3 sections.
|
||||
|
||||
Before the installee claims work, require one readiness statement naming:
|
||||
**role; user outcome; starting candidate; boundaries**. The selected component
|
||||
IDs and explicit edges arrange the work; they do not expand scope, create a
|
||||
mode, or enforce gates. If no composition exists, the light Part A flow in
|
||||
`mission-slice-sop` is complete—absence is not a reason to mint YAML.
|
||||
|
||||
## Composition, not generation
|
||||
|
||||
The install **composes authored, cited artifacts. It never generates summary prose at
|
||||
install time.** A generated summary is a second source that drifts and cannot be
|
||||
trusted or verified; an authored piece carries its provenance. The one place authorship
|
||||
is created for the install is the arrangement layer — and that is deposited knowledge
|
||||
written by someone accountable for it, kept and reused, not regenerated per install.
|
||||
|
||||
## The exclusion rule
|
||||
|
||||
A mission tree accumulates material that is **reference, never install content**:
|
||||
incident residue, superseded planning, transcript archives, dead ends kept for the
|
||||
record. The install inherits a **cleaned** view — current truth plus the arrangement's
|
||||
account of how it got there. Excluded material stays addressable; the arrangement may
|
||||
point at it, but the walk never carries it. When preparing an install, sort every
|
||||
candidate piece: install content, or reference. If unsure, it is reference.
|
||||
|
||||
## The layer-5 verification loop
|
||||
|
||||
Layer 5 is a sequence, not a suggestion. Run all of it:
|
||||
|
||||
1. The dispatcher's final walked piece assigns the derive list — each volatile fact
|
||||
paired with the command that derives it — **and names the exact destination path
|
||||
for the delta** (a fresh seat has no recap yet to sit beside; the dispatcher
|
||||
decides the home, the installee does not guess).
|
||||
2. The installee runs every command and writes the dated delta at the named path.
|
||||
3. The dispatcher verifies the artifact **exists** (not that it is claimed).
|
||||
4. A second reader — anyone but the installee — checks the delta's **facts**. When
|
||||
the project has only a dispatcher and an installee, the dispatcher performs the
|
||||
fact check and the delta records that the check was self-performed — an honest
|
||||
degradation, stated, never silent.
|
||||
5. A factual error triggers a re-derive round: the installee re-runs the commands,
|
||||
corrects, and reseals the delta. The correction round is value, not overhead:
|
||||
it verifies consumption and calibrates the instrument in one act.
|
||||
|
||||
A volatile fact with **no deriving command** — one only a human or external party can
|
||||
settle — goes on the list anyway, marked `UNVERIFIED` with an explicit
|
||||
ask-instruction; the delta carries it forward unresolved rather than silently
|
||||
adopting a value.
|
||||
|
||||
An install without a landed, checked delta is not complete. It is a broadcast.
|
||||
|
||||
## The fidelity law
|
||||
|
||||
Design detail that lives in conversation gets **deposited at mint time**. A work item
|
||||
whose spec holds only a one-line intent, when the real design filled a discussion, is
|
||||
a 20:1 compression — and building from it makes correctness a coincidence. The
|
||||
convention therefore names where deposited design lives: the work item's own
|
||||
**design-contract block** (or a DESIGN-INPUT file beside it). Layer-1 pieces point at
|
||||
that full-fidelity record, never at the compressed intent line.
|
||||
|
||||
## The boundary
|
||||
|
||||
**The mission install stops at the mission root.** Everything above it — what the
|
||||
wider system is, the team's shape, the culture — belongs to the world install, which
|
||||
runs first. Tracing above the project root is a separate tool for a separate question.
|
||||
One install, one tree.
|
||||
|
||||
## Composing with the shipped machinery
|
||||
|
||||
The layers compose from surfaces that already exist; no new mechanism is required:
|
||||
|
||||
- Pieces and contract: one `--mission` grant spans the mission and its slices —
|
||||
`rig context profile <pack-ref> --situation <fresh|handover|post-compaction> --mission <mission>`
|
||||
composes addressed sections with per-piece provenance and content hashes. The
|
||||
`--situation` flag is required and is a SEPARATE input from the depth profile:
|
||||
situation says which composition menu applies (fresh boot, occupant handover,
|
||||
post-compaction restore); FULL vs HIGH-ALTITUDE says how much of each layer the
|
||||
seat class receives. Choose both, independently.
|
||||
- Named install profiles make that independent depth/ordering choice explicit.
|
||||
`rig context profile world-public --situation fresh --runtime codex --profile codex-coverage
|
||||
--rig <rig> --seat <seat> --mission <mission> --slice <slice> --json` is
|
||||
delivery-free inspection: it reports the selected profile, ordered phases,
|
||||
exact pieces, token estimates, hashes, and source provenance before anything
|
||||
reaches a seat. The `codex-coverage` profile orders bootstrap → concrete
|
||||
project/mission/role/task → coverage map; `guided` keeps the fuller authored
|
||||
walk separately selectable. Both select from the same source graph.
|
||||
- Position: the `--rig`/`--seat` grant serves the seat's lessons and recap.
|
||||
- Runtime: `context profile` and `context work-install` both accept
|
||||
`--runtime claude-code` (alias `claude`) or `--runtime codex`. Profile
|
||||
composition retains the `claude` manifest/output key; skill projection
|
||||
retains `claude-code`. Profile defaults from `OPENRIG_RUNTIME`, otherwise
|
||||
Claude; work-install requires the flag to inspect or apply skills.
|
||||
- Delivery: `rig walk <seat> --through-profile world-public --profile <profile>
|
||||
--situation <situation> ...` applies the already-inspected profile;
|
||||
`--through <files> --pace <interval>` remains the raw byte-walk path. Paced
|
||||
sends naming addressed reads serve pointer-walks.
|
||||
- The delta: a dated markdown file beside the seat's recap; verified by reading it.
|
||||
|
||||
Where these verbs are unavailable (a different toolchain, a plain filesystem), the
|
||||
convention still holds: compose by copying addressed sections with their source paths,
|
||||
deliver paced, assign the derive list, require the delta.
|
||||
|
||||
## Quickstart for any project
|
||||
|
||||
1. Sort the mission's artifacts: install content vs reference (the exclusion rule).
|
||||
2. Write or update the arrangement document — how the pieces connect, what to trust.
|
||||
3. Mark every contract piece with `updated-at`; mark underived facts `UNVERIFIED`.
|
||||
4. Choose the profile: planning-class full, or execution-class high-altitude.
|
||||
5. If mission/slice YAML selects execution components, include those files and
|
||||
their addressed catalog sections at the appropriate layers.
|
||||
6. Compose the ordered piece list with provenance; deliver it paced, pointer-first.
|
||||
7. End with the derive list; require the dated delta; verify existence; second-read
|
||||
the facts; re-derive on error.
|
||||
|
||||
A project of any kind fits this: for a book, the pieces are the outline's history and
|
||||
voice decisions, the arrangement is how the drafts relate, the position is this
|
||||
editor-seat's lessons, the contract is the current chapter plan with its dates, and
|
||||
the derive list is "count the chapters, read the latest editorial notes, state today's
|
||||
word count" — facts the new agent must touch, not trust.
|
||||
@@ -0,0 +1,53 @@
|
||||
# The planning dial — how much rigor a plan gets, chosen per piece
|
||||
|
||||
Planning is a spectrum, not a binary. When the wave model is selected, build care
|
||||
varies by wave composition; planning needs the same flexibility, or every piece gets
|
||||
one protocol — over-working the trivial and under-working the hard. Choose the rung
|
||||
from the work's uncertainty and consequences.
|
||||
|
||||
## The rungs — dial up by what the piece is
|
||||
|
||||
- **P0 — mini-requirements + pointers.** A few observable outcomes and where to look;
|
||||
the builder figures out the how. Right for simple, reversible, well-trodden work.
|
||||
- **P1 — authored spec.** Intent, mini-requirements, a failing-test-first proof
|
||||
contract, declared territory. The default.
|
||||
- **P2 — plus a research round,** run BEFORE the spec freezes, never during the build.
|
||||
The research prompt is authored by an agent who holds the product context (see the
|
||||
gate below); execution may fan out to any agents — web legs, documentation legs,
|
||||
code-reading legs in parallel. Research returns as CONTEXT with citations, never
|
||||
prescriptions; synthesis against your own architecture and intent follows, and it
|
||||
differs for greenfield (design the thing) versus brownfield (sharpen your approach).
|
||||
- **P3 — plus an adversarial pass.** A non-author who holds the product context
|
||||
attacks the plan in both directions with the product goals as judge. Amendments land
|
||||
with proof-contract teeth — a new or changed checkbox — never as prose advice.
|
||||
- **P4 — plus a blind design gate.** A from-scratch design is committed before reading
|
||||
the priors, then diffed. Similar → proceed; significantly different → stop for a
|
||||
design session with the owner.
|
||||
|
||||
## The context gate
|
||||
|
||||
Only agents with the product's world context installed may author research prompts,
|
||||
run adversarial passes, or weigh in on architecture questions. Execution researchers
|
||||
and builders need no such install — the agent CREATING their instructions must have
|
||||
it. The reason: research shaped by someone who does not know what the product is for
|
||||
returns answers to the wrong question, fluently.
|
||||
|
||||
## What earns P2 or P3 — importance is not the test
|
||||
|
||||
A piece earns **P2** when the spec would otherwise freeze on an unresolved external
|
||||
unknown — a feasibility door or an open design space only research can close. A piece
|
||||
earns **P3** when its failure mode is invisible to its own author: load-bearing
|
||||
substrate where a plausible, self-consistent plan would ship the exact disease it
|
||||
exists to kill. The discriminator is "would a wrong plan be expensive, hard to undo,
|
||||
and invisible from inside" — never "is this piece central." Importance without an
|
||||
unclosable unknown or an author-blind failure mode stays at P1. Planning is text-only
|
||||
work; keep its scope proportional to the unresolved question and the cost of a wrong
|
||||
plan, rather than assuming a fixed duration for a rung.
|
||||
|
||||
## Cost calibration
|
||||
|
||||
Scope research to the unknown that could change the plan; scope an adversarial
|
||||
pass to the failure modes the author may miss. Record the questions resolved and
|
||||
the resulting design changes so the owner can judge the value of the selected
|
||||
rung. Run planned research before build dispatch, while its answers can still
|
||||
shape the spec.
|
||||
@@ -0,0 +1,343 @@
|
||||
# Product-journey SDLC components
|
||||
|
||||
This is a reusable menu for building the smallest complete user outcome. A
|
||||
mission or slice selects only the components its work needs and records their
|
||||
order in mission/slice YAML. This is **not a mode, pipeline, topology, rig
|
||||
shape, or mandatory gate set**. If no composition is present, use the light
|
||||
Part A flow in `sdlc-conventions.md`.
|
||||
|
||||
## The doghouse and the moon base
|
||||
|
||||
The **doghouse** is the actual product outcome the user asked for. A
|
||||
**missing door** is an implementation that may be polished internally but
|
||||
cannot perform its basic user job. A **moon base** is elaborate machinery—
|
||||
often guards, abstractions, proof ceremony, security theatre, or process
|
||||
scaffolding—that consumes most of the effort without making the doghouse work.
|
||||
|
||||
Use that disclosure ladder in this order:
|
||||
|
||||
1. Name the doghouse in one sentence, without process vocabulary.
|
||||
2. Exercise its door through the public surface before admiring internals.
|
||||
3. Repair the smallest missing product seam.
|
||||
4. Consider deeper machinery only when evidence shows the layer below is
|
||||
causing the visible failure.
|
||||
5. Remove or decline anything that does not protect or deliver a named user
|
||||
outcome.
|
||||
|
||||
The same discipline works for a new mission and for rescuing one that drifted.
|
||||
In a new mission, it prevents the moon base. In a rescue, it finds whether a
|
||||
usable doghouse remains and separates it from machinery that should not be
|
||||
carried forward.
|
||||
|
||||
## Component contract
|
||||
|
||||
Every component below states:
|
||||
|
||||
- **Use when** — the evidence that admits it.
|
||||
- **Skip when** — the cheapest reason not to run it.
|
||||
- **Input** — what the responsible agent must actually read or observe.
|
||||
- **Action** — the bounded work performed.
|
||||
- **Output** — the artifact or observable result handed to the next component.
|
||||
- **Stop** — the boundary that prevents the component expanding itself.
|
||||
- **Next** — the normal successor; YAML may choose another explicit edge.
|
||||
|
||||
Component IDs are stable addresses. Their prose may improve without forcing
|
||||
mission YAML to change.
|
||||
|
||||
## Component catalog
|
||||
|
||||
### context.fresh-start
|
||||
|
||||
- **Use when:** prior context may carry stale architecture, verdicts, or
|
||||
implementation momentum.
|
||||
- **Skip when:** the current agent can independently state the user outcome,
|
||||
starting candidate, and boundaries from primary sources.
|
||||
- **Input:** identity, project/mission/slice YAML, and the selected addresses.
|
||||
- **Action:** load the selected context into a fresh session, or reorient the
|
||||
current session from live sources. Clearing a seat requires its separate
|
||||
continuity decision; selecting this component does not authorize it.
|
||||
- **Output:** a one-paragraph readiness statement naming role, user outcome,
|
||||
starting candidate, and boundaries.
|
||||
- **Stop:** do not reconstruct old queue lore merely because it exists.
|
||||
- **Next:** `intent.recover` or `journey.choose`.
|
||||
|
||||
### intent.recover
|
||||
|
||||
- **Use when:** work already exists but may have drifted from its original user
|
||||
intent.
|
||||
- **Skip when:** this is greenfield work with one current, unambiguous intent.
|
||||
- **Input:** the earliest authoritative intent, later mission/slice specs,
|
||||
candidate code, and observed user behavior.
|
||||
- **Action:** distinguish requirements from later planner elaboration; identify
|
||||
the smallest still-required outcomes and explicit anti-goals.
|
||||
- **Output:** a concise salvage map: keep, inspect, and not-required—never a
|
||||
destructive verdict by itself.
|
||||
- **Stop:** do not treat prior review intensity or sunk cost as product value.
|
||||
- **Next:** `journey.probe`.
|
||||
|
||||
### journey.choose
|
||||
|
||||
- **Use when:** a new mission has intent but no proven product path yet.
|
||||
- **Skip when:** `intent.recover` already names the next smallest outcome.
|
||||
- **Input:** authoritative intent and current public product surface.
|
||||
- **Action:** select one end-to-end user journey whose completion provides
|
||||
standalone value.
|
||||
- **Output:** one observable outcome plus its smallest credible public probe.
|
||||
- **Stop:** one outcome only; adjacent capability is another composition.
|
||||
- **Next:** `journey.probe`.
|
||||
|
||||
### journey.probe
|
||||
|
||||
- **Use when:** always, before product code is added or rescued.
|
||||
- **Skip when:** never; scale the probe down instead.
|
||||
- **Input:** the public CLI, API, UI, or other surface a real user operates.
|
||||
- **Action:** run the smallest disposable end-to-end journey and inspect its
|
||||
effects, not merely its exit status.
|
||||
- **Output:** PASS with no code needed, or one concrete user-visible gap with
|
||||
preserved evidence.
|
||||
- **Stop:** a passing journey does not authorize speculative hardening.
|
||||
- **Next:** finish on PASS; otherwise `depth.look-below` or
|
||||
`build.minimal-gap`.
|
||||
|
||||
### depth.look-below
|
||||
|
||||
- **Use when:** the public failure recurs, crosses a component boundary, is
|
||||
contradicted by lower-level state, or a surface patch would duplicate logic
|
||||
already owned below.
|
||||
- **Skip when:** the failure is local, understood, and repairable at the public
|
||||
seam without duplication; also skip when the only argument is “more robust.”
|
||||
- **Input:** one observed public failure and the immediately responsible seam.
|
||||
- **Action:** descend **one layer only**; state the causal hypothesis, name the
|
||||
lower-layer observation that would falsify it, and inspect or probe that
|
||||
layer.
|
||||
- **Output:** one of three depth decisions:
|
||||
- `LOCAL` — repair the public seam;
|
||||
- `BELOW` — repair the named load-bearing seam and replay the public journey;
|
||||
- `HOUSEKEEPING` — repair environment/data/topology hygiene before changing
|
||||
product code.
|
||||
- **Stop:** one descent per admission. A second descent requires new evidence
|
||||
and a newly stated hypothesis. “While here,” generalized prevention, and
|
||||
unrelated cleanup are out of scope.
|
||||
- **Next:** `build.minimal-gap`, `research.before-build`, or housekeeping.
|
||||
|
||||
This component is the balance point. Refusing ever to look below produces
|
||||
surface patches and recurring defects. Looking below without an evidence gate
|
||||
produces a moon base. The aim is the first load-bearing seam, not the deepest
|
||||
possible explanation.
|
||||
|
||||
### research.before-build
|
||||
|
||||
- **Use when:** the causal hypothesis depends on an unstable external fact or
|
||||
a product contract that cannot be derived locally.
|
||||
- **Skip when:** source, runtime state, and the public probe already establish
|
||||
the mechanism.
|
||||
- **Input:** one answerable uncertainty and its decision consequence.
|
||||
- **Action:** acquire only enough primary evidence to decide the implementation
|
||||
shape.
|
||||
- **Output:** decision, citations, and remaining uncertainty.
|
||||
- **Stop:** research ends when the implementation decision is supported.
|
||||
- **Next:** `build.minimal-gap`.
|
||||
|
||||
### build.minimal-gap
|
||||
|
||||
- **Use when:** a failed journey and causal seam are established.
|
||||
- **Skip when:** `journey.probe` passed or the finding is housekeeping rather
|
||||
than code.
|
||||
- **Input:** the user-visible gap, depth decision, existing seam, and explicit
|
||||
boundaries.
|
||||
- **Action:** add the shortest readable change that completes the journey,
|
||||
reusing the existing product seam.
|
||||
- **Output:** one clean candidate and focused evidence of the failure changing
|
||||
for the stated reason.
|
||||
- **Stop:** no new framework, security layer, schema, abstraction, or adjacent
|
||||
verb unless the outcome cannot work without it.
|
||||
- **Next:** `qa.public-journey`.
|
||||
|
||||
### qa.public-journey
|
||||
|
||||
- **Use when:** a candidate claims a complete user outcome.
|
||||
- **Skip when:** never before integration; it may be held by the builder on a
|
||||
tiny change only when independence was not selected.
|
||||
- **Input:** exact candidate, original public probe, and boundaries.
|
||||
- **Action:** run the journey from the user surface in a disposable context;
|
||||
inspect resulting state and partial-failure behavior.
|
||||
- **Output:** CLEAR or one concrete mismatch.
|
||||
- **Stop:** QA does not redesign the product or demand unselected ceremony.
|
||||
- **Next:** `review.scope-maintainability` or back to `build.minimal-gap`.
|
||||
|
||||
### review.scope-maintainability
|
||||
|
||||
- **Use when:** independent review is selected for the outcome.
|
||||
- **Skip when:** the composition deliberately keeps a low-risk change within
|
||||
the light single-agent path.
|
||||
- **Input:** exact candidate, intent, public QA evidence, and base diff.
|
||||
- **Action:** judge correctness, readability, necessity, reuse of existing
|
||||
seams, and whether the public journey—not an internal proxy—works.
|
||||
- **Output:** CLEAR or a bounded source/product finding.
|
||||
- **Stop:** a reviewer may not ratchet rigor merely because prior review found
|
||||
something; review the product, not the ceremony.
|
||||
- **Next:** `integrate.one-outcome` or back to `build.minimal-gap`.
|
||||
|
||||
### integrate.one-outcome
|
||||
|
||||
- **Use when:** the selected evidence for one outcome is complete.
|
||||
- **Skip when:** the candidate is not clear or its base moved incompatibly.
|
||||
- **Input:** exact candidate identity and selected QA/review results.
|
||||
- **Action:** integrate the smallest coherent outcome and keep unrelated
|
||||
candidates separate.
|
||||
- **Output:** clean canonical source containing one additional user outcome.
|
||||
- **Stop:** do not batch unrelated salvage because it shares a release label.
|
||||
- **Next:** `runtime.adopt` or the next `journey.choose`.
|
||||
|
||||
### runtime.adopt
|
||||
|
||||
- **Use when:** the outcome needs installed-runtime or migration proof.
|
||||
- **Skip when:** source-level proof is sufficient and no live behavior changed.
|
||||
- **Input:** clean canonical commit, rollback boundary, and runtime SOP.
|
||||
- **Action:** build/package/install the exact commit, switch atomically, and
|
||||
verify health plus continuity.
|
||||
- **Output:** exact live version and preserved rollback point.
|
||||
- **Stop:** adoption is deployment, not permission to change topology or data.
|
||||
- **Next:** `qa.installed-smoke`.
|
||||
|
||||
### qa.installed-smoke
|
||||
|
||||
- **Use when:** `runtime.adopt` ran or packaging/path resolution is part of the
|
||||
product claim.
|
||||
- **Skip when:** no installed artifact or runtime behavior is in scope.
|
||||
- **Input:** exact live version and disposable public journey.
|
||||
- **Action:** exercise the installed command path and verify effects and
|
||||
cleanup.
|
||||
- **Output:** installed-runtime CLEAR or one concrete mismatch.
|
||||
- **Stop:** do not convert a smoke test into fleet-wide monitoring.
|
||||
- **Next:** finish the outcome or return to `build.minimal-gap`.
|
||||
|
||||
## Composition rules
|
||||
|
||||
1. `project.yaml` may declare project defaults; `mission.yaml` may select the
|
||||
catalog and override those defaults for the mission.
|
||||
2. `slice.yaml` selects or overrides the components for that slice.
|
||||
3. Edges are explicit data. Array order is presentation, not dependency.
|
||||
4. Select the fewest components that honestly fit the work.
|
||||
5. A component may be held by the same seat as its neighbor unless independence
|
||||
is an intentional property of the composition.
|
||||
6. YAML describes the work; it is not an enforcement engine and creates no
|
||||
automatic gates.
|
||||
|
||||
Repository-address example:
|
||||
|
||||
```yaml
|
||||
sdlc:
|
||||
catalog:
|
||||
root: repository
|
||||
address: docs/reference/product-journey-sdlc.md#component-catalog
|
||||
components:
|
||||
- id: journey.choose
|
||||
owner: planner
|
||||
- id: journey.probe
|
||||
owner: qa
|
||||
- id: depth.look-below
|
||||
owner: builder
|
||||
admission: evidence-gated
|
||||
- id: build.minimal-gap
|
||||
owner: builder
|
||||
- id: qa.public-journey
|
||||
owner: qa
|
||||
edges:
|
||||
- from: journey.choose
|
||||
to: journey.probe
|
||||
- from: journey.probe
|
||||
to: depth.look-below
|
||||
when: public failure needs causal discrimination
|
||||
- from: journey.probe
|
||||
to: build.minimal-gap
|
||||
when: local gap is already established
|
||||
- from: depth.look-below
|
||||
to: build.minimal-gap
|
||||
when: decision is LOCAL or BELOW
|
||||
- from: build.minimal-gap
|
||||
to: qa.public-journey
|
||||
```
|
||||
|
||||
Resolve repository addresses from `git rev-parse --show-toplevel`. Use the
|
||||
`loading-addressable-markdown` skill for `path#h2-slug` and
|
||||
`path#h2-slug/h3-slug` addresses outside the context library.
|
||||
|
||||
## Example compositions
|
||||
|
||||
### Greenfield product journey
|
||||
|
||||
`context.fresh-start → journey.choose → journey.probe →
|
||||
build.minimal-gap → qa.public-journey → review.scope-maintainability →
|
||||
integrate.one-outcome`
|
||||
|
||||
Add `depth.look-below` only when the probe meets its admission rule. A new
|
||||
mission does not need rescue archaeology.
|
||||
|
||||
### Drifted-mission rescue
|
||||
|
||||
`context.fresh-start → intent.recover → journey.probe →
|
||||
depth.look-below? → build.minimal-gap → qa.public-journey →
|
||||
review.scope-maintainability → integrate.one-outcome`
|
||||
|
||||
The question is not “which old commits pass review?” It is “which required
|
||||
user outcome works, what prevents the next one, and how little code completes
|
||||
it?”
|
||||
|
||||
### Recurring cross-layer defect
|
||||
|
||||
`journey.probe → depth.look-below → research.before-build? →
|
||||
build.minimal-gap → qa.public-journey → qa.installed-smoke?`
|
||||
|
||||
The recurrence admits one causal descent. It does not authorize a general
|
||||
platform rewrite.
|
||||
|
||||
## Resolve the selected path
|
||||
|
||||
On a fresh start, restore, or new assignment:
|
||||
|
||||
1. Derive identity with `rig whoami --json`. Resolve the project's work root from
|
||||
the assignment/workspace binding; `rig config get workspace.root` locates the
|
||||
configured work tree, which may differ from the seat's code checkout.
|
||||
2. Read `project.yaml` and its project-authority context. Follow its mission root
|
||||
to the assigned `mission.yaml`, then its composition ref to the active
|
||||
`slice.yaml`. Read the current intent and acceptance files those records name.
|
||||
Do not choose a mission from an old onboarding packet or a folder's recency.
|
||||
3. Resolve SDLC selection in order: project defaults, mission defaults, then the
|
||||
active slice's explicit selection/override. A narrower explicit selection
|
||||
replaces the broader component list; it does not append every ancestor's
|
||||
gates. Follow an addressed wave map for its membership, review model and
|
||||
boundary; an explicit named-slice exception applies only to that slice.
|
||||
4. Read only the selected components and additional addressed context. Repository
|
||||
references resolve from the code repository root; installed references from
|
||||
`$OPENRIG_HOME/reference`; context-pack refs use `rig context get`. For a
|
||||
Markdown `#section` address, use `loading-addressable-markdown` rather than
|
||||
loading an entire unrelated manual. Expand the investigation when the task's
|
||||
evidence requires it: the address map is a starting point, not a search limit.
|
||||
5. State briefly the user outcome, current role, candidate, selected path and
|
||||
next completion boundary. No composition means the light Part A flow in
|
||||
`sdlc-conventions.md#a1-the-flow-in-one-pass`. Do not author a
|
||||
composition merely to have one, or mint QA, guard, review, lock or proof gates
|
||||
because those roles exist. On a tiny change the builder can verify the whole
|
||||
outcome; independent review happens only when selected.
|
||||
|
||||
A missing referenced file is a named gap, not evidence of no selection. Inspect
|
||||
or ask the owner to resolve a consequential ambiguity; do not silently fall back
|
||||
from an explicitly selected rigorous path. An owner can explicitly amend the
|
||||
selection for named work; record that decision at the work's authority. Current
|
||||
command/source reality outranks stale YAML as a factual claim: record the
|
||||
mismatch, re-derive, and preserve the distinction between scope and observed state.
|
||||
|
||||
Startup overlays, role guidance, available seats, old mode names and historical
|
||||
packets cannot add steps to this selection. They supply capabilities. Skills load
|
||||
when the selected work calls for them; there is no quiz, per-file ACK gate or
|
||||
universal preloading requirement.
|
||||
|
||||
## Blank-agent start
|
||||
|
||||
Follow **Resolve the selected path** above before choosing work. For an authored
|
||||
wave, build and verify each slice with its local discipline and integrate serially
|
||||
where required; request independent review once when the authored wave boundary
|
||||
arrives. Do not start per-slice reviews while waiting. A separately selected
|
||||
rigorous slice retains its named checks. Review findings justify bounded repairs,
|
||||
not an automatic increase in process for the next round.
|
||||
@@ -0,0 +1,139 @@
|
||||
# The product-management pass — what runs above planning, before planners touch a mission
|
||||
|
||||
Graduated 2026-08-28 from a working SOP born during live release preparation; the
|
||||
originating team's channel specifics stay in its playbook.
|
||||
|
||||
## Why this layer exists (read this or the steps are ceremony)
|
||||
|
||||
The build is a lossy compression pipeline: product intent → planner spec → builder
|
||||
code, and each stage is a narrower reader than the one above. Nothing leaks downward
|
||||
by proximity — a dimension the product owner's seat holds and does not WRITE AND
|
||||
ROUTE is deleted from the product, and the deletion is silent: the downstream agent
|
||||
invents a plausible replacement rather than raising a hand. This pass is the only
|
||||
point where product taste physically enters the pipeline. Its output is judged by one
|
||||
measuring stick:
|
||||
|
||||
> A slice folder is ready when SPEC.md alone, plus only what SPEC.md explicitly
|
||||
> routes to, reproduces the design intent in a reader who has none of your context.
|
||||
|
||||
## The steps
|
||||
|
||||
1. **GROUND IN THE RAW MATERIAL.** Survey where the ideas actually live: owner intent
|
||||
layers, prior-era designs, the conversations. Enumerate cheaply first (intent
|
||||
frontmatter, indexes); read the centerpiece documents yourself; fan out readers for
|
||||
breadth, requiring citation-bearing reports (claims with openable paths; status:
|
||||
shipped / partial / designed-never-built). Never grep-and-hope.
|
||||
2. **SYNTHESIZE ONE SCOPE OF RECORD.** Theme; waves; binding design principles; an
|
||||
explicitly-NOT-in list (decisions, not omissions); the owner-decision set, each
|
||||
with a recommendation. Archive superseded planning where a cold reader cannot
|
||||
mistake it for authoritative. Every other surface DERIVES from this document;
|
||||
where they disagree, it wins.
|
||||
3. **PRESENT DECISIONS DECISION-READY.** One at a time, lettered, recommendation
|
||||
first, records cited. The owner's ruling gates everything downstream —
|
||||
mechanically where possible (a blocked-on anchor), never by memory.
|
||||
4. **MINT SLICES AT PLAN TIME** with real one-paragraph intents.
|
||||
5. **DEPOSIT THE DESIGN — the fidelity law.** Every design-bearing slice's SPEC.md
|
||||
carries the full design contract INLINE: the owner's words verbatim, the settled
|
||||
do-not-reopen decisions, the live evidence, the shape of done, the anti-goals. A
|
||||
one-line intent is a ~20:1 lossy compression; building from it alone matches the
|
||||
design only by coincidence. Design that happened in conversation is deposited at
|
||||
mint time or it dies with the author's context window. Mechanical slices with
|
||||
durable named seeds may stay thin — thinness is a per-slice judgment, never a
|
||||
default.
|
||||
6. **ROUTE EVERYTHING — the spec is the router.** The downstream reader's world is
|
||||
SPEC.md; their curiosity does not extend past it, and a file's existence is not a
|
||||
route. The spec itself carries the complete per-slice reading list (the exact
|
||||
sources YOU used, with one line of why each) and per-slice research routing
|
||||
(which tool answers which question — including explicit NOs where outside
|
||||
frameworks would contaminate an owner-designed model). Nothing load-bearing may
|
||||
depend on a reader's initiative.
|
||||
7. **FRAME THE PLANNING PER SLICE.** In each spec: the planning-dial rung WITH its
|
||||
reason (`docs/reference/planning-dial.md`); what matters most; the required output
|
||||
shape. Author the research prompts for research-round slices yourself — the
|
||||
context gate: prompts come from the agent who holds the product context;
|
||||
execution can fan out. Constraints binding every spec are stated in the spec,
|
||||
never assumed known.
|
||||
8. **THE THEORY-OF-MIND REREAD.** Before releasing the batch, reread each spec AS the
|
||||
narrow downstream reader — an agent who got a queue ticket, will read this one
|
||||
file, and will invent anything missing without asking. Every "but how would they
|
||||
know X?" is a hole to fix now.
|
||||
9. **SEQUENCE AND GATE.** Waves file-disjoint; anything that must precede dispatch
|
||||
gates it as a row dependency, never a remembered intention; elaboration batons
|
||||
carry the dial assignments and route back per slice.
|
||||
10. **KEEP THE RECORD LIVE.** The owner-facing board re-derives from the scope of
|
||||
record; corrections land in the governing document first and project outward.
|
||||
|
||||
## The failure modes this pass exists to prevent (each observed live)
|
||||
|
||||
- Thin intents shipped as if elaboration could recover fidelity the elaborator never
|
||||
had.
|
||||
- Beautiful context written to disk but never routed — write-only artifacts.
|
||||
- Reading lists that were curated excerpts of the real source set.
|
||||
- Research needs known to the shaper but never assigned a tool or a question.
|
||||
- Dispatch before the gate; sequencing held in memory instead of a row.
|
||||
- Knowing the world at ten dimensions and writing for readers as if the missing
|
||||
seven would leak through on their own. They do not. Route it or it is gone.
|
||||
|
||||
## Addressing the wide-angle seat: context, never instructions
|
||||
|
||||
When any seat sends findings, problems, or proposals to a wide-angle seat (an
|
||||
advisor, a product shaper), it frames them as CONTEXT ON THE TABLE — the problem
|
||||
articulated, the grounding, candidate solutions if it has them — and explicitly
|
||||
leaves the steering to the receiver. Never as instructions, even with a confident
|
||||
recommendation in hand. Why: narrow-aperture seats produce narrow solutions and
|
||||
default to imperative speech; a wide-angle seat that trusts the sender will comply,
|
||||
and that is exactly how narrow decisions leak into a product unsynthesized. The
|
||||
pattern converts the sender's intensity (deep, focused, current) into the receiver's
|
||||
synthesis (wide, cross-cutting, temporal). This is the inbound half of the fidelity
|
||||
law: outbound, the shaper deposits design so compression cannot eat it; inbound,
|
||||
senders frame as context so aperture cannot bypass synthesis.
|
||||
|
||||
THE PROTOCOL IS BIDIRECTIONAL. Downward — orchestrator to builder — the same wire
|
||||
discipline applies in mirror: put the goal, the acceptance criteria, and the
|
||||
complete context ON THE TABLE, and leave the implementation steering to the
|
||||
receiver. An orchestrator that converts context into step-by-step orders bypasses
|
||||
the builder's synthesis exactly the way a narrow seat's imperative speech bypasses
|
||||
a wide seat's. Empowerment is delivered as context, never as instructions — in
|
||||
both directions.
|
||||
|
||||
## Aperture narrows by default — and that is the point
|
||||
|
||||
Do not read the above as "assign every seat an aperture." Agents inevitably narrow:
|
||||
heads-down on one kind of work, a seat specializes, and the narrowing IS the
|
||||
specialization effect — the system's core mechanism, never a flaw to correct.
|
||||
Widening a specialist diffuses what made them elite. Never set a narrow aperture
|
||||
intentionally; seats arrive at theirs on their own. The ONLY aperture you ever steer
|
||||
is the wide one: wide-angle is itself a specialization, and its defining discipline
|
||||
is MAINTAINING width against the same pull that narrows everyone else. State
|
||||
aperture in a role only when the role must hold altitude.
|
||||
|
||||
## The topology pairing (blueprint note)
|
||||
|
||||
A strong topology pairs an ORCHESTRATION seat (flow: dispatch, sequencing, capacity —
|
||||
wide across the board, narrow in the act) with a PRODUCT-SHAPING seat (taste:
|
||||
coherence, contracts, temporal judgment — wide across the product), each with its
|
||||
aperture stated in its role. The context-not-instructions rule is the wire protocol
|
||||
between them.
|
||||
|
||||
## Price one layer lower — the right-layer check
|
||||
|
||||
Before accepting the fix your hands reached for, price ONE LAYER LOWER. The tendency
|
||||
is to fix high — high fixes are easy to write and test — and narrow is sometimes
|
||||
exactly right: sometimes all you need is a band-aid. But sometimes you are setting a
|
||||
bone, and the tells are learnable:
|
||||
|
||||
- **The bone tell:** the lower fix collapses several open problems at once, and is
|
||||
often SMALLER than the patch — it deletes or reuses instead of adding.
|
||||
- **The band-aid tell:** the lower layer is stable, correct, and merely
|
||||
inconvenient; the defect is genuinely local; nothing else downstream shares it.
|
||||
- **The floor:** there is a sweet spot. A fix below the primitives you own is
|
||||
someone else's lane, and a new engine where composition suffices fails the
|
||||
simplicity bar.
|
||||
- **The move, mechanically:** name the layer you instinctively chose; name the
|
||||
layer below it; state what each fixes and what each leaves broken; choose with
|
||||
the price visible.
|
||||
|
||||
This is the vertical twin of the wide-angle lens above: where
|
||||
context-not-instructions protects synthesis across seats, ARE-YOU-AT-THE-RIGHT-LAYER
|
||||
protects it across layers — a question to ask of every fix, and one layer lower
|
||||
deserves a look every time.
|
||||
@@ -0,0 +1,86 @@
|
||||
schema: openrig.project/v0alpha1
|
||||
kind: project
|
||||
metadata: { id: example }
|
||||
missions: { root: missions }
|
||||
lifecycle:
|
||||
profile: release-boundary-v0
|
||||
profiles:
|
||||
release-boundary-v0:
|
||||
required_steps:
|
||||
- mission-outcome
|
||||
- exact-release-candidate
|
||||
- capability-delta
|
||||
- exact-cut-substance
|
||||
- release-verification
|
||||
- git-canonicalization
|
||||
- public-release
|
||||
- parent-adoption
|
||||
- record-shipped
|
||||
- release-boundary
|
||||
workflow:
|
||||
entry: { role: orchestrator }
|
||||
# Entry/ordinary custody and exception selection are separate decisions.
|
||||
# Replace the example target with a declared member (rig ps --nodes --json).
|
||||
# Inspect with workflow compile/show; adopt later corrections with workflow revise.
|
||||
exception_routing: { orchestrator_role: orchestrator }
|
||||
roles:
|
||||
orchestrator: { preferred_targets: [orch@example] }
|
||||
steps:
|
||||
- id: mission-outcome
|
||||
actor_role: orchestrator
|
||||
depends_on: []
|
||||
objective: Judge the mission's promised outcomes against retained evidence; name remaining gaps.
|
||||
allowed_exits: [handoff, waiting, failed]
|
||||
- id: exact-release-candidate
|
||||
actor_role: orchestrator
|
||||
depends_on: [mission-outcome]
|
||||
objective: Establish the exact candidate and tree to which subsequent receipts apply.
|
||||
allowed_exits: [handoff, waiting, failed]
|
||||
- id: capability-delta
|
||||
actor_role: orchestrator
|
||||
depends_on: [exact-release-candidate]
|
||||
objective: Reconcile the shipped capability description with this candidate's actual behavior.
|
||||
allowed_exits: [handoff, waiting, failed]
|
||||
- id: exact-cut-substance
|
||||
actor_role: orchestrator
|
||||
depends_on: [capability-delta]
|
||||
objective: Obtain and judge independent substance admission for the exact release cut.
|
||||
allowed_exits: [handoff, waiting, failed]
|
||||
- id: release-verification
|
||||
actor_role: orchestrator
|
||||
depends_on: [exact-cut-substance]
|
||||
objective: Judge privacy, package shape, conventions and mirrors, builds, tests, and clean-tree evidence for the exact admitted release candidate.
|
||||
allowed_exits: [handoff, waiting, failed]
|
||||
- id: git-canonicalization
|
||||
actor_role: orchestrator
|
||||
depends_on: [release-verification]
|
||||
objective: Obtain the authorized canonical Git identity and reconcile it with the verified candidate.
|
||||
allowed_exits: [handoff, waiting, failed]
|
||||
- id: public-release
|
||||
actor_role: orchestrator
|
||||
depends_on: [git-canonicalization]
|
||||
objective: Obtain publication authorization and the release owner's exact public artifact receipt.
|
||||
allowed_exits: [handoff, waiting, failed]
|
||||
- id: parent-adoption
|
||||
actor_role: orchestrator
|
||||
depends_on: [public-release]
|
||||
objective: Obtain separately authorized parent adoption and judge its effect receipt.
|
||||
allowed_exits: [handoff, waiting, failed]
|
||||
- id: record-shipped
|
||||
actor_role: orchestrator
|
||||
depends_on: [parent-adoption]
|
||||
objective: Record durable shipped truth after judging ceremony receipts; the post-release boundary follows.
|
||||
allowed_exits: [handoff, waiting, failed]
|
||||
- id: release-boundary
|
||||
actor_role: orchestrator
|
||||
depends_on: [record-shipped]
|
||||
objective: >-
|
||||
After the release ceremony, author one attributed disposition record
|
||||
covering seat renewal and re-prime, memory distillation, destination
|
||||
queue sweep, substrate teardown or retention, board freeze and clean-box
|
||||
baseline, capability-delta absorption and expiry, and packaged-product
|
||||
discoverability. These are agent-judged guides, not seven daemon gates.
|
||||
Inspect beyond this packet when useful. Record reasoned not-applicable
|
||||
or deferred dispositions and wait on live dependencies. Finish after
|
||||
this boundary when no successor is authored.
|
||||
allowed_exits: [done, waiting, failed]
|
||||
@@ -1,9 +1,12 @@
|
||||
# Project Workspace Contract
|
||||
|
||||
OpenRig's Project UI is a file-backed view over a workspace root. The folder
|
||||
OpenRig's Project TUI is a file-backed view over a workspace root. The folder
|
||||
shape is intentionally simple so humans and agents can create or repair it
|
||||
without daemon-internal knowledge.
|
||||
|
||||
This subtree is part of the wider [OpenRig Instance Layout](instance-layout.md).
|
||||
The instance initializer delegates these exact workspace bytes to this owner.
|
||||
|
||||
## Default Shape
|
||||
|
||||
`rig config init-workspace` creates the default workspace at
|
||||
@@ -11,48 +14,101 @@ without daemon-internal knowledge.
|
||||
|
||||
```text
|
||||
workspace/
|
||||
README.md
|
||||
STEERING.md
|
||||
SPEC.md
|
||||
project.yaml
|
||||
workspace.yaml
|
||||
.gitignore
|
||||
missions/
|
||||
README.md
|
||||
idea-ledger/
|
||||
README.md
|
||||
PROGRESS.md
|
||||
slices/
|
||||
capture-product-ideas/
|
||||
README.md
|
||||
PROGRESS.md
|
||||
IMPLEMENTATION-PRD.md
|
||||
triage-product-ideas/
|
||||
README.md
|
||||
PROGRESS.md
|
||||
IMPLEMENTATION-PRD.md
|
||||
handoff-loop/
|
||||
README.md
|
||||
PROGRESS.md
|
||||
slices/
|
||||
route-work-packets/
|
||||
README.md
|
||||
PROGRESS.md
|
||||
IMPLEMENTATION-PRD.md
|
||||
verify-loop-evidence/
|
||||
README.md
|
||||
PROGRESS.md
|
||||
IMPLEMENTATION-PRD.md
|
||||
progress/
|
||||
field-notes/
|
||||
specs/
|
||||
exhaust/
|
||||
```
|
||||
|
||||
The scaffold is additive: it creates missing canonical entries and never
|
||||
overwrites existing files, including with the deprecated `--force` flag.
|
||||
`exhaust/` and local `.openrig/` runtime projections are ignored; authored
|
||||
project context and mission/slice files remain versionable. `workspace.yaml`
|
||||
is the project-location catalog. The project manifest exposes empty
|
||||
`install.context` and `install.skills` selectors for ordered Markdown
|
||||
addresses and stable managed-catalog skill IDs, but neither skill source nor a
|
||||
System World belongs in this tree.
|
||||
|
||||
## Project-world install
|
||||
|
||||
`project.yaml` may select project context and managed skills together:
|
||||
|
||||
```yaml
|
||||
schema: openrig.project/v0alpha1
|
||||
kind: project
|
||||
install:
|
||||
intent: SPEC.md
|
||||
context:
|
||||
- conventions.md
|
||||
skills:
|
||||
- repository-maintenance
|
||||
```
|
||||
|
||||
`install.context` contains project-relative Markdown addresses. `install.skills`
|
||||
contains stable skill identities only. Skill source bytes live in the single
|
||||
configured managed catalog (`skills.root`, default `$OPENRIG_HOME/skills`),
|
||||
never under the project or workspace. `rig context work-install --runtime
|
||||
<claude-code|codex>` resolves both parts; add `--apply-skills` to reconcile the
|
||||
selected exact bytes into `.claude/skills/` or `.agents/skills/` under the
|
||||
caller's current working directory. Use `--cwd` when the receiving agent works
|
||||
somewhere else; the project-world metadata root is never assumed to be its code
|
||||
working directory.
|
||||
|
||||
The generated harness directories and `.openrig/skill-loadouts/` ownership
|
||||
receipts are projections, not source. Product repositories should ignore them.
|
||||
Reconciliation removes a deselected entry only when its current bytes still
|
||||
match OpenRig's last owned projection; unrelated and locally modified entries
|
||||
are preserved or reported as conflicts.
|
||||
|
||||
Seats of the same runtime commonly share one working directory. Their topology
|
||||
selectors are retained per canonical seat identity and projected as a union, so
|
||||
starting one role cannot remove another role's skill. A changed projection is
|
||||
visible only to a fresh harness process; reconciliation reports that boundary
|
||||
instead of claiming a running seat hot-reloaded it.
|
||||
|
||||
An installed project selection is also retained in that working directory's
|
||||
ownership receipt. A later seat start with no project-world input preserves it;
|
||||
an explicit install whose `install.skills` is empty clears it. This keeps
|
||||
"project not supplied" distinct from "project deliberately selects no skills."
|
||||
|
||||
## TUI project selection
|
||||
|
||||
Open **PROJECTS** (or enter `projects`) to choose an ID and root from the
|
||||
configured workspace catalog. `project <id>` selects an exact catalog entry;
|
||||
`mission <directory>` then opens that project's execution view. The historical
|
||||
`scopes` machine section ID remains valid. Project selection and its canonical
|
||||
root travel through mission, slice, source-file and Back navigation. Changing a
|
||||
catalog root requires selecting it again. Missing or malformed sources remain
|
||||
unavailable; another project's matching work ID is never a fallback.
|
||||
|
||||
`source` opens the current project, mission or slice's actual source through the
|
||||
existing file-reader allowlist. Catalog membership does not grant file-read or
|
||||
execution permission. These views only read; they do not select a lifecycle
|
||||
operation or activate a mission. Execution and slice queue membership require
|
||||
an exact `project:<id>` tag; lifecycle instances use their authored project
|
||||
identity. Unscoped historical queue rows and global review artifacts without
|
||||
project binding are excluded from project-specific claims.
|
||||
|
||||
The read API adds `GET /api/scopes/projects` and optional `project` and
|
||||
`projectRoot` parameters to scopes, execution and slice-detail reads. Slice
|
||||
detail also requires the selected mission directory. Existing reads without a
|
||||
project parameter keep their legacy contract.
|
||||
|
||||
## UI Mapping
|
||||
|
||||
- `workspace.root` maps to the Project workspace.
|
||||
- `workspace.catalog_path` maps to the `workspace.yaml` project catalog.
|
||||
- `workspace.projects_root` is the default home for catalogued project worlds.
|
||||
- `workspace.root/missions/<mission-id>` maps to a Project mission.
|
||||
- `workspace.root/missions/<mission-id>/slices/<slice-id>` maps to a Project slice.
|
||||
- Mission `PROGRESS.md` frontmatter supplies the mission status badge when the
|
||||
file root is allowlisted.
|
||||
- Slice `README.md`, `PROGRESS.md`, and `IMPLEMENTATION-PRD.md` frontmatter
|
||||
supply display name, lifecycle status, mission id, and queue linkage hints.
|
||||
- Mission and slice `SPEC.md` frontmatter supplies intent, advisory sibling
|
||||
build-order `depends_on`, lifecycle status, and queue linkage hints.
|
||||
- Slice `PROGRESS.md` is the durable acceptance checklist; `PROOF.md` and
|
||||
`proof/` retain evidence paired to the SPEC proof contract.
|
||||
|
||||
Mission and slice ids should be stable kebab-case strings. Keep slice ids
|
||||
unique inside the workspace so `/project/slice/<slice-id>` resolves without
|
||||
@@ -90,12 +146,13 @@ default setup contract.
|
||||
If Project shows a mission discovery warning:
|
||||
|
||||
1. Run `rig config get workspace.root --show-source`.
|
||||
2. Run `rig config get workspace.slices_root --show-source`.
|
||||
3. Confirm `workspace.slices_root` points at a folder containing mission
|
||||
2. Run `rig config get workspace.catalog_path --show-source`.
|
||||
3. Run `rig config get workspace.slices_root --show-source`.
|
||||
4. Confirm `workspace.slices_root` points at a folder containing mission
|
||||
directories with `slices/` children.
|
||||
4. Confirm `files.allowlist` includes `workspace:<workspace.root>` so the UI
|
||||
5. Confirm `files.allowlist` includes `workspace:<workspace.root>` so the TUI
|
||||
can read mission `PROGRESS.md`.
|
||||
5. If the workspace is missing, run `rig config init-workspace` after operator
|
||||
6. If the workspace is missing, run `rig config init-workspace` after operator
|
||||
approval.
|
||||
|
||||
No daemon restart is required for most config reads. Restart when changing
|
||||
|
||||
@@ -12,17 +12,22 @@ it is the mechanism that makes shipped chain files (see
|
||||
|
||||
| event | Claude Code | Codex | why |
|
||||
|---|---|---|---|
|
||||
| SessionStart | ✓ | ✓ | fresh orientation |
|
||||
| UserPromptSubmit | ✓ | ✓ | growth check at each turn boundary |
|
||||
| Stop | ✓ | ✓ | catches the long single turn |
|
||||
| PostCompact | ✓ | — (no compact hook) | a compacted seat's picture is lossy |
|
||||
| UserPromptSubmit | ✓ | ✓ | deliver due or on-demand context at a model-visible boundary |
|
||||
| Stop | ✓ | — | catch a long Claude turn crossing the growth threshold |
|
||||
| PostCompact | ✓ | ✓ | retain exact compaction due-state; deliver on the next prompt |
|
||||
|
||||
The firing signal is **transcript growth** — not turns (one turn can burn 200k
|
||||
tokens across fifty tool calls) and not wall-clock (the failure is sustained
|
||||
work, not elapsed time). Default threshold ~2.6MB of JSONL growth (≈300k
|
||||
tokens); tune with `OPENRIG_REFOCUS_BYTES`. SessionStart and PostCompact
|
||||
always fire. Otherwise the hook is a silent no-op, and it degrades to silence
|
||||
on unrelated hook errors. A configured REF resolution failure instead degrades
|
||||
Claude's additional firing signal is **transcript growth** — not turns (one turn
|
||||
can burn 200k tokens across fifty tool calls) and not wall-clock (the failure is
|
||||
sustained work, not elapsed time). Its default threshold is ~2.6MB of JSONL
|
||||
growth (≈300k tokens); tune with `OPENRIG_REFOCUS_BYTES`. Codex never uses that
|
||||
threshold: its exact `PostCompact` lifecycle hook is the trigger, avoiding a
|
||||
redundant double-fire around Codex's own compaction cadence. Set
|
||||
`OPENRIG_REFOCUS_NOW=1` for an on-demand refocus and
|
||||
`OPENRIG_REFOCUS_ENABLED=0` to disable the feature. Fresh `SessionStart` is
|
||||
always a no-op: the default onboarding pack owns fresh orientation. Both
|
||||
runtimes retain `PostCompact` due-state for the next prompt. Otherwise the hook
|
||||
is a silent no-op, and it degrades to silence on unrelated hook errors. A
|
||||
configured REF resolution failure instead degrades
|
||||
loudly in the delivered payload while still completing the hook — a refocus
|
||||
must never break a seat's turn.
|
||||
|
||||
@@ -42,9 +47,10 @@ Resolution order:
|
||||
2. `OPENRIG_REFOCUS_CONTENT_FILE` — an operator-authored file (per-seat or
|
||||
per-rig via spec env).
|
||||
3. `$OPENRIG_HOME/refocus/REFOCUS.md` — the instance's standing content.
|
||||
4. The generic default baked into the hook: three project-neutral orientation
|
||||
questions plus the pointer to the seat's topology chain
|
||||
(`rig context trace … --name CRAFT.md`) and the LEARNED-ownership warning.
|
||||
4. The generic default at
|
||||
`skills/refocusing/references/refocus.md`: three project-neutral orientation
|
||||
questions, the ladder itself, and a pointer to the separately shipped
|
||||
onboarding assets.
|
||||
|
||||
The hook names a resolved REF in the delivered payload. If REF resolution
|
||||
fails, the payload starts with `REFOCUS CONTENT REF FAILED`, the exact ref, and
|
||||
@@ -55,10 +61,14 @@ unset, the existing FILE and generic paths are unchanged.
|
||||
Mission-, project-, or box-specific refocus text belongs in a context-library
|
||||
entry or one of those FILES on the instance that needs it. **It must never be committed into product
|
||||
source** — the shipped default carries no path, seat name, mission, or
|
||||
practice that is not generally applicable. (The pre-promotion lab hook had
|
||||
exactly this defect: a hardcoded per-box distillation-tool path. The
|
||||
promotion removed it; the grep for project residue is part of the slice's
|
||||
proof contract.)
|
||||
practice that is not generally applicable.
|
||||
|
||||
Every delivered refocus pairs its content with the public `refocusing` skill's
|
||||
path-only trace. Configure `OPENRIG_REFOCUS_TREES=topology|work|both` and
|
||||
`OPENRIG_REFOCUS_DEPTH=light|full`; optional `OPENRIG_REFOCUS_TOPOLOGY_NODE`
|
||||
and `OPENRIG_REFOCUS_WORK_NODE` select starts that cannot be derived. The
|
||||
script resolves `topology.root` and `workspace.root` through live config and
|
||||
reports broken links as gaps rather than following pointers.
|
||||
|
||||
This automatic hook path is additive to one-shot manual injection through
|
||||
`rig send --context`; neither mode substitutes for the other.
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# The release boundary — housekeeping run once per fence
|
||||
|
||||
A CHECKLIST OF JUDGMENT GUIDES, not gates: every item says WHY, most items are one
|
||||
act, and anything situational expires at the next boundary. Runs once per release
|
||||
fence, after the prior release's ceremony completes. The project and seat owners
|
||||
select the applicable actions and preserve their reasons in the boundary record.
|
||||
|
||||
## 1. Seats — reset or re-prime, per each seat's continuity needs
|
||||
|
||||
- **Choose continuity per seat, with its owner.** Fresh launch, managed compaction
|
||||
with re-prime, or retaining the current session serve different needs. Role names
|
||||
alone do not select one. Record the choice, the reason and custody of open work;
|
||||
selecting a procedure does not itself authorize a lifecycle action.
|
||||
- **Fresh launch** can clear accumulated context and load current startup guidance.
|
||||
Before an authorized exit, preserve the seat's earned knowledge and continuation
|
||||
(recap where provisioned); verify the new occupant's actual identity and install.
|
||||
- **Managed compaction with re-prime** can retain planning continuity while
|
||||
restoring the context needed for the next phase. **Retaining the current session**
|
||||
can preserve ongoing custody. In either case, inspect which guidance is stale and
|
||||
deliver the selected current context; an edit on disk is not delivery to a session.
|
||||
- **Re-prime = the world walk + the current capability delta + the mission install**
|
||||
per the mission-install convention (`docs/reference/mission-install.md`): the world
|
||||
half restores where-you-are; the project half (five layers, two depth profiles,
|
||||
paced pointer-walk) restores what-you-are-doing. Composing from named artifacts
|
||||
keeps source precedence visible.
|
||||
- **THE CEREMONY IS THE EXPERIMENT.** Every act above runs on the product's own
|
||||
primitives, so the ceremony doubles as their live test. Observe passively: what did
|
||||
each re-primed seat's first natural turns reach for, and what broke? Findings land
|
||||
in one dated observations file beside the boundary record.
|
||||
|
||||
## 2. Memory — distill, never accumulate
|
||||
|
||||
- Per-seat learned files: a distillation pass. The cut test: "has this changed a
|
||||
decision recently?" Keep those; move the rest to a dated archive section in the
|
||||
same file. A learned file at volume stops constraining and starts justifying.
|
||||
- Any shared agent memory: same pass, same test; retire entries the release
|
||||
falsified.
|
||||
- The finished release's mission notes: SEALED AS RECORD, carried as nothing. The
|
||||
new mission starts thin. Rules do not migrate; requirements do, via the plan.
|
||||
|
||||
## 3. Queue — sweep by destination, disposition everything
|
||||
|
||||
- Every seat sweeps its DESTINATION for open rows (wakes are unreliable; the sweep
|
||||
is the read). Each open row from the finished release gets exactly one of:
|
||||
done-with-receipt · carried-to-the-new-mission (re-filed against the new plan,
|
||||
never dragged) · closed-superseded. The sweep exposes obligations that a missed
|
||||
wake or a stale remembered work list can hide.
|
||||
- Verify every closing-paper inheritance has a live intake row with an owner. Zero
|
||||
orphans.
|
||||
|
||||
## 4. Substrate — teardown as its own atom
|
||||
|
||||
- Retain the current release and the immediately previous release. At a boundary
|
||||
for release N, worktrees and other release-scoped substrate from N-2 and older
|
||||
are eligible for cleanup. Eligibility is not permission to discard unique dirty
|
||||
bytes: archive or otherwise disposition those first. Identify live `main` and
|
||||
runtime state by their actual refs and identity, never by a stale directory name.
|
||||
- Prune worktrees; remove session-scoped scratch trees; verify merged branches
|
||||
deleted (merge receipts authorize).
|
||||
- Scratch homes and scenario roots in temp directories: swept.
|
||||
- Runtime installs: keep LIVE + LAST-KNOWN-GOOD; archive the hotfix chain;
|
||||
disposition launcher backups.
|
||||
- Mid-release vendored-file backups removed once the release containing the real
|
||||
fix is live.
|
||||
- Snapshots of torn-down topology: indexed in one line each, or deleted.
|
||||
|
||||
## 5. Instruments and boards
|
||||
|
||||
- The finished release's tracking and planning boards: FROZEN as records, labeled
|
||||
so. The new mission gets fresh boards — source-of-truth markdown first, board
|
||||
derived.
|
||||
- Frozen review criteria: archived beside their evidence, marked "available,
|
||||
unweakened, not gates."
|
||||
- Capture ONE clean-box baseline receipt at mission start (load, process counts,
|
||||
health tail latency) — the next performance question deserves a before.
|
||||
|
||||
## 6. The capability-delta lifecycle (standing law)
|
||||
|
||||
Each release's capability delta is authored at the fence and bound to the exact
|
||||
candidate, then reconciled to the published cut. Its baseline is the canon's actual
|
||||
last-absorbed marker, not an assumption that the immediately prior semver was
|
||||
absorbed. When canon is behind, the new delta is a cumulative synthesis of every
|
||||
intervening delta, preserving supersessions and pivots rather than concatenating
|
||||
old prose.
|
||||
|
||||
Canon absorbs the kept public-product truths. Expiry requires BOTH the canon header
|
||||
naming that exact delta AND a distinct successor delta file existing. A header alone
|
||||
does not expire it; missing or unreadable inputs remain unknown/live. At the actual
|
||||
boundary, verify the event before recording expiry, then stop citing the expired
|
||||
delta. Do not create a successor merely to make the condition true. Candidate
|
||||
absorption is not proof of publication or served production adoption.
|
||||
|
||||
Private project topology, host practice, and release ownership go to the versioned
|
||||
project world instead of leaking into the public package. The new release's delta
|
||||
obligations start at ITS cut.
|
||||
|
||||
## 7. World absorption and discoverability
|
||||
|
||||
- Every changed capability gets one small map: **user situation · current capability
|
||||
· authoritative source · natural first product encounter · durable context home ·
|
||||
clean-agent probe**. Record an already-good route and leave it alone.
|
||||
- Prefer point-of-need truth from the existing `--help`, error, command output, live
|
||||
state, context catalog, work install, or skill router. The world install supplies
|
||||
width and ontology; it should not copy exact facts a live surface can derive.
|
||||
- The release mission owns this review before its cut when possible, so public
|
||||
world/help changes ship with the capability. This boundary verifies the exact
|
||||
packaged result, repairs any missed absorption as explicit debt, absorbs private
|
||||
project truth, re-primes, and observes whether a clean world-installed agent
|
||||
reaches the capability from its situation without being told its name.
|
||||
- A missing route is a release finding. Repair the smallest existing seam or deposit
|
||||
the evidenced gap into the derivable-context mission; do not create a second
|
||||
capability registry, search service, or world store at the boundary.
|
||||
|
||||
## 8. What deliberately does NOT happen at the boundary
|
||||
|
||||
- No process migration: situational rules expired at the fence. The standing corpus
|
||||
is the four checking principles — verify by effect · evidence at source · honest
|
||||
verdicts including against yourself · never fabricate.
|
||||
- No re-litigating the finished release's verdicts; the sealed paper is the record.
|
||||
- No additions to this checklist without a lived specimen attached.
|
||||
@@ -1,7 +1,7 @@
|
||||
# RigBundle Reference
|
||||
|
||||
Version: 2 (pod-aware)
|
||||
Last validated against code: 2026-04-11
|
||||
Last validated against code: 2026-09-29 (install paths and `--target` only; other sections last checked 2026-04-11)
|
||||
Source of truth: `packages/daemon/src/domain/bundle-types.ts`, `packages/daemon/src/domain/bundle-archive.ts`, `packages/daemon/src/domain/pod-bundle-assembler.ts`
|
||||
|
||||
A `.rigbundle` is a self-contained distributable archive that packages a rig spec, all referenced agent specs, their resources (skills, guidance, startup files), culture file, documentation, and an integrity manifest into a single file. The recipient can install and launch the rig without needing the original source tree.
|
||||
@@ -180,6 +180,13 @@ If any check fails, extraction is aborted and an error is thrown.
|
||||
|
||||
## CLI Surface
|
||||
|
||||
### Paths
|
||||
|
||||
`rig bundle create`, `inspect` and `install` resolve every path you give them (`<spec-path>`, `-o`, `--rig-root`,
|
||||
`<bundle-path>`, `--target`) to an absolute path against **your** current directory before sending the request. The
|
||||
daemon then reads and writes those paths on **its own host**: nothing is uploaded, so the files must already exist
|
||||
where the daemon runs.
|
||||
|
||||
### Create a bundle
|
||||
|
||||
```bash
|
||||
@@ -211,7 +218,7 @@ The create command:
|
||||
rig bundle inspect <bundle-path> [--json]
|
||||
```
|
||||
|
||||
Shows the manifest, digest validity, and integrity verification result. Inspect extracts the archive into a temporary directory for safe validation, then cleans that directory up. It does not install the bundle.
|
||||
Shows the manifest, digest validity, and integrity verification result. Inspect extracts the archive into a temporary directory for safe validation, then cleans that directory up. It does not install or launch anything.
|
||||
|
||||
### Install a bundle
|
||||
|
||||
@@ -222,12 +229,20 @@ rig bundle install <bundle-path> [--plan] [--yes] [--target <root>] [--json]
|
||||
| Flag | Required | Default | Description |
|
||||
|------|----------|---------|-------------|
|
||||
| `<bundle-path>` | yes | — | Path to the `.rigbundle` file. |
|
||||
| `--plan` | no | `false` | Preview the bootstrap plan without installing or launching. |
|
||||
| `--plan` | no | `false` | Preview without installing or launching. Not side-effect free: it runs the preflight, which executes the members' runtime `--version` probes, and it records a bootstrap run. It writes nothing to the target. |
|
||||
| `--yes` | no | `false` | Auto-approve trusted actions during apply mode. |
|
||||
| `--target <root>` | yes in apply mode | — | Target root directory for package installation. Required unless `--plan` is used. |
|
||||
| `--target <root>` | yes in apply mode | — | Directory the bundle is installed into and launched from. Required unless `--plan` is used. |
|
||||
| `--json` | no | `false` | Emit machine-readable JSON. |
|
||||
|
||||
Extracts the bundle, validates integrity, and bootstraps the rig. In apply mode, the daemon requires `targetRoot`, so `rig bundle install` must be given `--target <root>` unless you are running with `--plan`.
|
||||
Install **launches the rig**; it does not just unpack it. It extracts the bundle to a temporary directory, validates integrity, and bootstraps the rig. In apply mode, the daemon requires `targetRoot`, so `rig bundle install` must be given `--target <root>` unless you are running with `--plan`.
|
||||
|
||||
For a pod-aware (schema version 2) bundle, apply copies the extracted contents (`bundle.yaml`, `rig.yaml`, `agents/`, culture and docs files) into the target and launches from there, then removes the temporary extraction. So:
|
||||
|
||||
- the target becomes the rig root: `agent_ref` paths resolve inside it, and a member with `cwd: "."` (or no `cwd`) starts in the target;
|
||||
- an absolute member `cwd` stays as authored, and `rig up --cwd <dir>` still overrides every member's cwd;
|
||||
- if the target already has a file with **different** content at any bundle path (for example its own `rig.yaml`), install refuses with `target_conflict` and writes nothing. Identical files are accepted, so reinstalling the same bundle into the same target works. Use an empty or dedicated directory as the target.
|
||||
|
||||
Legacy (schema version 1) bundles keep their old behavior: `--target` is only where packages are installed.
|
||||
|
||||
### Launch directly
|
||||
|
||||
@@ -237,8 +252,9 @@ rig up <bundle-path> [--target <root>] [--cwd <dir>]
|
||||
|
||||
`rig up` auto-detects `.rigbundle` files and routes them through the bundle bootstrap path.
|
||||
|
||||
- `--target <root>` controls where packaged files are installed
|
||||
- if `--target` is omitted for a `.rigbundle`, the CLI defaults the install target to the current working directory
|
||||
- `--target <root>` is the install target described above (for a schema-version-2 bundle, the directory the bundle is copied into and launched from)
|
||||
- if `--target` is omitted for a `.rigbundle`, the CLI defaults the install target to the current working directory, so the bundle's files are written there
|
||||
- `rig up` resolves a relative `--target` against your current directory before sending it, like `rig bundle install`; with `--host`, `--target` is sent as given and must be a path that exists on that host
|
||||
- `--cwd <dir>` does **not** change the install target; it only overrides the launched members' working directory for that run
|
||||
|
||||
---
|
||||
@@ -364,4 +380,4 @@ Equivalent explicit form:
|
||||
rig up /path/to/my-team.rigbundle --target ~/projects/my-project
|
||||
```
|
||||
|
||||
The bundle is extracted to a temporary directory, integrity is verified, packaged files are installed into the target root, and the rig is bootstrapped with all agents and resources from the bundle. If you also want agents to launch with a different working directory for that run, pass `--cwd <dir>` separately.
|
||||
The bundle is extracted to a temporary directory and its integrity is verified. A schema-version-2 bundle is then copied into the target root (refused if the target holds different files at the same paths) and the rig is launched from there, with all agents and resources from the bundle. If you also want agents to launch with a different working directory for that run, pass `--cwd <dir>` separately.
|
||||
+92
-10
@@ -174,7 +174,8 @@ edges:
|
||||
| `name` | string | yes | — | Rig name. Used in session naming (`{pod}-{member}@{name}`), snapshot identification, and spec library lookup. |
|
||||
| `summary` | string | no | — | Human-readable description. Shown in spec library, review surfaces, and `rig specs show`. |
|
||||
| `culture_file` | string | no | — | Relative path to a rig-wide culture/constitution file. Must be a safe relative path (no `..`, no absolute). |
|
||||
| `permission_policy` | string | no | — | Permission policy attached to the rig. Either a built-in (`builtin:locked`, `builtin:standard`, `builtin:open`, `builtin:operator`) or a safe relative path to a custom policy file (resolved from this spec's directory; no `..`, no absolute). Absent leaves the default floor. A member may set its own `permission_policy`, which takes precedence over the rig-level one. See "Attaching a permission policy" below. |
|
||||
| `permission_policy` | string | no | — | Permission policy attached to the rig. Either a built-in (`builtin:locked`, `builtin:standard`, `builtin:open`, `builtin:yolo`) or a safe relative path to a custom policy file (resolved from this spec's directory; no `..`, no absolute). Absent leaves the default floor. A member may set its own `permission_policy`, which takes precedence over the rig-level one. See "Attaching a permission policy" below. |
|
||||
| `managed_blocks` | map | no | `CLAUDE.md` | File that receives OpenRig's managed instruction blocks for Claude Code members. Only the `claude-code` key is accepted, with `CLAUDE.md` or `CLAUDE.local.md`. Codex members always use `AGENTS.md`. See "Choosing the Claude instruction file" below. |
|
||||
| `docs` | Doc[] | no | — | Documentation files that should travel with the rig. Included in rig bundles. Each entry has a `path` field (safe relative path). The engine does not consume these — they are for humans and agents setting up the environment before launch. |
|
||||
| `startup` | StartupBlock | no | — | Rig-level startup files and actions. Applied to all members via the startup layering model. |
|
||||
| `services` | ServicesBlock | no | — | Optional managed services (Docker Compose). When present, services boot before any agent launches. |
|
||||
@@ -193,12 +194,53 @@ permission_policy: builtin:standard
|
||||
permission_policy: policies/my-cautious-dev.policy.md
|
||||
```
|
||||
|
||||
Built-in policies (`locked` / `standard` / `open` / `operator`) are read-only and are
|
||||
Built-in policies (`locked` / `standard` / `open` / `yolo`) are read-only and are
|
||||
referenced as `builtin:<name>`. A custom policy lives in your own project and is
|
||||
referenced by a safe relative path (no `..`, no absolute). A shipped example of the
|
||||
custom shape is `packages/daemon/policies/examples/my-cautious-dev.policy.md` — copy it
|
||||
into your project and edit it to taste.
|
||||
|
||||
This records a selection, not a live permission change. Flag-surface policies
|
||||
select launch flags; config-surface policies still need native configuration
|
||||
application and inspection. In particular, `builtin:yolo` selects Codex's
|
||||
`danger-full-access` sandbox and `never` approval policy, and replaces
|
||||
any `codex_config_profile` argument. See [practical permission choices](getting-started.md#opt-in-permissive-operation).
|
||||
|
||||
An explicit `rig seat set-permissions` choice overrides member/rig policy for
|
||||
future managed launches of that stable seat; it does not rewrite this spec or
|
||||
its inherited policy provenance. `inherit` removes that override. See
|
||||
[per-seat permission mode](getting-started.md#per-seat-permission-mode).
|
||||
|
||||
### Choosing the Claude instruction file
|
||||
|
||||
OpenRig writes its instructions for Claude Code members into managed blocks in
|
||||
the member's working directory. By default the file is `CLAUDE.md`. If your
|
||||
repository tracks `CLAUDE.md`, write the blocks to `CLAUDE.local.md` instead:
|
||||
|
||||
```yaml
|
||||
managed_blocks:
|
||||
claude-code: CLAUDE.local.md
|
||||
```
|
||||
|
||||
Claude Code loads `CLAUDE.local.md` from the working directory as well. By
|
||||
convention the file is kept out of git, for example with a `.gitignore` entry.
|
||||
|
||||
- Accepted values are `CLAUDE.md` and `CLAUDE.local.md`. Any other value or
|
||||
runtime key is rejected before a member launches.
|
||||
- The setting applies to launch, restore, relaunch, handover, adding members,
|
||||
and export. `rig down` removes OpenRig's blocks from the selected file only.
|
||||
- OpenRig never edits, moves or deletes blocks in the other file.
|
||||
|
||||
A rig that already wrote blocks into `CLAUDE.md` keeps them there after you
|
||||
switch. Until you remove them, `CLAUDE.md` stays modified and Claude Code loads
|
||||
both copies. Delete each `<!-- BEGIN OpenRig MANAGED BLOCK: … -->` …
|
||||
`<!-- END OpenRig MANAGED BLOCK: … -->` section by hand and keep the rest of the
|
||||
file. If `CLAUDE.md` has no other uncommitted edits you need to keep, you can
|
||||
instead run `git restore CLAUDE.md`; that command discards every unstaged change
|
||||
to the file, not only OpenRig's blocks. Running `rig down` on a rig that still uses
|
||||
the default is not a substitute: it strips every OpenRig block from that
|
||||
directory's `CLAUDE.md`, including blocks written by other rigs.
|
||||
|
||||
---
|
||||
|
||||
## Pod
|
||||
@@ -229,6 +271,7 @@ into your project and edit it to taste.
|
||||
| `id` | string | yes | — | Member identifier. Must not contain dots. Must be unique within the pod. |
|
||||
| `agent_ref` | string | yes | — | Reference to an AgentSpec. Must start with `local:` (relative) or `path:` (absolute). Exception: `builtin:terminal` for infrastructure nodes. |
|
||||
| `profile` | string | yes | — | Profile name from the referenced AgentSpec. Use `default` for the default profile. Exception: `none` for terminal nodes. |
|
||||
| `codex_config_profile` | string | no | — | Codex-only native profile passed as `-p <name>`; letters, numbers, `_`, `.`, `-`. Separate from the AgentSpec `profile`. With the normal launch mode, this replaces OpenRig's explicit workspace-write sandbox flag. A full-bypass policy instead emits danger-full-access and omits this profile argument. |
|
||||
| `runtime` | string | yes | — | Agent runtime. Current supported values: `claude-code`, `codex`, `terminal`. |
|
||||
| `cwd` | string | yes | — | Working directory for the agent. Resolved relative to the rig root (the directory containing the rig spec). Use `"."` for the rig root itself. Can be overridden at launch time with `rig up --cwd`. |
|
||||
| `label` | string | no | — | Human-readable member name. Shown in UI when present. |
|
||||
@@ -343,12 +386,51 @@ Startup blocks can appear at three levels: rig, pod, and member. They are merged
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `type` | string | yes | — | Action type. One of: `slash_command`, `send_text`. Note: `shell` is explicitly NOT supported in v1. |
|
||||
| `value` | string | yes | — | The command or text to send. |
|
||||
| `phase` | string | no | `after_files` | When to execute. One of: `after_files` (after startup files are delivered), `after_ready` (after harness readiness check passes). |
|
||||
| `type` | string | yes | — | Action type. One of: `slash_command`, `send_text`, `startup_proof`. Note: `shell` is explicitly NOT supported in v1. |
|
||||
| `value` | string | yes | — | Command/text to send, or `authenticated` / `none` for `startup_proof`. |
|
||||
| `phase` | string | no | `after_files` | When to execute text/commands. One of: `after_files` (after startup files are delivered), `after_ready` (after harness readiness check passes). Proof selection is resolved before projection regardless of phase. |
|
||||
| `idempotent` | boolean | yes | — | Whether this action is safe to replay on restore. **Required field.** Non-idempotent actions must NOT include `restore` in `applies_on`. |
|
||||
| `applies_on` | string[] | no | `[fresh_start, restore]` | When this action runs. Subset of: `fresh_start`, `restore`. |
|
||||
|
||||
### Startup proof selection
|
||||
|
||||
Startup adds no orientation exercise by default. To select the authenticated
|
||||
startup challenge, declare an action in an agent, profile, rig, pod, member, or
|
||||
operator startup block:
|
||||
|
||||
```yaml
|
||||
startup:
|
||||
actions:
|
||||
- type: startup_proof
|
||||
value: authenticated
|
||||
idempotent: true
|
||||
```
|
||||
|
||||
Use `value: none` in a later layer to select lean startup explicitly. The last
|
||||
applicable declaration wins in agent → profile → rig → pod → member → operator
|
||||
order. Culture contributes files, not a proof selection. With no applicable
|
||||
declaration, the result is `none`; the number of startup files never selects
|
||||
proof. Invalid values and non-idempotent proof declarations fail validation,
|
||||
including declarations overridden later. These actions declare policy and are
|
||||
never typed into a terminal.
|
||||
|
||||
An authenticated selection challenges only a fresh or fresh-fallback managed
|
||||
agent launch. Resumed, forked, rebuilt, and adopted sessions receive no new
|
||||
challenge; terminal nodes never receive one. `applies_on` follows the requested
|
||||
startup context, so a fresh fallback during restore uses `restore` selections.
|
||||
Keep the default `[fresh_start, restore]` to cover both fresh launch paths.
|
||||
|
||||
Identity delivery, projection, readiness, and ordinary startup actions still
|
||||
run. `startup_status: ready` means startup completed, while `oriented: missing`
|
||||
means a selected proof awaits authenticated submission. Omission/`none` yields
|
||||
`oriented: n-a` on a new fresh launch and retires an older challenge without
|
||||
deleting its audit history. Retirement follows successful harness launch,
|
||||
before readiness checks, so attention, timeout, or a readiness exception cannot
|
||||
retain the preceding proof. A replacement that fails to launch does not retire
|
||||
the current proof; resume/adoption also preserves existing proof history.
|
||||
The effective selection is recorded on `node.startup_pending`; actions are
|
||||
persisted in startup context for restore and fresh relaunch.
|
||||
|
||||
---
|
||||
|
||||
## Services Block
|
||||
@@ -477,7 +559,7 @@ These rules are enforced by the validator. A spec that violates any of these wil
|
||||
19. Each wait target must define exactly one of: `service`, `url`, `tcp`.
|
||||
20. `condition` is only valid on `service` targets and must be `healthy`.
|
||||
21. Startup file `delivery_hint` must be one of: `auto`, `guidance_merge`, `skill_install`, `send_text`.
|
||||
22. Startup action `type` must be one of: `slash_command`, `send_text`. (`shell` is explicitly rejected.)
|
||||
22. Startup action `type` must be one of: `slash_command`, `send_text`, `startup_proof`. (`shell` is explicitly rejected.) Proof selection requires `value: authenticated` or `none` and `idempotent: true`.
|
||||
23. Startup action `phase` must be one of: `after_files`, `after_ready`.
|
||||
24. Startup action `idempotent` is a required boolean.
|
||||
25. Non-idempotent actions must not include `restore` in `applies_on`.
|
||||
@@ -491,8 +573,8 @@ These are the built-in specs shipped with OpenRig. Read them as worked examples.
|
||||
|
||||
| Spec | Location | Pods | Members | Services |
|
||||
|------|----------|------|---------|----------|
|
||||
| `product-team` | `packages/daemon/specs/rigs/product-team.yaml` | orch, dev, rev | 7 (lead, peer, impl, qa, design, r1, r2) | no |
|
||||
| `implementation-pair` | `packages/daemon/specs/rigs/implementation-pair.yaml` | dev | 2 (impl, qa) | no |
|
||||
| `adversarial-review` | `packages/daemon/specs/rigs/adversarial-review.yaml` | orch, rev | 3 (lead, r1, r2) | no |
|
||||
| `research-team` | `packages/daemon/specs/rigs/research-team.yaml` | orch, research | 3 (lead, analyst, synthesizer) | no |
|
||||
| `product-team` | `packages/daemon/specs/rigs/preview/product-team/rig.yaml` | orch1, dev1, rev1 | 7 (lead, peer, impl, qa, design, r1, r2) | no |
|
||||
| `implementation-pair` | `packages/daemon/specs/rigs/launch/implementation-pair/rig.yaml` | dev | 2 (impl, qa) | no |
|
||||
| `adversarial-review` | `packages/daemon/specs/rigs/focused/adversarial-review/rig.yaml` | orch, review | 3 (lead, r1, r2) | no |
|
||||
| `research-team` | `packages/daemon/specs/rigs/focused/research-team/rig.yaml` | orch, research | 3 (lead, analyst, synthesizer) | no |
|
||||
| `secrets-manager` | `packages/daemon/specs/rigs/launch/secrets-manager/rig.yaml` | vault | 1 (specialist) | yes (Vault) |
|
||||
@@ -0,0 +1,95 @@
|
||||
# Scoped operating posture
|
||||
|
||||
Human-led work is quiet by default for process-only diagnosis. Delegated work can
|
||||
receive oversight during planning as well as implementation. This preference does
|
||||
not grant execution authority or change permissions, work phase, workflow reminders,
|
||||
context/continuity health, or health policy.
|
||||
|
||||
## Inspect and choose
|
||||
|
||||
Use the existing `rig mode` surface. Give an exact rig or authored work scope:
|
||||
|
||||
```sh
|
||||
rig mode effective --rig my-rig --json
|
||||
rig mode effective --project my-project --mission release-1 --json
|
||||
rig mode effective --qitem my-packet --json
|
||||
rig mode set delegated --scope mission --qualifier my-project/release-1 --evidence "Operator delegated this outcome"
|
||||
# The proposal above exits 2 without writing. Apply the deliberate choice:
|
||||
rig mode set delegated --scope mission --qualifier my-project/release-1 --evidence "Operator delegated this outcome" --confirm
|
||||
rig mode set human-led --scope mission --qualifier my-project/release-1 --evidence "Return to interactive planning" --confirm
|
||||
rig mode unset mission my-project/release-1
|
||||
```
|
||||
|
||||
Unsetting reveals the next applicable explicit choice, or the visible human-led
|
||||
product default. A new, resolved rig with no binding reports `human-led`,
|
||||
`source: product-default`, and `binding: null`. Missing scope identity, conflicting
|
||||
linkage, unavailable storage or malformed authored context reports `unknown` with
|
||||
a reason. Calling `effective` without a scope does not select a rig implicitly.
|
||||
The existing operator bearer requirement still applies. `set` requires `--confirm`;
|
||||
`unset` is an explicit deletion command and applies immediately.
|
||||
|
||||
## One scope and phase contract
|
||||
|
||||
`GET /api/rig-mode/effective` accepts `rig`, `project`, `mission`, `workstream`, and
|
||||
`qitem`. Its additive `operatingPosture` object is also attached to health records
|
||||
and diagnosis findings. CLI explanation and TUI health detail display the same data.
|
||||
Consumers of posture, phase, and health should use this object rather than invent
|
||||
another preference store or infer delegation from workflow existence or activity.
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| `posture` | `human-led`, `delegated`, or `unknown` |
|
||||
| `source` | `product-default`, `binding`, or `unknown` |
|
||||
| `context` | Resolved rig/project/mission/workstream/qitem IDs, source addresses, canonical authored paths and phase; null if scope resolution failed |
|
||||
| `context.phase` | `{value, source}` from an explicit workflow packet step, otherwise slice stage/status, otherwise mission release phase/status; null fields when unavailable |
|
||||
| `binding` | Winning binding ID, scope, timestamp and evidence citation; null for default/unknown |
|
||||
| `reason` | Explanation of the result or the missing/conflicting fact |
|
||||
| `grantsAuthority` | Always `false` |
|
||||
| `members` | For queue-backed findings, each member's qitem, posture, source and binding ID; differing or unknown member postures yield unknown aggregate posture |
|
||||
|
||||
The existing SQLite mode-binding table remains the only preference store. Matching
|
||||
posture bindings resolve in this order: qitem, workstream, mission, project, rig,
|
||||
global host. The qualifiers are respectively a qitem ID, `project/mission/slice-id`,
|
||||
`project/mission`, project ID, canonical rig ID, and null. Rig names resolve to IDs.
|
||||
The workstream is an existing authored slice, found by its directory or SPEC ID;
|
||||
the returned identity uses its SPEC ID and project/mission qualification.
|
||||
|
||||
Project selection reads `workspace.yaml`'s declared `projects: [{id, root}]`
|
||||
catalog, or the workspace's own `project.yaml` when no catalog exists. Multiple
|
||||
projects require an explicit project selection. A sole project may be derived.
|
||||
The project manifest validates identity and supplies `missions.root` (default
|
||||
`missions`). Mission and slice identities come from those authored work nodes;
|
||||
missing, duplicate or conflicting identities remain unknown. The selected work
|
||||
must stay inside its canonical project root.
|
||||
|
||||
Qitem reads join explicit `project:`, `mission:`, `slice:`/`workstream:` tags,
|
||||
destination rig, and the existing workflow packet binding. Workflow lifecycle
|
||||
project/mission identity must agree with the tags and requested scope. The bound
|
||||
packet's exact step is the phase source; phase names are not classified by guess.
|
||||
An unlinked qitem does not silently inherit the default. Scope reads are observational.
|
||||
|
||||
Legacy ergonomic modes (`sleep`, `desk`, `mobile`, `away`, `focus`, `debug`) retain
|
||||
their bindings and ten-field records. They do not imply either operating posture.
|
||||
Only human-led/delegated bindings participate in posture precedence. The legacy
|
||||
`effective` and `posture: known|unknown_posture` fields remain for compatibility;
|
||||
`unknown_posture` there means no legacy mode binding and does not override the
|
||||
new `operatingPosture.source: product-default` result. Setting another mode at the
|
||||
same scope replaces that row; inspect effective posture after any mode change.
|
||||
|
||||
## Diagnosis effects
|
||||
|
||||
Process-category findings remain visible under human-led or unknown posture, but
|
||||
new diagnosis presentations, re-presentations and human notifications require
|
||||
explicit delegated posture. Retained occurrences are not canceled or rewritten
|
||||
when posture changes. Their current finding is refreshed for inspection and
|
||||
notification; unavailable current findings cannot reuse old delegated posture.
|
||||
Notification admission is rechecked after readiness I/O.
|
||||
|
||||
Delegation is necessary for process interruptions and is insufficient by itself:
|
||||
enabled diagnosis policy, detector selection, source freshness, custody, cooldown,
|
||||
recurrence limits and human delivery readiness still apply. Other health categories
|
||||
and ordinary queue/workflow reminders retain their existing behavior. The automatic
|
||||
ceremony source reads declared catalog projects through the same resolver; unresolved
|
||||
lineages remain visible with unknown scope instead of acquiring invented authority.
|
||||
|
||||
See [health diagnosis](health-diagnosis.md) for policy, evidence and disposition.
|
||||
@@ -1,52 +1,16 @@
|
||||
# SDLC conventions — the markdown control plane
|
||||
|
||||
> **Succession rule (source of truth):** this document was derived from the
|
||||
> corrective review-surface redesign spec
|
||||
> (`CORRECTIVE-REDESIGN-review-surface-2026-07-05`, DRAFT v0.2, §2–§6,
|
||||
> grounded against openrig main `c27f2aff`, 2026-07-05/06). **Once shipped,
|
||||
> THIS repo document is the living SSOT for the SDLC conventions going
|
||||
> forward**; the corrective spec is the historical design record. Scaffold
|
||||
> templates, the advisory scope audit, the `mission-slice-sop` skill, and the
|
||||
> shipped bootstrap overlay all point HERE — they must not restate this
|
||||
> document in full (restatement is how drift is born).
|
||||
> **Source of truth:** this repo document is the living SSOT for the SDLC
|
||||
> conventions. Scaffold templates, the advisory scope audit, the
|
||||
> `mission-slice-sop` skill, and the shipped bootstrap overlay all point HERE —
|
||||
> they must not restate this document in full (restatement is how drift is born).
|
||||
|
||||
OpenRig is a software factory the human steers from a high altitude. The human
|
||||
records intent; agents turn intent into a plan, the plan into a build, the
|
||||
build into proof. The Living Notes UI is a **plain projection of well-formed
|
||||
markdown on disk** — agents change the files, the UI re-projects. These
|
||||
conventions define "well-formed." Everything here is **advisory / fail-open
|
||||
for agents**: nothing below blocks a write; the audit records and advises.
|
||||
|
||||
|
||||
## WHERE YOU ARE DECIDES WHICH LOOP YOU RUN — the VM inner loop vs the host outer loop
|
||||
|
||||
The parent host's SDLC was being applied wholesale to the VM. **They are different worlds with
|
||||
different responsibilities**, and conflating them is what turned this document into friction.
|
||||
|
||||
| | **INNER LOOP — the VM** | **OUTER LOOP — the parent host** |
|
||||
|---|---|---|
|
||||
| **question it answers** | *does it actually work?* | *does what was delivered match what was intended?* |
|
||||
| **method** | ground → fix → upgrade in place → **run it and look** → iterate | intent-vs-delivered comparison, commit-message review, privacy/leak scrub, release management, publish |
|
||||
| **definition of done** | to the best of our knowledge the code is **completely functional and works**, and needs **no heavy refactoring** to be release-ready | the release is shippable and honest |
|
||||
| **ceremony** | **Part A only.** Part B is not the default here. | **Part B applies.** The release itself earns it. |
|
||||
|
||||
**WHY the ceremony belongs there and not here — the rule that makes this self-enforcing:** the
|
||||
proof ceremony is a **protocol for transmitting confidence to someone who was not there.**
|
||||
Plan-lock, proof contracts, C1 drops and intent-vs-delivered all exist so a reader who did NOT
|
||||
watch the work happen can verify that it did. On the host that is exactly the situation, so the
|
||||
artifacts must carry the confidence. **On the VM the builder IS there** — they ran it and looked at
|
||||
it. Running the ceremony there re-encodes, at high cost, something already known, for an audience
|
||||
standing in the room.
|
||||
|
||||
**WHERE A FIX GOES — go where the validation is POSSIBLE, not where the ceremony lives:**
|
||||
|
||||
- **Needs a live daemon to verify → the VM.** That is the only place it CAN be verified. (Worked
|
||||
example: proving the ACTIVITY telemetry was really fixed required cutting the runtime over and
|
||||
reading real values; no amount of host-side review could establish it.)
|
||||
- **Verifiable by reading — docs, comment scrubs, message hygiene → the host.**
|
||||
|
||||
**The VM's north star for a release:** hand the host code that, to the best of our knowledge, is
|
||||
completely functional and works. Not a proof pack. **Effect-verified functionality.**
|
||||
build into proof. The TUI projects authored work on disk and, where an owning
|
||||
proof policy is selected, derives readiness from recorded judgments. These
|
||||
authoring conventions are **advisory / fail-open for agents**: the audit
|
||||
records and advises; the selected proof policy separately governs who may judge.
|
||||
|
||||
|
||||
## HOW TO READ THIS DOCUMENT — two parts, and only one of them is the default
|
||||
@@ -54,11 +18,43 @@ completely functional and works. Not a proof pack. **Effect-verified functionali
|
||||
| | applies | contents |
|
||||
|---|---|---|
|
||||
| **PART A — THE SIMPLE SDLC** | **ALWAYS. This is the default.** | artifact conventions, proportionality, the advisory audit, the honesty rails |
|
||||
| **PART B — THE RIGOROUS OVERLAY** | **ONLY when the founder, or an orchestrator relaying the founder, ASSIGNS the heavy path to a NAMED piece of work** | the proof-contract format, the two locks, C1 proof drops, the locked role contracts |
|
||||
| **PART B — THE RIGOROUS OVERLAY** | **ONLY when the human operator, or an orchestrator relaying that decision, ASSIGNS the heavy path to a NAMED piece of work** | the proof-contract format, the two locks, C1 proof drops, the locked role contracts |
|
||||
|
||||
**You may not select Part B for yourself.** If you believe something earns it, say so in ONE
|
||||
sentence and continue on Part A until told otherwise. Part A corresponds to the `mission-slice-sop`
|
||||
skill; Part B to `mission-slice-intent-proof-sop`.
|
||||
skill; Part B is defined below and applies only when assigned.
|
||||
|
||||
|
||||
## THE COMPONENT MENU — this document is a menu, not a pipeline
|
||||
|
||||
The SDLC is assembled from **components chosen per mission**, not a single mandated flow.
|
||||
This document describes the components and carries recommendations; a mission (or the team
|
||||
running it) picks per phase. Multiple valid options can exist for the same phase — choosing
|
||||
one does not deprecate the others. Nothing below deletes or overrides an existing component.
|
||||
|
||||
| phase | components available today |
|
||||
|---|---|
|
||||
| **PRODUCT / MISSION PREPARATION** | **The product-management pass** — the layer above planning, run by the advisor/PM seat before planners touch a mission. The build is a lossy compression pipeline (PM intent → planner spec → builder code); nothing leaks downward by proximity, so this pass grounds in the raw sources, synthesizes ONE scope of record, presents owner decisions decision-ready, mints slices at plan time, deposits the full design contract INLINE in each design-bearing spec (owner words verbatim, settled decisions, evidence, shape of done, anti-goals), routes the complete reading list and internet-research assignments from the spec itself, frames each slice's planning-dial rung with its reason, and rereads every spec as the narrow downstream reader. The measuring stick: SPEC.md alone, plus what it explicitly routes to, reproduces the intent in a reader with none of the author's context. Full reference: `product-management-pass.md`. |
|
||||
| **PLAN** | **The planning dial** — planning rigor is a spectrum chosen per piece: P0 mini-requirements and pointers (simple, reversible work) · P1 an authored spec with a proof contract (the default) · P2 plus a research round before the spec freezes, run BEFORE build dispatch · P3 plus an adversarial pass by a non-author, judged against product goals, landing amendments with proof-contract teeth · P4 plus a blind from-scratch design diffed against priors before proceeding. Dial up by what the piece is, exactly as build care is priced below. Full reference: `planning-dial.md`. |
|
||||
| **BUILD** | **Part A, the simple flow** (below — the default) · **product-journey components** — a mission-selectable menu for greenfield work, drift recovery, public journey probes, one evidence-gated look at the layer below, minimal repair, independent QA/review, integration, and installed smoke; compose only what the work needs in mission/slice YAML, never as a new mode or mandatory pipeline. Full reference: `product-journey-sdlc.md` · **the wave model** — slices build in parallel in disjoint file territories, an integrator merges serially, and independent review fires once per wave (two reviewers with different vantages, never the writers; fix rounds re-earn verdicts at one final revision; per-slice discipline stays failing-test-first and verify-by-effect). The wave IS the care dial: what shares a wave decides how much review each piece gets · **Part B, the rigorous overlay** (below — only when assigned). These combine: a wave can carry one Part-B piece. Full wave reference: `wave-sdlc.md`. |
|
||||
| **DISPATCH DATA** | **The execution-data conventions (S27)** — three tiny data disciplines that make execution state derivable instead of hand-tabulated, consumed by `rig view show execution`. **EC-1, edges in frontmatter:** every open slice carries its hard edges in `depends_on:` (an inline JSON array; an explicit `[]` is a statement, absence is INDETERMINATE); soft/serialization edges are one machine-readable Territory line, exactly `SOFT-AFTER: [ids] — reason`. **EC-2, wave-as-data:** wave composition + review model live on a durable queue row tagged `wave-map` + `format:wave-map-v1` whose body carries one fenced ```json block (`{format:"wave-map-v1", mission, waves:[{id, slices, serialized_order?, review_model?}]}`) — composition only, never status labels (status derives at read time); the planning dial recorded at lock surfaces as the frontmatter field `approved-spec-dial:` beside the approve stamp. **EC-3, dispatch names the worktree:** build batons carry the exact body line `worktree_path=<path>` from dispatch onward — it is Q1's join key; batons without it join by naming only and the view marks them `fragile_join`. Existing batons are never rewritten. |
|
||||
| **RELEASE** | The release ceremony conventions (publication identity, honest release notes) and the CAPABILITY-DELTA LIFECYCLE LAW: each release's delta is authored at the fence and bound to the exact candidate, then reconciled to the published cut. Expiry requires both the canon header naming the absorbed delta and a distinct successor delta existing; a draft header alone is insufficient. Verify the actual boundary event and stop citing an expired delta. Full statement: `release-boundary.md` §6. |
|
||||
| **BOUNDARY** | **The release boundary** — housekeeping run once per fence so the next release starts clean: seats reset or re-primed per their continuity needs, memory distilled (keep what changed a decision recently), every queue swept by destination and dispositioned, substrate torn down, boards frozen as records, a clean-box baseline captured. A checklist of judgment guides, not gates. Full reference: `release-boundary.md`. |
|
||||
|
||||
> Full reference documents for each component (graduated 2026-08-28): the
|
||||
> product-management pass — `product-management-pass.md` · the
|
||||
> planning dial — `planning-dial.md` · the product-journey component catalog —
|
||||
> `product-journey-sdlc.md` · the wave model — `wave-sdlc.md` · the release
|
||||
> boundary (including the capability-delta lifecycle law) — `release-boundary.md` ·
|
||||
> the mission install — `mission-install.md` (lands with its own slice). The
|
||||
> summaries above remain the one-glance description; this document remains the SSOT
|
||||
> pointer for the whole menu.
|
||||
|
||||
Mission and slice YAML may select component IDs from
|
||||
`product-journey-sdlc.md` and record explicit edges between them. That YAML is
|
||||
an address map and execution dashboard prototype, not an enforcement engine.
|
||||
If it is absent, Part A remains the complete default; agents do not invent a
|
||||
composition merely to make one exist.
|
||||
|
||||
# PART A — THE SIMPLE SDLC (applies always)
|
||||
|
||||
@@ -72,10 +68,18 @@ intent (what is this for, what does done look like)
|
||||
→ hand off (rig queue handoff) or stop
|
||||
```
|
||||
|
||||
One full-breadth review at the end, not per increment. Bring work when it is **done**. The human
|
||||
should almost never see something that doesn't match what was planned — QA catches mismatches and
|
||||
kicks them back. When the human looks, they map intent → plan → delivered at a glance, mostly by
|
||||
scanning screenshots down a single column, and give the final 1% approval.
|
||||
Verify the whole outcome at completion, not through a review chain per increment.
|
||||
For a tiny change the builder may hold this check. Independent QA/review runs only
|
||||
when selected by the project/mission/slice composition or an explicit owner
|
||||
assignment; for a wave, at its authored boundary. Available roles never add gates.
|
||||
The authority lookup is `product-journey-sdlc.md#resolve-the-selected-path`.
|
||||
Bring work with evidence that its promised user outcome actually works.
|
||||
|
||||
For a release mission, `mission.yaml` also names the Git strategy before build
|
||||
work starts: the branch/ref that is cumulative integration truth, its base, and
|
||||
when that truth is reconciled to main. Continuous integration to main and a
|
||||
release-line fold followed by one explicit merge are both valid; leaving the
|
||||
choice implicit is not.
|
||||
|
||||
**The heavier flow — proof contracts, plan-lock, C1 drops, proof-lock — is PART B and is not in
|
||||
force unless assigned.**
|
||||
@@ -84,21 +88,41 @@ force unless assigned.**
|
||||
|
||||
Each slice directory carries:
|
||||
|
||||
- **`README.md`** (or the slice body) opening with the three convention
|
||||
sections, in this order and with these exact headings:
|
||||
- **`SPEC.md`** — the one authored node file. Its frontmatter carries
|
||||
`intent:` and may carry `depends_on:` as a list of sibling work-node IDs;
|
||||
`depends_on` orders siblings and is never followed by the path-only context
|
||||
trace. The body opens with the three convention sections, in this order and
|
||||
with these exact headings:
|
||||
- **`## Intent`** — the recorded intent, verbatim. The UI projects this
|
||||
text as the INTENT section.
|
||||
- **`## Mini-requirements`** — the concise, one-glance requirement tier
|
||||
(numbered list). This is the founder's first structured catchpoint;
|
||||
(numbered list). This is the human operator's first structured catchpoint;
|
||||
approval starts here.
|
||||
- **`## Proof contract`** — a checkbox list of promised deliverables (see
|
||||
§3). The UI's DELIVERED section pairs each item with its proof.
|
||||
- **`IMPLEMENTATION-PRD.md`** — the full PRD. It OPENS with the
|
||||
mini-requirements; everything between intent and proof is **elastic**
|
||||
(see §7 — for a small slice the mini-requirements may BE the whole PRD).
|
||||
- **`PROGRESS.md`** — the slice's acceptance checklist: the durable
|
||||
mission/slice-level to-do checked at acceptance. In-process steps stay in
|
||||
the working agent's own todo tool.
|
||||
- **`PROOF.md`** — the retained proof summary, explicitly paired to the
|
||||
`SPEC.md` proof contract.
|
||||
- **`proof/`** — the proof-artifact directory. Curated canonical evidence
|
||||
lands here via `rig proof` with valid C1 headers (§5).
|
||||
|
||||
Legacy `README.md`, `IMPLEMENTATION-PRD.md`, and pre-convention trees remain
|
||||
readable indefinitely. New scaffolds do not create or mirror them, and repair
|
||||
is additive: it never deletes, renames, or rewrites legacy bytes.
|
||||
|
||||
When the owning scope selects `proofPolicy.judges`, use `rig proof judge` to
|
||||
record an attributed accept, reject or withdraw decision on a contract item,
|
||||
and `rig proof show` to read derived readiness up through slice, mission and
|
||||
project. The nearest slice, mission or project policy selects authorized judges;
|
||||
`rig proof --help` describes setup, selectors and evidence requirements. A
|
||||
correction preserves history and unrelated judgments without editing ancestor
|
||||
status checklists. An artifact can identify non-code work without a fabricated
|
||||
commit. Evidence capture (`proof add`), policy acceptance, higher outcome
|
||||
judgment and publication are distinct. This optional component does not select
|
||||
Part B or add a mandatory review gate.
|
||||
|
||||
The three sections project into the UI's one review structure: a vertical
|
||||
stack of **INTENT → PLAN → DELIVERED**. A slice missing a section still
|
||||
renders (the projection degrades to a muted "—", never invents content) —
|
||||
@@ -140,21 +164,9 @@ either being wrong (for example, one over the frontmatter block and one over
|
||||
the whole file). Coverage is what makes a cited hash verifiable rather than
|
||||
merely impressive.
|
||||
|
||||
**Canonical convention (the ONE origin — cite it, do not restate it here),
|
||||
cited in the form that convention itself prescribes — path + section anchor +
|
||||
dated whole-file hash:**
|
||||
**Use path + anchor as the authority and a whole-file hash only to date the
|
||||
citation.** The address survives legitimate content changes; the digest does not.
|
||||
|
||||
- **path:** `artifact-cross-citation-path-plus-lineage.md` (internal authoring convention — not shipped in this public repo)
|
||||
- **anchor:** *A cited hash STATES ITS COVERAGE — span is part of the definition*
|
||||
(2026-08-08)
|
||||
- **hash:** `sha256 14d9a273d542648ab1e0a82c7fd98e8a8c59cb02a83be76b8af9abfb717b34d2`
|
||||
— **coverage: WHOLE FILE, bytes as stored, INCLUDING the trailing newline**
|
||||
(0x0a terminal byte verified), 229 lines, as-of 2026-08-08.
|
||||
|
||||
The **path + anchor is the AUTHORITY**; the whole-file hash **DATES** the
|
||||
citation. That artifact is a living doc: the hash goes stale at the next
|
||||
legitimate append, the anchor does not — the CURRENT-line convention applied to
|
||||
a citation.
|
||||
## A4. When implementation finds the intent rested on something that does not exist
|
||||
|
||||
A slice's intent can only be honoured to the extent its assumptions hold. Sometimes implementation
|
||||
@@ -188,7 +200,7 @@ Do not over-engineer this. The entire goal is an accurate story for whoever read
|
||||
The SDLC has exactly three fixed capture points: **intent** → a
|
||||
**proportional structured requirement** → **proof**. Everything between is
|
||||
elastic. For a small slice (a bug fix, a research note), the
|
||||
mini-requirements may BE the whole PRD — the convention sections must be
|
||||
mini-requirements may BE the whole specification — the convention sections must be
|
||||
present so the slice projects, but their contents scale to the work. Gates
|
||||
are losslessness checks on the decompression from intent to delivery, not
|
||||
paperwork. Scaffolding emits the sections; it must never mint ceremony.
|
||||
@@ -203,19 +215,20 @@ reported as unknown, not failure.
|
||||
## A7. Where the knowledge lives (the four pointers)
|
||||
|
||||
- **This document** — the SSOT.
|
||||
- **Scaffold**: `rig scope slice create` emits the convention sections +
|
||||
`proof/` + an `IMPLEMENTATION-PRD.md` skeleton for every template kind;
|
||||
`rig scope mission create` emits the convention pointer.
|
||||
- **Skills — TWO, matching this document's two parts**: `mission-slice-sop`
|
||||
teaches **Part A**, the light default; `mission-slice-intent-proof-sop`
|
||||
teaches **Part B**, the assigned overlay. Assigned-only, never self-selected.
|
||||
- **Scaffold**: `rig scope slice create` emits `SPEC.md` + `PROGRESS.md` +
|
||||
`PROOF.md` + `proof/` for every template kind; `rig scope mission create`
|
||||
emits an intent-bearing `SPEC.md` + `NOTES.md`. Core emits only this
|
||||
mode-neutral shape; mode plugins add richer material through the extension
|
||||
seam rather than teaching core a mode.
|
||||
- **Skill**: `mission-slice-sop` teaches **Part A**, the light default. **Part B**
|
||||
is defined below and applies only when assigned — never self-selected.
|
||||
- **Bootstrap**: the shipped agent overlay points fresh seats at the skill
|
||||
and this document at boot.
|
||||
|
||||
# PART B — THE RIGOROUS OVERLAY (assigned only)
|
||||
|
||||
> **ENTRY CONDITION:** everything below applies **only** when the founder — or an orchestrator
|
||||
> explicitly relaying the founder — has assigned the heavy path to a **named** piece of work.
|
||||
> **ENTRY CONDITION:** everything below applies **only** when the human operator — or an orchestrator
|
||||
> explicitly relaying that decision — has assigned the heavy path to a **named** piece of work.
|
||||
> If you arrived here without an assignment, stop and use Part A.
|
||||
>
|
||||
> **Rails:** the locks are **two, and only two**. Nothing here authorizes a second gate round on
|
||||
@@ -264,9 +277,9 @@ Two deliberate stamps, both written by the SAME shipped verb
|
||||
(`rig scope slice approve`, one daemon-side write path: frontmatter stamp +
|
||||
append-only audit row land together):
|
||||
|
||||
- **Plan-lock:** `rig scope slice approve <slice> --scope spec` — "the PRD
|
||||
matches my intent; THIS artifact set is what gets built." Pins the locked
|
||||
artifact set (spec/PRD/mockups) out of everything else in the folder.
|
||||
- **Plan-lock:** `rig scope slice approve <slice> --scope spec` — "the spec
|
||||
matches my intent; THIS artifact set is what gets built." Pins `SPEC.md`
|
||||
(and planned mockups, when present) out of everything else in the folder.
|
||||
- **Proof-lock:** `rig scope slice approve <slice> --scope delivery`
|
||||
(the default scope) — the terminal "this is done" sign-off; fires the
|
||||
freeze.
|
||||
@@ -335,9 +348,6 @@ an append-only pile where the human can't tell final from superseded.
|
||||
|
||||
## Scratch-daemon hermeticity: the DB a scratch daemon opens is the one you named
|
||||
|
||||
*(Desk-ruled 2026-08-25, row qitem-20260825005846-7960f4dd; 0.5.3-era doctrine — a harness rule,
|
||||
not product behavior.)*
|
||||
|
||||
A scratch daemon launch **strips the inherited environment's `OPENRIG_DB` and names an explicit
|
||||
scratch DB path under the scratch `OPENRIG_HOME`** — both halves, every launch:
|
||||
|
||||
@@ -353,6 +363,6 @@ the scratch context read or wrote state it never owned, and whether anything vis
|
||||
luck, not hermeticity.
|
||||
|
||||
**The discriminator that keeps this rule honest:** an explicit `OPENRIG_DB` still WINS by design
|
||||
(`daemon-db-path.ts`; prior ruling 00c4ab76) — deliberate split-path configuration is legitimate
|
||||
and untouched. This rule governs **inheritance into scratch contexts**, where nobody decided
|
||||
anything: strip what you did not choose, then choose.
|
||||
(`daemon-db-path.ts`) — deliberate split-path configuration remains legitimate and
|
||||
untouched. This rule governs **inheritance into scratch contexts**, where nobody
|
||||
decided anything: strip what you did not choose, then choose.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Create your own Slack app for OpenRig (experimental)
|
||||
|
||||
> **Experimental in 0.6.0.** The manifest (`rig slack manifest`) and this setup guide are new
|
||||
> and have not been confirmed against a real app creation. The Slack connector itself and its
|
||||
> `setup`, `verify`, `enable`, `disable` and `status` commands are not experimental. If a step
|
||||
> here does not match what Slack shows you, please
|
||||
> [open an issue](https://github.com/mvschwarz/openrig/issues/new/choose) or send a pull request
|
||||
> ([CONTRIBUTING.md](../../CONTRIBUTING.md)).
|
||||
|
||||
OpenRig's Slack connector talks to a Slack app that you create in your own workspace. OpenRig
|
||||
ships the app's manifest; it does not host an app, run an install endpoint, or publish anything
|
||||
to the Slack Marketplace. The app is a Socket Mode app, so it lives in the one workspace you
|
||||
create it in.
|
||||
|
||||
`rig slack manifest` prints the manifest. It works offline, before any daemon or token exists:
|
||||
|
||||
```bash
|
||||
rig slack manifest # the manifest as YAML
|
||||
rig slack manifest --url # Slack's create-app link with the manifest prefilled
|
||||
rig slack manifest --json # the manifest, its scopes and events, and why each scope is requested
|
||||
```
|
||||
|
||||
The TUI shows the same link on the Connections page while Slack is not configured. Neither the
|
||||
CLI nor the TUI opens a browser, accepts tokens, or creates an app.
|
||||
|
||||
## Steps
|
||||
|
||||
These are the expected steps, based on Slack's app-manifest documentation. They have not yet
|
||||
been confirmed against a real creation run. Slack's form may ask for something the prefill did
|
||||
not fill in; if so, follow the form.
|
||||
|
||||
1. Open the link from `rig slack manifest --url` in a browser.
|
||||
2. Sign in to Slack if asked, pick the workspace, review the prefilled manifest, and click
|
||||
**Create**.
|
||||
3. Under **Socket Mode**, confirm it is enabled. Enable it if it is not.
|
||||
4. Under **Basic Information → App-Level Tokens**, generate a token with the
|
||||
`connections:write` scope and copy it (it starts with `xapp-`).
|
||||
5. Install the app to the workspace and approve the requested scopes.
|
||||
6. Under **OAuth & Permissions**, copy the **Bot User OAuth Token** (it starts with `xoxb-`).
|
||||
7. Put both tokens in a private env file readable only by you (`chmod 600`):
|
||||
|
||||
```bash
|
||||
SLACK_BOT_TOKEN=xoxb-...
|
||||
SLACK_APP_TOKEN=xapp-...
|
||||
```
|
||||
|
||||
8. Run `rig slack setup --channel <channel-id> --secrets-env-file <path>`, then
|
||||
`rig slack verify`, then `rig slack enable`.
|
||||
9. Invite the bot to the channel you configured (`/invite @OpenRig` in that channel).
|
||||
|
||||
## What the app asks for
|
||||
|
||||
Run `rig slack manifest --json` for the exact list and the reason for each scope. There are two
|
||||
groups:
|
||||
|
||||
- **Baseline scopes**: posting messages, reading message history in public channels the app is
|
||||
a member of, and reading channel details.
|
||||
`rig slack verify` checks these.
|
||||
- **Feature scopes**: `files:read` (download attachments people send), `files:write` (upload
|
||||
attachments to Slack), and `app_mentions:read` (receive @-mentions of the app). `rig slack verify`
|
||||
does **not** check these, so a READY from verify does not prove attachments or mentions will
|
||||
work.
|
||||
|
||||
If a feature scope was not granted, the effect differs by feature:
|
||||
|
||||
- **Attachments** (`files:read`, `files:write`): the file download or upload call fails. A
|
||||
message with an attachment that could not be downloaded is still delivered, with the failed
|
||||
file named in it. A post whose attachment could not be uploaded still delivers its text, and
|
||||
the failure appears only in the daemon log (`rig daemon logs`).
|
||||
- **Mentions** (`app_mentions:read`): Slack does not deliver `app_mention` events to the app, and
|
||||
nothing in OpenRig reports that they are missing.
|
||||
|
||||
So after installing, compare the granted scopes Slack shows for the app with all six scopes that
|
||||
`rig slack manifest --json` lists.
|
||||
|
||||
The app subscribes to messages in public channels it is a member of (`message.channels`) and to
|
||||
mentions of the app (`app_mention`). It does not request direct-message or private-channel access.
|
||||
|
||||
## What the connector does with the tokens
|
||||
|
||||
The tokens stay in the env file you created. The connector reads them from that file and uses them
|
||||
to authenticate to Slack: it opens an outbound Socket Mode connection with the app-level token and
|
||||
calls Slack's Web API with the bot token. This connector has no OpenRig-hosted component and
|
||||
makes outbound connections only. That statement is about this Slack connector, not about every
|
||||
part of OpenRig.
|
||||
|
||||
## Next
|
||||
|
||||
- `rig slack status` shows what is still missing, without contacting Slack.
|
||||
- `rig slack verify` checks the granted baseline scopes and channel membership with Slack.
|
||||
@@ -0,0 +1,244 @@
|
||||
# Occupant-owned stream classification and shadow capture (experimental)
|
||||
|
||||
> **Experimental in 0.6.0, optional and off by default.** The Jev classification path and shadow
|
||||
> capture may be incomplete or not work, and no accuracy or production-reliability claim is
|
||||
> made. OpenRig's delivery, watchdog and routing decisions do not use their output. If they do
|
||||
> not behave as described here, please
|
||||
> [open an issue](https://github.com/mvschwarz/openrig/issues/new/choose) or send a pull request
|
||||
> ([CONTRIBUTING.md](../../CONTRIBUTING.md)).
|
||||
|
||||
This source provides a bounded receiver for one real classifier occupant. It does
|
||||
not start an agent, register a watchdog job, or enable capture by default.
|
||||
Taxonomy, questions and decisions belong to the occupant; the daemon supplies
|
||||
source facts and enforces its existing lease, execution and idempotency fences.
|
||||
An explicit optional Jev mode supplies advisory decisions through the same worker.
|
||||
Experimental labels have no demonstrated accuracy or calibrated confidence and
|
||||
do not replace delivery, watchdog, or routing decisions.
|
||||
|
||||
## Optional foreground Jev experiment
|
||||
|
||||
With no configuration, the experiment is off. Choose an explicit local JSON path
|
||||
in an existing private directory; the file contains only enabled state and bounds:
|
||||
|
||||
```sh
|
||||
rig project experimental status --config ./experiment.json --json
|
||||
rig project experimental enable --config ./experiment.json --max-requests 3 --timeout-ms 10000 --json
|
||||
rig project experimental disable --config ./experiment.json --json
|
||||
```
|
||||
|
||||
Enable does not start processing, capture, a daemon or an agent. Status and disable
|
||||
make no provider request. The invoking process needs `OPENROUTER_API_KEY` in its
|
||||
environment for an enabled run; this entry never searches credential files or
|
||||
copies credentials into configuration. Missing credentials fail before attempts
|
||||
or provider work. Normal key provisioning remains with the operator.
|
||||
|
||||
The selected route is `https://openrouter.ai/api/alpha/decisions`, pinned to
|
||||
`typesafe/jev-1.13`, without provider fallback or HTTP redirects. A request declares
|
||||
maximum provider prices of $0.05 per million prompt tokens, zero completion and
|
||||
request price. These are admission ceilings, not a price quote, spend estimate or
|
||||
credential-access claim. Each foreground run allows 1–20 requests (default 3),
|
||||
each with a 1–30 second deadline (default 10), 24 KiB request and 256 KiB response
|
||||
limits. Failed requests consume their count. Nothing retries in the background.
|
||||
|
||||
To process stream items from the actual selected occupant:
|
||||
|
||||
```sh
|
||||
rig project wake --project PROJECT --taxonomy ./taxonomy.yaml \
|
||||
--classifier-version experimental-jev-1.13 --evidence-epoch OWNER_EPOCH \
|
||||
--limit 3 --experiment ./experiment.json --json
|
||||
```
|
||||
|
||||
Use the taxonomy format described below. Each criterion must have a nonempty
|
||||
string description; `__unknown__` is reserved. Questions stay outside the daemon.
|
||||
The request sends the selected item's body, supplied taxonomy questions/criteria,
|
||||
canonical scope IDs and current roster destinations to OpenRouter. Select inputs
|
||||
appropriate for that external processing. It does not transmit the credential
|
||||
in evidence or store it in the classification. The output binds current source,
|
||||
taxonomy, candidate version, lease, occupant generation and attempt execution.
|
||||
No action or confidence label is inferred. Duplicate judgment remains manual.
|
||||
|
||||
The smaller of `--limit` and the configured request maximum bounds the wake.
|
||||
Unknown answers remain null; a wholly unknown answer abstains. Invalid model,
|
||||
provider, answer keys, choices, probabilities or response bytes reject the whole
|
||||
answer. Probability sums retain the strict 0.001 tolerance; there is no rounding
|
||||
repair or partial salvage. Unavailable/invalid provider work stops that run with
|
||||
no classification write. Inspect the returned attempt state: terminal abstentions
|
||||
are not reopened, and failed/unknown daemon writes must be reconciled through the
|
||||
existing ledger. Never change the evidence epoch merely to retry an experiment.
|
||||
|
||||
Disable is checked before each call and before applying its result. A separate
|
||||
disable command cannot cancel an already forwarded remote request; its late
|
||||
result is discarded, with the existing deadline bounding the wait. Ctrl-C or
|
||||
SIGTERM requests immediate cancellation of the foreground run. An uncooperative
|
||||
request remains reported pending and cannot cause a late write or a second call
|
||||
in that run. Remote cancellation or refund is not proven by local cancellation.
|
||||
The command registers no wake, returns no automatic continuation, and requires
|
||||
an explicit later invocation to do more work.
|
||||
|
||||
For selected observations from an existing shadow archive:
|
||||
|
||||
```sh
|
||||
rig project experimental capture --config ./experiment.json \
|
||||
--input ./selected-shadow.jsonl --output ./new-advisory-results.jsonl --json
|
||||
```
|
||||
|
||||
This reads at most 8 MiB and considers only the first configured maximum number
|
||||
of records. It never captures a terminal. The input remains unchanged; output is
|
||||
exclusively created at mode 0600. Keep prior output and choose another filename
|
||||
only for a deliberately selected new run. It sends captured post-screen text to
|
||||
the same provider, and retains the original attempt/node/occupant/pane binding and
|
||||
capture hash beside the answer. Missing capture or binding is unavailable and
|
||||
makes no provider call. State answers are experimental hints; delivery remains
|
||||
`INDETERMINATE` because screen text cannot prove submitted/consumed/effect and the
|
||||
archive does not retain the original sent text. No result feeds back into live
|
||||
transport or inventory. Failed runs preserve already written output; inspect
|
||||
`unavailable`, `remaining`, call count and stop reason rather than treating a file
|
||||
as proof of success.
|
||||
|
||||
## One bounded wake
|
||||
|
||||
From the selected occupant, read candidates using the configured project ID:
|
||||
|
||||
```sh
|
||||
rig project candidates --project PROJECT --taxonomy /private/taxonomy.yaml \
|
||||
--classifier-version VERSION --evidence-epoch OWNER_EPOCH --limit 20 --json
|
||||
```
|
||||
|
||||
The result includes eligible stream IDs, authored scope IDs with exact source
|
||||
paths/hashes, the current running roster of the occupant's rig, observation time,
|
||||
and a candidate version. Taxonomy YAML supplies `version` and
|
||||
`fields.{kind,urgency,maturity,area}` with a `question` and `values` mapping. These
|
||||
questions are returned to the occupant; draft values are not verified truth.
|
||||
Confidence has no selectable values until calibration selects that contract.
|
||||
Destination choices are the current roster sessions plus explicit `pool`.
|
||||
Null/omitted destination remains unknown; it is not converted into `pool`.
|
||||
The candidate hash binds the selectable values as well as source and taxonomy
|
||||
hashes. Packets prepared before this pool correction require fresh preparation;
|
||||
it does not reopen historical attempts or change the evidence epoch.
|
||||
|
||||
Read each eligible item with `rig stream show ITEM --json`. Duplicate candidates
|
||||
are the latest 100 nonarchived items with exact body-hash evidence references;
|
||||
their preview is capped at 2,000 characters and marked when truncated. Read the
|
||||
complete candidate before selecting a duplicate. A retrieval miss means unknown.
|
||||
Directory names, issue IDs and bare membership never supply scope IDs. Source
|
||||
errors are explicit, and partial source snapshots cause abstention.
|
||||
|
||||
Write an occupant-owned decision file (at most 100 unique item IDs, at most 1 MiB):
|
||||
|
||||
```json
|
||||
{
|
||||
"candidateSetVersion": "sha256:<version returned by candidates>",
|
||||
"decisions": [{
|
||||
"streamItemId": "<eligible ID>",
|
||||
"bodyHash": "sha256:<SHA256 of the exact UTF-8 item body>",
|
||||
"decision": { "kind": "classify", "labels": {
|
||||
"classificationType": "<selected taxonomy value>",
|
||||
"scopeRef": "<selected canonical mission or slice ID>",
|
||||
"needsHuman": null
|
||||
}}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
An explicit abstention is `{"kind":"abstain","reason":"why unknown"}`. Other
|
||||
label keys are `classificationUrgency`, `classificationMaturity`,
|
||||
`classificationDestination`, `area`, and `classificationConfidence`. Omitted or
|
||||
null labels stay unknown. A duplicate needs both `duplicateOfStreamItemId` and
|
||||
the matching `duplicateEvidenceRef` from the candidates, plus occupant judgment
|
||||
that it really is a duplicate. An existing item alone is not that judgment.
|
||||
|
||||
```sh
|
||||
rig project wake --project PROJECT --taxonomy /private/taxonomy.yaml \
|
||||
--classifier-version VERSION --evidence-epoch OWNER_EPOCH --limit 20 \
|
||||
--decisions /private/decisions.json --json
|
||||
```
|
||||
|
||||
This command actually invokes one `StreamClassificationWorker.wake()` through
|
||||
the existing HTTP services. It derives sender identity through `DaemonClient`,
|
||||
requires a current running occupant, and carries the captured generation through
|
||||
each lease/attempt/classification request. It never invents a daemon holder.
|
||||
Existing direct HTTP callers keep their documented sender-provenance behavior.
|
||||
|
||||
The command refreshes source candidates before acquiring a lease. Missing or
|
||||
stale decision packets return unavailable without starting attempts; refresh
|
||||
preparation if the roster, scopes, taxonomy or recent stream changed. During a
|
||||
wake, an item with no exact body-bound decision, missing sources, or a selected
|
||||
candidate that is unavailable abstains. This is deliberately strict; continuous
|
||||
source churn can require repeated preparation and is not a throughput claim.
|
||||
|
||||
Each wake processes at most `--limit` items (1–100; default 20), starting with the
|
||||
oldest eligible work rather than relying on a lossy notification cursor. Durable
|
||||
terminal attempts stay excluded. A new candidate version does **not** authorize
|
||||
another pass: only an owner-selected evidence epoch changes that attempt identity.
|
||||
Retain the candidates output beside the decisions and result so its version is
|
||||
reproducible. The CLI does not keep a second local ledger.
|
||||
|
||||
The result carries per-item outcomes, `moreEligible`, `nextWakeAt`, and a watchdog
|
||||
`wakeRequest` descriptor with the real session/generation and a receiving
|
||||
instruction. `registered:false` is literal. A periodic-reminder message asks the
|
||||
occupant to prepare and run this entry; delivery of that message is not execution.
|
||||
Registration/placement remains an explicit later action. Honor `nextWakeAt`:
|
||||
lease maintenance uses TTL/3 opportunities; lease loss or an unavailable service
|
||||
backs off 60 seconds. The daemon's retry/exhaustion ledger remains authoritative.
|
||||
The ordinary client bounds each request at five seconds; a timed-out write has
|
||||
an unknown outcome until the ledger is read, and is never reported as written.
|
||||
|
||||
There is no provider in the manual-decision path. The reusable asynchronous worker has one
|
||||
pending classifier slot; timeout requests cancellation but cannot force it. An
|
||||
uncooperative classifier blocks further calls in that instance and late results
|
||||
cannot write. That in-memory pending slot is not cross-process cancellation.
|
||||
|
||||
## Separate bounded shadow drain
|
||||
|
||||
The production transport and inventory probes accept the same optional observer.
|
||||
They record only captures already taken, with the node, occupant, pane and capture
|
||||
target bound before awaits. The ordinary cheap inventory path still takes no
|
||||
capture. Retained-no-write is a separate event, never delivery evidence.
|
||||
|
||||
Activation requires explicitly supplied `OPENRIG_SHADOW_CAPTURE` JSON at a later
|
||||
authorized daemon start. Absence or invalid configuration leaves capture disabled.
|
||||
This document supplies no live destination or activation instruction. Required
|
||||
fields and engineering ceilings are:
|
||||
|
||||
| Field | Meaning | Maximum |
|
||||
|---|---|---|
|
||||
| `destination` | New absolute JSONL file in a canonical, current-user-owned 0700 directory | Explicit path |
|
||||
| `maxRecords` | Total reserved sink records | 100,000 |
|
||||
| `maxBytes` | Total reserved sink bytes, including newlines | 1 GiB |
|
||||
| `capacity` | Queued observation count | 1,000 |
|
||||
| `maxQueuedBytes` | Serialized bytes waiting in the queue | 8 MiB |
|
||||
| `maxObservationBytes` | Serialized bytes per observation | 1 MiB |
|
||||
|
||||
All numeric fields are required positive safe integers. These are engineering
|
||||
capacity limits, not approved corpus sizes or classification thresholds. One
|
||||
in-flight drain can hold up to 64 additional observations, bounded by the queue's
|
||||
byte allowance; queue plus drain can therefore retain up to twice that allowance.
|
||||
The standalone observer defaults are 256 entries, 4 MiB queued and 256 KiB per
|
||||
observation; explicit production configuration supplies each value.
|
||||
|
||||
`rig project shadow-status` only inspects configuration/counters.
|
||||
`rig project shadow-drain` asks the existing HTTP service to drain up to 64 rows;
|
||||
it requires an actor and never enables capture. No background drain timer is added.
|
||||
`rig project shadow-stop` disables new enqueueing immediately, finishes any active
|
||||
drain and the finite retained queue, then closes the sink. Repeated stop is
|
||||
idempotent; it cannot enable capture or delete the existing archive. Existing
|
||||
quota and sink-failure losses remain counted. A slow disk can leave stop pending;
|
||||
inspect status after a client timeout instead of assuming the flush completed.
|
||||
Enabling capture again requires a separately authorized daemon start with a new
|
||||
destination; enabling the local provider experiment does not enable capture.
|
||||
The file is exclusively created with mode 0600. An existing file is refused,
|
||||
never overwritten, appended, rotated or deleted. A restart requires a new selected
|
||||
destination. Private-file creation currently requires POSIX ownership support.
|
||||
|
||||
The hot path only performs bounded synchronous enqueue work; it never awaits disk
|
||||
or drain completion. Slow sinks leave one drain pending; concurrent drains do not
|
||||
start another writer. Overflow drops new observations. Capacity exhaustion stops
|
||||
the sink. Errors stop writes, count losses and preserve any partial file; failed
|
||||
batches are not replayed. `reservedRecords/Bytes` include attempted writes, while
|
||||
`completedRecords/Bytes` count successful appends. `drained` means removed from
|
||||
the observer, not necessarily persisted. Queue count drops, byte-bound drops,
|
||||
record errors, missing/unavailable captures and sink failures remain separate.
|
||||
|
||||
Live collection, private destination/retention selection, corpus labels, hosted
|
||||
calibration, native placement, package/install proof and independent visible /
|
||||
submitted / consumed / effect evidence remain outside this source entry.
|
||||
@@ -0,0 +1,102 @@
|
||||
# The wave SDLC — parallel build, wave-level review, composition as the care dial
|
||||
|
||||
This is a selectable build and review model from `sdlc-conventions.md`'s component
|
||||
menu. The mission owner decides whether it fits the work. Lock stamps in this model
|
||||
are ADDITIVE: approval appends metadata and never rewrites authored bytes, so a
|
||||
candidate's identity survives its own approval.
|
||||
|
||||
## The model in one paragraph
|
||||
|
||||
Slices build IN PARALLEL across every build-class seat, each in a DISJOINT FILE
|
||||
TERRITORY, each merged serially by one integrator the moment its candidate freezes
|
||||
with builder-proven receipts. Independent review happens ONCE PER WAVE over the wave's
|
||||
whole accumulated range — two reviewers with different vantages (ideally different
|
||||
runtimes), never the writers — and their findings drive fix rounds that re-earn both
|
||||
verdicts at the round's final revision. Checks stay per-slice (failing-test-first
|
||||
proof, verify-by-effect, honest receipts); rounds happen per-wave.
|
||||
|
||||
## The mechanics
|
||||
|
||||
1. **PLAN:** wave entries in the plan of record; the planner shapes specs; the plan
|
||||
authority locks them. Every slice gets a real workspace or proof has no home.
|
||||
2. **DISPATCH:** batons fan out with EXCLUSIVE file territories. THE BRIEF IS THE
|
||||
UNIT: every assignment carries the goal, the acceptance criteria, the complete
|
||||
context routes (exact paths), the territory — and this standing grant: "Navigate
|
||||
the implementation yourself: derive what you need, research, spawn readers,
|
||||
decide your own build order. Ask when stuck; the orchestrator is here to help."
|
||||
DEFAULT SCOPE IS THE WHOLE SPEC, built in one sustained run to ONE frozen
|
||||
candidate. Handing out anything smaller requires naming the reason on the brief
|
||||
itself (an unresolved decision, a live territory conflict, a genuine
|
||||
fork-in-the-road risk) — an unexplained small chunk is a dispatch defect.
|
||||
Territory overlap = serialize. Conflict discovered mid-build = STOP AND RULE.
|
||||
Why: the builder holds the spec and the code; decisions about how to build
|
||||
belong where the building context lives. Detailed sequencing instructions were
|
||||
scaffolding when models were weak; today they are a cage, and per-command
|
||||
routing is the failure mode, not diligence.
|
||||
THE BRIEF IS A MAP, NOT THE TERRITORY. Distillation is welcome — for a minimal
|
||||
slice the brief may honestly be richer than the spec — but it is never the
|
||||
receiver's ONLY context: the builder's first act is self-onboarding from the
|
||||
routed reading list (the mission-install pattern at slice scale), then checking
|
||||
the map against the ground it actually stands on. Nobody knows the ground floor
|
||||
better than the builder; the wide angle lives a layer up. When the ground
|
||||
disagrees with the map, that is a finding to raise — not a reason to follow
|
||||
the map off a cliff.
|
||||
3. **BUILD:** failing-test-first against a pristine base with committed final test
|
||||
bytes; freeze a single candidate revision; receipts cite the FINAL revision
|
||||
(re-earned after any rebase).
|
||||
4. **MERGE:** integrator-only, serially, verified at source each time (candidate
|
||||
parent equals pre-merge tip; changed files equal declared territory). Merge
|
||||
announcements are REBASE TRIGGERS for every open candidate.
|
||||
5. **WAVE REVIEW:** fires when the wave's build completes. Two independent reviewers,
|
||||
base-scoped at the tip, writers excluded from reviewing their own work. The two
|
||||
gates ask DIFFERENT questions — does the structure hold, versus does each claim
|
||||
survive contact with source. That is the design, not redundancy.
|
||||
Reviewers also review for DRIFT, not only defects: does the built thing still
|
||||
match the acceptance criteria the brief stated — is this still the doghouse the
|
||||
owner asked for, or did locally-defensible steps accrete toward a moon base?
|
||||
Plot-loss is a first-class finding with the same standing as a logic defect.
|
||||
Reviews also DISPOSE each miss by cause: CONTEXT-GAP — the spec or its routes
|
||||
lacked what the moment needed (a planning finding; the fix lands in the spec) —
|
||||
versus JUDGMENT-GAP — the context was there and the call was wrong (a builder
|
||||
finding; the fix is a check). One word in the verdict artifact. The rate this
|
||||
produces is the calibration the dispatch shape tunes on.
|
||||
6. **FIX ROUNDS:** findings become forward-fix candidates (merges stand; merged is
|
||||
not running). One re-review pass per round re-earns both legs at the final
|
||||
revision. Narrow rechecks for narrow corrections.
|
||||
7. **SEAL:** both legs clear at one revision = wave sealed; the next slot opens.
|
||||
|
||||
## THE WAVE IS THE CARE DIAL
|
||||
|
||||
Wave COMPOSITION is the control surface for how carefully a piece is handled: a
|
||||
large, complicated, or load-bearing slice gets a wave OF ITS OWN (its review ceremony
|
||||
fires for it alone); a big piece can even split across waves. This decouples what an
|
||||
older model conflated: mission and slice workspaces stay flexible organizational
|
||||
containers; waves are the separate layer that sequences work, assigns it, and prices
|
||||
its handling care. Care is a per-piece property, never a per-mission one.
|
||||
|
||||
## When to use it
|
||||
|
||||
Many small-to-medium, root-caused, evidence-backed slices; enough seats to
|
||||
parallelize; territories that partition cleanly; an integrator who can merge serially
|
||||
and verify fast.
|
||||
|
||||
## When NOT to use it
|
||||
|
||||
Design-uncertain or shared-region work (one slice, classic two-leg review, or a
|
||||
design session first). Anything irreversible within the hour (publish, cutover,
|
||||
destructive operations) — that is the heavy path. And when no independent non-writing
|
||||
reviewers exist: nothing ever self-reviews; without them the wave gate is theater.
|
||||
|
||||
## Failure modes and mitigations
|
||||
|
||||
- **Freeze-merge races:** a candidate freezes seconds before a merge moves the tip.
|
||||
Mitigation: zero-overlap restack with content-identity proof (stable patch-id) in
|
||||
an isolated worktree; builder-side self-catch on merge announcements.
|
||||
- **Evidence theater:** a failing test that fails for the wrong reason reads
|
||||
identical to a real one. Mitigation: reviewers re-derive the failures
|
||||
independently; false characterizations retire to a correction history, never a
|
||||
silent rewrite.
|
||||
- **Presence-not-absence tests:** asserting the true line exists while the false
|
||||
line still prints. Rule: pin the ABSENCE of the false claim in every encoding.
|
||||
- **Phantom checkout status:** merge-by-reference leaves a shared checkout's index
|
||||
stale. Never build or commit from a shared checkout; sync its index at fences.
|
||||
@@ -21,11 +21,11 @@ A worktree can therefore report green against code it does not contain.
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Run `npm install` in each worktree** (~4s, one time). It is not the slow path; it is the
|
||||
only path that resolves workspace packages to the worktree's own source.
|
||||
1. **Run `npm install` in each worktree** so workspace packages resolve to the
|
||||
worktree's own source.
|
||||
2. **Never symlink `node_modules` from the primary checkout.** It appears to work — builds pass,
|
||||
typechecks pass — which is exactly the danger.
|
||||
3. **`npx tsc` with no `node_modules` installs an unrelated 12-year-old `tsc` package** and prints
|
||||
3. **`npx tsc` with no `node_modules` installs an unrelated `tsc` package** and prints
|
||||
"This is not the tsc command you are looking for". That message means *no install*, not a
|
||||
TypeScript error.
|
||||
4. **Build `@openrig/daemon` before typechecking `@openrig/cli`** in a fresh worktree: cli imports
|
||||
|
||||
+42
-3
@@ -33,14 +33,53 @@ For each release:
|
||||
|
||||
1. Copy `_template.md` to `vX.Y.Z.md`.
|
||||
2. Fill in the release summary, included changes, operator notes, known limitations, and verification performed.
|
||||
3. Create a git tag for the release:
|
||||
3. Run the **substance gate** at the exact release cut. The gate derives every
|
||||
file under the shipped pack sources, verifies a hash-bound human judgment
|
||||
and an individual disposition for each mechanical candidate, scans the
|
||||
complete npm artifact set derived by the packager, and writes the durable
|
||||
receipt. The receipt records both artifact and scanned file lists; their
|
||||
artifact-minus-scanned diff must be empty. The review JSON carries one
|
||||
entry per file:
|
||||
|
||||
```json
|
||||
{
|
||||
"surfaces": [{
|
||||
"path": "packages/daemon/context-packs-src/example/guide.md",
|
||||
"sha256": "<sha256 of the reviewed bytes>",
|
||||
"verdict": "ship",
|
||||
"reason": "Generic product guidance.",
|
||||
"candidateDispositions": []
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Assemble the package first, then run the named gate from the same clean
|
||||
worktree with the judge and cut SHA explicit. The assembly emits the
|
||||
substance roots it actually staged; the gate never carries its own root
|
||||
list. A non-shipping human verdict is one of `instance-fact`,
|
||||
`internal-path`, `position-knowledge`, or `lore-class` and carries its
|
||||
reason in the same per-file entry:
|
||||
|
||||
```bash
|
||||
bash scripts/build-package.sh
|
||||
npm run gate:substance -- \
|
||||
--review /path/to/substance-review.json \
|
||||
--receipt /path/to/substance-receipt.json \
|
||||
--judge <seat-or-person> \
|
||||
--cut-sha "$(git rev-parse HEAD)"
|
||||
```
|
||||
|
||||
A missing/stale judgment, an undispositioned candidate, internal substance,
|
||||
a lore-classed pack, a failed full artifact scan, or any artifact absent
|
||||
from the scanned set refuses the cut.
|
||||
4. Create a git tag for the release:
|
||||
|
||||
```bash
|
||||
git tag vX.Y.Z
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
4. Create a GitHub Release using the same file:
|
||||
5. Create a GitHub Release using the same file:
|
||||
|
||||
```bash
|
||||
gh release create vX.Y.Z \
|
||||
@@ -49,7 +88,7 @@ For each release:
|
||||
--notes-file docs/releases/vX.Y.Z.md
|
||||
```
|
||||
|
||||
5. Publish the npm package if that is part of the release flow.
|
||||
6. Publish the npm package if that is part of the release flow.
|
||||
|
||||
## Guidance
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
# OpenRig v0.5.10 — explainable System Health
|
||||
|
||||
OpenRig 0.5.10 makes fleet health inspectable without turning observation into
|
||||
another source of work. Agents and operators can query the same bounded,
|
||||
evidence-bearing findings in the CLI and TUI, hand uncertain patterns to an
|
||||
agent for diagnosis, and route a verified human request when project policy
|
||||
requires it. The release also carries authored mission context through workflow
|
||||
waits, so lifecycle progress no longer depends on an orchestrator remembering
|
||||
the ceremony.
|
||||
|
||||
## System Health
|
||||
|
||||
- **Inspect health at the scope you actually own.** `rig health` supports the
|
||||
current seat, a stable seat ID, a rig, or the local instance. Each finding
|
||||
retains its category, severity, status, confidence, source window, freshness,
|
||||
rule, evidence, policy version, and smallest useful next inspection. Empty
|
||||
output explicitly does not claim health.
|
||||
- **See the same truth in the terminal UI.** Instance, rig, and seat views expose
|
||||
compact health summaries, a HEALTH list, and finding drill-in from the same
|
||||
canonical records used by the CLI. There is no second score or TUI-only health
|
||||
authority.
|
||||
- **Detect high-confidence operational failures without transcript sentiment.**
|
||||
Deterministic detectors cover process, governance, and context signals such as
|
||||
queue amplification, authority-boundary traffic, and context pressure. Stable
|
||||
episode identity, source bounds, freshness, and explicit indeterminate states
|
||||
prevent missing or stale evidence from reading as healthy.
|
||||
- **Ask an agent to diagnose the cases code cannot settle.** Opt-in health policy
|
||||
can present one bounded investigation packet for ceremony amplification. The
|
||||
assigned agent may inspect beyond the packet, record an attributed disposition,
|
||||
and request human notification only under configured policy and connector
|
||||
readiness. Diagnosis traffic is excluded from its own detector input.
|
||||
|
||||
## Durable lifecycle and human delivery
|
||||
|
||||
- **Carry authored mission context through workflow waits.** A mission can select
|
||||
an explicit boundary workflow in `mission.yaml`; compilation includes the
|
||||
project and mission manifests plus declared context references. Waiting steps
|
||||
can re-present the same packet with bounded exponential backoff, preserving one
|
||||
obligation rather than manufacturing replacement rows.
|
||||
- **Select SDLC care from authored components and waves.** Shipped role and pod
|
||||
guidance follows the project and mission selection instead of inferring a
|
||||
builder-to-QA-to-review chain from seat names. Role capacity does not create a
|
||||
gate.
|
||||
- **Address humans through registered delivery channels.** Registered
|
||||
`<entityId>@external` addresses expose readiness and a next inspection. A
|
||||
verified queue create persists the request before checking its receipt, while
|
||||
posted, pending, failed, and indeterminate outcomes remain distinct. Direct
|
||||
replies return through the correlated queue thread instead of being rewritten
|
||||
as terminal input.
|
||||
|
||||
## Upgrade and setup notes
|
||||
|
||||
- Root, CLI, daemon, and web UI package versions advance to `0.5.10`. The
|
||||
terminal TUI keeps its independent `0.1.0` version.
|
||||
- This release adds no database migration beyond the `0.5.9` migration head.
|
||||
Normal daemon restart is sufficient for schema state; runtime adoption remains
|
||||
a separate operator decision.
|
||||
- Health list and explain are read-only. Automated diagnosis is disabled until a
|
||||
policy explicitly enables it, and no detector automatically retries, reroutes,
|
||||
relaunches, edits scope, or changes lifecycle state.
|
||||
- Human delivery requires a registered address and a configured, enabled, ready
|
||||
connector. Connector setup or verification does not itself send a message.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- Deterministic shipped detector domains are process, governance, and context.
|
||||
Behavioral and epistemic conclusions remain attributed agent judgment rather
|
||||
than opaque scoring.
|
||||
- System Health observes bounded retained records. Unsupported, clipped, stale,
|
||||
contradictory, or unavailable inputs stay indeterminate; absence of a finding
|
||||
is not proof that the system is healthy.
|
||||
- Mission-boundary workflows carry context and scheduling, but agents retain the
|
||||
judgment about evidence, publication, and activation. The daemon does not
|
||||
interpret Markdown receipts or invent the next mission.
|
||||
- Slack is one human-delivery connector, not the addressing contract, and a
|
||||
posted receipt proves connector acceptance rather than human readership.
|
||||
|
||||
## Release verification
|
||||
|
||||
- The accepted product frontier is exact commit
|
||||
`b26419182d98f65ddc8f5cf1997d0b3a9bfe6f0f`, tree
|
||||
`7b311e5dca2086266a682b7ebb43f68a70344d3b`.
|
||||
- Mission evidence includes positive, negative, stale, and indeterminate health
|
||||
fixtures; CLI/TUI parity; an author-excluded diagnosis, human-request, reply,
|
||||
and workflow-wake journey with zero real external sends; and live negative
|
||||
dogfood of the 5/10/20/40/60-minute workflow backoff.
|
||||
- Release preparation rebuilds and tests the exact clean versioned descendant.
|
||||
Publication, tagging, pushing, and runtime adoption are separate lifecycle
|
||||
actions and are not implied by this private cut.
|
||||
@@ -0,0 +1,101 @@
|
||||
# OpenRig v0.5.11 — clearer first use, workflows, and everyday operation
|
||||
|
||||
**Status:** release candidate. Publication and final release verification are
|
||||
separate steps.
|
||||
|
||||
OpenRig 0.5.11 makes it easier to start useful work in an ordinary repository,
|
||||
understand the team and its workflow in the TUI, and diagnose configuration or
|
||||
lifecycle failures without mistaking uncertain state for success.
|
||||
|
||||
## First useful work and a shared view
|
||||
|
||||
- The `first-project` starter provides a focused Codex owner/checker pair for
|
||||
one repository. The README follows that path from launch through a useful
|
||||
change, independent checking, and returning to the work.
|
||||
- A new kernel includes a shared TUI terminal. `rig tui --shared` attaches to
|
||||
that existing view; plain `rig tui` opens a standalone view. Joining the
|
||||
shared view does not silently launch a missing terminal or agent.
|
||||
- Herdr users can follow the same repository and terminal journey. This is
|
||||
preparation for useful operation without a plugin; no Herdr plugin or
|
||||
community-adoption result is part of this release.
|
||||
|
||||
## Understand work in the ordinary TUI
|
||||
|
||||
- Mission views expose workflows, their current step and owner, waits, and
|
||||
the work packet behind a transition. Source navigation connects the view
|
||||
to the authored work and specs.
|
||||
- Specs presents the declared purpose, including a rig's `summary`, and
|
||||
distinguishes missing content from a failed read.
|
||||
- Connections shows the running daemon and CLI identities, effective instance
|
||||
settings, configured and applied gateway state, and registered human routes.
|
||||
It is a passive inspection surface; a configured connector is not proof
|
||||
that a person received a message.
|
||||
- Command completion, readable Recent activity, and a persistent configurable
|
||||
timezone make daily navigation easier. The default display timezone is
|
||||
America/Los_Angeles.
|
||||
- A timed-out daemon probe stays unverified throughout the TUI chrome instead
|
||||
of being labeled as a confirmed outage.
|
||||
|
||||
## More reliable coordination and lifecycle
|
||||
|
||||
- Project-owned release profiles provide reusable ceremony and post-release
|
||||
boundary steps. Current-release closure does not depend on inventing a
|
||||
successor mission. Agents still judge the evidence behind each obligation.
|
||||
- Workflow exception recovery preserves the correct owner and clears resolved
|
||||
exception state. Stuck-work findings retain the source evidence needed to
|
||||
route them honestly.
|
||||
- Terminal delivery preserves long authored context; paced-walk verification
|
||||
checks the intended piece and the applicable runtime/generation record.
|
||||
Delivery and native-turn completion remain distinct from comprehension.
|
||||
- Context profile and work-install commands accept consistent Claude runtime
|
||||
spellings (`claude-code` and `claude`) alongside `codex`, with invalid
|
||||
explicit values rejected before projection.
|
||||
- Startup uses the selected authored proof. It does not add an unrequested
|
||||
exercise, and terminal-only members do not receive agent proof challenges.
|
||||
- Daemon shutdown has a bounded budget and an attributed outcome receipt.
|
||||
Failed or missing receipts remain visible on the supported stop paths.
|
||||
- Daemon startup binds success and state publication to its actual child,
|
||||
handles competing starts, and retains uncertainty when child liveness
|
||||
cannot be established. Cross-host request attribution identifies the origin
|
||||
independently of the selected destination.
|
||||
|
||||
## Operator notes
|
||||
|
||||
Root, CLI, daemon, and web-UI package versions are 0.5.11. The terminal TUI
|
||||
retains its independent 0.1.0 package version. Node requirements are unchanged.
|
||||
The ordered database migration head remains `079_workflow_lifecycle_parallel`;
|
||||
this release adds no migration beyond the 0.5.10 source baseline.
|
||||
|
||||
Diagnosis `show` and `list` now return summary-depth `--json`, retaining current
|
||||
state and decisions while identifying omitted evidence. Consumers needing the
|
||||
complete record must use `--full --json`, or execute the printed expansion
|
||||
command. Queue `show` retains its 512-codepoint body preview and adds explicit
|
||||
size, completeness, and expansion metadata. HTTP responses and mutation JSON
|
||||
remain complete. These defaults are not a global output-size cap.
|
||||
|
||||
Use the supported upgrade procedure with a database backup and continuity
|
||||
checks. A daemon restart and a rig shutdown have different effects; the latter
|
||||
stops seats. Existing kernels are not automatically rebuilt to acquire the new
|
||||
shared terminal.
|
||||
|
||||
## Validation scope and limitations
|
||||
|
||||
The candidate retains earlier isolated, installed-package evidence for the
|
||||
connected first-project/workflow/TUI journey and focused verification of the
|
||||
purpose and unverified-state corrections. Those earlier runs are attributed
|
||||
prior evidence, not new end-to-end or provider enrollment tests for this cut.
|
||||
Package identity and bounded installed rendering checks accompany candidate
|
||||
preparation; exact-cut substance and final release verification remain required
|
||||
before publication.
|
||||
|
||||
- First-use evidence includes fresh re-entry; it does not establish
|
||||
same-conversation resume or complete mixed-runtime enrollment.
|
||||
- Controlled Connections evidence and passive inspection do not establish
|
||||
external human delivery. Transient probe latency and delayed view population
|
||||
remain observed qualifications, with no universal responsiveness claim.
|
||||
- Authored completion, legacy provenance, and proof pairing are separate
|
||||
readers. Updating a declaration does not create missing provenance or an
|
||||
execution-wave history.
|
||||
- A bounded health-analysis proof of concept produced an attributed diagnosis
|
||||
and exited. It is research evidence, not a shipped cold-agent/run API,
|
||||
provider abstraction, or proof of a cost or latency advantage over residency.
|
||||
@@ -0,0 +1,114 @@
|
||||
# OpenRig v0.5.12 — coherent work, quiet waits, and a normal TUI front door
|
||||
|
||||
**Status:** release candidate. Exact-cut substance review, final release
|
||||
verification and publication remain separate steps.
|
||||
|
||||
OpenRig 0.5.12 connects everyday workflow actions to useful progress views,
|
||||
recoverable ownership and a readable TUI. The goal is to let an operator or
|
||||
agent start, understand, adapt and return to work without maintaining status
|
||||
copies or knowing a private recovery recipe.
|
||||
|
||||
## Record a judgment once
|
||||
|
||||
An authorized, evidence-backed proof judgment derives the affected proof,
|
||||
slice, mission and project readiness. Correcting that judgment updates its
|
||||
dependent views while retaining unrelated work and the original evidence.
|
||||
Code, attributed patch-equivalent work and non-code artifacts can carry
|
||||
truthful provenance. A checkbox, uploaded file or closed queue item does not
|
||||
become semantic acceptance, and readiness does not mean publication.
|
||||
|
||||
## Know which graph is running
|
||||
|
||||
Ordinary workflow inspection distinguishes authored scope from the bound
|
||||
running graph, advisory ordering from executable dependencies, and planning
|
||||
waves from runtime steps. `rig workflow revise` previews supported changes
|
||||
before applying them, preserving completed work and current custody.
|
||||
Incompatible edits name the decision that needs reconsideration. Operation
|
||||
identity and `rig workflow operation` recover an ambiguous creation/revision
|
||||
result without duplicating its effect. Selected development-method guidance
|
||||
returns with workflow continuation, including the original story and useful
|
||||
next action; the daemon does not judge the meaning of agent-authored prose.
|
||||
|
||||
## Give failures an owner and a recovery path
|
||||
|
||||
Exception readiness explains the selected owner before failure and keeps
|
||||
missing configuration, a genuine no-match and unavailable routing evidence
|
||||
distinct. Routing uses authored agent identities or registered human fallback.
|
||||
An unhandled dependency-branch failure records its occurrence, owning task
|
||||
and wake together, even when another branch remains live. A failed nudge does
|
||||
not erase the task. After correcting the cause, use `rig workflow resume`
|
||||
with `--occurrence <failed-qitem-id>` to select that failure: its obsolete
|
||||
exception closes in the redrive transaction, while other unresolved failures
|
||||
and completed work remain visible.
|
||||
|
||||
## Wait quietly without losing work
|
||||
|
||||
Healthy unchanged waits avoid repeated full-context packets. Overlapping
|
||||
notifications for the same actionable episode share presentation and retain
|
||||
provenance. Changed blockers, missed delivery and stranded work retain bounded
|
||||
recovery or escalation. Ordinary reads expose the current owner, blocker and
|
||||
next useful decision first, with explicit expansion for complete evidence.
|
||||
Unknown activity remains unknown; a reminder is not progress.
|
||||
|
||||
## Start and return through the same TUI
|
||||
|
||||
Bare interactive `rig` is the normal front door, including first setup,
|
||||
daemon-down startup and return after a restart. The TUI shows the intended
|
||||
instance and observed prerequisites, offers daemon startup, and supports
|
||||
selecting the rigs and seats to return to work. Kernel is recommended first;
|
||||
unselected rigs keep their histories. Resume preserves the selected seat's
|
||||
recorded conversation when available. An unavailable resume offers a named
|
||||
repair or an explicitly confirmed fresh, re-primed conversation; it is never
|
||||
silently replaced. Launcher and native startup fixes retain useful errors
|
||||
when a runtime or dependency is unavailable.
|
||||
|
||||
## Browse general instance settings in CONFIG
|
||||
|
||||
CONFIG organizes instance, workspace/context, display/timezone, waiting,
|
||||
snapshot and recovery settings into readable categories with source and
|
||||
override provenance. Advanced settings and full values remain discoverable.
|
||||
Slack and human connections are one child of CONFIG. Opening or refreshing
|
||||
the browser is passive and secret-safe; configured values remain distinct
|
||||
from observed application, connectivity and delivery. Users can return to
|
||||
ordinary scope and work views without opening configuration files.
|
||||
|
||||
## Operator notes
|
||||
|
||||
Root, CLI, daemon and web-UI package versions are 0.5.12. The terminal TUI
|
||||
retains its independent 0.1.0 version. Node requirements and the ordered
|
||||
migration head `079_workflow_lifecycle_parallel` are unchanged from 0.5.11;
|
||||
this release adds no database migration.
|
||||
|
||||
Use the supported upgrade and continuity procedure when adoption is authorized.
|
||||
Preparing this candidate does not restart a daemon or renew existing seats.
|
||||
Configuration inspection is not an editor, credential migration, automatic
|
||||
repair or a promise that every setting hot-reloads. Editing an authored graph
|
||||
alone does not adopt it into a progressed workflow. The exception repair does
|
||||
not retroactively create tasks for failures already committed by older code;
|
||||
inspect retained occurrences and use the supported recovery route deliberately.
|
||||
|
||||
## Validation scope and known limitations
|
||||
|
||||
The accepted development outcome includes one less-primed, returning/assisted
|
||||
agent journey through two dependent slices: recording and correcting proof,
|
||||
revising the graph, waiting and returning, recovering a real failed handout
|
||||
check, and navigating CONFIG back to work. Focused mechanism reviews and
|
||||
installed controls support that journey. Native startup, resume and wake
|
||||
observations retain their original source cuts and times; they are reused
|
||||
with attribution, not presented as new runs on the version-only candidate.
|
||||
Candidate package and generated-version checks are recorded separately from
|
||||
those development judgments.
|
||||
|
||||
- This was not an unfamiliar-human study, an unassisted greenfield usability
|
||||
trial or all-platform verification. Setup and one-time native approvals
|
||||
remain part of the recorded journey.
|
||||
- The final exception task was consumed through public read/claim/action
|
||||
after its placeholder destination could not receive a nudge. It does not
|
||||
establish native exception delivery or external human receipt.
|
||||
- Quiet-wait measurements establish the tested workload's attention and
|
||||
recovery behavior, not a global latency guarantee or provider billing-token
|
||||
savings. A retained startup catalog-unavailable control also failed on its
|
||||
unchanged parent; the evidence is not an all-green test-suite claim.
|
||||
- Whole-package substance review, exact-release verification and publication
|
||||
must still bind this prepared candidate. These notes do not announce an
|
||||
available npm release or authorize runtime adoption.
|
||||
@@ -0,0 +1,131 @@
|
||||
# OpenRig v0.5.13 — read, decide, and return to work
|
||||
|
||||
**Status:** release candidate, unpublished. Exact-cut substance review,
|
||||
release verification and publication remain pending. This candidate has not
|
||||
been adopted into a live installation.
|
||||
|
||||
OpenRig 0.5.13 connects the human TUI journey: enter the instance, choose a
|
||||
project, read its current instructions, understand what needs your decision,
|
||||
and deliberately open the terminals you need. Scoped oversight and explicit
|
||||
Git context updates help agents work from the right instructions while keeping
|
||||
authority, evidence and local authorship visible.
|
||||
|
||||
## Start with something useful
|
||||
|
||||
Bare interactive `rig` exposes Help, Skip and local reading while startup
|
||||
checks are running, when the daemon is down, or when its state is unverified.
|
||||
Selected project, mission and slice intent can be read from disk without
|
||||
pretending it is live queue, execution or topology data. Read failures and
|
||||
source provenance stay visible. Starting a daemon or returning seats to work
|
||||
remains a deliberate action; reading and skipping do not imply consent.
|
||||
|
||||
## Read the current source and find your way back
|
||||
|
||||
PROJECTS keeps a selected project's identity and root through missions,
|
||||
slices and source views, including projects with the same display name.
|
||||
An unavailable source is reported for that project instead of substituting
|
||||
another project's work. Refresh reads the current file.
|
||||
|
||||
The shared reader shows source paths, modification/hash information and
|
||||
bounded content, with scrolling, local links, heading links and Back.
|
||||
HTTP(S) links show a selectable destination for copying; no browser is opened.
|
||||
Missing, unreadable, binary or out-of-root files remain named errors. Large files show the
|
||||
1 MiB returned-content limit; a heading absent from a truncated prefix is
|
||||
not claimed absent from the whole file. Back restores the prior reading
|
||||
and navigation position.
|
||||
|
||||
## Know what needs your decision
|
||||
|
||||
ATTENTION separates **Action required** from **Updates**. Human requests
|
||||
and explicit blockers show the request, urgency, scope and work it would
|
||||
unblock. Outcome and health updates retain their own source and history.
|
||||
Open the detail or linked evidence to understand it; viewing a request is
|
||||
not approval or delivery, and closing a queue item is not proof of a
|
||||
completed product outcome. Missing or incomplete reads stay visible.
|
||||
|
||||
## Preview terminals before opening them
|
||||
|
||||
TERMINALS lists saved and derived views with their members and resolved
|
||||
layout. A preview is passive. Deliberate Open uses the inspected plan and
|
||||
reports opened, absent or degraded members, including partial failures.
|
||||
Help and side trips return to the same preview and page. The observed native
|
||||
provider journey uses Herdr; broader provider limits are listed below.
|
||||
|
||||
CONFIG keeps full values, defaults and source/override provenance readable.
|
||||
Help supplies command descriptions, grammar and examples, including during
|
||||
startup failures. Narrow views retain scrolling and return controls. Health
|
||||
keeps INFO severity separate from Unknown assessment, exposes stale and
|
||||
unavailable reads, and explains that an empty findings list is **not a healthy
|
||||
verdict**. The complete empty-health explanation is readable at 80 columns.
|
||||
|
||||
## Choose oversight for the work in front of you
|
||||
|
||||
Human-led work is quiet by default for process-only diagnosis. Explicit
|
||||
delegation can enable that oversight during planning as well as execution,
|
||||
subject to the existing diagnosis policy and delivery prerequisites. Ordinary
|
||||
work reminders and operational health retain their behavior. Scope, phase
|
||||
and posture come from explicit sources; unresolved identity stays unknown.
|
||||
|
||||
Use `rig mode effective` with an explicit scope to inspect the result.
|
||||
The [scoped operating posture reference](../reference/scoped-operating-posture.md)
|
||||
describes selection and precedence. A posture preference grants no execution
|
||||
authority. Correction records can retain selected current guidance and
|
||||
distinguish an assessment, an action actually taken and a later observed
|
||||
effect. These are supported mechanics, not evidence of improved judgment:
|
||||
natural behavioral effect remains **UNOBSERVED**, and human time cost remains
|
||||
**UNMEASURED**. No cure or reduced recurrence is claimed.
|
||||
|
||||
## Update context without discarding local authorship
|
||||
|
||||
`rig context add <repository-path-or-URL> --git` retains the relationship
|
||||
between a Git source, its checkout and a selected context pack. Use `--pack`
|
||||
when selecting a pack within a repository. Existing Git credentials apply.
|
||||
|
||||
Use `rig context source inspect <ref>` to inspect local changes, conflicts,
|
||||
revision and selection without fetching. `rig context source update <ref>`
|
||||
explicitly fetches and merges upstream before selecting clean content.
|
||||
Committed local work survives an ordinary merge. Dirty work, conflicting
|
||||
changes, unavailable upstreams or edits to the served selection cause a
|
||||
named refusal rather than a reset or overwrite. Resolve and commit, or abort
|
||||
the merge, in the retained checkout before retrying; the prior served
|
||||
selection remains available during a conflict. Updates do not push.
|
||||
|
||||
Checkout history, selected served bytes and actual agent consumption remain
|
||||
different facts. Read the selected bytes with `rig context get <ref>` and
|
||||
check the intended consumer. This does not automatically synchronize every
|
||||
context library or adopt guidance into running agents.
|
||||
|
||||
## Operator notes and validation limits
|
||||
|
||||
Root, CLI, daemon and web-UI package versions are 0.5.13. The terminal TUI
|
||||
retains its independent 0.1.0 version. Node requirements are unchanged.
|
||||
The ordered migration head advances from 079 to
|
||||
`080_scoped_operating_posture`, extending the existing mode-binding store
|
||||
with scoped human-led/delegated choices while preserving legacy rows.
|
||||
Use the supported upgrade and continuity procedure when adoption is
|
||||
authorized; preparing this candidate performs no live migration or restart.
|
||||
|
||||
Accepted development evidence connects the journey at 140×42 and 80×24,
|
||||
including unavailable/Unknown controls, two-project isolation, current-source
|
||||
reading, deliberate native terminal opening and Git conflict preservation.
|
||||
Those observations retain their original development cuts and attribution.
|
||||
Version, release-note and package checks are separate from those journeys;
|
||||
they are not a new whole-release usability study.
|
||||
|
||||
- Simultaneous default TUI clients can replace one another's programmatic
|
||||
control-socket address. This remains a known, unfixed limitation, judged
|
||||
nonblocking for the demonstrated single-client human journey. Unique test
|
||||
sockets do not prove product-level multi-client isolation; historical lost
|
||||
requests remain unverified.
|
||||
- Native terminal proof covers Herdr. SSH login, cmux end-to-end behavior
|
||||
and effects on a parent host remain unverified. Existing native continuity
|
||||
evidence is reused only for unchanged paths, not new provider coverage.
|
||||
- A private shutdown timed out while closing connections; clean completion
|
||||
and pending effects remain unverified. Earlier sandbox public-start failures
|
||||
remain recorded failures despite a later successful managed private start.
|
||||
- Synthetic findings and requests exercise mechanics, not natural behavioral
|
||||
improvement or real human approval/delivery. No unfamiliar-human study,
|
||||
real-host reboot, all-platform run or live 0.5.13 adoption is established.
|
||||
- Final exact-cut verification and publication still require their separate
|
||||
evidence and authorization. These notes do not announce an available npm
|
||||
release or authorize adoption.
|
||||
@@ -0,0 +1,147 @@
|
||||
# OpenRig v0.5.14 — clearer navigation, mission progress and human notifications
|
||||
|
||||
OpenRig 0.5.14 makes reading more predictable when opened or refreshed, keeps
|
||||
human requests distinct from informational updates, and groups instance settings
|
||||
and health where they are easier to find.
|
||||
|
||||
## Enter and keep reading
|
||||
|
||||
Bare interactive `rig` enters the ordinary TUI after confirming a running rig.
|
||||
An instance without running rigs keeps the startup chooser. Help, Skip and Local
|
||||
remain responsive during slow or unverified detection; a late observation does
|
||||
not take navigation away after the user has chosen another route.
|
||||
|
||||
Pages distinguish initial loading, successful empty results, refresh and failure.
|
||||
The navigator remains usable through page and rig changes. A previously read page
|
||||
can stay visible with its original scope label while the selected page loads;
|
||||
that prior content has no action targets. Same-scope refresh retains useful content,
|
||||
with the previous read time and Retry. A failed Feed source retains its own data
|
||||
while healthy sibling sources update. Confirmed denial, empty results and removals
|
||||
clear affected content, and late replies cannot replace the current selection.
|
||||
First visits retain navigation and local feedback. Topology first offers rig
|
||||
selection, then reads that rig before unrelated fleet details. Terminal names are
|
||||
available before detailed readiness and preview.
|
||||
|
||||
Projects keeps malformed mission and slice sources visible as local errors while
|
||||
healthy neighbors remain readable. Correcting the affected source and refreshing
|
||||
restores its content without showing its parser fragment above unrelated work.
|
||||
|
||||
Optional blocker descriptions load independently, so an unavailable description
|
||||
does not hide primary queue facts or change the recorded human recipient.
|
||||
Fleet reads share one elapsed request budget across stages and retain local
|
||||
results when a remote source fails. Repeated proof-policy and delivery-receipt
|
||||
reads avoid duplicate work while later reads still observe current inputs.
|
||||
These changes reduce measured work; they do not establish a latency guarantee.
|
||||
|
||||
Selective restore and startup event indexes reduce inventory read work while
|
||||
preserving event history and projection results. Private query replay does not
|
||||
establish a general live response-time guarantee.
|
||||
|
||||
Terminal catalogs reuse one inventory read instead of repeating the same global
|
||||
scan. The measured result was equivalent with less query work; this is not a
|
||||
general latency guarantee. Existing saved-view discovery, passive preview and
|
||||
deliberate Herdr Open remain available.
|
||||
|
||||
## Complete requests and quiet updates
|
||||
|
||||
Human notifications distinguish explicit decision requests from informational
|
||||
updates. A request carries a complete brief and requested action; related
|
||||
supplemental detail can continue in its thread. Content that exceeds supported
|
||||
limits is refused with an author correction instead of silently losing essential
|
||||
text. The shipped authoring guidance calls for concise, complete human briefs.
|
||||
|
||||
Informational FYIs require no action or reply and do not create approval
|
||||
obligations. Delivery completion is separate from a human decision. Partial or
|
||||
ambiguous delivery remains distinguishable, and retry/reply tracking keeps each
|
||||
request and its thread separate. Existing callers without an explicit intent
|
||||
retain decision behavior.
|
||||
|
||||
## Find requests and instance health
|
||||
|
||||
The six top-level entries are **Topology, Specs, Projects, Terminals, Feed and
|
||||
System**. Feed explicitly covers the instance and all humans. **Human requests**
|
||||
shows recipients and relevant scope; **Updates** includes confirmed delivered
|
||||
FYIs alongside existing outcome and health updates. Delivered FYIs remain in a
|
||||
bounded history after their delivery obligation closes. Reading is not approval.
|
||||
Feed's Explorer contains these categories, with entries in the content pane.
|
||||
Specs kind groups and Derived terminal views start collapsed and expand explicitly;
|
||||
Saved views stay prominent. Saved membership still requires deliberate setup, and
|
||||
opening a terminal view remains an explicit action.
|
||||
|
||||
System opens to instance **Health**, followed by **Configuration** and
|
||||
**Connections**. Contextual Health, global Help and compatible direct commands
|
||||
remain available, including `attention`, `needs`, `feed`, `config` and
|
||||
`connections`.
|
||||
|
||||
## Understand mission progress
|
||||
|
||||
Projects puts completed outcomes, current work and owner, blockers, next dependency
|
||||
and slice boxes before process prose at both terminal widths. Accepted required
|
||||
proof judgments determine slice outcomes; all slice outcomes must be complete
|
||||
before the mission reports outcomes complete. Evidence attachments, quiet activity
|
||||
and handoff alone do not accept proof. Reopened or assigned unfinished work stays
|
||||
open, including when there is no unassigned next slice.
|
||||
|
||||
Actual custody and planned ownership remain separate. Authored lifecycle and
|
||||
publication are separate from outcome completion; unknown ownership or eligibility
|
||||
stays unknown. Narrow screens retain a full first box and scroll for the remainder.
|
||||
|
||||
## Operator notes and verification limits
|
||||
|
||||
Root, CLI, daemon and web UI versions are 0.5.14; the terminal TUI remains 0.1.0.
|
||||
Node requirements are unchanged. Migration 081 adds explicit intent and
|
||||
supplemental detail to queue records. The migration head is now
|
||||
`084_inventory_event_indexes`, adding three selective inventory read indexes;
|
||||
083 adds the two review read indexes. Migration 082 adds nullable identity
|
||||
provenance to archived queue transitions. Archival preserves recorded values and
|
||||
refuses an archive schema that cannot carry them; legacy unknown values remain null.
|
||||
Shared history, RECENT and OWNER/receipt readers now include archived records,
|
||||
so their results can broaden even where the command or TUI code is unchanged.
|
||||
Migration alone does not reconstruct previously lost metadata. Use the supported
|
||||
upgrade and continuity process when adoption is authorized.
|
||||
|
||||
The private combined review exercised installed journeys at 140×42 and 80×24,
|
||||
including entry, refresh, source failure, scope identity, detail/Back, saved
|
||||
terminal Open and notification correlation. An initial Feed source-failure defect
|
||||
was retained in the evidence and repaired, then independently rechecked. Owner
|
||||
candidate-native evidence and the reviewer's child TUI against an unchanged
|
||||
parent daemon remain separate observations.
|
||||
|
||||
Later operator inspection exposed disruptive navigation and a misleading mission
|
||||
overview. The corrected private combined review covers the new navigation,
|
||||
organizers and native mission states. Its false NEXT-completion finding was
|
||||
preserved, repaired and independently rechecked at both widths. These private
|
||||
results remain limited to the journeys and environments exercised.
|
||||
|
||||
VM inspection was accepted on September 14: generally zero-to-two-second
|
||||
loading with occasional timeouts, along with formatting, icon colors and default
|
||||
views. This observed result is not a general latency or availability guarantee.
|
||||
Later recovery of the same candidate preserved sessions but did not establish a
|
||||
durable responsiveness fix.
|
||||
|
||||
- Intermittent read timeouts remain. An inherited CLI health-check deadline can
|
||||
report the daemon unresponsive before sending a queue request even when the
|
||||
direct request succeeds. That defect is not fixed here; occasional server
|
||||
delay also remains unattributed.
|
||||
- Earlier notification checks used controlled HTTP responses and synthetic replies.
|
||||
A later real single-consumer Slack exercise confirmed complete decision/detail/FYI
|
||||
posting and reply correlation; the operator confirmed phone readership and
|
||||
readability. That observation does not establish OS push behavior or all clients.
|
||||
- Terminal provider evidence covers disposable Herdr members. Other provider,
|
||||
remote and parent-host behavior is not established by this review.
|
||||
- Saved Build watch discovery was exercised in the VM and its native judgment
|
||||
recorded. That observation does not establish general navigation responsiveness.
|
||||
- Simultaneous default TUI clients retain the existing programmatic control-socket
|
||||
limitation. Unique test sockets do not prove general multi-client isolation.
|
||||
- Version and package checks do not repeat the development journeys or establish
|
||||
a new whole-source, all-platform or unfamiliar-human usability result.
|
||||
- Observed shutdown-drain timeouts, recorded config-preservation uncertainties
|
||||
and a prior `recorder_write_failed` observation are not claimed resolved.
|
||||
Recovery evidence remains distinct from ordinary-entry verification and does
|
||||
not establish whole-system health.
|
||||
- Historical source-scan findings retain their recorded dispositions; a clean
|
||||
changed-path scan is not a clean whole-source scan.
|
||||
- Bundled Workflow Specs, including `basic-loop`, can disappear from discovery
|
||||
after an upgrade changes the install directory despite their YAML remaining
|
||||
intact. The discovery fix is explicitly deferred beyond 0.5.14; this release
|
||||
contains no repair for it.
|
||||
@@ -0,0 +1,117 @@
|
||||
# OpenRig v0.5.15 — first-use recovery, software factory guidance and Pi fixes
|
||||
|
||||
Changes for version **0.5.15**. Publication status and date are recorded on the
|
||||
[GitHub release](https://github.com/mvschwarz/openrig/releases/tag/v0.5.15) and npm.
|
||||
|
||||
OpenRig 0.5.15 improves startup and recovery, adds public guidance for a continuing
|
||||
software team and user-chosen command permissions, and fixes Pi reply visibility,
|
||||
shell context and terminal input handling.
|
||||
|
||||
## Start and return with clearer guidance
|
||||
|
||||
Before launching, read
|
||||
[what OpenRig changes on your machine](../../README.md#what-openrig-changes-on-your-machine).
|
||||
Provider integration can configure executable hooks and workspace trust; changing
|
||||
only `OPENRIG_HOME` does not isolate provider configuration. The
|
||||
[getting-started guide](../reference/getting-started.md) walks through your first
|
||||
task and explains these setup effects.
|
||||
|
||||
Check the commands available in your installation:
|
||||
|
||||
```sh
|
||||
rig --version
|
||||
rig --help
|
||||
rig up --help
|
||||
```
|
||||
|
||||
Startup and recovery recognize native Codex through shell and Node launchers,
|
||||
and report uncertainty when process identity cannot be established. Recognized
|
||||
update notices are skipped without installing provider updates. Authentication
|
||||
and trust choices remain yours.
|
||||
|
||||
Startup errors now describe the problem and a recovery action. If a name matches
|
||||
both a starter and a stopped rig, `rig up <name> --existing` selects the existing
|
||||
rig. Inspect its reported state before assigning more work.
|
||||
|
||||
## Continue useful reviewed work
|
||||
|
||||
Give your agent a real repository, an outcome and the decisions you want to keep.
|
||||
The bundled OpenRig Software Factory recipe supports direct team work,
|
||||
queue-supported orchestration or an optional explicit Workflow.
|
||||
|
||||
```sh
|
||||
rig context show skills/core/openrig-software-factory --json
|
||||
rig context get skills/core/openrig-software-factory/SKILL.md
|
||||
rig context get skills/core/openrig-software-factory/references/worked-example.md
|
||||
```
|
||||
|
||||
Start with the two-seat team. Add one or two seats with `rig grow` when the work
|
||||
benefits from separate responsibilities. The example covers roles, context,
|
||||
work ownership, independent review, concurrency and cost, and distinguishes
|
||||
changes to the running team from saving an updated RigSpec. Use guidance matching
|
||||
your installed version.
|
||||
|
||||
## Choose command permissions
|
||||
|
||||
Ask your agent to inspect the target harness, explain the available scopes and
|
||||
apply your choice while preserving existing rules:
|
||||
|
||||
```sh
|
||||
rig context get skills/applying-a-permission-policy/SKILL.md
|
||||
```
|
||||
|
||||
Allowing the whole `rig` command family includes lifecycle and configuration
|
||||
changes as well as reads. Choose narrower commands or retain approval prompts
|
||||
when appropriate. Project-only setup requires supported, active and trusted
|
||||
project configuration; if it is unavailable, the agent should explain the limit
|
||||
instead of substituting user-wide permission. Back up touched files and verify
|
||||
the selected rules in the actual conversation. Shipped sandbox, approval and
|
||||
permission defaults are unchanged.
|
||||
|
||||
## Known compatibility limitation
|
||||
|
||||
On macOS arm64 with Node 24, installing the SQLite dependency can fail when a
|
||||
suitable prebuilt binary is unavailable and compilation is not available.
|
||||
Compiler-built SQLite has also failed during database cleanup at runtime.
|
||||
**Use Node 22 on macOS arm64 for now.** This recommendation does not change the
|
||||
declared Node support range, and this version does not update SQLite.
|
||||
|
||||
## Pi replies, input and recovery
|
||||
|
||||
Pi replies and tool progress are visible for the supported RPC event shapes.
|
||||
Shell tools retain the managed OpenRig context needed for identity and routing.
|
||||
Provider errors are shown with bounded text, and exhausted retries do not print
|
||||
the same error repeatedly.
|
||||
|
||||
Pasted messages wait for explicit submission. The terminal runner accepts input
|
||||
beyond the terminal's canonical buffer limit, preserves editing and cancellation,
|
||||
and recovers after rejecting an oversized framed message. This is not a general
|
||||
latency guarantee or an unrestricted input-size promise.
|
||||
|
||||
An added seat whose first startup failed during resource projection, before its
|
||||
startup context was saved, can use the guarded retry after the setup problem is corrected.
|
||||
Retain the complete original member fragment and its source root; a snapshot alone
|
||||
does not reconstruct missing startup configuration. Use one retry at a time.
|
||||
Ordinary conversation resume remains separate.
|
||||
|
||||
**Pi support is qualified and supervised.** Controlled coding, follow-up,
|
||||
reporting and conversation continuity were verified with Pi 0.87.1 and OpenRouter
|
||||
`z-ai/glm-5.3-flash`; ordinary-seat identity, tools and a reply also worked.
|
||||
Completion of an ordinary useful task and unattended team operation remain
|
||||
unverified. Context-profile selection and hook-generation coverage are limited;
|
||||
do not assume parity with every Codex or Claude integration. The Software Factory
|
||||
guide likewise does not establish fully unattended operation.
|
||||
|
||||
## Contributors and included pull requests
|
||||
|
||||
Thanks to [@danielkuykendall23-boop](https://github.com/danielkuykendall23-boop)
|
||||
for the Pi reply fix in [#37](https://github.com/mvschwarz/openrig/pull/37), and to
|
||||
[@mvschwarz](https://github.com/mvschwarz) for Codex startup and restore guidance
|
||||
([#38](https://github.com/mvschwarz/openrig/pull/38)), Software Factory and permission
|
||||
guidance ([#39](https://github.com/mvschwarz/openrig/pull/39)), contribution and
|
||||
support guidance ([#45](https://github.com/mvschwarz/openrig/pull/45)), and the
|
||||
first-use README ([#46](https://github.com/mvschwarz/openrig/pull/46)).
|
||||
|
||||
This release also includes the startup-identity refinements, guarded first-start
|
||||
retry, Pi shell/error handling and terminal-input fixes described above. Project
|
||||
work-policy features and the Node 22/SQLite 13 upgrade are outside this release.
|
||||
@@ -0,0 +1,62 @@
|
||||
# OpenRig v0.5.16 — choose the Claude instruction file
|
||||
|
||||
Changes for version **0.5.16**. Publication status and date are recorded on the
|
||||
[GitHub release](https://github.com/mvschwarz/openrig/releases/tag/v0.5.16) and npm.
|
||||
|
||||
OpenRig 0.5.16 lets a rig keep its managed Claude Code instructions out of a
|
||||
tracked `CLAUDE.md`, and adds an advisory portability report for pull requests.
|
||||
|
||||
## Keep a tracked CLAUDE.md unchanged
|
||||
|
||||
OpenRig writes its instructions for Claude Code members into managed blocks in
|
||||
each member's working directory. By default that file is `CLAUDE.md`. If your
|
||||
repository tracks `CLAUDE.md`, select `CLAUDE.local.md` in the rig spec:
|
||||
|
||||
```yaml
|
||||
managed_blocks:
|
||||
claude-code: CLAUDE.local.md
|
||||
```
|
||||
|
||||
Claude Code loads `CLAUDE.local.md` from the working directory as well; keep it
|
||||
out of git, for example with a `.gitignore` entry. The selection applies when
|
||||
members launch, restore, relaunch or are added, and it is kept by `rig export`
|
||||
and rig bundles. `rig down` removes OpenRig's blocks only from the selected file.
|
||||
OpenRig never edits, moves or deletes blocks in the other file.
|
||||
|
||||
Only the `claude-code` key is accepted, with `CLAUDE.md` or `CLAUDE.local.md`;
|
||||
any other key or value is rejected before a member launches. Without the field,
|
||||
behavior is unchanged. Codex members always use `AGENTS.md`.
|
||||
|
||||
**Switching an existing rig:** blocks OpenRig already wrote into `CLAUDE.md`
|
||||
stay there. Until you remove them, the file stays modified and Claude Code loads
|
||||
both copies. Delete each `<!-- BEGIN OpenRig MANAGED BLOCK: … -->` …
|
||||
`<!-- END OpenRig MANAGED BLOCK: … -->` section by hand and keep the rest of the
|
||||
file. If `CLAUDE.md` has no other uncommitted edits you need to keep, you can
|
||||
instead run `git restore CLAUDE.md`; that command discards every unstaged change
|
||||
to the file, not only OpenRig's blocks. See
|
||||
[the RigSpec reference](../reference/rig-spec.md#choosing-the-claude-instruction-file).
|
||||
|
||||
## Portability report for pull requests
|
||||
|
||||
A new pull-request workflow lists added lines that contain values tied to one
|
||||
machine, network or account: credentials, home and temporary paths, network
|
||||
addresses and email addresses. Suspected credentials are withheld from the
|
||||
report. Findings never fail the check; the author decides what to change. It is
|
||||
pattern-based and does not guarantee that nothing private remains. Run it
|
||||
locally with:
|
||||
|
||||
```sh
|
||||
node scripts/portability-report.mjs
|
||||
```
|
||||
|
||||
## Contributors and included pull requests
|
||||
|
||||
Thanks to [@hvpaiva](https://github.com/hvpaiva) for reporting and proposing the
|
||||
Claude instruction-file option in [#25](https://github.com/mvschwarz/openrig/issues/25),
|
||||
and to [@mvschwarz](https://github.com/mvschwarz) for the README badges and demo
|
||||
([#54](https://github.com/mvschwarz/openrig/pull/54)) and the portability report
|
||||
([#56](https://github.com/mvschwarz/openrig/pull/56)).
|
||||
|
||||
The Node support range and SQLite version are unchanged. The macOS arm64 with
|
||||
Node 24 SQLite installation limitation noted for 0.5.15 still applies; use
|
||||
Node 22 on macOS arm64.
|
||||
@@ -0,0 +1,35 @@
|
||||
# OpenRig v0.5.17 — install with Bun
|
||||
|
||||
Changes for version **0.5.17**. Publication status and date are recorded on the
|
||||
[GitHub release](https://github.com/mvschwarz/openrig/releases/tag/v0.5.17) and npm.
|
||||
|
||||
OpenRig 0.5.17 can be installed with Bun as well as npm.
|
||||
|
||||
## Install with Bun
|
||||
|
||||
```sh
|
||||
bun add -g @openrig/cli
|
||||
```
|
||||
|
||||
Earlier versions listed the unpublished `@openrig/daemon` package as a dependency.
|
||||
npm used the copy bundled in the package, but Bun looked it up in the registry
|
||||
and failed with a 404. The package now imports the daemon copy it already ships
|
||||
and no longer depends on `@openrig/daemon`, which also makes it smaller. npm
|
||||
installation is unchanged.
|
||||
|
||||
OpenRig still runs on Node.js, so install Node.js 22 as well. Bun may block this
|
||||
package's postinstall script. In that case the Node.js version and SQLite check
|
||||
does not run at install time. See [what OpenRig changes on your machine](../../README.md#what-openrig-changes-on-your-machine).
|
||||
|
||||
The installation fix was verified with Bun 1.4.0 and npm on macOS arm64 with
|
||||
Node.js 22. Other combinations are not verified.
|
||||
|
||||
## Contributors and included pull requests
|
||||
|
||||
Thanks to [@drewpayment](https://github.com/drewpayment) for reporting the Bun
|
||||
installation failure in [#66](https://github.com/mvschwarz/openrig/issues/66),
|
||||
fixed in [#68](https://github.com/mvschwarz/openrig/pull/68).
|
||||
|
||||
The Node support range and SQLite version are unchanged. The macOS arm64 with
|
||||
Node 24 SQLite installation limitation noted for 0.5.15 still applies; use
|
||||
Node 22 on macOS arm64.
|
||||
@@ -0,0 +1,62 @@
|
||||
# OpenRig v0.5.4
|
||||
|
||||
## Summary - "The honesty release"
|
||||
|
||||
**v0.5.4 makes command output describe the effect that actually happened.** Transport resolution distinguishes missing, found, and indeterminate sessions. Delivery verification distinguishes staged input from an agent-consumed turn. Queue, refocus, lifecycle, import, and preview surfaces preserve real state and refusal semantics instead of collapsing them into plausible success.
|
||||
|
||||
**v0.5.4 contains v0.5.3 in full.** The VM cut was reconciled onto the published line by an ordered 32-of-32 stable patch-id match.
|
||||
|
||||
No new migrations. Migration head stays at 071 (matching v0.5.3). Node engine requirements are unchanged. Existing CLI commands remain; this release adds `rig seat set-model`, `rig seat stop`, and `rig seat clean`.
|
||||
|
||||
## What Shipped
|
||||
|
||||
### One classified session-resolution path
|
||||
|
||||
Send, capture, nudge, and walk consume one probe result: found, absent, or indeterminate. A tmux or transport blip no longer becomes a false `Session not found`, and recovery output says what was checked.
|
||||
|
||||
### Delivery verification by effect
|
||||
|
||||
`rig send --verify` checks whether input remained staged or was consumed as a turn. Single-target, fan-out, and JSON output use the same classification, so "typed" and "delivered" are no longer synonyms.
|
||||
|
||||
### Honest unknown-sender delivery
|
||||
|
||||
Messages without attributable sender identity are delivered with an unknown-sender label instead of refused. Where possible, the sender receives a notice to follow up with attribution.
|
||||
|
||||
### Supported single-seat lifecycle controls
|
||||
|
||||
- `rig seat set-model` changes the canonical model binding with an audited reason.
|
||||
- `rig seat stop` stops one live seat without disturbing siblings.
|
||||
- `rig seat clean` removes dead-session residue so the logical seat can launch again.
|
||||
|
||||
Safety is whole-class: every session a lifecycle action could mutate is probed, and a live or indeterminate result refuses the action. The model change was exercised through a real managed successor.
|
||||
|
||||
### State-preserving queue and refocus behavior
|
||||
|
||||
Queue notes preserve the row's state. Owned listing without identity refuses instead of silently widening or changing scope. Refocus keeps due state until delivery becomes visible. Preview and handoff output say when content is bounded or optional.
|
||||
|
||||
### Running-name guard across create and import
|
||||
|
||||
A running rig name cannot be recreated through another instantiator or import route. Every path preserves the same `409 rig_name_running` code and teaching message through the CLI boundary.
|
||||
|
||||
### Hermetic startup and plugin authority
|
||||
|
||||
Scratch/test startup boundaries are hermetic. The bundled `openrig-core` plugin advances under explicit version authority, preventing an older runtime from silently rolling installed guidance backward. Stale lab refocus registrations are retired.
|
||||
|
||||
## Behavior and Compatibility
|
||||
|
||||
- **Migrations** - none; schema head remains 071.
|
||||
- **API compatibility** - existing commands remain available; three seat lifecycle verbs are additive.
|
||||
- **Lineage** - the 0.5.4 product delta is reconciled onto the published 0.5.3 line by patch identity.
|
||||
- **Release boundary** - tag, npm publish, host install, and daemon cutover remain separate authorized acts; this document does not imply that they occurred.
|
||||
|
||||
## Known Limitations (0.5.5 backlog)
|
||||
|
||||
- **Writer-layer disk cap** - cleanup is an interim control; the permanent write-layer cap is unfinished.
|
||||
- **Repair/approve frontmatter rewrite** - the known rewrite defect remains open.
|
||||
- **Observation pipeline performance** - the larger pane/process polling redesign remains measurement-gated and is not claimed by this release.
|
||||
|
||||
## See Also
|
||||
|
||||
- `CHANGELOG.md` - the `[0.5.4]` entry.
|
||||
- `docs/releases/v0.5.3.md` - the prior context release.
|
||||
- GitHub Release: https://github.com/mvschwarz/openrig/releases/tag/v0.5.4 (created only after the authorized publish ceremony).
|
||||
@@ -0,0 +1,97 @@
|
||||
# OpenRig v0.5.5
|
||||
|
||||
## Summary — "The ambient-attention release"
|
||||
|
||||
**v0.5.5 makes the fleet notice, retry, escalate, diagnose, and onboard on its own.** Stop hand-babysitting the work in flight: `rig parked` diagnoses silent-stuck with confidence and teaching; the standing stuck-sweep runs without you; baton wakes retry, aggregate, and escalate on their own; parks carry their own wake and read HEALTHY; `rig ps` grows a typed `ACTIVITY` column from one oracle; the Slack human layer is live end-to-end; fresh installs onboard themselves.
|
||||
|
||||
**v0.5.5 contains v0.5.4 in full.** One reconciled lineage.
|
||||
|
||||
**Migrations**: TWO new. Head advances `071` → `073`:
|
||||
- `072_thread_seat_map.ts`
|
||||
- `073_queue_transition_wakes.ts`
|
||||
|
||||
**New verbs**: `rig parked [seat]`, `rig seat handover --source fork:|rebuild` (execute path), `queue block --on <blocker>` (park-with-wake), `rig gateway human` fragment-lifecycle verbs, `rig view show escalations`. `rig ps` gains a typed `ACTIVITY` column.
|
||||
|
||||
## What Shipped
|
||||
|
||||
### `rig parked [seat]` — derived silent-stuck diagnosis
|
||||
|
||||
Activity × open obligations, with per-input confidence and remedy taught inline. Reach for it when a seat looks idle and you suspect dropped work — before any `claimedAt` arithmetic or pane capture. The row face used to hide the difference between park and strand; this verb ends that.
|
||||
|
||||
### Self-retrying wake ladder + escalations view
|
||||
|
||||
Failed baton wakes retry, aggregate, and escalate on their own. Transitions ARE the ladder state (restart-safe); per-destination aggregation; rungs deliver-and-advance; unconfirmed-with-no-pickup escalates without re-send. Watch `rig view show escalations` for the aggregates. Hand off the row and stop chasing nudges.
|
||||
|
||||
### Standing stuck-sweep runs without you
|
||||
|
||||
Overdue + undelivered become routed findings with derived evidence inline. Stop hand-running `queue overdue` / `undelivered` on a timer — consume the sweep's findings instead.
|
||||
|
||||
**Caveat (5.6 backlog):** cross-host successor visibility is a proven false-positive class — 39 active findings at census (20 proven FP, 19 unverified). Contained fleet-wide one-per-condition; treat cross-host-lineage findings as unverified until the 5.6 detector fix. The delta itself teaches the caveat.
|
||||
|
||||
### `queue block --on <blocker>` — park-with-wake
|
||||
|
||||
A park can carry its wake and read HEALTHY. `queue block --on <blocker-qitem>` records the wake; held-with-live-wake is not flagged; auto-unparked owners get an honest wake. For imminent-but-blocked work only; not-imminent work belongs in the mission workspace — the queue is a conveyor.
|
||||
|
||||
### `rig ps` gains a true `ACTIVITY` column from one oracle
|
||||
|
||||
Typed taxonomy: working / idle-at-prompt / needs-input-as-count+reason / unknown. Evidence ladder with self-report on top and visible rung degradation. Seat-keyed across swaps. The TUI reads the same truth. For any "is it thinking or stuck" question — the oracle beats capture.
|
||||
|
||||
### `rig seat handover --source fork:|rebuild` — EXECUTE
|
||||
|
||||
`fork:` carries live context; `rebuild` primes from the durable chain and names its priming artifacts. Mid-swap failures record honestly. When replacing an occupant — no more dry-run-only planning surface.
|
||||
|
||||
### Slack human layer live end-to-end
|
||||
|
||||
Gateway running, thread-per-seat, exactly-once inbound reconciliation, escalation loudness (mention) distinct from routine. Humans are addressable members; `rig gateway human` has full fragment-lifecycle verbs. Any agent can escalate directly; escalation-class sends arrive loud on the destination's phone.
|
||||
|
||||
### Fresh installs onboard themselves
|
||||
|
||||
Fresh installs get focused onboarding packs by default (config off; existing rigs untouched). When standing up a new rig or seat — stop hand-walking the eight pieces.
|
||||
|
||||
### Refocus fires only when it should
|
||||
|
||||
At a context threshold or on demand — NEVER at session start. Post-compaction both runtimes; usage threshold Claude-only. A fresh seat getting a refocus is now a bug to file, not noise to ignore.
|
||||
|
||||
### One-file scope convention
|
||||
|
||||
`scope mission create` → `NOTES.md` + intent-bearing `SPEC`; `slice create` → `SPEC`-only (no `IMPLEMENTATION-PRD`) + acceptance `PROGRESS`; release missions get a capability-delta scaffold. `repair` is ADDITIVE — stamps append, never rewrite author frontmatter. Stamp verification is a mechanical strip again.
|
||||
|
||||
### Daemon bind intent has provenance
|
||||
|
||||
An inherited `OPENRIG_HOST` never selects single-bind; adoption gates test ALL required listeners. Daemon maintenance from any managed environment — no more `env -u` ceremony.
|
||||
|
||||
## STOP doing (each was correct under 0.5.4 and is wrong now)
|
||||
|
||||
1. **STOP the per-lock author-reconstruction protocol.** Stamps APPEND now; strip-reconstruction is mechanical.
|
||||
2. **STOP diagnosing parks by `claimedAt` arithmetic + capture.** `rig parked` derives the diagnosis with confidence and teaching; capture is a fallback glance.
|
||||
3. **STOP storing deferred work as queue rows.** The queue is a conveyor for imminent sequential work; deferred work lives in the mission workspace.
|
||||
4. **STOP authoring `IMPLEMENTATION-PRD.md` and `MISSION_NOTES.md` on new work.** `SPEC.md` is the one spec file; `NOTES.md` is the chain name.
|
||||
5. **STOP routing contact through an orchestrator by default.** Any agent escalates directly.
|
||||
|
||||
## Landed, not yet drivable
|
||||
|
||||
| Landed surface | Missing live door | Honest current action |
|
||||
|---|---|---|
|
||||
| A–D delivery preferences + availability (stored, validated) | rules engine (0.5.6 slice 01) | set availability via `rig gateway human` verbs; expect no routing effect yet |
|
||||
| S01 operator rung | human-layer connector delivery leg (0.5.6 wave S11 territory) | escalation view + daemon health carry the floor |
|
||||
| `@external` addresses on the direct send path | routing falls through to tmux (0.5.6 wave-2 fix) | reach humans via the gateway path (queue/escalation), not raw `rig send` |
|
||||
| Multi-human topology | single-human ships (honesty marker); multi-human is 0.5.7 | one registered human; design assumptions accordingly |
|
||||
|
||||
## Behavior + Compatibility
|
||||
|
||||
- **Migrations** — two new: `072_thread_seat_map.ts`, `073_queue_transition_wakes.ts`. Head advances `071` → `073`.
|
||||
- **API compatibility** — existing commands remain available; new verbs are additive.
|
||||
- **Lineage** — the 0.5.5 product delta is reconciled onto the published 0.5.4 line by aggregate patch identity.
|
||||
- **Release boundary** — tag, npm publish, host install, and daemon cutover remain separate authorized acts.
|
||||
|
||||
## Known Limitations (0.5.6 backlog)
|
||||
|
||||
- **Cross-host stuck-sweep false positives** — 39 active findings at census (20 proven FP, 19 unverified); contained fleet-wide; detector fix lands in 0.5.6 wave 1.
|
||||
- **`@external` direct-send routing** — 0.5.6 wave-2 routing fix.
|
||||
- **Delivery preferences → rules engine** — 0.5.6 slice 01.
|
||||
|
||||
## See Also
|
||||
|
||||
- `CHANGELOG.md` — the `[0.5.5]` entry.
|
||||
- `docs/releases/v0.5.4.md` — the prior honesty release.
|
||||
- GitHub Release: https://github.com/mvschwarz/openrig/releases/tag/v0.5.5
|
||||
@@ -0,0 +1,128 @@
|
||||
# v0.5.6 — world-building as context engineering
|
||||
|
||||
OpenRig 0.5.6 ships the context system's next layer, makes long-running
|
||||
agents durable, and makes messages to humans trustworthy. Three themes: the
|
||||
context a seat runs on is structured, tagged, and verifiable; an agent seat
|
||||
can now outlive its own context window without a human managing the
|
||||
transition; and anything routed to a human carries proof of delivery.
|
||||
|
||||
## The context system: structured, tagged, verifiable
|
||||
|
||||
- **The mission install.** A documented five-layer convention
|
||||
(`docs/reference/mission-install.md`) for installing project context into a
|
||||
seat: the pieces, their arrangement, the seat's own position, the current
|
||||
contract, and a derive-your-own-delta step that proves the install was
|
||||
consumed rather than skimmed.
|
||||
- **Lore routing.** What a seat learns locally routes into addressable,
|
||||
rig-local lore packs (`docs/reference/lore-routing.md`) — carried with a
|
||||
maturity stage from birth, and structurally excluded from anything that
|
||||
ships.
|
||||
- **Taxonomy everywhere.** Every pack entering `rig context` carries its
|
||||
class — WORLD / LORE / SKILLS / MISSION — surfaced in listings, with a loud
|
||||
refusal for untagged packs.
|
||||
- **A public world pack with checkable claims.** The public onboarding pack
|
||||
is authored as atoms whose claims each carry a check that can fail, with
|
||||
the verifier shipped inside the pack — public content that cannot rot
|
||||
silently.
|
||||
- **A complete, self-retrievable skill index.** The skill router's index now
|
||||
covers the full shipped set, is retrievable through the routes it teaches,
|
||||
and carries a recurrence test so coverage gaps stay found.
|
||||
- **A substance gate for what ships.** A whole-surface review pass over every
|
||||
packaged file (bundle and npm surfaces included) runs as a named release
|
||||
step; this release shipped with all 5,316 packaged files passing.
|
||||
|
||||
## Continuity: seats that outlive their context windows
|
||||
|
||||
- **Continuity policies in the agent spec.** `compaction_strategy` is now live
|
||||
configuration with four modes: `default-compaction`, `managed-compaction`,
|
||||
`handover`, and `apprentice-handover`. Modes apply to Claude seats
|
||||
(positive runtime matching — Codex seats keep their native behavior) and
|
||||
resolve through the same spec-default < profile < member precedence as the
|
||||
rest of the agent spec.
|
||||
- **The apprentice handover, automated.** A seat declaring
|
||||
`apprentice-handover` gets two armed triggers: near its context threshold,
|
||||
the system creates a fresh successor seat and runs its onboarding install;
|
||||
nearer the wall, it mints an owned handover work item for the seat that
|
||||
executes the swap. The executing seat is declared configuration (`mechanic:` on the
|
||||
policy) — never inferred from topology, and arming refuses loudly if it is
|
||||
missing.
|
||||
- **Managed compaction with honest restores.** Seats in `managed-compaction`
|
||||
mode get a pre-compaction prep nudge (deposit your state before the window
|
||||
closes) and, after a configured restore, a durable receipt recording how
|
||||
much usable context the restore actually recovered — so a restore that
|
||||
silently refills the window instead of recovering width is visible.
|
||||
- **Context-usage triggers as a primitive.** `rig watchdog` gains a
|
||||
usage-threshold condition: fire any command when a seat's context usage
|
||||
crosses a calibrated per-seat threshold. The continuity modes are built on
|
||||
it; your own automation can be too.
|
||||
|
||||
## The human layer: delivery you can prove
|
||||
|
||||
- **No more silent fall-through.** One destination resolver classifies every
|
||||
dispatch: terminal-bound seats use the terminal; registered humans and
|
||||
virtual seats route through the gateway; nothing falls through to a
|
||||
terminal lookup that cannot succeed. Unregistered external addresses get a
|
||||
structured, teaching refusal.
|
||||
- **Delivery receipts on the work item.** Every gateway post writes its
|
||||
receipt atomically on the queue row: `posted` (with the message timestamp
|
||||
and channel), `transport-failed` (with the error), or — derivably —
|
||||
`never-posted`. `rig queue undelivered` now consults that ledger first and
|
||||
returns only genuine failures. "Did this reach them?" is one read.
|
||||
- **Notification levels, one vocabulary.** Queue transitions classify as
|
||||
`RECORD` < `NOTICE` < `ALERT` at write time — a human-required decision is
|
||||
`ALERT`; system ceremony events are `NOTICE`; the rest is durable record.
|
||||
Two independent dials configure the minimum level that posts and the
|
||||
minimum level that interrupts.
|
||||
- **A delivery-rules engine.** Stored per-human preferences and availability
|
||||
decide how each message class is delivered. An `off` preference suppresses
|
||||
interruption — never the durable record, the post, or the receipt. Away
|
||||
windows defer routine traffic into digests (4-hour and daily), with
|
||||
exactly-once delivery by construction: durable episode identities, retry
|
||||
until a real receipt, and structurally non-overlapping digests.
|
||||
- **Files dropped in Slack become work.** A file or image upload lands as a
|
||||
work item with the file transferred to local storage and referenced by
|
||||
path — authenticated download, size-bounded, per-file failures named, and
|
||||
no URL or token ever written to a row. A park blocked on a registered human
|
||||
automatically enters the alert path and posts exactly once per episode.
|
||||
|
||||
## Operational honesty
|
||||
|
||||
- A rate-limited seat gets exactly one timed wake at its stated reset.
|
||||
- A seat holding claimed live work while sitting idle gets woken by the
|
||||
system, consuming the arbitrated activity oracle — with exactly one wake
|
||||
per park episode.
|
||||
- Refocus state keys to the current occupant of a seat; a fresh occupant
|
||||
never inherits a predecessor's baselines.
|
||||
- `rig ps` counts claimed in-progress work as assigned work.
|
||||
- Sends refuse empty bodies; unknown daemon API routes return JSON 404s.
|
||||
- Stuck-sweep findings stop re-minting after closure; cross-host custody
|
||||
verification completes durably instead of refreshing forever.
|
||||
- Blocker actuation is unified: one helper, cross-host closure honest,
|
||||
delivery after the transaction commits.
|
||||
- Rig expansion and serialization preserve what they were given:
|
||||
`session_source` versions, `services`, and member overrides survive
|
||||
round-trips; fork forwards model, role, restore policy, and label.
|
||||
- `rig view show execution` derives who is building what, what is next, and
|
||||
each item's completion rung (locked / built / reviewed / folded / adopted)
|
||||
at read time — unknowns render as INDETERMINATE, never as idle or done.
|
||||
|
||||
## Process and reference
|
||||
|
||||
- The SDLC conventions document is now a component menu: choose per mission
|
||||
from the simple flow, the wave model, or the rigorous overlay, with
|
||||
planning rigor as a dial. Four full references ship at `docs/reference/`:
|
||||
`planning-dial.md`, `wave-sdlc.md`, `release-boundary.md`, and
|
||||
`product-management-pass.md`, joined by `mission-install.md` and
|
||||
`lore-routing.md`.
|
||||
|
||||
## Upgrade notes
|
||||
|
||||
- Three database migrations apply automatically on daemon start:
|
||||
`074_context_usage_watchdog`, `075_context_usage_watchdog_generation`, and
|
||||
`076_owner_notification_levels`. No manual steps.
|
||||
- Context packs: 41 ship (up from 39).
|
||||
- Delivery defaults are deliberately wide open (post at `NOTICE` and above,
|
||||
interrupt at `ALERT` and above). Tune down from measured volume with the
|
||||
two dials rather than pre-filtering.
|
||||
- Continuity modes are opt-in per seat; unconfigured seats keep today's
|
||||
behavior exactly.
|
||||
@@ -0,0 +1,74 @@
|
||||
# OpenRig v0.5.7 — elastic topology and coherent work delivery
|
||||
|
||||
OpenRig 0.5.7 makes a running topology easier to create and reshape without
|
||||
hand-authoring YAML, and gives agents a coherent, addressable install of the
|
||||
work they are joining.
|
||||
|
||||
## Elastic topology lifecycle
|
||||
|
||||
- **Create without YAML.** Start a rig from public CLI inputs without first
|
||||
authoring a RigSpec.
|
||||
- **Grow the topology in place.** Add one or several members to an existing
|
||||
pod, or create and populate a new pod, with mixed outcomes reported
|
||||
honestly.
|
||||
- **Keep the declaration faithful.** Spec show/export preserves the topology,
|
||||
including a member's declared agent-image source, so save and recreate do
|
||||
not silently change how that member launches.
|
||||
- **Shrink safely.** Removal refuses while a target owns active work. An
|
||||
explicit drain fallback preserves queue-item identity and state, verifies
|
||||
that the fallback session is live, and removes only the intended seat or
|
||||
pod. Ordinary no-work cleanup remains direct.
|
||||
|
||||
## Coherent work delivery
|
||||
|
||||
- **Composable product-journey SDLC.** The shipped work model composes world,
|
||||
project, mission, and slice context instead of requiring one monolithic
|
||||
briefing.
|
||||
- **Useful work-install defaults.** Fresh project, mission, and slice
|
||||
scaffolds provide conventional Markdown and manifest files without
|
||||
overwriting authored content.
|
||||
- **Optional YAML enrichment.** A project manifest can add an authored intent
|
||||
and ordered context addresses. Invalid optional values warn explicitly and
|
||||
retain the safe default install.
|
||||
- **Addressable Markdown loading.** Installed skills can resolve precise
|
||||
Markdown sections by relative address, including through normal symlinked
|
||||
plugin projections.
|
||||
- **Explicit project identity.** Agents can select and switch between projects
|
||||
without carrying context across roots; ambiguous, duplicate, and unknown
|
||||
identities refuse instead of guessing.
|
||||
- **Complete public plugin-only skill catalog.** Every cataloged public skill
|
||||
supplied only by a plugin is projected with the helper files its
|
||||
instructions reference.
|
||||
|
||||
## Verification performed
|
||||
|
||||
- Targeted Node 22 daemon and CLI lifecycle/work-install suites and affected
|
||||
package builds passed throughout the surviving release range, with
|
||||
independent QA and scope/maintainability review at the completed user-story
|
||||
boundaries.
|
||||
- The product cut at `14e785197` was packaged and installed in an isolated VM.
|
||||
Daemon health reported that exact clean commit, SQLite `quick_check` passed,
|
||||
and the existing ready topology was preserved across cutover.
|
||||
- Disposable public CLI journeys covered create, grow, faithful export,
|
||||
guarded shrink, explicit drain fallback, and project/mission/slice work
|
||||
installation. A fresh agent session reconstructed the current five-piece
|
||||
work frontier through the installed command without cross-project leakage.
|
||||
- A bounded TUI smoke opened the live topology, exercised help and basic
|
||||
navigation, and exited cleanly.
|
||||
- The final release-note-only cut carries no product-source change from the
|
||||
validated product cut. Its package assembly, hash-bound surface review, and
|
||||
substance-gate receipt are release-manager gates that must pass before tag or
|
||||
publication.
|
||||
|
||||
## Operator notes
|
||||
|
||||
- Database migration head is `077_node_session_source`; normal daemon startup
|
||||
applies the ordered migration list automatically. No manual migration step
|
||||
is required.
|
||||
- Run the exact-cut substance gate and retain its durable receipt before any
|
||||
tag, GitHub Release, or npm publication. Candidate QA does not imply those
|
||||
publication acts occurred.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- Restore-honesty and disaster-path work are deferred to 0.5.8.
|
||||
@@ -0,0 +1,80 @@
|
||||
# OpenRig v0.5.8 — recoverable operations and visible work
|
||||
|
||||
OpenRig 0.5.8 makes long-running rigs easier to recover, understand, and
|
||||
operate. It adds exact selected-rig recovery after an abrupt outage, makes the
|
||||
current mission and its execution story visible to agents and operators, and
|
||||
tightens the diagnostics and guardrails that keep live state honest.
|
||||
|
||||
## Recovery and topology lifecycle
|
||||
|
||||
- **Recover the selected rig after an outage.** Restore now rejects stale
|
||||
native identity, uses real provider-session evidence, and avoids substituting
|
||||
a fresh or wrong occupant or starting unrelated sibling rigs.
|
||||
- **Use one topology-creation path.** Public creation and growth converge on the
|
||||
same daemon effect while preserving member working-directory overrides.
|
||||
- **Keep topology declarations useful.** Rigs expose honest self-description,
|
||||
workspace declarations survive export, and a validated workspace declaration
|
||||
can be applied to an existing rig without changing its topology.
|
||||
|
||||
## Mission and context visibility
|
||||
|
||||
- **Open mission execution from SCOPES.** A selected mission now shows a
|
||||
readable `DONE` / `NOW` / `NEXT` / `ATTENTION` story, truthful working-lane
|
||||
and evidence-gap counts, bounded overflow, and stable mission switching.
|
||||
- **Install the work that is actually current.** Work-install resolves complete
|
||||
files and dot-form slice IDs, and refocus derives the seat's typed current
|
||||
mission and slice instead of asking the agent to reconstruct them from
|
||||
memory.
|
||||
- **Discover shipped capability.** The public capability world and its
|
||||
addressable context are available through the installed context routes.
|
||||
|
||||
## Operational honesty and safer setup
|
||||
|
||||
- **Wake once for the state that needs attention.** Park timers retire on every
|
||||
exit path, new park episodes supersede old timers, and idle-gate watchdogs use
|
||||
the same arbitrated activity oracle as the rest of the system.
|
||||
- **Diagnose the mechanism that is actually live.** Producer-link health derives
|
||||
from effective endpoints, topology freshness is bounded, and synthetic model
|
||||
records no longer create false model-divergence findings.
|
||||
- **Preserve user-owned Codex configuration.** Managed TOML projection refuses
|
||||
malformed or root-scoped fragments before write and preserves user tables
|
||||
across valid collisions.
|
||||
- **Keep upgrades agent-led.** The shipped upgrade skill supplies bounded
|
||||
inspection, verified database backup, and contained managed-plugin refresh
|
||||
helpers while leaving cutover judgment with the operating agent.
|
||||
- **Trust clean-checkout results.** The documented daemon test gate and the
|
||||
same-SHA long-lived gate now agree on deterministic source truth.
|
||||
|
||||
## Operator / setup notes
|
||||
|
||||
- Root, CLI, daemon, and web UI package versions advance to `0.5.8`. The
|
||||
terminal TUI keeps its independent `0.1.0` version.
|
||||
- Database migration head advances from `077_node_session_source` to
|
||||
`078_idle_gate_fired_condition`; normal daemon startup applies it
|
||||
automatically. No manual migration step is required.
|
||||
- Existing rigs do not need a topology rewrite. The new workspace-only apply
|
||||
path deliberately leaves members, pods, edges, and running sessions alone.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- Selected-rig recovery does not cover total database plus filesystem loss or
|
||||
fleet-wide automatic restart.
|
||||
- The existing-rig workspace story does not validate `target_repo`; that
|
||||
separate concern remains outside this release contract.
|
||||
- A pre-existing backend `attention_required` state is not part of the bounded
|
||||
TUI topology-freshness work.
|
||||
|
||||
## Release verification
|
||||
|
||||
- Independent QA and review exercised the final SCOPES execution journey at
|
||||
110 and 160 columns, including mission selection, readable evidence gaps,
|
||||
bounded overflow, slice drill-in, and Escape navigation.
|
||||
- Recovery testing used real Claude and Codex occupants across current, stale,
|
||||
absent, and insufficient-evidence states. It also covers native-session
|
||||
identity changing within the same OpenRig occupant record.
|
||||
- The accepted product tree was replayed as a linear public lineage rooted on
|
||||
0.5.7. Every author and committer uses the project noreply identity, and the
|
||||
replayed product tree is byte-identical to the accepted local-main tree.
|
||||
- Release acceptance includes package assembly, a hash-bound per-file
|
||||
substance review, and installed-runtime QA and review against the exact
|
||||
private cut.
|
||||
@@ -0,0 +1,105 @@
|
||||
# OpenRig v0.5.9 — developer-beautiful mission control
|
||||
|
||||
OpenRig 0.5.9 turns the terminal UI into a real mission-control surface and
|
||||
gives the instance beneath it a cleaner, more durable operating model. The TUI
|
||||
is easier to scan, navigate, and drive at ordinary terminal sizes; System
|
||||
World, project context, and managed skills now compose explicitly; and restore,
|
||||
fresh launch, scheduled wakes, and project workflows report the state they
|
||||
actually acted on.
|
||||
|
||||
## Developer-beautiful terminal UI
|
||||
|
||||
- **Read the whole instance at a glance.** The topology view now presents one
|
||||
continuous agent table across rigs, keeps pod separators visible, and shows
|
||||
bounded `RECENT` work from material queue transitions without duplicating
|
||||
archived rigs or inventing missing summaries.
|
||||
- **Move from overview to action without losing context.** Instance, rig,
|
||||
mission, slice, agent, and Explorer pages share one responsive visual grammar,
|
||||
preserve rig identity while scrolling, and keep lifecycle evidence and
|
||||
actions reachable at 160, 120, and 84 columns.
|
||||
- **Use the terminal as a first-class interface.** Semantic state colors,
|
||||
compact glyphs, restrained motion, resize redraw, filtering, pointer-local
|
||||
scrolling, terminal-native selection/copy, attach actions, and socket-driven
|
||||
commands work through the same production surface.
|
||||
|
||||
## A coherent instance, context, and skill model
|
||||
|
||||
- **Start from one additive instance layout.** CLI and daemon startup reconcile
|
||||
the same canonical `$OPENRIG_HOME` structure, preserve existing content, and
|
||||
refuse wrong-type managed paths before writing.
|
||||
- **Compose a System World with the current project.** `context.root` is the
|
||||
addressable context library, `context.system_world` selects the instance-wide
|
||||
baseline, and `rig context work-install` reports System World, topology, and
|
||||
project inputs together.
|
||||
- **Manage skills as their own resource.** `skills.root` names one versionable
|
||||
catalog. `rig skill loadout` composes system, topology, and project selectors;
|
||||
its apply mode owns only its manifest-listed projection and refuses to erase
|
||||
local or modified content.
|
||||
- **Prime each runtime at the right width.** Provider-shaped onboarding gives
|
||||
Codex a compact bootstrap plus addressable coverage map while retaining the
|
||||
fuller guided route for runtimes that benefit from it.
|
||||
|
||||
## Lifecycle and recovery honesty
|
||||
|
||||
- **Create a deliberately blank occupant for one seat.** `rig seat launch
|
||||
<seat> --fresh --reason <why>` is the explicit no-continuity path; it preserves
|
||||
sibling seats and durable work and refuses unmanaged ambiguity.
|
||||
- **Compile authored work into an executable lifecycle.** `rig workflow
|
||||
compile` reads `project.yaml`, `mission.yaml`, and `slice.yaml` as one graph;
|
||||
`instantiate-lifecycle` starts an eligible graph with an opaque replay key,
|
||||
and typed acceptance evidence prevents a workflow advance from standing in
|
||||
for release acceptance.
|
||||
- **Know what a restore attempted.** Snapshot selection can be explicit,
|
||||
snapshots can retain the intended seat roster, and `rig restore status`
|
||||
derives the current intended-set result while keeping historical nodes and
|
||||
unverified resume lineage out of a false success.
|
||||
- **Keep automation attributable.** Watchdog reminders deliver the authored
|
||||
message under an explicit scheduler identity, and effective-model detection
|
||||
follows the identity-verified current occupant rather than a retained
|
||||
predecessor session.
|
||||
- **Choose judgment only where it earns the loop.** The shipped
|
||||
`agent-operated-workflows` skill distinguishes AI-enabled software,
|
||||
agent-operated workflows, and agent-operated software. The 0.5.9 instance
|
||||
migration is its first reference implementation.
|
||||
|
||||
## Upgrade and setup notes
|
||||
|
||||
- Root, CLI, daemon, and web UI package versions advance to `0.5.9`. The
|
||||
terminal TUI keeps its independent `0.1.0` version.
|
||||
- Database migration head advances from `078_idle_gate_fired_condition` to
|
||||
`079_workflow_lifecycle_parallel`; normal daemon startup applies it.
|
||||
- Existing instances have a deliberate layout boundary: context telemetry moves
|
||||
to `state/context-usage`, provider telemetry lives at
|
||||
`state/provider-usage`, the addressable library moves to `context/`, and the
|
||||
default System World lives at `context/system/system-world.yaml`.
|
||||
- Do not perform that change as a directory rename while an old collector is
|
||||
writing. Load the shipped `openrig-upgrade` skill and follow its 0.5.9
|
||||
Agent-Operated Migration: inspect, prepare with a retained preimage, activate
|
||||
the exact target runtime separately, verify paired canonical samples, then
|
||||
invoke the non-destructive library finalizer with that exact receipt. The
|
||||
complete command sequence and rollback contract are in the top-level README.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- The migration helper deliberately does not stop or start the daemon, mutate
|
||||
the database, relaunch seats, or delete legacy telemetry/library state. Those
|
||||
lifecycle and eventual-retirement decisions remain explicit operator work.
|
||||
- A restore attempt receipt derives what the durable event and current process
|
||||
evidence can prove; missing or ambiguous native identity remains unresolved
|
||||
instead of being guessed.
|
||||
- The TUI remains a terminal surface. Font choice and terminal color rendering
|
||||
stay under the user's terminal emulator rather than OpenRig.
|
||||
|
||||
## Release verification
|
||||
|
||||
- The accepted product candidate is exact commit
|
||||
`102dabce4ea56d870243ecd3f6ef38185ec76537`, tree
|
||||
`4b7898b6829bd3e28113bfd996f67bb1cc2761e9`, with independent whole-outcome
|
||||
evidence for the composed source heads and focused QA on the final TUI repair.
|
||||
- The build VM ran the accepted candidate as a healthy, clean daemon. SQLite
|
||||
integrity passed, and the 0.5.9 migration verifier reported a complete fresh
|
||||
canonical Claude telemetry pair with no legacy tails.
|
||||
- Release preparation additionally rebuilds the exact versioned cut, scans the
|
||||
complete npm inventory for private substance, installs the frozen tarball in
|
||||
isolation, and binds its runtime identity and public capability reads to the
|
||||
cut. Those receipts travel with the private publication packet.
|
||||
@@ -0,0 +1,213 @@
|
||||
# OpenRig v0.6.0 — works for strangers
|
||||
|
||||
Changes for version **0.6.0**. Publication status and date are recorded on the
|
||||
[GitHub release](https://github.com/mvschwarz/openrig/releases/tag/v0.6.0) and npm.
|
||||
|
||||
OpenRig 0.6.0 requires Node.js 22 or 24. It adds per-seat permission choices, a
|
||||
per-seat typing guard for seats where you type by hand, and a one-step way to open
|
||||
a whole rig in Herdr. It fixes Pi seat activity and clarifies the kernel starter's
|
||||
runtime choice.
|
||||
|
||||
It also includes two **experimental** features: Slack app manifest and setup
|
||||
assistance, and an optional stream classifier (Jev with Rig Stream) that is off by
|
||||
default. They may be incomplete or not work in your setup. See
|
||||
[Report experimental problems](#report-experimental-problems).
|
||||
|
||||
## Breaking change: Node.js 22 or 24
|
||||
|
||||
OpenRig 0.6.0 supports Node.js 22 and 24 only, and uses better-sqlite3 13, which
|
||||
requires Node 22 or newer. Node 20 is no longer supported: the install check
|
||||
refuses it with an explanation. Node 26 and other versions are untested.
|
||||
|
||||
If you run OpenRig on Node 20, switch Node first, then reinstall the CLI under
|
||||
the new Node. Your existing data stays where it is; the daemon reopens the same
|
||||
database and applies pending migrations in place. See
|
||||
[Moving off Node 20](../../README.md#moving-off-node-20).
|
||||
|
||||
OpenRig runs on macOS or Linux. Native Windows is not supported yet, and WSL2
|
||||
has not been tested. On a Mac with Apple silicon, use Node.js 22 (see the
|
||||
[compatibility history](v0.5.15.md#known-compatibility-limitation)).
|
||||
|
||||
## Choose permissions per seat
|
||||
|
||||
`rig seat set-permissions <seat> --mode <mode> --reason <text>` records an audited
|
||||
permission choice for a seat's future launches: `floor`, `full_bypass`, or
|
||||
`inherit` to clear the override. Codex `full_bypass` now passes
|
||||
`-s danger-full-access -a never`, so it bypasses approvals as well as the
|
||||
sandbox. Additional Claude Code modes such as `auto` are accepted only when the
|
||||
seat's managed Claude executable advertises them; OpenRig checks this at
|
||||
selection and again at launch, and refuses without a fallback.
|
||||
|
||||
The choice does not relaunch the seat or change its current process, history,
|
||||
rules or hooks. `rig seat status` shows the selected mode and the last launch
|
||||
arguments; neither proves the native permission behavior, so check the running
|
||||
session before relying on it. Rig-level policy moves to
|
||||
`rig policy permissions list|show|current|apply`; the old `rig policy` verbs
|
||||
remain as aliases. See the [permission guide](../reference/getting-started.md#per-seat-permission-mode).
|
||||
|
||||
## Protect a seat where you type by hand
|
||||
|
||||
`rig seat set-typing-guard <seat> --enabled true --reason <text>` pauses all
|
||||
automatic terminal input to that seat, even at an empty prompt. Messages and
|
||||
wakes are kept in the outbox instead of being typed in; read them with
|
||||
`rig seat held-messages <seat>` and retire reviewed ones with
|
||||
`rig seat retire-held-message <seat> <id> --reason <text>`. Turning the guard off
|
||||
allows new sends; it never replays held messages.
|
||||
|
||||
The guard is off by default, and other seats keep automatic delivery. Wait for
|
||||
`rig seat status <seat>` to report it as effective before relying on it. It covers
|
||||
OpenRig's own input paths, not other tools writing directly to the terminal.
|
||||
`rig seat set-typing-guard --help` lists the retention limits.
|
||||
|
||||
## Experimental: Slack app manifest and setup assistance
|
||||
|
||||
`rig slack manifest` prints the Slack app manifest for the app you create in your
|
||||
own workspace. It runs offline, before any daemon or token exists; `--url`
|
||||
prints Slack's create-app link with the manifest prefilled, and `--json` lists
|
||||
each requested scope with its reason. The TUI Connections page shows the same
|
||||
link while Slack is not configured. OpenRig does not host an app, open a
|
||||
browser or accept tokens here.
|
||||
|
||||
This manifest and the [Slack setup guide](../reference/slack-app-setup.md) are
|
||||
experimental. The guide's creation steps follow Slack's manifest documentation
|
||||
and have not been confirmed against a real app creation: we have not observed
|
||||
which fields Slack prefills or a complete set of granted scopes. You still
|
||||
create the tokens and the private env file by hand. The existing Slack connector
|
||||
and its `rig slack setup`, `verify`, `enable`, `disable` and `status` commands are
|
||||
not experimental; `setup` and `status` now also point you to the manifest.
|
||||
`rig slack verify` checks only the baseline scopes, so a READY result does not
|
||||
prove attachments or mentions work.
|
||||
|
||||
## Open a whole rig in Herdr
|
||||
|
||||
In the TUI, a rig's detail view has a `term ▸ rig <name>` link, and the command
|
||||
bar accepts `terminal <view>`. With Herdr, every running seat of the rig opens in
|
||||
a workspace named after the rig, up to 16 seats per tab (cmux keeps its own
|
||||
limits). The empty starting tab is closed only when OpenRig can confirm the rig's
|
||||
own tabs opened; otherwise it is kept, and the result says so. Seats that are not
|
||||
running are reported as absent or degraded rather than opened.
|
||||
|
||||
## First-use and compatibility fixes
|
||||
|
||||
- Pi seat activity reports now carry the seat's occupant generation, and the
|
||||
daemon accepts them only when that generation matches the seat's current
|
||||
occupant. Reports with a missing, earlier or foreign generation are refused
|
||||
instead of being attributed to the seat.
|
||||
- The kernel starter's summary now says when it shows the library preview,
|
||||
explains that the runtime variant is chosen automatically, and points to the
|
||||
seats actually running.
|
||||
- Codex seats launch with `--no-daemon` when the installed Codex supports it, so
|
||||
each seat's tools run in its own app-server instead of a shared daemon. OpenRig
|
||||
checks support before each launch and refuses the launch if the check fails.
|
||||
- Missions and slices created with `rig scope` no longer fail the readiness
|
||||
reader when their manifest has no `metadata` block.
|
||||
- Choosing `CLAUDE.local.md` for Claude Code managed blocks, added in 0.5.16, is
|
||||
included unchanged.
|
||||
|
||||
## Experimental: stream classification (Jev with Rig Stream)
|
||||
|
||||
OpenRig 0.6.0 includes an optional, experimental way for a classifier seat to
|
||||
have Jev, through OpenRouter, label Rig Stream observations. It may be incomplete
|
||||
or not work. It is off by default; turn it on for a local configuration file:
|
||||
|
||||
```sh
|
||||
rig project experimental status --config ./experiment.json --json
|
||||
rig project experimental enable --config ./experiment.json --max-requests 3 --timeout-ms 10000 --json
|
||||
rig project experimental disable --config ./experiment.json --json
|
||||
```
|
||||
|
||||
Enabling starts no processing, capture, daemon or agent. Work happens only when
|
||||
the classifier seat runs one foreground command, `rig project wake … --experiment
|
||||
./experiment.json`, which allows 1–20 requests (default 3) and never retries in
|
||||
the background. The run needs `OPENROUTER_API_KEY` in that process's environment
|
||||
and sends the selected items' text, the taxonomy questions, scope IDs and roster
|
||||
destinations to OpenRouter. Disable stops further calls and discards late
|
||||
results; Ctrl-C cancels the run, though a request already forwarded may still be
|
||||
charged.
|
||||
|
||||
The labels are advisory. They have no demonstrated accuracy or calibrated
|
||||
confidence, and this release adds no delivery, watchdog or routing consumer for
|
||||
them. `rig project experimental capture` applies the same client to an existing
|
||||
shadow-capture archive, and `rig project shadow-status`, `shadow-drain` and
|
||||
`shadow-stop` manage capture, which needs its own separately configured daemon
|
||||
start; `rig project shadow-stop` stops it. See
|
||||
[stream classification](../reference/stream-classifier-worker.md) for the taxonomy
|
||||
format, request limits and error handling.
|
||||
|
||||
## Report experimental problems
|
||||
|
||||
If the Slack manifest, the setup guide or the stream classifier does not work as
|
||||
described, please [open an issue](https://github.com/mvschwarz/openrig/issues/new/choose)
|
||||
with the bug template, or send a pull request following
|
||||
[CONTRIBUTING.md](../../CONTRIBUTING.md). A problem with the existing Slack
|
||||
connector or any other existing feature is a regular bug report.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- Per-seat permissions: authenticated native enforcement across fresh launch,
|
||||
resume and same-seat fork was not verified before release. Status output and
|
||||
launch arguments do not prove a selected mode's effective permissions; inspect
|
||||
the running session.
|
||||
- Slack manifest and setup assistance (experimental): creating the app in a real
|
||||
workspace, Slack's prefilled fields and the complete granted scopes have not
|
||||
been observed against the published steps.
|
||||
- Herdr: check that the rig opened as expected in your Herdr version.
|
||||
- Pi: activity reporting from an installed Pi seat has not been verified end to end.
|
||||
- Stream classification (experimental): an installed run and real OpenRouter
|
||||
access have not been verified, and its output may be incomplete or wrong. No
|
||||
accuracy or production-reliability claim is made.
|
||||
- The Codex hook-trust dialog report in [#17](https://github.com/mvschwarz/openrig/issues/17)
|
||||
has not been reproduced, and this release does not change it.
|
||||
- Node 26 and odd-numbered Node versions are not supported or tested.
|
||||
|
||||
## Verification scope
|
||||
|
||||
Release preparation is tracked in [#109](https://github.com/mvschwarz/openrig/pull/109).
|
||||
An ordinary candidate-tarball installation passed on a fresh macOS 15.7.3 ARM64
|
||||
VM with Node.js 22.22.1 and the real npm postinstall. Checks covered
|
||||
better-sqlite3 13.0.3, all 89 migrations, database writes and close/reopen, and
|
||||
normal unauthenticated daemon startup and shutdown.
|
||||
|
||||
Prior candidate checks on Node 24 covered installed SQLite, migrations and
|
||||
allocation-driven garbage collection. The relevant install and database
|
||||
mechanisms are unchanged. That installation used a staged offline postinstall;
|
||||
it was not a fresh-machine install. A fresh Node 24 VM installation was not
|
||||
performed because of host resource pressure.
|
||||
|
||||
These checks do not establish an authenticated first-agent launch, a fresh
|
||||
Linux or Windows installation, installation without compiler tools, or an
|
||||
installation downloaded from the npm registry. The final artifact will be
|
||||
separately bound to the merged release commit.
|
||||
|
||||
## Contributors and included pull requests
|
||||
|
||||
Thanks to:
|
||||
|
||||
- [@jimallen](https://github.com/jimallen) for reporting the Node 26 installation
|
||||
failure and proposing the better-sqlite3 13 upgrade in
|
||||
[#16](https://github.com/mvschwarz/openrig/pull/16).
|
||||
- [@shravansumanthanan](https://github.com/shravansumanthanan) for the mission
|
||||
readiness fix in [#91](https://github.com/mvschwarz/openrig/pull/91), and
|
||||
[@kainne44](https://github.com/kainne44) for reporting it in
|
||||
[#72](https://github.com/mvschwarz/openrig/issues/72).
|
||||
- [@mvdpoel](https://github.com/mvdpoel) for repairing two daemon test fixtures in
|
||||
[#84](https://github.com/mvschwarz/openrig/pull/84).
|
||||
- [@reisalbuquerque](https://github.com/reisalbuquerque) for reporting the shared
|
||||
Codex app-server identity problem in
|
||||
[#69](https://github.com/mvschwarz/openrig/issues/69), fixed in
|
||||
[#77](https://github.com/mvschwarz/openrig/pull/77).
|
||||
- [@DoowanKang](https://github.com/DoowanKang) for
|
||||
[#29](https://github.com/mvschwarz/openrig/issues/29) and
|
||||
[#30](https://github.com/mvschwarz/openrig/issues/30),
|
||||
[@djogss](https://github.com/djogss) for
|
||||
[#33](https://github.com/mvschwarz/openrig/issues/33),
|
||||
[@some-marketing](https://github.com/some-marketing) for
|
||||
[#48](https://github.com/mvschwarz/openrig/issues/48),
|
||||
[@shintaii](https://github.com/shintaii) for
|
||||
[#26](https://github.com/mvschwarz/openrig/issues/26) and
|
||||
[@tgrundtvig](https://github.com/tgrundtvig) for
|
||||
[#21](https://github.com/mvschwarz/openrig/issues/21).
|
||||
|
||||
The platform statement in [#94](https://github.com/mvschwarz/openrig/pull/94)
|
||||
answers the Windows question in [#88](https://github.com/mvschwarz/openrig/issues/88);
|
||||
native Windows support is not part of this release.
|
||||
@@ -0,0 +1,107 @@
|
||||
# OpenRig 0.6.1 — First-use repairs and one help path
|
||||
|
||||
OpenRig 0.6.1 improves first-use guidance, Codex readiness and linked-worktree
|
||||
launches, with a single help path and regression checks on pull requests.
|
||||
|
||||
## Find help from the installed version
|
||||
|
||||
Ask your agent to read:
|
||||
|
||||
```sh
|
||||
rig context get help
|
||||
```
|
||||
|
||||
The [help guide](../reference/help.md) brings together environment checks,
|
||||
setup and restart symptoms, permissions, instance layout and escalation. Its
|
||||
linked reference guides are included with the package too. If OpenRig cannot
|
||||
install or `rig` cannot run, use the guide online at
|
||||
[openrig.dev/help/agents](https://www.openrig.dev/help/agents), or read
|
||||
`daemon/docs/reference/help.md` inside the installed `@openrig/cli` package.
|
||||
For help, email **[email protected]**; a GitHub account is not required.
|
||||
|
||||
This is the canonical guide added in
|
||||
[#113](https://github.com/mvschwarz/openrig/pull/113), with entry points from the
|
||||
README and agent startup guidance.
|
||||
|
||||
## Read a catalogued project's execution view
|
||||
|
||||
```sh
|
||||
rig view show execution --project <catalog-id> --mission <mission> --json
|
||||
```
|
||||
|
||||
The CLI now forwards the selected project and mission to the existing daemon
|
||||
resolver, which uses that project's configured missions root. Previously the
|
||||
CLI could only use the default workspace for this view. Omitting `--project`
|
||||
keeps the existing behavior. This does not add project inference from the
|
||||
current directory. Thanks to [@dajiaohuang](https://github.com/dajiaohuang) for
|
||||
[#105](https://github.com/mvschwarz/openrig/pull/105).
|
||||
|
||||
## Codex readiness and bundled guidance
|
||||
|
||||
- Startup/resume checks recognize Codex's `»` conversation prompt, including a
|
||||
fresh prompt after a dismissed hook-review panel. Numbered selection menus
|
||||
still require attention. Thanks to
|
||||
[@dajiaohuang](https://github.com/dajiaohuang) for
|
||||
[#111](https://github.com/mvschwarz/openrig/pull/111).
|
||||
- The bundled Vault skill has valid frontmatter, with a regression check for
|
||||
shipped skill headers ([#115](https://github.com/mvschwarz/openrig/pull/115)).
|
||||
- Historical development evidence has been removed from the public source tree
|
||||
([#118](https://github.com/mvschwarz/openrig/pull/118)). The retired
|
||||
`spike/tui-drivability` prototype was also removed
|
||||
([#119](https://github.com/mvschwarz/openrig/pull/119)); the shipped TUI remains.
|
||||
|
||||
## Identity readings and Codex launches
|
||||
|
||||
- **Boot-time comparisons on non-UTC hosts:** parse SQLite's zone-less boot
|
||||
timestamp as UTC. Identity and context-usage readers then compare observations
|
||||
with the actual generation start, avoiding time-zone shifts that could reject
|
||||
current observations or accept older ones. Thanks to
|
||||
[@Coder8124](https://github.com/Coder8124) for
|
||||
[#124](https://github.com/mvschwarz/openrig/pull/124).
|
||||
- **Custom Codex status lines:** recognize an interactive conversation when its
|
||||
header has scrolled away and the status line puts a mixed-case model name
|
||||
after other fields. Unresolved trust, hook, update and model-selection gates
|
||||
remain in place. This is recognition only; the separate stale restore-warning
|
||||
behavior from [#116](https://github.com/mvschwarz/openrig/issues/116) is not
|
||||
repaired here. Thanks to [@Hexgunner69](https://github.com/Hexgunner69) for the
|
||||
report and [@Aummadour](https://github.com/Aummadour) for screen-derived
|
||||
regression cases included in [#125](https://github.com/mvschwarz/openrig/pull/125).
|
||||
- **Linked Git worktrees:** Codex fresh launches resolve the worktree's Git
|
||||
directory and common Git directory instead of treating its `.git` file as a
|
||||
directory. Resume/fork arguments and permission modes are unchanged. Thanks to
|
||||
[@mgall-ibizdigital](https://github.com/mgall-ibizdigital) for the report,
|
||||
reproduction and suggested approach in
|
||||
[#121](https://github.com/mvschwarz/openrig/issues/121), addressed by
|
||||
[#126](https://github.com/mvschwarz/openrig/pull/126).
|
||||
|
||||
## Regression checks on pull requests
|
||||
|
||||
[#117](https://github.com/mvschwarz/openrig/pull/117) adds eight test jobs: build
|
||||
and package-file inspection, type checking, repository checks, four separate
|
||||
daemon/CLI/TUI/UI suites, and an installed queue-durability scenario. The latter
|
||||
checks a normal run, an intentionally introduced lost-baton failure and a
|
||||
healthy return. A checker that misses that seeded failure cannot pass.
|
||||
|
||||
The workflow uses Node 22, Linux runners for the build/repository/installed
|
||||
scenario jobs, and macOS runners for the four package suites. It does not run
|
||||
all eleven historical scenario seeds or certify every platform, provider or
|
||||
authenticated native-agent workflow.
|
||||
|
||||
## Upgrade and compatibility
|
||||
|
||||
Node.js 22 and 24 remain the supported versions; Node 20 is unsupported. No
|
||||
dependency, engine, permission-mode or database-migration change is introduced
|
||||
by this maintenance release. Follow the existing
|
||||
[upgrade procedure](../../skills/_canonical/core/openrig-upgrade/SKILL.md)
|
||||
when updating an existing instance.
|
||||
|
||||
Package identities and the checks performed on each published artifact belong
|
||||
with its [GitHub release](https://github.com/mvschwarz/openrig/releases). These
|
||||
source notes do not certify every platform or authenticated native workflow.
|
||||
Native permission enforcement across fresh launch, resume and fork retains
|
||||
the verification limits recorded for 0.6.0.
|
||||
|
||||
Slack app manifest/setup assistance and Rig Stream classification remain
|
||||
**experimental**. This release makes no new real-app, provider-run, accuracy or
|
||||
reliability claim for them. The existing Slack connector is not experimental.
|
||||
See the [0.6.0 limitations](v0.6.0.md#known-limitations) for retained boundaries.
|
||||
@@ -0,0 +1,114 @@
|
||||
# OpenRig 0.6.2 — Start with the team you already have accounts for
|
||||
|
||||
Choose your providers, give an owner one useful change in your repository, and
|
||||
have a second agent check the result. OpenRig 0.6.2 adds first-project choices,
|
||||
clearer permission guidance and repairs for bundle installation and agent delivery.
|
||||
|
||||
## A small team and one useful result
|
||||
|
||||
The [first-use guide](../reference/getting-started.md) offers the same owner and
|
||||
checker workflow with three recipes:
|
||||
|
||||
| Team | Recipe |
|
||||
| --- | --- |
|
||||
| Two Codex agents | `first-project` |
|
||||
| Two Claude Code agents | `first-project-claude` |
|
||||
| Claude owner and Codex checker | `first-project-mixed` |
|
||||
|
||||
Use the accounts you already have; an unused provider's installation or login is
|
||||
not a prerequisite. Codex seats retain `gpt-6-astra`; Claude seats use the native
|
||||
configured default. Check model access before launching. Automatic kernel startup
|
||||
continues to select independently from available authenticated providers.
|
||||
|
||||
Agent-guided setup asks once whether to allow OpenRig commands without repeated
|
||||
permission prompts, with **Yes recommended**. An explicit Yes authorizes native
|
||||
`rig` command rules at personal project scope unless you choose user-wide scope.
|
||||
No or no answer leaves settings unchanged. The allowance includes lifecycle and
|
||||
configuration commands, but does not grant general shell access or change sandbox
|
||||
and network settings. The guide covers applying, checking and undoing the rules;
|
||||
bootstrap does not silently apply them
|
||||
([#147](https://github.com/mvschwarz/openrig/pull/147)).
|
||||
|
||||
## Bundle files stay where you install them
|
||||
|
||||
Schema-version-2 bundles now copy their verified contents into `--target` and
|
||||
launch from there, keeping relative working directories and agent references
|
||||
available after temporary extraction is removed. Different files at a bundle
|
||||
path cause `target_conflict` before copying; identical files can be reused.
|
||||
|
||||
Without `--target`, `rig up <file>.rigbundle` installs into your current directory, so run it from the project folder you want.
|
||||
|
||||
`rig bundle create|inspect|install` and local `rig up --target` resolve relative
|
||||
paths against your current directory. Files still live on the daemon's host;
|
||||
this is not an upload. Install launches the rig. Legacy version-1 behavior and
|
||||
the separate `--cwd` override are unchanged. See the
|
||||
[bundle guide](../reference/rig-bundle.md#cli-surface)
|
||||
([#146](https://github.com/mvschwarz/openrig/pull/146)).
|
||||
|
||||
## Delivery and restore diagnostics
|
||||
|
||||
Managed launches after a tmux/host restart preserve the named launch target
|
||||
instead of confusing it with an old bare pane binding. The existing identity
|
||||
checks still apply to each write. Thanks to
|
||||
[@diaztunjano](https://github.com/diaztunjano) for reporting
|
||||
[#141](https://github.com/mvschwarz/openrig/issues/141), addressed by
|
||||
[#151](https://github.com/mvschwarz/openrig/pull/151). Actual reboot and power-loss
|
||||
recovery remain subject to the verification limits below.
|
||||
|
||||
Message delivery now refuses a known bare-shell foreground when an agent runtime
|
||||
should be running, so the message is not entered as shell commands. Terminal-only
|
||||
seats still receive text; unreadable or unknown foregrounds retain the existing
|
||||
advisory behavior ([#150](https://github.com/mvschwarz/openrig/pull/150), fixes
|
||||
[#142](https://github.com/mvschwarz/openrig/issues/142)).
|
||||
|
||||
## Community fixes
|
||||
|
||||
- **Local sender identity:** recognize a sender qualified with this daemon's own
|
||||
host ID as its local seat, avoiding false ownership refusals. Foreign host
|
||||
qualifiers and destination handling stay unchanged. Thanks to
|
||||
[@korallis](https://github.com/korallis) for
|
||||
[#135](https://github.com/mvschwarz/openrig/pull/135), and
|
||||
[@Yi-111-a](https://github.com/Yi-111-a) for the additional cases from
|
||||
[#137](https://github.com/mvschwarz/openrig/pull/137), included through
|
||||
[#144](https://github.com/mvschwarz/openrig/pull/144).
|
||||
- **Terminal and status details:** parse tmux window fields with printable
|
||||
separators ([#101](https://github.com/mvschwarz/openrig/pull/101)); distinguish
|
||||
health that was not requested from a failed read
|
||||
([#102](https://github.com/mvschwarz/openrig/pull/102)); use native realpath for
|
||||
Codex hook trust ([#103](https://github.com/mvschwarz/openrig/pull/103)); pass
|
||||
known Claude/Codex runtime hints to Herdr
|
||||
([#106](https://github.com/mvschwarz/openrig/pull/106)); report missing fresh
|
||||
startup context or adapters as needing attention
|
||||
([#107](https://github.com/mvschwarz/openrig/pull/107)); accept numeric Claude
|
||||
rate-limit reset timestamps ([#108](https://github.com/mvschwarz/openrig/pull/108)).
|
||||
Thanks to [@dajiaohuang](https://github.com/dajiaohuang).
|
||||
- **Pi input and portable checks:** retain line editing with `TERM=dumb`, and
|
||||
correct macOS path and timezone assumptions in tests. Thanks to
|
||||
[@shravansumanthanan](https://github.com/shravansumanthanan) for
|
||||
[#123](https://github.com/mvschwarz/openrig/pull/123).
|
||||
- **Configured restore paths:** restore-check uses the configured shared-docs
|
||||
root. This does not repair every issue in
|
||||
[#130](https://github.com/mvschwarz/openrig/issues/130). Thanks to
|
||||
[@jbaehova](https://github.com/jbaehova) for
|
||||
[#139](https://github.com/mvschwarz/openrig/pull/139).
|
||||
- **Anthropic gateways:** permit `ANTHROPIC_BASE_URL` in configured provider-auth
|
||||
environment forwarding. Thanks to [@FenjuFu](https://github.com/FenjuFu) for
|
||||
[#143](https://github.com/mvschwarz/openrig/pull/143).
|
||||
- **Terminal authentication maintenance:** reuse the shared token-comparison
|
||||
helper. Thanks to [@Sunil56224972](https://github.com/Sunil56224972) for
|
||||
[#145](https://github.com/mvschwarz/openrig/pull/145).
|
||||
|
||||
## Upgrade and verification limits
|
||||
|
||||
Node.js 22 and 24 remain supported; Node 20 is unsupported. This maintenance
|
||||
change adds no dependency, engine or database-migration change. Follow the
|
||||
[existing upgrade procedure](../../skills/_canonical/core/openrig-upgrade/SKILL.md).
|
||||
|
||||
Installed first-use and reboot/power-loss verification remain pending. Source
|
||||
and regression checks do not establish native permission enforcement or recovery
|
||||
of an authenticated team after a reboot.
|
||||
|
||||
Slack app manifest/setup assistance and Rig Stream classification remain
|
||||
experimental, with no new real-app or provider-run validation here. The existing
|
||||
Slack connector is not experimental. See the
|
||||
[retained limitations](v0.6.0.md#known-limitations).
|
||||
@@ -0,0 +1,62 @@
|
||||
# OpenRig 0.6.3 — Managed Claude messaging repair
|
||||
|
||||
This patch repairs a 0.6.2 regression that refused messages to running Claude
|
||||
Code seats launched with an explicit permission mode. It also fixes snapshot
|
||||
helper cleanup and interference from archived rigs with duplicate names.
|
||||
|
||||
## Managed Claude seats can receive messages
|
||||
|
||||
OpenRig's managed launch can leave a shell wrapper in front of Claude Code.
|
||||
The delivery check previously treated that wrapper as a bare shell and refused
|
||||
`rig send`, even though Claude was running beneath it.
|
||||
|
||||
The check now recognizes a stable, foreground Claude process descended from the
|
||||
bound pane, with the expected native session identity. A bare shell alone still
|
||||
does not qualify. Fork arguments that identify the parent rather than the
|
||||
current occupant remain insufficient evidence.
|
||||
|
||||
Thanks to [@dmelo](https://github.com/dmelo) for the detailed
|
||||
[#197 report](https://github.com/mvschwarz/openrig/issues/197), addressed by
|
||||
[#220](https://github.com/mvschwarz/openrig/pull/220).
|
||||
|
||||
## Snapshot helpers and archived rigs
|
||||
|
||||
- Clean up a newly created snapshot helper when its input target cannot be
|
||||
established and its ownership can still be proven. This prevents unusable
|
||||
`rigged-refresh-*` helpers accumulating on repeated attempts. If ownership
|
||||
cannot be established, preserve the session and report the limitation.
|
||||
Thanks to [@z4cc](https://github.com/z4cc) for
|
||||
[#188](https://github.com/mvschwarz/openrig/issues/188), addressed by
|
||||
[#189](https://github.com/mvschwarz/openrig/pull/189).
|
||||
- Exclude archived rigs from seat-reference resolution, prefer unarchived
|
||||
delivery targets, and preserve another live seat's session and queue work
|
||||
when removing a stale duplicate node. Removal reports whose session it kept.
|
||||
Thanks to [@Farkinell](https://github.com/Farkinell) for
|
||||
[#174](https://github.com/mvschwarz/openrig/issues/174), addressed by
|
||||
[#181](https://github.com/mvschwarz/openrig/pull/181).
|
||||
|
||||
## Proof media and repository checks
|
||||
|
||||
`rig proof add` rejects binary files supplied as artifact text, requires a
|
||||
Markdown artifact name, and requires `--replace` before overwriting an existing
|
||||
artifact. Attach screenshots and other binary evidence with `--media`. This
|
||||
prevents a proof write from replacing media with a Markdown body
|
||||
([#177](https://github.com/mvschwarz/openrig/pull/177)).
|
||||
|
||||
Repository script tests now run serially because packaging tests rebuild daemon
|
||||
output that other tests import. This removes the reproduced shared-output race
|
||||
without weakening those tests
|
||||
([#221](https://github.com/mvschwarz/openrig/pull/221)).
|
||||
|
||||
## Upgrade and verification scope
|
||||
|
||||
Node.js 22 and 24 remain supported. This patch changes no dependencies, engine
|
||||
requirements or database migrations. Use the
|
||||
[existing upgrade procedure](../../skills/_canonical/core/openrig-upgrade/SKILL.md).
|
||||
|
||||
The fixes have source reviews and focused regression coverage. The Claude
|
||||
process checks use synthetic process metadata; an installed Claude/Fedora send
|
||||
has not been demonstrated by that evidence. These checks do not establish
|
||||
native receipt, automatic recovery, or protection against a process exiting
|
||||
between verification and input. Final package and publication verification
|
||||
remain separate from this source preparation.
|
||||
Generated
+34
-473
File diff suppressed because it is too large.
Load diff
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openrig",
|
||||
"version": "0.5.3",
|
||||
"version": "0.6.3",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
@@ -13,7 +13,7 @@
|
||||
"scripts": {
|
||||
"build": "npm run build --workspaces",
|
||||
"test": "npm run test:repo && npm run test:workspaces",
|
||||
"test:repo": "npm run build -w packages/daemon && node --test scripts/*.test.mjs && node scripts/check-docs-guard.mjs && node scripts/mirror-skills.mjs --check && node scripts/generate-context-packs.mjs --check",
|
||||
"test:repo": "npm run build -w packages/daemon && node --test --test-concurrency=1 scripts/*.test.mjs && node scripts/check-docs-guard.mjs && node scripts/mirror-skills.mjs --check && node scripts/generate-context-packs.mjs --check",
|
||||
"test:workspaces": "npm run test -w packages/daemon -w packages/cli -w packages/tui",
|
||||
"test:ui": "npm run test -w packages/ui",
|
||||
"typecheck:prep": "npm run build -w packages/daemon",
|
||||
@@ -24,9 +24,10 @@
|
||||
"mirror-skills:check": "node scripts/mirror-skills.mjs --check",
|
||||
"generate-context-packs": "node scripts/generate-context-packs.mjs",
|
||||
"generate-context-packs:check": "node scripts/generate-context-packs.mjs --check",
|
||||
"gate:substance": "node scripts/check-substance-gate.mjs",
|
||||
"gate": "node scripts/gate-lane.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20 || ^22 || ^24"
|
||||
"node": "^22 || ^24"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
# Build artifacts assembled by scripts/build-package.sh
|
||||
daemon/
|
||||
ui/
|
||||
|
||||
# Root license copied by the CLI build
|
||||
/LICENSE
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openrig/cli",
|
||||
"version": "0.5.3",
|
||||
"version": "0.6.3",
|
||||
"type": "module",
|
||||
"description": "Local control plane for multi-agent coding topologies",
|
||||
"keywords": [
|
||||
@@ -30,6 +30,7 @@
|
||||
"openrig-tui": "tui/dist/main.js"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"dist",
|
||||
"daemon",
|
||||
"ui",
|
||||
@@ -37,7 +38,7 @@
|
||||
"scripts"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc && chmod +x dist/bin-wrapper.js && mkdir -p dist/schemas && cp src/schemas/*.json dist/schemas/ && mkdir -p dist/lib/scope-templates && cp src/lib/scope-templates/*.md dist/lib/scope-templates/",
|
||||
"build": "tsc && chmod +x dist/bin-wrapper.js && mkdir -p dist/schemas && cp src/schemas/*.json dist/schemas/ && mkdir -p dist/lib/scope-templates && cp src/lib/scope-templates/*.md dist/lib/scope-templates/ && cp ../../LICENSE LICENSE",
|
||||
"postinstall": "node scripts/check-abi.mjs",
|
||||
"prepublishOnly": "bash ../../scripts/build-package.sh",
|
||||
"test": "vitest run",
|
||||
@@ -47,27 +48,24 @@
|
||||
"@hono/node-server": "^1.13.0",
|
||||
"@hono/node-ws": "^1.3.1",
|
||||
"@modelcontextprotocol/sdk": "^1.28.0",
|
||||
"@openrig/daemon": "*",
|
||||
"ajv": "^8.18.0",
|
||||
"ajv-formats": "^3.0.1",
|
||||
"better-sqlite3": "^11.8.0",
|
||||
"better-sqlite3": "^13.0.3",
|
||||
"commander": "^13.0.0",
|
||||
"hono": "^4.7.0",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"smol-toml": "^1.8.0",
|
||||
"tar": "^7.5.13",
|
||||
"ulid": "^2.3.0",
|
||||
"yaml": "^2.8.3",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"bundledDependencies": [
|
||||
"@openrig/daemon"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=22"
|
||||
}
|
||||
}
|
||||
@@ -1,103 +1,153 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Postinstall ABI sanity check for @openrig/cli.
|
||||
// Fails loudly if better-sqlite3 native binary does not match the active
|
||||
// Node runtime. Layered on top of the engines.node constraint in
|
||||
// package.json — engines warns at npm level; this hard-blocks at install
|
||||
// time with a copy-paste fix.
|
||||
// Postinstall native-SQLite check for @openrig/cli.
|
||||
// OpenRig supports Node.js 22 and 24. better-sqlite3 13 requires Node 22 or
|
||||
// newer; on Node 20 it installs and loads, then crashes (SIGSEGV) the first
|
||||
// time a database is opened. This check refuses unsupported majors BEFORE any
|
||||
// native code runs, then proves the binding can actually open a database.
|
||||
// The open runs in a child process so a native crash is reported, not silent.
|
||||
|
||||
const SUPPORTED_MAJORS = [22, 24];
|
||||
|
||||
const box = (lines) =>
|
||||
[
|
||||
"",
|
||||
" ╔══════════════════════════════════════════════════════════════╗",
|
||||
...lines.map((line) => ` ║ ${line.padEnd(60)}║`),
|
||||
" ╚══════════════════════════════════════════════════════════════╝",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
const FIX_INSTALL = "Fix: nvm install 22 && npm install -g @openrig/cli";
|
||||
|
||||
/**
|
||||
* @param {{ nodeVersion: string, loadNativeAddon: () => void }} deps
|
||||
* @returns {{ ok: true } | { ok: false, message: string }}
|
||||
* @param {{
|
||||
* nodeVersion: string,
|
||||
* loadNativeAddon: () => void,
|
||||
* openNativeDatabase?: () => { ok: true } | { ok: false, detail: string },
|
||||
* }} deps
|
||||
* @returns {{ ok: true, warning?: string } | { ok: false, message: string }}
|
||||
*/
|
||||
export function checkAbi({ nodeVersion, loadNativeAddon }) {
|
||||
// Phase 1: version-range check (fast path).
|
||||
// Even-numbered Node majors (20, 22, 24) are LTS lines with native addon
|
||||
// prebuilds. Odd-numbered majors (21, 23, 25) generally lack them.
|
||||
export function checkAbi({ nodeVersion, loadNativeAddon, openNativeDatabase }) {
|
||||
// Phase 1: version-range check. Runs before any native code is loaded.
|
||||
const match = nodeVersion.match(/^v?(\d+)/);
|
||||
const major = match ? parseInt(match[1], 10) : 0;
|
||||
|
||||
if (major < 20) {
|
||||
if (major < SUPPORTED_MAJORS[0]) {
|
||||
return {
|
||||
ok: false,
|
||||
message: [
|
||||
message: box([
|
||||
"@openrig/cli requires Node.js 22 or 24 (LTS).",
|
||||
`Current: ${nodeVersion}`,
|
||||
"",
|
||||
" ╔══════════════════════════════════════════════════════════════╗",
|
||||
` ║ @openrig/cli requires Node.js 20, 22, or 24 (LTS). ║`,
|
||||
` ║ Current: ${nodeVersion.padEnd(49)}║`,
|
||||
" ║ ║",
|
||||
" ║ Fix: nvm install 22 && npm install -g @openrig/cli ║",
|
||||
" ╚══════════════════════════════════════════════════════════════╝",
|
||||
"Node 20 is no longer supported: the SQLite binding",
|
||||
"(better-sqlite3 13) crashes when it opens a database there.",
|
||||
"",
|
||||
].join("\n"),
|
||||
FIX_INSTALL,
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
if (major % 2 !== 0) {
|
||||
return {
|
||||
ok: false,
|
||||
message: [
|
||||
message: box([
|
||||
"@openrig/cli does not support odd-numbered Node releases.",
|
||||
`Current: ${nodeVersion}`,
|
||||
"",
|
||||
" ╔══════════════════════════════════════════════════════════════╗",
|
||||
` ║ @openrig/cli does not support odd-numbered Node releases. ║`,
|
||||
` ║ Current: ${nodeVersion.padEnd(49)}║`,
|
||||
" ║ ║",
|
||||
" ║ Odd Node versions (21, 23, 25, …) lack native addon ║",
|
||||
" ║ prebuilds for better-sqlite3. The daemon will fail. ║",
|
||||
" ║ ║",
|
||||
" ║ Fix: nvm install 22 && npm install -g @openrig/cli ║",
|
||||
" ╚══════════════════════════════════════════════════════════════╝",
|
||||
"Supported: Node.js 22 and 24 (LTS).",
|
||||
"",
|
||||
].join("\n"),
|
||||
FIX_INSTALL,
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
// Phase 2: ABI load check (catches edge cases like local Node builds
|
||||
// or prebuild packaging gaps on supported versions).
|
||||
// Even majors above the supported range are untested, not refused.
|
||||
const warning = SUPPORTED_MAJORS.includes(major)
|
||||
? undefined
|
||||
: box([
|
||||
`Node ${major} is untested with @openrig/cli.`,
|
||||
`Current: ${nodeVersion}`,
|
||||
"",
|
||||
"Supported: Node.js 22 and 24 (LTS). The SQLite check below",
|
||||
"passed, but other behavior on this Node is unverified.",
|
||||
]);
|
||||
|
||||
// Phase 2: load the native addon (ABI / packaging / permission problems).
|
||||
try {
|
||||
loadNativeAddon();
|
||||
} catch (err) {
|
||||
const detail = err instanceof Error ? err.message : String(err);
|
||||
const isAbiMismatch = detail.includes("NODE_MODULE_VERSION");
|
||||
|
||||
if (isAbiMismatch) {
|
||||
return {
|
||||
ok: false,
|
||||
message: [
|
||||
"",
|
||||
" ╔══════════════════════════════════════════════════════════════╗",
|
||||
" ║ better-sqlite3 native binary does not match this Node. ║",
|
||||
` ║ Current: ${nodeVersion.padEnd(49)}║`,
|
||||
" ║ ║",
|
||||
" ║ Fix: npm rebuild better-sqlite3 ║",
|
||||
" ║ or: nvm install 22 && npm install -g @openrig/cli ║",
|
||||
" ╚══════════════════════════════════════════════════════════════╝",
|
||||
"",
|
||||
` Detail: ${detail}`,
|
||||
"",
|
||||
].join("\n"),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
message: [
|
||||
"",
|
||||
" ╔══════════════════════════════════════════════════════════════╗",
|
||||
" ║ better-sqlite3 native addon failed to load. ║",
|
||||
` ║ Current: ${nodeVersion.padEnd(49)}║`,
|
||||
" ║ ║",
|
||||
" ║ Fix: npm rebuild better-sqlite3 ║",
|
||||
" ║ If that fails, reinstall with a supported Node version: ║",
|
||||
" ║ nvm install 22 && npm install -g @openrig/cli ║",
|
||||
" ╚══════════════════════════════════════════════════════════════╝",
|
||||
"",
|
||||
` Detail: ${detail}`,
|
||||
"",
|
||||
].join("\n"),
|
||||
message:
|
||||
box(
|
||||
isAbiMismatch
|
||||
? [
|
||||
"better-sqlite3 native binary does not match this Node.",
|
||||
`Current: ${nodeVersion}`,
|
||||
"",
|
||||
"Fix: npm rebuild better-sqlite3",
|
||||
" or: nvm install 22 && npm install -g @openrig/cli",
|
||||
]
|
||||
: [
|
||||
"better-sqlite3 native addon failed to load.",
|
||||
`Current: ${nodeVersion}`,
|
||||
"",
|
||||
"Fix: npm rebuild better-sqlite3",
|
||||
"If that fails, reinstall with a supported Node version:",
|
||||
" nvm install 22 && npm install -g @openrig/cli",
|
||||
],
|
||||
) + ` Detail: ${detail}\n`,
|
||||
};
|
||||
}
|
||||
|
||||
// Phase 3: open an in-memory database. Loading alone does not prove the
|
||||
// binding works (Node 20 + better-sqlite3 13 loads, then segfaults here).
|
||||
if (openNativeDatabase) {
|
||||
const opened = openNativeDatabase();
|
||||
if (!opened.ok) {
|
||||
return {
|
||||
ok: false,
|
||||
message:
|
||||
box([
|
||||
"better-sqlite3 loaded but could not open a database.",
|
||||
`Current: ${nodeVersion}`,
|
||||
"",
|
||||
"Fix: npm rebuild better-sqlite3",
|
||||
"If that fails, reinstall with Node 22 or 24:",
|
||||
" nvm install 22 && npm install -g @openrig/cli",
|
||||
]) + ` Detail: ${opened.detail}\n`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return warning ? { ok: true, warning } : { ok: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an in-memory database in a child process, so a native crash
|
||||
* (e.g. SIGSEGV) is observed as a signal instead of killing this script.
|
||||
* @param {string} addonPath absolute path resolved from this package
|
||||
*/
|
||||
export function openInChildProcess(addonPath, spawnSync) {
|
||||
const probe =
|
||||
`const Database = require(${JSON.stringify(addonPath)});` +
|
||||
`const db = new Database(":memory:");` +
|
||||
`db.prepare("select sqlite_version() as v").get();` +
|
||||
`db.close();`;
|
||||
const result = spawnSync(process.execPath, ["-e", probe], {
|
||||
encoding: "utf8",
|
||||
timeout: 30_000,
|
||||
});
|
||||
if (result.error) return { ok: false, detail: result.error.message };
|
||||
if (result.signal) return { ok: false, detail: `database open was killed by ${result.signal}` };
|
||||
if (result.status !== 0) {
|
||||
const stderr = (result.stderr || "").trim().split("\n").slice(-3).join(" ");
|
||||
return { ok: false, detail: `database open exited ${result.status}${stderr ? `: ${stderr}` : ""}` };
|
||||
}
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
@@ -109,15 +159,19 @@ const isMain =
|
||||
|
||||
if (isMain) {
|
||||
const { createRequire } = await import("node:module");
|
||||
const { spawnSync } = await import("node:child_process");
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const result = checkAbi({
|
||||
nodeVersion: process.version,
|
||||
loadNativeAddon: () => require("better-sqlite3"),
|
||||
openNativeDatabase: () => openInChildProcess(require.resolve("better-sqlite3"), spawnSync),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
console.error(result.message);
|
||||
process.exitCode = 1;
|
||||
} else if (result.warning) {
|
||||
console.error(result.warning);
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,10 @@ function maybeReexecWithSiblingNode(argv = process.argv): void {
|
||||
if (!preferredNode) return;
|
||||
|
||||
const scriptPath = argv[1] ? realpathSync(argv[1]) : fileURLToPath(import.meta.url);
|
||||
// An alias is not runtime authority. Keep a working interpreter; only switch
|
||||
// when the installed native dependency actually loads under the alternative.
|
||||
if (canLoadInstalledNative(process.execPath, scriptPath)) return;
|
||||
if (!canLoadInstalledNative(preferredNode, scriptPath)) return;
|
||||
const result = spawnSync(preferredNode, [scriptPath, ...argv.slice(2)], {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
@@ -67,6 +71,14 @@ function maybeReexecWithSiblingNode(argv = process.argv): void {
|
||||
process.exit(result.status ?? 0);
|
||||
}
|
||||
|
||||
export function canLoadInstalledNative(node: string, wrapper: string): boolean {
|
||||
const probe = spawnSync(node, ["--input-type=module", "-e",
|
||||
"import {createRequire} from 'node:module'; const require=createRequire(process.argv[1]); const D=require('better-sqlite3'); new D(':memory:').close();",
|
||||
wrapper,
|
||||
], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "pipe"] });
|
||||
return probe.status === 0;
|
||||
}
|
||||
|
||||
export async function run(argv = process.argv): Promise<void> {
|
||||
const normalizedArgv = [...argv];
|
||||
if (normalizedArgv[1]) {
|
||||
|
||||
@@ -112,7 +112,7 @@ export function renderDaemonTransportError(
|
||||
fact: e.message,
|
||||
consequence: "The command's outcome is UNKNOWN — the daemon did not answer in time.",
|
||||
action:
|
||||
"The daemon is slow or unresponsive (check load); retry once conditions ease. This is a slow response, not a stopped daemon.",
|
||||
"Check the command's effect before any retry, then inspect daemon load with 'rig status' and the daemon logs if it remains slow. This is a slow response, not a stopped daemon.",
|
||||
};
|
||||
} else if (e instanceof DaemonConnectionError) {
|
||||
parts = {
|
||||
|
||||
+61
-38
@@ -3,6 +3,7 @@ import path from "node:path";
|
||||
import { ConfigStore } from "./config-store.js";
|
||||
import { readOpenRigEnv } from "./openrig-compat.js";
|
||||
import { fetchWithTimeout, FetchTimeoutError } from "./fetch-with-timeout.js";
|
||||
import { readLocalOrigin } from "./local-origin.js";
|
||||
|
||||
export function terminalAuthHeaders(): Record<string, string> {
|
||||
const token = resolveTerminalToken();
|
||||
@@ -23,14 +24,7 @@ export const SENDER_IDENTITY_HEADER = "X-OpenRig-Session";
|
||||
export function senderIdentityHeaders(originSelfHostId?: string): Record<string, string> {
|
||||
const session = readOpenRigEnv("OPENRIG_SESSION_NAME", "RIGGED_SESSION_NAME")?.trim();
|
||||
if (!session) return {};
|
||||
// A4 HTTP-path origin-triple carry: on a REMOTE-targeting client, compose the origin TRIPLE
|
||||
// (member@rig@<this host's selfHostId>) at stamp time so the remote daemon's wrapPaneEnvelope
|
||||
// renders the ORIGIN host, not the destination's (its preserve branch fires on a 3-part sender).
|
||||
// The identity stays DERIVED — env session + THIS host's own selfHostId — never a caller-supplied
|
||||
// string (Rail 1 anti-forgery: `originSelfHostId` signals only THAT the client targets a remote host,
|
||||
// never WHO it is). An already-3-part origin (an upstream relay's triple) is preserved verbatim,
|
||||
// never re-stamped with this host's id (which would forge the origin). Local clients pass no
|
||||
// `originSelfHostId` and stamp the 2-part value, byte-identical to today.
|
||||
// Preserve qualified senders; append only a locally derived instance id.
|
||||
const value =
|
||||
originSelfHostId && originSelfHostId.length > 0 && session.split("@").length < 3
|
||||
? `${session}@${originSelfHostId}`
|
||||
@@ -38,18 +32,7 @@ export function senderIdentityHeaders(originSelfHostId?: string): Record<string,
|
||||
return { [SENDER_IDENTITY_HEADER]: value };
|
||||
}
|
||||
|
||||
/**
|
||||
* A4 — the SINGLE remote-origin construction point (shape b). Every remote-targeting client is built
|
||||
* HERE so the origin triple is stamped uniformly. Two structural laws the A4 grep-guard enforces:
|
||||
* (1) no remote site constructs a client by calling `clientFactory(<registry host>.url)` directly —
|
||||
* they all route through here; (2) `originSelfHostId` is ASSIGNED in exactly ONE place: this function
|
||||
* (the sole-assignment law, so pin 3 "no caller-supplied identity path" is structurally true).
|
||||
*
|
||||
* `originSelfHostId` is THIS host's own id (resolved fail-open via `fetchSelfHostId` on the LOCAL
|
||||
* daemon), NOT a caller-supplied identity — the caller only signals it targets a remote host. Preserves
|
||||
* the injected-mock `clientFactory` test seam: the client is built by the injected factory; this only
|
||||
* sets a field on it afterward (an injected mock harmlessly carries the extra property).
|
||||
*/
|
||||
/** Construct registered remote clients with the origin derived from this instance's durable store. */
|
||||
export function remoteDaemonClient(
|
||||
clientFactory: (baseUrl: string) => DaemonClient,
|
||||
url: string,
|
||||
@@ -57,6 +40,7 @@ export function remoteDaemonClient(
|
||||
): DaemonClient {
|
||||
const client = clientFactory(url);
|
||||
client.originSelfHostId = originSelfHostId;
|
||||
client.remoteTarget = true;
|
||||
return client;
|
||||
}
|
||||
|
||||
@@ -93,7 +77,7 @@ export class DaemonTimeoutError extends DaemonConnectionError {
|
||||
}
|
||||
|
||||
/**
|
||||
* Bad-response: the daemon replied, but the body could not be read as JSON
|
||||
* Bad-response: the daemon replied, but the body could not be read or parsed as JSON
|
||||
* (truncated / unparseable / non-JSON — a real symptom under daemon saturation).
|
||||
* DISTINCT from a stopped or unreachable daemon: the request WAS delivered and
|
||||
* the outcome is unknown, so this must never render as daemon-not-running.
|
||||
@@ -129,15 +113,41 @@ export class DaemonClient {
|
||||
readonly baseUrl: string;
|
||||
private fetchImpl: typeof fetch = fetch;
|
||||
private timeoutMs = 5_000;
|
||||
/**
|
||||
* A4 HTTP-path origin-triple carry: THIS host's `selfHostId`, set ONLY when this client targets a
|
||||
* REMOTE host, so `senderIdentityHeaders` stamps the origin TRIPLE (member@rig@selfHostId) instead of
|
||||
* the 2-part local value. Undefined for local clients (2-part stamp, unchanged). Assigned in EXACTLY
|
||||
* ONE place — `remoteDaemonClient` — enforced by the A4 grep-guard (the sole-assignment law); this is
|
||||
* NOT a caller identity string (Rail 1: it is THIS host's derived id, never who the caller claims to
|
||||
* be). Public (not ctor-injected) to keep the injected-mock `clientFactory` test seam untouched.
|
||||
*/
|
||||
/** Set by remoteDaemonClient; absent origin is labelled explicitly on remote requests. */
|
||||
originSelfHostId?: string;
|
||||
remoteTarget = false;
|
||||
private identity?: Promise<Record<string, string>>;
|
||||
private originUnknown = false;
|
||||
|
||||
private async identityHeaders(): Promise<Record<string, string>> {
|
||||
const localHeaders = senderIdentityHeaders();
|
||||
const session = localHeaders[SENDER_IDENTITY_HEADER];
|
||||
if (!session || session.split("@").length >= 3) return localHeaders;
|
||||
const directTarget = readOpenRigEnv("OPENRIG_URL", "RIGGED_URL");
|
||||
if (!this.remoteTarget && !directTarget) return localHeaders;
|
||||
const origin = this.originSelfHostId ?? readLocalOrigin();
|
||||
if (!origin) {
|
||||
this.originUnknown = true;
|
||||
return { ...localHeaders, "X-OpenRig-Origin-Unknown": "true" };
|
||||
}
|
||||
if (!this.remoteTarget) {
|
||||
// Loopback may be a forwarded endpoint. Only matching instance identity proves locality.
|
||||
const controller = new AbortController();
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
const target = await Promise.race([
|
||||
(async () => {
|
||||
const res = await this.fetchImpl(`${this.baseUrl.replace(/\/+$/, "")}/healthz`, { signal: controller.signal });
|
||||
return res.ok ? (await res.json() as { selfHostId?: unknown }).selfHostId : undefined;
|
||||
})(),
|
||||
new Promise<undefined>(resolve => { timer = setTimeout(() => resolve(undefined), Math.min(this.timeoutMs, 1000)); }),
|
||||
]);
|
||||
if (target === origin) return localHeaders;
|
||||
} catch { /* Unproved locality: carry the known origin, never the endpoint's identity. */ }
|
||||
finally { clearTimeout(timer); controller.abort(); }
|
||||
}
|
||||
return senderIdentityHeaders(origin);
|
||||
}
|
||||
|
||||
constructor(baseUrl?: string, options?: DaemonClientOptions) {
|
||||
if (baseUrl) {
|
||||
@@ -206,26 +216,36 @@ export class DaemonClient {
|
||||
}, options);
|
||||
}
|
||||
|
||||
private async fetch(path: string, init: RequestInit, options?: DaemonRequestOptions): Promise<Response> {
|
||||
private async fetch(path: string, init: RequestInit, options?: DaemonRequestOptions, consumeResponse?: (response: Response) => Promise<void>): Promise<Response> {
|
||||
const timeoutMs = options?.timeoutMs ?? this.timeoutMs;
|
||||
if (options?.headers) {
|
||||
init = { ...init, headers: { ...(init.headers as Record<string, string> ?? {}), ...options.headers } };
|
||||
}
|
||||
// P18 sender-provenance: stamp the seat-derived identity header LAST, so the transport — never a
|
||||
// caller-supplied header or a request body — decides the caller identity the channel of record records.
|
||||
// A4: a remote-targeting client (originSelfHostId set by remoteDaemonClient) stamps the origin TRIPLE;
|
||||
// local clients stamp the 2-part value, byte-identical to today.
|
||||
init = { ...init, headers: { ...(init.headers as Record<string, string> ?? {}), ...senderIdentityHeaders(this.originSelfHostId) } };
|
||||
// Known remote or unproved direct endpoints carry origin; proven local requests remain bare.
|
||||
this.identity ??= this.identityHeaders();
|
||||
init = { ...init, headers: { ...(init.headers as Record<string, string> ?? {}), ...await this.identity } };
|
||||
let responseStatus: number | undefined;
|
||||
try {
|
||||
return await fetchWithTimeout(
|
||||
const response = await fetchWithTimeout(
|
||||
this.fetchImpl,
|
||||
`${this.baseUrl}${path}`,
|
||||
init,
|
||||
{
|
||||
timeoutMs,
|
||||
consumeResponse: consumeResponse ? async (response) => {
|
||||
responseStatus = response.status;
|
||||
await consumeResponse(response);
|
||||
} : undefined,
|
||||
timeoutMessage: `Request to ${this.baseUrl}${path} timed out after ${timeoutMs}ms`,
|
||||
},
|
||||
);
|
||||
if (response.ok && this.originUnknown) {
|
||||
console.error("Origin instance unknown: local durable identity is unavailable; delivery continues with origin-unknown provenance.");
|
||||
this.originUnknown = false;
|
||||
}
|
||||
return response;
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
// Slow-response (a timed-out request) is a DISTINCT class from no-connect:
|
||||
@@ -234,18 +254,21 @@ export class DaemonClient {
|
||||
if (err instanceof FetchTimeoutError) {
|
||||
throw new DaemonTimeoutError(`The OpenRig daemon at ${this.baseUrl} did not respond in time: ${msg}`);
|
||||
}
|
||||
// Headers prove that the daemon received the request, but not that a write
|
||||
// finished. A dropped body must preserve unknown-outcome guidance.
|
||||
if (responseStatus !== undefined) throw new DaemonResponseError(responseStatus, "");
|
||||
throw new DaemonConnectionError(`Cannot connect to the OpenRig daemon at ${this.baseUrl}: ${msg}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async requestJson<T>(path: string, init: RequestInit, options?: DaemonRequestOptions): Promise<DaemonResponse<T>> {
|
||||
const res = await this.fetch(path, init, options);
|
||||
let text = "";
|
||||
const res = await this.fetch(path, init, options, async (response) => { text = await response.text(); });
|
||||
// Read the raw body once, THEN parse — so a truncated / unparseable response
|
||||
// (a real symptom under daemon saturation) surfaces as a typed
|
||||
// DaemonResponseError carrying the status + a bounded snippet, instead of a
|
||||
// raw SyntaxError bubbling to a cryptic (json) or silent (human) CLI exit.
|
||||
// A well-formed non-2xx body still parses and returns {status,data}; 204 has none.
|
||||
const text = await res.text();
|
||||
if (res.status === 204) return { status: res.status, data: undefined as T };
|
||||
try {
|
||||
return { status: res.status, data: JSON.parse(text) as T };
|
||||
@@ -255,8 +278,8 @@ export class DaemonClient {
|
||||
}
|
||||
|
||||
private async requestText(path: string, init: RequestInit, options?: DaemonRequestOptions): Promise<DaemonResponse<string>> {
|
||||
const res = await this.fetch(path, init, options);
|
||||
const data = await res.text();
|
||||
let data = "";
|
||||
const res = await this.fetch(path, init, options, async (response) => { data = await response.text(); });
|
||||
return { status: res.status, data };
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ export function attachCommand(depsOverride?: StatusDeps): Command {
|
||||
}
|
||||
|
||||
const hasNode = !!opts.node;
|
||||
const hasPodFields = !!opts.pod || !!opts.member || !!opts.runtime;
|
||||
const hasPodFields = !!opts.pod || !!opts.member;
|
||||
if (hasNode && hasPodFields) {
|
||||
console.error("Specify either --node or --pod + --member + --runtime, not both.");
|
||||
process.exitCode = 1;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { emitCrossHostError, emitRemoteHttpFailure } from "../cross-host-cli-hel
|
||||
import { runRemoteHttpOp } from "../remote-host-ops.js";
|
||||
import { resolveContextRef, walkSizedWarning } from "../context-resolve.js";
|
||||
import { resolveSenderSession, SENDER_FALLBACK } from "../sender-identity.js";
|
||||
import { refuseEmptyMessage } from "./send.js";
|
||||
|
||||
/**
|
||||
* OPR.0.4.6.MH4 C3 — the cross-host broadcast deadline, named at the call
|
||||
@@ -71,11 +72,12 @@ selection, not the agent@rig@host sugar.`)
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if ((text === undefined || text.length === 0) && !opts.context) {
|
||||
if (text === undefined && !opts.context) {
|
||||
console.error("Provide a message to broadcast (or --context <ref>).");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (!opts.context && refuseEmptyMessage(text!, "broadcast", Boolean(opts.json))) return;
|
||||
|
||||
// P18 DELIVER-AND-LABEL — the seat boundary no longer refuses. Resolve the broadcasting seat from the
|
||||
// seat env; when it is unresolvable the broadcast STILL proceeds carrying the honest `<unknown sender>`
|
||||
@@ -117,6 +119,7 @@ selection, not the agent@rig@host sugar.`)
|
||||
const warn = walkSizedWarning(resolved);
|
||||
if (warn && !opts.json) console.log(`Advisory: ${warn}`);
|
||||
}
|
||||
if (refuseEmptyMessage(payload, "broadcast", Boolean(opts.json))) return;
|
||||
const body: Record<string, unknown> = { text: payload, force: opts.force };
|
||||
if (opts.rig) body.rig = opts.rig;
|
||||
if (opts.pod) body.pod = opts.pod;
|
||||
@@ -228,6 +231,12 @@ function renderBroadcastResults(data: Record<string, unknown>): void {
|
||||
}
|
||||
}
|
||||
console.log(`${data["sent"]}/${data["total"]} delivered`);
|
||||
// S2 (OPR.0.5.4.3): surface additive advisories (e.g. the unknown-sender
|
||||
// sign-it notice) — an env-less operator must see it, not "sent" lines alone.
|
||||
const advisory = data["warning"] as string | undefined;
|
||||
if (advisory) {
|
||||
console.log(`Advisory: ${advisory}`);
|
||||
}
|
||||
|
||||
if ((data["failed"] as number) > 0 || results.some((r) => !r.ok)) {
|
||||
process.exitCode = 1;
|
||||
|
||||
@@ -85,8 +85,10 @@ export function bundleCommand(depsOverride?: StatusDeps): Command {
|
||||
// bundle sizes; if a bundle author hits this, the daemon-side
|
||||
// operation is async-completable in a future /bundles/jobs/<id>
|
||||
// surface (not in slice-05 scope).
|
||||
// Paths are resolved here, against the operator's cwd: the daemon would otherwise resolve
|
||||
// them against ITS cwd. Nothing is uploaded — the files must exist on the daemon's host.
|
||||
const res = await client.post<Record<string, unknown>>("/api/bundles/create", {
|
||||
specPath: spec, bundleName: opts.name, bundleVersion: opts.bundleVersion, outputPath: opts.output,
|
||||
specPath: nodePath.resolve(spec), bundleName: opts.name, bundleVersion: opts.bundleVersion, outputPath: nodePath.resolve(opts.output),
|
||||
includePackages: opts.includePackages,
|
||||
rigRoot: opts.rigRoot ? nodePath.resolve(opts.rigRoot) : undefined,
|
||||
provenance: buildClientProvenance(opts.notes),
|
||||
@@ -117,7 +119,7 @@ export function bundleCommand(depsOverride?: StatusDeps): Command {
|
||||
const client = await getClient(deps);
|
||||
if (!client) { process.exitCode = 1; return; }
|
||||
|
||||
const res = await client.post<Record<string, unknown>>("/api/bundles/inspect", { bundlePath });
|
||||
const res = await client.post<Record<string, unknown>>("/api/bundles/inspect", { bundlePath: nodePath.resolve(bundlePath) });
|
||||
|
||||
// Check for structured failures (200 with error or failed integrity)
|
||||
const hasError = typeof res.data["error"] === "string";
|
||||
@@ -166,7 +168,8 @@ export function bundleCommand(depsOverride?: StatusDeps): Command {
|
||||
// daemon completed the mutating install — operator-unsafe retry
|
||||
// path (rig_name_collision on second attempt). Bumped to 120s.
|
||||
const res = await client.post<Record<string, unknown>>("/api/bundles/install", {
|
||||
bundlePath, plan: opts.plan ?? false, autoApprove: opts.yes ?? false, targetRoot: opts.target,
|
||||
bundlePath: nodePath.resolve(bundlePath), plan: opts.plan ?? false, autoApprove: opts.yes ?? false,
|
||||
targetRoot: opts.target ? nodePath.resolve(opts.target) : undefined,
|
||||
// Item 2 / slice-05 Checkpoint 3.3: send CLI version + skip flag for the
|
||||
// daemon-side install-time compatibility check. CLI version read at call
|
||||
// time (no module-level constant) via the existing getCliVersion helper.
|
||||
|
||||
@@ -40,7 +40,7 @@ Examples:
|
||||
rig capture --rig my-rig
|
||||
rig capture --pod dev --rig my-rig
|
||||
rig capture --rig my-rig --json
|
||||
rig capture --host vm-claude-test dev-impl@my-rig --lines 50
|
||||
rig capture --host remote-dev dev-impl@my-rig --lines 50
|
||||
|
||||
Supported notes:
|
||||
- Multi-target capture reports unsupported external_cli nodes as explicit per-target failures.
|
||||
|
||||
@@ -1,605 +1,31 @@
|
||||
// User Settings v0 — `rig config init-workspace` workspace scaffolding.
|
||||
//
|
||||
// Creates the mission-aware default workspace at ~/.openrig/workspace/ (or a
|
||||
// caller-supplied --root). The same scaffold is also used idempotently by
|
||||
// daemon startup so a fresh install has a browsable Project workspace before
|
||||
// the user discovers the explicit command.
|
||||
//
|
||||
// Behavior:
|
||||
// - Reads workspace.root setting (default ~/.openrig/workspace/).
|
||||
// - Creates canonical subdirs: missions/ progress/ field-notes/ specs/ dogfood-evidence/.
|
||||
// - Seeds two example missions with multiple slices.
|
||||
// - Drops workspace README.md + STEERING.md.
|
||||
// - --dry-run: report what would be created without acting.
|
||||
// - --force: overwrite existing files (NOT directories — never deletes
|
||||
// operator content).
|
||||
// - Idempotent without --force: existing-dir + existing-readme is a no-op.
|
||||
// Public adapter for the S01 project-workspace owner used by S05 instance
|
||||
// initialization. The bytes and behavior live in @openrig/daemon.
|
||||
|
||||
import { Command } from "commander";
|
||||
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
ensureDefaultWorkspace,
|
||||
workspaceScaffoldDirs,
|
||||
workspaceScaffoldFiles,
|
||||
type InitWorkspaceResult,
|
||||
} from "@openrig/daemon/instance-initialization";
|
||||
import { ConfigStore } from "../config-store.js";
|
||||
import { renderMissionBriefTemplate, renderMissionNotesTemplate, renderSliceProofTemplate } from "../lib/scope/templates.js";
|
||||
|
||||
export { workspaceScaffoldDirs, workspaceScaffoldFiles };
|
||||
export type { InitWorkspaceResult };
|
||||
|
||||
export interface InitWorkspaceOpts {
|
||||
root?: string;
|
||||
/** Deprecated compatibility input. Existing files are always preserved. */
|
||||
force?: boolean;
|
||||
dryRun?: boolean;
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
export interface InitWorkspaceResult {
|
||||
root: string;
|
||||
/** True when this run would (or did) create the root dir; false if root existed. */
|
||||
rootCreated: boolean;
|
||||
subdirs: Array<{ name: string; path: string; created: boolean }>;
|
||||
files: Array<{ relPath: string; absPath: string; created: boolean; skipped: "exists" | null }>;
|
||||
/** True when --dry-run; nothing was actually written. */
|
||||
dryRun: boolean;
|
||||
}
|
||||
|
||||
type DefaultSlice = {
|
||||
id: string;
|
||||
/** §1 dot-ID per conventions/scope-and-versioning (release-0.3.2
|
||||
* slice 01 GA-coherence pass). Mirror of the daemon constant in
|
||||
* packages/daemon/src/domain/workspace/default-workspace-scaffold.ts
|
||||
* — keep in lockstep for the slice-21 parity test. */
|
||||
dotId: string;
|
||||
title: string;
|
||||
status: "active" | "draft";
|
||||
objective: string;
|
||||
};
|
||||
|
||||
type DefaultMission = {
|
||||
id: string;
|
||||
/** §1 mission dot-ID — escape band for non-release missions. */
|
||||
dotId: string;
|
||||
title: string;
|
||||
status: "active" | "draft";
|
||||
objective: string;
|
||||
slices: DefaultSlice[];
|
||||
};
|
||||
|
||||
// Release-0.3.2 slice 01 (OPR.0.3.2.1) — seeded missions/slices now
|
||||
// carry §1 dot-IDs in frontmatter so the scaffold coheres with
|
||||
// `conventions/scope-and-versioning`. Mirror of the daemon constant;
|
||||
// the slice-21 parity test asserts both sides emit identical bodies.
|
||||
const DEFAULT_MISSIONS: DefaultMission[] = [
|
||||
{
|
||||
id: "getting-started",
|
||||
dotId: "OPR.99.0.1",
|
||||
title: "Getting Started",
|
||||
status: "active",
|
||||
objective: "Launch the conveyor starter and learn how OpenRig moves work through queue-backed slices.",
|
||||
slices: [
|
||||
{
|
||||
id: "first-conveyor-run",
|
||||
dotId: "OPR.99.0.1.1",
|
||||
title: "First Conveyor Run",
|
||||
status: "active",
|
||||
objective: "Move one small packet through intake, planning, build, review, and close on the conveyor starter.",
|
||||
},
|
||||
{
|
||||
id: "inspect-project-evidence",
|
||||
dotId: "OPR.99.0.1.2",
|
||||
title: "Inspect Project Evidence",
|
||||
status: "draft",
|
||||
objective: "Open Project, Queue, Story, and Tests to inspect the evidence created by the first conveyor run.",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const WORKSPACE_DIRS = [
|
||||
"missions",
|
||||
"artifacts",
|
||||
"evidence",
|
||||
"progress",
|
||||
"field-notes",
|
||||
"specs",
|
||||
"dogfood-evidence",
|
||||
...DEFAULT_MISSIONS.flatMap((mission) => [
|
||||
`missions/${mission.id}`,
|
||||
`missions/${mission.id}/slices`,
|
||||
...mission.slices.flatMap((slice) => [
|
||||
`missions/${mission.id}/slices/${slice.id}`,
|
||||
`missions/${mission.id}/slices/${slice.id}/proof`,
|
||||
]),
|
||||
]),
|
||||
] as const;
|
||||
|
||||
function subdirReadmeContent(subdir: string): string {
|
||||
switch (subdir) {
|
||||
case "missions":
|
||||
return "# missions\n\nProject missions live here. Each mission folder maps to one Project mission in the UI and owns a `slices/` child folder.\n\nExpected shape:\n\n```text\nmissions/<mission-name>/README.md\nmissions/<mission-name>/PROGRESS.md\nmissions/<mission-name>/slices/<slice-name>/README.md\n```\n\nEvery mission and slice README carries a stable dot-ID in its frontmatter (`id: OPR.<ver>[.<n>]`) per the scope-and-versioning convention. Use `rig scope mission create <name>` and `rig scope slice create <mission> <slug>` to mint conformant artifacts; the CLI handles the dot-ID + auto-numbering for you. The folder name is the operator-facing slug; the `id:` is the stable handle that survives renames.\n";
|
||||
case "artifacts":
|
||||
return "# artifacts\n\nWork products live here: plans, drafts, generated outputs, and other files that a slice may reference before closure.\n";
|
||||
case "evidence":
|
||||
return "# evidence\n\nHuman-readable verification notes live here. Use this for compact proof summaries that are not tied to a screenshot or video packet.\n";
|
||||
case "progress":
|
||||
return "# progress\n\nPROGRESS.md tree. OpenRig's Progress browse view scans this directory recursively for PROGRESS.md files and renders them as a hierarchical tree.\n";
|
||||
case "field-notes":
|
||||
return "# field-notes\n\nOperator field notes. Free-form markdown notes from your daily work. OpenRig surfaces these alongside missions and slices for context.\n";
|
||||
case "specs":
|
||||
return "# specs\n\nWorkspace specs (rig specs / agent specs / workflow specs / context packs / skills). OpenRig's Library browses this directory alongside bundled specs.\n";
|
||||
case "dogfood-evidence":
|
||||
return "# dogfood-evidence\n\nProof packets live here. Each proof packet folder is matched to a Project slice by folder-name tokens and may contain markdown, screenshots, videos, traces, and other verification artifacts.\n";
|
||||
default:
|
||||
return `# ${subdir}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
const WORKSPACE_README = `# OpenRig Workspace
|
||||
|
||||
This workspace is file-backed. The Project UI mirrors this structure:
|
||||
|
||||
- \`missions/<mission-id>\` becomes a Project mission.
|
||||
- \`missions/<mission-id>/slices/<slice-id>\` becomes a Project slice.
|
||||
- Queue items should mention or tag the mission id and slice id so Project can attach live work to the right slice.
|
||||
- \`artifacts/\` is for work products that a slice needs to keep.
|
||||
- \`evidence/\` is for proof notes and verification summaries.
|
||||
- \`dogfood-evidence/<proof-packet-id>\` becomes Tests proof when the packet id contains the slice id tokens.
|
||||
|
||||
Use stable kebab-case names for mission and slice folders. Every mission/slice README also carries a stable dot-ID in its frontmatter (\`id: OPR.<ver>[.<n>]\`) per the \`scope-and-versioning\` convention; mint conformant artifacts via \`rig scope mission create <name>\` and \`rig scope slice create <mission> <slug>\`. Folder names are operator-facing slugs; the \`id:\` is the rename-proof handle.
|
||||
`;
|
||||
|
||||
const STEERING_PLACEHOLDER = `---
|
||||
title: Priority Stack
|
||||
status: placeholder
|
||||
---
|
||||
|
||||
# OpenRig Priority Stack
|
||||
|
||||
This file is a placeholder created by \`rig config init-workspace\`. Edit it
|
||||
to record your top priorities. OpenRig's Steering surface reads this file
|
||||
alongside your Project workspace.
|
||||
|
||||
## Top 3
|
||||
|
||||
1. Run the \`conveyor\` starter rig.
|
||||
2. Move one packet through \`basic-loop\` or \`conveyor\`.
|
||||
3. Inspect the Project, Queue, Story, and Tests surfaces.
|
||||
|
||||
## In Motion
|
||||
|
||||
(Active slices land here as you push them through the priority rail.)
|
||||
|
||||
## Loop State
|
||||
|
||||
(Health gates + loop diagnostics land here.)
|
||||
`;
|
||||
|
||||
function missionReadme(mission: DefaultMission): string {
|
||||
return `---
|
||||
id: ${mission.dotId}
|
||||
title: ${mission.title}
|
||||
status: ${mission.status}
|
||||
mission: ${mission.id}
|
||||
---
|
||||
|
||||
# ${mission.title}
|
||||
|
||||
${mission.objective}
|
||||
|
||||
## Slices
|
||||
|
||||
${mission.slices.map((slice) => `- [${slice.title}](slices/${slice.id}/README.md)`).join("\n")}
|
||||
`;
|
||||
}
|
||||
|
||||
function missionProgress(mission: DefaultMission): string {
|
||||
return `---
|
||||
title: ${mission.title} Progress
|
||||
status: ${mission.status}
|
||||
mission: ${mission.id}
|
||||
---
|
||||
|
||||
# ${mission.title} Progress
|
||||
|
||||
- [ ] Keep mission README current.
|
||||
- [ ] Keep active slices queue-backed with mission and slice ids.
|
||||
`;
|
||||
}
|
||||
|
||||
// V0.3.1 slice 21 onboarding-conveyor — narrative content for the
|
||||
// getting-started mission's two slices. PARITY CONTRACT with
|
||||
// `packages/daemon/src/domain/workspace/getting-started-narrative.ts`:
|
||||
// the daemon side is the canonical source; this CLI-side mirror MUST
|
||||
// stay byte-identical. The narrative-parity test in
|
||||
// `packages/daemon/test/getting-started-narrative-parity.test.ts`
|
||||
// (added in this slice) asserts byte-identity by reading both this
|
||||
// file and the daemon module — if either drifts, the test fails.
|
||||
// If you update content here, update the daemon mirror in lockstep.
|
||||
|
||||
const FIRST_CONVEYOR_RUN_README = `# First Conveyor Run
|
||||
|
||||
Welcome to OpenRig. This is the first slice in your getting-started mission. It exists to teach you what a **conveyor run** is + how OpenRig moves work through a multi-agent topology.
|
||||
|
||||
## What a conveyor run is
|
||||
|
||||
A **conveyor** is OpenRig's central work-movement primitive. One conveyor run = one complete flow of work through your topology:
|
||||
|
||||
1. **Slice declared** — operator (you) or an agent declares a slice with intent + acceptance criteria
|
||||
2. **Work routed** — orchestrator picks the slice up + routes it to the right agent
|
||||
3. **Agents collaborate** — work moves agent-to-agent via durable handoffs (queue items)
|
||||
4. **Evidence accumulates** — each step leaves a trail: commits, files, proof packets, screenshots
|
||||
5. **Slice closes** — acceptance criteria met; orchestrator routes the result back
|
||||
|
||||
You're looking at a mocked conveyor run RIGHT NOW. The other tabs (Story / Progress / Artifacts / Tests / Queue / Topology) each show a different slice of this same run.
|
||||
|
||||
## Walking the tabs
|
||||
|
||||
- **Overview** (you're here) — read what a slice intends + how to use the rest of the tabs
|
||||
- **Story** — narrative timeline of what happened, beat by beat
|
||||
- **Progress** — acceptance checklist; what's done, what's pending
|
||||
- **Artifacts** — files produced, commits, proof packets — the durable evidence trail
|
||||
- **Tests** — pass/fail summaries + screenshots + verification proof
|
||||
- **Queue** — operational qitems showing how work moved between agents
|
||||
- **Topology** — the rig graph; which agents touched this slice + which edges fired
|
||||
|
||||
## Click through
|
||||
|
||||
Try them in order. Each tab teaches itself.
|
||||
`;
|
||||
|
||||
const FIRST_CONVEYOR_RUN_TIMELINE = `# Story — First Conveyor Run
|
||||
|
||||
## 2026-04-15 09:00 — Slice declared
|
||||
|
||||
Operator request: "Build a CLI tool that lints todo lists." Slice declared in \`getting-started/slices/first-conveyor-run/\`. Acceptance: \`tdl lint <file>\` exits non-zero on malformed entries.
|
||||
|
||||
## 09:02 — Orchestrator routes
|
||||
|
||||
\`orch-lead@getting-started\` picks up the slice. Routes to \`driver@getting-started\` via \`rig queue handoff\`. Driver receives nudge; opens IMPL-PRD; reads acceptance.
|
||||
|
||||
## 09:05 — Driver picks up
|
||||
|
||||
Driver claims qitem. Reads slice scope. Surveys existing tdl repo (this is a worked example — imagine the repo is real). Plans: parse YAML; validate entries; exit code per finding.
|
||||
|
||||
## 09:30 — First commit
|
||||
|
||||
\`feat(lint): parse + validate todo entries\` — driver commits 4 files. Stream event emitted; visible in Artifacts tab.
|
||||
|
||||
## 10:15 — Driver hands to reviewer
|
||||
|
||||
Acceptance met locally. Driver hands off to \`reviewer@getting-started\`. Reviewer opens diff; reads tests.
|
||||
|
||||
## 10:25 — Reviewer flags concern
|
||||
|
||||
"Edge case: what about UTF-8 entries with combining characters?" Hands back to driver as \`concerning\`.
|
||||
|
||||
## 10:40 — Driver addresses
|
||||
|
||||
Driver fixes UTF-8 handling. Re-runs tests; all pass. Hands back.
|
||||
|
||||
## 11:00 — Reviewer accepts
|
||||
|
||||
\`accept\` decision; closes qitem with \`closure-reason: handed_off_to orch-lead\`. Conveyor proceeds.
|
||||
|
||||
## 11:05 — Slice ships
|
||||
|
||||
Orchestrator merges. Proof packet generated. Slice marked SHIPPED.
|
||||
|
||||
---
|
||||
|
||||
**What you just read**: one conveyor run from declaration to ship. ~2 hours wall-clock; 3 agents involved; 1 handoff with concern + remediation; 5 stream events; 1 proof packet.
|
||||
|
||||
The Story tab shows this kind of narrative for any real slice. Driver-authored \`timeline.md\` files live in each slice folder; this is yours to read + your operator's to update as work moves.
|
||||
`;
|
||||
|
||||
const FIRST_CONVEYOR_RUN_PROGRESS = `---
|
||||
title: First Conveyor Run Progress
|
||||
status: active
|
||||
mission: getting-started
|
||||
rail-item: getting-started
|
||||
slice: first-conveyor-run
|
||||
---
|
||||
|
||||
# Progress — First Conveyor Run
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] Parse YAML todo entries
|
||||
- [x] Validate per-entry shape (id, title, status, due-date)
|
||||
- [x] Exit non-zero on malformed entries
|
||||
- [x] Handle UTF-8 entries with combining characters
|
||||
- [x] Tests pass (12/12)
|
||||
- [x] Reviewer accepts
|
||||
- [x] Slice merged
|
||||
|
||||
## Status: SHIPPED
|
||||
|
||||
This was a mocked conveyor run — no real code was produced. But the Progress tab works the same way for real work: as acceptance criteria are met, boxes get checked; the operator can scan at a glance whether the slice is on track.
|
||||
|
||||
## How the Progress tab works
|
||||
|
||||
The tab renders the slice's \`PROGRESS.md\` markdown file. Driver updates it as work progresses. Operator scans it during walks. Founder reviews at slice closure.
|
||||
|
||||
Try writing one for a real slice you start. It's just markdown; live updates appear here.
|
||||
`;
|
||||
|
||||
const INSPECT_PROJECT_EVIDENCE_README = `# Inspect Project Evidence
|
||||
|
||||
The previous slice (First Conveyor Run) showed you what a conveyor run looks like as it happens. This slice teaches you how to inspect the evidence after a run completes.
|
||||
|
||||
## Why inspect evidence?
|
||||
|
||||
In OpenRig, agents work autonomously. The operator (you) doesn't watch every keystroke. Instead, the operator:
|
||||
|
||||
1. Declares slices with clear acceptance
|
||||
2. Routes them to agents
|
||||
3. Inspects evidence when slices close
|
||||
|
||||
The Artifacts + Tests + Queue + Topology tabs are your inspection surface. They show you what happened, with enough detail to:
|
||||
|
||||
- Verify the acceptance was actually met
|
||||
- Spot subtle issues a passing test might miss
|
||||
- Build trust in the agents over time
|
||||
|
||||
## Walking the tabs
|
||||
|
||||
- **Overview** — you're here; learn what to look for
|
||||
- **Story** — read the narrative of what happened (high-level)
|
||||
- **Progress** — acceptance status at a glance
|
||||
- **Artifacts** — the durable evidence: files, commits, proof packets. **THIS IS THE TAB YOU LEARN MOST FROM.**
|
||||
- **Tests** — pass/fail + screenshots + verification proof
|
||||
- **Queue** — who handed what to whom (audit trail of decisions)
|
||||
- **Topology** — which agents touched this; which edges fired
|
||||
|
||||
## Key inspection skills
|
||||
|
||||
When walking a finished slice:
|
||||
|
||||
1. **Read the Story** for context — what was the operator trying to do?
|
||||
2. **Check Progress** — did acceptance actually pass? Any TODO boxes?
|
||||
3. **Open Artifacts → Files** — sample-read 2-3 files; does the code match what the Story claimed?
|
||||
4. **Open Tests → Proof packets** — do screenshots actually show what was claimed?
|
||||
5. **Read Queue** — were there concerning decisions? Hand-offs to escalation?
|
||||
6. **Skim Topology** — were unexpected agents involved?
|
||||
|
||||
Trust comes from repeated successful inspections, not from a single proof packet.
|
||||
`;
|
||||
|
||||
const INSPECT_PROJECT_EVIDENCE_TIMELINE = `# Story — Inspect Project Evidence
|
||||
|
||||
This slice mirrors the previous one (First Conveyor Run). The same agents shipped the same work. But this view is from the inspector's angle, not the runner's.
|
||||
|
||||
## 11:10 — Operator opens project
|
||||
|
||||
Operator (you, in the real world) opens \`/project/slice/first-conveyor-run\` after the slice ships. First read: the Progress tab. All boxes checked.
|
||||
|
||||
## 11:12 — Read Story
|
||||
|
||||
Operator reads the Story narrative. Notices the reviewer's UTF-8 concern + driver's remediation. Confidence-building: there was friction, friction was caught, friction was addressed.
|
||||
|
||||
## 11:15 — Open Artifacts
|
||||
|
||||
Operator clicks Artifacts. Sees 4 commits, 5 files, 1 proof packet, 2 screenshots. Opens commit \`feat(lint): parse + validate todo entries\`. Reads the diff.
|
||||
|
||||
## 11:18 — Read tests
|
||||
|
||||
Operator opens Tests tab. 12/12 passing. Opens the proof packet — screenshot shows CLI output \`Error: malformed entry at line 3\`. Visually verifies the acceptance was met.
|
||||
|
||||
## 11:22 — Skim Queue
|
||||
|
||||
Operator scrolls Queue. 4 qitems in chronological order. Sees the \`concerning\` decision + subsequent \`accept\`. Confidence reinforced.
|
||||
|
||||
## 11:25 — Mark complete
|
||||
|
||||
Operator marks slice acceptance complete. Closes the inspection.
|
||||
|
||||
---
|
||||
|
||||
**What you just read**: a clean inspection in ~15 minutes. Operator builds trust through the evidence trail. This is the OpenRig usage pattern at scale: declare → route → inspect.
|
||||
`;
|
||||
|
||||
const INSPECT_PROJECT_EVIDENCE_PROGRESS = `---
|
||||
title: Inspect Project Evidence Progress
|
||||
status: active
|
||||
mission: getting-started
|
||||
rail-item: getting-started
|
||||
slice: inspect-project-evidence
|
||||
---
|
||||
|
||||
# Progress — Inspect Project Evidence
|
||||
|
||||
## Inspection checklist
|
||||
|
||||
- [x] Read Story (context: what was being done)
|
||||
- [x] Check Progress (acceptance status)
|
||||
- [x] Sample-read Artifacts files
|
||||
- [x] Verify Tests proof packets visually
|
||||
- [x] Skim Queue for concerning decisions
|
||||
- [x] Skim Topology for unexpected agents
|
||||
|
||||
## Status: COMPLETE
|
||||
|
||||
This was a mocked inspection — a worked example of the inspection workflow.
|
||||
|
||||
## How to apply this
|
||||
|
||||
For any real slice that ships in YOUR project: open the slice page, walk the tabs in this order, check each box mentally. Over time you'll develop instincts for what passes the smell test vs what needs deeper investigation.
|
||||
`;
|
||||
|
||||
const GETTING_STARTED_NARRATIVE: Record<
|
||||
string,
|
||||
{ readme: string; timeline: string; progress: string }
|
||||
> = {
|
||||
"first-conveyor-run": {
|
||||
readme: FIRST_CONVEYOR_RUN_README,
|
||||
timeline: FIRST_CONVEYOR_RUN_TIMELINE,
|
||||
progress: FIRST_CONVEYOR_RUN_PROGRESS,
|
||||
},
|
||||
"inspect-project-evidence": {
|
||||
readme: INSPECT_PROJECT_EVIDENCE_README,
|
||||
timeline: INSPECT_PROJECT_EVIDENCE_TIMELINE,
|
||||
progress: INSPECT_PROJECT_EVIDENCE_PROGRESS,
|
||||
},
|
||||
};
|
||||
|
||||
function sliceReadme(mission: DefaultMission, slice: DefaultSlice): string {
|
||||
// V0.3.1 slice 21: getting-started slices ship rich narrative.
|
||||
const narrative = GETTING_STARTED_NARRATIVE[slice.id];
|
||||
if (narrative) {
|
||||
return `---
|
||||
id: ${slice.dotId}
|
||||
title: ${slice.title}
|
||||
status: ${slice.status}
|
||||
mission: ${mission.id}
|
||||
rail-item: ${mission.id}
|
||||
slice: ${slice.id}
|
||||
---
|
||||
|
||||
${narrative.readme}`;
|
||||
}
|
||||
return `---
|
||||
id: ${slice.dotId}
|
||||
title: ${slice.title}
|
||||
status: ${slice.status}
|
||||
mission: ${mission.id}
|
||||
rail-item: ${mission.id}
|
||||
slice: ${slice.id}
|
||||
---
|
||||
|
||||
# ${slice.title}
|
||||
|
||||
## Intent
|
||||
|
||||
${slice.objective}
|
||||
|
||||
## Mini-requirements
|
||||
|
||||
1. The work is visible in the Project slice and its queue items link back to \`${slice.id}\`.
|
||||
|
||||
## Proof contract
|
||||
|
||||
- [ ] The work is visible in the Project slice — captured (see IMPLEMENTATION-PRD.md).
|
||||
|
||||
## Queue Mapping
|
||||
|
||||
Queue items for this slice should mention or tag:
|
||||
|
||||
- mission: \`${mission.id}\`
|
||||
- slice: \`${slice.id}\`
|
||||
|
||||
This lets Project attach queue activity to the slice story, queue, tests, and topology tabs.
|
||||
`;
|
||||
}
|
||||
|
||||
function sliceTimeline(mission: DefaultMission, slice: DefaultSlice): string | null {
|
||||
const narrative = GETTING_STARTED_NARRATIVE[slice.id];
|
||||
return narrative ? narrative.timeline : null;
|
||||
}
|
||||
|
||||
function sliceProgress(mission: DefaultMission, slice: DefaultSlice): string {
|
||||
const narrative = GETTING_STARTED_NARRATIVE[slice.id];
|
||||
if (narrative) return narrative.progress;
|
||||
return `---
|
||||
title: ${slice.title} Progress
|
||||
status: ${slice.status}
|
||||
mission: ${mission.id}
|
||||
rail-item: ${mission.id}
|
||||
slice: ${slice.id}
|
||||
---
|
||||
|
||||
# ${slice.title} Progress
|
||||
|
||||
- [ ] Define the next concrete packet.
|
||||
- [ ] Attach queue work to this slice id.
|
||||
- [ ] Capture proof or notes before closing.
|
||||
`;
|
||||
}
|
||||
|
||||
function slicePrd(mission: DefaultMission, slice: DefaultSlice): string {
|
||||
return `---
|
||||
title: ${slice.title} Implementation Notes
|
||||
status: ${slice.status}
|
||||
mission: ${mission.id}
|
||||
rail-item: ${mission.id}
|
||||
slice: ${slice.id}
|
||||
---
|
||||
|
||||
# ${slice.title} Implementation Notes
|
||||
|
||||
## Intent
|
||||
|
||||
${slice.objective}
|
||||
|
||||
## Mini-requirements
|
||||
|
||||
1. The work is visible in the Project slice and its queue items link back to \`${slice.id}\`.
|
||||
|
||||
## Proof contract
|
||||
|
||||
- [ ] The work is visible in the Project slice — captured.
|
||||
- [ ] Queue items include enough body or tag context to link back to \`${slice.id}\`.
|
||||
- [ ] Proof artifacts are referenced from the slice before closure (drop via \`rig proof add\`).
|
||||
|
||||
> Conventions SSOT: \`docs/reference/sdlc-conventions.md (installed: $OPENRIG_HOME/reference/sdlc-conventions.md)\` — section names, proof-contract format, the two locks, C1 headers. For a small slice the mini-requirements may BE the whole PRD.
|
||||
`;
|
||||
}
|
||||
|
||||
export function workspaceScaffoldDirs(): string[] {
|
||||
return [...WORKSPACE_DIRS];
|
||||
}
|
||||
|
||||
export function workspaceScaffoldFiles(): Array<{ relPath: string; content: string }> {
|
||||
const files: Array<{ relPath: string; content: string }> = [
|
||||
{ relPath: "README.md", content: WORKSPACE_README },
|
||||
{ relPath: "STEERING.md", content: STEERING_PLACEHOLDER },
|
||||
{ relPath: "missions/README.md", content: subdirReadmeContent("missions") },
|
||||
{ relPath: "artifacts/README.md", content: subdirReadmeContent("artifacts") },
|
||||
{ relPath: "evidence/README.md", content: subdirReadmeContent("evidence") },
|
||||
{ relPath: "progress/README.md", content: subdirReadmeContent("progress") },
|
||||
{ relPath: "field-notes/README.md", content: subdirReadmeContent("field-notes") },
|
||||
{ relPath: "specs/README.md", content: subdirReadmeContent("specs") },
|
||||
{ relPath: "dogfood-evidence/README.md", content: subdirReadmeContent("dogfood-evidence") },
|
||||
];
|
||||
for (const mission of DEFAULT_MISSIONS) {
|
||||
files.push(
|
||||
{ relPath: `missions/${mission.id}/README.md`, content: missionReadme(mission) },
|
||||
{ relPath: `missions/${mission.id}/PROGRESS.md`, content: missionProgress(mission) },
|
||||
{ relPath: `missions/${mission.id}/MISSION_BRIEF.md`, content: renderMissionBriefTemplate(mission.title) },
|
||||
// FR-5e A1 — reuse the FR-3 MISSION_NOTES scaffold helper so a
|
||||
// fresh `rig config init-workspace` produces a getting-started
|
||||
// mission that doctor check #7 (mission_notes_presence) marks
|
||||
// ok. Same env-var-pivot (OPENRIG_MISSION_NOTES_TEMPLATE_PATH >
|
||||
// built-in bundled template) FR-3's `rig scope mission create`
|
||||
// honors.
|
||||
{
|
||||
relPath: `missions/${mission.id}/MISSION_NOTES.md`,
|
||||
content: renderMissionNotesTemplate({
|
||||
mission_id: mission.dotId,
|
||||
mission_name: mission.title,
|
||||
created_date: new Date().toISOString().slice(0, 10),
|
||||
}).rendered,
|
||||
},
|
||||
);
|
||||
for (const slice of mission.slices) {
|
||||
files.push(
|
||||
{ relPath: `missions/${mission.id}/slices/${slice.id}/README.md`, content: sliceReadme(mission, slice) },
|
||||
{ relPath: `missions/${mission.id}/slices/${slice.id}/PROGRESS.md`, content: sliceProgress(mission, slice) },
|
||||
{ relPath: `missions/${mission.id}/slices/${slice.id}/PROOF.md`, content: renderSliceProofTemplate({ id: slice.dotId, title: slice.title }) },
|
||||
{ relPath: `missions/${mission.id}/slices/${slice.id}/IMPLEMENTATION-PRD.md`, content: slicePrd(mission, slice) },
|
||||
);
|
||||
// V0.3.1 slice 21: getting-started slices ship timeline.md.
|
||||
const timeline = sliceTimeline(mission, slice);
|
||||
if (timeline) {
|
||||
files.push({
|
||||
relPath: `missions/${mission.id}/slices/${slice.id}/timeline.md`,
|
||||
content: timeline,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
export function initWorkspaceCommand(configPath?: string): Command {
|
||||
const cmd = new Command("init-workspace")
|
||||
.description("Scaffold the default workspace at ~/.openrig/workspace/ with mission/slice folders")
|
||||
.description("Scaffold a repo-ready project workspace")
|
||||
.option("--root <path>", "Override workspace root (default: workspace.root setting)")
|
||||
.option("--force", "Overwrite existing scaffolded files (does NOT remove directories)")
|
||||
.option("--force", "Deprecated compatibility flag; existing files are still preserved")
|
||||
.option("--dry-run", "Show what would be created without writing")
|
||||
.option("--json", "JSON output")
|
||||
.action((opts: InitWorkspaceOpts) => {
|
||||
@@ -614,8 +40,8 @@ export function initWorkspaceCommand(configPath?: string): Command {
|
||||
for (const sub of result.subdirs) {
|
||||
console.log(` ${sub.created ? "+" : " "} ${sub.name}/`);
|
||||
}
|
||||
for (const f of result.files) {
|
||||
console.log(` ${f.created ? "+" : " "} ${f.relPath}${f.skipped ? ` (skipped: ${f.skipped})` : ""}`);
|
||||
for (const file of result.files) {
|
||||
console.log(` ${file.created ? "+" : " "} ${file.relPath}${file.skipped ? ` (skipped: ${file.skipped})` : ""}`);
|
||||
}
|
||||
if (result.dryRun) console.log("(dry-run; no files written.)");
|
||||
}
|
||||
@@ -630,48 +56,12 @@ export function initWorkspaceCommand(configPath?: string): Command {
|
||||
export function runInitWorkspace(opts: InitWorkspaceOpts & { configPath?: string }): InitWorkspaceResult {
|
||||
const store = new ConfigStore(opts.configPath);
|
||||
const root = opts.root ?? (store.get("workspace.root") as string);
|
||||
const dryRun = !!opts.dryRun;
|
||||
const force = !!opts.force;
|
||||
|
||||
// FR-5e BLOCKER-1 — precompute the full scaffold file list BEFORE
|
||||
// any filesystem mutation. workspaceScaffoldFiles() now invokes
|
||||
// renderMissionNotesTemplate(), which throws on invalid
|
||||
// OPENRIG_MISSION_NOTES_TEMPLATE_PATH. If we mkdir first then
|
||||
// throw on render, the operator is left with a half-created
|
||||
// workspace that the next `rig workspace doctor` will misread as
|
||||
// partially-initialized. Verify-first-then-write (banked from
|
||||
// FR-3 self-lesson) — fail before any side effect.
|
||||
const scaffoldFiles = workspaceScaffoldFiles();
|
||||
const scaffoldDirs = workspaceScaffoldDirs();
|
||||
|
||||
const rootExists = existsSync(root);
|
||||
const result: InitWorkspaceResult = {
|
||||
root,
|
||||
rootCreated: !rootExists,
|
||||
subdirs: [],
|
||||
files: [],
|
||||
dryRun,
|
||||
};
|
||||
|
||||
if (!rootExists && !dryRun) mkdirSync(root, { recursive: true });
|
||||
|
||||
for (const sub of scaffoldDirs) {
|
||||
const subPath = join(root, sub);
|
||||
const subExists = existsSync(subPath);
|
||||
if (!subExists && !dryRun) mkdirSync(subPath, { recursive: true });
|
||||
result.subdirs.push({ name: sub, path: subPath, created: !subExists });
|
||||
const result = ensureDefaultWorkspace({ root, dryRun: !!opts.dryRun });
|
||||
if (!result.ok) {
|
||||
const detail = result.conflicts
|
||||
.map((conflict) => `${conflict.path}: expected ${conflict.expected}, found ${conflict.actual}`)
|
||||
.join("; ");
|
||||
throw new Error(`Workspace initialization blocked: ${detail}`);
|
||||
}
|
||||
|
||||
for (const file of scaffoldFiles) {
|
||||
const absPath = join(root, file.relPath);
|
||||
const exists = existsSync(absPath);
|
||||
if (exists && !force) {
|
||||
result.files.push({ relPath: file.relPath, absPath, created: false, skipped: "exists" });
|
||||
} else {
|
||||
if (!dryRun) writeFileSync(absPath, file.content, "utf-8");
|
||||
result.files.push({ relPath: file.relPath, absPath, created: true, skipped: null });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -54,7 +54,7 @@ Examples:
|
||||
rig config set workspace.slices_root /path # configure a workspace path
|
||||
rig config reset # delete config file, revert all to defaults
|
||||
rig config reset workspace.slices_root # clear one key, revert to default
|
||||
rig config init-workspace # scaffold ~/.openrig/workspace/ with missions + slices
|
||||
rig config init-workspace # scaffold a repo-ready project workspace
|
||||
|
||||
Keys:
|
||||
daemon.* port, host
|
||||
@@ -66,12 +66,17 @@ Keys:
|
||||
scrollback. OpenRig launches Claude with
|
||||
CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1 by default; set
|
||||
OPENRIG_CLAUDE_DISABLE_ALTERNATE_SCREEN=0 to opt back into fullscreen.)
|
||||
workspace.* root, slices_root, steering_path, field_notes_root,
|
||||
specs_root, dogfood_evidence_root, operator_seat_name
|
||||
workspace.* root, slices_root, steering_path, specs_root,
|
||||
projects_root, catalog_path, operator_seat_name
|
||||
topology.root the topology tree root (instance altitude at its top; default $OPENRIG_HOME/topology)
|
||||
context.packs_root context-pack library landing zone for 'rig context add' (default $OPENRIG_HOME/context-packs)
|
||||
context.root addressable context library for 'rig context add' (default $OPENRIG_HOME/context)
|
||||
context.system_world default System World, replacement manifest path, or disabled
|
||||
skills.root versioned skill catalog (default $OPENRIG_HOME/skills)
|
||||
onboarding.default_pack.enabled deliver the two-part fresh-seat mental-model pack (default on)
|
||||
health.context_pressure.* context-pressure warning/critical percentages (defaults 95/99)
|
||||
files.allowlist name:/abs/path,name:/abs/path
|
||||
progress.scan_roots name:/abs/path,name:/abs/path
|
||||
ui.timezone TUI IANA timezone (default America/Los_Angeles; reopen TUI after changing)
|
||||
ui.preview.* refresh_interval_seconds, max_pins, default_lines
|
||||
recovery.* auto_drive_provider_prompts, provider_auth_env_allowlist
|
||||
agents.* advisor_session, operator_session
|
||||
@@ -84,6 +89,10 @@ Keys:
|
||||
policies.idle_gate_qitem.scan_interval_seconds
|
||||
policies.idle_gate_qitem.active_wake_interval_seconds
|
||||
snapshots.periodic.* enabled, interval_seconds, retention_keep
|
||||
queue.* pickup_stall_threshold_minutes (S04 pickup-receipt stall threshold),
|
||||
stuck_sweep_interval_seconds, stuck_sweep_unclaimed_age_minutes (S02 standing stuck sweep),
|
||||
wake_retry_interval_seconds, wake_retry_cap, wake_unconfirmed_window_minutes,
|
||||
wake_swap_grace_seconds (S01 wake-or-escalate ladder)
|
||||
retention.* enabled, transitions_days, watchdog_days,
|
||||
watchdog_keep_per_job, batch_size
|
||||
terminal.status_bar show the inner tmux status bar on launch (default off)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
// Each delegates to /api/context-packs/library/* against the daemon.
|
||||
// The `add` verb installs a pack from a directory at
|
||||
// ~/.openrig/context-packs/<name>/ — host-symlink-free contract,
|
||||
// $OPENRIG_HOME/context/<name>/ — host-symlink-free contract,
|
||||
// matches `rig specs add` shape (regular files only; no symlinks).
|
||||
|
||||
import { Command } from "commander";
|
||||
@@ -17,27 +17,40 @@ import {
|
||||
lstatSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
renameSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
import { basename, extname, isAbsolute, join, resolve } from "node:path";
|
||||
import { basename, join, resolve } from "node:path";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { assertSafeInstallRef, assertTreeHasNoSymlinks, assertDestinationNamespaceContained, validateContextPackManifestForInstall } from "../lib/context-install.js";
|
||||
import { addGitContext, inspectGitContext, updateGitContext } from "../lib/context-git.js";
|
||||
import { ConfigStore } from "../config-store.js";
|
||||
import { DaemonClient } from "../client.js";
|
||||
import { enumArg } from "../cli-error.js";
|
||||
import { getDaemonStatus, getDaemonUrl , statusGuardMessage} from "../daemon-lifecycle.js";
|
||||
import { resolveWorkPosition, type WorkInstallPlan } from "../lib/work-install.js";
|
||||
import {
|
||||
reconcileSkillLoadout,
|
||||
resolveSkillLoadout,
|
||||
type ReconcileSkillLoadoutResult,
|
||||
type SkillLoadout,
|
||||
} from "@openrig/daemon/skill-loadout";
|
||||
import { realDeps } from "./daemon.js";
|
||||
import type { StatusDeps } from "./status.js";
|
||||
|
||||
const contextRuntimeArg = enumArg(["claude-code", "claude", "codex"]);
|
||||
|
||||
interface ContextPackEntryWire {
|
||||
id: string;
|
||||
kind: "context-pack";
|
||||
name: string;
|
||||
version: string;
|
||||
purpose: string | null;
|
||||
/** OPR.0.5.6.10 — pack-level classification from the daemon's ATOM_TAXONOMIES enum. */
|
||||
taxonomy: string;
|
||||
sourceType: "builtin" | "user_file" | "workspace";
|
||||
sourcePath: string;
|
||||
relativePath: string;
|
||||
@@ -54,6 +67,26 @@ interface ContextPackEntryWire {
|
||||
}>;
|
||||
}
|
||||
|
||||
function selectedIds(ids: string[], none: string): string {
|
||||
return ids.length > 0 ? ids.join(", ") : none;
|
||||
}
|
||||
|
||||
function printWorkInstallSelectors(result: WorkInstallPlan, topologySkills: string[]): void {
|
||||
const world = result.systemWorld;
|
||||
const identity = world.id ? ` ${world.id}@${world.version}` : "";
|
||||
const path = world.manifestPath ? ` ${world.manifestPath}` : "";
|
||||
console.log(`system ${world.state} [${world.source}]${identity}${path}`);
|
||||
for (const selection of world.context) {
|
||||
const profiles = selection.profiles
|
||||
? ` (${Object.entries(selection.profiles).map(([runtime, profile]) => `${runtime}=${profile}`).join(", ")})`
|
||||
: "";
|
||||
console.log(`context system ${selection.ref}${profiles}`);
|
||||
}
|
||||
console.log(`skills system=${selectedIds(world.skills, "(none)")}`);
|
||||
console.log(`skills topology=${selectedIds(topologySkills, "(none)")}`);
|
||||
console.log(`skills project=${selectedIds(result.skills, "(none)")}`);
|
||||
}
|
||||
|
||||
interface PreviewWire {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -65,125 +98,17 @@ interface PreviewWire {
|
||||
missingFiles: Array<{ path: string; role: string }>;
|
||||
}
|
||||
|
||||
// Slice-03 Atom 2 — mirror the daemon's per-segment ref contract at the
|
||||
// local install boundary. This must run before creating the context store.
|
||||
const SAFE_REF_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
||||
|
||||
// Slice-03 lineage repair (R2 HIGH-2): the install boundary mirrors the daemon
|
||||
// bounded, delimiter-free version token (ref-safety.SAFE_VERSION) so an unsafe
|
||||
// version is rejected BEFORE any local write — matching the per-segment ref
|
||||
// mirror above.
|
||||
const SAFE_INSTALL_VERSION = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,31}$/;
|
||||
|
||||
function assertSafeInstallRef(ref: string): void {
|
||||
const safe =
|
||||
ref.length > 0 &&
|
||||
ref.split("/").every(
|
||||
(segment) => segment.length > 0 && segment !== "." && segment !== ".." && SAFE_REF_SEGMENT.test(segment),
|
||||
);
|
||||
if (!safe) {
|
||||
throw new Error(
|
||||
`unsafe install ref '${ref}' — a ref must be one or more '/'-separated segments, each matching ` +
|
||||
`[A-Za-z0-9][A-Za-z0-9._-]{0,63} (no '.'/'..', no absolute path, no empty segment, no ` +
|
||||
`whitespace or injection), so packs stay inside the context store root.`,
|
||||
);
|
||||
function assertSafeTopologySegment(kind: "rig" | "seat", value: string): void {
|
||||
if (value === "." || value === ".." || !SAFE_REF_SEGMENT.test(value)) {
|
||||
throw new Error(`unsafe ${kind} segment '${value}' — topology addresses require one bounded path segment`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertTreeHasNoSymlinks(root: string): void {
|
||||
const stack = [root];
|
||||
while (stack.length > 0) {
|
||||
const current = stack.pop()!;
|
||||
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
||||
const absPath = join(current, entry.name);
|
||||
if (entry.isSymbolicLink()) {
|
||||
throw new Error(`Context pack directories must not contain symlinks: ${absPath}`);
|
||||
}
|
||||
if (entry.isDirectory()) stack.push(absPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Slice-03 lineage repair (R2 HIGH-1): a lexically-safe path-like install ref
|
||||
// can still escape the store if one of its parent namespace segments is a
|
||||
// symlink (or a non-directory). Walk every ANCESTOR segment under the store root
|
||||
// and reject before the copy — the FS-canonical containment the lexical ref
|
||||
// check alone cannot give, mirroring the daemon compose namespace walk
|
||||
// (context-pack-library-service.ts). A not-yet-created segment (ENOENT) is safe:
|
||||
// cpSync will materialize it as a real directory.
|
||||
function assertDestinationNamespaceContained(targetRoot: string, installName: string): void {
|
||||
const segments = installName.split("/");
|
||||
let cursor = targetRoot;
|
||||
for (const segment of segments.slice(0, -1)) {
|
||||
cursor = join(cursor, segment);
|
||||
let stat: ReturnType<typeof lstatSync>;
|
||||
try {
|
||||
stat = lstatSync(cursor);
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException).code === "ENOENT") break;
|
||||
throw err;
|
||||
}
|
||||
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
||||
throw new Error(
|
||||
`unsafe install ref '${installName}' — its namespace segment '${cursor}' is a symlink or non-directory, ` +
|
||||
`so the copy would escape the context store root. Remove it or install under a different --name.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kept in lockstep with the daemon parser's ALLOWED_FILE_SUFFIXES (manifest-parser.ts).
|
||||
// OPR.0.5.3.7 R2 added .sh/.ts (skill helper assets, served as text); the install
|
||||
// validator must accept what the daemon will serve.
|
||||
const ALLOWED_CONTEXT_PACK_SUFFIXES = new Set([".md", ".markdown", ".yaml", ".yml", ".txt", ".sh", ".ts"]);
|
||||
|
||||
function validateContextPackManifestForInstall(manifestPath: string): void {
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = parseYaml(readFileSync(manifestPath, "utf-8"));
|
||||
} catch (err) {
|
||||
throw new Error(`manifest at ${manifestPath} is not valid YAML: ${(err as Error).message}`);
|
||||
}
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
throw new Error(`manifest at ${manifestPath} must be a YAML object at the root`);
|
||||
}
|
||||
const obj = parsed as Record<string, unknown>;
|
||||
if (typeof obj["name"] !== "string" || obj["name"].length === 0) {
|
||||
throw new Error(`manifest at ${manifestPath} is missing required field 'name' (string)`);
|
||||
}
|
||||
if (obj["version"] === undefined || obj["version"] === null) {
|
||||
throw new Error(`manifest at ${manifestPath} is missing required field 'version'`);
|
||||
}
|
||||
const versionStr = String(obj["version"]);
|
||||
if (!SAFE_INSTALL_VERSION.test(versionStr)) {
|
||||
throw new Error(
|
||||
`manifest at ${manifestPath} has an invalid version '${versionStr}' — a version must be a single bounded ` +
|
||||
`token [A-Za-z0-9][A-Za-z0-9._+-]{0,31} (no ':' or separator, no whitespace, ≤32 chars).`,
|
||||
);
|
||||
}
|
||||
const files = obj["files"];
|
||||
if (!Array.isArray(files)) {
|
||||
throw new Error(`manifest at ${manifestPath} must declare 'files: [...]'`);
|
||||
}
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const entry = files[i];
|
||||
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
||||
throw new Error(`manifest at ${manifestPath} has malformed entry at files[${i}]`);
|
||||
}
|
||||
const file = entry as Record<string, unknown>;
|
||||
const relPath = file["path"];
|
||||
if (typeof relPath !== "string" || relPath.length === 0) {
|
||||
throw new Error(`manifest at ${manifestPath} files[${i}] missing 'path' (string)`);
|
||||
}
|
||||
if (relPath.includes("..") || isAbsolute(relPath) || relPath.startsWith("\\")) {
|
||||
throw new Error(`manifest at ${manifestPath} files[${i}].path '${relPath}' must be a relative path inside the pack (no '..' segments, no leading '/')`);
|
||||
}
|
||||
if (!ALLOWED_CONTEXT_PACK_SUFFIXES.has(extname(relPath))) {
|
||||
throw new Error(`manifest at ${manifestPath} files[${i}].path '${relPath}' has an unsupported suffix; allowed: ${Array.from(ALLOWED_CONTEXT_PACK_SUFFIXES).join(", ")}`);
|
||||
}
|
||||
if (typeof file["role"] !== "string" || file["role"].length === 0) {
|
||||
throw new Error(`manifest at ${manifestPath} files[${i}] missing 'role' (string)`);
|
||||
}
|
||||
function assertLocalGitClient(client: DaemonClient): void {
|
||||
if (!["127.0.0.1", "localhost", "[::1]"].includes(new URL(client.baseUrl).hostname)) {
|
||||
throw new Error("Run Git source selection/inspection/update on the daemon host through its loopback URL; these commands use local Git and filesystem paths.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +235,10 @@ Examples:
|
||||
rig context add ./my-pack
|
||||
rig context rm packs/compaction-restore
|
||||
rig context sync
|
||||
rig context profile world-public --situation fresh --runtime claude-code
|
||||
rig context work-install --runtime claude-code
|
||||
rig context trace --rig product-team --seat orch1-lead --name LEARNED.md
|
||||
rig context trace --rig product-team --pod delivery --seat dev1-qa --name LEARNED.md
|
||||
`);
|
||||
|
||||
const getDeps = (): StatusDeps => depsOverride ?? {
|
||||
@@ -318,6 +246,120 @@ Examples:
|
||||
clientFactory: (url: string) => new DaemonClient(url),
|
||||
};
|
||||
|
||||
cmd.command("work-install")
|
||||
.description("Resolve System World plus project work context and the managed skill loadout")
|
||||
.option("--project <id>", "Exact project id from workspace.yaml")
|
||||
.option("--mission <id>", "Exact mission id under the selected project")
|
||||
.option("--slice <id>", "Exact slice id under the selected mission")
|
||||
.option("--deliver", "Include the exact content of each extant planned file")
|
||||
.option("--runtime <runtime>", "Inspect skills for claude-code (alias: claude) or codex", contextRuntimeArg)
|
||||
.option("--cwd <path>", "Agent working directory that receives skill projections (default: current directory)")
|
||||
.option("--topology <ids>", "Comma-separated topology/profile skill identities")
|
||||
.option("--apply-skills", "Reconcile selected skills into the runtime harness directory")
|
||||
.option("--json", "JSON output")
|
||||
.action((opts: { project?: string; mission?: string; slice?: string; deliver?: boolean; runtime?: string; cwd?: string; topology?: string; applySkills?: boolean; json?: boolean }) => {
|
||||
if (opts.applySkills && opts.runtime === undefined) {
|
||||
console.error("invalid_runtime: --apply-skills requires --runtime claude-code (alias: claude) or codex");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const store = new ConfigStore();
|
||||
const workspaceRoot = String(store.resolveWithSource("workspace.root").value);
|
||||
const catalogPath = String(store.resolveWithSource("workspace.catalog_path").value);
|
||||
const contextRoot = String(store.resolveWithSource("context.root").value);
|
||||
const systemWorldSetting = store.resolveWithSource("context.system_world");
|
||||
const result = resolveWorkPosition({
|
||||
workspaceRoot,
|
||||
catalogPath,
|
||||
contextRoot,
|
||||
systemWorldSelection: String(systemWorldSetting.value),
|
||||
systemWorldSource: systemWorldSetting.source,
|
||||
...(opts.project !== undefined ? { project: opts.project } : {}),
|
||||
...(opts.mission !== undefined ? { mission: opts.mission } : {}),
|
||||
...(opts.slice !== undefined ? { slice: opts.slice } : {}),
|
||||
});
|
||||
if ("error" in result) {
|
||||
if (opts.json) console.log(JSON.stringify({ ok: false, ...result }));
|
||||
else console.error(`${result.error.code}: ${result.error.message}`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
let skillLoadout: SkillLoadout | undefined;
|
||||
let skillProjection: ReconcileSkillLoadoutResult | undefined;
|
||||
if (opts.runtime) {
|
||||
const topologySkills = (opts.topology ?? "").split(",").map((id) => id.trim()).filter(Boolean);
|
||||
const resolvedSkills = resolveSkillLoadout({
|
||||
catalogRoot: String(store.resolveWithSource("skills.root").value),
|
||||
systemSkills: result.systemWorld.skills,
|
||||
topologySkills,
|
||||
projectRoot: result.position.projectRoot,
|
||||
projectSkills: result.skills,
|
||||
});
|
||||
if (!resolvedSkills.ok) {
|
||||
if (opts.json) console.log(JSON.stringify({ ok: false, errors: resolvedSkills.errors }, null, 2));
|
||||
else for (const error of resolvedSkills.errors) console.error(`${error.code}: ${error.message}`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
skillLoadout = resolvedSkills.loadout;
|
||||
skillProjection = reconcileSkillLoadout({
|
||||
loadout: skillLoadout,
|
||||
runtime: opts.runtime === "codex" ? "codex" : "claude-code",
|
||||
cwd: resolve(opts.cwd ?? process.cwd()),
|
||||
apply: opts.applySkills === true,
|
||||
});
|
||||
if (!skillProjection.ok) process.exitCode = 1;
|
||||
}
|
||||
if (opts.json) {
|
||||
const output = opts.deliver
|
||||
? {
|
||||
...result,
|
||||
pieces: result.pieces.map((piece) => piece.exists
|
||||
? { ...piece, content: readFileSync(piece.path, "utf8") }
|
||||
: piece),
|
||||
...(skillLoadout ? { skillLoadout, skillProjection } : {}),
|
||||
}
|
||||
: { ...result, ...(skillLoadout ? { skillLoadout, skillProjection } : {}) };
|
||||
console.log(JSON.stringify(output, null, 2));
|
||||
return;
|
||||
}
|
||||
if (opts.deliver) {
|
||||
for (const planned of result.pieces) {
|
||||
if (!planned.exists) {
|
||||
console.log(`=== ${planned.address} (absent: ${planned.path}) ===`);
|
||||
continue;
|
||||
}
|
||||
console.log(`=== ${planned.altitude} ${planned.address} ===`);
|
||||
console.log(readFileSync(planned.path, "utf8"));
|
||||
}
|
||||
printWorkInstallSelectors(result, (opts.topology ?? "").split(",").map((id) => id.trim()).filter(Boolean));
|
||||
if (skillProjection) {
|
||||
for (const receipt of skillProjection.receipts) {
|
||||
console.log(`${receipt.status.padEnd(7)} ${receipt.id} [${receipt.selectedBy.join("+")}] ${receipt.target}`);
|
||||
}
|
||||
for (const id of skillProjection.removed) console.log(`removed ${id}`);
|
||||
if (skillProjection.freshLaunchRequired) console.log("skills fresh seat process required to observe changed ambient skills");
|
||||
if (!opts.applySkills) console.log("skills read-only; add --apply-skills to reconcile");
|
||||
}
|
||||
for (const warning of result.warnings) console.error(`Warning: ${warning}`);
|
||||
return;
|
||||
}
|
||||
console.log(`project ${result.position.projectId ?? "(unmanifested)"}: ${result.position.projectRoot}`);
|
||||
printWorkInstallSelectors(result, (opts.topology ?? "").split(",").map((id) => id.trim()).filter(Boolean));
|
||||
for (const planned of result.pieces) {
|
||||
console.log(`${planned.altitude.padEnd(7)} ${planned.address} [${planned.source}] ${planned.exists ? planned.path : `(absent: ${planned.path})`}`);
|
||||
}
|
||||
if (skillProjection) {
|
||||
for (const receipt of skillProjection.receipts) {
|
||||
console.log(`${receipt.status.padEnd(7)} ${receipt.id} [${receipt.selectedBy.join("+")}] ${receipt.target}`);
|
||||
}
|
||||
for (const id of skillProjection.removed) console.log(`removed ${id}`);
|
||||
if (skillProjection.freshLaunchRequired) console.log("skills fresh seat process required to observe changed ambient skills");
|
||||
if (!opts.applySkills) console.log("skills read-only; add --apply-skills to reconcile");
|
||||
}
|
||||
for (const warning of result.warnings) console.error(`Warning: ${warning}`);
|
||||
});
|
||||
|
||||
async function getClient(): Promise<DaemonClient> {
|
||||
const deps = getDeps();
|
||||
const status = await getDaemonStatus(deps.lifecycleDeps);
|
||||
@@ -332,12 +374,13 @@ Examples:
|
||||
// design: the walk is a config read + filesystem reads, so it works on a
|
||||
// box whose daemon is down (orientation is exactly when that happens).
|
||||
cmd.command("trace")
|
||||
.description("Walk the topology tree for one chain filename (instance -> rig -> seat), keyed off topology.root")
|
||||
.description("Walk the topology tree for one chain filename (instance -> rig -> optional pod -> optional seat), keyed off topology.root")
|
||||
.requiredOption("--rig <rig>", "Rig name (the rigs/<rig> altitude)")
|
||||
.option("--pod <pod>", "Pod id (the pods/<pod> altitude); omit when no pod context is selected")
|
||||
.option("--seat <seat>", "Seat id (the seats/<seat> altitude); omit for a rig-level trace")
|
||||
.requiredOption("--name <file>", "Chain filename, identical at every altitude (e.g. LEARNED.md, CULTURE.md)")
|
||||
.option("--json", "JSON output for agents")
|
||||
.action(async (opts: { rig: string; seat?: string; name: string; json?: boolean }) => {
|
||||
.action(async (opts: { rig: string; pod?: string; seat?: string; name: string; json?: boolean }) => {
|
||||
const { ConfigStore } = await import("../config-store.js");
|
||||
const { traceTopologyChain } = await import("../lib/topology-trace.js");
|
||||
const store = new ConfigStore();
|
||||
@@ -348,6 +391,7 @@ Examples:
|
||||
topologyRoot: String(resolved.value),
|
||||
name: opts.name,
|
||||
rig: opts.rig,
|
||||
pod: opts.pod ?? null,
|
||||
seat: opts.seat ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -428,11 +472,11 @@ Examples:
|
||||
return;
|
||||
}
|
||||
if (entries.length === 0) {
|
||||
console.log("No context packs in library. Author one at ~/.openrig/context-packs/<name>/ then run: rig context sync");
|
||||
console.log("No context packs in library. Author one under `rig config get context.root`, then run: rig context sync");
|
||||
return;
|
||||
}
|
||||
for (const e of entries) {
|
||||
console.log(`${e.relativePath.padEnd(36)} ${e.name.padEnd(24)} v${String(e.version).padEnd(6)} ${String(e.files.length).padStart(2)} files ~${String(e.derivedEstimatedTokens).padStart(6)} tokens ${e.sourceType} ${e.sourcePath}`);
|
||||
console.log(`${e.relativePath.padEnd(36)} ${e.name.padEnd(24)} v${String(e.version).padEnd(6)} ${(e.taxonomy ?? "—").padEnd(8)} ${String(e.files.length).padStart(2)} files ~${String(e.derivedEstimatedTokens).padStart(6)} tokens ${e.sourceType} ${e.sourcePath}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error((err as Error).message);
|
||||
@@ -519,11 +563,10 @@ Examples:
|
||||
.action(async (nameOrRef: string, opts: { json?: boolean }) => {
|
||||
try {
|
||||
const client = await getClient();
|
||||
// OPR.0.5.3.5 Atom 4c (Q4: one `name#H2/H3` form across the verb
|
||||
// family): an address routes to the daemon's single resolver home;
|
||||
// stdout is exactly the resolved span bytes so an agent consumes the
|
||||
// addressed section and nothing else. Fail-loud passthrough — the
|
||||
// daemon names every failure; this verb adds nothing to it.
|
||||
// OPR.0.5.3.5 Atom 4c: fragment addresses route directly to the
|
||||
// daemon's resolver. A bare slash-containing value may be either a
|
||||
// pack ref or a whole-file address, so exact-pack lookup wins and only
|
||||
// its 404 falls through to the same resolver.
|
||||
if (nameOrRef.includes("#")) {
|
||||
const res = await client.get<{ text?: string; message?: string; error?: string }>(
|
||||
`/api/context-packs/library/resolve-address?address=${encodeURIComponent(nameOrRef)}`,
|
||||
@@ -535,7 +578,28 @@ Examples:
|
||||
else console.log(res.data.text);
|
||||
return;
|
||||
}
|
||||
const entry = await resolvePack(client, nameOrRef);
|
||||
let entry: ContextPackEntryWire;
|
||||
if (nameOrRef.includes("/")) {
|
||||
const exact = await client.get<ContextPackEntryWire & { error?: string; message?: string }>(
|
||||
`/api/context-packs/library/by-ref?ref=${encodeURIComponent(nameOrRef)}`,
|
||||
);
|
||||
if (exact.status === 404) {
|
||||
const res = await client.get<{ text?: string; message?: string; error?: string }>(
|
||||
`/api/context-packs/library/resolve-address?address=${encodeURIComponent(nameOrRef)}`,
|
||||
);
|
||||
if (res.status !== 200) {
|
||||
throw new Error(res.data?.message ?? res.data?.error ?? `Daemon returned HTTP ${res.status} for resolve-address`);
|
||||
}
|
||||
if (opts.json) console.log(JSON.stringify(res.data, null, 2));
|
||||
else process.stdout.write(res.data.text ?? "");
|
||||
return;
|
||||
}
|
||||
if (exact.status === 400) throw new Error(exact.data?.message ?? `Unsafe context pack ref '${nameOrRef}'.`);
|
||||
if (exact.status !== 200) throw new Error(`Daemon returned HTTP ${exact.status} for /api/context-packs/library/by-ref`);
|
||||
entry = exact.data;
|
||||
} else {
|
||||
entry = await resolvePack(client, nameOrRef);
|
||||
}
|
||||
const res = await client.get<PreviewWire>(`/api/context-packs/library/by-ref/preview?ref=${encodeURIComponent(entry.relativePath)}`);
|
||||
if (res.status !== 200) throw new Error(`Daemon returned HTTP ${res.status}`);
|
||||
const bundle = res.data;
|
||||
@@ -568,13 +632,15 @@ Examples:
|
||||
// the rule that the runtimes compose DIFFERENT profiles). Flag beats env;
|
||||
// an unrecognized env value falls back to claude rather than erroring a
|
||||
// surface the env owner may not control.
|
||||
.option("--runtime <runtime>", "claude | codex (default: $OPENRIG_RUNTIME, else claude)")
|
||||
.option("--runtime <runtime>", "claude-code (alias: claude) or codex (default: $OPENRIG_RUNTIME, else claude-code)", contextRuntimeArg)
|
||||
.option("--profile <profile>", "Named install profile declared by the pack (selection + ordered phases)")
|
||||
.option("--budget <tokens>", "Situation token budget — overage is REPORTED, never truncated")
|
||||
.option("--rig <rig>", "With --seat: grant read access to that seat's tree (seat: atoms)")
|
||||
.option("--seat <seat>", "With --rig: the seat whose tree seat: atoms may read")
|
||||
.option("--mission <mission>", "Grant read access to that mission's tree (mission: atoms)")
|
||||
.option("--slice <slice>", "With --mission: compose the legacy default project/mission/slice SPEC walk")
|
||||
.option("--json", "JSON output (the full composed profile)")
|
||||
.action(async (nameOrRef: string, opts: { situation: string; runtime?: string; budget?: string; rig?: string; seat?: string; mission?: string; json?: boolean }) => {
|
||||
.action(async (nameOrRef: string, opts: { situation: string; runtime?: string; profile?: string; budget?: string; rig?: string; seat?: string; mission?: string; slice?: string; json?: boolean }) => {
|
||||
try {
|
||||
const client = await getClient();
|
||||
const entry = await resolvePack(client, nameOrRef);
|
||||
@@ -593,12 +659,18 @@ Examples:
|
||||
runtime = "claude";
|
||||
}
|
||||
}
|
||||
// Keep the composer/manifest key and returned metadata compatible.
|
||||
if (runtime === "claude-code") runtime = "claude";
|
||||
const params = new URLSearchParams({ ref: entry.relativePath, situation: opts.situation, runtime });
|
||||
if (opts.profile !== undefined) params.set("profile", opts.profile);
|
||||
if (opts.budget !== undefined) params.set("budget", opts.budget);
|
||||
if (opts.rig !== undefined) params.set("rig", opts.rig);
|
||||
if (opts.seat !== undefined) params.set("seat", opts.seat);
|
||||
if (opts.mission !== undefined) params.set("mission", opts.mission);
|
||||
if (opts.slice !== undefined) params.set("slice", opts.slice);
|
||||
const res = await client.get<{
|
||||
profileId?: string;
|
||||
phases?: Array<{ id: string; kind: string; sources?: string[]; estimatedTokens: number }>;
|
||||
pieces?: Array<{ atomId: string; address: string; sourceKind: string; text: string; estimatedTokens: number }>;
|
||||
totalEstimatedTokens?: number;
|
||||
budget?: { limitTokens: number; overageTokens: number; dropCandidates: Array<{ atomId: string; priority: string; estimatedTokens: number }> };
|
||||
@@ -614,6 +686,13 @@ Examples:
|
||||
console.log(JSON.stringify(profile, null, 2));
|
||||
return;
|
||||
}
|
||||
if (profile.profileId) {
|
||||
console.error(`PROFILE ${profile.profileId}`);
|
||||
for (const phase of profile.phases ?? []) {
|
||||
const sources = phase.sources ? ` [${phase.sources.join(", ")}]` : "";
|
||||
console.error(`PHASE ${phase.id} (${phase.kind}${sources}, ~${phase.estimatedTokens} tokens)`);
|
||||
}
|
||||
}
|
||||
// Warnings and the budget report ride stderr so stdout is exactly the
|
||||
// composed walk an agent consumes.
|
||||
for (const w of profile.provenanceWarnings ?? []) console.error(`PROVENANCE ${w}`);
|
||||
@@ -656,10 +735,15 @@ Examples:
|
||||
const content = readFileSync(opts.file, "utf-8");
|
||||
const store = new ConfigStore();
|
||||
const topologyRoot = String(store.resolveWithSource("topology.root").value);
|
||||
const seatDir = join(topologyRoot, "rigs", opts.rig, "seats", opts.seat);
|
||||
if (!existsSync(seatDir)) {
|
||||
throw new Error(`seat directory ${seatDir} does not exist — check --rig/--seat against the topology tree (topology.root=${topologyRoot}).`);
|
||||
assertSafeTopologySegment("rig", opts.rig);
|
||||
assertSafeTopologySegment("seat", opts.seat);
|
||||
const rigDir = join(topologyRoot, "rigs", opts.rig);
|
||||
if (!existsSync(rigDir)) {
|
||||
throw new Error(`rig directory ${rigDir} does not exist — check --rig against the topology tree (topology.root=${topologyRoot}).`);
|
||||
}
|
||||
const seatDir = join(topologyRoot, "rigs", opts.rig, "seats", opts.seat);
|
||||
assertDestinationNamespaceContained(join(topologyRoot, "rigs"), `${opts.rig}/seats/${opts.seat}/RECAP.md`);
|
||||
mkdirSync(seatDir, { recursive: true });
|
||||
for (const f of validateRecapContract(content)) {
|
||||
console.error(f.kind === "no-decisions-section"
|
||||
? "ADVISORY no-decisions-section: the authoring contract asks for decisions WITH rationale — conclusions alone are the lossy handoff shape."
|
||||
@@ -701,17 +785,26 @@ Examples:
|
||||
});
|
||||
|
||||
cmd.command("add")
|
||||
.argument("<source>", "Local directory OR http(s):// URL of a context pack (manifest.yaml + files)")
|
||||
.description("Install a context pack from a local directory or a URL into the context-packs landing zone")
|
||||
.argument("<source>", "Pack directory/manifest URL, or Git repository path/URL with --git")
|
||||
.description("Install a pack; --git discovers a pack in a Git repository and retains its update relationship")
|
||||
.option("--name <name>", "Override the install name (defaults to the manifest name / source basename)")
|
||||
.option("--git", "Clone a Git repository path/URL with existing Git credentials; select a pack snapshot")
|
||||
.option("--checkout", "With --git, select an existing checkout instead of cloning; updates may merge in it")
|
||||
.option("--pack <path>", "With --git, select a repository-relative pack; default discovers manifest.yaml or .openrig/context-packs")
|
||||
.option("--json", "JSON output")
|
||||
.action(async (source: string, opts: { name?: string; json?: boolean }) => {
|
||||
.action(async (source: string, opts: { name?: string; json?: boolean; git?: boolean; checkout?: boolean; pack?: string }) => {
|
||||
try {
|
||||
// OPR.0.5.3.7 R4 — config-resolved landing zone (env > config > $OPENRIG_HOME/context-packs),
|
||||
// OPR.0.5.9.5 Wave B — config-resolved context library,
|
||||
// never a hardcoded ~/.openrig literal; the daemon resolves the same key.
|
||||
const targetRoot = new ConfigStore().resolve().context.packsRoot;
|
||||
const targetRoot = new ConfigStore().resolve().context.root;
|
||||
let targetDir: string;
|
||||
if (isHttpUrl(source)) {
|
||||
let gitSelection: ReturnType<typeof addGitContext>["selected"] | undefined;
|
||||
if ((opts.pack || opts.checkout) && !opts.git) throw new Error("--pack and --checkout require --git.");
|
||||
if (opts.git) {
|
||||
const gitClient = await getClient();
|
||||
assertLocalGitClient(gitClient);
|
||||
({ installedAt: targetDir, selected: gitSelection } = addGitContext(source, opts, targetRoot));
|
||||
} else if (isHttpUrl(source)) {
|
||||
// R4 — URL install: fetch → validate → atomic stage+rename (no partial pack).
|
||||
({ targetDir } = await installPackFromUrl(source, opts.name, targetRoot));
|
||||
} else {
|
||||
@@ -764,10 +857,14 @@ Examples:
|
||||
if (syncError) {
|
||||
throw new Error(`Installed at ${targetDir}, but daemon rejected the pack during sync: ${syncError.error}`);
|
||||
}
|
||||
if (gitSelection && !syncRes.data.entries.some((entry) => resolve(entry.sourcePath) === resolve(targetDir))) {
|
||||
throw new Error(`Git selection retained at ${targetDir}, but this daemon does not serve it. Check context.root and workspace ref precedence before using it.`);
|
||||
}
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify({ installedAt: targetDir, count: syncRes.data.count }, null, 2));
|
||||
console.log(JSON.stringify({ installedAt: targetDir, count: syncRes.data.count, ...(gitSelection ? { gitSource: gitSelection } : {}) }, null, 2));
|
||||
} else {
|
||||
console.log(`Installed at ${targetDir}. Library now has ${syncRes.data.count} context pack(s).`);
|
||||
if (gitSelection) console.log(`Git ${gitSelection.revision} from ${gitSelection.checkout}; inspect/update with rig context source.`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error((err as Error).message);
|
||||
@@ -775,6 +872,41 @@ Examples:
|
||||
}
|
||||
});
|
||||
|
||||
const source = cmd.command("source")
|
||||
.description("Inspect Git checkout vs served context, or explicitly fetch/merge and select an update")
|
||||
.addHelpText("after", "\nStart: rig context add <repository-path-or-URL> --git [--pack path]\nEdit/commit in the reported checkout with ordinary Git. Updates never push or reset.\nConflicts retain the old selection; resolve/commit or abort in the checkout before retrying.\nSelection does not prove agent consumption; use context get and check the actual consumer.\n");
|
||||
for (const operation of ["inspect", "update"] as const) {
|
||||
source.command(operation)
|
||||
.argument("<ref>", "Selected Git-backed context pack ref")
|
||||
.description(operation === "inspect"
|
||||
? "Read local revision, edits, conflicts and selection; no fetch or consumption claim"
|
||||
: "Explicitly fetch/merge the upstream, then select the clean pack; refuse local selection edits")
|
||||
.option("--json", "JSON output")
|
||||
.action(async (ref: string, opts: { json?: boolean }) => {
|
||||
try {
|
||||
const client = await getClient();
|
||||
assertLocalGitClient(client);
|
||||
const entry = await resolvePack(client, ref);
|
||||
const localRoot = new ConfigStore().resolve().context.root;
|
||||
if (resolve(entry.sourcePath) !== resolve(localRoot, entry.relativePath)) throw new Error("This pack is not in the locally configured context library. Run Git source commands on its owning instance.");
|
||||
if (entry.sourceType === "builtin") throw new Error("Builtin context is not a writable Git selection.");
|
||||
const result = operation === "inspect" ? inspectGitContext(entry.sourcePath) : updateGitContext(entry.sourcePath);
|
||||
if (operation === "update") {
|
||||
const sync = await client.post<{ errors?: Array<{ source: string; error: string }>; entries: ContextPackEntryWire[] }>("/api/context-packs/library/sync");
|
||||
if (sync.status !== 200) throw new Error(`Selection updated, but library sync failed (HTTP ${sync.status}); run rig context sync.`);
|
||||
if (!sync.data.entries.some((candidate) => resolve(candidate.sourcePath) === resolve(entry.sourcePath))) throw new Error(`Selection retained, but the daemon cannot serve it: ${sync.data.errors?.map((error) => error.error).join("; ") || "check context root and ref precedence"}`);
|
||||
}
|
||||
// Structured output keeps checkout/selected bytes/consumption distinct
|
||||
// in both terminal and machine use, without a second status renderer.
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} catch (err) {
|
||||
const message = (err as Error).message;
|
||||
console.error(opts.json ? JSON.stringify({ error: message }) : message);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cmd.command("rm")
|
||||
.argument("<ref>", "Path-like ref of the context pack to remove (e.g. packs/compaction-restore)")
|
||||
.description("Remove a context pack from the library by its path-like ref")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Command } from "commander";
|
||||
import { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { copyFileSync, lstatSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { ConfigStore } from "../config-store.js";
|
||||
|
||||
// `rig crash-cart --json` — the daemon-DOWN recovery verdict emit (plan c015d9ed §C3, coupling ruling
|
||||
// option A). Prints ONE JSON = the 3-state detector verdict + (on DOWN) the discovery — READ-ONLY, a
|
||||
@@ -37,18 +38,24 @@ function openrigHome(): string {
|
||||
* the detector's readDaemonJson omits it). */
|
||||
function readDaemonJsonWithDb(home: string): { pid: number; port: number; host?: string; db: string } | undefined {
|
||||
const p = join(home, "daemon.json");
|
||||
if (!existsSync(p)) return undefined;
|
||||
if (!existsOrThrow(p)) return undefined;
|
||||
try {
|
||||
const j = JSON.parse(readFileSync(p, "utf8")) as { pid?: unknown; port?: unknown; host?: unknown; db?: unknown };
|
||||
if (typeof j.pid === "number" && typeof j.port === "number" && typeof j.db === "string") {
|
||||
return { pid: j.pid, port: j.port, host: typeof j.host === "string" ? j.host : undefined, db: j.db };
|
||||
}
|
||||
return undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
throw new Error("daemon.json has no usable PID, port and database path");
|
||||
} catch (error) {
|
||||
throw new Error(`Cannot read recorded daemon state at ${p}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Only ENOENT means absent; permissions and dangling links must fail visibly. */
|
||||
function existsOrThrow(path: string): boolean {
|
||||
try { lstatSync(path); return true; }
|
||||
catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return false; throw error; }
|
||||
}
|
||||
|
||||
/**
|
||||
* The real verdict: LAZY-imports the narrow @openrig/daemon/crash-cart subpath at invocation (dep
|
||||
* rail 2 — rig startup + other verbs never load the daemon module) and composes the shipped detector
|
||||
@@ -91,7 +98,8 @@ async function realEmit(): Promise<CrashCartEmit> {
|
||||
isProcessAlive: cc.isProcessAlive,
|
||||
probeHealthz: (url) => probeClassified(url).then((r) => r === "answered"),
|
||||
copyFile: copyFileSync,
|
||||
exists: existsSync,
|
||||
exists: existsOrThrow,
|
||||
configuredDbPath: new ConfigStore().resolve().db.path,
|
||||
makeScratchDir: () => mkdtempSync(join(tmpdir(), "crash-cart-")),
|
||||
removeScratchDir: (d) => rmSync(d, { recursive: true, force: true }),
|
||||
openDb: cc.openDaemonDbReadonly,
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { resolve as resolvePath } from "node:path";
|
||||
import { stringify as stringifyYaml } from "yaml";
|
||||
import { Command } from "commander";
|
||||
import { DaemonClient } from "../client.js";
|
||||
import { getDaemonStatus, getDaemonUrl, daemonStatusGuard } from "../daemon-lifecycle.js";
|
||||
import { realDeps } from "./daemon.js";
|
||||
import type { StatusDeps } from "./status.js";
|
||||
import { resolveDefaultAgentRef } from "./topology-default-agent.js";
|
||||
|
||||
interface CreateResult {
|
||||
rigId: string;
|
||||
specName: string;
|
||||
specVersion: string;
|
||||
nodes: Array<{
|
||||
logicalId: string;
|
||||
status: "launched" | "failed" | "attention_required";
|
||||
sessionName?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
errors?: string[];
|
||||
message?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export function createCommand(depsOverride?: StatusDeps): Command {
|
||||
const cmd = new Command("create").description("Create a one-seat rig without writing a spec");
|
||||
const getDeps = () => depsOverride ?? {
|
||||
lifecycleDeps: realDeps(),
|
||||
clientFactory: (url: string) => new DaemonClient(url),
|
||||
};
|
||||
|
||||
cmd
|
||||
.argument("<name>", "Name of the new rig")
|
||||
.option("--runtime <runtime>", "Agent runtime", "claude-code")
|
||||
.option("--cwd <path>", "Working directory for the seat", process.cwd())
|
||||
.option("--json", "JSON output for agents")
|
||||
.action(async (name: string, opts: { runtime: string; cwd: string; json?: boolean }) => {
|
||||
const deps = getDeps();
|
||||
const status = await getDaemonStatus(deps.lifecycleDeps);
|
||||
if (!daemonStatusGuard(status)) return;
|
||||
|
||||
const client = deps.clientFactory(getDaemonUrl(status));
|
||||
let agentRef: string;
|
||||
try {
|
||||
agentRef = await resolveDefaultAgentRef(client);
|
||||
} catch (err) {
|
||||
console.error(err instanceof Error ? err.message : String(err));
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const cwd = resolvePath(opts.cwd);
|
||||
const logicalId = "main.lead";
|
||||
const yaml = stringifyYaml({
|
||||
version: "0.2",
|
||||
name,
|
||||
pods: [{
|
||||
id: "main",
|
||||
label: "Main",
|
||||
members: [{
|
||||
id: "lead",
|
||||
agent_ref: agentRef,
|
||||
runtime: opts.runtime,
|
||||
profile: "default",
|
||||
cwd,
|
||||
}],
|
||||
edges: [],
|
||||
}],
|
||||
edges: [],
|
||||
});
|
||||
|
||||
const res = await client.postText<CreateResult>(
|
||||
"/api/rigs/import",
|
||||
yaml,
|
||||
"text/yaml",
|
||||
{ "X-Rig-Root": cwd, "X-Cwd-Override": cwd },
|
||||
{ timeoutMs: 120_000 },
|
||||
);
|
||||
const node = res.data.nodes?.find((candidate) => candidate.logicalId === logicalId);
|
||||
const ok = res.status < 400 && node?.status === "launched";
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify({
|
||||
ok,
|
||||
rigId: res.data.rigId,
|
||||
rigName: name,
|
||||
pod: "main",
|
||||
seat: logicalId,
|
||||
source: agentRef,
|
||||
node,
|
||||
}, null, 2));
|
||||
} else if (ok) {
|
||||
console.log(`Created rig ${name} (${res.data.rigId})`);
|
||||
console.log(` Seat: ${logicalId}${node?.sessionName ? ` (${node.sessionName})` : ""}`);
|
||||
} else {
|
||||
const detail = res.data.errors?.join("; ")
|
||||
?? res.data.message
|
||||
?? res.data.error
|
||||
?? node?.error
|
||||
?? `Create failed (HTTP ${res.status})`;
|
||||
console.error(detail);
|
||||
}
|
||||
|
||||
if (!ok) process.exitCode = 1;
|
||||
});
|
||||
|
||||
return cmd;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Command } from "commander";
|
||||
import fs from "node:fs";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { acquireDaemonStartLock } from "../daemon-start-lock.js";
|
||||
import { execFileSync, spawn } from "node:child_process";
|
||||
import { fetchWithTimeout } from "../fetch-with-timeout.js";
|
||||
import {
|
||||
@@ -10,6 +12,8 @@ import {
|
||||
tailLogs,
|
||||
type LifecycleDeps,
|
||||
OPENRIG_DIR,
|
||||
STATE_FILE,
|
||||
resolveBindIntent,
|
||||
} from "../daemon-lifecycle.js";
|
||||
|
||||
interface ProcessAliveDeps {
|
||||
@@ -28,6 +32,22 @@ export function createIsProcessAlive(deps: ProcessAliveDeps): (pid: number) => b
|
||||
};
|
||||
}
|
||||
|
||||
type ExecFile = (file: string, args: string[], options: { encoding: "utf-8" }) => string;
|
||||
|
||||
// Windows has no ps(1) and no zombie state, so the signal probe alone decides liveness there.
|
||||
export function readProcessState(
|
||||
pid: number,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
run: ExecFile = execFileSync,
|
||||
): string | null {
|
||||
if (platform === "win32") return "R";
|
||||
try {
|
||||
return run("ps", ["-o", "state=", "-p", String(pid)], { encoding: "utf-8" });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function realDeps(): LifecycleDeps {
|
||||
const isProcessAlive = createIsProcessAlive({
|
||||
signalCheck: (pid) => {
|
||||
@@ -38,16 +58,11 @@ export function realDeps(): LifecycleDeps {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
readProcessState: (pid) => {
|
||||
try {
|
||||
return execFileSync("ps", ["-o", "state=", "-p", String(pid)], { encoding: "utf-8" });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
readProcessState: (pid) => readProcessState(pid),
|
||||
});
|
||||
|
||||
return {
|
||||
acquireStartLock: () => acquireDaemonStartLock(OPENRIG_DIR),
|
||||
spawn: (cmd, args, opts) => spawn(cmd, args, opts as Parameters<typeof spawn>[2]),
|
||||
fetch: async (url) => {
|
||||
const res = await fetchWithTimeout(globalThis.fetch, url, {}, {
|
||||
@@ -60,11 +75,31 @@ export function realDeps(): LifecycleDeps {
|
||||
},
|
||||
kill: (pid, signal) => { process.kill(pid, signal as NodeJS.Signals); return true; },
|
||||
readFile: (p) => { try { return fs.readFileSync(p, "utf-8"); } catch { return null; } },
|
||||
writeFile: (p, content) => fs.writeFileSync(p, content, "utf-8"),
|
||||
writeFile: (p, content) => {
|
||||
if (p !== STATE_FILE) { fs.writeFileSync(p, content, "utf-8"); return; }
|
||||
const temporary = `${p}.${randomUUID()}.tmp`;
|
||||
try {
|
||||
fs.writeFileSync(temporary, content, { encoding: "utf-8", flag: "wx" });
|
||||
fs.renameSync(temporary, p);
|
||||
} finally {
|
||||
try { fs.unlinkSync(temporary); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; }
|
||||
}
|
||||
},
|
||||
removeFile: (p) => { try { fs.unlinkSync(p); } catch { /* ignore */ } },
|
||||
exists: (p) => fs.existsSync(p),
|
||||
mkdirp: (p) => fs.mkdirSync(p, { recursive: true }),
|
||||
pathKind: (p) => {
|
||||
try {
|
||||
const value = fs.lstatSync(p);
|
||||
if (value.isDirectory()) return "directory";
|
||||
if (value.isFile()) return "file";
|
||||
return "other";
|
||||
} catch {
|
||||
return "missing";
|
||||
}
|
||||
},
|
||||
openForAppend: (p) => fs.openSync(p, "a"),
|
||||
closeFile: (fd) => fs.closeSync(fd),
|
||||
isProcessAlive,
|
||||
// RULING 1ae863d2 — sibling-home scan (home-resolution honesty).
|
||||
listDir: (p) => { try { return fs.readdirSync(p); } catch { return []; } },
|
||||
@@ -78,6 +113,7 @@ export function daemonCommand(depsOverride?: LifecycleDeps): Command {
|
||||
cmd
|
||||
.command("start")
|
||||
.description("Start the daemon")
|
||||
.addHelpText("after", "\nStartup reserves this local instance before initialization and verifies the spawned child PID on every required listener.\nMissing/mismatched identity or child exit fails startup; failed publication withdraws only this launch's matching state. Use a matching CLI/daemon installation.\nA concurrent start fails without spawning another child. Inspect daemon-start.lock for launcher/child PIDs after an interrupted start;\nonly archive an abandoned reservation after proving both processes absent. A failed cleanup retains it and reports the child PID.\n")
|
||||
.option("--port <port>", "Port to listen on")
|
||||
.option("--host <host>", "Host to bind on")
|
||||
.option("--db <path>", "Database path")
|
||||
@@ -108,9 +144,19 @@ export function daemonCommand(depsOverride?: LifecycleDeps): Command {
|
||||
// multi-bind (loopback + tailscale auto-detect) when the operator
|
||||
// never opted in to a specific host.
|
||||
const hostResolution = configStore.resolveWithSource("daemon.host");
|
||||
const hostUserExplicit = opts.host !== undefined || hostResolution.source !== "default";
|
||||
const effectiveHost = opts.host ?? config.daemon.host;
|
||||
const hostForDaemon = hostUserExplicit ? effectiveHost : undefined;
|
||||
// S20 — bind intent comes ONLY from the dedicated surfaces: the --host flag,
|
||||
// a FILE-sourced daemon.host, or OPENRIG_BIND_HOST. An env-sourced daemon.host
|
||||
// is the overloaded routing channel (ENV_MAP maps it from OPENRIG_HOST — the
|
||||
// exact injected state a managed environment carries) and never creates intent.
|
||||
const intent = resolveBindIntent({
|
||||
flagHost: opts.host,
|
||||
envBindHost: process.env["OPENRIG_BIND_HOST"],
|
||||
configSource: hostResolution.source,
|
||||
configHost: config.daemon.host,
|
||||
});
|
||||
const hostUserExplicit = intent.explicit;
|
||||
const effectiveHost = intent.host ?? "127.0.0.1";
|
||||
const hostForDaemon = intent.host;
|
||||
|
||||
// Run preflight before starting
|
||||
const preflight = new SystemPreflight({
|
||||
@@ -140,6 +186,9 @@ export function daemonCommand(depsOverride?: LifecycleDeps): Command {
|
||||
transcriptsEnabled: config.transcripts.enabled,
|
||||
transcriptsPath: config.transcripts.path,
|
||||
workspaceRoot: config.workspace.root,
|
||||
contextRoot: config.context.root,
|
||||
skillsRoot: config.skills.root,
|
||||
topologyRoot: config.topology.root,
|
||||
// V1 pre-release CLI/daemon Item 1 — project the
|
||||
// ConfigStore-resolved rotation tunables into the daemon
|
||||
// process env so file-stored values
|
||||
@@ -190,11 +239,12 @@ export function daemonCommand(depsOverride?: LifecycleDeps): Command {
|
||||
|
||||
cmd
|
||||
.command("stop")
|
||||
.description("Stop the daemon")
|
||||
.description("Stop the daemon (10s shutdown budget; 12s process wait; incomplete drain exits nonzero)")
|
||||
.addHelpText("after", "\nSends at most one SIGTERM to a live target and verifies the original PID and listener. Repeated signals join shutdown.\nFor a recorded target, missing/stale receipts and incomplete drains exit nonzero, including retries.\nTarget state is retained until a matching clean receipt; status reads preserve unverified state.\nNo target is a distinct no-op, never clean-drain certification; unbound incomplete evidence stays unverified.\nInspect OPENRIG_HOME/daemon-shutdown.json and daemon.log for the phase and outcome.\nThe bound covers asynchronous shutdown; an event-loop wedge still requires operator recovery.\n")
|
||||
.action(async () => {
|
||||
try {
|
||||
await stopDaemon(getDeps());
|
||||
console.log("Daemon stopped");
|
||||
const outcome = await stopDaemon(getDeps());
|
||||
console.log(outcome === "stopped" ? "Daemon stopped" : "No daemon target recorded; listener refused. Nothing to stop; prior drain not certified.");
|
||||
} catch (err) {
|
||||
console.error(err instanceof Error ? err.message : String(err));
|
||||
process.exitCode = 1;
|
||||
@@ -235,7 +285,7 @@ export function daemonCommand(depsOverride?: LifecycleDeps): Command {
|
||||
console.log("Daemon stopped");
|
||||
break;
|
||||
case "stale":
|
||||
console.log("Daemon stale (cleaned up)");
|
||||
console.log("Daemon PID is absent (stale state)");
|
||||
break;
|
||||
case "unverified":
|
||||
// 1ae863d2 — C3 semantics: we could NOT confirm up or down; never claim stopped.
|
||||
|
||||
@@ -70,7 +70,7 @@ function resolveRuntimeConfig(configStore: Pick<ConfigStore, "resolve">): Resolv
|
||||
function realDestroyDeps(): DestroyDeps {
|
||||
const lifecycleDeps = realDeps();
|
||||
return {
|
||||
stopDaemon: async () => stopDaemon(lifecycleDeps),
|
||||
stopDaemon: async () => { await stopDaemon(lifecycleDeps); },
|
||||
inspectListener: async (host: string, port: number): Promise<ListenerInspection> => {
|
||||
try {
|
||||
const res = await fetch(`http://${host}:${port}/healthz`);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { buildTmuxControlFailure, probeTmuxControl } from "../tmux-health.js";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { compareSpecToLive, topologyFromRigSpec, topologyFromLiveLogicalIds } from "@openrig/daemon/spec-conformance";
|
||||
import { classifyNodeVersion } from "../node-support.js";
|
||||
|
||||
interface DoctorCheck {
|
||||
name: string;
|
||||
@@ -48,7 +49,6 @@ export interface DoctorDeps {
|
||||
fetchLiveLogicalIds?: (rigName: string) => Promise<string[] | null>;
|
||||
}
|
||||
|
||||
const MIN_NODE_MAJOR = 20;
|
||||
const DEFAULT_PORT = 7433;
|
||||
|
||||
function defaultCheckPort(port: number, host: string): Promise<boolean> {
|
||||
@@ -96,16 +96,18 @@ export function runDoctorChecks(deps: DoctorDeps): { checks: DoctorCheck[]; port
|
||||
}
|
||||
|
||||
// 3. Node version
|
||||
const major = parseInt(process.version.replace(/^v/, ""), 10);
|
||||
if (major >= MIN_NODE_MAJOR) {
|
||||
const nodeSupport = classifyNodeVersion(process.version);
|
||||
if (nodeSupport.kind === "supported") {
|
||||
checks.push({ name: "node_version", status: "pass", message: `Node ${process.version}` });
|
||||
} else if (nodeSupport.kind === "untested") {
|
||||
checks.push({ name: "node_version", status: "warn", message: nodeSupport.message! });
|
||||
} else {
|
||||
checks.push({
|
||||
name: "node_version",
|
||||
status: "fail",
|
||||
message: `Node ${process.version} is below minimum (v${MIN_NODE_MAJOR}).`,
|
||||
reason: "OpenRig requires Node 20+ for built-in fetch, ESM, and stable API support.",
|
||||
fix: "Install Node 20+ via nvm, fnm, or your package manager.",
|
||||
message: nodeSupport.message!,
|
||||
reason: nodeSupport.reason,
|
||||
fix: nodeSupport.fix,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,12 @@ Examples:
|
||||
}
|
||||
|
||||
const receipt = res.data["receipt"] as Record<string, unknown> | null;
|
||||
const probeStatus = res.data["probeStatus"];
|
||||
if (probeStatus === "stale" || probeStatus === "no_orchestrator") {
|
||||
const probeError = res.data["probeError"];
|
||||
console.error(`Service status is ${probeStatus === "stale" ? "stale" : "unverified"} (probe: ${probeStatus}); ${receipt ? "showing the saved receipt" : "no saved receipt is available"}.${typeof probeError === "string" && probeError ? ` ${probeError}` : ""}`);
|
||||
}
|
||||
|
||||
if (!receipt) {
|
||||
console.log("Services configured but no receipt available yet.");
|
||||
return;
|
||||
|
||||
@@ -98,22 +98,19 @@ export function expandCommand(depsOverride?: StatusDeps): Command {
|
||||
for (const w of data.warnings) console.log(` Warning: ${w}`);
|
||||
}
|
||||
|
||||
// Honest retry guidance for failed nodes. The pod-aware /launch route
|
||||
// returns pod_aware_launch_unsupported for any node `expand` creates
|
||||
// (every expand-created node has podId set), so the recoverable path is
|
||||
// shrink-the-failed-pod + fix + re-expand, not per-node relaunch.
|
||||
// Honest retry guidance for failed nodes. The /launch route runs every
|
||||
// pod-bearing node (which every expand-created node is) through the
|
||||
// managed subset launch with full startup orchestration, so per-node
|
||||
// relaunch is the recovery path once the underlying cause is fixed.
|
||||
if (data.retryTargets && data.retryTargets.length > 0) {
|
||||
console.log("");
|
||||
console.log(" Failed nodes cannot be relaunched directly because pod-aware launch");
|
||||
console.log(" bypasses startup orchestration. Recover by:");
|
||||
console.log(" Failed nodes can be relaunched individually. Recover by:");
|
||||
console.log(` 1. Verify other seats are healthy: rig ps --nodes --rig ${rigId}`);
|
||||
if (data.podNamespace) {
|
||||
console.log(` 2. Shrink the failed new pod: rig shrink ${rigId} ${data.podNamespace}`);
|
||||
} else {
|
||||
console.log(` 2. Shrink the failed new pod: rig shrink ${rigId} <pod-namespace>`);
|
||||
console.log(` 2. Fix the spec/config issue behind each failure.`);
|
||||
console.log(` 3. Relaunch each failed node:`);
|
||||
for (const target of data.retryTargets) {
|
||||
console.log(` rig launch ${rigId} ${target}`);
|
||||
}
|
||||
console.log(` 3. Fix the spec/config issue.`);
|
||||
console.log(` 4. Re-run: rig expand ${rigId} <pod-fragment-path>`);
|
||||
console.log(` Failed targets: ${data.retryTargets.join(", ")}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Command } from "commander";
|
||||
import { resolveSenderSession, SENDER_FALLBACK } from "../sender-identity.js";
|
||||
// The human registry is home-state owned by the daemon; the verb LAZY-imports the narrow
|
||||
// @openrig/daemon/gateway-human-registry surface at invocation (the C3/crash-cart dep rail —
|
||||
// one source, no twin). Type-only import keeps the surface off the eager cli graph.
|
||||
@@ -9,45 +10,115 @@ import type { addHumanFragment as AddHumanFragment } from "@openrig/daemon/gatew
|
||||
// write -> re-project). address is DERIVED as <entityId>@external (the registered
|
||||
// convention, pinned by the schema); no-clobber unless --replace is passed explicitly.
|
||||
|
||||
interface BindingSpec {
|
||||
kind: string;
|
||||
connectorRef: string;
|
||||
secretsRef: string;
|
||||
role: string;
|
||||
handle?: string;
|
||||
// The --binding spec parse (kind:connectorRef:secretsRef:role[:handle=<id>], ':'-bearing
|
||||
// vault-pointer secretsRef, optional handle= token) lives in the registry surface as
|
||||
// parseBindingSpec — ONE source for add (here) and `set binding.<n>` (S12), lazy-imported
|
||||
// with the rest of the surface so the eager CLI graph stays daemon-free.
|
||||
|
||||
// ── S12 (OPR.0.5.5.12): the queue-row half of the remove guard, injectable so verb tests
|
||||
// never need a live daemon. ok:false = the source COULD NOT BE CHECKED (unreachable daemon)
|
||||
// — an indeterminate, never evidence of an empty board.
|
||||
export interface HumanRowRef {
|
||||
id: string;
|
||||
state: string;
|
||||
summary?: string;
|
||||
}
|
||||
export type HumanRowsLookup = (address: string) => Promise<
|
||||
| { ok: true; rows: HumanRowRef[] }
|
||||
| { ok: false; error: string }
|
||||
>;
|
||||
|
||||
export interface GatewayCommandDeps {
|
||||
queueRows?: HumanRowsLookup;
|
||||
humanReadiness?: HumanReadinessLookup;
|
||||
}
|
||||
|
||||
/** --binding kind:connectorRef:secretsRef:role[:handle=<id>]. secretsRef is a vault POINTER and
|
||||
* may itself contain ':' (e.g. "vault://slack/mike"), so kind/connectorRef are the first two
|
||||
* fields, role is the LAST positional, and secretsRef is everything between (colons intact).
|
||||
* A6 v3: an OPTIONAL `handle=<id>` token (the human's platform id, e.g. Slack user id) may appear
|
||||
* anywhere; it is extracted first so it never disturbs the positional parse. A handle-less binding
|
||||
* is outbound-only (not inbound-resolvable). */
|
||||
function parseBinding(spec: string): BindingSpec | { error: string } {
|
||||
const all = spec.split(":");
|
||||
const handleTokens = all.filter((p) => p.startsWith("handle="));
|
||||
if (handleTokens.length > 1) {
|
||||
return { error: `--binding must carry at most one handle= token (got "${spec}")` };
|
||||
}
|
||||
const handle = handleTokens.length === 1 ? handleTokens[0]!.slice("handle=".length) : undefined;
|
||||
if (handle !== undefined && handle.length === 0) {
|
||||
return { error: `--binding handle= must be non-empty (got "${spec}")` };
|
||||
}
|
||||
const parts = all.filter((p) => !p.startsWith("handle="));
|
||||
if (parts.length < 4) {
|
||||
return { error: `--binding must be kind:connectorRef:secretsRef:role[:handle=<id>] (got "${spec}")` };
|
||||
}
|
||||
const kind = parts[0]!;
|
||||
const connectorRef = parts[1]!;
|
||||
const role = parts[parts.length - 1]!;
|
||||
const secretsRef = parts.slice(2, -1).join(":");
|
||||
if (!kind || !connectorRef || !secretsRef || !role) {
|
||||
return { error: `--binding fields must be non-empty: kind:connectorRef:secretsRef:role[:handle=<id>] (got "${spec}")` };
|
||||
}
|
||||
return handle !== undefined ? { kind, connectorRef, secretsRef, role, handle } : { kind, connectorRef, secretsRef, role };
|
||||
export interface HumanReadinessView {
|
||||
state: "ready" | "not-ready" | "indeterminate";
|
||||
configured: boolean | null;
|
||||
enabled: boolean | null;
|
||||
active: boolean | null;
|
||||
ready: boolean;
|
||||
connector?: { kind: string; ref: string };
|
||||
reason: string;
|
||||
nextAction: string | null;
|
||||
checkedAt?: string;
|
||||
}
|
||||
|
||||
export function gatewayCommand(): Command {
|
||||
export type HumanReadinessLookup = (entityId: string) => Promise<HumanReadinessView>;
|
||||
|
||||
export async function daemonHumanReadiness(entityId: string): Promise<HumanReadinessView> {
|
||||
try {
|
||||
const { DaemonClient } = await import("../client.js");
|
||||
const response = await new DaemonClient().get<{ ok?: boolean; readiness?: HumanReadinessView; error?: unknown; message?: unknown }>(
|
||||
`/api/gateway/human/${encodeURIComponent(entityId)}/readiness`,
|
||||
);
|
||||
if (response.status !== 200) {
|
||||
const detail = [response.data?.error, response.data?.message].filter((value) => typeof value === "string").join(": ");
|
||||
throw new Error(`HTTP ${response.status}: ${detail || "readiness request refused"}`);
|
||||
}
|
||||
const readiness = response.data?.readiness;
|
||||
if (response.data?.ok !== true || !readiness
|
||||
|| !["ready", "not-ready", "indeterminate"].includes(readiness.state)
|
||||
|| [readiness.configured, readiness.enabled, readiness.active].some((value) => value !== null && typeof value !== "boolean")
|
||||
|| readiness.ready !== (readiness.state === "ready")
|
||||
|| typeof readiness.reason !== "string" || !readiness.reason.trim()
|
||||
|| (readiness.nextAction !== null && typeof readiness.nextAction !== "string")
|
||||
|| (readiness.connector !== undefined && (!readiness.connector || typeof readiness.connector.kind !== "string" || typeof readiness.connector.ref !== "string"))
|
||||
|| (readiness.checkedAt !== undefined && typeof readiness.checkedAt !== "string")) {
|
||||
throw new Error("HTTP 200: malformed delivery readiness response");
|
||||
}
|
||||
return readiness;
|
||||
} catch (error) {
|
||||
return {
|
||||
state: "indeterminate",
|
||||
configured: null,
|
||||
enabled: null,
|
||||
active: null,
|
||||
ready: false,
|
||||
reason: `delivery readiness could not be read from the daemon: ${(error as Error).message}`,
|
||||
nextAction: "rig status",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Default queue-row half of the remove guard: non-terminal rows addressed to the human,
|
||||
* via the daemon. ok:false carries the reason the board COULD NOT BE CHECKED. */
|
||||
export async function daemonQueueRows(address: string): ReturnType<HumanRowsLookup> {
|
||||
try {
|
||||
const { DaemonClient } = await import("../client.js");
|
||||
const client = new DaemonClient();
|
||||
// Fix-r1 F2 (R2 blocking): enumerate to EXHAUSTION. A bounded read that comes back
|
||||
// FULL is truncation-suspect — completeness is proven only by a window strictly larger
|
||||
// than the result. Grow the window until that holds; a board that outgrows every
|
||||
// window is an honest completeness refusal, never a silently-shortened orphan list.
|
||||
let limit = 1000;
|
||||
for (let attempt = 0; attempt < 6; attempt++) {
|
||||
const res = await client.get<Array<{ qitemId?: string; state?: string; summary?: string | null }>>(
|
||||
`/api/queue/list?destinationSession=${encodeURIComponent(address)}&state=pending,in-progress,blocked&limit=${limit}&compact=1`,
|
||||
);
|
||||
const data = Array.isArray(res.data) ? res.data : [];
|
||||
if (data.length < limit) {
|
||||
return {
|
||||
ok: true,
|
||||
rows: data.map((r) => ({
|
||||
id: String(r.qitemId ?? "(unknown-id)"),
|
||||
state: String(r.state ?? "(unknown-state)"),
|
||||
...(r.summary ? { summary: String(r.summary) } : {}),
|
||||
})),
|
||||
};
|
||||
}
|
||||
limit *= 4;
|
||||
}
|
||||
return { ok: false, error: `queue enumeration for ${address} saturated every window up to ${limit / 4} rows — completeness unproven` };
|
||||
} catch (err) {
|
||||
return { ok: false, error: (err as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
export function gatewayCommand(deps: GatewayCommandDeps = {}): Command {
|
||||
const queueRows: HumanRowsLookup = deps.queueRows ?? daemonQueueRows;
|
||||
const humanReadiness: HumanReadinessLookup = deps.humanReadiness ?? daemonHumanReadiness;
|
||||
const cmd = new Command("gateway").description("Gateway: the human registry + connector surfaces");
|
||||
const human = cmd.command("human").description("Manage human specs (file-per-human fragments under gateway/humans/)");
|
||||
|
||||
@@ -63,12 +134,39 @@ export function gatewayCommand(): Command {
|
||||
.requiredOption("--delivery-class <A|B|C|D>", "Notification loudness class (the notifications register selection)")
|
||||
.option("--away", "Set the AWAY preset")
|
||||
.option("--replace", "Explicitly replace an existing human (no silent overwrite)")
|
||||
.action(async (entityId: string, opts: { displayName: string; binding: string[]; deliveryClass: string; away?: boolean; replace?: boolean }) => {
|
||||
const bindings: BindingSpec[] = [];
|
||||
.option("--reason <reason>", "Reason recorded with connector binding changes", "register human delivery")
|
||||
.option("--actor <actor>", "Named operator when outside a managed seat")
|
||||
.action(async (entityId: string, opts: { displayName: string; binding: string[]; deliveryClass: string; away?: boolean; replace?: boolean; reason: string; actor?: string }) => {
|
||||
// LAZY import the narrow daemon surface at invocation (dep rail 2).
|
||||
const registry = await import("@openrig/daemon/gateway-human-registry");
|
||||
const { addHumanFragment, parseBindingSpec } = registry as unknown as {
|
||||
addHumanFragment: typeof AddHumanFragment;
|
||||
parseBindingSpec: (spec: string) => { ok: true; binding: Record<string, unknown> } | { ok: false; error: string };
|
||||
};
|
||||
// Fix-r1 F1 (R2 blocking, A1/R5): the add VERB is the single-human boundary. A second
|
||||
// DISTINCT human is refused BEFORE any parse or write — the several-fragment state A1
|
||||
// describes as hand-authored must never be produced by the product's own surface.
|
||||
// Fail closed: an unverifiable registry refuses too (never add into an unknown state).
|
||||
const existing = registry.listHumans();
|
||||
if (!existing.ok) {
|
||||
console.error(`refused: cannot verify the single-human boundary — ${existing.error}`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (existing.humans.length > 0 && !existing.humans.some((h) => h.entityId === entityId)) {
|
||||
const ids = existing.humans.map((h) => h.entityId).join(", ");
|
||||
console.error(
|
||||
`refused: a human is already configured (${ids}) — 0.5.5 ships the SIMPLE SINGLE-HUMAN surface (amendment A1, founder R5), so \`rig gateway human add\` manages one human. ` +
|
||||
`If you truly need several, hand-author a fragment YAML under ${registry.humansDir()} (several fragments are displayed honestly, with an advisory); multi-human MANAGEMENT arrives in 0.5.7.`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const bindings: Record<string, unknown>[] = [];
|
||||
for (const spec of opts.binding) {
|
||||
const b = parseBinding(spec);
|
||||
if ("error" in b) { console.error(b.error); process.exitCode = 1; return; }
|
||||
bindings.push(b);
|
||||
const b = parseBindingSpec(spec);
|
||||
if (!b.ok) { console.error(`--binding: ${b.error}`); process.exitCode = 1; return; }
|
||||
bindings.push(b.binding);
|
||||
}
|
||||
const fragment: Record<string, unknown> = {
|
||||
entityId,
|
||||
@@ -78,11 +176,130 @@ export function gatewayCommand(): Command {
|
||||
connectorBindings: bindings,
|
||||
prefs: { deliveryClass: opts.deliveryClass, ...(opts.away ? { away: true } : {}) },
|
||||
};
|
||||
// LAZY import the narrow daemon surface at invocation (dep rail 2).
|
||||
const { addHumanFragment } = (await import("@openrig/daemon/gateway-human-registry")) as { addHumanFragment: typeof AddHumanFragment };
|
||||
const res = addHumanFragment(fragment, undefined, { replace: !!opts.replace });
|
||||
const before = registry.showHuman(entityId);
|
||||
try {
|
||||
const result = await registry.runChannelOperation({
|
||||
actor: resolveSenderSession() ?? opts.actor ?? SENDER_FALLBACK, provenance: "claimed:v1", reason: opts.reason,
|
||||
action: "binding", subject: `${entityId}@external`,
|
||||
before: { digest: registry.channelStateDigest(before.ok ? before.record.connectorBindings.map(({ inboundResolvable: _derived, ...binding }) => binding) : null) },
|
||||
run: async () => {
|
||||
const res = addHumanFragment(fragment, undefined, { replace: !!opts.replace });
|
||||
if (!res.ok) throw new Error(res.error);
|
||||
const digest = registry.channelStateDigest(res.fragment.connectorBindings);
|
||||
return { value: res, after: { digest }, effect: before.ok && registry.channelStateDigest(before.record.connectorBindings.map(({ inboundResolvable: _derived, ...binding }) => binding)) === digest ? "no-op" : "applied" };
|
||||
},
|
||||
});
|
||||
console.log(JSON.stringify({ ok: true, entityId, path: result.value.path, receipt: result.receipt }));
|
||||
} catch (error) { console.error(`refused: ${(error as Error).message}`); process.exitCode = 1; }
|
||||
});
|
||||
|
||||
// ── S12 (OPR.0.5.5.12): the fragment lifecycle beyond add. Every verb operates through
|
||||
// fragments + regeneration (the registry surface); none writes the generated projection.
|
||||
|
||||
human
|
||||
.command("list")
|
||||
.description("Show the configured human (single-human surface per A1/R5; several fragments render honestly with a 0.5.7 advisory)")
|
||||
.option("--json", "Complete record(s) as JSON")
|
||||
.action(async (opts: { json?: boolean }) => {
|
||||
const { listHumans } = await import("@openrig/daemon/gateway-human-registry");
|
||||
const res = listHumans();
|
||||
if (!res.ok) { console.error(`refused: ${res.error}`); process.exitCode = 1; return; }
|
||||
console.log(JSON.stringify({ ok: true, entityId: res.fragment.entityId, path: res.path }));
|
||||
const humans = await Promise.all(res.humans.map(async (record) => ({ ...record, deliveryReadiness: await humanReadiness(record.entityId) })));
|
||||
if (opts.json) { console.log(JSON.stringify({ ok: true, humans, ...(res.advisory ? { advisory: res.advisory } : {}) })); return; }
|
||||
if (res.humans.length === 0) { console.log("no human configured yet — register one: rig gateway human add <entityId> --display-name … --binding … --delivery-class …"); return; }
|
||||
for (const h of humans) {
|
||||
const inbound = h.bindings.inboundResolvable ? "" : " [outbound-only]";
|
||||
console.log(`${h.entityId} "${h.displayName}" class=${h.deliveryClass} ${h.away ? "away" : "available"} bindings=${h.bindings.count} (primary ${h.bindings.primary.kind}:${h.bindings.primary.connectorRef}) delivery=${h.deliveryReadiness.state}${inbound}`);
|
||||
if (!h.deliveryReadiness.ready) console.log(` delivery: ${h.deliveryReadiness.reason}${h.deliveryReadiness.nextAction ? `; next: ${h.deliveryReadiness.nextAction}` : ""}`);
|
||||
}
|
||||
if (res.advisory) console.log(`advisory: ${res.advisory}`);
|
||||
});
|
||||
|
||||
human
|
||||
.command("show <entityId>")
|
||||
.description("Show the effective record: fragment values + which defaults filled the rest, with provenance")
|
||||
.option("--json", "Complete record as JSON")
|
||||
.action(async (entityId: string, opts: { json?: boolean }) => {
|
||||
const { showHuman } = await import("@openrig/daemon/gateway-human-registry");
|
||||
const res = showHuman(entityId);
|
||||
if (!res.ok) { console.error(`refused: ${res.error}`); process.exitCode = 1; return; }
|
||||
const r = { ...res.record, deliveryReadiness: await humanReadiness(entityId) };
|
||||
if (opts.json) { console.log(JSON.stringify({ ok: true, record: r })); return; }
|
||||
console.log(`${r.entityId} (${r.address}) — "${r.displayName}"`);
|
||||
console.log(` fragment: ${r.fragmentPath}`);
|
||||
console.log(` delivery-class: ${r.prefs.deliveryClass.value} (${r.prefs.deliveryClass.source})`);
|
||||
console.log(` away: ${r.prefs.away.value} (${r.prefs.away.source})`);
|
||||
r.connectorBindings.forEach((b, i) => {
|
||||
console.log(` binding.${i}: ${b.kind}:${b.connectorRef} role=${b.role}${b.handle ? ` handle=${b.handle}` : " [outbound-only]"}`);
|
||||
});
|
||||
console.log(` delivery-readiness: ${r.deliveryReadiness.state} (${r.deliveryReadiness.reason})`);
|
||||
if (r.deliveryReadiness.nextAction) console.log(` next: ${r.deliveryReadiness.nextAction}`);
|
||||
});
|
||||
|
||||
human
|
||||
.command("set <entityId> <field> <value>")
|
||||
.description("Edit one field through the verb (same validation as add; re-projection immediate). Fields: display-name, delivery-class, away, binding.<n>")
|
||||
.option("--reason <reason>", "Reason recorded with connector binding changes", "update human binding")
|
||||
.option("--actor <actor>", "Named operator when outside a managed seat")
|
||||
.action(async (entityId: string, field: string, value: string, opts: { reason: string; actor?: string }) => {
|
||||
const registry = await import("@openrig/daemon/gateway-human-registry");
|
||||
if (field.startsWith("binding.")) {
|
||||
const before = registry.showHuman(entityId);
|
||||
if (!before.ok) { console.error(`refused: ${before.error}`); process.exitCode = 1; return; }
|
||||
try {
|
||||
const prior = registry.channelStateDigest(before.record.connectorBindings.map(({ inboundResolvable: _derived, ...binding }) => binding));
|
||||
const result = await registry.runChannelOperation({
|
||||
actor: resolveSenderSession() ?? opts.actor ?? SENDER_FALLBACK, provenance: "claimed:v1", reason: opts.reason,
|
||||
action: "binding", subject: `${entityId}@external`, before: { digest: prior },
|
||||
run: async () => {
|
||||
const res = registry.setHumanField(entityId, field, value);
|
||||
if (!res.ok) throw new Error(res.error);
|
||||
const digest = registry.channelStateDigest(res.fragment.connectorBindings);
|
||||
return { value: res, after: { digest }, effect: digest === prior ? "no-op" : "applied" };
|
||||
},
|
||||
});
|
||||
console.log(JSON.stringify({ ok: true, entityId, field, path: result.value.path, receipt: result.receipt }));
|
||||
} catch (error) { console.error(`refused: ${(error as Error).message}`); process.exitCode = 1; }
|
||||
return;
|
||||
}
|
||||
const res = registry.setHumanField(entityId, field, value);
|
||||
if (!res.ok) { console.error(`refused: ${res.error}`); process.exitCode = 1; return; }
|
||||
console.log(JSON.stringify({ ok: true, entityId: res.fragment.entityId, field, path: res.path }));
|
||||
});
|
||||
|
||||
human
|
||||
.command("remove <entityId>")
|
||||
.description("Remove a human: refuses while open conversations or non-terminal rows exist (--force archives and records what was orphaned; the fragment is archived, never deleted)")
|
||||
.option("--force", "Archive despite KNOWN in-flight items (each is recorded as orphaned)")
|
||||
.action(async (entityId: string, opts: { force?: boolean }) => {
|
||||
const { removeHumanFragment, pendingConversationsFor, ADDRESS_DOMAIN } =
|
||||
await import("@openrig/daemon/gateway-human-registry");
|
||||
const address = `${entityId}@${ADDRESS_DOMAIN}`;
|
||||
// Guard input 1 (fs): un-Acked outbound decisions = open conversations.
|
||||
const conversations = pendingConversationsFor(entityId);
|
||||
// Guard input 2 (daemon): non-terminal rows addressed to the human. An unreachable
|
||||
// board is INDETERMINATE — remove refuses rather than fabricating an absence, and
|
||||
// --force does not apply (it overrides KNOWN in-flight work, not ignorance of it).
|
||||
const rowsRes = await queueRows(address);
|
||||
if (!rowsRes.ok) {
|
||||
console.error(
|
||||
`refused: the queue could not be checked for non-terminal rows addressed to ${address} (${rowsRes.error}) — ` +
|
||||
`remove needs a reachable daemon to prove nothing would be orphaned; --force does not override an unchecked board.`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const inflight = [
|
||||
...conversations,
|
||||
...rowsRes.rows.map((r) => ({
|
||||
kind: "queue-row" as const,
|
||||
id: r.id,
|
||||
detail: `${r.state} row ${r.id}${r.summary ? ` — ${r.summary}` : ""}`,
|
||||
})),
|
||||
];
|
||||
const res = removeHumanFragment(entityId, { force: !!opts.force, inflight });
|
||||
if (!res.ok) { console.error(`refused: ${res.error}`); process.exitCode = 1; return; }
|
||||
console.log(JSON.stringify({ ok: true, removed: res.removed, archivedPath: res.archivedPath, ...(res.orphanRecordPath ? { orphanRecordPath: res.orphanRecordPath } : {}) }));
|
||||
});
|
||||
|
||||
return cmd;
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
import { resolve as resolvePath } from "node:path";
|
||||
import { Command } from "commander";
|
||||
import { DaemonClient } from "../client.js";
|
||||
import { getDaemonStatus, getDaemonUrl, daemonStatusGuard } from "../daemon-lifecycle.js";
|
||||
import { realDeps } from "./daemon.js";
|
||||
import type { StatusDeps } from "./status.js";
|
||||
import { resolveDefaultAgentRef } from "./topology-default-agent.js";
|
||||
|
||||
interface RigNode {
|
||||
rigName: string;
|
||||
podNamespace: string | null;
|
||||
}
|
||||
|
||||
interface GrowNode {
|
||||
logicalId: string;
|
||||
status: "launched" | "failed" | "attention_required";
|
||||
sessionName?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface AddMemberResult {
|
||||
ok: boolean;
|
||||
result?: {
|
||||
podNamespace: string;
|
||||
node: GrowNode;
|
||||
};
|
||||
errors?: string[];
|
||||
message?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface ExpandResult {
|
||||
ok: boolean;
|
||||
status?: "ok" | "partial" | "failed";
|
||||
podNamespace?: string;
|
||||
nodes?: GrowNode[];
|
||||
warnings?: string[];
|
||||
retryTargets?: string[];
|
||||
errors?: string[];
|
||||
message?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export function growCommand(depsOverride?: StatusDeps): Command {
|
||||
const cmd = new Command("grow").description("Add one or more seats to a running rig");
|
||||
const getDeps = () => depsOverride ?? {
|
||||
lifecycleDeps: realDeps(),
|
||||
clientFactory: (url: string) => new DaemonClient(url),
|
||||
};
|
||||
|
||||
cmd
|
||||
.argument("<rig-id>", "Target rig ID")
|
||||
.argument("<members...>", "Names for the new seats")
|
||||
.option("--pod <pod>", "Target pod; inferred when the rig has one pod")
|
||||
.option("--new-pod <pod>", "Create a new pod for the seats")
|
||||
.option("--runtime <runtime>", "Agent runtime", "claude-code")
|
||||
.option("--cwd <path>", "Working directory for the seat", process.cwd())
|
||||
.option("--json", "JSON output for agents")
|
||||
.action(async (
|
||||
rigId: string,
|
||||
members: string[],
|
||||
opts: { pod?: string; newPod?: string; runtime: string; cwd: string; json?: boolean },
|
||||
) => {
|
||||
if (opts.pod && opts.newPod) {
|
||||
console.error("Choose either --pod or --new-pod, not both.");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const deps = getDeps();
|
||||
const status = await getDaemonStatus(deps.lifecycleDeps);
|
||||
if (!daemonStatusGuard(status)) return;
|
||||
const client = deps.clientFactory(getDaemonUrl(status));
|
||||
|
||||
const rigRes = await client.get<RigNode[]>(`/api/rigs/${encodeURIComponent(rigId)}/nodes`);
|
||||
if (rigRes.status >= 400) {
|
||||
console.error(`Rig ${rigId} was not found.`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const rigName = rigRes.data[0]?.rigName;
|
||||
const pods = [...new Set(
|
||||
rigRes.data
|
||||
.map((node) => node.podNamespace)
|
||||
.filter((namespace): namespace is string => typeof namespace === "string"),
|
||||
)];
|
||||
if (opts.newPod && pods.includes(opts.newPod)) {
|
||||
console.error(`Pod ${opts.newPod} already exists. Use --pod ${opts.newPod} to grow it.`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const pod = opts.newPod
|
||||
?? (opts.pod
|
||||
? pods.find((candidate) => candidate === opts.pod)
|
||||
: pods.length === 1
|
||||
? pods[0]
|
||||
: undefined);
|
||||
if (!pod) {
|
||||
const available = pods.join(", ") || "none";
|
||||
console.error(opts.pod
|
||||
? `Pod ${opts.pod} was not found. Available pods: ${available}.`
|
||||
: `Choose a pod with --pod. Available pods: ${available}.`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
let agentRef: string;
|
||||
try {
|
||||
agentRef = await resolveDefaultAgentRef(client);
|
||||
} catch (err) {
|
||||
console.error(err instanceof Error ? err.message : String(err));
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const cwd = resolvePath(opts.cwd);
|
||||
const memberSpecs = members.map((member) => ({
|
||||
id: member,
|
||||
agent_ref: agentRef,
|
||||
runtime: opts.runtime,
|
||||
profile: "default",
|
||||
cwd,
|
||||
}));
|
||||
let nodes: GrowNode[] = [];
|
||||
let ok = false;
|
||||
let detail: string | undefined;
|
||||
let warnings: string[] = [];
|
||||
let retryTargets: string[] = [];
|
||||
|
||||
if (opts.newPod) {
|
||||
const res = await client.post<ExpandResult>(
|
||||
`/api/rigs/${encodeURIComponent(rigId)}/expand`,
|
||||
{
|
||||
pod: { id: pod, label: pod, members: memberSpecs, edges: [] },
|
||||
rigRoot: cwd,
|
||||
},
|
||||
{ timeoutMs: 120_000 },
|
||||
);
|
||||
nodes = res.data.nodes ?? [];
|
||||
warnings = res.data.warnings ?? [];
|
||||
retryTargets = res.data.retryTargets ?? [];
|
||||
ok = res.status < 400
|
||||
&& res.data.ok
|
||||
&& res.data.status === "ok"
|
||||
&& nodes.length === members.length
|
||||
&& nodes.every((node) => node.status === "launched");
|
||||
detail = res.data.errors?.join("; ")
|
||||
?? res.data.message
|
||||
?? res.data.error
|
||||
?? nodes.find((node) => node.error)?.error;
|
||||
const returned = new Set(nodes.map((node) => node.logicalId));
|
||||
for (const member of memberSpecs) {
|
||||
const logicalId = `${pod}.${member.id}`;
|
||||
if (!returned.has(logicalId)) {
|
||||
nodes.push({ logicalId, status: "failed", error: detail ?? "No node outcome returned." });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const failures: string[] = [];
|
||||
for (const member of memberSpecs) {
|
||||
const res = await client.post<AddMemberResult>(
|
||||
`/api/rigs/${encodeURIComponent(rigId)}/pods/${encodeURIComponent(pod)}/members`,
|
||||
{ member, rigRoot: cwd },
|
||||
{ timeoutMs: 120_000 },
|
||||
);
|
||||
const responseDetail = res.data.errors?.join("; ")
|
||||
?? res.data.message
|
||||
?? res.data.error
|
||||
?? res.data.result?.node.error
|
||||
?? `Grow failed (HTTP ${res.status})`;
|
||||
const node = res.data.result?.node ?? {
|
||||
logicalId: `${pod}.${member.id}`,
|
||||
status: "failed" as const,
|
||||
error: responseDetail,
|
||||
};
|
||||
nodes.push(node);
|
||||
if (res.status >= 400 || !res.data.ok || node.status !== "launched") {
|
||||
failures.push(`${member.id}: ${responseDetail}`);
|
||||
}
|
||||
}
|
||||
ok = failures.length === 0;
|
||||
detail = failures.join("; ") || undefined;
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify({
|
||||
ok,
|
||||
rigId,
|
||||
rigName,
|
||||
pod,
|
||||
seats: nodes.map((node) => node.logicalId),
|
||||
source: agentRef,
|
||||
...(nodes.length === 1 ? { seat: nodes[0]?.logicalId, node: nodes[0] } : {}),
|
||||
nodes,
|
||||
...(detail ? { detail } : {}),
|
||||
warnings,
|
||||
retryTargets,
|
||||
}, null, 2));
|
||||
} else if (ok) {
|
||||
console.log(`Grew rig ${rigName ?? rigId}`);
|
||||
if (nodes.length === 1) {
|
||||
const node = nodes[0]!;
|
||||
console.log(` Seat: ${node.logicalId}${node.sessionName ? ` (${node.sessionName})` : ""}`);
|
||||
} else {
|
||||
console.log(" Seats:");
|
||||
for (const node of nodes) {
|
||||
console.log(` ${node.logicalId}${node.sessionName ? ` (${node.sessionName})` : ""}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error(`Grow did not fully succeed for ${rigName ?? rigId}`);
|
||||
for (const node of nodes) {
|
||||
const icon = node.status === "launched" ? "OK" : node.status === "attention_required" ? "ATTENTION" : "FAIL";
|
||||
const session = node.sessionName ? ` (${node.sessionName})` : "";
|
||||
const error = node.error ? ` - ${node.error}` : "";
|
||||
console.error(` [${icon}] ${node.logicalId}: ${node.status}${session}${error}`);
|
||||
}
|
||||
if (detail && !nodes.some((node) => node.error && detail.includes(node.error))) {
|
||||
console.error(` ${detail}`);
|
||||
}
|
||||
for (const warning of warnings) console.error(` Warning: ${warning}`);
|
||||
if (retryTargets.length > 0) {
|
||||
console.error(" Failed nodes can be relaunched individually. Recover by:");
|
||||
for (const target of retryTargets) console.error(` rig launch ${rigId} ${target}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) process.exitCode = 1;
|
||||
});
|
||||
|
||||
return cmd;
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Command, InvalidArgumentError, Option } from "commander";
|
||||
import {
|
||||
HEALTH_SEVERITIES,
|
||||
HEALTH_STATUSES,
|
||||
type HealthRecord,
|
||||
type HealthScope,
|
||||
} from "@openrig/daemon/health-projection";
|
||||
import type { HealthListProjection } from "@openrig/daemon/health-detectors";
|
||||
import {
|
||||
DaemonClient,
|
||||
DaemonConnectionError,
|
||||
DaemonResponseError,
|
||||
DaemonTimeoutError,
|
||||
} from "../client.js";
|
||||
import {
|
||||
getDaemonStatus,
|
||||
getDaemonUrl,
|
||||
statusGuardMessage,
|
||||
} from "../daemon-lifecycle.js";
|
||||
import { realDeps } from "./daemon.js";
|
||||
import { resolveIdentitySource } from "./whoami.js";
|
||||
import type { StatusDeps } from "./status.js";
|
||||
import { shellQuote } from "../cross-host-executor.js";
|
||||
import { omittedReadField, readView } from "../read-view.js";
|
||||
|
||||
interface DiagnosisEntry {
|
||||
row: Record<string, unknown> & { qitemId: string };
|
||||
finding: HealthRecord;
|
||||
packet: Record<string, unknown> & { instructions: string };
|
||||
receipts: unknown[];
|
||||
authority: Array<{ path: string; state: string; content?: string; role?: string; reason?: string; selectedBy?: string }>;
|
||||
disposition: { verdict: string; causalStart: string | null; steering: string; uncertainty: string; evidenceRefs: string[];
|
||||
correction?: { applicability: string; causalJudgment: string; action: { state: string; summary: string }; effect: { state: string; summary: string } } } | null;
|
||||
assessment?: { actor?: string; at: string } | null;
|
||||
behavioralEffect?: string;
|
||||
guidance?: string;
|
||||
humanDelivery: { qitemId: string; outcome: string } | null;
|
||||
notificationReadiness?: { ready: boolean; reason: string } | null;
|
||||
}
|
||||
|
||||
function diagnosisPreview(entry: DiagnosisEntry) {
|
||||
const omitted: ReturnType<typeof omittedReadField>[] = [];
|
||||
// ponytail: omit only known evidence payloads; keep current decisions and unknowns intact.
|
||||
function without<T extends object>(record: T, keys: string[], prefix = ""): T {
|
||||
const copy = { ...record } as Record<string, unknown>;
|
||||
for (const key of keys) {
|
||||
const value = copy[key];
|
||||
if (value === undefined || value === null || value === "" || (Array.isArray(value) && !value.length)) continue;
|
||||
omitted.push(omittedReadField(`${prefix}${key}`, value));
|
||||
delete copy[key];
|
||||
}
|
||||
return copy as T;
|
||||
}
|
||||
const result = without(entry, ["receipts", "guidance"]);
|
||||
result.row = without(entry.row, ["body", "chainOfRecord"], "row.");
|
||||
result.packet = without(entry.packet, ["finding", "authority", "instructions"], "packet.");
|
||||
result.finding = without(entry.finding, ["evidence"], "finding.");
|
||||
if (entry.finding.ceremony) {
|
||||
result.finding.ceremony = { ...entry.finding.ceremony,
|
||||
workflowReceipts: entry.finding.ceremony.workflowReceipts.map((receipt, index) => {
|
||||
const envelope = without(receipt, ["evidence"], `finding.ceremony.workflowReceipts[${index}].`);
|
||||
const evidence = receipt.evidence;
|
||||
// These are the existing cut/acceptance identity spellings, not a verdict on the receipt.
|
||||
const identity = evidence && typeof evidence === "object" && !Array.isArray(evidence)
|
||||
? Object.fromEntries(Object.entries(evidence).filter(([key, value]) =>
|
||||
["candidate", "candidateSha", "candidate_sha", "cutSha", "tree", "verdict", "evidenceRef", "evidence_ref"].includes(key)
|
||||
&& typeof value === "string"))
|
||||
: {};
|
||||
return { ...envelope, evidenceIdentity: identity };
|
||||
}),
|
||||
};
|
||||
}
|
||||
result.authority = entry.authority.map((ref, index) => without(ref, ["content"], `authority[${index}].`));
|
||||
return { ...result, readView: readView(entry, `rig health diagnosis show ${shellQuote(entry.row.qitemId)} --full --json`, omitted) };
|
||||
}
|
||||
|
||||
const HEALTH_ERROR_SCHEMA = "openrig.health-error/v0alpha1" as const;
|
||||
|
||||
type IdentitySource = { nodeId?: string; sessionName?: string };
|
||||
|
||||
export interface HealthDeps extends StatusDeps {
|
||||
resolveIdentity: () => IdentitySource | null;
|
||||
}
|
||||
|
||||
interface HealthListOptions {
|
||||
self?: boolean;
|
||||
seat?: string;
|
||||
rig?: string;
|
||||
instance?: string | boolean;
|
||||
severity?: string;
|
||||
status?: string;
|
||||
limit?: number;
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
interface HealthCliError {
|
||||
schema: typeof HEALTH_ERROR_SCHEMA;
|
||||
error: string;
|
||||
message: string;
|
||||
nextInspection: string;
|
||||
details?: unknown;
|
||||
}
|
||||
|
||||
function defaultDeps(): HealthDeps {
|
||||
return {
|
||||
lifecycleDeps: realDeps(),
|
||||
clientFactory: (baseUrl: string) => new DaemonClient(baseUrl),
|
||||
resolveIdentity: () => resolveIdentitySource({}),
|
||||
};
|
||||
}
|
||||
|
||||
function parseLimit(value: string): number {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 200) {
|
||||
throw new InvalidArgumentError("must be an integer from 1 to 200");
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function emitError(error: HealthCliError, json: boolean): void {
|
||||
if (json) {
|
||||
console.log(JSON.stringify(error));
|
||||
} else {
|
||||
console.error(`Error: ${error.message}`);
|
||||
console.error(` Next inspection: ${error.nextInspection}`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
function daemonError(details: unknown): HealthCliError {
|
||||
return {
|
||||
schema: HEALTH_ERROR_SCHEMA,
|
||||
error: "health_daemon_unavailable",
|
||||
message: "The health projection could not be read because the daemon did not respond.",
|
||||
nextInspection: "rig daemon status",
|
||||
details,
|
||||
};
|
||||
}
|
||||
|
||||
function responseError(status: number, data: unknown): HealthCliError {
|
||||
const daemonCode = typeof data === "object" && data !== null && typeof (data as { error?: unknown }).error === "string"
|
||||
? (data as { error: string }).error
|
||||
: null;
|
||||
if (status === 503 && daemonCode === "health_projection_unavailable") {
|
||||
return {
|
||||
schema: HEALTH_ERROR_SCHEMA,
|
||||
error: daemonCode,
|
||||
message: "The daemon is reachable, but its health projection is unavailable.",
|
||||
nextInspection: "rig daemon status",
|
||||
};
|
||||
}
|
||||
if (status === 404 && daemonCode === "health_finding_not_found") {
|
||||
return {
|
||||
schema: HEALTH_ERROR_SCHEMA,
|
||||
error: daemonCode,
|
||||
message: "No health finding exists with that ID in the current projection.",
|
||||
nextInspection: "rig health --instance --json",
|
||||
};
|
||||
}
|
||||
if (status === 404 && daemonCode === "not_found") {
|
||||
return {
|
||||
schema: HEALTH_ERROR_SCHEMA,
|
||||
error: "health_projection_unavailable",
|
||||
message: "The daemon is reachable, but it does not expose the health projection.",
|
||||
nextInspection: "rig --version",
|
||||
details: data,
|
||||
};
|
||||
}
|
||||
return {
|
||||
schema: HEALTH_ERROR_SCHEMA,
|
||||
error: "health_query_failed",
|
||||
message: `The daemon rejected the health query (HTTP ${status}).`,
|
||||
nextInspection: "rig health --help",
|
||||
details: data,
|
||||
};
|
||||
}
|
||||
|
||||
async function readyClient(deps: HealthDeps, json: boolean): Promise<DaemonClient | null> {
|
||||
const status = await getDaemonStatus(deps.lifecycleDeps, { cleanupStaleState: false });
|
||||
if (status.state !== "running" || status.healthy === false) {
|
||||
emitError(daemonError(statusGuardMessage(status)), json);
|
||||
return null;
|
||||
}
|
||||
return deps.clientFactory(getDaemonUrl(status));
|
||||
}
|
||||
|
||||
async function readSelfSeatId(client: DaemonClient, deps: HealthDeps, json: boolean): Promise<string | null> {
|
||||
const source = deps.resolveIdentity();
|
||||
if (!source) {
|
||||
emitError({
|
||||
schema: HEALTH_ERROR_SCHEMA,
|
||||
error: "health_identity_unavailable",
|
||||
message: "The current seat identity is unavailable, so self health cannot be scoped safely.",
|
||||
nextInspection: "rig whoami --json",
|
||||
}, json);
|
||||
return null;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (source.nodeId) params.set("nodeId", source.nodeId);
|
||||
else if (source.sessionName) params.set("sessionName", source.sessionName);
|
||||
params.set("compact", "1");
|
||||
const response = await client.get<Record<string, unknown>>(`/api/whoami?${params.toString()}`);
|
||||
if (response.status === 409) {
|
||||
emitError({
|
||||
schema: HEALTH_ERROR_SCHEMA,
|
||||
error: "health_identity_ambiguous",
|
||||
message: "More than one managed seat matches the current identity.",
|
||||
nextInspection: "rig ps --nodes -A",
|
||||
details: response.data,
|
||||
}, json);
|
||||
return null;
|
||||
}
|
||||
if (response.status >= 400) {
|
||||
emitError({
|
||||
schema: HEALTH_ERROR_SCHEMA,
|
||||
error: "health_identity_unavailable",
|
||||
message: "The daemon could not resolve the current seat identity.",
|
||||
nextInspection: "rig whoami --json",
|
||||
details: response.data,
|
||||
}, json);
|
||||
return null;
|
||||
}
|
||||
const identity = response.data["identity"];
|
||||
const nodeId = typeof identity === "object" && identity !== null
|
||||
? (identity as Record<string, unknown>)["nodeId"]
|
||||
: null;
|
||||
if (typeof nodeId !== "string" || nodeId.length === 0) {
|
||||
emitError({
|
||||
schema: HEALTH_ERROR_SCHEMA,
|
||||
error: "health_identity_indeterminate",
|
||||
message: "The identity response did not carry a stable node ID, so self health is indeterminate.",
|
||||
nextInspection: "rig whoami --full --json",
|
||||
details: response.data,
|
||||
}, json);
|
||||
return null;
|
||||
}
|
||||
return nodeId;
|
||||
}
|
||||
|
||||
function scopeLabel(scope: HealthScope): string {
|
||||
switch (scope.type) {
|
||||
case "instance": return `instance:${scope.instanceId}`;
|
||||
case "rig": return `rig:${scope.rigId}`;
|
||||
case "seat": return `seat:${scope.seatId}`;
|
||||
case "mission": return `mission:${scope.missionId}`;
|
||||
case "slice": return `slice:${scope.sliceId}`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderList(projection: HealthListProjection): void {
|
||||
const evaluated = projection.evaluatedAt ?? "unavailable";
|
||||
console.log(`Fleet health — evaluated=${evaluated} findings=${projection.total} limit=${projection.limit}`);
|
||||
if (projection.records.length === 0) {
|
||||
console.log("No health findings match this bounded query. This is not a healthy assertion.");
|
||||
console.log("Next inspection: widen only as needed with `rig health --instance --json`.");
|
||||
return;
|
||||
}
|
||||
for (const record of projection.records) {
|
||||
console.log(`${record.id} ${record.severity} ${record.status} ${record.detector}`);
|
||||
console.log(` ${scopeLabel(record.scope)} ${record.confidence} confidence freshness=${record.freshness.state}${record.indeterminateReason ? ` indeterminate=${record.indeterminateReason}` : ""}`);
|
||||
console.log(` ${record.summary}`);
|
||||
if (record.operatingPosture) console.log(` Operating posture: ${record.operatingPosture.posture} (${record.operatingPosture.source}); phase=${record.operatingPosture.context?.phase.value ?? "unknown"}`);
|
||||
}
|
||||
if (projection.truncated) {
|
||||
console.log(`Truncated at ${projection.limit} of ${projection.total}; narrow the scope or raise --limit (maximum 200).`);
|
||||
}
|
||||
}
|
||||
|
||||
function renderExplanation(record: HealthRecord): void {
|
||||
console.log(`Health finding ${record.id}`);
|
||||
console.log(` Detector: ${record.detector} (${record.category})`);
|
||||
console.log(` Scope: ${scopeLabel(record.scope)}`);
|
||||
console.log(` Outcome: ${record.severity} / ${record.status} / ${record.confidence} confidence`);
|
||||
console.log(` Observed: ${record.startedAt ?? "unavailable"} → ${record.lastObservedAt ?? "unavailable"}`);
|
||||
console.log(` Window: ${record.window.startedAt} → ${record.window.endedAt}; source=${record.window.source}; limit=${record.window.limit}; retention=${record.window.retentionSeconds}s`);
|
||||
console.log(` Freshness: ${record.freshness.state}; evaluated=${record.freshness.evaluatedAt}; newest=${record.freshness.newestSourceAt ?? "unavailable"}; max-age=${record.freshness.maxAgeSeconds}s; age=${record.freshness.ageSeconds ?? "unavailable"}s`);
|
||||
if (record.indeterminateReason) console.log(` Indeterminate: ${record.indeterminateReason}`);
|
||||
console.log(` Rule: ${record.threshold}`);
|
||||
console.log(` Policy: ${record.policyVersion ?? "not reported by source"}`);
|
||||
if (record.operatingPosture) console.log(` Posture: ${JSON.stringify(record.operatingPosture)}`);
|
||||
console.log(` Explanation: ${record.explanation}`);
|
||||
console.log(` Evidence: ${JSON.stringify(record.evidence)}`);
|
||||
if (record.ceremony) console.log(` Diagnosis stage: ${record.ceremony.stage}\n Normal context: ${JSON.stringify(record.ceremony)}`);
|
||||
console.log(` Inspect: ${record.suggestedInspection}`);
|
||||
}
|
||||
|
||||
async function guardedRequest<T>(
|
||||
request: () => Promise<{ status: number; data: T }>,
|
||||
json: boolean,
|
||||
): Promise<{ status: number; data: T } | null> {
|
||||
try {
|
||||
return await request();
|
||||
} catch (error) {
|
||||
const details = error instanceof DaemonTimeoutError
|
||||
? { kind: "timeout", message: error.message }
|
||||
: error instanceof DaemonResponseError
|
||||
? { kind: "unreadable-response", status: error.status, bodySnippet: error.bodySnippet }
|
||||
: error instanceof DaemonConnectionError
|
||||
? { kind: "connection", message: error.message }
|
||||
: { kind: "unexpected", message: error instanceof Error ? error.message : String(error) };
|
||||
emitError(daemonError(details), json);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function healthCommand(depsOverride?: HealthDeps): Command {
|
||||
const deps = depsOverride ?? defaultDeps();
|
||||
const command = new Command("health")
|
||||
.description("Inspect read-only, explainable system health records")
|
||||
.addOption(new Option("--self", "Inspect the current seat (default)").conflicts(["seat", "rig", "instance"]))
|
||||
.addOption(new Option("--seat <node-id>", "Inspect one seat by stable node ID").conflicts(["self", "rig", "instance"]))
|
||||
.addOption(new Option("--rig <rig-id>", "Inspect findings whose canonical scope is this rig").conflicts(["self", "seat", "instance"]))
|
||||
.addOption(new Option("--instance [instance-id]", "Inspect the whole local instance, or one canonical instance scope by ID").conflicts(["self", "seat", "rig"]))
|
||||
.addOption(new Option("--severity <severity>", "Filter by severity").choices([...HEALTH_SEVERITIES]))
|
||||
.addOption(new Option("--status <status>", "Filter by finding status").choices([...HEALTH_STATUSES]))
|
||||
.option("--limit <count>", "Maximum findings (1-200)", parseLimit, 100)
|
||||
.option("--json", "JSON output; diagnosis list/show need --full for complete evidence")
|
||||
.option("--actor <name>", "Attribute explicit diagnosis writes when operating outside a managed seat")
|
||||
.addHelpText("after", `
|
||||
The default scope is the current seat. --instance without an ID is the bounded
|
||||
instance-wide projection. Empty output is not a healthy assertion. This command
|
||||
list/explain never mutate. Diagnosis mutations use explicit subcommands; automatic diagnostic presentation is opt-in policy.`);
|
||||
|
||||
command.action(async (options: HealthListOptions) => {
|
||||
const json = Boolean(options.json);
|
||||
const client = await readyClient(deps, json);
|
||||
if (!client) return;
|
||||
|
||||
let scopeType: HealthScope["type"] | undefined;
|
||||
let scopeId: string | undefined;
|
||||
if (options.seat) {
|
||||
scopeType = "seat";
|
||||
scopeId = options.seat;
|
||||
} else if (options.rig) {
|
||||
scopeType = "rig";
|
||||
scopeId = options.rig;
|
||||
} else if (typeof options.instance === "string") {
|
||||
scopeType = "instance";
|
||||
scopeId = options.instance;
|
||||
} else if (!options.instance) {
|
||||
const seatId = await guardedRequest(
|
||||
() => readSelfSeatId(client, deps, json).then((data) => ({ status: data === null ? 1 : 0, data })),
|
||||
json,
|
||||
);
|
||||
if (!seatId || seatId.data === null) return;
|
||||
scopeType = "seat";
|
||||
scopeId = seatId.data;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (scopeType && scopeId) {
|
||||
params.set("scope_type", scopeType);
|
||||
params.set("scope_id", scopeId);
|
||||
}
|
||||
params.set("limit", String(options.limit ?? 100));
|
||||
if (options.severity) params.set("severity", options.severity);
|
||||
if (options.status) params.set("status", options.status);
|
||||
const response = await guardedRequest(
|
||||
() => client.get<HealthListProjection>(`/api/health?${params.toString()}`),
|
||||
json,
|
||||
);
|
||||
if (!response) return;
|
||||
if (response.status >= 400) {
|
||||
emitError(responseError(response.status, response.data), json);
|
||||
return;
|
||||
}
|
||||
if (json) console.log(JSON.stringify(response.data));
|
||||
else renderList(response.data);
|
||||
});
|
||||
|
||||
command
|
||||
.command("explain <finding-id>")
|
||||
.description("Explain one health finding from its canonical bounded record")
|
||||
.option("--json", "Emit the canonical daemon health record as JSON")
|
||||
.action(async (findingId: string, options: { json?: boolean }) => {
|
||||
const json = Boolean(options.json || command.opts().json);
|
||||
const client = await readyClient(deps, json);
|
||||
if (!client) return;
|
||||
const response = await guardedRequest(
|
||||
() => client.get<HealthRecord>(`/api/health/${encodeURIComponent(findingId)}`),
|
||||
json,
|
||||
);
|
||||
if (!response) return;
|
||||
if (response.status >= 400) {
|
||||
emitError(responseError(response.status, response.data), json);
|
||||
return;
|
||||
}
|
||||
if (json) console.log(JSON.stringify(response.data));
|
||||
else renderExplanation(response.data);
|
||||
});
|
||||
|
||||
async function diagnosisRequest(path: string, options: { json?: boolean; full?: boolean }, payload?: unknown) {
|
||||
const json = Boolean(options.json || command.opts().json);
|
||||
const client = await readyClient(deps, json);
|
||||
if (!client) return;
|
||||
const response = await guardedRequest(() => payload === undefined ? client.get(`/api/health-diagnosis${path}`) : client.post(`/api/health-diagnosis${path}`, { ...(payload as object), actor: command.opts().actor }), json);
|
||||
if (!response) return;
|
||||
if (response.status >= 400) { console.error(JSON.stringify(response.data)); process.exitCode = 1; return; }
|
||||
const occurrenceRead = payload === undefined && path !== "/policy" && path !== "/checkpoints";
|
||||
if (options.full || json || path === "/policy" || path === "/checkpoints" || path === "/evaluate" || path.endsWith("/notify")) {
|
||||
const data = occurrenceRead && !options.full
|
||||
? (Array.isArray(response.data) ? response.data.map((entry) => diagnosisPreview(entry as DiagnosisEntry)) : diagnosisPreview(response.data as DiagnosisEntry))
|
||||
: response.data;
|
||||
console.log(JSON.stringify(data, null, json ? undefined : 2));
|
||||
} else {
|
||||
const entries = Array.isArray(response.data) ? response.data : [response.data];
|
||||
if (!entries.length) console.log("No diagnostic occurrences. This is not a healthy assertion.");
|
||||
for (const entry of entries as DiagnosisEntry[]) {
|
||||
console.log(`${entry.row.qitemId} ${entry.finding.status} ${entry.finding.detector}`);
|
||||
console.log(` Disposition: ${entry.disposition?.verdict ?? "awaiting agent investigation"}`);
|
||||
if (path) {
|
||||
console.log(` Queue: ${entry.row.state ?? "unknown"} Owner: ${entry.row.destinationSession ?? "unknown"} Blocker: ${entry.row.blockedOn ?? "none recorded"}`);
|
||||
if (entry.notificationReadiness) console.log(` Human readiness: ${entry.notificationReadiness.ready ? "ready" : "unavailable"} — ${entry.notificationReadiness.reason}`);
|
||||
if (entry.humanDelivery) console.log(` Human delivery: ${entry.humanDelivery.outcome} (${entry.humanDelivery.qitemId})`);
|
||||
console.log(` Finding: ${entry.finding.id} Policy: ${entry.finding.policyVersion ?? "unreported"}`);
|
||||
console.log(` ${entry.finding.explanation}`);
|
||||
if (entry.finding.operatingPosture) console.log(` Posture: ${entry.finding.operatingPosture.posture} (${entry.finding.operatingPosture.source}); phase=${entry.finding.operatingPosture.context?.phase.value ?? "unknown"}; ${entry.finding.operatingPosture.reason}`);
|
||||
console.log(` Start: ${entry.disposition?.causalStart ?? "unknown"}`);
|
||||
console.log(` Steering: ${entry.disposition?.steering ?? "not yet recorded"}`);
|
||||
console.log(` Uncertainty: ${entry.disposition?.uncertainty ?? entry.finding.indeterminateReason ?? "diagnosis pending"}`);
|
||||
if (entry.assessment) console.log(` Attributed assessment: ${entry.assessment.actor ?? "unknown"} at ${entry.assessment.at}`);
|
||||
const correction = entry.disposition?.correction;
|
||||
if (correction) {
|
||||
console.log(` Applicability: ${correction.applicability}`);
|
||||
console.log(` Causal judgment: ${correction.causalJudgment}`);
|
||||
console.log(` Action (${correction.action.state}): ${correction.action.summary}`);
|
||||
}
|
||||
console.log(` Later behavioral effect (owner report): ${correction?.effect.state ?? "unobserved"}${correction ? " — " + correction.effect.summary : " — a disposition or clearance alone is not changed behavior"}`);
|
||||
for (const ref of entry.authority) console.log(` ${ref.role ?? "Authority"} (${ref.state}): ${ref.path}${ref.selectedBy ? " selected by " + ref.selectedBy : ""}${ref.reason ? " — " + ref.reason : ""}`);
|
||||
if (occurrenceRead) {
|
||||
const view = diagnosisPreview(entry).readView;
|
||||
console.log(` Evidence preview; full record ${view.fullJsonBytes} JSON bytes: ${view.fullCommand}`);
|
||||
} else console.log(` ${entry.packet.instructions}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function fromFile(file: string): unknown {
|
||||
const text = readFileSync(file, "utf8");
|
||||
if (Buffer.byteLength(text) > 1048576) throw new Error("Health input exceeds 1 MiB");
|
||||
return JSON.parse(text);
|
||||
}
|
||||
command.command("policy").description("Inspect effective policy and engine state; apply edited JSON with --file")
|
||||
.option("--file <path>", "Apply policy JSON with an audit record").option("--json")
|
||||
.action(async (o: { file?: string; json?: boolean }) => diagnosisRequest("/policy", o, o.file ? { value: fromFile(o.file) } : undefined));
|
||||
command.command("checkpoint").description("Inspect or submit an outcome-boundary lineage census (not a per-edit ritual)")
|
||||
.option("--file <path>", "Submit checkpoint JSON with exact queue and product evidence").option("--json")
|
||||
.action(async (o: { file?: string; json?: boolean }) => diagnosisRequest("/checkpoints", o, o.file ? { value: fromFile(o.file) } : undefined));
|
||||
command.command("diagnose").description("Preview policy admission; --apply creates or re-presents bounded diagnostic context")
|
||||
.option("--apply").option("--json").action(async (o: { apply?: boolean; json?: boolean }) => diagnosisRequest("/evaluate", o, { apply: Boolean(o.apply) }));
|
||||
const diagnosis = command.command("diagnosis").description("Read occurrences and record agent-owned dispositions");
|
||||
diagnosis.command("list").description("List occurrence summaries; evidence payloads require --full")
|
||||
.option("--json", "Summary array with explicit omitted fields and per-occurrence expansion commands")
|
||||
.option("--full", "Complete records including all evidence and receipts; may be large")
|
||||
.action(async (o: { json?: boolean; full?: boolean }) => diagnosisRequest("", o));
|
||||
diagnosis.command("show <qitem-id>").description("Inspect current state and decisions; expand retained evidence deliberately")
|
||||
.option("--json", "Summary JSON with omitted fields, original byte size and exact full command")
|
||||
.option("--full", "Complete original record; use --full --json for lossless JSON (may be large)")
|
||||
.action(async (id: string, o: { json?: boolean; full?: boolean }) => diagnosisRequest(`/${encodeURIComponent(id)}`, o));
|
||||
diagnosis.command("record <qitem-id>").requiredOption("--file <path>", "Disposition JSON with verdict, causalStart, steering, uncertainty, evidenceRefs").option("--json")
|
||||
.action(async (id: string, o: { file: string; json?: boolean }) => diagnosisRequest(`/${encodeURIComponent(id)}/disposition`, o, { value: fromFile(o.file) }));
|
||||
diagnosis.command("notify <qitem-id>").description("Explicitly request human delivery under policy and verified connector readiness").option("--json")
|
||||
.action(async (id: string, o: { json?: boolean }) => diagnosisRequest(`/${encodeURIComponent(id)}/notify`, o, {}));
|
||||
return command;
|
||||
}
|
||||
@@ -594,8 +594,9 @@ export function hostCommand(doctorDepsOverride?: DoctorDeps): Command {
|
||||
.argument("<url>", "The target daemon's address (http[s]://host:port, or host:port)")
|
||||
.option("--id <id>", "Registry id for the new host (default: derived from the hostname)")
|
||||
.option("--timeout <seconds>", "How long to wait for the target-side approval", "600")
|
||||
.option("--human <address>", "Registered @external approval recipient on the target (required when several exist)")
|
||||
.option("--json", "JSON output")
|
||||
.action(async (rawUrl: string, opts: { id?: string; timeout?: string; json?: boolean }) => {
|
||||
.action(async (rawUrl: string, opts: { id?: string; timeout?: string; json?: boolean; human?: string }) => {
|
||||
// OPR.0.4.6.MH1 FR-6 — THE founder-simple add path: one pasted
|
||||
// address, one approval ON the target, done. The flag-heavy
|
||||
// `rig host add` stays as the unchanged advanced path. B1: the CLI
|
||||
@@ -668,6 +669,7 @@ export function hostCommand(doctorDepsOverride?: DoctorDeps): Command {
|
||||
try {
|
||||
const res = await httpPost(`${targetBase}/api/hosts/pair-request`, {
|
||||
requester: `${userInfo().username}@${osHostname()}`,
|
||||
...(opts.human ? { human: opts.human } : {}),
|
||||
});
|
||||
issued = parseJson(res.body) as typeof issued;
|
||||
if (res.status !== 200 || !issued.pairId || !issued.code) {
|
||||
|
||||
@@ -24,11 +24,12 @@ export function importCommand(depsOverride?: ImportDeps): Command {
|
||||
.argument("<path>", "Path to YAML rig spec file")
|
||||
.option("--instantiate", "Instantiate the rig after import")
|
||||
.option("--materialize-only", "Create rig topology without launching sessions")
|
||||
.option("--workspace-only", "Apply only the workspace declaration to an existing rig")
|
||||
.option("--preflight", "Run preflight checks")
|
||||
.option("--target-rig <rigId>", "Target existing rig for additive materialization")
|
||||
.option("--target-rig <rigId>", "Target existing rig for materialization or workspace apply")
|
||||
.option("--rig-root <root>", "Root directory for pod-aware resolution")
|
||||
.option("--cwd <path>", "Override launch/materialization working directory for all members")
|
||||
.action(async (filePath: string, opts: { instantiate?: boolean; materializeOnly?: boolean; preflight?: boolean; targetRig?: string; rigRoot?: string; cwd?: string }) => {
|
||||
.action(async (filePath: string, opts: { instantiate?: boolean; materializeOnly?: boolean; workspaceOnly?: boolean; preflight?: boolean; targetRig?: string; rigRoot?: string; cwd?: string }) => {
|
||||
const deps = getDeps();
|
||||
|
||||
// Read local file first (before daemon check — fail fast on missing file)
|
||||
@@ -63,6 +64,35 @@ export function importCommand(depsOverride?: ImportDeps): Command {
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (opts.workspaceOnly && (opts.instantiate || opts.materializeOnly || opts.preflight)) {
|
||||
console.error("--workspace-only cannot be combined with --instantiate, --materialize-only, or --preflight.");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.workspaceOnly) {
|
||||
if (!opts.targetRig) {
|
||||
console.error("--workspace-only requires --target-rig <rigId>.");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const res = await client.postText<
|
||||
{ rigId: string; changed: boolean; workspace: unknown }
|
||||
| { ok: false; code: string; errors?: string[]; message?: string; error?: string }
|
||||
>("/api/rigs/import/workspace", yaml, "text/yaml", { "X-Target-Rig-Id": opts.targetRig });
|
||||
if (res.status >= 400) {
|
||||
const data = res.data as { errors?: string[]; message?: string; error?: string };
|
||||
const detail = data.errors?.join("\n ") ?? data.message ?? data.error ?? `status ${res.status}`;
|
||||
console.error(`Workspace apply failed:\n ${detail}\nFix: update the RigSpec workspace or target rig and retry.`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const data = res.data as { rigId: string; changed: boolean };
|
||||
console.log(data.changed
|
||||
? `Workspace applied to rig ${data.rigId}`
|
||||
: `Workspace already matches rig ${data.rigId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.preflight) {
|
||||
const res = await client.postText<{ ready?: boolean; warnings?: string[]; errors?: string[] }>("/api/rigs/import/preflight", yaml, "text/yaml", extraHeaders);
|
||||
@@ -97,9 +127,14 @@ export function importCommand(depsOverride?: ImportDeps): Command {
|
||||
...extraHeaders,
|
||||
...(opts.targetRig ? { "X-Target-Rig-Id": opts.targetRig } : {}),
|
||||
};
|
||||
const res = await client.postText<{ rigId: string; specName: string; specVersion: string; nodes: Array<{ logicalId: string; status: string }> } | { ok: false; code: string; errors?: string[]; message?: string }>("/api/rigs/import/materialize", yaml, "text/yaml", headers);
|
||||
const res = await client.postText<{ rigId: string; specName: string; specVersion: string; nodes: Array<{ logicalId: string; status: string }> } | { ok: false; code: string; errors?: string[]; message?: string; error?: string }>("/api/rigs/import/materialize", yaml, "text/yaml", headers);
|
||||
if (res.status === 409 || res.status === 400 || res.status === 404) {
|
||||
const data = res.data as { ok?: false; code?: string; errors?: string[]; message?: string; error?: string };
|
||||
if (data.code === "rig_name_running") {
|
||||
console.error(data.error ?? data.message ?? "A rig with this name is already running.");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const detail = data.errors?.join("\n ") ?? data.message ?? data.error ?? `status ${res.status}`;
|
||||
console.error(`Materialize failed:\n ${detail}\nFix: update your spec or target rig and retry.`);
|
||||
process.exitCode = 1;
|
||||
@@ -119,7 +154,7 @@ export function importCommand(depsOverride?: ImportDeps): Command {
|
||||
}
|
||||
|
||||
if (opts.instantiate) {
|
||||
const res = await client.postText<{ rigId: string; specName: string; specVersion: string; nodes: Array<{ logicalId: string; status: string }> } | { ok: false; code: string; errors?: string[]; message?: string }>(
|
||||
const res = await client.postText<{ rigId: string; specName: string; specVersion: string; nodes: Array<{ logicalId: string; status: string }> } | { ok: false; code: string; errors?: string[]; message?: string; error?: string }>(
|
||||
"/api/rigs/import",
|
||||
yaml,
|
||||
"text/yaml",
|
||||
@@ -127,7 +162,12 @@ export function importCommand(depsOverride?: ImportDeps): Command {
|
||||
{ timeoutMs: LONG_RUNNING_IMPORT_TIMEOUT_MS },
|
||||
);
|
||||
if (res.status === 409 || res.status === 400) {
|
||||
const data = res.data as { ok: false; code: string; errors?: string[]; message?: string };
|
||||
const data = res.data as { ok: false; code: string; errors?: string[]; message?: string; error?: string };
|
||||
if (data.code === "rig_name_running") {
|
||||
console.error(data.error ?? data.message ?? "A rig with this name is already running.");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const detail = data.errors?.join("\n ") ?? data.message ?? `status ${res.status}`;
|
||||
console.error(`Import failed:\n ${detail}\nFix: check your rig spec and retry. Validate first with: rig spec validate <path>`);
|
||||
process.exitCode = 1;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Command } from "commander";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { isAbsolute } from "node:path";
|
||||
import { resolveEffectiveHost } from "../host-selection.js";
|
||||
import { DaemonClient } from "../client.js";
|
||||
import { getDaemonStatus, getDaemonUrl , daemonStatusGuard} from "../daemon-lifecycle.js";
|
||||
@@ -12,11 +14,16 @@ type LaunchResponse = {
|
||||
logicalId?: string;
|
||||
sessionName?: string;
|
||||
error?: string;
|
||||
message?: string;
|
||||
code?: string;
|
||||
launched?: Array<{ nodeId: string; logicalId: string; status: string; error?: string }>;
|
||||
held?: Array<{ nodeId: string; logicalId: string; reason: string }>;
|
||||
alreadyRunning?: Array<{ nodeId: string; logicalId: string }>;
|
||||
failedTargets?: Array<{ nodeId: string; logicalId: string; reason: string }>;
|
||||
targetNodes?: Array<{ nodeId: string; logicalId: string }>;
|
||||
snapshotSelection?: { snapshotId: string; kind: string; createdAt: string; ageMs: number; mode: "explicit" | "automatic"; rationale: string; newerUsableAlternative: unknown };
|
||||
nonTargetEffects?: { mode: "unchanged" | "detach_and_hold"; reason: string | null; affected: unknown[] };
|
||||
planOnly?: boolean;
|
||||
// OPR.0.4.3.28 correction — non-blocking launch warnings (e.g. liveness_probe_unknown: launched
|
||||
// despite a failed tmux liveness probe). Surfaced as human output WITHOUT a non-zero exit.
|
||||
warnings?: string[];
|
||||
@@ -30,6 +37,14 @@ function launchStatusRunning(status: string): boolean {
|
||||
return !NON_RUNNING_LAUNCH_STATUSES.has(status);
|
||||
}
|
||||
|
||||
function printSnapshotSelection(selection: LaunchResponse["snapshotSelection"]): void {
|
||||
if (!selection) return;
|
||||
console.log(`Snapshot: ${selection.snapshotId} (${selection.kind}, ${selection.mode}, age ${Math.round(selection.ageMs / 1000)}s)`);
|
||||
console.log(`Selection: ${selection.rationale}`);
|
||||
const newer = selection.newerUsableAlternative as { snapshotId?: string; kind?: string } | null;
|
||||
if (newer?.snapshotId) console.log(`Newer usable alternative: ${newer.snapshotId}${newer.kind ? ` (${newer.kind})` : ""}`);
|
||||
}
|
||||
|
||||
export function launchCommand(depsOverride?: StatusDeps): Command {
|
||||
const cmd = new Command("launch").description("Launch or relaunch a node in a running rig");
|
||||
const getDeps = () => depsOverride ?? { lifecycleDeps: realDeps(), clientFactory: (url: string) => new DaemonClient(url) };
|
||||
@@ -45,14 +60,37 @@ export function launchCommand(depsOverride?: StatusDeps): Command {
|
||||
.argument("[nodeRef]", "Node logical ID or node ID (single target)")
|
||||
.option("--seats <ids>", "Comma-separated logical IDs for subset launch")
|
||||
.option("--hold-reason <reason>", "Reason for holding non-target seats")
|
||||
.option("--snapshot-id <id>", "Use this exact restore-usable snapshot")
|
||||
.option("--retry-startup-from <member-file>", "Retry a stopped first start that failed during projection, using its original member fragment")
|
||||
.option("--rig-root <path>", "Original absolute source root for --retry-startup-from")
|
||||
.option("--plan", "Show subset selection and non-target effects without mutation")
|
||||
.option("--json", "JSON output")
|
||||
.option("--host <id>", "Run on a remote host declared in ~/.openrig/hosts.yaml")
|
||||
.action(async (rigId: string, nodeRef: string | undefined, opts: { json?: boolean; holdReason?: string; seats?: string; host?: string }) => {
|
||||
.action(async (rigId: string, nodeRef: string | undefined, opts: { json?: boolean; holdReason?: string; seats?: string; host?: string; snapshotId?: string; plan?: boolean; retryStartupFrom?: string; rigRoot?: string }) => {
|
||||
// OPR.0.4.6.MH1 FR-2: selected-host routing — explicit --host wins;
|
||||
// else the persisted selection feeds the SHIPPED --host path; no
|
||||
// selection = today exactly.
|
||||
opts.host = resolveEffectiveHost(opts.host);
|
||||
const deps = getDeps();
|
||||
let retryBody: { retryStartupFrom: { member: Record<string, unknown>; rigRoot: string } } | undefined;
|
||||
if (opts.retryStartupFrom || opts.rigRoot) {
|
||||
if (!nodeRef || !opts.retryStartupFrom || !opts.rigRoot || !isAbsolute(opts.rigRoot) || opts.seats || opts.snapshotId || opts.plan || opts.holdReason) {
|
||||
console.error("First-start retry requires one node, --retry-startup-from and an absolute --rig-root; it cannot combine with snapshot/subset/plan options.");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { parse } = await import("yaml");
|
||||
const parsed = parse(readFileSync(opts.retryStartupFrom, "utf8"));
|
||||
const member = parsed?.member ?? parsed;
|
||||
if (!member || typeof member !== "object" || Array.isArray(member) || parsed.edges || member.edges) throw new Error("Supply a single member fragment without edges; retry preserves existing topology.");
|
||||
retryBody = { retryStartupFrom: { member, rigRoot: opts.rigRoot } };
|
||||
} catch (error) {
|
||||
console.error(`Cannot read retry member: ${(error as Error).message}`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.host) {
|
||||
const { runRemoteHttpOp } = await import("../remote-host-ops.js");
|
||||
@@ -61,10 +99,20 @@ export function launchCommand(depsOverride?: StatusDeps): Command {
|
||||
let body: unknown;
|
||||
if (seatList.length > 0) {
|
||||
apiPath = `/api/rigs/${encodeURIComponent(rigId)}/nodes/launch-subset`;
|
||||
body = { seats: seatList, ...(opts.holdReason ? { holdReason: opts.holdReason } : {}) };
|
||||
body = { seats: seatList, ...(opts.holdReason ? { holdReason: opts.holdReason } : {}), ...(opts.snapshotId ? { snapshotId: opts.snapshotId } : {}), ...(opts.plan ? { plan: true } : {}) };
|
||||
} else if (nodeRef) {
|
||||
if (opts.holdReason) {
|
||||
console.error("--hold-reason applies only to multi-seat --seats launch; single-seat launch never changes non-targets");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (opts.plan) {
|
||||
console.error("--plan currently applies only to multi-seat --seats launch");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
apiPath = `/api/rigs/${encodeURIComponent(rigId)}/nodes/${encodeURIComponent(nodeRef)}/launch`;
|
||||
body = opts.holdReason ? { holdReason: opts.holdReason } : {};
|
||||
body = retryBody ?? (opts.snapshotId ? { snapshotId: opts.snapshotId } : {});
|
||||
} else {
|
||||
console.error("Either a node reference or --seats is required for launch --host");
|
||||
process.exitCode = 1;
|
||||
@@ -92,17 +140,29 @@ export function launchCommand(depsOverride?: StatusDeps): Command {
|
||||
const seatList = opts.seats ? opts.seats.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
||||
|
||||
if (seatList.length > 0) {
|
||||
const body: { seats: string[]; holdReason?: string } = { seats: seatList };
|
||||
const body: { seats: string[]; holdReason?: string; snapshotId?: string; plan?: boolean } = { seats: seatList };
|
||||
if (opts.holdReason) body.holdReason = opts.holdReason;
|
||||
if (opts.snapshotId) body.snapshotId = opts.snapshotId;
|
||||
if (opts.plan) body.plan = true;
|
||||
const res = await client.post<LaunchResponse>(`/api/rigs/${encodeURIComponent(rigId)}/nodes/launch-subset`, body);
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(res.data, null, 2));
|
||||
if (res.status >= 400) process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (opts.plan && res.data.planOnly) {
|
||||
console.log("Plan only; no changes made.");
|
||||
printSnapshotSelection(res.data.snapshotSelection);
|
||||
console.log(`Non-target effect: ${res.data.nonTargetEffects?.mode ?? "unavailable"}`);
|
||||
for (const node of res.data.nonTargetEffects?.affected ?? []) {
|
||||
const affected = node as { logicalId?: string; reason?: string };
|
||||
console.log(` ${affected.logicalId ?? "unknown"}: ${affected.reason ?? "unspecified"}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Hard failure (no per-seat result to render — e.g. rig_not_found): error + exit.
|
||||
if (!res.data.launched && !res.data.held && !res.data.alreadyRunning) {
|
||||
console.error(res.data.error ?? `Launch failed (HTTP ${res.status})`);
|
||||
console.error(res.data.error ?? res.data.message ?? `Launch failed (HTTP ${res.status})`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
@@ -110,6 +170,7 @@ export function launchCommand(depsOverride?: StatusDeps): Command {
|
||||
// A seat that landed awaiting-decision / attention_required / failed is NOT
|
||||
// launched (no session running) — print it honestly + exit non-zero.
|
||||
const launchedAll = res.data.launched ?? [];
|
||||
printSnapshotSelection(res.data.snapshotSelection);
|
||||
const running = launchedAll.filter((n) => launchStatusRunning(n.status));
|
||||
const needsDecision = launchedAll.filter((n) => !launchStatusRunning(n.status));
|
||||
const heldIds = (res.data.held ?? []).map((n) => `${n.logicalId} (${n.reason})`).join(", ");
|
||||
@@ -139,9 +200,19 @@ export function launchCommand(depsOverride?: StatusDeps): Command {
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (opts.plan) {
|
||||
console.error("--plan currently applies only to multi-seat --seats launch");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (opts.holdReason) {
|
||||
console.error("--hold-reason applies only to multi-seat --seats launch; single-seat launch never changes non-targets");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const body: Record<string, string> = {};
|
||||
if (opts.holdReason) body.holdReason = opts.holdReason;
|
||||
const body: Record<string, unknown> = retryBody ?? {};
|
||||
if (opts.snapshotId) body.snapshotId = opts.snapshotId;
|
||||
|
||||
const res = await client.post<LaunchResponse>(`/api/rigs/${encodeURIComponent(rigId)}/nodes/${encodeURIComponent(nodeRef)}/launch`, body);
|
||||
if (opts.json) {
|
||||
@@ -151,12 +222,13 @@ export function launchCommand(depsOverride?: StatusDeps): Command {
|
||||
}
|
||||
|
||||
if (res.status >= 400 || !res.data.ok) {
|
||||
console.error(res.data.error ?? `Launch failed (HTTP ${res.status})`);
|
||||
console.error(res.data.error ?? res.data.message ?? `Launch failed (HTTP ${res.status})`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const logicalId = res.data.logicalId ?? nodeRef;
|
||||
printSnapshotSelection(res.data.snapshotSelection);
|
||||
// OPR.0.4.3.28 correction — proceed-with-warning: print non-blocking launch warnings (e.g.
|
||||
// liveness_probe_unknown) WITHOUT setting a non-zero exit.
|
||||
const printLaunchWarnings = () => {
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { Command } from "commander";
|
||||
|
||||
// OPR.0.5.5.19 A7 — `rig parked [seat]`: the founder's one-command ask. The diagnosis is
|
||||
// DERIVED at read time by the daemon (activity oracle × the queue's obligation face) and
|
||||
// returns confidence for BOTH inputs — this command renders it, it never computes it.
|
||||
|
||||
interface SeatDiagnosis {
|
||||
seatNodeId: string;
|
||||
sessionName: string;
|
||||
parked: boolean | "indeterminate";
|
||||
reason: string;
|
||||
activity: { value: string; needsInput: { count: number; reason: string | null }; decidedBy: string | null; confidence: string };
|
||||
obligations: {
|
||||
scope: string;
|
||||
openCount: number;
|
||||
heldCount: number;
|
||||
unhealthyHeldCount?: number;
|
||||
complete: boolean;
|
||||
limit: number;
|
||||
items: Array<{ qitemId: string; state: string; summary?: string | null }>;
|
||||
held?: Array<{
|
||||
qitemId: string;
|
||||
state: string;
|
||||
summary?: string | null;
|
||||
healthy?: boolean;
|
||||
wake: { kind: string; ref: string; live: boolean; unconsumed?: boolean; deliveryStatus?: string | null } | null;
|
||||
}>;
|
||||
};
|
||||
confidence: { activity: string; obligations: string };
|
||||
}
|
||||
|
||||
function verdictWord(parked: boolean | "indeterminate"): string {
|
||||
return parked === true ? "PARKED" : parked === false ? "not parked" : "INDETERMINATE";
|
||||
}
|
||||
|
||||
function renderSeat(d: SeatDiagnosis): void {
|
||||
console.log(`${d.sessionName}: ${verdictWord(d.parked)} — ${d.reason}`);
|
||||
console.log(` activity: ${d.activity.value}${d.activity.needsInput.count > 0 ? ` (needs-input x${d.activity.needsInput.count}: ${d.activity.needsInput.reason})` : ""} [decided by ${d.activity.decidedBy ?? "nothing — unknown"}; confidence ${d.confidence.activity}]`);
|
||||
console.log(` obligations: ${d.obligations.openCount} open, ${d.obligations.heldCount} held [${d.obligations.scope}; ${d.obligations.complete ? "complete" : `MAY BE TRUNCATED at ${d.obligations.limit}`}]`);
|
||||
for (const item of d.obligations.items.slice(0, 10)) {
|
||||
console.log(` - ${item.state} ${item.qitemId}${item.summary ? ` — ${item.summary}` : ""}`);
|
||||
}
|
||||
if (d.obligations.items.length > 10) console.log(` … and ${d.obligations.items.length - 10} more`);
|
||||
for (const item of d.obligations.held ?? []) {
|
||||
const wake = item.wake;
|
||||
const wakeText = wake
|
||||
? `${wake.kind} ${wake.ref}: ${wake.unconsumed ? `FIRED but unconsumed${wake.deliveryStatus ? ` (${wake.deliveryStatus})` : ""}` : wake.live ? "live" : "not live"}`
|
||||
: "no recorded wake";
|
||||
console.log(` - HELD ${item.qitemId}${item.summary ? ` — ${item.summary}` : ""} [${wakeText}]`);
|
||||
if (!item.healthy && !(wake?.live && !wake.unconsumed)) {
|
||||
console.log(" Remedy: attach a live watchdog id, arm an atomic timer, or name a live blocker qitem; deferred/not-imminent work with a workspace home belongs in its mission/slice.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function parkedCommand(): Command {
|
||||
return new Command("parked")
|
||||
.description("Are we parked? Derived diagnosis: stopped seats owing work; HELD is healthy only while its recorded wake is live")
|
||||
.argument("[seat]", "Seat node id or canonical session name; omit to diagnose the whole rig")
|
||||
.option("--rig <rig>", "Rig scope (defaults to your seat's rig from OPENRIG_SESSION_NAME; a seat argument carrying @rig self-scopes)")
|
||||
.option("--json", "Full diagnosis as JSON")
|
||||
.action(async (seat: string | undefined, opts: { json?: boolean; rig?: string }) => {
|
||||
const { DaemonClient } = await import("../client.js");
|
||||
const client = new DaemonClient();
|
||||
// WAVE-O B2: the diagnosis is rig-scoped — carry the coordinate. A seat@rig
|
||||
// argument self-scopes; otherwise --rig, then the shell's own seat identity.
|
||||
// With none resolvable the daemon's teaching refusal is surfaced verbatim.
|
||||
const params = new URLSearchParams();
|
||||
if (seat) params.set("seat", seat);
|
||||
if (!seat?.includes("@")) {
|
||||
const envSession = process.env["OPENRIG_SESSION_NAME"];
|
||||
const rig = opts.rig ?? (envSession?.includes("@") ? envSession.split("@")[1] : undefined);
|
||||
if (rig) params.set("rig", rig);
|
||||
}
|
||||
const qs = params.toString();
|
||||
let data: { ok: boolean; error?: string; scope?: { rig: string; resolvedFrom: string }; seat?: SeatDiagnosis; rig?: { parked: boolean | "indeterminate"; reason: string; seats: SeatDiagnosis[]; scope?: { rig: string; resolvedFrom: string } } };
|
||||
try {
|
||||
const res = await client.get<typeof data>(`/api/activity/parked${qs ? `?${qs}` : ""}`);
|
||||
data = res.data;
|
||||
} catch (err) {
|
||||
console.error(`refused: the parked diagnosis is derived LIVE from the oracle and the queue — it needs a reachable daemon (${(err as Error).message}).`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (!data.ok) {
|
||||
console.error(`refused: ${data.error}`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(data));
|
||||
return;
|
||||
}
|
||||
if (data.seat) {
|
||||
if (data.scope) console.log(`scope: rig ${data.scope.rig} (from ${data.scope.resolvedFrom})`);
|
||||
renderSeat(data.seat);
|
||||
return;
|
||||
}
|
||||
const rig = data.rig!;
|
||||
console.log(`rig: ${verdictWord(rig.parked)} — ${rig.reason}`);
|
||||
if (rig.scope) console.log(`scope: rig ${rig.scope.rig} (from ${rig.scope.resolvedFrom})`);
|
||||
for (const d of rig.seats) {
|
||||
if (d.parked === false) continue; // the interesting cells are parked + indeterminate
|
||||
renderSeat(d);
|
||||
}
|
||||
});
|
||||
}
|
||||
Loaded 100 of 1374 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user