mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 22:58:42 -10:00
Compare commits
704
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
379f0a0d10 | ||
|
|
af1b61bf52 | ||
|
|
1a73ec57bc | ||
|
|
1711698dba | ||
|
|
098f73eac3 | ||
|
|
e47c158004 | ||
|
|
b194394fa4 | ||
|
|
cb79b8c980 | ||
|
|
d09d54b5e2 | ||
|
|
857065cd66 | ||
|
|
39565e7815 | ||
|
|
d0d8fad608 | ||
|
|
bdc19878fc | ||
|
|
335ceb0ec3 | ||
|
|
fec7772d05 | ||
|
|
76a4b33559 | ||
|
|
31779f7384 | ||
|
|
17707d65fb | ||
|
|
dbc8abe607 | ||
|
|
f06e626b4c | ||
|
|
ada3678219 | ||
|
|
9109360f57 | ||
|
|
3b31636f59 | ||
|
|
19202e6b4f | ||
|
|
3bc921233e | ||
|
|
5a947d02c4 | ||
|
|
4e14d01b8b | ||
|
|
bf75d81bea | ||
|
|
845733bb3d | ||
|
|
7837fc596b | ||
|
|
a5c22b0e78 | ||
|
|
9eac92c244 | ||
|
|
0b128f7467 | ||
|
|
14f96bfd0a | ||
|
|
c50c8e2b92 | ||
|
|
2bc91a70d2 | ||
|
|
43ecc58917 | ||
|
|
d8b92928d6 | ||
|
|
001c070ecb | ||
|
|
94d3f5207c | ||
|
|
c4936fe96f | ||
|
|
081088fb07 | ||
|
|
7bd0e01b2f | ||
|
|
77d9b9e30d | ||
|
|
46243bcf99 | ||
|
|
cbcac27911 | ||
|
|
1ff21ef8cf | ||
|
|
0b01b8819d | ||
|
|
b8de1d468d | ||
|
|
3001cc4366 | ||
|
|
18c0a0fd67 | ||
|
|
a186bdaa22 | ||
|
|
dfe51eea3c | ||
|
|
8376d39014 | ||
|
|
67efb11121 | ||
|
|
a460fa27fa | ||
|
|
4d2bbb9ec1 | ||
|
|
b0564afb9a | ||
|
|
423cee84f3 | ||
|
|
ec1e718c54 | ||
|
|
cda6e18e27 | ||
|
|
360c47bd82 | ||
|
|
404fd9590e | ||
|
|
f694382321 | ||
|
|
85b4f4311b | ||
|
|
76163deb04 | ||
|
|
45713f3276 | ||
|
|
ade359a38c | ||
|
|
693c26f24e | ||
|
|
0ccaa38d8e | ||
|
|
878d150e4e | ||
|
|
35a99b2ea2 | ||
|
|
7f0bf31fa9 | ||
|
|
e1403c2d37 | ||
|
|
916e1bbeb3 | ||
|
|
8145c49fe8 | ||
|
|
dfde2bab01 | ||
|
|
bed4d4580f | ||
|
|
1754628ae5 | ||
|
|
caa6c09c5e | ||
|
|
bc92469e77 | ||
|
|
5a0c238f74 | ||
|
|
66a13ca46b | ||
|
|
e50a13c2dc | ||
|
|
3df44055b4 | ||
|
|
62ee49149c | ||
|
|
b8b625817d | ||
|
|
db8e6dd0db | ||
|
|
1319609fdb | ||
|
|
9d0be982b0 | ||
|
|
f2e0ebd3fe | ||
|
|
7d9ce5c264 | ||
|
|
daa2fff7d9 | ||
|
|
d36982fdea | ||
|
|
4058495cac | ||
|
|
e8f32eed6b | ||
|
|
e4422def1f | ||
|
|
43983e67b2 | ||
|
|
115f973fbe | ||
|
|
a6efb5b461 | ||
|
|
484c329daa | ||
|
|
82b7b56813 | ||
|
|
e5b6fbc651 | ||
|
|
30e846c4b6 | ||
|
|
f7158c9a8a | ||
|
|
f9c1e899cc | ||
|
|
7cd2666156 | ||
|
|
d97ad032a1 | ||
|
|
4b48ca21a9 | ||
|
|
7c603da3ee | ||
|
|
f6bdf107f7 | ||
|
|
5eb57cc166 | ||
|
|
14b59f7e5c | ||
|
|
a29dfbcf9e | ||
|
|
98f1cfcc33 | ||
|
|
9e3581cba1 | ||
|
|
978ee52e7a | ||
|
|
7d177fa12d | ||
|
|
be5f5fcdb1 | ||
|
|
b047fbf15b | ||
|
|
aa126c74d5 | ||
|
|
f1f31cce50 | ||
|
|
68150ad9fc | ||
|
|
e8f0ab340d | ||
|
|
4f7626e64d | ||
|
|
c502584b18 | ||
|
|
dc234f0de0 | ||
|
|
9dd1b02948 | ||
|
|
ee2caa7773 | ||
|
|
8a0b46fee3 | ||
|
|
e31c976e51 | ||
|
|
a001444d7f | ||
|
|
f8eb5c0cd2 | ||
|
|
8e39778789 | ||
|
|
29c2a56104 | ||
|
|
a79568a08a | ||
|
|
c34233c072 | ||
|
|
83510564cb | ||
|
|
9f89fdbe45 | ||
|
|
376e653b9d | ||
|
|
60749d1570 | ||
|
|
b3c3c8575f | ||
|
|
824f80ce75 | ||
|
|
aa16224637 | ||
|
|
2caac7dd1b | ||
|
|
75d90b5070 | ||
|
|
114cdb1554 | ||
|
|
7bdc073677 | ||
|
|
72087f3aa0 | ||
|
|
770489c2f5 | ||
|
|
da5e9a927a | ||
|
|
de374b4855 | ||
|
|
56bfc498fc | ||
|
|
406e7c1a90 | ||
|
|
69ecce4336 | ||
|
|
ad956fb4a9 | ||
|
|
dbfc5a556b | ||
|
|
5fab9ae802 | ||
|
|
a621069811 | ||
|
|
8f80086275 | ||
|
|
9e08c89db6 | ||
|
|
1f69831f9e | ||
|
|
85e0a10dc3 | ||
|
|
4fa8a73035 | ||
|
|
817ca07c85 | ||
|
|
4176538318 | ||
|
|
1a7b886c20 | ||
|
|
69acd45b58 | ||
|
|
33f80efeaa | ||
|
|
2467b4ee60 | ||
|
|
680a210bd3 | ||
|
|
aec75a2644 | ||
|
|
edf4b21de9 | ||
|
|
770f8e195e | ||
|
|
97372ac132 | ||
|
|
343b4337c3 | ||
|
|
b61be0d37b | ||
|
|
fe23026fae | ||
|
|
f88322bb9b | ||
|
|
64e4d652ed | ||
|
|
a5f039590d | ||
|
|
134f128eb6 | ||
|
|
b577daf022 | ||
|
|
66314bc808 | ||
|
|
1af1b2abd4 | ||
|
|
67426bc185 | ||
|
|
1ed008420a | ||
|
|
ff0de7b9da | ||
|
|
feb6ed94fd | ||
|
|
baddd0a87d | ||
|
|
2a513ce7cd | ||
|
|
722689d18d | ||
|
|
3adf7718d2 | ||
|
|
c0fcc73a51 | ||
|
|
227b954128 | ||
|
|
6cdb203054 | ||
|
|
72ce7e7ae5 | ||
|
|
036e8aef52 | ||
|
|
b4ba38bc14 | ||
|
|
69f1a3ef8a | ||
|
|
2a23437766 | ||
|
|
f145780fbb | ||
|
|
23a1669106 | ||
|
|
09c4a44cbc | ||
|
|
475ccabeca | ||
|
|
7a4a21780d | ||
|
|
2ef93ad324 | ||
|
|
8209b81c19 | ||
|
|
ab32dde66e | ||
|
|
64bbcb3b1f | ||
|
|
d1d6eb9ed8 | ||
|
|
90cae3a570 | ||
|
|
960011982c | ||
|
|
baba1f608b | ||
|
|
dffb996bab | ||
|
|
3cdb3eb478 | ||
|
|
b482b83bb0 | ||
|
|
ee3b6c03d5 | ||
|
|
d648fca087 | ||
|
|
6840cd7d55 | ||
|
|
cd79a6bd05 | ||
|
|
4472577a11 | ||
|
|
0615de266f | ||
|
|
5561a05be5 | ||
|
|
9ad13828aa | ||
|
|
a7fed6313a | ||
|
|
b00a536b82 | ||
|
|
98e643b6d0 | ||
|
|
0146f6a03f | ||
|
|
51bd931ed6 | ||
|
|
14884db714 | ||
|
|
bb9e8a93b0 | ||
|
|
6bce9b2e74 | ||
|
|
b1fadc518f | ||
|
|
2c708f8b6c | ||
|
|
23da0752d1 | ||
|
|
77cbc8c2ad | ||
|
|
1864743130 | ||
|
|
f2b7fbf459 | ||
|
|
e6c9573761 | ||
|
|
6cb1017aad | ||
|
|
75be285838 | ||
|
|
b0e27eacec | ||
|
|
7ddf5e94ec | ||
|
|
e35d541754 | ||
|
|
5dadaf0359 | ||
|
|
8d047cacae | ||
|
|
d2348948f3 | ||
|
|
70985f1108 | ||
|
|
9ed83cf7ba | ||
|
|
fd068ea495 | ||
|
|
23bc9681ea | ||
|
|
96b79fd311 | ||
|
|
6e8a6d936a | ||
|
|
49c60b4938 | ||
|
|
9ba09105e1 | ||
|
|
f0cde45616 | ||
|
|
b57d66a20d | ||
|
|
82b9ed72f7 | ||
|
|
69bdf4d489 | ||
|
|
5f7378a7cf | ||
|
|
2620dea84e | ||
|
|
a622adaaf1 | ||
|
|
141513eba4 | ||
|
|
34a11aebd9 | ||
|
|
2bbe82a501 | ||
|
|
5d7d741b0e | ||
|
|
d6f54149dc | ||
|
|
5aef027024 | ||
|
|
2b75ae0557 | ||
|
|
52b5c86a2b | ||
|
|
2456a57205 | ||
|
|
27c79fb6a4 | ||
|
|
493669bc23 | ||
|
|
bf10c3976a | ||
|
|
289894e28d | ||
|
|
200606a514 | ||
|
|
44e47baacb | ||
|
|
f18d3d870d | ||
|
|
b4548482cf | ||
|
|
866ac40ea9 | ||
|
|
d1e9d5e5d7 | ||
|
|
5b05076b82 | ||
|
|
decf641773 | ||
|
|
5653be8492 | ||
|
|
71276237ce | ||
|
|
ed9dc3d7c3 | ||
|
|
d3ae73b2f2 | ||
|
|
7a6105470b | ||
|
|
47bfadfa77 | ||
|
|
0f2ea7dfac | ||
|
|
572dba831d | ||
|
|
09384907cb | ||
|
|
e6aaeb6ccc | ||
|
|
a60bf42b97 | ||
|
|
35d55ae286 | ||
|
|
fcef78aba5 | ||
|
|
e64c3aa667 | ||
|
|
0ee020f1a6 | ||
|
|
b02986a534 | ||
|
|
2fa61d1efe | ||
|
|
82eb4bed0f | ||
|
|
236f9a0c75 | ||
|
|
0757c7a812 | ||
|
|
a3b3592978 | ||
|
|
ddedb97658 | ||
|
|
d1c72756e8 | ||
|
|
e80efb7150 | ||
|
|
8f29405f5e | ||
|
|
a9d3e05fd7 | ||
|
|
da230f5b00 | ||
|
|
03d6f7baa2 | ||
|
|
e428972a18 | ||
|
|
e894d72e5b | ||
|
|
38d47b7e83 | ||
|
|
2ef409adb8 | ||
|
|
e923ce66f5 | ||
|
|
32adf26cb1 | ||
|
|
f6358a2a7b | ||
|
|
ca208a692f | ||
|
|
9be129624e | ||
|
|
23f08bbd31 | ||
|
|
b9ea007fab | ||
|
|
fda7cebde1 | ||
|
|
104b78eef3 | ||
|
|
c750304f73 | ||
|
|
7ba28077fb | ||
|
|
f1dc6207e7 | ||
|
|
3db2b16741 | ||
|
|
6ce7ec2089 | ||
|
|
33eaeda30f | ||
|
|
aa4ee793cd | ||
|
|
fcaf1f8ee8 | ||
|
|
596b389506 | ||
|
|
04c7ed81be | ||
|
|
49013002b5 | ||
|
|
763f4237e8 | ||
|
|
9b88b118d5 | ||
|
|
a350c59b5a | ||
|
|
c36c983581 | ||
|
|
4f3952e0c1 | ||
|
|
80531b3225 | ||
|
|
b5a9deafd5 | ||
|
|
2cc9de9579 | ||
|
|
c31c58051c | ||
|
|
bd82e19960 | ||
|
|
ad0b39f2b9 | ||
|
|
f53558d982 | ||
|
|
3a906f5baf | ||
|
|
4f17654bfc | ||
|
|
a8306cc96e | ||
|
|
106dbf867f | ||
|
|
6525f5f02f | ||
|
|
5bf44c14a9 | ||
|
|
a89c7f3400 | ||
|
|
934b8e3b8b | ||
|
|
44cf9b3d59 | ||
|
|
6f783f67e1 | ||
|
|
1460b2fae4 | ||
|
|
e1d9fec9da | ||
|
|
6c6a8aa1b4 | ||
|
|
2affb18647 | ||
|
|
c3c213eb51 | ||
|
|
30ff3dc5b9 | ||
|
|
4ef185adf0 | ||
|
|
824cd1d2ab | ||
|
|
6dc29e2534 | ||
|
|
63aecb85f9 | ||
|
|
c3d29553a4 | ||
|
|
e63784e8f7 | ||
|
|
dcbbd78f5a | ||
|
|
ce7ed5c63c | ||
|
|
73d6635390 | ||
|
|
5f024424f1 | ||
|
|
dce8c90f41 | ||
|
|
5c2d6e5394 | ||
|
|
9cc1d58112 | ||
|
|
7fd8d3f9d3 | ||
|
|
c31fa2689b | ||
|
|
22a4bb1342 | ||
|
|
99077a8308 | ||
|
|
3d54d2e646 | ||
|
|
874e5212a3 | ||
|
|
441e7d1fc6 | ||
|
|
591f80dfd5 | ||
|
|
aeb67bd0c7 | ||
|
|
9e2c584040 | ||
|
|
0cfcf5f888 | ||
|
|
78b8b34ce7 | ||
|
|
0bebb00d15 | ||
|
|
4a7f422d01 | ||
|
|
23a562f0bd | ||
|
|
7978b36bfd | ||
|
|
910bf9f14a | ||
|
|
e1440f20b4 | ||
|
|
c5b64e344c | ||
|
|
e6a73eac20 | ||
|
|
0ee9d100f7 | ||
|
|
efa809b780 | ||
|
|
7a9019d775 | ||
|
|
eb0251100d | ||
|
|
4792688de9 | ||
|
|
db56023533 | ||
|
|
b4d467810d | ||
|
|
4f0e0dee61 | ||
|
|
d2707daafa | ||
|
|
89dc45e178 | ||
|
|
6bdbed2b2e | ||
|
|
07da904fa7 | ||
|
|
cd3fa344b4 | ||
|
|
9e24dff936 | ||
|
|
00897f0766 | ||
|
|
e024a94538 | ||
|
|
a60ab60d72 | ||
|
|
d0ae339c23 | ||
|
|
c17014ed14 | ||
|
|
d3bb161755 | ||
|
|
91a5070ada | ||
|
|
028c70d642 | ||
|
|
a8141f22c3 | ||
|
|
7591ee7820 | ||
|
|
1d26bcf24b | ||
|
|
3425e9d207 | ||
|
|
ced90ff9c4 | ||
|
|
b4a68d1c0b | ||
|
|
53195656c7 | ||
|
|
f475fc3cff | ||
|
|
3c8a20e3d8 | ||
|
|
f0113149b7 | ||
|
|
f8c070a5bf | ||
|
|
0fe2cc3f35 | ||
|
|
b345706b65 | ||
|
|
3929e6d6a6 | ||
|
|
28ced738de | ||
|
|
db3aef8f2a | ||
|
|
85ed428291 | ||
|
|
53f2b5d9d0 | ||
|
|
69d3aa9d49 | ||
|
|
67e81ec9bb | ||
|
|
7748b87d43 | ||
|
|
20b3b48855 | ||
|
|
b89fcad7c0 | ||
|
|
1ed5bcceda | ||
|
|
47ae99d74e | ||
|
|
8bed067878 | ||
|
|
acd31a90b5 | ||
|
|
350eb7ba98 | ||
|
|
d116467692 | ||
|
|
7de30c9592 | ||
|
|
c550bfaa39 | ||
|
|
3dd8bc776f | ||
|
|
ec5e77aaff | ||
|
|
f027fdebb5 | ||
|
|
393297e10c | ||
|
|
4f3b766aa5 | ||
|
|
5862bafdce | ||
|
|
043b1bb3b4 | ||
|
|
a24471af62 | ||
|
|
63838b2ad2 | ||
|
|
7e9452d266 | ||
|
|
835e420608 | ||
|
|
b20af404be | ||
|
|
39ee4751dc | ||
|
|
3650524101 | ||
|
|
f88f590f75 | ||
|
|
050878b027 | ||
|
|
12cef7594d | ||
|
|
c0ca7d8759 | ||
|
|
73e8ef81e5 | ||
|
|
42c60fa466 | ||
|
|
a4eb66f9fb | ||
|
|
c513420164 | ||
|
|
c6e90d69fe | ||
|
|
887063afdd | ||
|
|
e198f97da6 | ||
|
|
4fb037ae20 | ||
|
|
4552b1613f | ||
|
|
1a3ffc245e | ||
|
|
236b1b17b4 | ||
|
|
22d5b62f23 | ||
|
|
72fedeed7a | ||
|
|
ad74c81426 | ||
|
|
1c6de1255c | ||
|
|
8b79355963 | ||
|
|
193253a657 | ||
|
|
e5c5ded7cb | ||
|
|
b4b3c96f17 | ||
|
|
a8c1c37581 | ||
|
|
0d794d0baa | ||
|
|
f0fcc2840e | ||
|
|
a5d9a1cf25 | ||
|
|
b95d976002 | ||
|
|
3be4cd50d4 | ||
|
|
c2329136b8 | ||
|
|
d5a255ef96 | ||
|
|
852b480628 | ||
|
|
1670e6e611 | ||
|
|
f45e0cef51 | ||
|
|
66fe3d2716 | ||
|
|
977537aa00 | ||
|
|
9bd60b8c01 | ||
|
|
0f687e63d9 | ||
|
|
a5b8f84e81 | ||
|
|
595b705f2d | ||
|
|
b6d37bfadc | ||
|
|
eeca3e02a7 | ||
|
|
0a4f761d5d | ||
|
|
9a89e1732d | ||
|
|
35a255237a | ||
|
|
254122872c | ||
|
|
b2bda7443c | ||
|
|
07f3367d8a | ||
|
|
207a7ffbca | ||
|
|
073b93a92d | ||
|
|
3606452a8a | ||
|
|
be95cb035b | ||
|
|
1389de2bab | ||
|
|
4cb9f23248 | ||
|
|
712a61fbcc | ||
|
|
384223e9a5 | ||
|
|
474df87f87 | ||
|
|
c78c980a06 | ||
|
|
2b63d69145 | ||
|
|
b1e271e7ce | ||
|
|
0c24607bcc | ||
|
|
d546d07f04 | ||
|
|
8a2602275a | ||
|
|
f8c30f13fd | ||
|
|
c720e2053f | ||
|
|
8b75297b17 | ||
|
|
ecc936014c | ||
|
|
8135136953 | ||
|
|
783d22b50c | ||
|
|
88165fb77e | ||
|
|
decc0bbe93 | ||
|
|
9a26e3afcb | ||
|
|
447dd828f7 | ||
|
|
ed275c360b | ||
|
|
f4ea676d62 | ||
|
|
bdfc58aeab | ||
|
|
70d0bdbf1d | ||
|
|
658bfcb4f6 | ||
|
|
cd435a5a99 | ||
|
|
6949b0e1a2 | ||
|
|
5e90ad4407 | ||
|
|
f16a0f3376 | ||
|
|
a02e25aed9 | ||
|
|
90970a9275 | ||
|
|
716d13a968 | ||
|
|
098f4848f9 | ||
|
|
23c1c701c0 | ||
|
|
8c242e979b | ||
|
|
d0d1113f7c | ||
|
|
d7cbe27d67 | ||
|
|
cb863a056e | ||
|
|
37dccd6612 | ||
|
|
88f1ef78f7 | ||
|
|
9146d034f1 | ||
|
|
2dd2c3bc61 | ||
|
|
ca3fb37370 | ||
|
|
f463030202 | ||
|
|
c883c721c9 | ||
|
|
bba9648bbd | ||
|
|
79ed7ff588 | ||
|
|
fb31f39650 | ||
|
|
78ab5ceb93 | ||
|
|
bff0142eae | ||
|
|
75dba3a878 | ||
|
|
c9c9e200a5 | ||
|
|
d410075e48 | ||
|
|
b7ec2f8092 | ||
|
|
875d359a3f | ||
|
|
6872516960 | ||
|
|
032a5e2f29 | ||
|
|
ad59ac9d0c | ||
|
|
0b14b07329 | ||
|
|
f37b25f4ef | ||
|
|
e6a391b66c | ||
|
|
0a108305f4 | ||
|
|
ebfd05f13a | ||
|
|
e41f4653dd | ||
|
|
1cb80c3d6b | ||
|
|
72191c5729 | ||
|
|
fed59d6d14 | ||
|
|
264fade906 | ||
|
|
5887d3b996 | ||
|
|
48f6cce716 | ||
|
|
e39060db49 | ||
|
|
b27190f1aa | ||
|
|
263e4dd82d | ||
|
|
62d548a9d9 | ||
|
|
51dc6c97e5 | ||
|
|
74b8b46f5c | ||
|
|
5197fb9483 | ||
|
|
75acf5510e | ||
|
|
4fbf56c0c6 | ||
|
|
907d104339 | ||
|
|
1c89408e38 | ||
|
|
e16076bd3a | ||
|
|
ccf4027e11 | ||
|
|
008c9faada | ||
|
|
5b0a8ccc96 | ||
|
|
1f555fb84b | ||
|
|
d7e716c909 | ||
|
|
34277827c8 | ||
|
|
d000fb2bb8 | ||
|
|
67c4851ea3 | ||
|
|
b5e2947a45 | ||
|
|
81c4d1054f | ||
|
|
68ea8325cf | ||
|
|
570f362748 | ||
|
|
b9ed6bf79b | ||
|
|
14c37af4f6 | ||
|
|
529ccd61ff | ||
|
|
9cb0934846 | ||
|
|
d797d63f8e | ||
|
|
b5bbdfcf2d | ||
|
|
b75e9cde04 | ||
|
|
a46f7312c0 | ||
|
|
50f747bc4a | ||
|
|
e6828b73e1 | ||
|
|
6b60035416 | ||
|
|
b43ff0d6fd | ||
|
|
71e66bfa25 | ||
|
|
71506f6ed9 | ||
|
|
8983aece22 | ||
|
|
fecd5979c1 | ||
|
|
f9e0df86ec | ||
|
|
60ffa7dd8b | ||
|
|
fde9a50eb2 | ||
|
|
5c13e08fd6 | ||
|
|
93f8d4abba | ||
|
|
a7e7598f69 | ||
|
|
8ef26dc009 | ||
|
|
28ca2476a6 | ||
|
|
e0de9f1bc4 | ||
|
|
b83b97fa44 | ||
|
|
a45e18ecf1 | ||
|
|
a2ebf97719 | ||
|
|
95d7a1b878 | ||
|
|
3ff5eff0fc | ||
|
|
4068baf1a2 | ||
|
|
e1baed7b87 | ||
|
|
1d250d7420 | ||
|
|
cdc22865fd | ||
|
|
7d43151ec5 | ||
|
|
aeb8816010 | ||
|
|
2a697d45ba | ||
|
|
a36fa1a4e2 | ||
|
|
bd41d7b181 | ||
|
|
6f7d6a4ee4 | ||
|
|
dc6ab9eed5 | ||
|
|
e5681fc4c8 | ||
|
|
065df99929 | ||
|
|
51676aa204 | ||
|
|
9dc0c3f1e3 | ||
|
|
451e373165 | ||
|
|
6a2b804c23 | ||
|
|
394613e95e | ||
|
|
3fba553a2f | ||
|
|
7ca2724101 | ||
|
|
29a72ad324 | ||
|
|
d0623e06cb | ||
|
|
9648c59fe5 | ||
|
|
9480978770 | ||
|
|
5fe41efa7c | ||
|
|
c40dbac0c1 | ||
|
|
3e1c3b0108 | ||
|
|
bb5d17223a | ||
|
|
8c4b30bee8 | ||
|
|
82dbc01443 | ||
|
|
5f33183055 | ||
|
|
22622b8981 | ||
|
|
809c338843 | ||
|
|
d70c5995a6 | ||
|
|
792ef42e5f | ||
|
|
94a68a3d77 | ||
|
|
274e05fdc2 | ||
|
|
07dab0761d | ||
|
|
d1ba4ee71e | ||
|
|
6963421604 | ||
|
|
b24ef4f6f6 | ||
|
|
15763c46bb | ||
|
|
217b5282f8 | ||
|
|
700e6a05be | ||
|
|
11b14d0150 | ||
|
|
76ef4d9b13 | ||
|
|
02a3ca7bf2 | ||
|
|
0ab94672de | ||
|
|
e1139e2276 | ||
|
|
25ee889495 | ||
|
|
964b904fd2 | ||
|
|
662288d791 | ||
|
|
030227c5d1 | ||
|
|
e0b135ca43 | ||
|
|
bc8c608506 | ||
|
|
b730558413 | ||
|
|
650b96bb4e | ||
|
|
ed9dc62f5a | ||
|
|
b4a05d279a | ||
|
|
1347d825c3 | ||
|
|
3691134c5b | ||
|
|
de9ec4f7ee | ||
|
|
c1ff76635e |
No files matched your search
@@ -0,0 +1,75 @@
|
||||
---
|
||||
name: developing-openrig
|
||||
description: "Use when changing OpenRig's own source in a clone of the openrig repository: finding which package or file owns a behaviour, choosing which tests to run, testing a change without disturbing the OpenRig daemon your own session runs on, judging whether a diff touches a high-risk area, or preparing a pull request. Not for operating rigs (openrig-skills) or designing rig topologies."
|
||||
---
|
||||
|
||||
# Developing OpenRig
|
||||
|
||||
This skill is a map to the other maps. Each one is incomplete and may be stale: where a map and the code disagree, the
|
||||
code is right, and fixing the map is a welcome contribution.
|
||||
|
||||
## First: which OpenRig are you in?
|
||||
|
||||
If you are an agent running inside OpenRig, two different OpenRigs are in play:
|
||||
|
||||
- **The installed one runs your session.** `rig --version` and `rig daemon status` describe it.
|
||||
- **The checkout is what you're changing.** `git rev-parse HEAD` describes it.
|
||||
|
||||
Restarting the installed daemon does not run your checkout, and stopping it stops the agents running on it, including
|
||||
you. Test your change with the repo's own harnesses, which start a private daemon and tmux server (see "Test it" below),
|
||||
not by restarting the daemon you live in.
|
||||
|
||||
## Find your way
|
||||
|
||||
| You want to know | Read | How much to trust it |
|
||||
|---|---|---|
|
||||
| What OpenRig can already do, before you decide something is missing | `rig context get onboarding-width/public-what-you-can-do.md`; in this checkout, `packages/daemon/context-packs-src/onboarding-width/public-what-you-can-do.md` (the installed copy can differ) | The capability map; check it before proposing a new command or calling a gap a defect |
|
||||
| Whether a `rig` command already exists | `rig --help`: the full command list, before you decide a command is missing | The running binary; read the whole list, not only the command you expected |
|
||||
| Whether a problem is already known | The "Known problems to compare against" section of [`docs/reference/help.md`](../../../docs/reference/help.md) | Lists known issues by symptom; check an issue's current status before relying on it |
|
||||
| Whether a feature is already planned | [`ROADMAP.md`](../../../ROADMAP.md) | Check it before proposing a feature |
|
||||
| How the packages fit, the request path, where to add a command, route, migration, adapter, skill, context pack or scenario | [`ARCHITECTURE.md`](../../../ARCHITECTURE.md) | Checked against the commit it names; counts come with the command to refresh them |
|
||||
| Whether your change touches a high-risk area, what depends on it, what broke there before | [`docs/as-built/arteries.md`](../../../docs/as-built/arteries.md) | Incomplete by design: absence from it doesn't make a change safe |
|
||||
| What to run before you push, and what each layer can and can't prove | [`docs/as-built/test-layers.md`](../../../docs/as-built/test-layers.md) | Checked against the commit it names |
|
||||
| The exact behaviour of a `rig` command | `rig <command> --help` on the running binary, then `packages/cli/src/commands/` | The binary and the code win over any document |
|
||||
| A subsystem in depth | [`docs/as-built/`](../../../docs/as-built/README.md) | Each module names the commit it was last checked against in `last-verified-against-source`; compare that marker with `git log` before relying on it |
|
||||
| Rig spec, agent spec and other user-facing reference | [`docs/reference/`](../../../docs/reference/) | Ships to every user; if you find drift, fix it there |
|
||||
| Which shipped skill covers a task | [`skills/_canonical/core/openrig-skills/SKILL.md`](../../../skills/_canonical/core/openrig-skills/SKILL.md) | The index of the skills that ship with OpenRig |
|
||||
| How to open a good pull request, and what review looks like | [`CONTRIBUTING.md`](../../../CONTRIBUTING.md) | Current |
|
||||
|
||||
If none of these answers your question, search the code before inventing an answer. A missing map is worth an issue.
|
||||
|
||||
## Before you change an artery
|
||||
|
||||
If your diff touches anything in `arteries.md` (message delivery, launch and resume, the queue, rig identity, skill
|
||||
projection, process observation, migrations, restore):
|
||||
|
||||
1. Read the artery row: what depends on it, and the past regressions listed there.
|
||||
2. Describe the downstream effect in your pull request, not just the diff.
|
||||
3. Add or extend a stub-agent scenario, or say precisely why the stub can't exercise it and what you ran instead.
|
||||
|
||||
## Test it
|
||||
|
||||
The short version is `npm run build`, `npm run lint`, `npm test`. `test-layers.md` has the full ladder, including the
|
||||
stub-agent scenarios, which run the real CLI, daemon, tmux and SQLite with scripted agents and no model cost.
|
||||
|
||||
- The scenario runner starts its own daemon and tmux server and drops your session's `TMUX` and daemon variables, so
|
||||
it won't touch the daemon your session runs on.
|
||||
- A stub proves OpenRig's own plumbing, not how Claude Code or Codex behave. Say which you exercised.
|
||||
|
||||
## How we judge changes
|
||||
|
||||
OpenRig is a coordination layer for a trusted environment: it should help agents and people keep work flowing.
|
||||
|
||||
- A change that removes friction, or makes state more truthful, is usually welcome.
|
||||
- OpenRig runs on agents working in context with tools. Before adding code for a behaviour, ask whether an agent with
|
||||
the right instructions and today's commands could do it. A skill or a page of guidance is often the better change.
|
||||
- Where to look: the capability map above, and the `agent-operated-workflows` skill, which ships in the
|
||||
`openrig-core` plugin (`packages/daemon/assets/plugins/openrig-core/skills/agent-operated-workflows/SKILL.md`).
|
||||
- A change that adds a refusal, a prompt or a required step needs the concrete case in CONTRIBUTING.md: who is
|
||||
harmed, how, and what it costs everyone else.
|
||||
- One concern per pull request. Keep the diff reviewable in one sitting.
|
||||
|
||||
## When the map is wrong
|
||||
|
||||
Fix it in the same pull request, or open an issue. Update a document's `last-verified-against-source` marker only when
|
||||
you have actually checked it against that commit.
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
name: developing-openrig
|
||||
description: "Use when changing OpenRig's own source in a clone of the openrig repository: finding which package or file owns a behaviour, choosing which tests to run, testing a change without disturbing the OpenRig daemon your own session runs on, judging whether a diff touches a high-risk area, or preparing a pull request. Not for operating rigs (openrig-skills) or designing rig topologies."
|
||||
---
|
||||
|
||||
# Developing OpenRig
|
||||
|
||||
This skill is a map to the other maps. Each one is incomplete and may be stale: where a map and the code disagree, the
|
||||
code is right, and fixing the map is a welcome contribution.
|
||||
|
||||
## First: which OpenRig are you in?
|
||||
|
||||
If you are an agent running inside OpenRig, two different OpenRigs are in play:
|
||||
|
||||
- **The installed one runs your session.** `rig --version` and `rig daemon status` describe it.
|
||||
- **The checkout is what you're changing.** `git rev-parse HEAD` describes it.
|
||||
|
||||
Restarting the installed daemon does not run your checkout, and stopping it stops the agents running on it, including
|
||||
you. Test your change with the repo's own harnesses, which start a private daemon and tmux server (see "Test it" below),
|
||||
not by restarting the daemon you live in.
|
||||
|
||||
## Find your way
|
||||
|
||||
| You want to know | Read | How much to trust it |
|
||||
|---|---|---|
|
||||
| What OpenRig can already do, before you decide something is missing | `rig context get onboarding-width/public-what-you-can-do.md`; in this checkout, `packages/daemon/context-packs-src/onboarding-width/public-what-you-can-do.md` (the installed copy can differ) | The capability map; check it before proposing a new command or calling a gap a defect |
|
||||
| Whether a `rig` command already exists | `rig --help`: the full command list, before you decide a command is missing | The running binary; read the whole list, not only the command you expected |
|
||||
| Whether a problem is already known | The "Known problems to compare against" section of [`docs/reference/help.md`](../../../docs/reference/help.md) | Lists known issues by symptom; check an issue's current status before relying on it |
|
||||
| Whether a feature is already planned | [`ROADMAP.md`](../../../ROADMAP.md) | Check it before proposing a feature |
|
||||
| How the packages fit, the request path, where to add a command, route, migration, adapter, skill, context pack or scenario | [`ARCHITECTURE.md`](../../../ARCHITECTURE.md) | Checked against the commit it names; counts come with the command to refresh them |
|
||||
| Whether your change touches a high-risk area, what depends on it, what broke there before | [`docs/as-built/arteries.md`](../../../docs/as-built/arteries.md) | Incomplete by design: absence from it doesn't make a change safe |
|
||||
| What to run before you push, and what each layer can and can't prove | [`docs/as-built/test-layers.md`](../../../docs/as-built/test-layers.md) | Checked against the commit it names |
|
||||
| The exact behaviour of a `rig` command | `rig <command> --help` on the running binary, then `packages/cli/src/commands/` | The binary and the code win over any document |
|
||||
| A subsystem in depth | [`docs/as-built/`](../../../docs/as-built/README.md) | Each module names the commit it was last checked against in `last-verified-against-source`; compare that marker with `git log` before relying on it |
|
||||
| Rig spec, agent spec and other user-facing reference | [`docs/reference/`](../../../docs/reference/) | Ships to every user; if you find drift, fix it there |
|
||||
| Which shipped skill covers a task | [`skills/_canonical/core/openrig-skills/SKILL.md`](../../../skills/_canonical/core/openrig-skills/SKILL.md) | The index of the skills that ship with OpenRig |
|
||||
| How to open a good pull request, and what review looks like | [`CONTRIBUTING.md`](../../../CONTRIBUTING.md) | Current |
|
||||
|
||||
If none of these answers your question, search the code before inventing an answer. A missing map is worth an issue.
|
||||
|
||||
## Before you change an artery
|
||||
|
||||
If your diff touches anything in `arteries.md` (message delivery, launch and resume, the queue, rig identity, skill
|
||||
projection, process observation, migrations, restore):
|
||||
|
||||
1. Read the artery row: what depends on it, and the past regressions listed there.
|
||||
2. Describe the downstream effect in your pull request, not just the diff.
|
||||
3. Add or extend a stub-agent scenario, or say precisely why the stub can't exercise it and what you ran instead.
|
||||
|
||||
## Test it
|
||||
|
||||
The short version is `npm run build`, `npm run lint`, `npm test`. `test-layers.md` has the full ladder, including the
|
||||
stub-agent scenarios, which run the real CLI, daemon, tmux and SQLite with scripted agents and no model cost.
|
||||
|
||||
- The scenario runner starts its own daemon and tmux server and drops your session's `TMUX` and daemon variables, so
|
||||
it won't touch the daemon your session runs on.
|
||||
- A stub proves OpenRig's own plumbing, not how Claude Code or Codex behave. Say which you exercised.
|
||||
|
||||
## How we judge changes
|
||||
|
||||
OpenRig is a coordination layer for a trusted environment: it should help agents and people keep work flowing.
|
||||
|
||||
- A change that removes friction, or makes state more truthful, is usually welcome.
|
||||
- OpenRig runs on agents working in context with tools. Before adding code for a behaviour, ask whether an agent with
|
||||
the right instructions and today's commands could do it. A skill or a page of guidance is often the better change.
|
||||
- Where to look: the capability map above, and the `agent-operated-workflows` skill, which ships in the
|
||||
`openrig-core` plugin (`packages/daemon/assets/plugins/openrig-core/skills/agent-operated-workflows/SKILL.md`).
|
||||
- A change that adds a refusal, a prompt or a required step needs the concrete case in CONTRIBUTING.md: who is
|
||||
harmed, how, and what it costs everyone else.
|
||||
- One concern per pull request. Keep the diff reviewable in one sitting.
|
||||
|
||||
## When the map is wrong
|
||||
|
||||
Fix it in the same pull request, or open an issue. Update a document's `last-verified-against-source` marker only when
|
||||
you have actually checked it against that commit.
|
||||
+4
-1
@@ -4,9 +4,12 @@
|
||||
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`.
|
||||
The same guide is at https://www.openrig.dev/help/agents.
|
||||
- **Email:** hello@openrig.dev, which needs no GitHub account.
|
||||
- **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.
|
||||
template. Include `rig --version`, your OS, Node and tmux versions, the harnesses involved, 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).
|
||||
|
||||
+8
-2
@@ -22,8 +22,14 @@ notes/*
|
||||
*.sqlite-wal
|
||||
*.sqlite.pre-reboot-backup
|
||||
.openrig/
|
||||
.claude
|
||||
.agents
|
||||
.claude/*
|
||||
!.claude/skills/
|
||||
.claude/skills/*
|
||||
!.claude/skills/developing-openrig/
|
||||
.agents/*
|
||||
!.agents/skills/
|
||||
.agents/skills/*
|
||||
!.agents/skills/developing-openrig/
|
||||
.openrig
|
||||
.worktrees/
|
||||
|
||||
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
# OpenRig architecture
|
||||
|
||||
> **This map is not the territory.** It is a zoomed-out guide for finding your way around the
|
||||
> code. It is incomplete by design and it may be stale. Where it disagrees with the code, the code
|
||||
> is right; please fix the page. Every path, symbol, count and command below was checked against
|
||||
> commit `c750304f` (0.6.6 development). Each count shows the command that produced it, so you
|
||||
> can re-run it.
|
||||
|
||||
## What OpenRig is
|
||||
|
||||
OpenRig runs a team of coding agents on your machine as one system. A local daemon keeps the team's
|
||||
state in SQLite and runs each agent as an ordinary Claude Code or Codex session (or a plain
|
||||
terminal, a Pi or Oh My Pi (OMP) runner, or a scripted test stub) inside tmux. People and agents drive it through the
|
||||
`rig` CLI, a terminal UI (TUI) and an MCP server, all of which talk to the daemon over local HTTP.
|
||||
The agents themselves are unmodified. What OpenRig adds is the coordination around them: launching
|
||||
and restoring agents, delivering messages, keeping durable work in a queue, and giving each agent
|
||||
the right skills and context.
|
||||
|
||||
## Packages
|
||||
|
||||
| Directory | Package | What it is | How it ships |
|
||||
|---|---|---|---|
|
||||
| `packages/daemon` | `@openrig/daemon` (private) | The daemon: HTTP routes, domain services, SQLite migrations, runtime adapters, built-in specs, skills, plugins, static context-pack sources | Copied into `@openrig/cli` |
|
||||
| `packages/cli` | `@openrig/cli` | The `rig` command, the MCP server, daemon start and stop | The only published package |
|
||||
| `packages/tui` | `@openrig/tui` (private) | The terminal UI | Copied into `@openrig/cli`; opened by `rig tui`, by bare `rig` in a terminal, or by the `openrig-tui` bin |
|
||||
| `packages/ui` | `@openrig/ui` (private) | The React web UI, in **maintenance mode**: it still ships and runs, but gets no new feature work. The CLI and TUI are the supported surfaces | Built files copied into `@openrig/cli` and served by the daemon |
|
||||
| `packages/test-system` | none (not an npm workspace) | The stub-agent scenario library, the CI scenario harness, live-model eval cases | Not shipped |
|
||||
|
||||
**What bundles what.** `packages/cli/package.json` publishes `LICENSE`, `dist`, `daemon`, `ui`,
|
||||
`tui` and `scripts`. `scripts/build-package.sh` (the CLI's `prepublishOnly`) builds the daemon, UI,
|
||||
TUI and CLI, then assembles them inside `packages/cli/`: the daemon's `dist`, `assets`, `specs` and
|
||||
`policies`, the generated context packs and a copy of `docs/reference/` go under `daemon/`; the UI
|
||||
build goes to `ui/dist`; the TUI build goes to `tui/dist`. So `docs/reference/` ships to every
|
||||
user; `docs/as-built/` and this file do not.
|
||||
|
||||
**Sharing code between packages.** The CLI and TUI import daemon code only through the subpaths in
|
||||
the `exports` field of `packages/daemon/package.json` (21 at this commit), for example
|
||||
`@openrig/daemon/attention`, which points at `packages/daemon/src/attention-surface.ts`. The CLI
|
||||
uses 19 of them and the TUI uses 2. In development they resolve through the npm workspace. At
|
||||
package time `scripts/rewrite-daemon-imports.mjs` rewrites them to the shipped `daemon/dist` copy
|
||||
and fails the build on a specifier with no `exports` entry. To share new daemon code, add a
|
||||
`*-surface.ts` file and an `exports` entry.
|
||||
|
||||
## The request path
|
||||
|
||||
```
|
||||
rig (CLI) TUI MCP server (rig mcp serve, stdio)
|
||||
\ | /
|
||||
+---- HTTP to the daemon (port 7433 by default) --------------+
|
||||
|
|
||||
packages/daemon/src/server.ts createApp(): context middleware, /healthz, app.route("/api/...")
|
||||
|
|
||||
packages/daemon/src/routes/ one Hono router per area; reads services with c.get(...)
|
||||
|
|
||||
packages/daemon/src/domain/ services, repositories, orchestrators, event bus
|
||||
/ | \
|
||||
SQLite (db/) tmux (adapters/tmux.ts) runtime adapters (adapters/*-adapter.ts)
|
||||
claude-code, codex, pi, omp, terminal, stub
|
||||
```
|
||||
|
||||
**Inside the daemon** (`packages/daemon/src/`):
|
||||
|
||||
- **Startup.** `index.ts` is the process entry: it resolves where to listen, starts the server and
|
||||
runs the periodic queue sweeps. It calls `createDaemon()` in `startup.ts`, which opens the
|
||||
database (`db/connection.ts`: WAL mode, foreign keys on), runs `migrate(db, ALL_MIGRATIONS)`,
|
||||
constructs every service and adapter, and passes them to `createAppWithWebSocket(deps)` in
|
||||
`server.ts`.
|
||||
- **`server.ts`.** In `createApp(deps)`, an early `app.use("*", ...)` middleware (after the
|
||||
request-phase observer and the JSON body-parse tracker) puts each
|
||||
service on the request context (`c.set("<name>" as never, deps.<name>)`), serves `/healthz`, and
|
||||
mounts each router with `app.route("/api/<area>", ...)`. Unknown `/api/*` paths get a JSON 404;
|
||||
other GET requests serve the web UI's built files.
|
||||
- **`routes/`.** Thin handlers. For example, `routes/ps.ts` reads `psProjectionService` from the
|
||||
context and returns its entries as JSON. `routes/terminal-ws.ts` registers the terminal
|
||||
WebSocket, and `routes/require-sender-identity.ts` is a shared helper rather than a router.
|
||||
- **`domain/`.** The behaviour: repositories over SQLite (`rig-repository.ts`,
|
||||
`queue-repository.ts`, `session-registry.ts`), orchestration (`startup-orchestrator.ts`,
|
||||
`node-launcher.ts`, `rigspec-instantiator.ts`), message delivery (`session-transport.ts`,
|
||||
`seat-delivery-guard.ts`) and events (`event-bus.ts`). Larger areas have their own
|
||||
subdirectories, such as `gateway/`, `provider/`, `policies/`, `scope/`, `context-packs/` and
|
||||
`mission-control/`.
|
||||
- **`adapters/`.** The outside world. `tmux.ts` wraps tmux. The six `RuntimeAdapter`
|
||||
implementations (OMP's extends Pi's) launch each runtime and project skills and files into it. `cmux.ts` and
|
||||
`compose-services-adapter.ts` cover other integrations.
|
||||
- **`db/`.** `connection.ts`, `migrate.ts`, `all-migrations.ts` and `migrations/`.
|
||||
- **`middleware/auth-bearer-token.ts`.** The bearer-token check for operator write routes, plus
|
||||
the loopback and Tailscale bind detection used at startup.
|
||||
- **Live updates** go out as server-sent events: `routes/events.ts` uses Hono's `streamSSE`, and
|
||||
the TUI subscribes to `/api/activity/events`.
|
||||
- **State** lives in `$OPENRIG_HOME` (default `~/.openrig`). The database is `openrig.sqlite`
|
||||
there (`daemon-db-path.ts`).
|
||||
|
||||
**The clients:**
|
||||
|
||||
- **CLI.** `createProgram()` in `packages/cli/src/index.ts` registers every top-level command;
|
||||
the commands live in `packages/cli/src/commands/`. Most are thin clients: they call a daemon route
|
||||
through `DaemonClient` (`packages/cli/src/client.ts`) and render the result. A few lifecycle
|
||||
helpers open the database file directly. Bare `rig` in a terminal opens the TUI
|
||||
(`packages/cli/src/front-door.ts`).
|
||||
- **MCP.** `createMcpServer(client)` in `packages/cli/src/mcp-server.ts` registers the tools over
|
||||
the same `DaemonClient`. `rig mcp serve` (`packages/cli/src/commands/mcp.ts`) runs it on stdio.
|
||||
- **TUI.** `packages/tui/src/daemon-client.ts` is its only HTTP module. It reads existing daemon
|
||||
projections, for example `/api/ps`, `/api/rigs/summary` and `/api/queue/list`, and the
|
||||
`/api/activity/events` stream when the daemon offers it. The daemon URL comes from `--url`,
|
||||
`OPENRIG_URL`, or `http://127.0.0.1:7433`. Agents can drive a running TUI through its control
|
||||
socket (`packages/tui/src/socket-server.ts`).
|
||||
- **Web UI.** Served by the daemon from the bundled `ui/dist`.
|
||||
|
||||
**In a development checkout,** `rig daemon start` prefers `packages/daemon/dist` over the bundled
|
||||
copy (`resolveDaemonPath` in `packages/cli/src/daemon-lifecycle.ts`), and the TUI launcher prefers
|
||||
`packages/tui/dist` (`packages/cli/src/front-door.ts`). Rebuild the package you changed. A daemon
|
||||
that is already running keeps running the code it started with.
|
||||
|
||||
## Key counts
|
||||
|
||||
At commit `c750304f`. Run these from the repository root to refresh them.
|
||||
|
||||
| What | Count | Command |
|
||||
|---|---|---|
|
||||
| Database migrations | 94 files (latest: `095_rig_non_interruptive.ts`; there is no 093) | `git ls-files packages/daemon/src/db/migrations \| wc -l` |
|
||||
| Files in `routes/` | 67 | `git ls-files packages/daemon/src/routes \| wc -l` |
|
||||
| ... of which create a Hono router | 65 | `git grep -l 'new Hono' -- packages/daemon/src/routes \| wc -l` |
|
||||
| `app.route(...)` mounts in `server.ts` | 69 | `grep -c 'app.route(' packages/daemon/src/server.ts` |
|
||||
| Top-level `rig` commands | 87 | `grep -c 'program.addCommand(' packages/cli/src/index.ts` |
|
||||
| Runtime adapters | 6 (`claude-code`, `codex`, `pi`, `omp`, `terminal`, `stub`); the command counts 5 because `OmpRuntimeAdapter` extends `PiRuntimeAdapter` | `git grep -l 'implements RuntimeAdapter' packages/daemon/src \| wc -l` |
|
||||
| MCP tools | 18 | `grep -c 'server.tool(' packages/cli/src/mcp-server.ts` |
|
||||
| Daemon `exports` subpaths | 21 | `node -p 'Object.keys(require("./packages/daemon/package.json").exports).length'` |
|
||||
| Library scenarios | 15 | `grep -l '^scenario:' packages/test-system/scenarios/*.yaml \| wc -l` |
|
||||
|
||||
## Where to add things
|
||||
|
||||
Each recipe names the files to touch. Read a neighbouring example before you start; the code is
|
||||
the authority.
|
||||
|
||||
### A CLI command
|
||||
|
||||
1. Create `packages/cli/src/commands/<name>.ts` exporting a function that returns a commander
|
||||
`Command` and takes optional injected dependencies. `packages/cli/src/commands/unarchive.ts`
|
||||
is a short example.
|
||||
2. In the action, get the daemon with `getDaemonStatus(...)` and `daemonStatusGuard(status)`
|
||||
(`packages/cli/src/daemon-lifecycle.ts`), then call it with
|
||||
`new DaemonClient(getDaemonUrl(status))` (`packages/cli/src/client.ts`). Offer `--json` for
|
||||
agents, and set `process.exitCode` on failure.
|
||||
3. Register it in `createProgram()` in `packages/cli/src/index.ts` with
|
||||
`program.addCommand(...)`. If it takes injected dependencies, add them to `ProgramDeps` in the
|
||||
same file.
|
||||
4. Add `packages/cli/test/<name>.test.ts`. `packages/cli/test/archive.test.ts` shows how tests
|
||||
inject mocked lifecycle dependencies.
|
||||
5. If the command needs new daemon behaviour, add a route as well.
|
||||
|
||||
### A daemon route
|
||||
|
||||
1. Create `packages/daemon/src/routes/<area>.ts` with a Hono router. Both styles exist: a
|
||||
constant (`export const psRoutes = new Hono()` in `routes/ps.ts`) and a factory
|
||||
(`whoamiRoutes()` in `routes/whoami.ts`).
|
||||
2. Read services from the request context with `c.get("<name>" as never)`. Keep the behaviour in
|
||||
`packages/daemon/src/domain/`.
|
||||
3. Mount it in `createApp()` in `packages/daemon/src/server.ts` with
|
||||
`app.route("/api/<area>", ...)`, above the `/api/*` 404 catch-all.
|
||||
4. A new service needs three more edits: a field on `AppDeps` and a `c.set(...)` line in
|
||||
`server.ts`, and its construction in `createDaemon()` in `packages/daemon/src/startup.ts`.
|
||||
5. Test it the way `packages/daemon/test/whoami-routes.test.ts` does: build a small Hono app,
|
||||
`c.set` the service, mount the router and call `app.request(...)`. `createFullTestDb()` in
|
||||
`packages/daemon/test/helpers/test-app.ts` gives you an in-memory database migrated with the
|
||||
test fixture list (`migrationsForFullTestDb`).
|
||||
|
||||
### A database migration
|
||||
|
||||
1. Create `packages/daemon/src/db/migrations/<NNN>_<name>.ts` exporting a `Migration`
|
||||
(`{ name: "<NNN>_<name>.sql", sql: "..." }`; the type is in `packages/daemon/src/db/migrate.ts`).
|
||||
`089_classification_identity_provenance.ts` is a one-line example.
|
||||
2. Take the next number after the highest on `main` (095 at this commit). Parallel pull requests
|
||||
often pick the same number, so check again and renumber if `main` moved before you merge.
|
||||
3. Import it in `packages/daemon/src/db/all-migrations.ts` and append it to `ALL_MIGRATIONS`.
|
||||
Startup runs exactly that list (`startup-migrations-mirror.test.ts` pins this).
|
||||
4. Add it to `migrationsForFullTestDb` in `packages/daemon/test/helpers/test-app.ts`, or list it in
|
||||
`migrationsForFullTestDbExclusions` with a reason. `migration-fixture-parity.test.ts` fails
|
||||
otherwise.
|
||||
5. `migrate()` sorts migrations by name and records each applied name in `schema_migrations`, so
|
||||
an install that already applied a migration never runs it again. Editing a migration that has
|
||||
shipped changes nothing for those installs; add a new one instead. A long-lived install applies
|
||||
every new migration on its next start, against real data.
|
||||
|
||||
### A runtime adapter
|
||||
|
||||
1. Implement `RuntimeAdapter` from `packages/daemon/src/domain/runtime-adapter.ts`: `runtime`,
|
||||
`listInstalled`, `project`, `deliverStartup`, `launchHarness` and `checkReady`. The smallest
|
||||
implementation is `packages/daemon/src/adapters/terminal-adapter.ts`; the scripted test double
|
||||
is `stub-runtime-adapter.ts`.
|
||||
2. Register it in `packages/daemon/src/startup.ts`. The adapter maps are keyed by runtime name:
|
||||
`adapters:` (for the pod instantiator) and `runtimeAdapters:`. The context monitor has its own
|
||||
map for the runtimes it samples.
|
||||
3. Accept the runtime name in rig spec validation: `SUPPORTED_RUNTIMES` in
|
||||
`packages/daemon/src/domain/rigspec-preflight.ts`. Other runtime lists, such as `RuntimeHint` in
|
||||
`domain/discovery-types.ts` and `LEGACY_KNOWN_RUNTIMES` in `domain/rigspec-schema.ts`, may need
|
||||
it too; search for an existing runtime name such as `"pi"` to find them.
|
||||
4. Launch, projection and readiness are arteries. Read
|
||||
[docs/as-built/arteries.md](docs/as-built/arteries.md) first.
|
||||
|
||||
### A shipped skill
|
||||
|
||||
Skills ship from three copies in this repository (the "edges"), listed in
|
||||
`scripts/skill-edge-layout.generated.json`:
|
||||
|
||||
| Edge | Path | Layout |
|
||||
|---|---|---|
|
||||
| `spec` | `packages/daemon/specs/agents/shared/skills/` | by category (`core`, `pm`, `pods`, `process`) |
|
||||
| `plugin` | `packages/daemon/assets/plugins/openrig-core/skills/` | flat |
|
||||
| `canonical` | `skills/_canonical/` | public mirror of the `spec` copy |
|
||||
|
||||
Each skill in the layout file lists which edges carry it. At this commit, 33 skills are in `spec`
|
||||
and `canonical`, 17 only in `plugin`, and 2 in all three.
|
||||
|
||||
- `npm run mirror-skills:check` (part of `npm test`) hashes every file in each edge and compares the
|
||||
hashes with `scripts/skill-edge-digests.generated.json`. It also fails on a `SKILL.md` the layout
|
||||
does not list.
|
||||
- The full apply, `npm run mirror-skills`, regenerates all three edges from the maintainers' skill
|
||||
source, which lives outside this repository. It needs `OPENRIG_SKILL_CANON_ROOT` and three
|
||||
authority-file environment variables, so an outside contributor cannot run it.
|
||||
- **To fix a skill:** edit the file in every edge that carries it (same bytes), run
|
||||
`node scripts/regen-edge-digests.mjs` to refresh the hashes from disk, then run
|
||||
`npm run mirror-skills:check`. Say in the pull request that you edited mirrored skill files: the
|
||||
next full apply regenerates these copies from the maintainers' source, so the change has to land
|
||||
there too.
|
||||
- **To add a skill:** open an issue first. Its layout entry is generated from files that are not
|
||||
in this repository.
|
||||
- Every shipped skill also becomes a context pack at package time (next recipe).
|
||||
|
||||
### A context pack
|
||||
|
||||
- **Static packs** are hand-written: a directory under `packages/daemon/context-packs-src/<name>/`
|
||||
with a `manifest.yaml` and its files. Keep `version: "0"` in the manifest; the generator stamps
|
||||
the package version. A pack file may be a symlink to a file under `docs/reference/`, so a
|
||||
reference document has one source (for example `help/help.md` points to
|
||||
`docs/reference/help.md`). Any other symlink fails the build. There are 5 static packs at this
|
||||
commit.
|
||||
- **Generated packs** go to `packages/daemon/context-packs/`, which is gitignored and never edited
|
||||
by hand. `scripts/generate-context-packs.mjs` builds it at package time from every shipped skill
|
||||
and every static pack.
|
||||
- `npm run generate-context-packs:check` (part of `npm test`) validates every pack through the
|
||||
daemon's own manifest parser, so build the daemon first. It also runs the leak scan over the
|
||||
static sources, including the `docs/reference/` files they link to.
|
||||
- The daemon serves the shipped packs through `rig context get`; `startup.ts` registers the bundled
|
||||
directory as its built-in root.
|
||||
|
||||
### A scenario
|
||||
|
||||
Scenarios run the real CLI, daemon, tmux and SQLite with scripted stub agents (`runtime: stub`)
|
||||
and no model cost.
|
||||
|
||||
1. Add `packages/test-system/scenarios/<defect-class>.yaml`, named for the class of bug it
|
||||
catches. Topology fixtures sit beside it (`*-stub.yaml`).
|
||||
2. The format is checked by `validateScenario` in
|
||||
`packages/daemon/test/helpers/scenario-schema.ts`. Step verbs: `up`, `down`, `send`, `restart`,
|
||||
`restore`, `emit`, `mutate`, `policy`, `seed_regression`, `daemon`, plus `expect`. `expect` may
|
||||
only read shipped surfaces: `ps`, `queue`, `stream`, `scope`, `pane`, `transcript`,
|
||||
`tui_socket`, `policy_provenance`.
|
||||
3. Today the runner binds `up`, `down`, `send`, `restart` and `daemon`. `restore`, `emit`,
|
||||
`mutate` and `policy` fail with `UnboundActionError`, and `seed_regression` needs a fault
|
||||
controller (`packages/daemon/test/helpers/scenario-real-deps.ts`).
|
||||
4. Write a header comment naming the defect class and the seed: what a seeded regression plants,
|
||||
and which `expect` must catch it. A scenario counts only as a pair: it passes on healthy code
|
||||
and fails on the seeded run (`packages/test-system/README.md`).
|
||||
5. **Run it on your machine** after `npm run build`:
|
||||
`node --import tsx packages/daemon/scripts/run-scenarios.mjs <file.yaml>`. It uses a private
|
||||
daemon and a private tmux server, and builds its environment from `HOME`, `PATH` and `TERM`
|
||||
only, so your session's `TMUX` and daemon variables are dropped and it is safe to run from
|
||||
inside tmux, including an agent's pane. It supplies no fault controller, so a
|
||||
`seed_regression` step fails there; the seeded pair runs in the container path.
|
||||
6. **CI.** The `installed-scenario` job runs `scripts/run-pr-scenarios.sh` in disposable,
|
||||
network-less containers built from the packed package. It runs four cases: `fixture` and `library`
|
||||
(`queue-baton-survives-restart.yaml`), each with a healthy run and a seeded lost-baton run, plus
|
||||
`transcript` and `capture`, healthy runs only. Adding a seeded case means changing three files:
|
||||
the `--case` list in `scripts/run-pr-scenarios.sh`, `CASES` in
|
||||
`packages/test-system/ci/result.mjs`, and the fault controller in
|
||||
`packages/test-system/ci/run.mjs`; a healthy-only case goes in `PASSING_CASES` in the first two
|
||||
and needs no fault controller. [docs/as-built/test-layers.md](docs/as-built/test-layers.md)
|
||||
covers both run modes, running a case on your own Docker host, and what a stub can and cannot
|
||||
prove.
|
||||
|
||||
## Guards you will meet
|
||||
|
||||
`npm test` runs `test:repo` (builds the daemon, runs `node --test scripts/*.test.mjs`, the docs
|
||||
guard, `mirror-skills --check` and `generate-context-packs --check`) and then the daemon, CLI and
|
||||
TUI suites. `npm run test:ui` runs the web UI suite separately. `npm run lint` typechecks all four
|
||||
packages.
|
||||
|
||||
- **Docs guard** (`scripts/check-docs-guard.mjs`). Under `docs/`, git may track only
|
||||
`docs/as-built/`, `docs/reference/`, `docs/releases/` and exactly `docs/DESIGN.md`;
|
||||
`.gitignore` ignores the rest of `docs/`. Remember that `docs/reference/` ships inside the npm
|
||||
package, and the daemon copies its Markdown files to `$OPENRIG_HOME/reference/` when it
|
||||
starts. Files in `docs/as-built/` carry a `last-verified-against-source` commit in their
|
||||
frontmatter.
|
||||
- **Internal-leak guard** (`scripts/internal-leak-scanner.mjs`, with rules in
|
||||
`scripts/internal-tokens.generated.json`). It refuses maintainer-internal names and paths in
|
||||
content that ships. It runs in `generate-context-packs.mjs` (static pack sources, on every
|
||||
`npm test`), in the full skill mirror apply (each public skill), in `scripts/build-package.sh`
|
||||
(the daemon `specs/` tree before it is copied into the package), and in the release-time
|
||||
substance gate (`scripts/check-substance-gate.mjs`, `npm run gate:substance`) over the packed
|
||||
CLI. No workflow runs it over a pull request's whole diff.
|
||||
- **Portability report** (`.github/workflows/portability-report.yml`, running
|
||||
`scripts/portability-report.mjs`). On each pull request it lists added lines containing
|
||||
machine-specific values: credentials, home and temporary paths, network addresses, email
|
||||
addresses. It never fails the job; you read it and decide.
|
||||
- **Generated files.** Do not hand-edit `scripts/*.generated.json` or anything under
|
||||
`packages/daemon/context-packs/`. The skill-edge digests are the one generated file a contributor
|
||||
refreshes, with `node scripts/regen-edge-digests.mjs` (see "A shipped skill").
|
||||
The internal-token rules support optional `allowed_context_lines` for reviewed generic
|
||||
examples: the complete line must match exactly, including indentation and case. Unlike
|
||||
`allowed_context_substrings`, this does not exempt a line with added or substituted text;
|
||||
internal-path checks and the substance gate's human-review dispositions still apply.
|
||||
- **Hosted CI** (`.github/workflows/tests.yml`): `build-and-package`, `typecheck`, `repo-checks`,
|
||||
`package-tests` (macOS; daemon, CLI, TUI and UI suites with no credentials and no external
|
||||
network) and `installed-scenario`.
|
||||
|
||||
## Pointers
|
||||
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md): setup, what a pull request needs, what to expect from review.
|
||||
- [docs/reference/developing.md](docs/reference/developing.md): which checks block and which are
|
||||
advisory, and what `.github/workflows/tests.yml` runs.
|
||||
- [docs/reference/worktree-builds.md](docs/reference/worktree-builds.md): building in a git
|
||||
worktree, with its own `npm ci` rather than a symlinked `node_modules`.
|
||||
- [docs/reference/](docs/reference/): user and operator reference; it ships with the package.
|
||||
- [docs/as-built/](docs/as-built/README.md): module-by-module descriptions of the system. **These
|
||||
are being re-verified for 0.6.6.** Each page's `last-verified-against-source` names the commit it was
|
||||
checked against; until a page is re-verified, treat its counts as historical. Use them for
|
||||
orientation, then check the code.
|
||||
- [docs/as-built/test-layers.md](docs/as-built/test-layers.md): what each test layer covers and
|
||||
what it cannot catch.
|
||||
- [docs/as-built/arteries.md](docs/as-built/arteries.md): the areas where a small change has a
|
||||
large effect, what depends on them, and what broke there before.
|
||||
- [packages/test-system/ci/README.md](packages/test-system/ci/README.md): the CI scenario job.
|
||||
- [docs/releases/](docs/releases/) and [CHANGELOG.md](CHANGELOG.md): release history, written by
|
||||
the maintainers at release time.
|
||||
+1403
File diff suppressed because it is too large.
Load diff
+32
-12
@@ -8,12 +8,12 @@ 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.
|
||||
OS, Node and tmux versions, 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.
|
||||
- **Features and behaviour changes:** open an issue or a Discussion in *Ideas* first. Search issues
|
||||
and Discussions, and check ROADMAP.md, before proposing a feature. 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.
|
||||
|
||||
@@ -45,17 +45,29 @@ Node `^22 || ^24` and a working `tmux` are required. Then:
|
||||
```bash
|
||||
git clone https://github.com/mvschwarz/openrig.git
|
||||
cd openrig
|
||||
npm install
|
||||
npm ci # exactly what package-lock.json records
|
||||
npm run build # all workspaces
|
||||
npm test # repo checks + daemon, cli, tui test suites
|
||||
npm run lint # typecheck every package
|
||||
```
|
||||
|
||||
`npm ci` installs the locked versions and never rewrites `package-lock.json`, which is what CI
|
||||
runs too. Use `npm install <package>` only when you're adding or changing a dependency, and commit
|
||||
the lockfile change with it.
|
||||
|
||||
`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`.
|
||||
specific failure. A shipped skill may exist in more than one copy (see ARCHITECTURE.md): edit every copy, then run
|
||||
`node scripts/regen-edge-digests.mjs` and commit the digest file it changes (the full
|
||||
`npm run mirror-skills` apply needs maintainer-only inputs; see "A shipped skill" in
|
||||
[ARCHITECTURE.md](ARCHITECTURE.md#a-shipped-skill)). The repository checks include a docs guard: a
|
||||
tracked file under `docs/` must be in `docs/as-built/`, `docs/reference/` or `docs/releases/`
|
||||
(`docs/DESIGN.md` is the one exception). They check that shipped first-run text names only teams, skills and
|
||||
commands that exist, and they validate the context-pack sources;
|
||||
`npm run generate-context-packs` rebuilds the generated packs locally, which aren't committed.
|
||||
The UI unit-test suite is separate from `npm test`: run `npm run test:ui`. CI runs it on every pull
|
||||
request as `package-tests (ui)`, so a change has to pass it.
|
||||
[docs/as-built/test-layers.md](docs/as-built/test-layers.md) lists every test layer, what CI runs,
|
||||
and what each layer can and cannot prove.
|
||||
|
||||
For hands-on development, read the [check requirements](docs/reference/developing.md),
|
||||
[worktree setup](docs/reference/worktree-builds.md), and
|
||||
@@ -77,7 +89,7 @@ your working copy.
|
||||
- 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: …`.
|
||||
`docs(reference): …`, `test: …`.
|
||||
|
||||
## The pull request
|
||||
|
||||
@@ -103,8 +115,16 @@ clarifying question or run the reproduction; a maintainer makes the merge decisi
|
||||
|
||||
## Where things live
|
||||
|
||||
- **Start here:** [ARCHITECTURE.md](ARCHITECTURE.md) maps the packages, the request path, and where to
|
||||
add a command, route, migration, adapter, skill, context pack or scenario.
|
||||
[docs/as-built/arteries.md](docs/as-built/arteries.md) lists the areas where a small change has a
|
||||
large effect; if your change touches one, read it first.
|
||||
- **Using a coding agent?** Claude Code and Codex load the repository's `developing-openrig` skill
|
||||
when they work in this checkout (`.claude/skills/` and `.agents/skills/`). It points at the maps
|
||||
above and at what to run before you push.
|
||||
- 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/`.
|
||||
- Skills: `packages/daemon/specs/agents/shared/skills/`, plugin skills under
|
||||
`packages/daemon/assets/plugins/`, and `skills/_canonical/`; 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`.
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
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. 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.
|
||||
OpenRig is open-source software for building and running your own network of agents. It 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.
|
||||
|
||||
It's the open-source system behind my AI civilization experiments.
|
||||
|
||||
**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)
|
||||
|
||||
@@ -12,13 +14,24 @@ OpenRig turns AI coding agents from a pile of terminal sessions into a persisten
|
||||
|
||||

|
||||
|
||||
**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.
|
||||
**Start here:** [the guided first-use path](docs/reference/getting-started.md): install, talk to the kernel operator, then choose a team for your first useful 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.
|
||||
Requires Node.js 22 or 24 and tmux, on macOS or Linux. On Linux, the distribution's own Node.js can be older (Ubuntu 24.04's is 18); install a supported version with [nvm](https://github.com/nvm-sh/nvm) (`nvm install 22`) or NodeSource. On a Mac with Apple silicon, use Node.js 22 ([compatibility history](docs/releases/v0.5.15.md#known-compatibility-limitation)). On Windows, use WSL2, the Windows route OpenRig supports; native Windows isn't supported. OpenRig's automated tests don't run on WSL2 yet; see [one user's working setup](docs/reference/getting-started.md#wsl2-a-reported-working-setup). 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.
|
||||
|
||||
**One command.** This runs OpenRig's install script from the `v0.6.8` release. It checks Node.js and npm, installs the latest published `@openrig/cli` with `npm install -g`, runs the Node.js and SQLite check, then runs `rig setup --dry-run` and `rig setup`. The first line prints that plan and changes nothing:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.6.8/scripts/install.sh | sh -s -- --dry-run
|
||||
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.6.8/scripts/install.sh | sh
|
||||
```
|
||||
|
||||
If a provider isn't signed in yet, the last step reports `FAILED [4/4]`, and `rig setup` lists the sign-in under "Some steps need attention". If the only remaining failures are provider sign-ins, the install steps finished; sign in to each selected provider as below and continue.
|
||||
|
||||
**Or step by step:**
|
||||
|
||||
```bash
|
||||
npm install -g @openrig/cli
|
||||
@@ -27,46 +40,102 @@ 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.
|
||||
|
||||
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).
|
||||
npm 11 and later can skip that postinstall script and print `npm warn install-scripts` naming `@openrig/cli`. That's expected: the CLI still works, and only the Node.js and SQLite check was skipped. To run it yourself, use `node "$(npm root -g)/@openrig/cli/scripts/check-abi.mjs"`; `rig doctor` checks the Node.js version only.
|
||||
|
||||
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.
|
||||
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; applying `rig setup` checks both harnesses and installs a missing one, plus Herdr unless declined with `--no-herdr`. Pass the choice with `--providers claude` or `--providers codex` so the other reads not selected instead of failed. The one-command install above doesn't know the choice, so it installs and checks both; for one provider, go step by step and run `rig setup --providers <choice>`. On macOS, the installing agent offers Ghostty once. Setup leaves an existing cmux installation alone. It is optional for the [selected-provider path](docs/reference/getting-started.md#choose-your-providers).
|
||||
|
||||
A team seat with no permission policy, per-seat choice or (for Codex) named profile launches with OpenRig's [team default](docs/reference/rig-spec.md#team-launch-defaults): Claude runs ordinary `rig` commands, project reads and common tests without prompting, while lifecycle commands such as `rig up` and `rig down` still ask; Codex also gets the OpenRig workspace and its pod's state directory as writable directories. Before launching, your agent recommends keeping that default, and offers to remember selected OpenRig commands in your native settings only if you want that, 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 keeps the team default and leaves settings unchanged. An existing explicit choice is reused. Say “Undo the OpenRig command allowances added by this setup” to remove only its additions.
|
||||
|
||||
If permission prompts are slowing you down, consider the
|
||||
[workshop bundle](https://openrig.dev/rigs/workshop). It ships with broad access
|
||||
and [non-interruptive mode](docs/reference/non-interruptive-mode.md): its agents
|
||||
bypass permission prompts and supported harness warning dialogs. The listing
|
||||
and before-install view explain that access before you choose it.
|
||||
|
||||
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` |
|
||||
### Your agent opens OpenRig for you
|
||||
|
||||
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.
|
||||
After installing, your agent opens the OpenRig TUI and the operator for you. To get back to them later, ask your
|
||||
agent for OpenRig or your agents in any words. After checking the selected login, it runs:
|
||||
|
||||
```sh
|
||||
rig daemon start # if stopped
|
||||
rig terminal open saved:kernel --window
|
||||
```
|
||||
|
||||
`--window` opens a new terminal tab or window itself, or a space in your current
|
||||
Herdr session. Below 120 measured columns,
|
||||
the **operator** fills the first page; from 120, the dashboard and operator share
|
||||
it equally. The advisor has a separate tab or tmux window.
|
||||
An installing agent can run it from its shell on the daemon's
|
||||
desktop; the person copies nothing. It uses Herdr when installed, otherwise
|
||||
the same layout in plain tmux, and preserves existing conversations. `rig tui`
|
||||
is the team dashboard. Installation is finished when you are talking to the
|
||||
operator; if you choose to talk later, that handoff remains pending.
|
||||
|
||||
If setup stops, read the named step: missing tools and selected-provider logins
|
||||
need their specific setup action. A harness permission rule, sandbox or automatic
|
||||
permission decision can also block the installing agent's command. The agent
|
||||
should name the command and reported reason, then help you resolve that specific
|
||||
step within your chosen permissions. A permission refusal is not an OpenRig
|
||||
installation result.
|
||||
|
||||
The desktop may need an Automation approval; SSH/headless sessions may have no
|
||||
display. Herdr can show first-run panels, and an 80×24 window can cramp the view.
|
||||
Follow [the OpenRig view checks and next steps](docs/reference/getting-started.md#what-can-interrupt-installation-and-the-openrig-view).
|
||||
Herdr is visible only inside a terminal you can see. Changing the shared TUI to
|
||||
`:terminals` does not open that terminal. The agent checks the visible window when
|
||||
it has desktop access and says what it could not verify otherwise. Over headless
|
||||
SSH, it asks you to open a new terminal window or tab and gives the exact connection
|
||||
and attachment command for the current view. If you choose a manual attachment
|
||||
after a failure,
|
||||
ask the installing agent for the exact command using your current operator
|
||||
session; run that in a new terminal window.
|
||||
|
||||
The kernel's operator helps you pick a first team. It asks what you want to build,
|
||||
recommends one of three, and fits it to the providers you have.
|
||||
|
||||
| Team | Talk to | Agents and purpose |
|
||||
| --- | --- | --- |
|
||||
| `starter` | `dev-build@starter` | A Claude Code builder and Codex reviewer for one bounded change |
|
||||
| `workshop` | `orch-lead@workshop` | A lead, builder, QA and reviewer for ongoing work in one repository; a rig bundle the operator installs from its pinned listing |
|
||||
| `factory` | `orch-lead@factory` | Seven: a lead, advisor, build, QA, design and two independent reviewers for sustained product work |
|
||||
|
||||
As shipped, `starter` uses both Claude Code and Codex. With only one of them, ask
|
||||
the kernel operator to adapt it: it writes a copy of the team for your providers
|
||||
under the same name. [The naming guide](docs/reference/topology-naming.md) explains
|
||||
how their pods and roles grow and how to name your own team. `first-project` is
|
||||
starter's old name and still starts it,
|
||||
unless you already have a rig named `first-project` or `starter`: then it refuses
|
||||
and names the `rig up <name> --existing` command that brings that rig back.
|
||||
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 teams. A missing unused provider is not a setup requirement.
|
||||
|
||||
To launch the starter yourself instead (the manual path, without the operator):
|
||||
|
||||
```bash
|
||||
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
|
||||
rig specs preview starter --kind rig
|
||||
rig up starter --cwd . --plan
|
||||
rig up starter --cwd .
|
||||
```
|
||||
|
||||
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.
|
||||
To see your agents, use `rig terminal open saved:kernel --window`. If that window cannot open, `rig tui --shared` is the named dashboard-only fallback; it does not show the operator conversation. The installing agent should resolve or explain the window failure, not finish by suggesting a command for you to type. Closing a viewing terminal does not mean you should relaunch the team.
|
||||
|
||||
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:
|
||||
Check project-seat readiness with `rig ps --nodes --rig starter` and resolve any authentication, trust or permission prompt before assigning work. If a seat stopped at such a prompt before its startup context arrived, `rig ps` shows the `rig seat continue <seat>` command that delivers it once the prompt is answered. Then give the builder one bounded outcome from your repository:
|
||||
|
||||
```bash
|
||||
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 send dev-build@starter 'Implement <one useful change>. Track the task in the queue and return its ID. Keep it local, verify the behavior, ask dev-review in this rig to check the exact candidate, and record the result and how I can try it.'
|
||||
rig queue list --destination dev-build@starter --limit 1000
|
||||
```
|
||||
|
||||
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.
|
||||
Sending a message does not itself create a queue item; the builder records the task. Read the final artifact and the review of its exact candidate, then return to the same builder 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
|
||||
|
||||
@@ -89,31 +158,36 @@ 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. |
|
||||
| **npm installation** | Installs the CLI (`rig` and `openrig-tui`), 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. Installs herdr by default on macOS and Linux unless declined with `--no-herdr`; an unavailable herdr install is a warning. On macOS the installing agent offers Ghostty, and `--ghostty` attempts it after acceptance. Existing cmux settings are left unchanged. `--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, the `refocusing` skill and the person-facing `rigs` 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. In a Git repository, newly created files under `.codex/plugins/shared:openrig-core/` (or the unqualified `.codex/plugins/openrig-core/`) are added to the repository's Git `info/exclude` inside an `# BEGIN OpenRig generated files` block; new `AGENTS.md`, `CLAUDE.md` and `CLAUDE.local.md` files stay visible with a warning. |
|
||||
| **Bundle install** (`rig bundle install`, or `rig up` with a `.rigbundle` or GitHub link) | Writes the bundle's files into the install target: `--target`, or the current directory for `rig up` and for a GitHub link. `rig bundle install` with a local archive needs `--target`. Routes its declared skills, plugins and context packs into your libraries. A GitHub link's archive is kept under `OPENRIG_HOME/bundle-imports/`, and installs are recorded in `OPENRIG_HOME/bundle-audit.jsonl`. A bundle that carries a project creates it under `workspace.projects_root` and records it, with the rig's association, in the workspace catalog. |
|
||||
| **Explicit permission configuration** | The built-in bootstrap does **not** add `rig` command allow rules to your settings files; a team seat with no permission policy, per-seat choice or (for Codex) named profile gets the per-launch [team default](docs/reference/rig-spec.md#team-launch-defaults) instead. Agent-guided setup recommends keeping it and requires your actual answer before the agent [adds remembered rules at your chosen scope](docs/reference/getting-started.md#have-your-agent-configure-permissions). No/no answer keeps the team default and 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
|
||||
- **Claude Code:** startup writes workspace trust and onboarding completion.
|
||||
With an explicit permission mode, it uses the launch-selected `HOME/.claude.json`,
|
||||
or `<CLAUDE_CONFIG_DIR>/.claude.json` when that variable is set. Classic startup
|
||||
writes `HOME/.claude.json`, and also `<CLAUDE_CONFIG_DIR>/.claude.json` when the
|
||||
daemon has that variable set. 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
|
||||
the collector script lives under `.openrig/`, and the activity-hook relay under
|
||||
the instance's `state/claude-activity-hooks/`. 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.
|
||||
older allowances. `CLAUDE_CONFIG_DIR` does not relocate the project-local 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.
|
||||
add MCP settings. Managed launches pass `-c check_for_update_on_startup=false`,
|
||||
which writes no config; a recognized update notice that still appears can be
|
||||
skipped, 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`
|
||||
@@ -128,14 +202,38 @@ 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.
|
||||
On that plain launch OpenRig first asks Codex for its own configuration and adds
|
||||
`-c sandbox_workspace_write.network_access=true` only when Codex answers that no
|
||||
configuration layer sets network access and no managed requirement could restrict
|
||||
it, so the seat can reach the local daemon. If Codex can't be asked or doesn't
|
||||
answer in time, the launch is left unchanged.
|
||||
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`.
|
||||
A team seat with no permission policy, per-seat choice or (for Codex) named
|
||||
profile also gets the per-launch team default: Claude gets `--settings` allowing
|
||||
ordinary `rig` commands, project reads and common tests, with a session-only
|
||||
PreToolUse hook that asks before lifecycle commands, and Codex also gets the
|
||||
OpenRig workspace root as a writable directory. Neither writes a permission file.
|
||||
Seats of the rig named `kernel` launch with an operational default instead,
|
||||
unless a permission policy, a per-seat choice or (for Codex) a named profile
|
||||
applies: Claude in `acceptEdits` with a per-launch allow list for its file tools,
|
||||
web fetches and operational commands (including reads under your home folder),
|
||||
and Codex with `-s danger-full-access -a never`. Neither writes a permission file.
|
||||
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.
|
||||
path, with no policy attached, selects Claude's bypass flag, Pi's `--approve` and
|
||||
Codex's `-s danger-full-access` without `-a never`; a resolved policy overrides
|
||||
that environment setting. The `builtin:auto` policy launches Claude with
|
||||
`--permission-mode auto`; Codex and Pi seats launch as they do without a policy (a
|
||||
named Codex profile still governs its sandbox, and Pi keeps its configured
|
||||
resource trust).
|
||||
`--non-interruptive` on `rig up` or `rig bundle install` additionally passes
|
||||
per-launch flags to full-bypass seats so Claude's bypass warning and Codex's
|
||||
full-access and GPT-5.1 migration notices don't stop startup; it writes nothing to
|
||||
their settings files and is saved on the rig. See
|
||||
[non-interruptive mode](docs/reference/non-interruptive-mode.md).
|
||||
|
||||
Permission mode controls native execution permissions; work posture is separate
|
||||
project guidance. Use `rig policy permissions list|show|current|apply` for rig
|
||||
@@ -174,29 +272,22 @@ OpenRig is a multi-agent harness — it manages the system that coding agents fo
|
||||
|
||||
Every agent runs in a tmux session you can attach to, inspect, and work with directly.
|
||||
|
||||
## Starter Rigs
|
||||
## Teams
|
||||
|
||||
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:
|
||||
Three teams grow with you, and the kernel operator helps you pick one:
|
||||
|
||||
- **`starter`**: a builder and a reviewer for one bounded change.
|
||||
- **`workshop`**: a lead, a builder, QA and a reviewer for ongoing work in one repository. It's a rig bundle, installed from its pinned listing.
|
||||
- **`factory`**: seven agents (a lead, an advisor, build, QA, design and two independent reviewers) for sustained product work. You bring ideas to the advisor, and the lead runs the team.
|
||||
|
||||
```bash
|
||||
rig specs preview product-team --kind rig
|
||||
rig up product-team
|
||||
rig specs preview factory --kind rig
|
||||
rig up factory
|
||||
```
|
||||
|
||||
Use it when you want a larger product squad: two orchestrators, implementation, QA, design, and two independent reviewers.
|
||||
Specialist teams: `code-review` (two independent reviews), `research` (an analyst and a synthesizer) and `pm` (a product lead, a researcher and a builder for prototypes).
|
||||
|
||||
For a smaller starter, use `conveyor`:
|
||||
|
||||
```bash
|
||||
rig specs preview conveyor --kind rig
|
||||
rig up conveyor
|
||||
```
|
||||
|
||||
`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).
|
||||
Also built in: `secrets-manager`, a HashiCorp Vault instance run by a specialist agent.
|
||||
|
||||
Browse the library:
|
||||
|
||||
@@ -221,7 +312,7 @@ Hono HTTP daemon
|
||||
- **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.
|
||||
- **Runtimes**: Native Claude Code and Codex sessions, terminal nodes, and Pi and Oh My Pi via RPC runners.
|
||||
|
||||
## Terminal UI and Workspaces
|
||||
|
||||
@@ -234,21 +325,21 @@ The TUI shows the team's coordination state; herdr and cmux show the actual agen
|
||||
With herdr installed and connected, open the starter's terminals together:
|
||||
|
||||
```bash
|
||||
rig terminal open first-project --provider herdr
|
||||
rig terminal open starter --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.
|
||||
For cmux, use `--provider cmux`. In the TUI, a rig's detail view has an `Open terminals ▸ rig <name>` link that opens a terminal view of the running seats of that rig (in a new window or tab, or the current Herdr session), laid out with herdr when installed, otherwise plain tmux; with herdr that is up to 16 seats per tab, in a workspace named after the rig. A seat it can't attach is reported as absent or degraded. 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.
|
||||
- **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.
|
||||
- **Seat**: A stable role and address in a rig, such as `dev-build@starter`. 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.
|
||||
- **Culture**: CULTURE.md sets coordination norms for the group. Research rigs get exploratory culture. Implementation rigs get conservative, trust-but-verify culture.
|
||||
- **Snapshot/Restore**: `rig down --snapshot` captures full state. `rig up <name>` restores from latest snapshot. Restore reports per-node outcomes: resumed, fresh-primed, awaiting-decision (the original conversation can't resume; choose `--fresh`), attention_required, or failed.
|
||||
- **RigBundle**: Portable archive with vendored AgentSpecs and SHA-256 integrity. Share topologies across machines. `rig up`, `rig bundle create`, `inspect` and `install` also take a GitHub folder link, `rig bundle check` checks a folder before you share it, and `rig bundle inspect` shows what a bundle will do before you install it. See [publishing a rig bundle](docs/reference/publishing-a-rig-bundle.md).
|
||||
- **Culture**: A rig's `CULTURE.md` (its `culture_file`) sets coordination norms for the group, on top of OpenRig's default culture, which every seat receives.
|
||||
|
||||
## Agent-Managed Software
|
||||
|
||||
@@ -264,7 +355,7 @@ 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).
|
||||
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); notes for later releases are in [CHANGELOG.md](CHANGELOG.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
|
||||
|
||||
@@ -329,13 +420,16 @@ 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 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
|
||||
- macOS or Linux. On Windows, use WSL2, the Windows route OpenRig supports; native
|
||||
Windows isn't supported. OpenRig's automated tests don't run on WSL2 yet; one user's
|
||||
working setup is in the [getting-started guide](docs/reference/getting-started.md#wsl2-a-reported-working-setup)
|
||||
|
||||
Optional:
|
||||
- herdr or cmux for terminal workspaces showing the agents together
|
||||
@@ -343,9 +437,9 @@ Optional:
|
||||
|
||||
## Setup and Troubleshooting
|
||||
|
||||
- `rig setup` attempts core machine preparation: tmux, cmux, Claude Code, Codex, and tmux defaults. It reports what it tried and what actually succeeded. If something fails, it gives the local agent enough context to finish the job.
|
||||
- `rig setup --full` attempts a broader operator workstation setup (jq, gh) on top of core.
|
||||
- `rig doctor` inspects current system health and helps diagnose problems after setup. Use it when something stops working or after machine changes.
|
||||
- `rig setup` attempts core machine preparation: it installs a missing Claude Code or Codex and writes tmux defaults. It installs Herdr by default on macOS and Linux; use `--no-herdr` if you decline it. On macOS it also installs a missing tmux with an existing Homebrew; elsewhere install tmux yourself (setup only checks it). On a Mac, the installing agent offers Ghostty once; accepting uses `rig setup --ghostty`, and declining leaves Terminal.app available. Existing cmux settings are preserved. It reports what it tried and what actually succeeded. If something fails, it gives the local agent enough context to finish the job.
|
||||
- `rig setup --full` attempts a broader operator workstation setup (jq, gh, via Homebrew, so macOS only) on top of core.
|
||||
- `rig doctor` inspects current system health and helps diagnose problems after setup. Use it when something stops working or after machine changes. It shares setup's local provider login checks. These do not prove provider acceptance or a working agent; also inspect `rig ps --nodes --rig <rig>`.
|
||||
|
||||
Both commands support `--json` for agent-driven workflows.
|
||||
|
||||
@@ -353,7 +447,7 @@ Before setup or managed launch, review [what OpenRig changes on your machine](#w
|
||||
|
||||
Already-running adopted sessions may need restart before they pick up newly written runtime config.
|
||||
|
||||
**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.
|
||||
**For agents:** First ask which available provider(s) the user wants, reusing an explicit choice, and pass it to setup (`rig setup --providers claude`, `codex` or `both`). 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. Starting OpenRig and opening its view are the last steps of installing, not extras, so do them without stopping to ask whether to go on: start the daemon if it is stopped, open the OpenRig TUI and operator for them (say so, then run `rig terminal open saved:kernel --window --json`; no commands for the person to copy), and hand the person's goal and project folder to the kernel operator ([installing-agent handoff](docs/reference/getting-started.md#installing-agent-handoff)). Installation is finished when the person is talking to the operator; don't implement the project yourself. 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
|
||||
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# Roadmap
|
||||
|
||||
These are things we'd welcome help with. Most came from the community, and most are small enough for one pull request.
|
||||
This isn't a schedule or a promise: we release often, and an item ships when a good change for it lands.
|
||||
|
||||
## How to help
|
||||
|
||||
1. Pick an item and comment on its issue to say you're working on it. If it has no issue yet, open one first.
|
||||
2. Read `CONTRIBUTING.md`, and ask your agent to use the `developing-openrig` skill. It's a map of how OpenRig is
|
||||
built, tested and changed.
|
||||
3. Open a pull request that says what a user gets and how you verified it.
|
||||
|
||||
If more than one pull request arrives for the same item, we take the best one, and the first equivalent one breaks a
|
||||
tie. Feel free to coordinate on the issue first.
|
||||
|
||||
## Testing
|
||||
|
||||
- **Behavioural scenarios for more command families.** `docs/as-built/test-layers.md` has a "Help wanted" list of
|
||||
`rig` command families that have no scenario yet, each with a suggested first check. One family per pull request is
|
||||
ideal.
|
||||
|
||||
## Agents and runtimes
|
||||
|
||||
- **Codex seats that can reach the local daemon by default** (#275), with an easy opt-out.
|
||||
- **Custom Codex providers such as Bedrock** (#194): accept providers that authenticate with an environment key, and
|
||||
forward their token.
|
||||
- **Operator recovery for Pi and Oh My Pi seats** (#41).
|
||||
- **New agent types:** OpenCode (#87, #227, #517), the Cursor CLI (#352), the Grok CLI (#522) and Devin (#44) are
|
||||
planned on a shared adapter.
|
||||
Design discussion: #43. Adapter pull requests are welcome once that adapter lands.
|
||||
|
||||
## Slack
|
||||
|
||||
- **A DM or private channel for single-user setups** (#313).
|
||||
- **A channel map** (#192): one channel per seat, keyed by channel ID.
|
||||
|
||||
## Reliability and everyday use
|
||||
|
||||
- **A configurable readiness window for fresh launches** (#182), so seats under load don't time out at a fixed 30
|
||||
seconds.
|
||||
- **Draft-aware delivery** (#48): automated sends wait while someone is typing in a seat, or a seat can be set to
|
||||
inbox-only.
|
||||
- **A host-aware resource budget for seats** (#80).
|
||||
- **Predictable `rig up` over a stopped rig with the same name.**
|
||||
- **A "merged" stage in the project view** for teams that integrate on a branch other than main (#402).
|
||||
- **Codex seats that leave a repository's tracked `AGENTS.md` alone** (related: #64).
|
||||
- **Install and run on Node 26.**
|
||||
|
||||
Bugs aren't listed here. Please file them as issues; anything labelled `good first issue` is a good place to start.
|
||||
+5
-1
@@ -9,7 +9,11 @@ Include the affected version even when reporting a problem in an older installat
|
||||
## 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
|
||||
configuration for those harnesses, including workspace trust records and executable hooks: for
|
||||
example `~/.claude.json`, skills under `~/.claude`, `$CODEX_HOME/config.toml` (default `~/.codex`),
|
||||
and, in the project, `.claude/settings.local.json`, `.mcp.json` and managed blocks in `CLAUDE.md`,
|
||||
`CLAUDE.local.md` or `AGENTS.md`. `rig setup` also edits `~/.tmux.conf` and, on macOS, cmux's
|
||||
settings. OpenRig's own state lives under `$OPENRIG_HOME` (default `~/.openrig`). 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.
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
"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.",
|
||||
"description": "Build your own network of agents from Claude Code, Codex and Pi: persistent teams with roles, shared context and owned work.",
|
||||
"folders": [],
|
||||
"excludeFolders": [
|
||||
"docs/releases",
|
||||
@@ -18,7 +18,7 @@
|
||||
],
|
||||
"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.",
|
||||
"Install with npm install -g @openrig/cli. OpenRig requires Node.js 22 or 24, tmux, and Claude Code or Codex signed in. The starter team uses both, and the kernel operator adapts it when you have only one.",
|
||||
"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.",
|
||||
|
||||
@@ -4,4 +4,5 @@ ARG TESTBED_IMAGE
|
||||
FROM ${TESTBED_IMAGE}
|
||||
COPY runner.mjs /opt/openrig-testbed/runner.mjs
|
||||
COPY scenarios/ /opt/openrig-testbed/scenarios/
|
||||
COPY library/ /opt/openrig-testbed/library/
|
||||
CMD ["node", "/opt/openrig-testbed/runner.mjs", "healthy"]
|
||||
@@ -4,6 +4,10 @@ This document captures the current OpenRig visual system so the product can be
|
||||
recreated, extended, or translated into other media without reverse-engineering
|
||||
the UI from code.
|
||||
|
||||
It describes the web UI in `packages/ui`, which is in maintenance mode (see
|
||||
`docs/reference/developing.md`). The terminal UI has its own theme layer in
|
||||
`packages/tui/src/theme.ts`.
|
||||
|
||||
## Essence
|
||||
|
||||
OpenRig is a local control plane for multi-agent work. The interface should feel
|
||||
@@ -82,6 +86,12 @@ terminal emulator.
|
||||
The canonical tokens live in `packages/ui/src/globals.css` and are surfaced to
|
||||
Tailwind in `packages/ui/tailwind.config.ts`.
|
||||
|
||||
The values below are the light set (`:root`). A `.dark` block in the same file
|
||||
redeclares the same names for Vellum Dark. The theme selector offers Vellum
|
||||
Light, Vellum Dark and System (`packages/ui/src/lib/theme.ts`). With no stored
|
||||
choice the UI follows the operating system's color scheme (`system`); the choice
|
||||
is stored under the `openrig.theme` local-storage key.
|
||||
|
||||
Paper:
|
||||
|
||||
- `--background: 47 20% 97%` - base paper, `#faf9f5`
|
||||
@@ -230,6 +240,8 @@ Runtime marks:
|
||||
|
||||
- Claude: pixel-style Claude mark, label `Claude`
|
||||
- Codex: Codex CLI terminal-circle mark, label `Codex`
|
||||
- Pi: blue rounded-square π mark, label `Pi`
|
||||
- Oh My Pi: violet rounded-square π mark, label `Oh My Pi` (short label `OMP`)
|
||||
- Terminal: compact terminal glyph, label `TTY` or `Terminal`
|
||||
- Unknown: neutral fallback
|
||||
|
||||
|
||||
+78
-74
@@ -1,101 +1,105 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: As-Built Docs — Map of Territory + Module Index
|
||||
title: As-Built Docs — Module Index
|
||||
status: active
|
||||
topics: [knowledge-and-context, observability]
|
||||
domains: [engineering-advisor, operating-advisor, product-advisor]
|
||||
applies-when: |
|
||||
Starting any technical task that needs the shipped OpenRig system as it
|
||||
actually is. Read this first to learn what the as-built tree contains and
|
||||
which module to open; then go to codemap.md for use-case navigation or
|
||||
straight to the named module.
|
||||
siblings: [codemap.md, cli-reference.md, frontmatter-schema.md]
|
||||
Starting a technical task and choosing the as-built module or source entry
|
||||
point to read. This index describes the document tree; each module carries
|
||||
its own source-verification stamp.
|
||||
siblings: [codemap.md, arteries.md, test-layers.md, cli-reference.md, frontmatter-schema.md]
|
||||
prerequisite-reads: []
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# OpenRig As-Built Docs
|
||||
# OpenRig as-built docs
|
||||
|
||||
OpenRig is a local control plane for multi-agent coding topologies — a
|
||||
multi-agent harness that manages your Claude Code and Codex sessions as a
|
||||
single system, with a daemon (`@openrig/daemon`), a CLI (`@openrig/cli`), a UI
|
||||
(`@openrig/ui`), and an MCP server all sitting on one SQLite-backed core. This
|
||||
tree is the **source-verified** description of that system as it actually
|
||||
ships — every load-bearing claim is grounded to `packages/*/src` at a named
|
||||
commit, not to memory, chat, or older docs.
|
||||
These pages describe the code and point to its owning files. This index was checked against
|
||||
main commit `e8f0ab340db773392ec8be75b072d1c0f3068a50`. A linked module's
|
||||
`last-verified-against-source` is its own verification boundary; updating this index does not
|
||||
revalidate that module or identify a running daemon or published package.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c` (`git describe` →
|
||||
> `v0.3.1-6-g7eaf524c`). Package version is **0.3.1** across all three
|
||||
> packages; HEAD carries 6 commits of unreleased 0.3.2 work, no `v0.3.2` tag.
|
||||
For the repository overview and contributor recipes, start with [ARCHITECTURE.md](../../ARCHITECTURE.md).
|
||||
Use [codemap.md](codemap.md) to locate source, [arteries.md](arteries.md) to identify dependent
|
||||
paths before a change, and [test-layers.md](test-layers.md) to choose checks.
|
||||
|
||||
## How this tree is organized
|
||||
## Repository surfaces
|
||||
|
||||
The as-built corpus was modularized (slice 08, `context-architecture-v1`) from
|
||||
two monolithic files into a folder of thematic modules, each independently
|
||||
loadable, each frontmatter-tagged for retrieval, each ≤300 lines (author-mode
|
||||
modules with no prior prose may run to ≤400 — see the slice-08 ACK).
|
||||
The npm workspaces are `packages/daemon`, `packages/cli`, `packages/tui`, and `packages/ui`
|
||||
([root package manifest](../../package.json)). The daemon provides the SQLite-backed HTTP
|
||||
services; CLI, TUI and MCP clients consume them. Runtime adapters include Claude Code, Codex,
|
||||
Pi, OMP, terminal and stub implementations under `packages/daemon/src/adapters/`.
|
||||
|
||||
```
|
||||
docs/as-built/
|
||||
├── README.md ← you are here: map-of-territory + index
|
||||
├── codemap.md ← navigation index (use-case lookup, source-root pointers)
|
||||
├── frontmatter-schema.md← the frontmatter convention these docs follow
|
||||
├── cli-reference.md ← full rig CLI surface (kept whole)
|
||||
├── architecture/ ← 14 backend/runtime modules
|
||||
└── ui/ ← 4 operator-surface modules
|
||||
```
|
||||
|
||||
`docs/DESIGN.md` (the canonical visual / brand / design-system spec) **stays
|
||||
at the repo `docs/` root** by design — it is referenced by many existing
|
||||
`docs/DESIGN.md` paths and carries no source drift. This tree points at it
|
||||
(see `ui/library-specs-and-design-system.md`); it is not copied here.
|
||||
The CLI package contains the assembled daemon, TUI and web UI. User reference documents ship
|
||||
from `docs/reference/`; this as-built tree is developer documentation. The assembly is in
|
||||
[scripts/build-package.sh](../../scripts/build-package.sh), with the published file list in
|
||||
[packages/cli/package.json](../../packages/cli/package.json).
|
||||
|
||||
## Module index
|
||||
|
||||
### `architecture/` — backend, daemon, runtime
|
||||
### Daemon and runtime
|
||||
|
||||
| Module | What it covers |
|
||||
| Module | Read for |
|
||||
|---|---|
|
||||
| [daemon-core.md](architecture/daemon-core.md) | How the daemon boots, `createDaemon` wiring, the SQLite schema/40-migration set, the route-mount surface. |
|
||||
| [adapters-and-runtimes.md](architecture/adapters-and-runtimes.md) | The five-method RuntimeAdapter contract, the Claude/Codex/Terminal adapters (launch/resume/fork in tmux), and the resume-honesty layer (honest resumed-vs-fresh assessment). |
|
||||
| [coordination-primitive.md](architecture/coordination-primitive.md) | PL-004 Phase A stream/queue/inbox/outbox; the hot-potato closure contract; where queue closure is enforced. |
|
||||
| [workflow-runtime.md](architecture/workflow-runtime.md) | PL-004 Phase D Workflow Runtime — spec cache, instance state, step trails, transactional-scribe projection, watchdog policies. |
|
||||
| [mission-control.md](architecture/mission-control.md) | PL-005 queue-observability surface — seven views, seven write verbs, action audit, bearer-token middleware. |
|
||||
| [agent-spec-and-startup.md](architecture/agent-spec-and-startup.md) | AgentSpec/RigSpec types, profile resolution, additive startup layering, StartupOrchestrator, whoami/materialize/bind/adopt identity. |
|
||||
| [lifecycle-snapshot-restore.md](architecture/lifecycle-snapshot-restore.md) | Snapshot capture, honest restore (resume vs rebuild vs fresh), restore-honesty enforcement, restore-check / restore-packet probes. |
|
||||
| [transport-and-transcripts.md](architecture/transport-and-transcripts.md) | rig send/capture/broadcast over tmux, pipe-pane transcript capture + search, durable SQLite chat, `rig ask`, MCP-name vs tmux-key distinction. |
|
||||
| [workspace-primitive.md](architecture/workspace-primitive.md) | The PL-007 typed workspace declaration (root/repos/defaultRepo/knowledgeRoot), migrations 038/039, per-item repo-scope gating, file-backed missions/slices indexing. |
|
||||
| [content-surfaces.md](architecture/content-surfaces.md) | The operator-allowlisted file browser, atomic conflict-checked writes + JSONL edit audit, PROGRESS.md tree indexer, the one-screen Steering composer. |
|
||||
| [living-notes-review.md](architecture/living-notes-review.md) | **v0.4.4** — the Living Notes review surface: the ONE `ComposedSliceReview` (intent→plan→delivered) projection, pure composer + gatherer, staged-approval locks, C1-bound `verified`, `/api/review/*` routes, freeze export, ranged media serving, and the SDLC on-disk convention it projects. |
|
||||
| [plugin-agent-image-context-pack.md](architecture/plugin-agent-image-context-pack.md) | The filesystem-canonical content layer — plugin discovery, agent images, context packs, the Claude auto-compaction policy enforcer. |
|
||||
| [packaging-bootstrap-bundles.md](architecture/packaging-bootstrap-bundles.md) | Bundle assembly (schema-v2 pod bundles + legacy v1), bundle create/inspect/install + `/api/up`, the staged BootstrapOrchestrator, legacy install seams. |
|
||||
| [architecture-rules-and-event-system.md](architecture/architecture-rules-and-event-system.md) | The cross-cutting invariants — the 25 architecture rules, the RigEvent union + SSE delivery, intentional compatibility limits. |
|
||||
| [daemon-core.md](architecture/daemon-core.md) | Process startup, service wiring, database migrations and HTTP mounts. |
|
||||
| [adapters-and-runtimes.md](architecture/adapters-and-runtimes.md) | Runtime adapter methods, harness launch/resume and native-session checks. |
|
||||
| [agent-spec-and-startup.md](architecture/agent-spec-and-startup.md) | Spec resolution, resource projection, startup delivery and identity. |
|
||||
| [coordination-primitive.md](architecture/coordination-primitive.md) | Stream, queue, inbox/outbox, handoff and closure. |
|
||||
| [workflow-runtime.md](architecture/workflow-runtime.md) | Workflow specifications, instances, projection, trails and watchdog policies. |
|
||||
| [mission-control.md](architecture/mission-control.md) | Queue observations, actions and their audit records. |
|
||||
| [lifecycle-snapshot-restore.md](architecture/lifecycle-snapshot-restore.md) | Snapshot capture, restore decisions and continuity checks. |
|
||||
| [transport-and-transcripts.md](architecture/transport-and-transcripts.md) | Session messaging, capture, transcripts, chat and ask. |
|
||||
| [workspace-primitive.md](architecture/workspace-primitive.md) | Workspace and repository declarations, project resolution and scope. |
|
||||
| [content-surfaces.md](architecture/content-surfaces.md) | File reads/writes, path checks, progress indexing and steering composition. |
|
||||
| [living-notes-review.md](architecture/living-notes-review.md) | Review gathering/composition, proof records, approvals, export and media. |
|
||||
| [plugin-agent-image-context-pack.md](architecture/plugin-agent-image-context-pack.md) | Plugin discovery, agent images, context packs and compaction policy. |
|
||||
| [packaging-bootstrap-bundles.md](architecture/packaging-bootstrap-bundles.md) | Package assembly, reusable topology bundles and bootstrap/install paths. |
|
||||
| [architecture-rules-and-event-system.md](architecture/architecture-rules-and-event-system.md) | Cross-cutting invariants, events and compatibility notes. |
|
||||
|
||||
### `ui/` — operator surfaces
|
||||
### Navigation, checks and reference
|
||||
|
||||
| Module | What it covers |
|
||||
| Document | Read for |
|
||||
|---|---|
|
||||
| [shell-and-routing.md](ui/shell-and-routing.md) | The UI shell (rail / Explorer / center workspace / drawer / preview stack), the actual route tree the shipped UI mounts, the shared detail drawer + event consumption. |
|
||||
| [topology.md](ui/topology.md) | The topology surface — host hybrid graph, table/terminal views, activity-ring / hot-potato visual language, terminal-preview popovers, navigation/overlay contracts. |
|
||||
| [project-and-for-you.md](ui/project-and-for-you.md) | The operator destination surfaces — the For-You attention feed (5-card classifier + verb actions), the Project workspace/mission/slice scope pages, the Dashboard landing on the vellum brand system. |
|
||||
| [library-specs-and-design-system.md](ui/library-specs-and-design-system.md) | The Library (`/specs`) UI — specs/skills/plugins/agent-images surfaces, the spec-review + spec-library + live-identity flows, the design-system pointer. |
|
||||
| [codemap.md](codemap.md) | Question-to-module navigation and source entry points. |
|
||||
| [arteries.md](arteries.md) | Code paths where changes affect several subsystems. |
|
||||
| [test-layers.md](test-layers.md) | Test scope, isolation, scenarios and hosted CI. |
|
||||
| [cli-reference.md](cli-reference.md) | CLI commands and options; confirm changing details in `packages/cli/src/index.ts` and command help. |
|
||||
| [frontmatter-schema.md](frontmatter-schema.md) | Metadata fields used by these documents. |
|
||||
| [architecture.md](architecture.md) | Compatibility pointer from the former monolithic architecture page. |
|
||||
| [DESIGN.md](../DESIGN.md) | Visual and design-system specification. |
|
||||
|
||||
### Root docs
|
||||
### Web UI reference
|
||||
|
||||
| Doc | What it covers |
|
||||
Each page below carries its own verification stamp. This index checks that the pages exist, not
|
||||
their contents.
|
||||
|
||||
| Document | Topic |
|
||||
|---|---|
|
||||
| [codemap.md](codemap.md) | Navigation index — module map, structural relationship diagram, fast-lookup-by-use-case, source-root pointer table. Start here when you know *what* you need but not *which module*. |
|
||||
| [cli-reference.md](cli-reference.md) | The full `rig` CLI surface — command groups, subcommands, flags, JSON output, cross-host, coordination primitives. Kept as one doc (slice-08 Q4). |
|
||||
| [frontmatter-schema.md](frontmatter-schema.md) | Which frontmatter convention governs every doc in this tree, plus the one as-built-unique field (`last-verified-against-source`). |
|
||||
| `../DESIGN.md` | The canonical visual / brand / design-system spec (lives at `docs/` root by design; pointer only — not duplicated here). |
|
||||
| [ui.md](ui.md) | Compatibility pointer to the UI modules. |
|
||||
| [shell-and-routing.md](ui/shell-and-routing.md) | Web UI shell and routing. |
|
||||
| [topology.md](ui/topology.md) | Web topology views. |
|
||||
| [project-and-for-you.md](ui/project-and-for-you.md) | Project and attention surfaces. |
|
||||
| [library-specs-and-design-system.md](ui/library-specs-and-design-system.md) | Library/spec views and design-system pointers. |
|
||||
|
||||
## Source-grounding contract
|
||||
For the terminal UI, start at `packages/tui/src/main.ts`, its HTTP client
|
||||
`packages/tui/src/daemon-client.ts`, and the TUI sections of [test-layers.md](test-layers.md).
|
||||
|
||||
Every module declares `last-verified-against-source: <commit-sha>` — the
|
||||
commit its claims were checked against. Load-bearing corrections are recorded
|
||||
inline with an auditable annotation (`> Drift-fix Dx — said X; corrected to Y;
|
||||
slice-00 §z; re-confirmed <file:line> @HEAD`). OPEN items (counts that are
|
||||
definitional or runtime-only) are carried verbatim, never smoothed. The
|
||||
schema is defined in [frontmatter-schema.md](frontmatter-schema.md) and the
|
||||
governing convention is `openrig-work/conventions/frontmatter-for-context/`.
|
||||
## Derive the document inventory
|
||||
|
||||
At the named commit there are **26 Markdown files**: **14** under `architecture/`, **4** under
|
||||
`ui/`, and **8** at the root of this tree (including both compatibility pointers). Reproduce
|
||||
these counts from the repository root:
|
||||
|
||||
```bash
|
||||
source_commit=e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
git ls-tree -r --name-only "$source_commit" -- docs/as-built | grep -E '\.md$' | wc -l
|
||||
git ls-tree -r --name-only "$source_commit" -- docs/as-built/architecture | grep -E '\.md$' | wc -l
|
||||
git ls-tree -r --name-only "$source_commit" -- docs/as-built/ui | grep -E '\.md$' | wc -l
|
||||
git ls-tree -r --name-only "$source_commit" -- docs/as-built | grep -E '^docs/as-built/[^/]+\.md$' | wc -l
|
||||
```
|
||||
|
||||
Read a module's source stamp and prerequisite pointers before relying on it. Source code at the
|
||||
named commit is the authority when prose disagrees; [frontmatter-schema.md](frontmatter-schema.md)
|
||||
describes how the stamp is represented.
|
||||
@@ -1,57 +1,48 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: architecture.md — Reorganized into the Modular As-Built Tree (Redirect Stub)
|
||||
title: Architecture — Module Navigation
|
||||
status: superseded
|
||||
topics: [knowledge-and-context]
|
||||
domains: [engineering-advisor, operating-advisor, product-advisor]
|
||||
applies-when: |
|
||||
You followed an old reference to docs/as-built/architecture.md. The monolith
|
||||
was reorganized (slice-08, context-architecture-v1) into a folder of thematic
|
||||
modules. Go to README.md (map of territory) or codemap.md (use-case lookup),
|
||||
then the named module.
|
||||
siblings: [README.md, codemap.md, ui.md]
|
||||
Following an older link to docs/as-built/architecture.md. Use the repository
|
||||
overview or the current as-built module index linked here.
|
||||
siblings: [README.md, codemap.md, arteries.md, test-layers.md, ui.md]
|
||||
prerequisite-reads: []
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# architecture.md was reorganized into the modular as-built tree
|
||||
# Architecture module navigation
|
||||
|
||||
This single-file monolith was split into a folder of independently-loadable,
|
||||
frontmatter-tagged thematic modules (slice-08, `context-architecture-v1`). The
|
||||
canonical as-built description of the shipped daemon/runtime now lives in
|
||||
`architecture/`. This stub is a forwarding pointer so old references still
|
||||
resolve.
|
||||
This compatibility page points to the modular documentation. Its links were checked against main
|
||||
`e8f0ab340db773392ec8be75b072d1c0f3068a50`; the linked pages retain their own source stamps.
|
||||
|
||||
**Start here:**
|
||||
- [Repository architecture](../../ARCHITECTURE.md): package boundaries, request flow, derived
|
||||
source counts and contributor recipes.
|
||||
- [As-built index](README.md): the complete module inventory, including the existing web UI references.
|
||||
- [Codemap](codemap.md): questions mapped to modules and source entry points.
|
||||
- [Arteries](arteries.md): dependencies to inspect before changing shared runtime paths.
|
||||
- [Test layers](test-layers.md): the checks available and what they establish.
|
||||
|
||||
- [`./README.md`](./README.md) — the map of territory and full module index.
|
||||
- [`./codemap.md`](./codemap.md) — navigation index: use-case lookup table and
|
||||
source-root pointers. Use this when you know *what* you need but not *which*
|
||||
module.
|
||||
## Daemon and runtime starting points
|
||||
|
||||
## Where the major content went
|
||||
|
||||
The architecture content is now the **14 modules under `architecture/`**
|
||||
(13 from the slice-08 reorg + `living-notes-review.md`, added v0.4.4):
|
||||
|
||||
| Module | What moved here |
|
||||
| Topic | Module |
|
||||
|---|---|
|
||||
| [`architecture/daemon-core.md`](architecture/daemon-core.md) | Daemon boot, `createDaemon` wiring, the SQLite schema/migration set, the route-mount surface, system overview, package boundaries. |
|
||||
| [`architecture/adapters-and-runtimes.md`](architecture/adapters-and-runtimes.md) | The five-method RuntimeAdapter contract; Claude/Codex/Terminal adapters; the **Resume honesty** layer (§ Resume honesty). |
|
||||
| [`architecture/coordination-primitive.md`](architecture/coordination-primitive.md) | PL-004 Phase A stream/queue/inbox/outbox; the hot-potato closure contract; durable handoff. |
|
||||
| [`architecture/workflow-runtime.md`](architecture/workflow-runtime.md) | PL-004 Phase D Workflow Runtime — spec cache, instance state, step trails, transactional-scribe, watchdog policies. |
|
||||
| [`architecture/mission-control.md`](architecture/mission-control.md) | PL-005 Mission Control / Queue Observability — the `/api/mission-control/*` surface, seven views, seven write verbs, action audit, bearer-token middleware. |
|
||||
| [`architecture/agent-spec-and-startup.md`](architecture/agent-spec-and-startup.md) | AgentSpec/RigSpec types, profile resolution, additive startup layering, StartupOrchestrator, whoami/materialize/bind/adopt. |
|
||||
| [`architecture/lifecycle-snapshot-restore.md`](architecture/lifecycle-snapshot-restore.md) | Snapshot capture, honest restore (resume vs rebuild vs fresh), the verbatim restore-honesty rules, restore-check / restore-packet probes. |
|
||||
| [`architecture/transport-and-transcripts.md`](architecture/transport-and-transcripts.md) | rig send/capture/broadcast over tmux, transcript capture + search, durable SQLite chat, `rig ask`, MCP-name vs tmux-key. |
|
||||
| [`architecture/workspace-primitive.md`](architecture/workspace-primitive.md) | The PL-007 typed workspace declaration, migrations 038/039, per-item repo-scope gating, file-backed missions/slices indexing. |
|
||||
| [`architecture/content-surfaces.md`](architecture/content-surfaces.md) | The operator-allowlisted file browser, atomic conflict-checked writes + edit audit, the PROGRESS.md tree indexer, the Steering composer. |
|
||||
| [`architecture/plugin-agent-image-context-pack.md`](architecture/plugin-agent-image-context-pack.md) | Plugin discovery, agent images, context packs, the Claude auto-compaction enforcer. |
|
||||
| [`architecture/packaging-bootstrap-bundles.md`](architecture/packaging-bootstrap-bundles.md) | Bundle assembly (schema-v2 + legacy v1), bundle create/inspect/install + `/api/up`, the staged BootstrapOrchestrator, legacy install seams. |
|
||||
| [`architecture/architecture-rules-and-event-system.md`](architecture/architecture-rules-and-event-system.md) | The cross-cutting invariants — the 25 architecture rules (incl. **rule 15**, restore honesty), the RigEvent union + SSE delivery, intentional compatibility limits. |
|
||||
| [`architecture/living-notes-review.md`](architecture/living-notes-review.md) | **Added v0.4.4** (not part of the original monolith): the Living Notes review surface — the one intent→plan→delivered projection, staged-approval locks, proof artifacts, freeze export, ranged media serving. |
|
||||
| Boot, HTTP services and database wiring | [Daemon core](architecture/daemon-core.md) |
|
||||
| Harness adapters and native session continuity | [Adapters and runtimes](architecture/adapters-and-runtimes.md) |
|
||||
| Spec resolution, projection and launch | [Agent spec and startup](architecture/agent-spec-and-startup.md) |
|
||||
| Durable queue and messaging state | [Coordination](architecture/coordination-primitive.md) |
|
||||
| Workflow execution and watchdog policies | [Workflow runtime](architecture/workflow-runtime.md) |
|
||||
| Queue observations and operator actions | [Mission control](architecture/mission-control.md) |
|
||||
| Snapshot and restore | [Lifecycle](architecture/lifecycle-snapshot-restore.md) |
|
||||
| Messaging, capture and transcripts | [Transport and transcripts](architecture/transport-and-transcripts.md) |
|
||||
| Project and repository scope | [Workspace](architecture/workspace-primitive.md) |
|
||||
| Files, progress and steering | [Content surfaces](architecture/content-surfaces.md) |
|
||||
| Review, proof and approvals | [Living notes and review](architecture/living-notes-review.md) |
|
||||
| Plugin discovery, images and context | [Plugins, agent images and context packs](architecture/plugin-agent-image-context-pack.md) |
|
||||
| Distribution, bundles and bootstrap | [Packaging and bundles](architecture/packaging-bootstrap-bundles.md) |
|
||||
| Events and cross-cutting invariants | [Architecture rules and events](architecture/architecture-rules-and-event-system.md) |
|
||||
|
||||
The UI half of the old `### UI architecture` section is now the **4 modules
|
||||
under `ui/`** (see [`ui.md`](ui.md), also a redirect stub, and the `ui/` index
|
||||
in [`./README.md`](./README.md)).
|
||||
Older web UI links continue through [ui.md](ui.md). For terminal UI source, use the
|
||||
[package entry points in the codemap](codemap.md#package-entry-points).
|
||||
@@ -1,228 +1,423 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: Adapters and Runtimes — Claude/Codex/Terminal, tmux/cmux, Resume Honesty
|
||||
title: Adapters and Runtimes — Claude/Codex/Pi/Stub/Terminal, tmux/cmux, Resume Honesty
|
||||
status: active
|
||||
topics: [agent-runtime, runtime-control]
|
||||
domains: [engineering-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
Need the runtime-adapter contract — how OpenRig launches and resumes a
|
||||
Claude Code, Codex, or terminal harness inside tmux, what the five adapter
|
||||
methods do, or how the daemon honestly assesses whether a harness actually
|
||||
resumed vs fresh-launched (the resume-honesty layer).
|
||||
Claude Code, Codex, Pi, Oh My Pi, stub or terminal harness inside tmux, what
|
||||
the five required adapter methods do, or how the daemon honestly assesses
|
||||
whether a harness actually resumed vs fresh-launched (the resume-honesty
|
||||
layer).
|
||||
siblings: [daemon-core.md, agent-spec-and-startup.md, lifecycle-snapshot-restore.md]
|
||||
prerequisite-reads: [../README.md, daemon-core.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Adapters and Runtimes
|
||||
|
||||
How OpenRig drives the agent harnesses. The daemon never talks to Claude Code,
|
||||
Codex, or a shell directly — it talks to a `RuntimeAdapter`. Three adapters
|
||||
implement one five-method contract; a separate resume-honesty layer answers the
|
||||
question "did this harness *actually* resume, or did it silently fresh-launch?"
|
||||
truthfully rather than optimistically.
|
||||
How OpenRig drives the agent harnesses. Pod-aware launches go through a
|
||||
`RuntimeAdapter`: five adapter classes implement one contract of five required
|
||||
methods. Legacy (non-pod-aware) restore resumes through separate resume
|
||||
adapters (`ClaudeResumeAdapter`, `CodexResumeAdapter`, `PiResumeAdapter` and
|
||||
`OmpResumeAdapter` in `packages/daemon/src/adapters/*-resume.ts`), which are not
|
||||
`RuntimeAdapter`s. A separate resume-honesty layer answers the question "did
|
||||
this harness *actually* resume, or did it silently fresh-launch?" truthfully
|
||||
rather than optimistically.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c` (`git describe` →
|
||||
> `v0.3.1-6-g7eaf524c`). All source in this module is **present at `v0.3.0`**
|
||||
> (`git cat-file -e v0.3.0:<path>` for all 7 files → present) — the adapter
|
||||
> layer is core reboot-era machinery, **not** a 0.3.x feature; do not
|
||||
> back-attribute (§10.8 version-attribution proof technique).
|
||||
|
||||
> Drift-fix (scope) — `architecture.md` §5 "Runtime adapters" carries **no
|
||||
> slice-00 numeric drift** (proposed-structure §4.2: "adapter contract is
|
||||
> current"). The corrections below are *precision* refinements where the source
|
||||
> is more specific than the prose, not stale-count fixes. Each is annotated
|
||||
> inline and re-confirmed at HEAD.
|
||||
> Verified against source at main `e8f0ab340db773392ec8be75b072d1c0f3068a50`. Each count below sits beside the
|
||||
> command that produces it; run the command from the repository root to refresh
|
||||
> it.
|
||||
|
||||
## 1. The five-method RuntimeAdapter contract
|
||||
|
||||
`RuntimeAdapter` is `packages/daemon/src/domain/runtime-adapter.ts:127`
|
||||
`RuntimeAdapter` is `packages/daemon/src/domain/runtime-adapter.ts:143`
|
||||
(`interface RuntimeAdapter`). Every adapter declares a `readonly runtime`
|
||||
string and implements exactly five methods (`runtime-adapter.ts:128–153`):
|
||||
string (`runtime-adapter.ts:146`) and implements five required methods
|
||||
(`runtime-adapter.ts:152–176`; **5** =
|
||||
`sed -n '/^export interface RuntimeAdapter/,/^}/p' packages/daemon/src/domain/runtime-adapter.ts | grep -c -E '^ [a-zA-Z]+\('`):
|
||||
|
||||
| Method | Signature (`runtime-adapter.ts`) | Responsibility |
|
||||
|---|---|---|
|
||||
| `listInstalled` | `(binding)` `:131` | List currently installed/projected resources for a node. |
|
||||
| `project` | `(plan, binding)` `:134` | Project resources from a `ProjectionPlan` to the runtime's target locations. |
|
||||
| `deliverStartup` | `(files, binding)` `:137` | Deliver resolved startup files to the runtime. |
|
||||
| `launchHarness` | `(binding, opts)` `:147` | Launch the harness inside the bound tmux session; return a resume token. |
|
||||
| `checkReady` | `(binding)` `:153` | Probe whether the harness is responsive and ready. |
|
||||
| `listInstalled` | `(binding)` `:153` | List currently installed/projected resources for a node. |
|
||||
| `project` | `(plan, binding)` `:156` | Project resources from a `ProjectionPlan` to the runtime's target locations. |
|
||||
| `deliverStartup` | `(files, binding, sendInteractiveText?)` `:160` | Deliver resolved startup files to the runtime. The startup orchestrator passes the optional third argument for Claude Code only, so Claude's `send_text` files go through the checked send described in `agent-spec-and-startup.md`. |
|
||||
| `launchHarness` | `(binding, opts)` `:170` | Launch the harness inside the bound tmux session; return a resume token. |
|
||||
| `checkReady` | `(binding)` `:176` | Probe whether the harness is responsive and ready. |
|
||||
|
||||
The interface also has two optional members: `claudeManagedLaunch` (`:145`)
|
||||
and `skillTargetPath?()` (`:150`), implemented by `PiRuntimeAdapter`
|
||||
(`pi-runtime-adapter.ts:118`) and overridden by `OmpRuntimeAdapter`
|
||||
(`omp-runtime-adapter.ts:12`).
|
||||
|
||||
Startup *action* execution (`slash_command` / `send_text`) is explicitly **not**
|
||||
part of this contract — the contract docstring (`runtime-adapter.ts:121–125`)
|
||||
part of this contract — the contract docstring (`runtime-adapter.ts:137–142`)
|
||||
states actions belong to the `StartupOrchestrator` *after* `checkReady()`. The
|
||||
orchestrator delivery split is in `agent-spec-and-startup.md`.
|
||||
|
||||
### `launchHarness` opts and the fork seam
|
||||
|
||||
`launchHarness` opts is `{ name: string; resumeToken?: string; forkSource?:
|
||||
ForkSource }` (`runtime-adapter.ts:147–150`).
|
||||
ForkSource }` (`runtime-adapter.ts:172`).
|
||||
|
||||
> Drift-fix (precision) — `architecture.md` §4 said `launchHarness(binding,
|
||||
> opts: { name, resumeToken? })`. Source adds a third, mutually-exclusive
|
||||
> `forkSource` field. Per the contract docstring (`runtime-adapter.ts:142–146`)
|
||||
> `resumeToken` and `forkSource` are mutually exclusive — if both are provided
|
||||
> the adapter **must refuse** with a clear error, not guess; `forkSource`
|
||||
> triggers a fork and the captured token is the NEW post-fork token, never the
|
||||
> parent. `ForkSource` is `runtime-adapter.ts:116` (`kind: "native_id" |
|
||||
> "artifact_path" | "name" | "last"`; v1 MVP accepts `native_id` only — other
|
||||
> shapes rejected at schema validation, docstring `:106–119`).
|
||||
Per the contract docstring (`runtime-adapter.ts:162–169`) `resumeToken` and
|
||||
`forkSource` are mutually exclusive — if both are provided the adapter **must
|
||||
refuse** with a clear error, not guess; `forkSource` triggers a fork and the
|
||||
captured token is the NEW post-fork token, never the parent. `ForkSource` is
|
||||
`runtime-adapter.ts:132` (`kind: "native_id" | "artifact_path" | "name" |
|
||||
"last"`, `:133`; v1 MVP accepts `native_id` only — other shapes rejected at
|
||||
schema validation, docstring `:122–131`, and by the Claude, Codex and Pi
|
||||
adapters themselves).
|
||||
|
||||
### `HarnessLaunchResult` is a discriminated union with an honest failure arm
|
||||
|
||||
> Drift-fix (precision) — `architecture.md` §4 said `HarnessLaunchResult` is
|
||||
> `{ ok, resumeToken?, resumeType?, error? }` (a single optional-field shape).
|
||||
> Source is a **discriminated union** (`runtime-adapter.ts:81–86`):
|
||||
> `| { ok: true; resumeToken?; resumeType? }`
|
||||
> `| { ok: false; error: string; recovery?: HarnessLaunchRecovery; evidence? }`.
|
||||
> The failure arm carries a typed `recovery` hint
|
||||
> (`HarnessLaunchRecovery = "retry_fresh" | "attention_required"`,
|
||||
> `:79`) and optional `evidence` (last-N pane lines, flowed through to
|
||||
> `RestoreNodeResult.attentionEvidence` for `attention_required` outcomes,
|
||||
> `:83–86`). This is the honest-failure shape, not a smoothed optional `error`.
|
||||
`HarnessLaunchResult` is a **discriminated union** (`runtime-adapter.ts:97–102`):
|
||||
`| { ok: true; resumeToken?; resumeType?; appliedLaunch? }`
|
||||
`| { ok: false; error: string; recovery?: HarnessLaunchRecovery; evidence? }`.
|
||||
The failure arm carries a typed `recovery` hint
|
||||
(`HarnessLaunchRecovery = "retry_fresh" | "attention_required"`, `:95`) and
|
||||
optional `evidence` (last-N pane lines, flowed through to
|
||||
`RestoreNodeResult.attentionEvidence` for `attention_required` outcomes,
|
||||
`:99–102`). This is the honest-failure shape, not a smoothed optional `error`.
|
||||
|
||||
## 2. The three adapters
|
||||
Readiness has its own attention codes (`runtime-adapter.ts:74–87`):
|
||||
`trust_gate`, `hook_trust_gate`, `update_gate`, `login_required`, `mcp_gate`,
|
||||
`bypass_consent_gate`, `codex_auth_refusal` and `codex_client_incompatible`.
|
||||
When `checkReady` reports one, the readiness wait returns at once and startup
|
||||
ends `attention_required`.
|
||||
|
||||
All three live under `packages/daemon/src/adapters/` and implement
|
||||
`RuntimeAdapter` (Architecture Rule 1: zero Hono in `adapters/`).
|
||||
## 2. The runtime adapters
|
||||
|
||||
### ClaudeCodeAdapter (`claude-code-adapter.ts:41`)
|
||||
**5** classes implement `RuntimeAdapter`
|
||||
(`git grep -l 'implements RuntimeAdapter' packages/daemon/src | wc -l`):
|
||||
`ClaudeCodeAdapter`, `CodexRuntimeAdapter`, `PiRuntimeAdapter`,
|
||||
`StubRuntimeAdapter` and `TerminalAdapter`. All live under
|
||||
`packages/daemon/src/adapters/` (Architecture Rule 1: **0** files there import
|
||||
Hono, `git grep -l 'from "hono' -- packages/daemon/src/adapters | wc -l`).
|
||||
`OmpRuntimeAdapter` (Oh My Pi) extends `PiRuntimeAdapter`, so the daemon wires
|
||||
**6** runtime keys — `claude-code`, `codex`, `pi`, `omp`, `stub`, `terminal`
|
||||
(`startup.ts:989`, and the same map at `:1259`;
|
||||
`grep 'adapters: {' packages/daemon/src/startup.ts | grep -o -E '"[a-z-]+": ' | wc -l`).
|
||||
|
||||
- `readonly runtime = "claude-code"` (`:42`).
|
||||
- **Projects** to `.claude/` targets: `guidance_merge` → `<cwd>/CLAUDE.md`
|
||||
(`:127`); `skill_install` → `<cwd>/.claude/skills/<name>/` (`:71,133`);
|
||||
subagents → `.claude/agents`, plugins → `.claude/plugins/<id>`,
|
||||
runtime resources → `.claude/extensions/<id>`, settings fragments merged
|
||||
into `.claude/settings.local.json` (`:388–400`).
|
||||
- **Launches** (`:213–215`): fresh = `claude <permissionMode> --session-id
|
||||
<generatedId> --name <name>`; resume = `claude <permissionMode> --resume
|
||||
<token> --name <name>`; fork = `claude <permissionMode> --resume <parentId>
|
||||
--fork-session --name <seat>` (`:188`).
|
||||
> Drift-fix (precision) — `architecture.md` §5 said only "launches via
|
||||
> `claude --name <name>`, resumes via `claude --resume <token>`". Source
|
||||
> shows fresh launch uses an explicit `--session-id` (so a deterministic
|
||||
> resume token exists immediately) and the fork branch exists. Re-confirmed
|
||||
> `claude-code-adapter.ts:188,213–215` @HEAD.
|
||||
- **Readiness** (`checkReady`, `:239`): verifies tmux session alive, captures
|
||||
40 pane lines + pane command, delegates to `assessNativeResumeProbe` (§3);
|
||||
ready only when probe `status === "resumed"`. The resume-launch verification
|
||||
loop `verifyResumeLaunch` retries up to **16 attempts** (`:273`), failing
|
||||
loudly with `recovery: "retry_fresh"` on `no_conversation_found`
|
||||
(`:284–289`) — no silent fresh fallback.
|
||||
### Launch posture
|
||||
|
||||
### CodexRuntimeAdapter (`codex-runtime-adapter.ts:37`)
|
||||
A seat's launch posture is `floor`, `auto` or `full_bypass`. It comes from the
|
||||
member's or rig's `permission_policy`: `builtinLaunchPosture()`
|
||||
(`packages/daemon/src/domain/permission-policy/policy-ref.ts:165`) maps
|
||||
`builtin:yolo` to `full_bypass`, `builtin:auto` to `auto`, and every other
|
||||
built-in (`locked`, `standard`, `open`) to `floor`.
|
||||
`resolvePermissionPolicyAttachment()` (`:172`) resolves the attachment, and
|
||||
`rigspec-instantiator.ts` binds the result: the member attachment
|
||||
(`resolveMemberPolicyAttachment`, `:1922`), then the persisted node or rig
|
||||
attachment (`:2007`), then the floor, set on the binding as `launchPosture`
|
||||
(`:2159`). `NativePermissionStore.apply()`
|
||||
(`native-permission-store.ts:238`) then overlays a seat's explicit
|
||||
`rig seat set-permissions` choice, or the kernel operational or team default
|
||||
described below. `bundle-behaviour.ts` is the read-only
|
||||
preview of the same mapping for an unopened bundle (see
|
||||
`packaging-bootstrap-bundles.md`).
|
||||
|
||||
- `readonly runtime = "codex"` (`:38`).
|
||||
`packages/daemon/src/adapters/yolo-mode.ts` turns the posture into flags:
|
||||
|
||||
- **Claude Code** (`claudePostureFlag`): an explicit `permissionMode` becomes
|
||||
`--permission-mode <mode>` (`:43`); `auto` becomes
|
||||
`--permission-mode auto` (`:47`); `full_bypass` becomes
|
||||
`--dangerously-skip-permissions`; otherwise the floor is
|
||||
`--permission-mode acceptEdits` (`:50`). For a seat with no resolved
|
||||
posture, the instance-wide YOLO setting `OPENRIG_YOLO=1` (`:27`; off by
|
||||
default) selects the bypass; a resolved posture always wins (`:26`).
|
||||
- **Codex** (`codexPostureArg`, `:62`): `full_bypass` becomes
|
||||
`-s danger-full-access -a never` (`:67`); for a seat with no resolved
|
||||
posture, `OPENRIG_YOLO=1` becomes `-s danger-full-access`; otherwise the
|
||||
seat's `-p <profile>` or `-s workspace-write`. Codex with `auto` gets the
|
||||
same: its selected profile, or the `-s workspace-write` floor without one.
|
||||
|
||||
**Non-interruptive mode** (`nonInterruptiveArgs()` in
|
||||
`packages/daemon/src/adapters/non-interruptive.ts:12`) adds per-launch
|
||||
arguments only for a `full_bypass` seat whose rig has it on
|
||||
(`rigs.non_interruptive`): Claude Code gets
|
||||
`--settings '{"skipDangerousModePermissionPrompt":true}'` (unless an explicit
|
||||
non-bypass `permissionMode` is set), and Codex gets
|
||||
`-c notice.hide_full_access_warning=true -c notice.hide_gpt5_1_migration_prompt=true`.
|
||||
It never changes permissions or native settings files. `rig up
|
||||
--non-interruptive` and `--no-non-interruptive` save the choice; otherwise the
|
||||
rig spec's `non_interruptive`, then the setting `launch.non_interruptive`,
|
||||
supplies the default (`packages/daemon/src/domain/bootstrap-orchestrator.ts:739`).
|
||||
|
||||
**Kernel operational default.** `NativePermissionStore.launchOverride()`
|
||||
(`native-permission-store.ts:47`) gives a Claude Code or Codex seat in the
|
||||
persisted rig named `kernel` operational launch arguments when the seat has no
|
||||
explicit permission choice, its member and rig declare no permission policy,
|
||||
and (for Codex) it has no `-p` profile (`launchDefault`, `:28`). Fresh start,
|
||||
continue, restore and handover all take this decision per launch.
|
||||
`operationalLaunchArgs()` (`packages/daemon/src/adapters/kernel-authority.ts:56`)
|
||||
then replaces the non-interruptive arguments for that seat:
|
||||
|
||||
- Claude Code keeps the floor, `--permission-mode acceptEdits`, and gets
|
||||
`--settings` whose `permissions.allow` is `KERNEL_CLAUDE_ALLOW`
|
||||
(`:9`): `Skill`, `Read`, `Edit`, `Write`, `Glob`, `Grep`, `WebFetch`, `Bash(<command>:*)`
|
||||
for 39 operational commands (`rig`, `tmux`, `node`, `npm`, `git`, `ssh`, `python3` and
|
||||
others), `Bash(command -v:*)` (`:16`), two read-only provider login checks (`Bash(claude auth status:*)`,
|
||||
`Bash(codex login status:*)`, `:18`), and `Read(~/**)` (`:19`), plus the `PreToolUse`
|
||||
hook described under the team default. It is not
|
||||
the bypass flag, and the user's own ask and deny rules still apply.
|
||||
- Codex gets the `full_bypass` posture, `-s danger-full-access -a never`, plus
|
||||
the two notice flags above.
|
||||
|
||||
It writes no settings file. If the rig lookup fails, the seat keeps its floor.
|
||||
`rig seat status` reports the source as "kernel operational default"
|
||||
(`kernel_default`, `native-permission-store.ts:182`).
|
||||
|
||||
**Team default.** Under the same conditions, a Claude Code or Codex seat in any
|
||||
other rig gets `teamPermissionDefault` instead (`native-permission-store.ts:38`,
|
||||
`:56`), on the same launch paths. A Claude Code seat at the floor with no native
|
||||
permission mode keeps `--permission-mode acceptEdits` and gets
|
||||
`--settings` (`kernel-authority.ts:64–66`):
|
||||
`TEAM_CLAUDE_ALLOW` (`:25`) is `Skill`, `Read(./**)`, `Glob`, `Grep`,
|
||||
`Bash(rig:*)`, eight read-only shell commands and 17 test-runner commands, and
|
||||
`TEAM_CLAUDE_ASK` (`:32`) asks before 41 `rig` lifecycle commands such as `up`,
|
||||
`down`, `seat stop` and `bundle install`. The settings carry the allow list and a
|
||||
`PreToolUse` Bash hook, `packages/daemon/assets/claude-team-permissions.cjs`, that applies
|
||||
both lists (`kernel-authority.ts:43–53`): it asks before a lifecycle command unless the
|
||||
command only asks for `--help` or `-h`, allows a simple literal command that matches an
|
||||
allow rule, and otherwise leaves the decision to Claude (`claude-team-permissions.cjs:234`,
|
||||
`:240`). Without the hook file, the settings carry both lists as plain `allow` and `ask`
|
||||
rules (`kernel-authority.ts:46`). A Codex seat launching at
|
||||
`-s workspace-write` gets `--add-dir` for the workspace root (`workspace.root`)
|
||||
and its pod's state directory, each created first and skipped with a warning if
|
||||
it is the home directory or an ancestor of it
|
||||
(`packages/daemon/src/domain/codex-team-workspace.ts:18`), on fresh, fork and
|
||||
resume launches (`codex-runtime-adapter.ts:378`, `codex-resume.ts:103`).
|
||||
|
||||
### ClaudeCodeAdapter (`claude-code-adapter.ts:55`)
|
||||
|
||||
- `readonly runtime = "claude-code"` (`:56`).
|
||||
- **Projects** to `.claude/` targets: guidance managed blocks →
|
||||
`<cwd>/CLAUDE.md` by default (`:194`, `:501`; default at
|
||||
`domain/managed-blocks.ts:17`, and the rig may select `CLAUDE.local.md`
|
||||
instead with `managed_blocks: { claude-code: <file> }`,
|
||||
`managed-blocks.ts:15`); `skill_install` → `<cwd>/.claude/skills/<name>/`
|
||||
(`:200`, `:582`); subagents → `.claude/agents`, plugins →
|
||||
`.claude/plugins/<id>` (only plugins that apply to Claude), runtime
|
||||
resources → `.claude/extensions/<id>` (`:584–586`); settings fragments
|
||||
merged into `.claude/settings.local.json` (`:594`) and MCP fragments into
|
||||
`<cwd>/.mcp.json` (`:597`).
|
||||
- **Launches**: fresh = `claude <posture> --session-id <generatedId> --name
|
||||
<name>` (`:329`); resume = `claude <posture> --resume <token> --name <name>`
|
||||
(`:328`); fork = `claude <posture> --resume <parentId> --fork-session
|
||||
--session-id <generatedId> --name <seat>`, which needs Claude Code 2.0.73 or
|
||||
later. `<posture>` is the flag above plus any non-interruptive,
|
||||
kernel operational or team default argument. Each command may carry a `CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1`
|
||||
env prefix (`yolo-mode.ts:101–103`; on by default, off with
|
||||
`OPENRIG_CLAUDE_DISABLE_ALTERNATE_SCREEN=0`), a `--model <model>` argument
|
||||
(`:275`) and `--effort <effort>` (`:277`). A fresh launch and a fork both
|
||||
return the generated `--session-id` as their `claude_id` token, so a forked
|
||||
seat's process names its own conversation and strict proof can verify it.
|
||||
Neither reads Claude's session files back.
|
||||
- **Managed launch.** When the binding selects a native permission mode, the
|
||||
command comes from `ClaudeManagedLaunch.prepare()`
|
||||
(`domain/claude-managed-launch.ts:97`), which resolves `claude` on the
|
||||
managed `PATH`, validates the mode against `claude --help` (5-second limit),
|
||||
and launches through `/usr/bin/env -i` with only the seat's environment
|
||||
(`PATH`, `HOME`, an optional `CLAUDE_CONFIG_DIR`, the OpenRig seat identity
|
||||
and forwarded keys).
|
||||
- **Managed bootstrap.** Before delivery the adapter marks the seat's working
|
||||
directory as trusted and onboarding as completed in Claude's state file,
|
||||
writing only when a flag is missing (`provisionManagedBootstrap`, `:697`).
|
||||
It writes no permission settings. The Codex adapter does the same for
|
||||
project trust in its `config.toml` (`codex-runtime-adapter.ts:736`).
|
||||
- **Readiness** (`checkReady`, `:356`): verifies tmux session alive (`:361`),
|
||||
captures the pane command and 40 pane lines, and assesses them with
|
||||
`assessNativeResumeProbe` (§3) through `assessManagedProbe` (called `:368`,
|
||||
defined `:386`); ready only when probe `status === "resumed"`. The
|
||||
resume-launch verification loop `verifyResumeLaunch` (`:420`) retries up to
|
||||
**16 attempts** (`:422`), failing loudly with `recovery: "retry_fresh"` on
|
||||
`no_conversation_found` (`:429–434`) and with `recovery:
|
||||
"attention_required"` plus pane evidence on an `attention_required` probe
|
||||
(`:444–450`) — the adapter never relaunches on its own.
|
||||
|
||||
### CodexRuntimeAdapter (`codex-runtime-adapter.ts:65`)
|
||||
|
||||
- `readonly runtime = "codex"` (`:66`).
|
||||
- **Projects** to `.agents/` targets: `guidance_merge` → `<cwd>/AGENTS.md`
|
||||
(`:139,330`); `skill_install` → `<cwd>/.agents/skills/<name>/` (`:89,145`);
|
||||
skills resolve under `.agents/skills/<id>` (`:377`).
|
||||
- **Launches/resumes** (`:205,225–226`): fresh launch then capture a fresh
|
||||
thread id; resume = `codex<profileArg> resume<queueStateDirArg> <token>`;
|
||||
fork = `codex<profileArg> fork<queueStateDirArg> <parentId>` (`:205`). On
|
||||
success returns `{ ok: true, resumeToken: threadId, resumeType: "codex_id" }`
|
||||
(`:222,245,250`).
|
||||
> Drift-fix (precision) — `architecture.md` §5 said "launches via `codex`,
|
||||
> resumes via `codex resume <threadId>`". Source confirms the `codex resume
|
||||
> <token>` shape (`:226`) and adds the fork branch + the
|
||||
> `resumeType: "codex_id"` tag. Re-confirmed @HEAD.
|
||||
(`:309`, `:576`); `skill_install` → `<cwd>/.agents/skills/<name>/` (`:315`);
|
||||
skills resolve under `.agents/skills/<id>` (`:664`); plugins go to
|
||||
`.codex/plugins/<id>` (`:667`).
|
||||
- **Launches/resumes**: fresh = `codex[ --no-daemon]<posture>[<network>] -C
|
||||
<cwd> …` (`:442`), then capture a fresh thread id (`:460`); resume is built
|
||||
by `buildCodexResumeCore` (`:441`) as `codex<daemon><posture> resume
|
||||
[<queueStateDirArg>] <token>` (`native-resume-probe.ts:81`); fork =
|
||||
`codex<posture> fork<queueStateDirArg> <parentId>` (`:411`). `--no-daemon`
|
||||
(`:389`) comes first, and only when `codex --help` lists it
|
||||
(`domain/codex-daemon-support.ts`; an unreadable answer refuses the launch).
|
||||
`-m <model>` (`:356`) and `-c 'model_reasoning_effort="<effort>"'`
|
||||
(`:358`) are added when selected. With a thread id it returns `{ ok: true,
|
||||
resumeToken: threadId, resumeType: "codex_id" }` (`:428`, `:457`, `:463`); a
|
||||
fresh launch whose thread id is not captured returns `{ ok: true }` with its
|
||||
applied launch but no token (`:466`). Every Codex launch, fresh, fork or resume,
|
||||
also carries `-c check_for_update_on_startup=false`, so a managed seat leaves Codex
|
||||
upgrades to the operator (`kernel-authority.ts:60`, added to the posture at
|
||||
`codex-runtime-adapter.ts:363`).
|
||||
- **Network default.** On the plain `-s workspace-write` floor,
|
||||
`domain/codex-network-default.ts` asks a one-shot `codex app-server` for the
|
||||
effective configuration; when it allows it, the launch adds
|
||||
`-c sandbox_workspace_write.network_access=true` (`:19`). Otherwise it adds
|
||||
nothing.
|
||||
- Refuses `resumeToken` + `forkSource` together with a clear error
|
||||
(`:180–181`) — honors the mutual-exclusivity contract.
|
||||
(`:350–351`) — honors the mutual-exclusivity contract.
|
||||
|
||||
### TerminalAdapter (`terminal-adapter.ts:19`)
|
||||
|
||||
- `readonly runtime = "terminal"` (`:20`).
|
||||
- **All operations are no-ops** — "the shell IS the harness"
|
||||
(`terminal-adapter.ts:15`): no-op `project`/`deliverStartup`/`launchHarness`
|
||||
(`:34`), and `checkReady` returns ready immediately as soon as the tmux
|
||||
session exists (`:47`). Used for infrastructure nodes — servers, log tails,
|
||||
build watchers. (A terminal node cannot fork; the runtime-adapter docstring
|
||||
notes fork-unsupported adapters refuse with a runtime-mismatch error,
|
||||
`runtime-adapter.ts:113–115`.)
|
||||
- **All operations are no-ops** — "the shell is immediately interactive"
|
||||
(`terminal-adapter.ts:15`): no-op `project`/`deliverStartup`, a
|
||||
`launchHarness` (`:34`) that returns `{ ok: true }`, and a `checkReady` that
|
||||
returns ready unconditionally (`:47–48`). Used for infrastructure nodes. A
|
||||
terminal node cannot fork: `launchHarness` refuses a `forkSource` with a
|
||||
clear error (`:38–42`), as the runtime-adapter docstring requires of
|
||||
adapters without fork (`runtime-adapter.ts:129–130`).
|
||||
|
||||
These three are constructed by `createDaemon` step 4 (`startup.ts`; see
|
||||
`daemon-core.md` §4 "Startup sequence").
|
||||
### Pi, Oh My Pi and stub adapters
|
||||
|
||||
- `PiRuntimeAdapter` (`pi-runtime-adapter.ts:64`), `runtime` `"pi"` (`:65`).
|
||||
- `OmpRuntimeAdapter` (`omp-runtime-adapter.ts:7`) extends it with
|
||||
`runtime = "omp"` (`:8`).
|
||||
- `StubRuntimeAdapter` (`stub-runtime-adapter.ts:69`), `runtime` defaulting to
|
||||
`"stub"` (`:81`).
|
||||
|
||||
Pi and stub launch an OpenRig runner inside the seat's tmux pane (header
|
||||
comments `pi-runtime-adapter.ts:1–10`, `stub-runtime-adapter.ts:1–14`). Pi
|
||||
and Oh My Pi support fork and report `pi_session_file` or `omp_session_file`
|
||||
resume tokens; the stub refuses fork and reports `stub_session`.
|
||||
|
||||
`createDaemon` constructs the adapters (`startup.ts:778`, `:779`, `:782`,
|
||||
`:783`, `:788`) and creates the terminal adapter inline in the runtime adapter
|
||||
maps (`startup.ts:989`, `:1259`); see `daemon-core.md` §4 "Startup sequence".
|
||||
|
||||
### Terminal providers
|
||||
|
||||
Opening a seat in a terminal app goes through `TerminalProvider`s under
|
||||
`packages/daemon/src/domain/terminal/`, served by `/api/terminal`:
|
||||
`HerdrAdapter` (`herdr-adapter.ts:351`), the default
|
||||
(`terminal-service.ts:55–58`), and `CmuxProviderAdapter`
|
||||
(`cmux-provider-adapter.ts:52`), which is best effort and refuses with
|
||||
`cmux_unavailable` when cmux is not connected. A request that names no
|
||||
provider takes the first of Herdr, then cmux, that is available and alive,
|
||||
and stays on Herdr when neither is (`providerNameFor`,
|
||||
`terminal-service.ts:175`); a named provider is used as named. These place existing tmux
|
||||
sessions in views; they do not launch harnesses.
|
||||
|
||||
## 3. Resume honesty
|
||||
|
||||
The daemon does not assume a harness resumed just because the launch command
|
||||
ran. Three domain files (`packages/daemon/src/domain/`, all present @v0.3.0)
|
||||
make resume assessment honest:
|
||||
ran. Three domain files (`packages/daemon/src/domain/`) carry the resume
|
||||
assessment:
|
||||
|
||||
### `native-resume-probe.ts`
|
||||
|
||||
`assessNativeResumeProbe(input)` (`native-resume-probe.ts:43`) reads pane
|
||||
`assessNativeResumeProbe(input)` (`native-resume-probe.ts:84`) reads pane
|
||||
command + pane content and returns one of four honest statuses
|
||||
(`NativeResumeProbeStatus`, `:6`):
|
||||
(`NativeResumeProbeStatus`, `:7`):
|
||||
|
||||
- `resumed` — runtime-specific indicators confirm a resumed session.
|
||||
- `failed` — terminal failure (e.g. Claude printed "No conversation found" →
|
||||
code `no_conversation_found`, `:51–57`).
|
||||
code `no_conversation_found`, `:99–104`).
|
||||
- `inconclusive` — we don't know yet (e.g. Claude trust gate, code
|
||||
`trust_gate`, `:65–70`).
|
||||
`trust_gate`, `:113–118`).
|
||||
- `attention_required` — alive and recoverable but **needs operator action**
|
||||
(e.g. Claude resume-selection prompt, code `claude_resume_selection_prompt`,
|
||||
`:58–64`). This is the proxy for "an operator must choose the conversation";
|
||||
it is *distinct* from `inconclusive` and `failed` (docstring `:3–5`).
|
||||
`:106–111`). This is the proxy for "an operator must choose the
|
||||
conversation"; it is *distinct* from `inconclusive` and `failed` (comment
|
||||
`:4–6`).
|
||||
|
||||
`buildNativeResumeCommand` (`:27`) builds the resume command per runtime:
|
||||
claude → `claude --resume <token> [--name <name>]` (`:35`); codex → `codex
|
||||
resume <token>` (`:38`); other runtimes → `null` (`:40`).
|
||||
Other codes include `bypass_consent_gate`, `mcp_gate`, `login_required` and
|
||||
`returned_to_shell` for Claude, and `codex_client_incompatible`,
|
||||
`no_saved_session`, `codex_auth_refusal` and `model_selection_gate` for Codex;
|
||||
an unknown runtime gets `unsupported_runtime`. For Claude, `assessManagedProbe`
|
||||
upgrades an auto-mode or composer-only screen to `resumed` only after a stable
|
||||
native process match on a single pane.
|
||||
|
||||
This is Architecture Rule 15 in code: a failed resume is FAILED loudly; there
|
||||
is no automatic fresh fallback (the adapter's `verifyResumeLaunch` returns
|
||||
`ok:false` with a `retry_fresh` recovery hint, never a silent relaunch).
|
||||
`buildNativeResumeCommand` (`:32`) builds the resume command per runtime:
|
||||
claude → `claude --resume <token> [--name <name>]` (`:41`); codex →
|
||||
`buildCodexResumeCore` (`:44`), i.e. `codex<posture> resume <token>`
|
||||
(`:81`); other runtimes → `null` (`:46`).
|
||||
|
||||
At the adapter level (Architecture Rule 15, §4), the adapter's
|
||||
`verifyResumeLaunch` returns `ok:false` with a `retry_fresh` recovery hint and
|
||||
never relaunches itself. Acting on that hint is the caller's choice:
|
||||
`StartupOrchestrator` retries once fresh on `retry_fresh` unless the caller
|
||||
passes `allowFreshFallback: false` (`startup-orchestrator.ts:319–330`), and the
|
||||
restore orchestrator passes `false` when a pod-aware node requested resume
|
||||
(`restore-orchestrator.ts:1224`).
|
||||
|
||||
### `resume-metadata-refresher.ts`
|
||||
|
||||
`ResumeMetadataRefresher` (`resume-metadata-refresher.ts:37`). Post-launch
|
||||
resume-token capture: `refresh(sessions)` (`:62`) skips sessions that already
|
||||
have a `resumeToken` (`:65`), and for `claude-code` sessions with a token runs
|
||||
a `probeClaudeResume` returning `"resumable" | "not_resumable" |
|
||||
"inconclusive"` (`:31,74–75`) — a real launch of the resume command in a
|
||||
throwaway probe tmux session (`:106–111`), not a metadata guess.
|
||||
`ResumeMetadataRefresher` (`resume-metadata-refresher.ts:59`) records which
|
||||
sessions can be resumed, without overwriting a token it can't re-derive
|
||||
exactly. `refresh(sessions, opts?)` (`:124`) works in two modes:
|
||||
|
||||
- **At shutdown** (default): a Codex session without a token gets its thread
|
||||
id captured. For a Claude session with a token, it first reads Claude's live
|
||||
per-process session file, and only if that read fails runs
|
||||
`probeClaudeResume` (`:216`) — a real launch of the resume command in a
|
||||
throwaway probe tmux session (`rigged-refresh-<name>-<ts>`, `:301`),
|
||||
returning `"resumable" | "not_resumable" | "inconclusive"`. The probe result
|
||||
is recorded without clearing the token.
|
||||
- **`fillNullOnly`** (`:127`): a missing Claude token is filled from the
|
||||
status-line sidecar, and a present Claude or Codex token is re-stamped
|
||||
resumable only when it matches what the runtime reports. No probe runs.
|
||||
|
||||
### `codex-thread-id.ts`
|
||||
|
||||
Codex thread-id extraction (`codex-thread-id.ts`). Reads the Codex thread id
|
||||
from the Codex *logs* SQLite databases under `~/.codex/`:
|
||||
`readCodexThreadIdFromCandidateHomes(...)` (`:22`) →
|
||||
`readCodexThreadIdFromLogs(...)` (`:49`) → `resolveCodexLogDbPaths(homeDir)`
|
||||
(`:79`) which globs `<homeDir>/.codex/logs_<N>.sqlite` (`:84–89`,
|
||||
regex `^logs_(\d+)\.sqlite$`) and falls back to `logs_1.sqlite` (`:97`).
|
||||
Uses `better-sqlite3` (`:5`). Resolves the home dir by the harness PID
|
||||
(`defaultResolveHomeDirByPid`, `:9`).
|
||||
from the Codex *logs* SQLite databases in the Codex home (`CODEX_HOME` when
|
||||
set, else `~/.codex`, `:40`): `readCodexThreadIdFromCandidateHomes(...)`
|
||||
(`:34`) → `readCodexThreadIdFromLogs(...)` (`:244`) →
|
||||
`resolveCodexDbPaths(homeDir, kind)` (`:302`), which globs
|
||||
`logs_<N>.sqlite` (`:308`) and falls back to `logs_1.sqlite`. The logged
|
||||
thread ids are then checked against the `threads` table in `state_<N>.sqlite`
|
||||
(falling back to `state_5.sqlite`, `:320`; query `:269–280`); an id is
|
||||
returned only when exactly one CLI conversation matches (`:282`). Uses
|
||||
`better-sqlite3` (`:6`). Resolves the home dir by the harness PID
|
||||
(`defaultResolveHomeDirByPid`, `:16`).
|
||||
|
||||
> Precision note — `architecture.md` §5 "Resume honesty" says codex thread IDs
|
||||
> come from "the Codex SQLite database". Source is more specific: the
|
||||
> per-version Codex *logs* DBs `~/.codex/logs_N.sqlite`. Stated precisely here;
|
||||
> re-confirmed `codex-thread-id.ts:79–97` @HEAD.
|
||||
## 4. Relevant Architecture Rules
|
||||
|
||||
## 4. Relevant Architecture Rules (source-verified at HEAD)
|
||||
The rule texts live in `architecture-rules-and-event-system.md` §1; this
|
||||
section cites them by number with the adapter-level evidence checked for this
|
||||
module.
|
||||
|
||||
From `architecture.md` §7 (re-confirmed against the source cited inline):
|
||||
|
||||
- **Rule 5** — Runtime is member-authoritative in the pod-aware model.
|
||||
- **Rule 13** — Readiness checking is a retry loop with exponential backoff and
|
||||
a configurable timeout, using adapter-specific probes (Claude TUI indicator,
|
||||
Codex ready message, terminal immediate). Re-confirmed: `checkReady`
|
||||
delegates to `assessNativeResumeProbe`; the retry loop is
|
||||
`claude-code-adapter.ts:273` (16 attempts).
|
||||
- **Rule 14** — Resume states are locked: `resumed` / `rebuilt` / `fresh`;
|
||||
`rebuilt` = new process assembled from artifacts. (The probe layer adds
|
||||
`inconclusive` / `attention_required` as honest *transient* states, not
|
||||
outcomes — see §3.)
|
||||
- **Rule 15** — Restore honesty: failed resume is FAILED loudly; no automatic
|
||||
fresh fallback; fresh launch is an explicit follow-up only. Enforced in code
|
||||
by §2/§3 (`verifyResumeLaunch` returns `ok:false`, never relaunches).
|
||||
- **Rule 1** — §2: **0** files in `adapters/` import Hono.
|
||||
- **Rule 5** — restore picks a node's adapter by that node's own runtime: the
|
||||
saved startup context's runtime, else the node's (`restore-orchestrator.ts:1115–1116`),
|
||||
used to choose the replay adapter (`:1138`).
|
||||
- **Rule 13** — the readiness loop is `StartupOrchestrator.waitForReady`
|
||||
(`startup-orchestrator.ts:562`; 1 s doubling to a 16 s cap), which calls each
|
||||
adapter's `checkReady` (§2). The timeout comes from the
|
||||
`runtime.readiness_timeout_seconds` setting (default 30, range 1–600,
|
||||
`domain/readiness-timeout.ts:3`).
|
||||
- **Rule 14** — the probe's `attention_required` (§3) reaches the restore
|
||||
result through `HarnessLaunchResult.recovery` and `evidence` (§1), which
|
||||
become `RestoreNodeResult.status` (`types.ts:457`, `:475`) and
|
||||
`attentionEvidence` (`types.ts:478`). A startup's `continuityOutcome` is a
|
||||
separate field that also allows `forked` (`startup-orchestrator.ts:101`).
|
||||
- **Rule 15** — adapters return `ok:false` and never relaunch (§2, §3);
|
||||
restore passes `allowFreshFallback: false` for pod-aware resume
|
||||
(`restore-orchestrator.ts:1224`), and a resume that concludes failed rolls
|
||||
back to zero sessions as `awaiting-decision` (`restore-orchestrator.ts:1055`,
|
||||
`:1243`).
|
||||
|
||||
## See also
|
||||
|
||||
- `daemon-core.md` — where `createDaemon` constructs the three adapters.
|
||||
- `daemon-core.md` — where `createDaemon` constructs the runtime adapters.
|
||||
- `agent-spec-and-startup.md` — the `StartupOrchestrator` that calls these
|
||||
adapters and owns startup-action execution after `checkReady()`.
|
||||
- `lifecycle-snapshot-restore.md` — how persisted resume tokens flow into
|
||||
snapshot/restore (resume vs rebuild vs fresh).
|
||||
- `architecture-rules-and-event-system.md` — the full architecture rule list.
|
||||
- Source roots: `packages/daemon/src/domain/runtime-adapter.ts`,
|
||||
`packages/daemon/src/adapters/{claude-code-adapter,codex-runtime-adapter,terminal-adapter}.ts`,
|
||||
`packages/daemon/src/domain/{native-resume-probe,resume-metadata-refresher,codex-thread-id}.ts`.
|
||||
`packages/daemon/src/adapters/{claude-code-adapter,codex-runtime-adapter,pi-runtime-adapter,omp-runtime-adapter,stub-runtime-adapter,terminal-adapter,yolo-mode,non-interruptive,kernel-authority}.ts`,
|
||||
`packages/daemon/src/domain/{native-resume-probe,resume-metadata-refresher,codex-thread-id,claude-managed-launch,codex-network-default,native-permission-store}.ts`,
|
||||
`packages/daemon/src/domain/permission-policy/policy-ref.ts`.
|
||||
@@ -11,83 +11,92 @@ applies-when: |
|
||||
resolve and preserve identity.
|
||||
siblings: [daemon-core.md, adapters-and-runtimes.md, lifecycle-snapshot-restore.md, packaging-bootstrap-bundles.md]
|
||||
prerequisite-reads: [../README.md, daemon-core.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Agent/Rig Spec, Resolution, Startup, Identity
|
||||
|
||||
How the daemon turns authored YAML specs into a resolved, projected, launched,
|
||||
and identity-addressable topology. The spec-and-startup contract: parse →
|
||||
resolve → project → deliver → launch → wait → persist replay context.
|
||||
resolve → project → deliver pre-launch files → persist replay context → launch
|
||||
→ wait → deliver post-launch files.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c` (`git describe` →
|
||||
> `v0.3.1-6-g7eaf524c`). Package version is **0.3.1** across all three
|
||||
> packages (slice-00 §1.1). Source feed located by `architecture.md` headings
|
||||
> (§4 Canonical Reboot Types, §5 Domain Services, §6 Execution Flows, §7
|
||||
> Architecture Rules) per slice-08 §10.1 — line numbers are advisory only.
|
||||
> Verified against source at main `e8f0ab340db773392ec8be75b072d1c0f3068a50`. `domain/…` and `routes/…` paths
|
||||
> are under `packages/daemon/src/`; a bare file name such as
|
||||
> `rigspec-schema.ts:119` is in `packages/daemon/src/domain/`. Each count sits
|
||||
> beside the command that produces it; run the command from the repository
|
||||
> root to refresh it.
|
||||
|
||||
## 1. Canonical spec and topology types
|
||||
|
||||
(`architecture.md` §4 "Spec and topology")
|
||||
- **AgentSpec** (`types.ts:941`) — parsed from `agent.yaml`
|
||||
(`agent-resolver.ts:150`). Owns imports, defaults, startup, resources, and
|
||||
profiles. Canonical parse/normalize/validate is `domain/agent-manifest.ts`.
|
||||
- **RigSpec** (`types.ts:1172`) — canonical pod-aware rig topology. Uses
|
||||
`version: "0.2"` and `pods[]`; owns cross-pod `edges[]`, rig-level startup
|
||||
overlays, and `cultureFile`, plus the optional `summary`, `permissionPolicy`,
|
||||
`nonInterruptive`, `managedBlocks`, `docs`, `services` and `workspace` fields.
|
||||
An authored `non_interruptive` must be a boolean (`rigspec-schema.ts:179`) and
|
||||
sets the rig's saved non-interruptive choice at instantiation unless an
|
||||
explicit launch option is given (`rigspec-instantiator.ts:1345`).
|
||||
|
||||
- **AgentSpec** — parsed from `agent.yaml`. Owns imports, defaults, startup,
|
||||
resources, and profiles. Canonical parse/normalize/validate is
|
||||
`domain/agent-manifest.ts`.
|
||||
- **RigSpec** — canonical pod-aware rig topology. Uses `version: "0.2"` and
|
||||
`pods[]`; owns cross-pod `edges[]`, rig-level startup overlays, and
|
||||
`cultureFile`.
|
||||
`version` is the spec-schema version, not the package version. It is not a
|
||||
code constant: `RigSpecSchema.validate` requires only a non-empty string
|
||||
(`rigspec-schema.ts:119`) and `RigSpecSchema.normalize` carries the authored
|
||||
value through (`rigspec-schema.ts:253`). `"0.2"` is the canonical authored
|
||||
value, and the pod-aware exporter writes it (`rigspec-exporter.ts:223`).
|
||||
|
||||
> Drift-fix D-spec-version — `architecture.md` §4 says RigSpec
|
||||
> "Uses `version: "0.2"`". This is the **spec-schema version, NOT the
|
||||
> package version** — a different versioning axis from the 0.3.1 package
|
||||
> version. The value is NOT a code constant: `rigspec-schema.ts:52`
|
||||
> validates `version` only as a "required non-empty string"
|
||||
> (`if (!obj["version"] || typeof obj["version"] !== "string")`); `:163`
|
||||
> carries it through as `raw["version"] as string`. `"0.2"` is the
|
||||
> canonical authored value, correctly described as a schema axis. Do NOT
|
||||
> "correct" it toward 0.3.1 (slice-08 §4.6 binding). Re-confirmed
|
||||
> `rigspec-schema.ts:52,163` @HEAD.
|
||||
|
||||
- **RigServicesSpec** — optional `services` block on a pod-aware RigSpec.
|
||||
Shipped kind is Compose-backed env management with `composeFile`,
|
||||
`projectName?`, `profiles?`, `downPolicy?`, `waitFor?`, `surfaces?`,
|
||||
`checkpoints?`.
|
||||
- **RigSpecPod** — pod-local bounded context with `members[]`, pod-local
|
||||
`edges[]`, pod startup, optional continuity policy.
|
||||
- **RigSpecPodMember** — member-level runtime/startup surface: `agentRef`,
|
||||
`profile`, `runtime`, `model?`, `cwd`, `restorePolicy?`, member startup
|
||||
overlays.
|
||||
- **Pod** — persisted DB entity for a pod.
|
||||
- **ContinuityState** — persisted live continuity row keyed by
|
||||
`podId + nodeId`.
|
||||
- **RigServicesSpec** (`types.ts:1221`) — optional `services` block on a
|
||||
pod-aware RigSpec. Shipped kind is Compose-backed env management
|
||||
(`kind: "compose"`) with `composeFile`, `projectName?`, `profiles?`,
|
||||
`downPolicy?`, `waitFor?`, `surfaces?`, `checkpoints?`.
|
||||
- **RigSpecPod** (`types.ts:1128`) — pod-local bounded context with
|
||||
`members[]`, pod-local `edges[]`, pod startup, optional continuity policy.
|
||||
- **RigSpecPodMember** (`types.ts:1072`) — member-level runtime/startup
|
||||
surface: `agentRef`, `profile`, `runtime`, `model?`, `effort?`, `cwd`, `restorePolicy?`,
|
||||
member startup overlays (`startup?`), plus `label?`, `codexConfigProfile?`,
|
||||
`role?`, `permissionPolicy?`, `compactionStrategy?`, `mechanic?`,
|
||||
`sessionSource?` and `starterRef?`.
|
||||
- **Pod** (`types.ts:10`) — persisted DB entity for a pod.
|
||||
- **ContinuityState** (`types.ts:20`) — persisted live continuity row keyed by
|
||||
`podId + nodeId` (`PRIMARY KEY (pod_id, node_id)`,
|
||||
`packages/daemon/src/db/migrations/014_agentspec_reboot.ts:24`).
|
||||
|
||||
## 2. Execution and projection types
|
||||
|
||||
(`architecture.md` §4 "Execution and restore" — restore/snapshot members are
|
||||
detailed in `lifecycle-snapshot-restore.md`; the spec/projection members live
|
||||
here)
|
||||
Restore/snapshot types are detailed in `lifecycle-snapshot-restore.md`; the
|
||||
spec/projection types live here.
|
||||
|
||||
- **ResolvedNodeConfig** — output of profile resolution. Carries effective
|
||||
runtime/model/cwd, narrowed restore policy, selected resources, layered
|
||||
startup block, resolved spec identity.
|
||||
- **ProjectionPlan** — runtime projection plan for a node: runtime, cwd,
|
||||
projection entries, startup block, diagnostics, conflict/no-op
|
||||
classifications.
|
||||
- **RuntimeAdapter** — the five-method contract (adapter detail in
|
||||
`adapters-and-runtimes.md`): `listInstalled(binding)`
|
||||
(`runtime-adapter.ts:131`), `project(plan, binding)` (`:134`),
|
||||
`deliverStartup(files, binding)` (`:137`), `launchHarness(binding, opts)`
|
||||
(`:147`), `checkReady(binding)` (`:153`). Re-confirmed @HEAD — the contract
|
||||
is current.
|
||||
- **HarnessLaunchResult** — `{ ok, resumeToken?, resumeType?, error? }`
|
||||
returned by `launchHarness`.
|
||||
- **StartupOrchestrator** — drives the full startup sequence (§4 below).
|
||||
- **ResolvedNodeConfig** (`profile-resolver.ts:33`) — output of profile
|
||||
resolution. Carries effective runtime/model/effort/cwd, narrowed restore policy,
|
||||
selected resources, layered startup block, resolved spec identity, and the
|
||||
resolved compaction strategy, continuity mechanic, lifecycle, activity and
|
||||
skill loadout.
|
||||
- **ProjectionPlan** (`projection-planner.ts:42`) — runtime projection plan for
|
||||
a node: runtime, cwd, projection entries, startup block, diagnostics,
|
||||
conflict/no-op classifications.
|
||||
- **RuntimeAdapter** (`runtime-adapter.ts:143`) — the five-method contract
|
||||
(adapter detail in `adapters-and-runtimes.md`): `listInstalled(binding)`
|
||||
(`runtime-adapter.ts:153`), `project(plan, binding)` (`:156`),
|
||||
`deliverStartup(files, binding, sendInteractiveText?)` (`:160`),
|
||||
`launchHarness(binding, opts)` (`:170`), `checkReady(binding)` (`:176`). The
|
||||
interface also declares an optional `claudeManagedLaunch` property (`:145`)
|
||||
and an optional method, `skillTargetPath?(...)` (`:150`). Required methods:
|
||||
**5**
|
||||
(`sed -n '/^export interface RuntimeAdapter /,/^}/p' packages/daemon/src/domain/runtime-adapter.ts | grep -c -E '^ [a-zA-Z]+\('`).
|
||||
- **HarnessLaunchResult** (`runtime-adapter.ts:97`) — returned by
|
||||
`launchHarness`: either `{ ok: true, resumeToken?, resumeType?,
|
||||
appliedLaunch? }` or `{ ok: false, error, recovery?, evidence? }`, where
|
||||
`recovery` is `"retry_fresh"` or `"attention_required"` (`:95`).
|
||||
- **StartupOrchestrator** (`startup-orchestrator.ts:140`) — drives the full
|
||||
startup sequence (§4 below).
|
||||
|
||||
## 3. Parsing, validation, resolution pipeline
|
||||
|
||||
(`architecture.md` §5 "Parsing and validation" + "Resolution pipeline"; all
|
||||
under `packages/daemon/src/domain/`, zero Hono imports per Architecture Rule 1)
|
||||
All of the files below are under `packages/daemon/src/domain/`, and none of
|
||||
them imports Hono: **0**
|
||||
(`git grep -l '"hono' -- packages/daemon/src/domain/{agent-manifest,rigspec-schema,rigspec-codec,startup-validation,path-safety,spec-validation-service,spec-review-service,agent-resolver,agent-preflight,profile-resolver,startup-resolver,projection-planner}.ts | wc -l`).
|
||||
|
||||
**Parse / validate:**
|
||||
|
||||
@@ -110,115 +119,210 @@ under `packages/daemon/src/domain/`, zero Hono imports per Architecture Rule 1)
|
||||
- `startup-resolver.ts` — additive startup layering.
|
||||
- `projection-planner.ts` — runtime resource projection planning.
|
||||
|
||||
All eleven files re-confirmed present in `packages/daemon/src/domain/` @HEAD.
|
||||
|
||||
## 4. Startup orchestration (the spec-startup contract)
|
||||
|
||||
(`architecture.md` §4 "StartupOrchestrator" + §5 "Startup, runtime, and
|
||||
instantiation" + §7 Architecture Rule 6)
|
||||
`StartupOrchestrator.startNode` (`startup-orchestrator.ts:172`, a thin wrapper
|
||||
over `startNodeWithWarnings`, `:178`) drives, in source order: apply the seat's
|
||||
permission selection and the rig's non-interruptive setting to the binding
|
||||
(`NativePermissionStore.apply`, `:183`; `:193`) → mark pending (`:209`) and
|
||||
select the startup proof (`:210`) → project resources (`:224`) → deliver
|
||||
pre-launch files (`:266`) → persist startup context (`:282`) → launch harness,
|
||||
recording any resume token (`:301`, `:313`) → wait for ready (`:384`) → deliver
|
||||
the session identity prompt and interactive files (`:420`, `:473`) → execute
|
||||
`after_files` then the remaining `after_ready` actions (`:495`, `:502`; see
|
||||
the resume preload below) → check readiness
|
||||
again and, for a Claude resume, that the launched session agrees with the
|
||||
requested token (`:507`–`535`) → mark ready (`:538`). The class doc comment
|
||||
(`startup-orchestrator.ts:120`–`139`) lists ten steps; the code persists the
|
||||
startup context before the harness launch.
|
||||
|
||||
`StartupOrchestrator` (`domain/startup-orchestrator.ts`) drives:
|
||||
mark pending → project resources → deliver pre-launch files → launch harness
|
||||
→ wait for ready → deliver interactive files → execute actions → persist
|
||||
context → mark ready.
|
||||
|
||||
**Pre-launch vs interactive delivery split** — the load-bearing seam,
|
||||
re-confirmed at source:
|
||||
**Pre-launch vs interactive delivery split** — the load-bearing seam:
|
||||
|
||||
- Pre-launch (filesystem, before harness boot): `guidance_merge`,
|
||||
`skill_install` (`startup-orchestrator.ts:78,167` — "Deliver pre-launch
|
||||
files (guidance_merge, skill_install → filesystem)").
|
||||
`skill_install` (`startup-orchestrator.ts:126` — "Deliver pre-launch files
|
||||
(guidance_merge, skill_install → filesystem)"; delivered at `:266`). Any
|
||||
file that is not `send_text` is delivered here.
|
||||
- Post-launch (TUI, after harness is ready): `send_text`
|
||||
(`startup-orchestrator.ts:82,160,263` — partition by concrete hint at
|
||||
`:141`, deliver after readiness at `:263`).
|
||||
(`startup-orchestrator.ts:130`; held for post-launch and delivered after
|
||||
readiness at `:473`). Rebuild artifacts are put in front of these files.
|
||||
- **Fresh launch:** the builtin `session_identity` action
|
||||
(`rigspec-instantiator.ts:2655`) is sent as one turn together with the first
|
||||
`send_text` file (`deliverInitialSessionPrompt`, `startup-orchestrator.ts:663`).
|
||||
- **Resume, fork or rebuild:** applicable `after_ready` `send_text` actions are
|
||||
sent ahead of the first `send_text` file as one turn.
|
||||
|
||||
The orchestrator persists replay context including the resume token for future
|
||||
restores (consumed by `lifecycle-snapshot-restore.md`).
|
||||
The orchestrator persists replay context for future restores (consumed by
|
||||
`lifecycle-snapshot-restore.md`): the projection entries, resolved startup
|
||||
files, startup actions and runtime go to `node_startup_context`
|
||||
(`startup-orchestrator.ts:282`), and a resume token returned by the launch is
|
||||
written to the session row (`updateResumeToken`, `:313`).
|
||||
|
||||
**Architecture Rule 6 — startup layering is additive and ordered** (carried
|
||||
verbatim, `architecture.md` §7): (1) agent base, (2) profile, (3) rig culture
|
||||
**Checked startup text (Claude Code).** Interactive text for a Claude seat goes
|
||||
through `sendInteractiveText` (`:777`): paste, wait 200 ms, press Enter, wait
|
||||
200 ms, then read 200 lines of the pane. `inspectStartupStagedText`
|
||||
(`startup-submission-evidence.ts:36`) classifies the composer as clear, staged
|
||||
or unverified. While the composer shows Claude's own collapsed-paste label for
|
||||
this text (`startupOwnCollapsedPaste`, `:49`), `settleOwnPaste`
|
||||
(`startup-orchestrator.ts:867`) looks again, up to 25 times 200 ms apart,
|
||||
without pressing anything. Text still staged gets one guarded Enter-only retry
|
||||
(`:823`), and then a "press Enter in that pane" warning (`:836`). An
|
||||
unverified or staged result is a warning, never a startup failure, and is
|
||||
reported in a `submission` block on `node.startup_ready` and on the result.
|
||||
|
||||
**Startup proof.** A seat can be `ready` without being oriented.
|
||||
`resolveStartupProof` (`startup-resolver.ts:81`) selects the last applicable
|
||||
authored `startup_proof` action; the default is none. For a fresh launch on a
|
||||
non-terminal runtime in `authenticated` mode, the orchestrator issues a
|
||||
challenge after readiness (`issueStartupChallenge`, `startup-proof.ts:90`),
|
||||
recorded as `node.startup_challenged` with a hash of the resolved startup files.
|
||||
The expected answer is derived from the challenge and that hash
|
||||
(`computeExpectedAnswer`, `:53`) and is never stored. The challenge text is
|
||||
appended to the identity prompt, or sent on its own when there is no identity
|
||||
action (`startup-orchestrator.ts:487`). On Claude, a short proof instruction
|
||||
line (`STARTUP_PROOF_INSTRUCTION_LINE`, `startup-proof.ts:63`) is sent only
|
||||
after the startup prompt was confirmed submitted (`sendProofInstruction`,
|
||||
`startup-orchestrator.ts:760`). The seat answers with
|
||||
`rig startup-proof submit --challenge-id … --answer …`
|
||||
(`packages/cli/src/commands/startup-proof.ts:99`), which posts to the activity
|
||||
hook route (`routes/activity.ts:239`); `verifyStartupProof`
|
||||
(`startup-proof.ts:133`) records `node.startup_proof_verified` or
|
||||
`node.startup_proof_rejected`. Orientation status is derived from those events
|
||||
(`startup-proof.ts:239`), never from `startup_status`. Other fresh launches (no
|
||||
proof selected, or a terminal runtime) record `node.startup_proof_skipped`
|
||||
(`startup-orchestrator.ts:374`); resume, fork and rebuild keep the existing proof
|
||||
history.
|
||||
|
||||
**Fresh context pending and `rig seat continue`.** When a fresh launch stops at
|
||||
a native prompt before its context is delivered, `fail()` (`:592`) marks the
|
||||
failure `freshContextPending` and adds "run: rig seat continue <session>".
|
||||
`canContinueFresh` (`:554`, using `hasPendingFreshStartup`, `:35`) allows
|
||||
continuation only while that failure is the occupant's latest startup event.
|
||||
`continueFreshStartup` (`seat-lifecycle-service.ts:395`) re-checks the binding,
|
||||
pane and runtime, then runs startup again on the same conversation without
|
||||
relaunching the harness. It is reached by `rig seat continue`,
|
||||
`POST /api/seat/continue/:seatRef` and the per-seat startup route's `continue`
|
||||
action (`routes/startup.ts:148`).
|
||||
|
||||
**Startup layering is additive and ordered** (`resolveStartup`,
|
||||
`startup-resolver.ts:15`–`22`): (1) agent base, (2) profile, (3) rig culture
|
||||
file, (4) rig startup, (5) pod startup, (6) member startup, (7) operator debug
|
||||
append. This is the spec-startup contract's invariant; the full 25-rule list
|
||||
lives in `architecture-rules-and-event-system.md`.
|
||||
append. Files and actions are concatenated in that order with no
|
||||
deduplication (`startup-resolver.ts:24`). This is the spec-startup contract's
|
||||
invariant; the cross-cutting architecture rules are collected in
|
||||
`architecture-rules-and-event-system.md`.
|
||||
|
||||
**Startup action constraints** (`architecture.md` §7 "Current startup action
|
||||
constraints"): no shell startup actions; action types are `slash_command` and
|
||||
`send_text` only; non-idempotent actions must not apply on restore; retrying
|
||||
failed startup is handled as restore.
|
||||
The launch path takes only the actions from `resolveStartup`. It builds the
|
||||
startup *file* chain itself (`buildResolvedStartupFiles`,
|
||||
`rigspec-instantiator.ts:2518`), in this order: agent base, profile, the shipped
|
||||
`CULTURE-default.md` floor (`:327`), the rig `culture_file`, rig, pod, member,
|
||||
the shipped `openrig-start.md` (`:2572`), and, for fresh launches when enabled,
|
||||
the onboarding files (`:2582`). Managed-block guidance is deduplicated, and a
|
||||
`starter_ref` layer is put in front when a member names one (`:2243`).
|
||||
|
||||
**Remote import constraints** (§7): the reboot supports `local:...` and
|
||||
`path:/abs/...`. Remote `agent_ref` sources remain unsupported and fail in
|
||||
preflight.
|
||||
**Startup action constraints** (`startup-validation.ts`): startup entries must
|
||||
be files (`:22`); no shell startup actions (`:65`); action types are
|
||||
`slash_command`, `send_text` and `startup_proof` (`:7`), and a `startup_proof`
|
||||
action must be idempotent with a value of `authenticated` or `none` (`:73`);
|
||||
non-idempotent actions must not apply on restore (`:100`). Retrying a failed
|
||||
startup is handled as a restore, which is why the orchestrator skips
|
||||
non-idempotent actions on restore (`startup-orchestrator.ts:634`,
|
||||
"retry-as-restore safety"). The exception is
|
||||
`PodRigInstantiator.retryFirstStart` (`rigspec-instantiator.ts:1147`), which
|
||||
re-runs a first start that failed at projection, before any harness launch.
|
||||
|
||||
**Remote import constraints**: `agent_ref` and AgentSpec imports accept
|
||||
`local:<relative path>` and `path:<absolute path>` only
|
||||
(`rigspec-schema.ts:41`, `agent-manifest.ts:40`); a terminal member uses the
|
||||
`builtin:terminal` sentinel (`rigspec-schema.ts:532`). Remote `agent_ref`
|
||||
sources remain unsupported: RigSpec validation rejects them
|
||||
(`rigspec-schema.ts:563`), `rigPreflight` runs that validation
|
||||
(`rigspec-preflight.ts:252`), and the resolver refuses a remote import again
|
||||
(`agent-resolver.ts:203`).
|
||||
|
||||
## 5. Instantiation, preflight, export
|
||||
|
||||
(`architecture.md` §5 "Startup, runtime, and instantiation" + §6 "RigSpec
|
||||
import / validate / preflight / export")
|
||||
|
||||
- `runtime-adapter.ts` — adapter contract + bridge types.
|
||||
- `rigspec-preflight.ts` — dual-stack legacy preflight plus rebooted
|
||||
`rigPreflight(...)`.
|
||||
- `rigspec-instantiator.ts` — dual-stack `RigInstantiator` plus
|
||||
`PodRigInstantiator`.
|
||||
- `rigspec-preflight.ts` — dual-stack legacy preflight (`RigSpecPreflight`,
|
||||
`rigspec-preflight.ts:26`) plus rebooted `rigPreflight(...)` (`:246`).
|
||||
- `rigspec-instantiator.ts` — dual-stack `RigInstantiator`
|
||||
(`rigspec-instantiator.ts:33`) plus `PodRigInstantiator` (`:475`).
|
||||
- `rigspec-exporter.ts` — dual-format live rig export to YAML/JSON.
|
||||
- `pod-repository.ts` — pod CRUD plus live continuity-state CRUD.
|
||||
|
||||
`routes/rigspec.ts` is the dual-format seam: validate
|
||||
`routes/rigspec.ts` is the dual-format seam; a parsed spec with a `pods` array
|
||||
takes the pod-aware path (`routes/rigspec.ts:83`): validate
|
||||
(pod-aware → `RigSpecSchema.validate`; legacy → `LegacyRigSpecSchema.validate`),
|
||||
preflight (`rigPreflight({ rigSpecYaml, rigRoot, fsOps })` vs
|
||||
preflight (`rigPreflight({ rigSpecYaml, rigRoot, … })` vs
|
||||
`RigSpecPreflight.check(spec)`), import
|
||||
(`podInstantiator.instantiate(yaml, rigRoot)` vs
|
||||
(`podInstantiator.instantiate(yaml, rigRoot, …)` vs
|
||||
`RigInstantiator.instantiate(spec)`), export (pod-aware exports canonical
|
||||
`version: "0.2"` RigSpec; legacy exports flat-node v1).
|
||||
`version: "0.2"` RigSpec; legacy exports flat-node `schemaVersion: 1`,
|
||||
`rigspec-exporter.ts:105`).
|
||||
|
||||
The import validation route delegates to `validateRigSpecImport`
|
||||
(`routes/rigspec.ts:215–223`, `spec-validation-service.ts:10–19`). The same
|
||||
helper powers local `rig spec validate` without contacting the daemon
|
||||
(`packages/cli/src/commands/rig.ts:204–207`); it selects the pod-aware or legacy
|
||||
validator and distinguishes YAML parse failures from validator exceptions.
|
||||
|
||||
## 6. Identity: whoami, materialize, bind, adopt
|
||||
|
||||
(`architecture.md` §6 "Whoami and adopted-session parity flow" +
|
||||
"Materialize / bind / adopt flow")
|
||||
|
||||
**Whoami resolution** — the daemon owns the truth surface through
|
||||
`/api/whoami` (`routes/whoami.ts`); tmux metadata is an adopted-session
|
||||
anchor, not sovereign truth. `whoami-service.ts:8` declares
|
||||
`resolvedBy: "node_id" | "session_name"`. The route requires `nodeId` or
|
||||
`sessionName` (`routes/whoami.ts:10-15`). Resolution order:
|
||||
explicit `--node-id` → explicit `--session` → env vars → tmux metadata →
|
||||
raw tmux session-name fallback.
|
||||
`sessionName` (`routes/whoami.ts:19`–`23`). The CLI resolves identity
|
||||
(`packages/cli/src/commands/whoami.ts:142`–`148`) in this order: explicit
|
||||
`--node-id` → explicit `--session` → env vars (`OPENRIG_NODE_ID` /
|
||||
`OPENRIG_SESSION_NAME`, or the legacy `RIGGED_NODE_ID` /
|
||||
`RIGGED_SESSION_NAME`, `whoami.ts:158`) → tmux metadata → raw tmux
|
||||
session-name fallback.
|
||||
|
||||
- Managed sessions prefer projected `OPENRIG_NODE_ID` /
|
||||
`OPENRIG_SESSION_NAME`.
|
||||
- Adopted sessions use tmux-owned metadata written at claim/bind time.
|
||||
`OPENRIG_SESSION_NAME`, set when the tmux session is created
|
||||
(`node-launcher.ts:143`–`144`).
|
||||
- Adopted sessions use tmux-owned metadata written at bind time.
|
||||
|
||||
> Drift-nuance D5 (the careful axis — see also `transport-and-transcripts.md`):
|
||||
> the tmux metadata keys `@rigged_node_id` / `@rigged_session_name` /
|
||||
> `@rigged_rig_id` / `@rigged_rig_name` / `@rigged_logical_id` in
|
||||
> `architecture.md` §6 are **CORRECT as-is** and were verified literally at
|
||||
> source — `claim-service.ts:77-81` writes exactly these `@rigged_*` keys;
|
||||
> `claim-service.ts` and `rig-lifecycle-service.ts` read them. This is a
|
||||
> SEPARATE axis from MCP tool names (which are `rig_*`, slice-00 §1.4). Do
|
||||
> NOT blanket-replace `rigged` → `rig` (banked
|
||||
> `feedback_release_prep_three_layer_depersonalization`: the rename was
|
||||
> scoped, not global). The tmux metadata keys were NOT renamed.
|
||||
> Re-confirmed `claim-service.ts:77-81` @HEAD.
|
||||
The tmux metadata keys use the `@rigged_` prefix:
|
||||
`ClaimService.setRiggedMetadata` writes `@rigged_node_id` /
|
||||
`@rigged_session_name` / `@rigged_rig_id` / `@rigged_rig_name` /
|
||||
`@rigged_logical_id` (`claim-service.ts:182`–`186`), called from `bind`,
|
||||
`reconcileSession` and `createAndBindToPod`;
|
||||
`RigLifecycleService.unclaimSession` clears the same five keys
|
||||
(`rig-lifecycle-service.ts:209`–`213`); and `rig whoami` reads
|
||||
`@rigged_node_id` and `@rigged_session_name`
|
||||
(`packages/cli/src/commands/whoami.ts:173`, `:179`). MCP tool names use a
|
||||
different prefix, `rig_*` (`packages/cli/src/mcp-server.ts`).
|
||||
|
||||
**Materialize / bind / adopt** (`architecture.md` §6): `POST
|
||||
/api/rigs/import/materialize` creates a pod-aware topology without launching
|
||||
sessions; `POST /api/discovery/:id/bind` attaches a discovered live session to
|
||||
an existing logical node; `POST /api/discovery/:id/adopt` is the composite
|
||||
**Materialize / bind / adopt**: `POST /api/rigs/import/materialize`
|
||||
(`routes/rigspec.ts:176`) creates a pod-aware topology without launching
|
||||
sessions and refuses a legacy spec (`routes/rigspec.ts:189`);
|
||||
`POST /api/discovery/:id/bind` (`routes/discovery.ts:77`) attaches a
|
||||
discovered live session to an existing logical node (`logicalId`), or creates
|
||||
a member in a pod (`podNamespace` + `memberName`) and binds it;
|
||||
`POST /api/discovery/:id/adopt` (`routes/discovery.ts:139`) is the composite
|
||||
route (bind to existing node, or create a new member in a target pod and bind
|
||||
immediately). CLI mirrors via `rig bind` / `rig adopt`. Authored pod namespace
|
||||
is preserved through adoption so logical ids stay
|
||||
`${podNamespace}.${memberName}` (Architecture Rule 24: adopted-session parity
|
||||
is tmux-metadata parity, not fake env-var parity).
|
||||
immediately). On the CLI, `rig bind` calls the bind route
|
||||
(`packages/cli/src/commands/bind.ts:56`), and `rig adopt` materializes the
|
||||
topology and then calls the bind route for each session
|
||||
(`packages/cli/src/commands/adopt.ts:163`, `:212`). Authored pod namespace is
|
||||
preserved through adoption so logical ids stay `${podNamespace}.${memberName}`
|
||||
(`claim-service.ts:663`). Adopted-session parity is tmux-metadata parity, not
|
||||
fake env-var parity.
|
||||
|
||||
## OPEN / carried items
|
||||
## 7. Specialist rosters
|
||||
|
||||
- **D-spec-version (resolved-as-correct):** RigSpec `version: "0.2"` is the
|
||||
spec-schema axis, validated only as a non-empty string in source. Documented,
|
||||
not "corrected".
|
||||
- **D5 (careful, resolved-as-correct for this module):** tmux `@rigged_*`
|
||||
metadata keys are literally still `@rigged_*` at HEAD — architecture.md §6 is
|
||||
accurate. The MCP-tool-name `rigged_*` drift lands in
|
||||
`transport-and-transcripts.md`.
|
||||
A roster is an authored list of recommended specialist seats, kept outside the
|
||||
specs. `rig roster list`, `show <id>` and `find <query>`
|
||||
(`packages/cli/src/commands/roster.ts`) read version-1 roster JSON files from
|
||||
`<workspace.root>/rosters/` (or `--folder`); they are read-only and need no
|
||||
daemon route. Each roster names a purpose and a curator, and each member names
|
||||
a seat, host, capabilities, engagement, `use_when` and `why`. `show` and
|
||||
`find` add current observations of each member by matching host and seat
|
||||
against `rig ps --nodes`, read from the configured daemon and again across
|
||||
registered hosts, and report a gap rather than guessing when no node matches. A roster is a
|
||||
recommendation, not an assignment or an authority. It is unrelated to the
|
||||
topology roster a snapshot records (see `lifecycle-snapshot-restore.md`).
|
||||
|
||||
## See also
|
||||
|
||||
@@ -226,7 +330,8 @@ is tmux-metadata parity, not fake env-var parity).
|
||||
- `adapters-and-runtimes.md` — the five-method RuntimeAdapter contract detail.
|
||||
- `lifecycle-snapshot-restore.md` — snapshot/restore consumes persisted replay
|
||||
context.
|
||||
- `architecture-rules-and-event-system.md` — the full 25 architecture rules.
|
||||
- `architecture-rules-and-event-system.md` — the cross-cutting architecture
|
||||
rules.
|
||||
- Source roots: `packages/daemon/src/domain/{agent-manifest,rigspec-schema,
|
||||
profile-resolver,startup-orchestrator,whoami-service,claim-service}.ts`,
|
||||
`packages/daemon/src/routes/{rigspec,whoami}.ts`.
|
||||
@@ -11,8 +11,8 @@ applies-when: |
|
||||
compatibility limits that still describe the shipped system.
|
||||
siblings: [daemon-core.md, coordination-primitive.md]
|
||||
prerequisite-reads: [../README.md, daemon-core.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Architecture Invariants, Event System, Compatibility Notes
|
||||
@@ -21,20 +21,25 @@ This module collects the cross-cutting invariants that do not belong to any
|
||||
single subsystem: the architecture rules the codebase holds itself to, the
|
||||
event-system shape, and the intentional compatibility limits.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c` (`git describe` →
|
||||
> `v0.3.1-6-g7eaf524c`); package version **0.3.1** across all three packages
|
||||
> (slice-00 §1.1). HEAD carries 6 commits of unreleased 0.3.2 work; no
|
||||
> `v0.3.2` tag exists.
|
||||
> Verified against source at main `e8f0ab340db773392ec8be75b072d1c0f3068a50`. Each count below sits beside the
|
||||
> command that produces it; run the command from the repository root to refresh
|
||||
> it.
|
||||
|
||||
## 1. Architecture rules
|
||||
|
||||
These are the invariants the codebase is built to preserve (`architecture.md`
|
||||
§7). Rule 6 (startup layering) and rule 7 (restore-policy narrowing) are the
|
||||
spec/startup contract — see `agent-spec-and-startup.md` for their flow detail;
|
||||
they are restated here as system-level invariants.
|
||||
These are the invariants the codebase is built to preserve. Rule 6 (startup
|
||||
layering) and rule 7 (restore-policy narrowing) are the spec/startup contract —
|
||||
see `agent-spec-and-startup.md` for their flow detail; they are restated here as
|
||||
system-level invariants.
|
||||
|
||||
1. Zero Hono in `domain/` and `adapters/`.
|
||||
2. Routes depend on the domain; the domain never depends on routes.
|
||||
1. No runtime Hono in `domain/` and `adapters/`. `adapters/` has none; `domain/`
|
||||
has type-only Hono imports (`import type`) in **4** files
|
||||
(`git grep -l 'from "hono' -- packages/daemon/src/domain packages/daemon/src/adapters | wc -l`).
|
||||
2. Routes depend on the domain; the domain does not depend on routes at
|
||||
runtime. **1** type-only import crosses back:
|
||||
`domain/project-classifier.ts:1` imports `IdentityProvenance` from
|
||||
`routes/require-sender-identity.js`
|
||||
(`git grep -lE 'from "(\.\./)+routes/' -- packages/daemon/src/domain packages/daemon/src/adapters | wc -l`).
|
||||
3. Shared DB-handle invariants are enforced at construction time.
|
||||
4. The reboot is engine-first: domain services land before public-surface
|
||||
rewiring.
|
||||
@@ -50,31 +55,52 @@ they are restated here as system-level invariants.
|
||||
in the owning artifact.
|
||||
10. Restore replay uses classification-free projection intent, not stale
|
||||
startup-time `no_op` / conflict classifications.
|
||||
11. Startup status is explicit session state: `pending`, `ready`, `failed`.
|
||||
11. Startup status is explicit session state: `pending`, `ready`,
|
||||
`attention_required`, `failed` (`types.ts:102`).
|
||||
12. Session recency depends on monotonic ULIDs: `session-registry.ts` uses
|
||||
`monotonicFactory()`; restore selects the newest session by max ULID.
|
||||
`monotonicFactory()`. Restore does not pick the newest session by max
|
||||
ULID; it resolves the active occupant recorded in the snapshot
|
||||
(`resolveActiveSnapshotSession`, `active-occupant.ts:74`, called at
|
||||
`restore-orchestrator.ts:774` and `:971`).
|
||||
13. Readiness checking is a retry loop with exponential backoff and
|
||||
configurable timeout, using adapter-specific probes (Claude TUI
|
||||
indicator, Codex ready message, terminal immediate).
|
||||
14. Resume states are locked: `resumed` / `rebuilt` / `fresh`. `rebuilt` =
|
||||
14. Restore outcome states are a fixed set (`RestoreNodeResult.status`,
|
||||
`types.ts:475`): `resumed`, `rebuilt`, `fresh-primed`,
|
||||
`awaiting-decision`, `attention_required`, `failed`, plus
|
||||
`operator_recovered` (set only by later reconciliation) and `fresh`
|
||||
(retained only for the legacy continuity-restoring skip path). `rebuilt` =
|
||||
new process assembled from artifacts.
|
||||
15. Restore honesty: failed resume is FAILED loudly. No automatic fresh
|
||||
fallback. Fresh launch is explicit follow-up only.
|
||||
15. Restore honesty: a failed resume stops loudly as `awaiting-decision`, with
|
||||
the blank session rolled back and no session running
|
||||
(`restore-orchestrator.ts:1055`). No automatic fresh fallback. Fresh launch
|
||||
is explicit follow-up only (`rig up --fresh <seats...>`,
|
||||
`packages/cli/src/commands/up.ts:99`).
|
||||
16. Post-command handoff required on `up`, `down`, `restore`,
|
||||
`snapshot create`: what happened + current state + next action.
|
||||
17. Session naming: `{pod}-{member}@{rig}` — human-authored,
|
||||
system-validated. No generation, no slugification.
|
||||
18. Communication: tmux is transport, not truth. `send/capture/broadcast`
|
||||
wrap tmux reliably with honest errors.
|
||||
19. Transcripts: raw capture via pipe-pane, ANSI strip on read. `rg`
|
||||
19. Transcripts: bounded capture — a periodic `tmux capture-pane` snapshot of
|
||||
the trailing lines (default 1000, every 2 s for an active seat) replaces
|
||||
the transcript file, replacing pipe-pane (`transcript-rotation.ts:3`,
|
||||
`:27`–`28`; started at `node-launcher.ts:181`). An idle seat is captured
|
||||
less often, within the 10-second freshness window (`:29`–`33`,
|
||||
`:162`–`163`), and the file is rewritten only when the captured bytes
|
||||
change (`:270`). `readTail` and `grep` strip ANSI; `readFull` returns the
|
||||
content unfiltered. `rig ask` transcript search: `rg`
|
||||
preferred, `grep -E` fallback.
|
||||
20. Config precedence: CLI flag > env var > config file
|
||||
(`~/.openrig/config.json`, with legacy fallback from
|
||||
`~/.rigged/config.json`) > default.
|
||||
(`~/.openrig/config.json`, or `$OPENRIG_HOME/config.json` when
|
||||
`OPENRIG_HOME` is set; `packages/cli/src/config-store.ts:939`) > default.
|
||||
21. Semi-deterministic calibration: build what agents use constantly. Agent
|
||||
handles edge cases from error messages.
|
||||
22. `rig ask` is context engineering: gathers evidence, does NOT call an
|
||||
external LLM. The agent IS the LLM.
|
||||
external LLM. The agent IS the LLM. The exception is the explicit `--wake`
|
||||
flag (`packages/cli/src/commands/ask.ts:88`), which runs one headless
|
||||
question against an existing agent session (`codex exec resume` or
|
||||
`claude -p --resume`, `packages/cli/src/ask-wake.ts:58`–`60`).
|
||||
23. Spec library truth is YAML on disk; daemon owns the structured
|
||||
review/index/cache layer.
|
||||
24. Adopted-session parity is tmux-metadata parity, not fake env-var parity.
|
||||
@@ -84,133 +110,184 @@ they are restated here as system-level invariants.
|
||||
### Startup action constraints
|
||||
|
||||
- No shell startup actions.
|
||||
- Action types are `slash_command` and `send_text` only.
|
||||
- Action types are `slash_command`, `send_text` and `startup_proof` only
|
||||
(`startup-validation.ts:7`).
|
||||
- Non-idempotent actions must not apply on restore.
|
||||
- Retrying failed startup is handled as restore.
|
||||
|
||||
### Remote import constraints
|
||||
|
||||
The reboot supports `local:...` and `path:/abs/...` agent refs. Remote
|
||||
`agent_ref` sources remain unsupported and fail in preflight
|
||||
(`architecture.md` §7 "Remote import constraints"; restated in compat note 1).
|
||||
`agent_ref` sources remain unsupported and fail in preflight (schema
|
||||
validation, `rigspec-schema.ts:563`; restated in compat note 1).
|
||||
|
||||
### Startup, delivery and launch invariants
|
||||
|
||||
These hold across the startup, transport and adapter code and are stated in its
|
||||
comments:
|
||||
|
||||
- **`ready` is not "submitted" and not "oriented".** A seat can reach
|
||||
`startupStatus: ready` while its startup text is unverified or still staged;
|
||||
`node.startup_ready` carries a `submission` block saying which
|
||||
(`types.ts:220`). Orientation is a separate, challenge-verified proof:
|
||||
`ready` never means oriented (`types.ts:222`–`227`). See
|
||||
`agent-spec-and-startup.md`.
|
||||
- **Default sends never block on busy or unknown.** `--wait-for-idle` is the
|
||||
caller's opt-in to wait, and a failed wait returns without sending. Otherwise
|
||||
only positive evidence of an open
|
||||
picker or approval prompt refuses a send; a busy or unknown seat gets the
|
||||
message with an advisory warning (`session-transport.ts:1427`–`1433`). The
|
||||
audited `--dangerously-interact` override is the only way past an open prompt.
|
||||
- **Non-interruptive mode is per-launch flags only.** It never changes
|
||||
permissions or native settings files, applies only to full-bypass Claude Code
|
||||
and Codex seats, and is saved per rig, off by default
|
||||
(`adapters/non-interruptive.ts:9`–`26`; `rigs.non_interruptive`, migration
|
||||
`095`). A rig spec can author it as `non_interruptive` (`rigspec-schema.ts:179`).
|
||||
- **Inside a notify envelope, events are delivered from the log.** Within
|
||||
`withNotifyEnvelope` (`event-bus.ts:106`–`112`), every event persisted through
|
||||
the event bus must be registered before the callback returns; after commit
|
||||
the bus delivers the committed rows from the log, and an unreadable row
|
||||
emits `event.delivery_poisoned`. Legacy callers outside an envelope persist
|
||||
inside their own transaction and notify subscribers explicitly after commit
|
||||
(`persistWithinTransaction`, `:70`; for example `node-launcher.ts:241`).
|
||||
|
||||
## 2. Event system
|
||||
|
||||
The daemon's event surface is the single `RigEvent` discriminated union.
|
||||
|
||||
> Drift-fix D8 / OPEN-4 (carried verbatim, slice-00): `architecture.md` §8
|
||||
> says "PL-004 Phase A adds 9 coordination events" and (in §3) "Existing 32
|
||||
> PL-004 events" / "Existing 20 PL-004 events" — internally inconsistent
|
||||
> contested PL-004 sub-counts. **Do NOT carry the 9 / 32 / 20 figures.** The
|
||||
> `architecture.md` §8 "Currently emitted in production code" / "Present in
|
||||
> the union but not yet emitted" lists also predate 0.3.x and are stale. The
|
||||
> union shape below is re-derived from source at HEAD, not migrated.
|
||||
`RigEvent` is declared at `packages/daemon/src/domain/types.ts:108`
|
||||
(`export type RigEvent =`) and runs through `types.ts:313`. It has **98 union
|
||||
members** declaring **99** `type` literals: one member (`types.ts:109`) carries
|
||||
both `proof.judged` and `proof.sources_changed`.
|
||||
|
||||
`RigEvent` is declared at `packages/daemon/src/domain/types.ts:94`
|
||||
(`export type RigEvent =`) and runs through `types.ts:218`. It has **73
|
||||
union members** (slice-00 §1.8, re-confirmed at HEAD:
|
||||
`grep -cE '^\s*\| \{ type:'` over L94–218 = 73). Every one of the 73
|
||||
declared `type:` literals is also constructed somewhere in
|
||||
`packages/daemon/src/{domain,routes}` (re-verified at HEAD: the set of
|
||||
`type: "<x>"` literals in domain+routes exactly equals the 73 union members
|
||||
— zero union-only-but-never-referenced types).
|
||||
- Members:
|
||||
`sed -n '/^export type RigEvent =/,/^export type PersistedEvent/p' packages/daemon/src/domain/types.ts | grep -c 'type: "'`
|
||||
- Type literals:
|
||||
`sed -n '/^export type RigEvent =/,/^export type PersistedEvent/p' packages/daemon/src/domain/types.ts | grep -oE '"[a-z_]+(\.[a-z_]+)+"' | sort -u | wc -l`
|
||||
|
||||
### Per-prefix event families (grep-verified at HEAD)
|
||||
**96** of the 99 literals appear as a `type: "<x>"` literal somewhere in
|
||||
`packages/daemon/src` outside `types.ts` (mostly in `domain/` and `routes/`;
|
||||
`seat.model_divergence` is built in `startup.ts`). **3** are declared but never
|
||||
constructed: `session.status_changed`, `continuity.sync`, and `continuity.degraded`
|
||||
(per #490, `qitem.closure_overdue` is produced by `QueueRepository.recordClosureOverdue`
|
||||
when swept by `queue-stuck-sweep`, and `mission_control.view_refreshed` was retired).
|
||||
List them with:
|
||||
|
||||
Each count below is a fresh, primary-source-grep-verified, explicitly-labeled
|
||||
per-prefix family count over the union body
|
||||
(`types.ts:94–218`) — NOT the contested PL-004 sub-count (OPEN-4 ruling:
|
||||
labeled grep-verified family counts are ground truth; the `9`/`32`/`20`
|
||||
figures are forbidden).
|
||||
`for t in $(sed -n '/^export type RigEvent =/,/^export type PersistedEvent/p' packages/daemon/src/domain/types.ts | grep -oE '"[a-z_]+(\.[a-z_]+)+"' | tr -d '"'); do git grep -q -F "type: \"$t\"" -- packages/daemon/src ':!packages/daemon/src/domain/types.ts' || echo "$t"; done`
|
||||
|
||||
| Prefix | Members | Sample / role |
|
||||
### Per-prefix event families
|
||||
|
||||
Each count below is the number of `type` literals per prefix in the union body
|
||||
(`types.ts:108`–`313`):
|
||||
`sed -n '/^export type RigEvent =/,/^export type PersistedEvent/p' packages/daemon/src/domain/types.ts | grep -oE '"[a-z_]+(\.[a-z_]+)+"' | tr -d '"' | cut -d. -f1 | sort | uniq -c | sort -rn`.
|
||||
|
||||
| Prefix | Types | Sample / role |
|
||||
|---|---|---|
|
||||
| `node.*` | 7 | `node.added` (`types.ts:97`) … `node.startup_failed` (`:139`) — lifecycle/startup |
|
||||
| `workflow.*` | 6 | PL-004 Phase D workflow runtime (detail in `workflow-runtime.md`) |
|
||||
| `watchdog.*` | 5 | `watchdog.evaluation_fired` (`:187`) … `watchdog.job_stopped` (`:191`) — PL-004 Phase C |
|
||||
| `rig.*` | 5 | `rig.created` / `rig.deleted` / `rig.imported` / `rig.stopped` / `rig.expanded` (`:151`) |
|
||||
| `queue.*` | 5 | PL-004 Phase A queue lifecycle (`:156`–`:159`, `:169`; detail in `coordination-primitive.md`) |
|
||||
| `node.*` | 15 | `node.added` (`types.ts:126`) … `node.startup_proof_rejected` (`:229`) — lifecycle/startup |
|
||||
| `workflow.*` | 8 | workflow runtime (detail in `workflow-runtime.md`) |
|
||||
| `session.*` | 8 | session discovery / status / detach / vanish / stop / clean / resume-token audit |
|
||||
| `rig.*` | 7 | `rig.created` / `rig.deleted` / `rig.imported` / `rig.stopped` / `rig.archived` / `rig.unarchived` / `rig.expanded` (`:241`) |
|
||||
| `watchdog.*` | 5 | `watchdog.evaluation_fired` (`:280`) … `watchdog.job_stopped` (`:284`) |
|
||||
| `seat.*` | 5 | `seat.model_divergence` (`:115`) … `seat.handover_completed` (`:200`) — model divergence, fresh launch, attention clear, handover |
|
||||
| `queue.*` | 5 | queue lifecycle (`:249`–`:252`, `:262`; detail in `coordination-primitive.md`) |
|
||||
| `package.*` | 5 | legacy package/install engine events |
|
||||
| `mission_control.*` | 5 | PL-005 audit/notification (`:212`–`:218`; detail in `mission-control.md`) |
|
||||
| `mission_control.*` | 4 | audit/notification (`:308`–`:313`; detail in `mission-control.md`) |
|
||||
| `bootstrap.*` | 5 | legacy bootstrap-run events |
|
||||
| `session.*` | 4 | session discovery / status / detach / vanish |
|
||||
| `classifier.*` | 4 | PL-004 Phase B classifier-lease lifecycle |
|
||||
| `restore.*` | 3 | restore start/complete/reconcile (detail in `lifecycle-snapshot-restore.md`) |
|
||||
| `qitem.*` | 2 | `qitem.fallback_routed` (`:160`), `qitem.closure_overdue` (`:161`) |
|
||||
| `pod.*` | 2 | `pod.created` (`:135`), `pod.deleted` (`:136`) |
|
||||
| `inbox.*` | 2 | `inbox.absorbed` (`:162`), `inbox.denied` (`:163`) |
|
||||
| `continuity.*` | 2 | `continuity.sync` (`:140`), `continuity.degraded` (`:141`) |
|
||||
| singletons | 11 | one member each: `workflow_spec.*`, `view.*`, `stream.*` (`:155`), `snapshot.*`, `seat.*`, `project.*`, `kernel.*`, `chat.*` (`:149`), `bundle.*`, `binding.*`, `agent.*` |
|
||||
| `restore.*` | 4 | restore start/complete/subset-complete/reconcile (detail in `lifecycle-snapshot-restore.md`) |
|
||||
| `classifier.*` | 4 | classifier-lease lifecycle |
|
||||
| `qitem.*` | 2 | `qitem.fallback_routed` (`:253`), `qitem.closure_overdue` (`:254`) |
|
||||
| `proof.*` | 2 | `proof.judged`, `proof.sources_changed` — one union member (`:109`) |
|
||||
| `pod.*` | 2 | `pod.created` (`:215`), `pod.deleted` (`:216`) |
|
||||
| `inbox.*` | 2 | `inbox.absorbed` (`:255`), `inbox.denied` (`:256`) |
|
||||
| `continuity.*` | 2 | `continuity.sync` (`:230`), `continuity.degraded` (`:231`) |
|
||||
| `agent.*` | 2 | `agent.activity` (`:151`), `agent.session_identity` (`:158`) |
|
||||
| singletons | 12 | one type each: `workflow_spec.*`, `view.*`, `transport.*` (`:155`), `topology.*` (`:138`), `stream.*` (`:245`), `snapshot.*`, `project.*`, `kernel.*`, `event.*` (`:110`), `chat.*` (`:239`), `bundle.*`, `binding.*` |
|
||||
|
||||
Family counts sum to 73 (15 multi-member families totalling 62 + 11
|
||||
singletons), re-confirmed at HEAD.
|
||||
Family counts sum to 99 type literals (18 multi-type families totalling 87 +
|
||||
12 singletons).
|
||||
|
||||
### Emission and delivery
|
||||
|
||||
Events are constructed and emitted via `eventBus.emit({ type: ... })` across
|
||||
domain services (`stream-store.ts`, `workflow-runtime.ts`,
|
||||
`restore-orchestrator.ts`, `node-launcher.ts`, etc.) and route handlers. The
|
||||
event log is append-only and SQLite-backed.
|
||||
Events are emitted via `eventBus.emit({ type: ... })` (`event-bus.ts:57`) or,
|
||||
inside a caller-managed transaction, `eventBus.persistWithinTransaction(...)`
|
||||
(`event-bus.ts:70`) with subscribers notified after commit — across domain
|
||||
services (`stream-store.ts`, `workflow-runtime.ts`, `restore-orchestrator.ts`,
|
||||
`node-launcher.ts`, etc.) and route handlers. The event log is append-only and
|
||||
SQLite-backed.
|
||||
|
||||
Three SSE delivery surfaces (re-confirmed at HEAD):
|
||||
SSE delivery surfaces include the following. The daemon has **11**
|
||||
`streamSSE(` call sites across **10** files in `packages/daemon/src/routes/`
|
||||
(`git grep -o 'streamSSE(' -- packages/daemon/src/routes | wc -l`;
|
||||
`git grep -l 'streamSSE(' -- packages/daemon/src/routes | wc -l`).
|
||||
|
||||
- `GET /api/events` — global stream of all events (`server.ts:457`
|
||||
- `GET /api/events` — global stream of all events (`server.ts:730`
|
||||
`app.route("/api/events", eventsRoute)`).
|
||||
- `GET /api/stream/watch` — new stream items (`routes/stream.ts:117`).
|
||||
- `GET /api/stream/watch` — new stream items (`routes/stream.ts:194`).
|
||||
- `GET /api/queue/watch` — queue/inbox coordination events
|
||||
(`routes/queue.ts:357`).
|
||||
(`routes/queue.ts:1054`, with a `/sse` alias at `:1055`).
|
||||
- The chat SSE stream `GET /api/rigs/:rigId/chat/watch` delivers
|
||||
`chat.message` for one rig (rig-scoped; see compat note 6).
|
||||
`chat.message` for one rig (`routes/chat.ts:82`, mounted at `server.ts:782`;
|
||||
rig-scoped; see compat note 6).
|
||||
- The other SSE routes are activity events and the watch or `sse` routes for
|
||||
mission control, projects, views, watchdog and workflow; list them with
|
||||
`git grep -n 'streamSSE(' -- packages/daemon/src/routes`.
|
||||
|
||||
> OPEN-4 (carried verbatim, slice-00): the precise PL-004-only vs PL-005-only
|
||||
> sub-partition is NOT asserted here. `architecture.md`'s `32`/`20`/`9`
|
||||
> figures are internally inconsistent and not reconcilable without
|
||||
> classifying all 73 members by introducing slice — flagged, not smoothed.
|
||||
> The grep-verified per-prefix family counts above are the substituted ground
|
||||
> truth.
|
||||
### Telemetry read surface
|
||||
|
||||
`/api/telemetry` (`server.ts:822`; `routes/telemetry.ts`) is a read-only,
|
||||
paged view over the event log and queue history, built by
|
||||
`readTelemetryPage()` in `packages/daemon/src/domain/finite-telemetry.ts`. It
|
||||
serves three streams: `GET /api/telemetry/v1/events`, `/v1/queue-transitions`
|
||||
(active and archived transitions) and `/v1/nodes/:nodeId/tenures` (a node's
|
||||
occupant tenures). Each page is one read with no writes or subscription,
|
||||
bounded at 128 rows and 128 KiB (`TELEMETRY_LIMITS`), and resumed with a
|
||||
cursor. Rows carry metadata only, never event payloads, notes or bodies, and
|
||||
the response names gaps in the history it could not vouch for (for example a
|
||||
daemon restart boundary or rows before the retained floor) instead of
|
||||
presenting a partial history as complete. The page records the source host and
|
||||
the daemon's boot epoch. Bad input returns `400 telemetry_invalid_request`; a
|
||||
failed read returns `503 telemetry_read_unavailable`. The same route also
|
||||
serves the usage series behind `rig usage` (`/usage/series`, `/usage/top`).
|
||||
`rig telemetry events|transitions|tenures`
|
||||
(`packages/cli/src/commands/telemetry.ts`) prints one bounded page.
|
||||
|
||||
## 3. Remaining compatibility notes
|
||||
|
||||
Intentional limits that still describe the shipped system (`architecture.md`
|
||||
§11), verified as still-current at HEAD:
|
||||
Intentional limits that still describe the shipped system:
|
||||
|
||||
1. Remote `agent_ref` imports remain unsupported (see §1 remote import
|
||||
constraints).
|
||||
2. Startup actions remain intentionally constrained (`slash_command`,
|
||||
`send_text`).
|
||||
`send_text`, `startup_proof`).
|
||||
3. Legacy compatibility seams still ship for pre-reboot data and v1
|
||||
artifacts.
|
||||
4. `rig ask` gathers context only — does not call an external LLM (rule 22).
|
||||
5. Transcript search prefers `rg`, falls back to `grep -E`; quality/perf
|
||||
varies by backend.
|
||||
4. `rig ask` gathers context only — does not call an external LLM — unless
|
||||
`--wake` is passed (rule 22).
|
||||
5. `rig ask` transcript search prefers `rg`, falls back to `grep -E`
|
||||
(`history-query.ts:252`, `:267`); quality/perf varies by backend.
|
||||
6. Chat is rig-scoped only — no cross-rig channels or DMs.
|
||||
7. `--verify` on `rig send` checks pane content for message visibility but
|
||||
can produce false positives from pre-existing matching content. Known
|
||||
limitation.
|
||||
7. `--verify` on `rig send` checks pane content for message visibility, not
|
||||
agent acknowledgement: it compares occurrences of the message's first 40
|
||||
characters before and after the send (`session-transport.ts:1605`–`1608`).
|
||||
8. Terminal node readiness is shell-ready only — no service health probes.
|
||||
9. `rig env down --volumes` exists in the CLI surface, but the explicit
|
||||
daemon-side override is not fully plumbed through yet.
|
||||
10. Managed-app service surfaces are descriptive only — OpenRig does not
|
||||
auto-inject service URLs/tokens into agent prompts beyond authored
|
||||
startup/context files.
|
||||
11. Specialist delegation is conventional, not automatic — addressed by
|
||||
9. Managed-app service surfaces are descriptive only — OpenRig does not
|
||||
auto-inject service URLs/tokens into agent prompts beyond authored
|
||||
startup/context files.
|
||||
10. Specialist delegation is conventional, not automatic — addressed by
|
||||
session name or normal communication surfaces.
|
||||
|
||||
## 4. Cross-references
|
||||
|
||||
`architecture.md` §12 names itself the architecture-level source of truth and
|
||||
points at `codemap.md` for file-by-file structure. Under the modular
|
||||
as-built, that role is distributed: this module owns the invariants + event
|
||||
shape; the source-of-truth pointer is the rewritten `../codemap.md`
|
||||
navigation index.
|
||||
Under the modular as-built, this module owns the invariants and event shape;
|
||||
`../codemap.md` is the navigation index for file-by-file structure.
|
||||
|
||||
## See also
|
||||
|
||||
- `daemon-core.md` — wiring, DB, migrations, startup; footprint drift-fixes.
|
||||
- `coordination-primitive.md` — PL-004 Phase A queue/stream/inbox/outbox
|
||||
events.
|
||||
- `workflow-runtime.md` — PL-004 Phase D `workflow.*` events.
|
||||
- `mission-control.md` — PL-005 `mission_control.*` events.
|
||||
- `daemon-core.md` — wiring, DB, migrations, startup.
|
||||
- `coordination-primitive.md` — queue/stream/inbox/outbox events.
|
||||
- `workflow-runtime.md` — `workflow.*` events.
|
||||
- `mission-control.md` — `mission_control.*` events.
|
||||
- Source roots: `packages/daemon/src/domain/types.ts` (RigEvent union),
|
||||
`packages/daemon/src/domain/event-bus.ts` (emit/persist),
|
||||
`packages/daemon/src/routes/{stream,queue}.ts` (SSE watch),
|
||||
`packages/daemon/src/server.ts` (`/api/events`).
|
||||
@@ -1,320 +1,191 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: Content Surfaces — Files Browser, Atomic Write, Progress Tree, Steering Composer
|
||||
title: Content Surfaces — Files, Progress, Steering, and Health
|
||||
status: active
|
||||
topics: [observability, specification-and-bundles]
|
||||
domains: [engineering-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
Need to know how the operator-allowlisted file browser enforces path
|
||||
safety, how atomic conflict-checked writes + the JSONL edit audit work,
|
||||
how the workspace PROGRESS.md tree is indexed, or how the one-screen
|
||||
Steering surface (priority stack + roadmap rail + lane rails + health
|
||||
gates) is composed. Author-mode module — no prior architecture.md prose;
|
||||
every load-bearing claim is sourced to file:line at HEAD.
|
||||
siblings: [workspace-primitive.md, daemon-core.md, ../ui/project-and-for-you.md]
|
||||
prerequisite-reads: [../README.md, workspace-primitive.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
Tracing allowlisted file access, conflict-checked writes, progress indexing,
|
||||
steering composition, or the daemon health-summary endpoints.
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Content Surfaces — Files Browser, Atomic Write, Progress Tree, Steering Composer
|
||||
# Content Surfaces — Files, Progress, Steering, and Health
|
||||
|
||||
> **See also (v0.4.4):** the Living Notes review surface — the composed
|
||||
> intent→plan→delivered projection over this same allowlisted content layer —
|
||||
> is documented in [`living-notes-review.md`](living-notes-review.md).
|
||||
Source snapshot: `e8f0ab340db773392ec8be75b072d1c0f3068a50`. This describes the source at that commit;
|
||||
it does not establish the version or behavior of a running daemon.
|
||||
|
||||
The daemon exposes filesystem content through the files, progress, and
|
||||
steering route families. Health summaries read existing daemon state. These
|
||||
are separate projections with separate configuration; configuring one root
|
||||
is not a universal filesystem-access policy.
|
||||
|
||||
The **content surfaces** are the operator-allowlisted, filesystem-canonical
|
||||
read/write layer that the Project / Steering UI sits on: a fail-closed file
|
||||
browser, an atomic conflict-checked write service with a JSONL edit audit,
|
||||
the recursive PROGRESS.md tree indexer, and the one-screen Steering composer
|
||||
(+ its compact health-summary gates). None adds SQLite state.
|
||||
## Configuration and wiring
|
||||
|
||||
> **AUTHOR-FROM-SOURCE module.** No prior `architecture.md` prose exists for
|
||||
> this. Every load-bearing claim carries `> Source: <file:line> @HEAD`;
|
||||
> ambiguity is declared as an OPEN item, never smoothed. Paths are relative
|
||||
> to `packages/daemon/src/` unless prefixed `packages/` or `docs/`.
|
||||
>
|
||||
> Verified at HEAD `7eaf524c` (`git describe` → `v0.3.1-6-g7eaf524c`).
|
||||
> Package version **0.3.1**; HEAD carries 6 unreleased release-0.3.2
|
||||
> commits; no `v0.3.2` tag (daemon-core.md; slice-00 §1.1).
|
||||
>
|
||||
> **SPLIT NOTE (§10.6).** This is the content-surfaces half of the
|
||||
> `workspace-and-content-primitives` SPLIT (sibling `workspace-primitive.md`
|
||||
> carries the workspace primitive + migrations 038/039 +
|
||||
> missions/projects/slices). See `workspace-primitive.md` § header SPLIT
|
||||
> NOTE for the §10.6 rationale and the transparent-noted-deviation
|
||||
> disclosure.
|
||||
[`startup.ts`](../../../packages/daemon/src/startup.ts) constructs
|
||||
`SettingsStore`, decodes `cfg.filesAllowlistRaw` and `cfg.progressScanRootsRaw`,
|
||||
and injects `FileWriteService`, `ProgressIndexer`, and `SteeringComposer`.
|
||||
The write service exists only when the decoded file allowlist is nonempty.
|
||||
Its audit path is explicitly `${OPENRIG_HOME}/file-edit-audit.jsonl`.
|
||||
|
||||
## 0. Release attribution (forensic proof — read first)
|
||||
[`SettingsStore`](../../../packages/daemon/src/domain/user-settings/settings-store.ts)
|
||||
resolves environment overrides, then settings-file values, then defaults.
|
||||
`files.allowlist` and `progress.scan_roots` both default to
|
||||
`workspace:<resolved-workspace-root>`. Their environment overrides are
|
||||
`OPENRIG_FILES_ALLOWLIST` and `OPENRIG_PROGRESS_SCAN_ROOTS`. Thus, an unset
|
||||
or empty environment variable, or an empty settings-file value, does **not**
|
||||
imply an empty allowlist in normal daemon startup. A non-empty value is not
|
||||
validated: the decoders silently skip malformed entries (no `name:` prefix, or
|
||||
a relative path), so a malformed value can leave no configured roots. Some source comments and route hints still say
|
||||
otherwise or point only at the environment variable. The standalone `readAllowlistFromEnv` helper has different inputs
|
||||
and retains its legacy environment fallback.
|
||||
|
||||
Per §10.8, re-verified at HEAD via `git cat-file -e <tag>:<path>`:
|
||||
Roots use comma-separated `name:/absolute/path` pairs. The decoders skip
|
||||
invalid pairs, canonicalize existing paths, and let the last duplicate name
|
||||
win. See [`decodeAllowlist`](../../../packages/daemon/src/domain/files/path-safety.ts)
|
||||
and [`decodeProgressScanRoots`](../../../packages/daemon/src/domain/progress/progress-indexer.ts).
|
||||
|
||||
| Subsystem | Release | Forensic proof @HEAD |
|
||||
|---|---|---|
|
||||
| `domain/files/{path-safety,file-write-service}.ts` | **≤0.3.0** | `v0.3.0:<path>` → present |
|
||||
| `domain/progress/progress-indexer.ts` | **≤0.3.0** | `v0.3.0:<path>` → present |
|
||||
| `domain/steering/{steering-composer,health-summary}.ts` | **≤0.3.0** | `v0.3.0:<path>` → present |
|
||||
| `routes/{files,progress,steering,health-summary}.ts` | **≤0.3.0** | `v0.3.0:<path>` → present |
|
||||
## Path resolution and reads
|
||||
|
||||
> Source: §10.8 proof re-run at HEAD `7eaf524c` — every listed path
|
||||
> resolves at `v0.3.0` (no failures). All content surfaces are
|
||||
> **≤0.3.0** features (UI Enhancement Pack v0 + Operator Surface
|
||||
> Reconciliation v0; slice-00 0.3.0-GT §1.7 "Files / Markdown /
|
||||
> Progress / Proofs" confirms the cluster shipped 0.3.0). Do **not**
|
||||
> back-attribute any of these to 0.3.1 — the gap is missing
|
||||
> architecture.md prose, not a new feature.
|
||||
[`resolveAllowedPath`](../../../packages/daemon/src/domain/files/path-safety.ts)
|
||||
requires a configured root, rejects `..` path segments and absolute relative
|
||||
paths, resolves existing symlinks, and checks containment with a path-separator
|
||||
boundary. An empty relative path denotes the root. If realpath fails, the
|
||||
helper falls back to the resolved candidate; file/directory helpers then
|
||||
perform a stat check. These are the implemented checks, not a claim of
|
||||
race-free isolation against concurrent filesystem changes.
|
||||
|
||||
## 1. Path safety — the fail-closed allowlist (UI Enhancement Pack v0)
|
||||
[`readAllowedFile`](../../../packages/daemon/src/domain/files/file-read.ts)
|
||||
is shared by the HTTP reader and the local TUI reader. It returns content,
|
||||
absolute and root-relative resolved paths, mtime, full-file SHA-256, size,
|
||||
binary classification, and truncation metadata. The returned text is capped
|
||||
at **1,048,576 bytes**, but the full file is read and hashed. The cap limits
|
||||
the response, not the memory required to read the file. The TUI's local
|
||||
reader runs as a CLI subprocess without the daemon: it resolves
|
||||
`files.allowlist` from the CLI configuration, offers four fixed workspace
|
||||
targets as roots, and hides dot-prefixed entries in listings
|
||||
(`packages/cli/src/local-reading.ts`).
|
||||
|
||||
The file browser is gated by a daemon-enforced, **fail-closed** allowlist.
|
||||
Roots are decoded from `OPENRIG_FILES_ALLOWLIST` (env), comma-separated
|
||||
`<name>:<absolute-path>` pairs (legacy fallback `RIGGED_FILES_ALLOWLIST`
|
||||
via `||` so an empty string falls through). Invalid pairs (no colon, empty
|
||||
name, non-absolute path) are **silently skipped**; duplicate names →
|
||||
last-wins. Empty/unset → no roots → routes return an empty list with a
|
||||
structured "configure OPENRIG_FILES_ALLOWLIST" hint. The safe default after
|
||||
a fresh install is **nothing allowlisted**.
|
||||
## Writes and audit
|
||||
|
||||
> Source: `domain/files/path-safety.ts:50-51` (env vars),
|
||||
> `:60-93` (`decodeAllowlist` / `readAllowlistFromEnv`; silent-skip
|
||||
> L67-71, last-wins L78, `||` fallback comment L89-91) @HEAD.
|
||||
[`FileWriteService.writeAtomic`](../../../packages/daemon/src/domain/files/file-write-service.ts)
|
||||
reads the existing target and compares **both** the expected mtime and content
|
||||
hash. A mismatch raises `WriteConflictError` with the current values. It then
|
||||
writes a temporary file in the same directory, preserves the target's
|
||||
ordinary permission bits, fsyncs the temporary file, and renames it over the
|
||||
target. This is optimistic conflict detection; it does not hold a lock over
|
||||
the read/compare/replace sequence.
|
||||
|
||||
`resolveAllowedPath(allowlist, rootName, relativePath)` is the load-bearing
|
||||
guard. Algorithm: (1) reject unknown root (`root_unknown`); (2) reject
|
||||
expressed-intent `..` segments **before** filesystem resolution
|
||||
(`path_escape`) — segment-split so `foo..bar` is not a false positive;
|
||||
(3) reject an absolute `relativePath` (`path_invalid`); (4) resolve
|
||||
symlinks via `fs.realpathSync` and reject when the realpath does not start
|
||||
with `<canonicalRoot><sep>` (`path_escape`) — a `path.sep` boundary so
|
||||
`/foo/bar` does not match `/foo/bar-other`; (5) `path === ""` resolves to
|
||||
the root itself (so callers can list the root). Symlinks **inside** the
|
||||
tree resolve normally; symlinks pointing **outside** are escape attempts.
|
||||
A non-existent candidate falls back to the unresolved path so the
|
||||
subsequent stat surfaces the absence with a specific code.
|
||||
`resolveAllowedFile` / `resolveAllowedDirectory` add a stat assertion
|
||||
(`not_a_file` / `not_a_directory`).
|
||||
`createAtomic` handles new files. It creates and fsyncs a temporary file, then
|
||||
uses an exclusive hard link to establish the target. If the target already
|
||||
exists, it returns the `target_exists` error instead of overwriting it.
|
||||
`createAtomic` is used by the Living Notes freeze export, and `writeAtomic`
|
||||
also backs the freeze-time `MISSION_BRIEF.md` spine update; both append to the
|
||||
same audit file.
|
||||
|
||||
> Source: `domain/files/path-safety.ts:95-171` (`resolveAllowedPath`;
|
||||
> `..`-before-fs L124-140, absolute-reject L141-147, sep-boundary
|
||||
> L158-169, empty-path base case in the doc-comment L108-110),
|
||||
> `:173-225` (file/dir convenience asserts) @HEAD.
|
||||
Both operations append a JSONL audit record containing actor/provenance,
|
||||
paths, timestamps, hashes, and byte-count delta. An audit append failure
|
||||
raises `audit_write_failed` **after the content has landed**; the write is not
|
||||
rolled back. The service appends records without implementing rotation.
|
||||
The service's standalone default is under the user's `.openrig` directory;
|
||||
daemon startup overrides it with the active OpenRig home as described above.
|
||||
|
||||
## 2. Atomic write + JSONL audit (UI Enhancement Pack v0 item 4)
|
||||
## HTTP surface
|
||||
|
||||
`FileWriteService.writeAtomic(req)` is the operator-actionable write surface
|
||||
for `STEERING.md` / `PROGRESS.md` / spec YAML / any allowlisted file.
|
||||
Sequence: (1) resolve target under allowlist (reuses §1 path-safety);
|
||||
(2) re-stat + re-hash the target — if `mtime !== expectedMtime` OR
|
||||
`contentHash !== expectedContentHash`, throw `WriteConflictError` carrying
|
||||
the **current** mtime + hash for the UI to surface (optimistic-concurrency,
|
||||
not a lock); (3) write to a temp file in the **same directory** (PID +
|
||||
random suffix so concurrent writes don't collide); (4) `fsync` the temp fd
|
||||
for durability before the rename; (5) atomic `renameSync` over the target
|
||||
(single inode swap on POSIX — readers see old or new, never partial);
|
||||
(6) recompute mtime + hash + byte delta; (7) append one JSONL audit row.
|
||||
[`server.ts`](../../../packages/daemon/src/server.ts) mounts these families.
|
||||
All of them sit behind the daemon's `/api/*` browser boundary
|
||||
(`middleware/browser-boundary.ts`), which accepts only known host names, and
|
||||
browser `Origin`s only from the daemon's own UI or `OPENRIG_ALLOWED_ORIGINS`;
|
||||
the file allowlist is a separate check.
|
||||
[`filesRoutes`](../../../packages/daemon/src/routes/files.ts) implements:
|
||||
|
||||
> Source: `domain/files/file-write-service.ts:9-25` (documented
|
||||
> atomic-write semantics), `:111-212` (`writeAtomic`; conflict-detect
|
||||
> L129-135, same-dir temp L137-141, fsync L146, atomic rename L161)
|
||||
> @HEAD.
|
||||
| Method and path | Result / important behavior |
|
||||
|---|---|
|
||||
| `GET /api/files/roots` | Configured roots; an empty list carries a setup hint. |
|
||||
| `GET /api/files/list?root=…&path=…` | Directory entries with metadata, including dotfiles. |
|
||||
| `GET /api/files/read?root=…&path=…` | `readAllowedFile` result. |
|
||||
| `GET /api/files/asset?root=…&path=…` | Raw asset; single byte-range support (`206`, invalid range `416`), five-minute cache. HTML and source files default to plain text and unknown types to `application/octet-stream`; `render=1` opts into HTML rendering. SVG is served as `image/svg+xml`. A `render=1` HTML preview or an SVG is served with no content-security or frame headers, so it can run script in the daemon's origin (`routes/files.ts:153–157`). |
|
||||
| `POST /api/files/write` | Requires root, path, string content, expected mtime and hash. Stale reads return `409 write_conflict`; an absent write service returns `503`. |
|
||||
|
||||
The audit file is `~/.openrig/file-edit-audit.jsonl` by default
|
||||
(`HOME`/`USERPROFILE` → `/tmp` fallback). **Append-only; never rotates at
|
||||
v0.** A failed audit append throws `FileWriteError("audit_write_failed")`
|
||||
but does **NOT** undo the landed write — the user's edit succeeded; an
|
||||
audit-system flake must not silently revert canon. (The route still
|
||||
surfaces it as a 500 so the failure is honest, not swallowed.)
|
||||
Writes resolve actor/provenance through `resolveActorWithDeferral`. Missing
|
||||
route dependencies return `503`; unknown roots and invalid paths return
|
||||
`400`. A failed stat returns `404` on reads, lists and assets; on a write, a
|
||||
missing or unreadable target returns `500 stat_failed`, because
|
||||
`POST /api/files/write` only replaces existing files
|
||||
(`domain/files/file-write-service.ts:120–128`; `routes/files.ts:264–265`). An
|
||||
audit failure can return `500` even though the file write succeeded, so an
|
||||
error is not proof of no effect.
|
||||
|
||||
> Source: `domain/files/file-write-service.ts:87-91` (default path +
|
||||
> fallback), `:24-25` (never-rotates-at-v0), `:182-204` (audit append;
|
||||
> "does NOT undo the write" comment L183-184) @HEAD.
|
||||
## Progress indexing
|
||||
|
||||
## 3. Files routes — browse + read + asset + write
|
||||
[`ProgressIndexer.scan`](../../../packages/daemon/src/domain/progress/progress-indexer.ts)
|
||||
walks configured roots on each request, to a default maximum depth of **6**.
|
||||
It skips dot-prefixed entries and named build/dependency directories, and
|
||||
collects regular `PROGRESS.md` and `STEERING.md` files. Unreadable directories
|
||||
and files are skipped. Consequently, an empty scan does not prove all files
|
||||
were readable.
|
||||
|
||||
`filesRoutes()` (mounted `server.ts:507` as `/api/files`). All routes are
|
||||
**literal** (no `/:param` catchall) so order doesn't matter for shadowing
|
||||
(Phase A R1 SSE lesson):
|
||||
The parser skips frontmatter, takes the first H1 as the title, turns
|
||||
H2–H4 headings into hierarchy rows, and reads checkbox rows as `[x]`/`[X]`
|
||||
= done, `[~]` = blocked, and `[ ]` = active. Checkbox depth uses two-space
|
||||
indentation; headings use heading depth. Per-file and aggregate counts cover
|
||||
checkbox rows. This is a progress-text projection, not proof readiness.
|
||||
|
||||
- `GET /roots` — allowlist root list (or `{roots:[], hint}` when none).
|
||||
- `GET /list?root&path` — directory entries (dirs first, then files,
|
||||
name-sorted; dotfiles always included — allowlisting a root expresses
|
||||
inspection intent).
|
||||
- `GET /read?root&path` — file content + `mtime` + `contentHash` (SHA-256)
|
||||
+ `size`. **Content is capped at `FILE_READ_TRUNCATION_BYTES` (1 MB)** but
|
||||
the hash is computed over the **FULL** file so atomic-write conflict
|
||||
detection stays honest even on a truncated read; response carries
|
||||
`truncated` / `truncatedAtBytes` / `totalBytes`.
|
||||
- `GET /asset?root&path` — raw bytes for embedded images/video/pdf (inferred
|
||||
`Content-Type`, 5-min cache). **v0.4.4 (OPR.0.4.4.20 FR-5):** serves HTTP
|
||||
**byte ranges** — single-range form, `206` + `Content-Range` +
|
||||
`Accept-Ranges: bytes` (malformed/unsatisfiable → `416`) — the
|
||||
iOS-Safari-class media-playback requirement; and `.html` renders as
|
||||
`text/html` ONLY under the explicit `?render=1` opt-in (text/plain stays
|
||||
the default). The slice proof-asset route (`/api/slices/:name/proof-asset/*`)
|
||||
carries the same range semantics (living-notes corrective). See
|
||||
[`living-notes-review.md`](living-notes-review.md) §5.
|
||||
- `POST /write` — atomic write (§2); 503 when no write service
|
||||
(`OPENRIG_FILES_ALLOWLIST` empty); 409 `write_conflict` on stale
|
||||
mtime/hash with the current values for the UI's refresh prompt.
|
||||
[`GET /api/progress/tree`](../../../packages/daemon/src/routes/progress.ts)
|
||||
returns the scan. Missing indexer or empty configured roots returns `503`.
|
||||
|
||||
503 `files_routes_unavailable` when the allowlist dep is unwired.
|
||||
Path-safety errors map to HTTP per code (`root_unknown`/`path_*` → 400,
|
||||
`stat_failed` → 404).
|
||||
## Steering composition
|
||||
|
||||
> Source: `routes/files.ts:51-242` (handlers; route-order comment
|
||||
> L17-19, truncation L139-154, write 503/409 L187-191/L225-231,
|
||||
> path-safety→HTTP `:61-69`); `FILE_READ_TRUNCATION_BYTES = 1_048_576`
|
||||
> `:49`; mounted `server.ts:507`; CLI: no `rig files` verb — files are
|
||||
> a UI-only surface @HEAD.
|
||||
[`steeringOptsFromSettings`](../../../packages/daemon/src/domain/steering/steering-composer.ts)
|
||||
combines resolved workspace settings with `OPENRIG_STEERING_WORKSPACE`,
|
||||
`OPENRIG_STEERING_PATH`, `OPENRIG_ROADMAP_PATH`, and
|
||||
`OPENRIG_DELIVERY_READY_DIR` overrides. `SteeringComposer.compose` reads:
|
||||
|
||||
> **§10.7 route-reality (source-verified at `packages/ui/src/routes.tsx`):**
|
||||
> `/files` is a **REAL route** (`FilesWorkspace`) the Phase-8.1 survey
|
||||
> MISSED. There is **NO `/markdown` route** — markdown rendering is a
|
||||
> *component* inside file / drawer surfaces, not a navigable destination.
|
||||
>
|
||||
> Source: `packages/ui/src/routes.tsx:192-195` (`path: "/files"` +
|
||||
> `FilesWorkspace`); grep-confirmed no `path: "/markdown"` declaration
|
||||
> anywhere in routes.tsx @HEAD.
|
||||
| Field | Source and selection |
|
||||
|---|---|
|
||||
| `priorityStack` | Verbatim `STEERING.md`, mtime, and byte count. |
|
||||
| `roadmapRail` | `roadmap/PROGRESS.md` `[ ]`/`[x]` rows (`[~]` rows are not read); the first unchecked row is marked next. |
|
||||
| `laneRails` | Every `PROGRESS.md` and `STEERING.md` under `delivery-ready`, using a depth-3 `ProgressIndexer` (lane ID `mode-N` for `mode-N/PROGRESS.md`, else the relative path); first non-done/non-blocked checkbox is next pull. Default top 3 prefers non-done rows, then fills with done rows. |
|
||||
|
||||
## 4. Progress tree indexer (UI Enhancement Pack v0 item 1B)
|
||||
Missing/unreadable sections become `unavailableSources`. `isReady` means at
|
||||
least one path resolves; it does not guarantee that every read succeeds.
|
||||
[`GET /api/steering`](../../../packages/daemon/src/routes/steering.ts)
|
||||
returns `503` when the composer is absent or no source path resolves. Because
|
||||
`workspace.steering_path` always has a value (default
|
||||
`<workspace.root>/STEERING.md`), the daemon's composer is normally ready, and
|
||||
a missing `STEERING.md` appears as a `priorityStack` read failure in
|
||||
`unavailableSources` with a `200`, not a `503`.
|
||||
Queue and health state are fetched through their own endpoints.
|
||||
|
||||
`ProgressIndexer` walks operator-allowlisted scan roots
|
||||
(`OPENRIG_PROGRESS_SCAN_ROOTS`, same `<name>:<abs-path>` shape as the file
|
||||
allowlist; legacy `RIGGED_PROGRESS_SCAN_ROOTS`), finds **`PROGRESS.md` AND
|
||||
`STEERING.md`** files (STEERING.md is anchored as the constraint-frame node
|
||||
per OSR v0 Item 2; the row-machinery is filename-agnostic), and parses each
|
||||
into a checkbox-hierarchy tree. Recursion is depth-bounded (default 6 —
|
||||
enough for mission/lane/slice nesting without descending into
|
||||
`node_modules`/`.git`/`.worktrees`/`dist`/`build`/`.turbo`/`.next` or
|
||||
dotfiles). Checkbox status: `[x]` → `done`, `[~]` → `blocked`, `[ ]` →
|
||||
`active`; headings (`##`–`####`) become hierarchy rows; depth from
|
||||
2-space indent or heading level. Per-file `counts` + a scan-wide
|
||||
`aggregate`. **In-memory walk per request; no caching at v0** (bounded by
|
||||
the operator's allowlist scope).
|
||||
## Health summaries
|
||||
|
||||
> Source: `domain/progress/progress-indexer.ts:79-84` (STEERING anchor +
|
||||
> env vars), `:81` (SKIP_DIRS), `:71` (`DEFAULT_MAX_DEPTH = 6`),
|
||||
> `:218-235` (checkbox status mapping), `:204-215` (heading rows),
|
||||
> `:21-23` (no-caching-at-v0) @HEAD.
|
||||
[`healthSummaryRoutes`](../../../packages/daemon/src/routes/health-summary.ts)
|
||||
exposes four reads under `/api/health-summary`:
|
||||
|
||||
`progressRoutes()` (mounted `server.ts:508` as `/api/progress`):
|
||||
`GET /tree` — the indexed hierarchy; 503 `progress_indexer_unavailable`
|
||||
when unwired; 503 `progress_scan_roots_not_configured` + setup hint when
|
||||
no roots.
|
||||
| Suffix | Source |
|
||||
|---|---|
|
||||
| `/nodes` | `computeNodeHealthSummary`: node inventory aggregated across rigs, grouped by session/lifecycle status, with an attention-required count. |
|
||||
| `/context` | `computeContextHealthSummary`: `context_usage` rows grouped by urgency and sample freshness. |
|
||||
| `/version` | `getDaemonVersion`: the build-stamped version, else the daemon package version, else `unknown`; always `200`. |
|
||||
| `/gateway` | The injected gateway subsystem's `status()`; `503` if unavailable. |
|
||||
|
||||
> Source: `routes/progress.ts:18-40` (handler; 503s L29-35); mounted
|
||||
> `server.ts:508` @HEAD.
|
||||
`/nodes` and `/context` return `503 health_summary_unavailable` when the rig
|
||||
repository is not wired.
|
||||
|
||||
> **§10.7 route-reality:** `/progress` is a **`<Navigate to="/project" />`
|
||||
> redirect-stub** — the progress system lives at the daemon
|
||||
> `/api/progress` route, but the UI URL folds into Project tabs (Phase 3).
|
||||
>
|
||||
> Source: `packages/ui/src/routes.tsx:464-468` ("folds into Project tabs
|
||||
> (Phase 3) — redirect to /project") @HEAD.
|
||||
The [context aggregation](../../../packages/daemon/src/domain/steering/health-summary.ts)
|
||||
uses **80%** for critical, **60%** for warning, and **300 seconds** for
|
||||
freshness. These are constants in this module: they do not follow the
|
||||
`health.context_pressure.*` settings (defaults 95% and 99%) that the health
|
||||
detectors use, so the two surfaces can disagree about the same session. A missing usage value is unknown; a missing timestamp has no
|
||||
freshness sample. The SQL read catches errors and falls back to an empty
|
||||
sample set, so zero counts alone are not a database-health guarantee.
|
||||
|
||||
## 5. Steering composer (Operator Surface Reconciliation v0 item 1)
|
||||
## Related source
|
||||
|
||||
`SteeringComposer.compose()` is the **one-screen composed steering surface**.
|
||||
It is deliberately narrow: it composes only the **filesystem-derived**
|
||||
pieces; the UI fetches PL-005 queue views (in-motion / loop-state) and
|
||||
health gates via their own existing endpoints so the composer stays
|
||||
testable. Three sources, all resolved from a single workspace root
|
||||
(`OPENRIG_STEERING_WORKSPACE`; per-piece overrides
|
||||
`OPENRIG_STEERING_PATH` / `OPENRIG_ROADMAP_PATH` /
|
||||
`OPENRIG_DELIVERY_READY_DIR` trump the root-derived defaults; legacy
|
||||
`RIGGED_STEERING_WORKSPACE`):
|
||||
|
||||
- **priorityStack** — verbatim `STEERING.md` content + mtime + byteCount.
|
||||
- **roadmapRail** — `roadmap/PROGRESS.md` checkbox rows; detects `PL-XXX`
|
||||
rail-item codes; marks the **first unchecked** item as `isNextUnchecked`.
|
||||
- **laneRails** — `delivery-ready/mode-{N}/PROGRESS.md` per-lane: reuses
|
||||
`ProgressIndexer` (maxDepth 3) for identical checkbox semantics to the
|
||||
Progress view; "next pull" = first non-done, non-blocked row (Priority
|
||||
Rail Rule — shelf/queue recency does NOT override); top-N (default 3)
|
||||
prefers active+blocked, falls back to done only to fill N.
|
||||
|
||||
Missing sources are reported in `unavailableSources` (each names the env
|
||||
var that would resolve it) rather than failing the whole payload.
|
||||
`isReady()` is true when **at least one** source resolves.
|
||||
|
||||
> Source: `domain/steering/steering-composer.ts:1-25` (narrow-by-design
|
||||
> rationale + 3-source list), `:103-119` (env vars +
|
||||
> `steeringOptsFromEnv`), `:155-169` (`isReady` / `compose`), `:198-250`
|
||||
> (roadmap rail; next-unchecked L231-235), `:282-316` (lane next-pull
|
||||
> L288-289, top-N L293-296), `RAIL_CODE_REGEX` `:346` @HEAD.
|
||||
|
||||
`steeringRoutes()` (mounted `server.ts:510` as `/api/steering`):
|
||||
`GET /` — the composed payload; 503 `steering_composer_unavailable` when
|
||||
unwired; 503 `steering_workspace_not_configured` + a hint pointing at
|
||||
`rig config init-workspace` / `workspace.steering_path` /
|
||||
`OPENRIG_STEERING_PATH` when no source resolves.
|
||||
|
||||
> Source: `routes/steering.ts:18-34` (handler; 503s L23-29); mounted
|
||||
> `server.ts:510` @HEAD.
|
||||
|
||||
> **§10.7 route-reality:** `/steering` is a **`<Navigate to="/project" />`
|
||||
> redirect-stub** — the composer lives at daemon `/api/steering`; the UI
|
||||
> URL folds into the Project workspace overview tab (Phase 3).
|
||||
>
|
||||
> Source: `packages/ui/src/routes.tsx:470-474` ("folds into Project
|
||||
> workspace overview tab (Phase 3) — redirect to /project") @HEAD.
|
||||
|
||||
## 6. Health-summary aggregator (OSR v0 item 1F)
|
||||
|
||||
`computeNodeHealthSummary` / `computeContextHealthSummary` are the compact
|
||||
health gates on the steering surface — **daemon-side aggregation, not a CLI
|
||||
shell-out** (no per-request subprocess). Node summary: cross-rig roll-up of
|
||||
`sessionStatus` + `lifecycleState` via `getNodeInventory` per rig, with an
|
||||
`attentionRequired` tally. Context summary: reads `context_usage` directly
|
||||
(ContextUsageStore exposes per-node accessors only; listing all rows is a
|
||||
steering-surface concern) and buckets by urgency
|
||||
(`usedPercentage` ≥80 critical / ≥60 warning / else low / null unknown) +
|
||||
freshness (`sampledAt` age vs 300 s → fresh/stale/none). A missing
|
||||
`context_usage` table (test harness without the migration) yields an empty
|
||||
summary, not an error.
|
||||
|
||||
> Source: `domain/steering/health-summary.ts:44-46` (thresholds:
|
||||
> `FRESHNESS_THRESHOLD_S=300`, `URGENCY_CRITICAL_PCT=80`,
|
||||
> `URGENCY_WARNING_PCT=60`), `:48-66` (node roll-up), `:75-117`
|
||||
> (context roll-up; table-absent → empty L81-84) @HEAD.
|
||||
|
||||
`healthSummaryRoutes()` (mounted `server.ts:511` as `/api/health-summary`):
|
||||
`GET /nodes`, `GET /context`; 503 `health_summary_unavailable` when the
|
||||
rigRepo dep is unwired.
|
||||
|
||||
> Source: `routes/health-summary.ts:23-44` (handlers; 503 L33,L40);
|
||||
> mounted `server.ts:511` @HEAD.
|
||||
|
||||
## 7. Cross-cutting properties
|
||||
|
||||
- **Filesystem-canonical, no new SQLite:** all four surfaces read/write the
|
||||
filesystem + the JSONL audit; health-summary reads existing
|
||||
`context_usage` only. No content-surface migration
|
||||
(`progress-indexer.ts:21-23`; `health-summary.ts:81-84`) @HEAD.
|
||||
- **Fail-closed, honest errors:** path-safety rejects every escape attempt
|
||||
before fs resolution; write conflicts surface the current mtime/hash for
|
||||
an explicit refresh; missing config returns 503 + a setup hint naming the
|
||||
exact env var — never a silent empty success
|
||||
(`path-safety.ts:124-169`; `file-write-service.ts:129-135`;
|
||||
`routes/steering.ts:25-29`) @HEAD.
|
||||
- **Atomic-or-nothing writes:** same-directory temp + fsync + atomic rename;
|
||||
a rename failure leaves the target untouched (never a partial write)
|
||||
(`file-write-service.ts:160-172`) @HEAD.
|
||||
- **Composer narrowness is deliberate:** the steering composer owns only
|
||||
filesystem-derived pieces; queue/health come from their own endpoints —
|
||||
keeps it testable (`steering-composer.ts:6-16`) @HEAD.
|
||||
|
||||
## OPEN items (carried, not smoothed)
|
||||
|
||||
- **OPEN-A** — the JSONL edit audit at `~/.openrig/file-edit-audit.jsonl`
|
||||
**never rotates at v0** by explicit design
|
||||
(`file-write-service.ts:24-25`); unbounded growth is a known accepted v0
|
||||
state (PRD § Item 4 defers rotation), stated here rather than smoothed.
|
||||
- **OPEN-B** — `OPENRIG_FILES_ALLOWLIST` vs `OPENRIG_PROGRESS_SCAN_ROOTS`
|
||||
vs `OPENRIG_STEERING_WORKSPACE` are three independent operator-config
|
||||
surfaces with the same encoding but separate resolution; there is no
|
||||
single "workspace allowlist" that unifies them at v0. Source-real;
|
||||
noted, not reconciled.
|
||||
- No slice-00 numeric-drift OPEN (1–5) applies — this module carries no
|
||||
migration-count / route-group / PL-004-event-count claim.
|
||||
- [Living Notes review](living-notes-review.md) composes proof and scope content.
|
||||
- [Workspace primitive](workspace-primitive.md) covers workspace identity.
|
||||
- [File-path tests](../../../packages/daemon/test/files-path-safety.test.ts),
|
||||
[progress tests](../../../packages/daemon/test/progress-indexer.test.ts), and
|
||||
[steering tests](../../../packages/daemon/test/steering-composer.test.ts)
|
||||
exercise these helpers; their presence is not a runtime verification claim.
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: Coordination Primitive — Stream/Queue/Inbox/Outbox (PL-004 Phase A)
|
||||
title: Coordination Primitive — Stream/Queue/Inbox/Outbox
|
||||
status: active
|
||||
topics: [coordination, observability]
|
||||
domains: [engineering-advisor, operating-advisor, orchestrator]
|
||||
@@ -10,57 +10,77 @@ applies-when: |
|
||||
transactional handoff guarantee, or where queue closure is enforced.
|
||||
siblings: [workflow-runtime.md, mission-control.md, daemon-core.md]
|
||||
prerequisite-reads: [../README.md, daemon-core.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Coordination Primitive — Stream/Queue/Inbox/Outbox (PL-004 Phase A)
|
||||
# Coordination Primitive — Stream/Queue/Inbox/Outbox
|
||||
|
||||
The PL-004 Phase A coordination primitive is the SQLite-canonical
|
||||
durable-work layer the daemon exposes through `/api/stream` and `/api/queue`.
|
||||
It replaces the POC filesystem `rigx queue` / `rigx stream` path for
|
||||
daemon-backed work; the POC filesystem path remains untouched and the daemon
|
||||
`rig queue` / `rig stream` write only to SQLite (`architecture.md` §5 L651).
|
||||
The coordination primitive is the SQLite-canonical durable-work layer the
|
||||
daemon exposes through `/api/stream` and `/api/queue`. It replaces the POC
|
||||
filesystem `rigx queue` / `rigx stream` path for daemon-backed work; the POC
|
||||
filesystem path remains untouched, and the daemon-backed `rig queue` /
|
||||
`rig stream` commands operate only through the daemon HTTP API, so they write
|
||||
only to SQLite (`packages/cli/src/commands/queue.ts:19`,
|
||||
`packages/cli/src/commands/stream.ts:11`).
|
||||
|
||||
> Verified against source at HEAD `7eaf524c`.
|
||||
> Verified against source at main `e8f0ab340db773392ec8be75b072d1c0f3068a50`. Each count below sits beside the
|
||||
> command that produces it; run the command from the repository root to refresh
|
||||
> it.
|
||||
|
||||
## 1. The five host-scoped tables
|
||||
|
||||
Five host-scoped tables back the primitive (`architecture.md` §3 L416–438;
|
||||
migrations re-confirmed at HEAD `packages/daemon/src/db/migrations/`):
|
||||
Five host-scoped tables back the primitive, one per migration `023`–`027` in
|
||||
`packages/daemon/src/db/migrations/` (**5** tables:
|
||||
`cat packages/daemon/src/db/migrations/02[3-7]_*.ts | grep -c 'CREATE TABLE'`):
|
||||
|
||||
- **`stream_items`** (`023_stream_items.ts`) — L1 append-only intake/audit
|
||||
root. Columns: `stream_item_id` (ULID PK), `ts_emitted`,
|
||||
root. Columns: `stream_item_id` (TEXT PK; a ULID unless the caller supplies
|
||||
one, `packages/daemon/src/domain/stream-store.ts:88`), `ts_emitted`,
|
||||
`stream_sort_key`, `source_session`, `body`, `format` (default `text`),
|
||||
`hint_type`, `hint_urgency`, `hint_destination`, `hint_tags` (JSON),
|
||||
`interrupt`, `archived_at`. Items immutable after emit (only `archived_at`
|
||||
may be set).
|
||||
- **`queue_items`** (`024_queue_items.ts`) — L3 owned-work queue. `qitem_id`
|
||||
is TEXT PK preserving the POC `qitem-YYYYMMDDHHMMSS-<hex>` shape. State
|
||||
enum (8 values): `pending | in-progress | done | blocked | failed |
|
||||
denied | canceled | handed-off`. Carries `closure_reason`,
|
||||
`closure_target`, `closure_required_at`, `chain_of_record` (JSON),
|
||||
`blocked_on`, `handed_off_to`/`handed_off_from`, nudge/heartbeat fields.
|
||||
`interrupt`, `archived_at`, and `identity_provenance` (added by
|
||||
`067_i3_identity_provenance.ts`). Items immutable after emit (only
|
||||
`archived_at` may be set, `stream-store.ts:217`).
|
||||
- **`queue_items`** (`024_queue_items.ts`) — L3 owned-work queue.
|
||||
Unless `--id` is supplied, the CLI generates the create request's `qitem_id` (TEXT PK) before sending as `qitem-<UTC YYYYMMDDHHMMSS>-<16 hex>` (64 random bits) and prints it to stderr as a request identity, not proof of persistence; reuse that ID with `--id` and the unchanged payload after an unknown outcome, while daemon callers without an ID still use `newQitemId()` and its `qitem-<UTC YYYYMMDDHHMMSS>-<8 hex>` form.
|
||||
State enum (**8** values, `QUEUE_STATES` at `queue-repository.ts:33`;
|
||||
`sed -n '/^export const QUEUE_STATES/,/] as const/p' packages/daemon/src/domain/queue-repository.ts | grep -c '^ "'`):
|
||||
`pending | in-progress | done | blocked | failed | denied | canceled |
|
||||
handed-off`. Carries `closure_reason`, `closure_target`,
|
||||
`closure_required_at`, `chain_of_record` (JSON), `blocked_on`,
|
||||
`handed_off_to`/`handed_off_from`, nudge/heartbeat columns.
|
||||
- **`queue_transitions`** (`025_queue_transitions.ts`) — L3 append-only
|
||||
transition log; authoritative audit trail for state evolution. No
|
||||
UPDATE/DELETE from domain code.
|
||||
transition log; authoritative audit trail for state evolution. Domain code
|
||||
never updates its rows. The one delete is the retention runner, which moves
|
||||
every transition of a terminal item whose last transition is older than the
|
||||
retention window into `queue_transitions_archive`
|
||||
(`054_queue_transitions_archive.ts`), insert and delete in one transaction
|
||||
(`packages/daemon/src/domain/queue-retention.ts:203`). Only `done` and
|
||||
`handed-off` items are archived; the default window is 30 days, and items
|
||||
still on a live workflow frontier are skipped (`queue-retention.ts:69`–`75`,
|
||||
`:158`–`171`).
|
||||
- **`inbox_entries`** (`026_inbox_entries.ts`) — mailbox-style asynchronous
|
||||
deposit; idempotent on `inbox_id`. State: `pending | absorbed | denied`.
|
||||
- **`outbox_entries`** (`027_outbox_entries.ts`) — sender-side audit;
|
||||
symmetric to inbox; idempotent on `outbox_id`. Delivery state:
|
||||
`pending | delivered | failed`.
|
||||
deposit; idempotent on `inbox_id`. State: `pending | absorbed | denied`
|
||||
(`INBOX_STATES`, `inbox-handler.ts:5`).
|
||||
- **`outbox_entries`** (`027_outbox_entries.ts`) — sender-side record;
|
||||
symmetric to inbox; idempotent on `outbox_id`. Delivery state (**7** values,
|
||||
`OUTBOX_DELIVERY_STATES` at `outbox-handler.ts:28`;
|
||||
`grep '^export const OUTBOX_DELIVERY_STATES' packages/daemon/src/domain/outbox-handler.ts | grep -o '"[a-z]*"' | wc -l`):
|
||||
`pending | sending | delivered | failed | indeterminate | retained |
|
||||
retired`.
|
||||
|
||||
> Drift-fix D3 — `architecture.md` §3 L243 frames the schema as "27
|
||||
> migrations (22 existing plus 5 added by PL-004 Phase A)". The 5 PL-004
|
||||
> Phase A coordination tables (`023`–`027`) are still correct as a *range*,
|
||||
> but the headline migration count is **40**, not 27 (slice-00 §1.3,
|
||||
> re-confirmed at HEAD: `startup.ts:206` 40-element `migrate()` array; full
|
||||
> drift-fix authority and the correction live in `daemon-core.md`).
|
||||
These five are migrations `023`–`027` of the daemon's **96**
|
||||
(`git ls-files packages/daemon/src/db/migrations | wc -l`), applied in the
|
||||
order of `ALL_MIGRATIONS` (`packages/daemon/src/db/all-migrations.ts:107`).
|
||||
Later migrations add columns to these tables; `daemon-core.md` covers the
|
||||
migration set.
|
||||
|
||||
## 2. The six host-scoped services
|
||||
|
||||
Six host-scoped domain services implement the layer (`architecture.md` §5
|
||||
L649–658; files re-confirmed in `packages/daemon/src/domain/` at HEAD).
|
||||
Six host-scoped domain services in `packages/daemon/src/domain/` implement the
|
||||
layer (**6** files:
|
||||
`ls packages/daemon/src/domain/{stream-store,queue-repository,queue-transition-log,hot-potato-enforcer,inbox-handler,outbox-handler}.ts | wc -l`).
|
||||
Routes import these; services are Hono-free:
|
||||
|
||||
- **`stream-store.ts`** — L1 stream: idempotent emit (on `stream_item_id`),
|
||||
@@ -68,40 +88,72 @@ Routes import these; services are Hono-free:
|
||||
and inclusive time-window filters, soft archive. `direction=latest` applies
|
||||
every filter before taking the newest bounded page, then returns that page
|
||||
chronologically.
|
||||
- **`queue-repository.ts`** — L3 queue: create, claim/unclaim, update
|
||||
(general state mutator with hot-potato strict-rejection on `done`),
|
||||
transactional handoff (close source as `handed-off` plus create new owned
|
||||
qitem in a single transaction), pod-fallback rerouting, overdue lookup,
|
||||
nudge/heartbeat tracking. Cross-rig validation hook exposed as
|
||||
`validateRig` constructor option.
|
||||
- **`queue-repository.ts`** — L3 queue: create (`:1391`), claim/unclaim
|
||||
(`:2186`, `:2271`), update (general state mutator with hot-potato
|
||||
strict-rejection on `done`, `:2334`), transactional handoff (close source as
|
||||
`handed-off` plus create new owned qitem in a single transaction, `:1656`;
|
||||
`handoffAndComplete`, `:1833`, closes the source as `done` instead),
|
||||
pod-fallback rerouting (`routeToFallback`, `:3678`), overdue lookup
|
||||
(`findOverdue`, `:3378`), nudge-result tracking (`recordNudgeAttempt`,
|
||||
`:3663`). Nothing in the daemon writes the `last_heartbeat` column
|
||||
(`queue-pickup.ts:16`). Cross-rig validation hook exposed as `validateRig`
|
||||
constructor option (`queue-repository.ts:717`).
|
||||
- **`queue-transition-log.ts`** — append-only state-transition log; used by
|
||||
`queue-repository.ts`, exposed read-only on
|
||||
`queue_repository.transitionLog`.
|
||||
`queue-repository.ts`, exposed as the read-only property
|
||||
`QueueRepository.transitionLog` (`queue-repository.ts:666`).
|
||||
- **`hot-potato-enforcer.ts`** — pure validator for the load-bearing API
|
||||
contract (see §3).
|
||||
- **`inbox-handler.ts`** — mailbox handler: authenticated drop (idempotent
|
||||
on `inbox_id`), absorb (promotes a pending entry to a `queue_item`,
|
||||
idempotent), deny (records reason). Auth check is a pluggable constructor
|
||||
hook.
|
||||
- **`outbox-handler.ts`** — sender-side outbox: idempotent record, mark
|
||||
delivered/failed, list. Emits no event-bus events (pure audit).
|
||||
- **`inbox-handler.ts`** — mailbox handler: drop (idempotent on `inbox_id`,
|
||||
`:100`), absorb (promotes a pending entry to a `queue_item`, idempotent,
|
||||
`:155`), deny (records reason, `:215`). The handler has no auth hook
|
||||
(`inbox-handler.ts:71`); identity comes from the route. `POST /inbox/drop`
|
||||
takes the sender only from the `X-OpenRig-Session` header through
|
||||
`requireSenderIdentity` (`routes/queue.ts:1094`) and answers 400
|
||||
`actor_required` without it. Absorb and deny (`:1121`, `:1135`) use the
|
||||
header when present; otherwise they accept the body's `receiverSession` and
|
||||
record it as `claimed:v1` (`routes/require-sender-identity.ts:83`–`92`).
|
||||
- **`outbox-handler.ts`** — sender-side outbox: idempotent record (`:130`),
|
||||
delivery-state marks (`markDelivered` `:239`, `markFailed` `:256`,
|
||||
`markIndeterminate` `:280`), list (`listForSender` `:339`). It also holds the
|
||||
queue's durable wake intents (ids prefixed `wake-intent-`, `:15`) and the
|
||||
messages the seat delivery guard retains (`retain` `:181`, `retire` `:217`),
|
||||
so it is not audit-only. Emits no event-bus events.
|
||||
|
||||
Create, handoff and handoff-and-complete return the committed row without
|
||||
waiting for terminal delivery. Each stages a deterministic
|
||||
`wake-intent-<qitem>` outbox row in its transaction and delivers it after
|
||||
commit (`queue-repository.ts:1412`–`1424`, `:1475`–`1483`). The returned
|
||||
`lastNudgeResult` is therefore normally null; `rig queue show <id>` reads the
|
||||
wake result later. If create cannot retain the intent, the task is still saved
|
||||
and the row records `failed:wake not retained: <reason>`. Startup reconciles
|
||||
and drains intents left pending by a crash once (`startup.ts:2361`–`2362`).
|
||||
`maybeNudge` remains only as the path for a repository with no outbox.
|
||||
|
||||
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.
|
||||
queue repository, with `evidenceRef: rig queue show <source-qitem-id>`
|
||||
(`queue-stuck-sweep.ts:605`) pointing to the underlying durable work row. This
|
||||
satisfies the existing human-route evidence contract without changing
|
||||
destination resolution: a finding goes to the destination's orchestrator when
|
||||
one is derivable, otherwise to the destination itself, and that seat may be
|
||||
dead. Repeated detections refresh the existing finding; when the source
|
||||
condition resolves, the sweep closes its own finding (`:612`–`637`). The sweep
|
||||
reports six finding kinds (`:50`). Its custody check covers closures that name
|
||||
a successor qitem or a seat (`closure_reason = handed_off_to`) in the last 24
|
||||
hours; a seat target is satisfied by a local successor carrying this row's
|
||||
lineage, and any target by a `custody-verified: <target> …` note on the closed
|
||||
row (`:168`–`244`, `:519`–`554`).
|
||||
|
||||
## 3. The hot-potato closure contract (where queue closure is enforced)
|
||||
|
||||
`hot-potato-enforcer.ts` is the pure validator for the load-bearing API
|
||||
contract. Re-confirmed at HEAD (`hot-potato-enforcer.ts:10–24`):
|
||||
contract. `validateClosure` (`hot-potato-enforcer.ts:59`) checks a transition
|
||||
against `CLOSURE_REASONS` (`:18`; **7** values:
|
||||
`sed -n '/^export const CLOSURE_REASONS/,/] as const/p' packages/daemon/src/domain/hot-potato-enforcer.ts | grep -c '^ "'`):
|
||||
|
||||
`state=done` requires `closure_reason ∈ {handed_off_to, blocked_on, denied,
|
||||
canceled, no-follow-on, escalation}`. The reasons
|
||||
canceled, no-follow-on, escalation, superseded}`. The reasons
|
||||
`handed_off_to | blocked_on | escalation` additionally require
|
||||
`closure_target`:
|
||||
`closure_target` (`:86`):
|
||||
|
||||
- `handed_off_to` — work continues with a different seat (`closure_target` =
|
||||
new owner).
|
||||
@@ -112,131 +164,203 @@ canceled, no-follow-on, escalation}`. The reasons
|
||||
- `no-follow-on` — terminal completion, nothing else needed.
|
||||
- `escalation` — kicked up to a higher tier (`closure_target` = escalation
|
||||
target).
|
||||
- `superseded` — the row was replaced by cancel-and-replace
|
||||
(`closure_target` = the successor qitem). The update path records it on
|
||||
`state=canceled` and refuses it there without a `closure_target`
|
||||
(`queue-repository.ts:2629`).
|
||||
|
||||
Tier→SLA mapping for `closure_required_at` also lives in
|
||||
`hot-potato-enforcer.ts`. This validator is invoked by
|
||||
`QueueRepository.update()` (and `updateWithinTransaction()`), so closure is
|
||||
enforced at the daemon transaction boundary — the workflow runtime
|
||||
*projects* on closure but does not gate it (see `workflow-runtime.md`).
|
||||
`hot-potato-enforcer.ts` (`TIER_SLA_SECONDS`, `:115`). This validator is
|
||||
invoked by `QueueRepository.update()` (and `updateWithinTransaction()`), both
|
||||
through the call at `queue-repository.ts:2531`, so closure is enforced at the
|
||||
daemon transaction boundary — the workflow runtime *projects* on closure but
|
||||
does not otherwise gate it (see `workflow-runtime.md`). The one
|
||||
workflow-aware check is in the queue: `update()` refuses a terminal close of a
|
||||
live workflow frontier packet from a non-workflow verb
|
||||
(`workflow_frontier_packet`, `queue-repository.ts:2557`), through a predicate
|
||||
startup injects. "Terminal" there is the queue's terminal set, `done` and
|
||||
`handed-off` (`queue-repository.ts:52`).
|
||||
|
||||
## 3b. Cross-host queue routing (v0.4.6 — OPR.0.4.6.MH3)
|
||||
Closure fields are stored only on `state=done`, the park record (`blocked`
|
||||
with `blocked_on`), the handoff close (`handed-off` with `handed_off_to`) and
|
||||
the superseded cancel; any other transition carrying them is refused with
|
||||
`closure_fields_not_admitted`, and `blocked_on` outside `blocked` with
|
||||
`blocked_on_not_admitted`. A `done`, `canceled` or `handed-off` row accepts a
|
||||
note without a state change as an append-only transition; moving it to
|
||||
another state requires an explicit reopen with a note. A `qitem-` blocker must
|
||||
exist and be live on this daemon; when a blocker leaves the active states,
|
||||
each row parked on it follows the blocker's handoff successor or returns to
|
||||
`pending` with a durable wake (`queue-repository.ts:2407`–`2529`,
|
||||
`:2612`–`2656`, `:2931`–`3008`). Claiming a parked row clears its
|
||||
`blocked_on`; the claim transition keeps the former gate as a `closure_target`
|
||||
audit pointer with no closure reason, which a re-park without `--blocked-on`
|
||||
reuses (`queue-repository.ts:2233`–`2254`, `:2571`–`2577`).
|
||||
`human-route-enforcer.ts` is a second pure validator at the same boundary: a
|
||||
`human-gate` row, a row addressed to a human seat, or a park on one must carry
|
||||
`summary` and `evidence_ref`.
|
||||
|
||||
The queue's two coordination WRITE verbs — **create + handoff(-and-complete)
|
||||
only** — are host-aware: a write body may carry an out-of-band `hostId`
|
||||
envelope (BR-1 — session strings stay `member@rig` everywhere; the 3-part
|
||||
`agent@rig@host` form is CLI input sugar that never leaves the CLI edge).
|
||||
The mechanism **generalizes the shipped mission-control forward-then-strip
|
||||
WRITE template** (`routes/mission-control.ts` § remote action): one shared
|
||||
route-layer helper (`forwardQueueWrite`, `routes/queue.ts`) resolves the
|
||||
host registry daemon-side (bearers never reach the caller), rejects
|
||||
ssh-declared hosts (`unsupported-transport` — the daemon→daemon path is
|
||||
http-only, because it is what fires the remote nudge), strips `hostId`, and
|
||||
forwards the WHOLE body via `remoteJsonRequest` under a named write-class
|
||||
deadline (`QUEUE_FORWARD_TIMEOUT_MS`). The origin's response returns
|
||||
verbatim; transport failures map to a structured host-named taxonomy
|
||||
(`remote_queue_write_failed`: unknown-host / unsupported-transport /
|
||||
unreachable / auth-failed / remote-error). No local row is ever written on
|
||||
the cross-host path.
|
||||
## 3b. Cross-host queue routing
|
||||
|
||||
Three queue write routes are host-aware — `POST /create`,
|
||||
`POST /:qitemId/handoff` and `POST /:qitemId/handoff-and-complete` (**3**:
|
||||
`grep -c '!resolvesToLocalHost(body.hostId, getSelfHostId())' packages/daemon/src/routes/queue.ts`):
|
||||
a write body may carry an out-of-band `hostId` envelope. The destination
|
||||
session stays `member@rig`; the 3-part `agent@rig@host` form is CLI input
|
||||
sugar that `resolveQueueHostDestination`
|
||||
(`packages/cli/src/commands/queue.ts:394`) splits into the destination and
|
||||
`hostId` before the request leaves the CLI. The same split applies to
|
||||
`--host <id>`; queue verbs never follow `rig host select`, and naming two
|
||||
different hosts is refused with `host_qualifier_conflict`. The mechanism
|
||||
follows the forward-then-strip shape of mission-control's remote action
|
||||
(`routes/mission-control.ts:352`): one shared route-layer helper
|
||||
(`forwardQueueWrite`, `routes/queue.ts:214`) resolves the host registry
|
||||
daemon-side (bearers never reach the caller), rejects ssh-declared hosts
|
||||
(`unsupported-transport` — the daemon→daemon path is http-only), and forwards
|
||||
the whole body, with `hostId` stripped, via `remoteJsonRequest` (`:248`) under
|
||||
a named write-class deadline (`QUEUE_FORWARD_TIMEOUT_MS`, `:41`). The origin's
|
||||
response returns verbatim; failures map to a structured host-named error
|
||||
(`remote_queue_write_failed`, HTTP 502, `:234`) whose `failureClass` is one of
|
||||
registry / unknown-host / unsupported-transport / unreachable / auth-failed /
|
||||
remote-error. No local row is ever written on the cross-host path.
|
||||
|
||||
**The model: origin-owns-the-record, at-least-once + idempotent,
|
||||
message-passing closure (never 2PC).**
|
||||
|
||||
- **Origin-owns-the-record.** The qitem lives in the TARGET host's DB; that
|
||||
row is THE record. The target daemon's OWN `maybeNudge` fires on ITS local
|
||||
tmux (the forwarded body includes the `nudge` flag) — the sending daemon
|
||||
never reaches across a host boundary.
|
||||
- **Idempotency (arch Q-a).** The forwarding daemon MINTS the `qitemId`
|
||||
before the first forward, so every retry carries the same id; dedup rides
|
||||
the existing `qitem_id TEXT PRIMARY KEY`. On PK conflict the origin
|
||||
returns the stored row when identity fields match (idempotent absorb) and
|
||||
a structured `qitem_id_reuse` error when they differ
|
||||
(`QueueRepository.create()` catch-path + `isQitemPrimaryKeyConflict`).
|
||||
- **Cross-host handoff choreography (arch Q-c).** The local atomic
|
||||
close+create cannot span two DBs, so the route-layer choreography
|
||||
(`crossHostHandoff`, `routes/queue.ts`) runs: successor-create on the
|
||||
target host FIRST (via the one forward helper), local source-close SECOND
|
||||
(`QueueRepository.closeCrossHostHandoffSource`) — never the reverse. A
|
||||
crash between the two leaves a live duplicate that the idempotent re-drive
|
||||
converges; the reverse order would leave a closed source pointing at a
|
||||
successor that does not exist (a dropped potato — the one forbidden
|
||||
outcome). The successor id is DERIVED, not minted:
|
||||
`deriveCrossHostSuccessorId(source, destination, host)` →
|
||||
`qitem-xh-<sha256[:16]>` — a pure stateless function, so a re-drive
|
||||
re-derives the same id across daemon restarts and absorbs on the target
|
||||
PK. *(Named residual, inherent to the at-least-once/no-2PC fence: a
|
||||
re-drive naming a DIFFERENT destination derives a different id and cannot
|
||||
absorb the earlier successor — that orphan stays visible via the chain +
|
||||
provenance tags; the source-close conflict check surfaces the
|
||||
row is THE record. The target daemon wakes the destination on ITS local
|
||||
tmux: its own `create` stages a durable wake intent and delivers it after
|
||||
commit (§2; the forwarded body carries the `nudge` flag) — the sending
|
||||
daemon never reaches across a host boundary. Before forwarding, the
|
||||
forwarding daemon stamps its own host id, when it has one, onto the source
|
||||
session (`stampSelfHostSuffix`, `queue-repository.ts:548`; called at
|
||||
`routes/queue.ts:378` and `:509`), so the target row records the sender as
|
||||
`member@rig@<forwarding host>`.
|
||||
- **Idempotency.** On create, the forwarding daemon mints the `qitemId` before
|
||||
the forward unless the caller supplied one (`routes/queue.ts:501`). The
|
||||
forward is a single request, so a retry dedups only when it carries the same
|
||||
id (a caller re-sending `--id`, or a handoff re-drive, whose successor id is
|
||||
derived). Dedup rides the existing `qitem_id TEXT PRIMARY KEY`. On PK
|
||||
conflict the origin returns the stored row when destination and source
|
||||
match (idempotent absorb) and a structured `qitem_id_reuse` error (409) when
|
||||
they differ (`QueueRepository.create()` catch path,
|
||||
`queue-repository.ts:1431`–`1472`, with `isQitemPrimaryKeyConflict`,
|
||||
`:461`). When destination and source match but the body differs, the stored
|
||||
row is returned unchanged with `createWarning: qitem_body_not_saved`; the
|
||||
supplied body is not saved (`:1450`–`1454`).
|
||||
- **Cross-host handoff choreography.** The local atomic close+create cannot
|
||||
span two DBs, so the route-layer choreography (`crossHostHandoff`,
|
||||
`routes/queue.ts:316`) runs: successor-create on the target host FIRST (via
|
||||
the one forward helper, `:397`), local source-close SECOND
|
||||
(`QueueRepository.closeCrossHostHandoffSource`, `queue-repository.ts:2031`)
|
||||
— never the reverse. A crash between the two leaves a live duplicate that
|
||||
the idempotent re-drive converges; the reverse order would leave a closed
|
||||
source pointing at a successor that does not exist (a dropped potato — the
|
||||
one forbidden outcome). The successor id is DERIVED, not minted:
|
||||
`deriveCrossHostSuccessorId(source, destination, host)`
|
||||
(`queue-repository.ts:489`) → `qitem-xh-<sha256[:16]>` — a pure stateless
|
||||
function, so a re-drive re-derives the same id across daemon restarts and
|
||||
absorbs on the target PK. *(Residual, inherent to at-least-once delivery
|
||||
without 2PC: a re-drive naming a DIFFERENT destination derives a different
|
||||
id and cannot absorb the earlier successor — that orphan stays visible via
|
||||
the chain + provenance tags; the source-close conflict check surfaces the
|
||||
disagreement.)*
|
||||
- **Closure across the boundary.** The source closes with
|
||||
`closure_reason=handed_off_to` and `closure_target=member@rig@<host>` —
|
||||
the 3-part form is legal THERE because `closure_target` is OPAQUE
|
||||
audit/display metadata, presence-checked and never parsed for routing
|
||||
(arch R1; any PR parsing it as a session string is a spec violation).
|
||||
The exemption spans exactly the `closure_target` column on `queue_items`
|
||||
and its verbatim mirror on `queue_transitions` — nothing else. In
|
||||
particular the transition log's free-text `transition_note` is a durable
|
||||
carrier too: the minted cross-host close note names the 2-part
|
||||
`toSession` only (rev1-r2 B1). `handed_off_to` and every other
|
||||
session-string carrier stay 2-part.
|
||||
`closure_reason=handed_off_to` and
|
||||
`closure_target=<successor qitem id>@<host>` (`routes/queue.ts:351`); a
|
||||
source already closed with the older `member@rig@<host>` target keeps it,
|
||||
and a re-drive matches against it (`:352`–`356`). `closure_target` is OPAQUE
|
||||
audit metadata, never parsed for routing; the standing stuck sweep reads it
|
||||
only to check successor custody (`queue-stuck-sweep.ts:519`–`554`). The
|
||||
host-qualified key appears only in the `closure_target` column on
|
||||
`queue_items` and its verbatim mirror on `queue_transitions`: the minted
|
||||
cross-host close note names the 2-part `toSession` only
|
||||
(`queue-repository.ts:2087`), and `handed_off_to` and the
|
||||
`queue.handed_off` event stay 2-part.
|
||||
Re-drive semantics: already-terminal + MATCHING `closure_target` = absorb;
|
||||
MISMATCH = structured `cross_host_close_conflict` (409). The successor
|
||||
carries `chain_of_record = [...source.chain, source.qitemId]` — A-side ids
|
||||
are opaque lineage identifiers on B (arch R2b; they do not dereference in
|
||||
B's DB) — plus provenance tags `cross-host` + `from-host:<self-declared
|
||||
name>` (honest best-effort, not authenticated identity).
|
||||
- **Boundary discipline.** Claim / update / inbox stay local-by-principle
|
||||
(after a cross-host handoff the successor lives where its worker lives);
|
||||
sender-side ops on a forwarded item are a named follow-up. The local
|
||||
(no-host) paths are byte-identical to pre-MH-3 behavior; the hot-potato
|
||||
validation contract (§3) is unweakened across the boundary.
|
||||
MISMATCH = structured `cross_host_close_conflict` (409), checked in the route
|
||||
before any forward (`routes/queue.ts:359`) and again in the repository. The
|
||||
successor carries `chain_of_record = [...source.chain, source.qitemId]`
|
||||
(`routes/queue.ts:384`) — A-side ids are opaque lineage identifiers on B
|
||||
(they do not dereference in B's DB) — plus provenance tags `cross-host` +
|
||||
`from-host:<self-declared name>` (the forwarding daemon's OS hostname,
|
||||
`routes/queue.ts:54`; honest best-effort, not authenticated identity).
|
||||
- **Boundary discipline.** Claim, update and inbox routes take no `hostId` and
|
||||
stay local (after a cross-host handoff the successor lives where its worker
|
||||
lives). Without a `hostId`, with `local`, or with this daemon's own resolved
|
||||
host id (an exact, case-sensitive match), create and handoff take the
|
||||
local path; a self-addressed handoff whose deterministic cross-host
|
||||
successor an earlier self-forward already created runs that cross-host
|
||||
create and close in process. The
|
||||
hot-potato validation contract (§3) is unweakened across the boundary: the
|
||||
cross-host close always records `handed_off_to` with a target.
|
||||
|
||||
## 4. Coordination events
|
||||
|
||||
> Drift-fix D8 / OPEN-4 (carried verbatim, slice-00): `architecture.md`
|
||||
> §3 L394 says "Existing 32 PL-004 events untouched" and L410 says "Existing
|
||||
> 20 PL-004 events are unchanged" — these two body claims are internally
|
||||
> inconsistent with each other and predate 0.3.x. **Do NOT carry either
|
||||
> number.** The current `RigEvent` union (`packages/daemon/src/domain/types.ts:94`)
|
||||
> has **73 union members** total (slice-00 §1.8, re-confirmed at HEAD:
|
||||
> `grep -cE '^\s*\| \{ type: ' = 73`). A precise PL-004-only vs PL-005-only
|
||||
> sub-partition is not reconcilable from static inspection without
|
||||
> classifying all 73 members by introducing slice; slice-00 OPEN-4 flags
|
||||
> this and it is carried, not smoothed.
|
||||
The `RigEvent` union (`packages/daemon/src/domain/types.ts:108`) has **98**
|
||||
members
|
||||
(`sed -n '/^export type RigEvent =/,/^export type PersistedEvent/p' packages/daemon/src/domain/types.ts | grep -c 'type: "'`);
|
||||
this module covers only the coordination families below.
|
||||
|
||||
Coordination events emitted by these services (re-confirmed in
|
||||
`domain/types.ts` at HEAD): `stream.emitted` (StreamStore.emit,
|
||||
`types.ts:155`); `queue.created` / `queue.handed_off` / `queue.claimed` /
|
||||
`queue.unclaimed` / `qitem.fallback_routed` / `qitem.closure_overdue`
|
||||
(QueueRepository); `inbox.absorbed` (`types.ts:162`) / `inbox.denied`
|
||||
(InboxHandler). `architecture.md` §8 L987–990 listed these as "9
|
||||
coordination events"; the count of the broader `stream|queue|inbox|qitem`
|
||||
event family at HEAD is 10 (`stream` 1 + `queue` 5 + `inbox` 2 + `qitem` 2)
|
||||
— stated as a re-derived family count, not as a contested PL-004 sub-count
|
||||
(OPEN-4).
|
||||
Coordination events emitted by these services: `stream.emitted`
|
||||
(`StreamStore.emit`, declared at `types.ts:245`); `queue.created` /
|
||||
`queue.handed_off` / `queue.claimed` / `queue.unclaimed` / `queue.updated` /
|
||||
`qitem.fallback_routed` / `qitem.closure_overdue` (QueueRepository); `inbox.absorbed` (`types.ts:255`) /
|
||||
`inbox.denied` (InboxHandler). `qitem.closure_overdue` (`types.ts:254`) is
|
||||
emitted by `QueueRepository.recordClosureOverdue` when swept by `queue-stuck-sweep`
|
||||
and passed through the queue watch filter (**2** files contain its literal:
|
||||
`git grep -l -F 'type: "qitem.closure_overdue"' -- packages/daemon/src | wc -l`).
|
||||
The `stream|queue|inbox|qitem` families declare **10** event types (`stream` 1
|
||||
+ `queue` 5 + `inbox` 2 + `qitem` 2;
|
||||
`sed -n '/^export type RigEvent =/,/^export type PersistedEvent/p' packages/daemon/src/domain/types.ts | grep -oE '"(stream|queue|inbox|qitem)\.[a-z_]+"' | sort -u | wc -l`).
|
||||
|
||||
Two SSE surfaces stream coordination events: `/api/stream/watch`
|
||||
(aliased `/api/stream/sse`, `routes/stream.ts:117–118`) for new stream
|
||||
items, and `/api/queue/watch` for queue/inbox events. The event log remains
|
||||
append-only and SQLite-backed. `rig stream watch` is a thin, single-connection
|
||||
consumer of `/api/stream/sse`; it does not add a daemon route or reconnect
|
||||
policy.
|
||||
Two SSE surfaces stream coordination events (**2**:
|
||||
`cat packages/daemon/src/routes/stream.ts packages/daemon/src/routes/queue.ts | grep -c 'return streamSSE('`):
|
||||
`/api/stream/watch` (aliased `/api/stream/sse`, `routes/stream.ts:194`–`195`),
|
||||
which first replays up to 50 unarchived items in chronological order (the
|
||||
oldest, `:174`–`181`) and then streams new items, and `/api/queue/watch`
|
||||
(aliased `/api/queue/sse`, `routes/queue.ts:1054`–`1055`) for queue/inbox
|
||||
events — every coordination type except `stream.emitted` and `queue.updated`
|
||||
(filter at `routes/queue.ts:1031`–`1038`). The event log remains append-only and
|
||||
SQLite-backed. `rig stream watch` is a thin, single-connection consumer of
|
||||
`/api/stream/sse` (`packages/cli/src/commands/stream.ts:191`); it does not add
|
||||
a daemon route or reconnect policy.
|
||||
|
||||
## 5. Route surface
|
||||
|
||||
- `/api/stream` (`server.ts:489`) — `POST /emit` (`routes/stream.ts:24`),
|
||||
`GET /list` (`:56`, including `sourceSession`, `hintDestination`, `hintTag`,
|
||||
`since`, and `until` filters), `GET /watch` + `/sse` SSE (`:117`),
|
||||
`GET /:streamItemId` (`:121`), `POST /:streamItemId/archive` (`:130`).
|
||||
- `/api/queue` (`server.ts:490`) — `POST /create` (`routes/queue.ts:99`),
|
||||
`POST /:qitemId/claim` (`:144`), `POST /:qitemId/unclaim` (`:157`),
|
||||
`POST /:qitemId/update` (`:170`), plus handoff/list/watch surfaces.
|
||||
- `/api/stream` (`server.ts:783`) — `POST /emit` (`routes/stream.ts:56`),
|
||||
`GET /list` (`:91`, including `sourceSession`, `hintDestination`, `hintTag`,
|
||||
`since`, `until` and `direction` filters), `GET /watch` + `/sse` SSE
|
||||
(`:194`), `GET /:streamItemId` (`:198`), `POST /:streamItemId/archive`
|
||||
(`:207`).
|
||||
- `/api/queue` (`server.ts:784`) — `POST /create` (`routes/queue.ts:450`),
|
||||
`POST /:qitemId/claim` (`:545`), `POST /:qitemId/unclaim` (`:561`),
|
||||
`POST /:qitemId/update` (`:600`), plus handoff (`:650`, `:732`),
|
||||
`POST /:qitemId/fallback` (`:808`), list (`:902`) and watch (`:1054`)
|
||||
surfaces; reads `GET /whoami`, `/attention-aggregate`, `/human-updates`,
|
||||
`/overdue`, `/undelivered`, `/recent-transitions`, `/:qitemId/transitions`
|
||||
and `/:qitemId` (`:822`–`1067`); inbox routes `POST /inbox/drop`,
|
||||
`/inbox/:id/absorb`, `/inbox/:id/deny` and `GET /inbox/pending`,
|
||||
`/inbox/list` (`:1076`–`1152`); outbox routes `POST /outbox/record` and
|
||||
`GET /outbox/list` (`:1161`, `:1190`).
|
||||
|
||||
Every write route derives its actor through `requireSenderIdentity`
|
||||
(`routes/require-sender-identity.ts:76`): the `X-OpenRig-Session` header wins
|
||||
over a body actor. On a direct request from a known origin it is recorded as
|
||||
`transport:v1`; `resolveRecordedProvenance` (`:199`) instead records
|
||||
`origin-unknown:v1` when the origin-unknown marker is set or a relayed request
|
||||
carried `origin-unknown:v1` (`:207`), `relay:v1` for a relayed request that
|
||||
carried `transport:v1`, and `claimed:v1` for other relayed requests. Without the
|
||||
header, the
|
||||
body actor is recorded as `claimed:v1`; with neither (or, for inbox drop,
|
||||
without the header) the route answers 400 `actor_required`. The stamp lands in
|
||||
`identity_provenance` on transitions, inbox, outbox and stream rows
|
||||
(migrations `067`, `082`).
|
||||
|
||||
## See also
|
||||
|
||||
- `daemon-core.md` — daemon wiring, the 40-migration set, route surface.
|
||||
- `workflow-runtime.md` — PL-004 Phase D runtime that projects on closure.
|
||||
- `mission-control.md` — PL-005 queue observability over `queue_items`.
|
||||
- `daemon-core.md` — daemon wiring, the migration set, route surface.
|
||||
- `workflow-runtime.md` — the workflow runtime that projects on closure.
|
||||
- `mission-control.md` — queue observability over `queue_items`.
|
||||
- Source roots: `packages/daemon/src/domain/{stream-store,queue-repository,
|
||||
queue-transition-log,hot-potato-enforcer,inbox-handler,outbox-handler}.ts`,
|
||||
`packages/daemon/src/routes/{stream,queue}.ts`.
|
||||
@@ -9,252 +9,234 @@ applies-when: |
|
||||
graph, the SQLite schema/migration set, or the route-mount surface.
|
||||
siblings: [coordination-primitive.md, agent-spec-and-startup.md, lifecycle-snapshot-restore.md]
|
||||
prerequisite-reads: [../README.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Daemon Core — Wiring, DB, Migrations, Startup
|
||||
|
||||
OpenRig is a local control plane for multi-agent coding topologies. The daemon
|
||||
(`@openrig/daemon`) is the framework-free SQLite-backed core that the CLI
|
||||
(`@openrig/cli`), UI (`@openrig/ui`), and MCP server all sit on top of.
|
||||
(`@openrig/cli`), the terminal UI (`@openrig/tui`), the web UI (`@openrig/ui`)
|
||||
and the MCP server all sit on top of.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c` (`git describe` →
|
||||
> `v0.3.1-6-g7eaf524c`). Current package version is **0.3.1** across all three
|
||||
> packages (`package.json` root `"version": "0.3.1"`; slice-00 §1.1). HEAD
|
||||
> carries 6 commits of unreleased release-0.3.2 work; no `v0.3.2` tag exists.
|
||||
> Verified against source at main `e8f0ab340db773392ec8be75b072d1c0f3068a50`. Each count below sits beside the
|
||||
> command that produces it; run the command from the repository root to refresh
|
||||
> it.
|
||||
|
||||
## 1. System overview
|
||||
|
||||
The system has six architectural layers (`architecture.md` §1 L23–30):
|
||||
For what OpenRig is and how its packages fit together, read `ARCHITECTURE.md` at
|
||||
the repository root. This module covers the daemon's own wiring.
|
||||
|
||||
1. **AgentSpec / pod-aware core** — spec parsing, resolution, precedence,
|
||||
startup orchestration, snapshot/restore, bundles.
|
||||
2. **Operator and topology layer** — harness auto-launch, node inventory,
|
||||
session naming, infrastructure nodes, explorer UI, existing-rig power-on,
|
||||
auto-snapshot, post-command handoff.
|
||||
3. **Communication and history layer** — transcript capture (pipe-pane),
|
||||
communication primitives (send/capture/broadcast), config/preflight,
|
||||
`rig ask` context packs, durable rig chat.
|
||||
4. **Authoring and identity layer** — spec review + spec library, `whoami`,
|
||||
adopted-session tmux-metadata parity, bind/materialize/adopt workflows.
|
||||
5. **Rig environment layer** — rig-scoped services records, Compose-backed
|
||||
service orchestration, readiness gates, env snapshot/restore integration.
|
||||
6. **Agent-managed software layer** — managed-app classification, app-focused
|
||||
browse/review/runtime UI, and the canonical `secrets-manager` example.
|
||||
### Source footprint at `e8f0ab340db773392ec8be75b072d1c0f3068a50`
|
||||
|
||||
Legacy flat-node/package flows remain for backward compatibility.
|
||||
The footprint counts use non-test TypeScript files under each package's `src/`
|
||||
(tests live in separate `packages/*/test/` directories):
|
||||
`find <dir> -type f \( -name '*.ts' -o -name '*.tsx' \) ! -name '*.test.*' | wc -l`.
|
||||
|
||||
### Verified source footprint at HEAD `7eaf524c`
|
||||
|
||||
> Drift-fix D1/D6 — `architecture.md` L6,11 said `OpenRig v0.2.0` /
|
||||
> "Current v0.2.0 release verification" and L7 said `376` source files. Both
|
||||
> are a frozen pre-v0.2.0-era header (slice-00 §1.9 — last edited `72982bb2`,
|
||||
> 2026-03-30). Corrected to the values below.
|
||||
|
||||
| Metric | Value @ HEAD | Source (independently re-confirmed) |
|
||||
| Metric | Count | Directory or command |
|
||||
|---|---|---|
|
||||
| Package version | **0.3.1** (HEAD +6 unreleased 0.3.2 commits) | slice-00 §1.1; `package.json:version` |
|
||||
| Total source footprint | **601** files | slice-00 §1.6; `find packages/*/src` non-test = 279+87+235 |
|
||||
| Daemon footprint | **279** total / **173** domain / **49** route mounts (46 route files) / **11** adapters / **40** migrations | slice-00 §1.5/§1.6; `find packages/daemon/src` non-test |
|
||||
| CLI footprint | **87** files | slice-00 §1.6; `find packages/cli/src` non-test |
|
||||
| UI footprint | **235** files | slice-00 §1.6; `find packages/ui/src` `.ts`+`.tsx` non-test |
|
||||
| All packages | **1205** | `packages/*/src` |
|
||||
| Daemon | **660** (**431** under `domain/`, **29** under `adapters/`) | `packages/daemon/src`, `…/src/domain`, `…/src/adapters` |
|
||||
| CLI | **174** | `packages/cli/src` |
|
||||
| Web UI | **304** | `packages/ui/src` |
|
||||
| TUI | **67** | `packages/tui/src` |
|
||||
| Database migrations | **96** (`001_core_schema.ts` … `097_thread_part_map.ts`; no `093`) | `git ls-files packages/daemon/src/db/migrations \| wc -l` |
|
||||
| Files in `routes/` | **68**, of which **65** create a Hono router | `git ls-files packages/daemon/src/routes \| wc -l`; `git grep -l 'new Hono' -- packages/daemon/src/routes \| wc -l` |
|
||||
| `app.route(...)` mounts in `server.ts` | **69** | `grep -c 'app.route(' packages/daemon/src/server.ts` |
|
||||
| Top-level `rig` commands | **87** | `grep -c 'program.addCommand(' packages/cli/src/index.ts` |
|
||||
| MCP tools | **18**, all named `rig_*` | `grep -c 'server.tool(' packages/cli/src/mcp-server.ts` |
|
||||
| Runtime adapter classes | **5**: Claude Code, Codex, Pi, Stub, Terminal | `git grep -l 'implements RuntimeAdapter' packages/daemon/src \| wc -l` |
|
||||
|
||||
> Footprint counts use the `find packages/*/src -type f \( -name '*.ts' -o
|
||||
> -name '*.tsx' \) ! -name '*.test.*'` predicate (tests live in separate
|
||||
> `packages/*/test/` dirs). Counts are reproducible with that predicate; a
|
||||
> different counting convention shifts absolute numbers (slice-00 OPEN-5).
|
||||
`OmpRuntimeAdapter` (Oh My Pi) extends `PiRuntimeAdapter`, so the daemon wires
|
||||
six runtime keys — `claude-code`, `codex`, `pi`, `omp`, `stub`, `terminal`
|
||||
(`startup.ts:989`).
|
||||
|
||||
### The stack
|
||||
|
||||
> Drift-fix D2 — `architecture.md` L37,125 said `CLI (53 command groups)`.
|
||||
> Corrected to **58** at HEAD (slice-00 §1.2: 53 at v0.2.0 tag, 56 at v0.3.0,
|
||||
> 57 at v0.3.1, **58 at HEAD** — the 58th is `scopeCommand()` added post-0.3.1
|
||||
> by `0b77cba4`, re-confirmed `index.ts:20,187`).
|
||||
>
|
||||
> Drift-fix D4 — `architecture.md` L40 said `31 route groups` and L76 said
|
||||
> "`createApp()` now mounts 22 route groups". Corrected to **49 `app.route()`
|
||||
> mounts + 4 dedicated handlers** (slice-00 §1.5; re-confirmed `server.ts`
|
||||
> 49×`app.route(` L450–513, plus `app.get("/healthz")` :446,
|
||||
> `handleExportYaml` :461, `handleExportJson` :462, `app.get("*")` :519).
|
||||
> Carry slice-00 **OPEN-2**: "route-group count is definitional" — there is
|
||||
> no single canonical "route group" definition in source; reported as mount
|
||||
> count + dedicated handlers.
|
||||
>
|
||||
> Drift-fix D3 — `architecture.md` L54 said `SQLite state (36 migrations)`.
|
||||
> Corrected to **40** (slice-00 §1.3; re-confirmed below).
|
||||
|
||||
```text
|
||||
CLI (58 command groups) / UI (explorer + workspace + drawer) / MCP (17 tools)
|
||||
CLI (87 top-level commands) / TUI / web UI / MCP (18 tools)
|
||||
|
|
||||
v
|
||||
Hono daemon routes (49 app.route() mounts + 4 dedicated health/export/static handlers)
|
||||
Hono daemon routes (69 app.route() mounts + direct handlers for /healthz,
|
||||
spec export, unknown /api paths and the static UI)
|
||||
|
|
||||
+-- dual-format route adapters (legacy v1 + rebooted v0.2)
|
||||
+-- rigspec routes (pod-aware and legacy spec formats)
|
||||
+-- env routes (status / logs / down)
|
||||
+-- transport routes (send/capture/broadcast)
|
||||
+-- transcript routes (tail/grep)
|
||||
+-- ask routes (context evidence packs)
|
||||
+-- transport routes (send / capture / broadcast)
|
||||
+-- transcript routes (tail / grep)
|
||||
+-- ask routes (context evidence)
|
||||
+-- chat routes (durable rig messaging + SSE)
|
||||
+-- spec review/library routes (managed-app enrichment + compose preview)
|
||||
+-- whoami identity + context-usage route
|
||||
+-- coordination routes (stream / queue / workflow / mission-control)
|
||||
+-- spec review / spec library routes
|
||||
+-- whoami identity route
|
||||
+-- coordination routes (stream / queue / workflow / mission control)
|
||||
|
|
||||
v
|
||||
Framework-free domain services (173 daemon domain files)
|
||||
Framework-free domain services (431 files under packages/daemon/src/domain)
|
||||
|
|
||||
+-- SQLite state (40 migrations)
|
||||
+-- SQLite state (96 migrations)
|
||||
+-- tmux / cmux / resume adapters
|
||||
+-- runtime adapters (Claude Code / Codex / Terminal)
|
||||
+-- RigEnv substrate (compose adapter, readiness, orchestrator)
|
||||
+-- runtime adapters (Claude Code / Codex / Pi / Oh My Pi / Stub / Terminal)
|
||||
+-- rig environment services (compose adapter, service readiness, orchestrator)
|
||||
+-- transport / transcript / chat / ask layers
|
||||
+-- whoami identity service
|
||||
+-- whoami identity service (including context usage)
|
||||
```
|
||||
|
||||
The core product loop: `down (auto-snapshot) → up <rig-name> (auto-restore) →
|
||||
handoff → inspect/attach → work → repeat`.
|
||||
|
||||
> MCP tool count is **17** (`rig_*`) — slice-00 §1.4 confirms the count is
|
||||
> correct and the names are `rig_*` (NOT `rigged_*`; the `rigged_*` text in
|
||||
> `architecture.md` §2 is stale — the rename predates v0.2.0). Per-occurrence
|
||||
> verification of any `rigged_*` reference lands in `transport-and-transcripts.md`
|
||||
> (D5); the tmux `@rigged_*` metadata keys are a separate axis, not blanket-replaced.
|
||||
|
||||
## 2. Database schema
|
||||
|
||||
> Drift-fix D3 — `architecture.md` L243 said "27 migrations (22 existing plus
|
||||
> 5 added by PL-004 Phase A)" and L1002 repeated "27 migrations"; the §1 stack
|
||||
> diagram L54 said "36 migrations". All corrected to **40** migrations
|
||||
> (`001`–`040`). Triangulated (slice-00 §1.3, re-confirmed at HEAD): (1)
|
||||
> filesystem `packages/daemon/src/db/migrations/[0-9][0-9][0-9]_*.ts` → 40
|
||||
> files, `001_core_schema.ts` … `040_workflow_specs_diagnostic.ts`; (2)
|
||||
> `startup.ts:206` `migrate(db, [...])` passes a 40-element schema array
|
||||
> (`coreSchema` … `workflowSpecsDiagnosticSchema`); (3) `migrate.ts:13`
|
||||
> applies them sorted by name, tracked in `schema_migrations`.
|
||||
The migrations live in `packages/daemon/src/db/migrations/` (96 files,
|
||||
`001_core_schema.ts` … `097_thread_part_map.ts`, with no `093`).
|
||||
`ALL_MIGRATIONS` (`packages/daemon/src/db/all-migrations.ts:107`) lists all 96, and `createDaemon`
|
||||
applies them with `migrate(db, ALL_MIGRATIONS)` (`startup.ts:298`). `migrate.ts`
|
||||
sorts them by name (`:29`) and records each applied name in `schema_migrations`
|
||||
(`:15`).
|
||||
|
||||
### Core state tables (`001_core_schema.ts`)
|
||||
### Core state tables
|
||||
|
||||
`rigs` (topology container, `001_core_schema.ts:7`), `nodes` (logical node
|
||||
identity, `:17`), `edges` (logical topology relationships, `:30`), plus
|
||||
`bindings` (physical tmux/cmux surface attachment), `sessions` (live execution
|
||||
state), `events` (append-only event log), `snapshots` (serialized rig state),
|
||||
`checkpoints` (per-node recovery state). `architecture.md` §3 L247–289.
|
||||
identity, `:17`) and `edges` (logical topology relationships, `:30`); then
|
||||
`bindings` (physical tmux/cmux surface attachment) and `sessions` (live
|
||||
execution state) in `002_bindings_sessions.ts`, `events` (append-only event log)
|
||||
in `003_events.ts`, `snapshots` (serialized rig state) in `004_snapshots.ts`, and
|
||||
`checkpoints` (per-node recovery state) in `005_checkpoints.ts`.
|
||||
|
||||
### Reboot-era schema
|
||||
### Pod-aware schema
|
||||
|
||||
- `014_agentspec_reboot.ts` — reboot schema shape; adds `pods`,
|
||||
`continuity_state`, and reboot columns on `nodes`/`sessions`/`checkpoints`
|
||||
(`pod_id`, `agent_ref`, `resolved_spec_*`, `startup_status`,
|
||||
`continuity_source`). `architecture.md` §3 L253–289.
|
||||
- `014_agentspec_reboot.ts` — the pod-aware schema; adds the `pods` and
|
||||
`continuity_state` tables and pod-aware columns on `nodes`, `sessions` and
|
||||
`checkpoints`.
|
||||
- `015_startup_context.ts` — persisted startup replay context for restore.
|
||||
- `016_chat_messages.ts` — durable rig-scoped chat (SQLite-backed; transcripts
|
||||
remain filesystem-backed via pipe-pane).
|
||||
remain filesystem-backed, written by bounded periodic `capture-pane` snapshots;
|
||||
see `transport-and-transcripts.md`).
|
||||
- `017_pod_namespace.ts` — first-class authored pod namespace for export/adoption.
|
||||
- `018_context_usage.ts` — per-node context-usage snapshots.
|
||||
- `019_external_cli_attachment.ts` — binding-row extension for external CLI attach.
|
||||
- `020_rig_services.ts` — rig-scoped environment record for service-backed rigs.
|
||||
- `021_seat_handover_observability.ts`, `022_node_codex_config_profile.ts`.
|
||||
|
||||
### Coordination / PL-004 / PL-005 / workspace migrations
|
||||
### Coordination, workflow, mission control and workspace migrations
|
||||
|
||||
- `023_stream_items.ts` … `027_outbox_entries.ts` — PL-004 Phase A
|
||||
coordination tables (detail in `coordination-primitive.md`).
|
||||
- `023_stream_items.ts` … `027_outbox_entries.ts` — the coordination tables
|
||||
(stream, queue, queue transitions, inbox, outbox; detail in
|
||||
`coordination-primitive.md`).
|
||||
- `028_project_classifications.ts`, `029_classifier_leases.ts`,
|
||||
`030_views_custom.ts` — PL-004 Phase B classifier + view tables. (Note:
|
||||
`architecture.md` L361 said "028 through 030"; the middle migration is
|
||||
literally `029_classifier_leases.ts` — re-confirmed at HEAD.)
|
||||
- `031_watchdog_jobs.ts`, `032_watchdog_history.ts` — PL-004 Phase C watchdog.
|
||||
`030_views_custom.ts` — classifier and view tables.
|
||||
- `031_watchdog_jobs.ts`, `032_watchdog_history.ts` — watchdog tables.
|
||||
- `033_workflow_specs.ts`, `034_workflow_instances.ts`,
|
||||
`035_workflow_step_trails.ts` — PL-004 Phase D Workflow Runtime tables
|
||||
(detail in `workflow-runtime.md`). `036_watchdog_policy_enum_extension.ts`
|
||||
is a documenting no-op.
|
||||
- `037_mission_control_actions.ts` — PL-005 Phase A audit table
|
||||
(`037_mission_control_actions.ts:54` `CREATE TABLE … mission_control_actions`;
|
||||
detail in `mission-control.md`).
|
||||
- `038_workspace_primitive.ts`, `039_queue_target_repo.ts` — PL-007 typed
|
||||
workspace primitive (`bab24bf7`).
|
||||
- `040_workflow_specs_diagnostic.ts` — slice-11 (`f68f453a`); an
|
||||
`ALTER TABLE ADD COLUMN` adding parser/validator diagnostic columns to
|
||||
`workflow_specs` (no new table). **Net-new since `architecture.md` was last
|
||||
edited** — slice-00 §1.3 provenance; carried into `workflow-runtime.md`.
|
||||
`035_workflow_step_trails.ts` — workflow runtime tables (detail in
|
||||
`workflow-runtime.md`). `036_watchdog_policy_enum_extension.ts` runs no DDL (its
|
||||
SQL is a no-op `SELECT 1 WHERE 0`); it documents an enum extension.
|
||||
- `037_mission_control_actions.ts` — the mission control audit table
|
||||
(`037_mission_control_actions.ts:58`; detail in `mission-control.md`).
|
||||
- `038_workspace_primitive.ts`, `039_queue_target_repo.ts` — the typed workspace
|
||||
primitive.
|
||||
- `040_workflow_specs_diagnostic.ts` — `ALTER TABLE workflow_specs ADD COLUMN`
|
||||
for parser/validator diagnostics (no new table).
|
||||
|
||||
Legacy package/bootstrap/discovery tables (`packages`, `package_installs`,
|
||||
`install_journal`, `bootstrap_runs`, `bootstrap_actions`,
|
||||
`runtime_verifications`, `discovered_sessions`) remain active.
|
||||
Migrations `041`–`092` and `094`–`097` continue in the same directory; list them with
|
||||
`git ls-files packages/daemon/src/db/migrations`. `095_rig_non_interruptive.ts` adds
|
||||
`rigs.non_interruptive` (0 or 1, default 0), the saved per-rig choice behind
|
||||
`rig up --non-interruptive`; `096_rig_install_root.ts` adds `rigs.install_root`, the rig's
|
||||
bundle materialization folder (`rig-repository.ts:169`). The newest, `097_thread_part_map.ts`,
|
||||
adds `thread_part_map`, which maps each message OpenRig posts into an ask's Slack thread to
|
||||
that ask and its seat.
|
||||
|
||||
The package, bootstrap and discovery tables remain: `packages`
|
||||
(`008_packages.ts`), `package_installs` and `install_journal`
|
||||
(`009_install_journal.ts`), `bootstrap_runs`, `bootstrap_actions` and
|
||||
`runtime_verifications` (`011_bootstrap.ts`), and `discovered_sessions`
|
||||
(`012_discovery.ts`).
|
||||
|
||||
## 3. Route-mount surface
|
||||
|
||||
`createApp(deps)` (`packages/daemon/src/server.ts:295`) mounts **49**
|
||||
`app.route()` route-group mounts (`server.ts` L450–513) plus 4 dedicated
|
||||
non-route handlers: `GET /healthz` (`:446`), `GET /api/rigs/:rigId/spec`
|
||||
(`handleExportYaml`, `:461`), `GET /api/rigs/:rigId/spec.json`
|
||||
(`handleExportJson`, `:462`), and the static/deep-link `app.get("*")`
|
||||
catch-all (`:519`).
|
||||
`createApp(deps)` (`packages/daemon/src/server.ts:442`) mounts **69**
|
||||
`app.route()` route groups (`server.ts:723`–`839`) plus direct handlers:
|
||||
`GET /healthz` (`:664`), `GET /api/rigs/:rigId/spec` (`handleExportYaml`,
|
||||
`:735`), `GET /api/rigs/:rigId/spec.json` (`handleExportJson`, `:736`), a JSON
|
||||
`404 not_found` for any other `/api/*` path (`:844`), and the static/deep-link
|
||||
`app.get("*")` catch-all (`:853`).
|
||||
|
||||
> OPEN-2 (carried verbatim, slice-00): the route-group "count" is definitional
|
||||
> — "49" is the count of `app.route()` mounts in `server.ts`. The older
|
||||
> "31 route groups" / "createApp now mounts 22 route groups" framing
|
||||
> (`architecture.md` L40,76) used a different, smaller accounting. Independent
|
||||
> corroboration: `packages/daemon/src/routes/` has 46 non-test route `.ts`
|
||||
> files (some groups composed from shared modules; the 49 mounts are the
|
||||
> authoritative count of mounted groups — slice-00 §1.5).
|
||||
The 69 is the count of `app.route(` lines in `server.ts`; `packages/daemon/src/routes/`
|
||||
has 68 files, 65 of which create a Hono router.
|
||||
|
||||
Mount families include reboot-era rig/session/spec routes plus the coordination
|
||||
routes (`/api/stream` `server.ts:489`, `/api/queue` `:490`,
|
||||
`/api/workflow` `:495`, `missionControlRoutes(...)` `:498`),
|
||||
`/api/health-summary` (`:511`), `/api/rigs/:rigId/env` (`:512`), and
|
||||
`/api/restore-check` (`:513`).
|
||||
Mount families include the rig, session and spec routes plus the coordination
|
||||
routes (`/api/stream` `server.ts:783`, `/api/queue` `:784`, `/api/workflow`
|
||||
`:789`, `missionControlRoutes(...)` `:792`), `/api/seat` (`:781`, the seat
|
||||
lifecycle verbs such as handover, launch, continue, stop and the typing guard),
|
||||
`/api/startup` (`:747`), `/api/terminal` (`:809`), `/api/telemetry` (`:822`),
|
||||
`/api/health-summary` (`:829`), `/api/rigs/:rigId/env` (`:835`) and
|
||||
`/api/restore-check` (`:836`). List every mount with
|
||||
`grep -n 'app.route(' packages/daemon/src/server.ts`.
|
||||
|
||||
Before the routes, `createApp` installs middleware in this order: the request-phase
|
||||
observer (`:497`), JSON body-parse error tracking, which turns a malformed body into
|
||||
`400 invalid_json` (`:499`), dependency injection, route timing, slow-operation
|
||||
recording, the browser boundary on `/api/*` (`:647`, Host and Origin checks; see
|
||||
`OPENRIG_ALLOWED_HOSTS` and `OPENRIG_ALLOWED_ORIGINS`), and host read-through.
|
||||
|
||||
`createAppWithWebSocket(deps)` (`server.ts:893`) sets `enableNodeWebSocket` and
|
||||
calls `createApp`; the terminal WebSocket is registered only when the web UI is
|
||||
also enabled (`server.ts:756`). Otherwise only its bearer-auth handler is
|
||||
registered (`registerTerminalAuthOnly`, `:762`).
|
||||
|
||||
## 4. Startup sequence (`createDaemon`)
|
||||
|
||||
`createDaemon(opts?)` is `packages/daemon/src/startup.ts:203` (async,
|
||||
returns `DaemonResult`). It returns `{ app, db, deps, contextMonitor }`
|
||||
(`startup.ts:1204`). The sequence (`architecture.md` §9 L1000–1028,
|
||||
corrected against source):
|
||||
`createDaemon(opts?)` is `packages/daemon/src/startup.ts:287` (async, returns
|
||||
`DaemonResult`). It returns `{ app, db, deps, contextMonitor, eventLoopMonitor,
|
||||
injectWebSocket }` (`startup.ts:2506`). In source order, it:
|
||||
|
||||
1. Open SQLite and run **all 40 migrations** (`startup.ts:206`
|
||||
`migrate(db, [coreSchema … workflowSpecsDiagnosticSchema])` — a 40-element
|
||||
array; `architecture.md` L1002 said "27 migrations (22 existing plus the 5
|
||||
PL-004 Phase A coordination tables)" — corrected to 40, drift-fix D3).
|
||||
2. Construct core repositories and legacy services.
|
||||
3. Construct package/bootstrap/discovery services.
|
||||
4. Construct rebooted startup/runtime services: `StartupOrchestrator`,
|
||||
`ClaudeCodeAdapter`, `CodexRuntimeAdapter`, `TerminalAdapter`,
|
||||
`PodRigInstantiator`, `PodBundleSourceResolver`.
|
||||
5. Construct rig environment services: `ComposeServicesAdapter`,
|
||||
`ServiceOrchestrator`.
|
||||
6. Construct operator/transport/history services: `TranscriptStore`,
|
||||
`SessionTransport`, `ChatRepository`, `AskService` (with `HistoryQuery`),
|
||||
`ResumeMetadataRefresher`, `ContextUsageStore`, `ContextMonitor`,
|
||||
`NodeInventory`.
|
||||
7. Construct authoring/identity/managed-app services: `SpecReviewService`,
|
||||
`SpecLibraryService`, `WhoamiService`.
|
||||
8. Construct `BootstrapOrchestrator` with both legacy and rebooted seams.
|
||||
9. Construct PL-004 Phase A coordination services from a shared
|
||||
`QueueRepository` instance (so `InboxHandler.absorb()` and `/api/queue`
|
||||
write to the same repo): `StreamStore`, `QueueRepository`, `InboxHandler`,
|
||||
`OutboxHandler`.
|
||||
10. Build `AppDeps`, enforce shared-DB invariants, and call
|
||||
`createApp(deps)` (`startup.ts:1202`) to mount the full route tree.
|
||||
1. Opens SQLite and applies all 96 migrations (`migrate(db, ALL_MIGRATIONS)`,
|
||||
`startup.ts:298`).
|
||||
2. Constructs the coordination stores early: `StreamStore` (`:330`),
|
||||
`QueueRepository` (`:428`) and `OutboxHandler` (`:443`).
|
||||
3. Constructs `TranscriptStore` (`:555`) and the rig environment services
|
||||
`ComposeServicesAdapter` and `ServiceOrchestrator` (`:644`–`645`).
|
||||
4. Constructs `StartupOrchestrator` (`:776`) and the runtime adapters:
|
||||
`ClaudeCodeAdapter` (`:778`), `CodexRuntimeAdapter` (`:779`),
|
||||
`PiRuntimeAdapter` (`:782`), `OmpRuntimeAdapter` (`:783`) and
|
||||
`StubRuntimeAdapter` (`:788`); the terminal adapter is created inline in the
|
||||
runtime adapter maps (`:989`, `:1259`).
|
||||
5. Constructs `PodRigInstantiator` (`:985`), `PodBundleSourceResolver` (`:1014`)
|
||||
and `BootstrapOrchestrator` (`:1016`).
|
||||
6. Constructs `ContextUsageStore` (`:1095`), `ResumeMetadataRefresher`
|
||||
(`:1129`), `SpecReviewService` (`:1154`) and `WhoamiService` (`:1157`).
|
||||
7. Builds `AppDeps` (`const deps: AppDeps`, `:1191`). `SessionTransport`
|
||||
(`:1262`), `ChatRepository` (`:1296`), `InboxHandler` (`:1300`), `AskService`
|
||||
(`:1349`) and `SpecLibraryService` (`:1387`) are constructed inside that
|
||||
object literal. `InboxHandler` receives the same queue repository instance
|
||||
(`queueRepoInstance`) that serves `/api/queue`, so absorbed inbox items and the
|
||||
queue route write to one store.
|
||||
8. Constructs `ContextMonitor` (`:2407`), then assigns later fields onto `deps`
|
||||
(among them the terminal service, the request-phase observer and the gateway
|
||||
subsystem).
|
||||
9. Calls `createAppWithWebSocket(deps)` (`startup.ts:2504`) to mount the full
|
||||
route tree.
|
||||
|
||||
The daemon entrypoint `packages/daemon/src/index.ts:36` calls
|
||||
`createDaemon({ dbPath, bearerToken })`.
|
||||
Node inventory is a set of functions (`getNodeInventory` and friends in
|
||||
`domain/node-inventory.ts`), imported where needed rather than constructed at
|
||||
startup.
|
||||
|
||||
## 5. Test and verification state
|
||||
The daemon entrypoint `packages/daemon/src/index.ts:306` calls
|
||||
`createDaemon({ dbPath, bearerToken, terminalBearerToken, … })`.
|
||||
|
||||
> Drift-fix D7 / OPEN-3 (carried verbatim, slice-00): `architecture.md` L12,13
|
||||
> and §10 L1036–1046 assert daemon `2561/2561` / CLI `794/794` / total
|
||||
> `2422/2422` test pass counts and per-package file counts (`127`/`37`/`37`).
|
||||
> **These are runtime claims and are NOT re-run here** — a read-only static
|
||||
> trace counted `*.test.ts` files only: daemon **255**, cli **234**
|
||||
> (slice-00 §1.7; ui test-file count not separately gathered). Pass tallies
|
||||
> are marked `unverified-runtime-claim`; do not assert them as current. To
|
||||
> assert pass counts, run `pnpm/npm test` and re-verify.
|
||||
## 5. Test files
|
||||
|
||||
A static count of tracked test files at `e8f0ab340db773392ec8be75b072d1c0f3068a50` (no pass counts are claimed
|
||||
here; CI runs the suites in `.github/workflows/tests.yml`): daemon **927**,
|
||||
CLI **261**, web UI **198**, TUI **105**
|
||||
(`git ls-files 'packages/<package>/**/*.test.ts' 'packages/<package>/**/*.test.tsx' | wc -l`).
|
||||
|
||||
## See also
|
||||
|
||||
- `coordination-primitive.md` — PL-004 Phase A stream/queue/inbox/outbox.
|
||||
- `ARCHITECTURE.md` (repository root) — what OpenRig is and where to add things.
|
||||
- `coordination-primitive.md` — the stream/queue/inbox/outbox coordination layer.
|
||||
- `agent-spec-and-startup.md` — spec parsing/resolution/startup contract.
|
||||
- `lifecycle-snapshot-restore.md` — snapshot/restore/continuity.
|
||||
- Source roots: `packages/daemon/src/{startup.ts,server.ts,index.ts}`,
|
||||
`packages/daemon/src/db/{all-migrations.ts,migrate.ts}`,
|
||||
`packages/daemon/src/db/migrations/`, `packages/cli/src/index.ts`.
|
||||
@@ -6,12 +6,13 @@ topics: [continuity, runtime-control]
|
||||
domains: [engineering-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
Need to know how OpenRig captures a snapshot, restores a rig (resume vs
|
||||
rebuild vs fresh), enforces restore honesty, consults live continuity state,
|
||||
or how the daemon-side restore-check / restore-packet readiness probes work.
|
||||
rebuild vs fresh-primed vs stop-and-ask), enforces restore honesty, consults
|
||||
live continuity state, or how the daemon-side restore-check readiness probe
|
||||
and the CLI restore-packet command work.
|
||||
siblings: [daemon-core.md, agent-spec-and-startup.md, transport-and-transcripts.md]
|
||||
prerequisite-reads: [../README.md, agent-spec-and-startup.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Lifecycle — Snapshot, Restore, Continuity
|
||||
@@ -21,194 +22,377 @@ The durable-state half of the core product loop:
|
||||
captures serialized rig state; restore replays it honestly (no silent
|
||||
fresh-fallback); restore-check is a separate read-only readiness probe.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c` (`git describe` →
|
||||
> `v0.3.1-6-g7eaf524c`). Package version **0.3.1** (slice-00 §1.1). Source
|
||||
> located by `architecture.md` headings (§4 Execution and restore, §5
|
||||
> Snapshot/restore/continuity, §6 Snapshot/restore + Auto-snapshot, §7 rules)
|
||||
> per slice-08 §10.1 — line numbers advisory only. This module is a pure
|
||||
> split EXCEPT §6 (restore-check/restore-packet) which is the D15
|
||||
> author-from-source piece — flagged explicitly there.
|
||||
> Verified against source at main `e8f0ab340db773392ec8be75b072d1c0f3068a50`. Each count below sits beside the
|
||||
> command that produces it; run the command from the repository root to refresh
|
||||
> it.
|
||||
|
||||
## 1. Snapshot / restore / continuity types
|
||||
|
||||
(`architecture.md` §4 "Execution and restore" — spec/projection members live
|
||||
in `agent-spec-and-startup.md`; the snapshot/restore members live here)
|
||||
All in `packages/daemon/src/domain/types.ts`. The spec/projection types live in
|
||||
`agent-spec-and-startup.md`; the snapshot/restore types live here.
|
||||
|
||||
- **NodeRestoreOutcome** — `"resumed" | "rebuilt" | "fresh" | "failed" |
|
||||
"n-a"` — the locked restore vocabulary. Re-confirmed at source:
|
||||
`restore-orchestrator.ts:725-726` sets `baseStatus = "resumed"` on a
|
||||
resumed harness; `:763` sets `"rebuilt"` when a checkpoint is replayed; the
|
||||
outcome union appears at `:944` (`{ kind: "resumed" }` … ).
|
||||
- **SnapshotData** — current serialized snapshot payload. Reboot extensions
|
||||
are optional for older-snapshot compatibility: `pods?`,
|
||||
`continuityStates?`, `nodeStartupContext?`.
|
||||
- **NodeStartupSnapshot** — persisted restore replay input:
|
||||
- **NodeRestoreOutcome** (`types.ts:500`) — the per-node restore outcome
|
||||
projected into `rig ps`. **9** values
|
||||
(`grep '^export type NodeRestoreOutcome' packages/daemon/src/domain/types.ts | grep -o '"[^"]*"' | wc -l`):
|
||||
`resumed`, `rebuilt`, `fresh`, `fresh-primed`, `awaiting-decision`,
|
||||
`failed`, `attention_required`, `operator_recovered`, `n-a`. The restore
|
||||
result itself, `RestoreNodeResult.status` (`types.ts:475`), has the same
|
||||
values without `n-a`: **8**
|
||||
(`grep -E '^ status: "resumed"' packages/daemon/src/domain/types.ts | grep -o '"[^"]*"' | wc -l`).
|
||||
Where `restore-orchestrator.ts` sets each one:
|
||||
- `resumed` — `baseStatus = "resumed"` after a legacy resume (`:1030`); the
|
||||
status is returned only by `finishJoinedResume` (defined `:1320`, called
|
||||
`:1244` and `:1311`, returning `resumed` at `:1380`) after it has rebound
|
||||
and verified the pane; otherwise that becomes `attention_required`
|
||||
(`:1352`, `:1376`).
|
||||
- `rebuilt` — a checkpoint file was written (`:1090`).
|
||||
- `fresh-primed` — the default for a deliberate non-resume launch
|
||||
(`:987`).
|
||||
- `fresh` — only the skip path for a pod node whose live continuity state
|
||||
is already `restoring` (`:785`).
|
||||
- `awaiting-decision` — the seat could not resume and nothing is running:
|
||||
before launch (`:820`), or after launch once the blank session is rolled
|
||||
back (`:1026`, `:1048`, `:1061`, `:1077`, `:1236`).
|
||||
- `attention_required` — a live session waiting on a runtime prompt
|
||||
(`:1039`, `:1262`) or a harness that never started (`:1125`).
|
||||
- `failed` — occupant ambiguity (`:772`, `:969`), launch failure (`:911`),
|
||||
a checkpoint that cannot be delivered (`:1086`, `:1092`), a missing
|
||||
required startup file (`:1161`), or a startup error (`:1271`, `:1282`,
|
||||
`:1292`, `:1300`).
|
||||
- `operator_recovered` — only from later reconciliation,
|
||||
`reconcileNodeRuntimeTruth` (`:1590`, event at `:1727`).
|
||||
- **RestoreRigResult** (`types.ts:444`) — the rig-level rollup, **4** values
|
||||
(`grep '^export type RestoreRigResult' packages/daemon/src/domain/types.ts | grep -o '"[^"]*"' | wc -l`):
|
||||
`fully_restored`, `partially_restored`, `failed`, `not_attempted`.
|
||||
`rollupRestoreRigResult` (`restore-orchestrator.ts:81`) produces only the
|
||||
first three; `not_attempted` is set when pre-restore validation fails
|
||||
(`:281`).
|
||||
- **SnapshotData** (`types.ts:351`) — the serialized snapshot payload. It has
|
||||
**7** optional fields, kept optional so older snapshots still load
|
||||
(`sed -n '/^export interface SnapshotData/,/^}/p' packages/daemon/src/domain/types.ts | grep -c '?:'`):
|
||||
`activeSessionIdByNode?`, `activeOccupantsByNode?`, `topologyRoster?`,
|
||||
`pods?`, `continuityStates?`, `nodeStartupContext?`, `envReceipt?`.
|
||||
- **NodeStartupSnapshot** (`types.ts:344`) — persisted restore replay input:
|
||||
classification-free projection entries, resolved startup files, startup
|
||||
actions, runtime.
|
||||
- **PersistedProjectionEntry** — the classification-free restore replay seam:
|
||||
persists only entry identity + source metadata, NOT stale `classification`,
|
||||
`conflicts`, or `noOps` (Architecture Rule 10).
|
||||
- **PersistedProjectionEntry** (`types.ts:332`) — the classification-free
|
||||
restore replay seam: persists only entry identity + source metadata, NOT
|
||||
stale `classification`, `conflicts`, or `noOps` (Architecture Rule 10).
|
||||
Restore rebuilds each entry as `safe_projection` with empty `conflicts` and
|
||||
`noOps` (`restore-orchestrator.ts:1177`, `:1182`, `:1183`).
|
||||
|
||||
## 2. Snapshot, restore, continuity domain services
|
||||
|
||||
(`architecture.md` §5 "Snapshot, restore, and continuity")
|
||||
All under `packages/daemon/src/domain/`:
|
||||
|
||||
- `checkpoint-store.ts` — checkpoint persistence with pod/continuity context.
|
||||
- `snapshot-capture.ts` — captures pods, continuity state, startup replay
|
||||
context, and latest env receipt. Re-confirmed: `snapshot-capture.ts:66-77`
|
||||
pulls `pods`, `continuity_state`, and `node_startup_context` rows per rig.
|
||||
- `snapshot-repository.ts` — snapshot CRUD.
|
||||
context, the latest env receipt, the active-occupant maps and the intended
|
||||
topology roster. `captureSnapshot` (`:60`) derives the occupant maps
|
||||
(`:73–79`), resolves the roster (`:81–90`), then reads `pods` (`:96`),
|
||||
`continuity_state` (`:100`) and `node_startup_context` (`:106`) rows per rig
|
||||
and the env receipt (`:116–123`).
|
||||
- `snapshot-repository.ts` — snapshot CRUD and restore-source selection:
|
||||
`findLatestRestoreUsable` (`:72`) and `selectRestoreUsable` (`:94`), which
|
||||
accept only snapshots that pass `isRestoreUsableSnapshotData` (`:250`).
|
||||
- `restore-orchestrator.ts` — resume, checkpoint delivery, startup replay,
|
||||
live continuity consultation, topology ordering, and RigEnv boot gating
|
||||
before agent restore.
|
||||
live continuity consultation, topology ordering (`computeRestorePlan`,
|
||||
`:676`, ordering only `delegates_to` and `spawned_by` edges, `:71`, among
|
||||
the roster's intended nodes, `:677`), and service boot gating before agent
|
||||
restore (`serviceOrchestrator.boot`, `:331`).
|
||||
- `restore-preconditions.ts` — `validatePreRestore` (`:7`), the one
|
||||
pre-restore validation shared by restore and restore-check (§3, §5.1).
|
||||
- `restore-topology.ts` — `resolveSnapshotRestoreTopology` (`:12`), which
|
||||
splits a snapshot's nodes into intended and excluded (§3).
|
||||
- `restore-attempt-receipt.ts` — `deriveRestoreAttemptReceipt` (`:42`), the
|
||||
read-only per-attempt receipt (§4).
|
||||
- `rehydrate-eligibility.ts` — `snapshotMatchesCurrentOccupants` (`:20`) and
|
||||
`assessCurrentStateRehydrateEligibility` (`:69`), shared by `routes/up.ts`
|
||||
and `routes/restore-check.ts`.
|
||||
- `active-occupant.ts` — the shared rule for which snapshot session row is a
|
||||
node's occupant (`resolveActiveSnapshotSession`, `:73`).
|
||||
|
||||
## 3. Restore flow and restore honesty
|
||||
|
||||
(`architecture.md` §6 "Snapshot / restore" + §7 rules 7/14/15/16)
|
||||
Restore behavior, each point checked in `restore-orchestrator.ts`:
|
||||
|
||||
Restore behavior, each point re-confirmed at `restore-orchestrator.ts`:
|
||||
|
||||
- Reads the newest session by **monotonic ULID**, not timestamp alone
|
||||
(`:700` — "Find the NEWEST session for this node. ULIDs are monotonic, so
|
||||
latest = max id").
|
||||
- Selects the snapshot with `selectRestoreUsable` even when a snapshot id is
|
||||
given (`:251–252`), so wrong-rig and unusable snapshots are refused before
|
||||
any mutation. The selection (`RestoreSnapshotSelection`, `types.ts:396`)
|
||||
records whether it was explicit or automatic, why, and any newer usable
|
||||
alternative; it is carried on the result and on `restore.started`.
|
||||
- Restores only the nodes the snapshot's topology roster intends
|
||||
(`SnapshotData.topologyRoster`, `types.ts:370`). The roster comes from the
|
||||
latest `topology.roster_recorded` event (`snapshot-capture.ts:171`), from
|
||||
`rig snapshot <rigId> --intended-seats` (`packages/cli/src/commands/snapshot.ts:20`),
|
||||
or, for older snapshots, all current nodes. `resolveSnapshotRestoreTopology`
|
||||
(`restore-topology.ts:12`) reports every other node as excluded with reason
|
||||
`historical_not_in_intended_roster` (`:27`); the result carries
|
||||
`intendedRoster` and `excludedNodes` (`types.ts:440–441`).
|
||||
- Resolves each node's occupant from the snapshot's recorded active-occupant
|
||||
maps, not by picking the newest session row
|
||||
(`resolveActiveSnapshotSession`, `active-occupant.ts:74`, called at
|
||||
`restore-orchestrator.ts:774` and `:971`). An ambiguous occupant fails the
|
||||
node loudly (`activeOccupantAmbiguityError`, `active-occupant.ts:97`).
|
||||
- Consults live `continuity_state`; preserves state when a node is already
|
||||
`restoring` (`:611-615` — `SELECT status FROM continuity_state …`; if
|
||||
`status === "restoring"` the node is skipped with a warning).
|
||||
`restoring` (`:777–791` — `SELECT status FROM continuity_state …` at
|
||||
`:780`; if `status === "restoring"` the node is skipped with a warning and
|
||||
reported as `fresh`, `:783–785`; `degraded` only adds a warning).
|
||||
- Reuses the seat's durably bound tmux session name when the prior binding
|
||||
has a usable one (`:840`), and otherwise falls back to the derived name
|
||||
with a warning (`:869`).
|
||||
- Replays restore-safe startup using persisted startup context;
|
||||
prefilters missing optional artifacts into warnings; **hard-fails a node if
|
||||
a required startup file is missing** (`:799` — status `"failed"`, error
|
||||
"Missing required startup files: …").
|
||||
- Writes a **transcript boundary marker before re-launch** (`:652` — "Write
|
||||
transcript boundary marker BEFORE launch (before pipe-pane attaches)").
|
||||
- Refuses to restore over live sessions (`:168` — `rig_not_stopped`: "Rig …
|
||||
has live sessions. Stop the rig with 'rig down' before restoring").
|
||||
- Uses `nativeResumeProbe` to honestly assess whether the harness actually
|
||||
resumed (`:857` continuity-outcome reconciliation).
|
||||
a required startup file is missing** (`:1161` — status `"failed"`, error
|
||||
"Missing required startup files: …"). Before any mutation,
|
||||
`validatePreRestore` (`restore-preconditions.ts:7`, called at
|
||||
`restore-orchestrator.ts:271`) already blocks the whole restore when a node
|
||||
that will consume replay is missing a required startup file
|
||||
(`required_startup_file_missing`, `restore-preconditions.ts:151`; outcome
|
||||
`pre_restore_validation_failed`, `restore-orchestrator.ts:291`). Its other
|
||||
blockers are `invalid_snapshot_data` (`restore-preconditions.ts:27`),
|
||||
`invalid_topology_roster` (`:78`), `checkpoint_missing_node_cwd` (`:91`),
|
||||
`startup_owner_root_missing` (`:139`), `service_rig_root_missing` (`:187`)
|
||||
and `service_compose_file_missing` (`:197`). The `/api/up` route returns
|
||||
that outcome as HTTP 409 with `status: "not_attempted"`
|
||||
(`routes/up.ts:162`). A node that resumes its exact native session replays
|
||||
no startup files, actions, guidance or skills (`replayContained`,
|
||||
`restore-orchestrator.ts:1114`); its saved Claude activity-hook selection
|
||||
is reapplied before native resume. A skipped hook reapply adds a warning
|
||||
without changing the restore outcome.
|
||||
- Writes a **transcript boundary marker before re-launch**
|
||||
(`writeBoundaryMarker`, `:900`, called before `launchNode` at `:911`) when
|
||||
the transcript store is enabled.
|
||||
- Refuses to restore over live sessions (`:259–262` — `rig_not_stopped`: "Rig …
|
||||
has live sessions. Stop the rig with 'rig down' before restoring, or use the
|
||||
latest auto-pre-down snapshot."). A tmux probe that fails also blocks it
|
||||
(`classifyRunningSessions`, `:612`).
|
||||
- Checks that the harness actually resumed. The resume adapters judge the
|
||||
pane with `assessNativeResumeProbe` (`domain/native-resume-probe.ts:84`). A
|
||||
pod-aware resume counts as resumed only when startup reports `resumed`
|
||||
continuity or the launched session row carries the snapshot's resume type
|
||||
and token (`launchedSessionMatchesSnapshotResume`,
|
||||
`restore-orchestrator.ts:1390`); a reported `fresh` continuity without that
|
||||
proof rolls back to `awaiting-decision` (`:1228–1245`).
|
||||
- Saves a `rig up --non-interruptive` or `--no-non-interruptive` choice on
|
||||
the rig only after the live-session check and pre-restore validation pass
|
||||
(`:301`); see [adapters-and-runtimes.md](adapters-and-runtimes.md) for the
|
||||
launch flags.
|
||||
|
||||
**Restore-honesty rules (carried verbatim, `architecture.md` §7):**
|
||||
`launchNodeSubset` (`:392`) and `launchSingleNode` restore chosen seats rather
|
||||
than the whole rig; unlike a full restore, a failed tmux probe there proceeds
|
||||
with a `liveness_probe_unknown` warning (`:517`).
|
||||
|
||||
- Rule 7 — restore-policy narrowing is one-way only:
|
||||
`resume_if_possible` → `relaunch_fresh` → `checkpoint_only`.
|
||||
- Rule 14 — resume states are locked: `resumed` / `rebuilt` / `fresh`.
|
||||
`rebuilt` = new process assembled from artifacts.
|
||||
- Rule 15 — failed resume is FAILED loudly. No automatic fresh fallback.
|
||||
Fresh launch is explicit follow-up only.
|
||||
- Rule 16 — post-command handoff required on `up`, `down`, `restore`,
|
||||
`snapshot create`: what happened + current state + next action.
|
||||
**Restore-honesty rules** (texts in `architecture-rules-and-event-system.md`;
|
||||
cited here by number with the code that carries them):
|
||||
|
||||
(The full 25-rule list lives in `architecture-rules-and-event-system.md`.)
|
||||
- Rule 7 — `RESTORE_POLICY_LEVEL`
|
||||
(`packages/daemon/src/domain/profile-resolver.ts:106`) orders the three
|
||||
policies, and `resolveRestorePolicy` (`profile-resolver.ts:589`) rejects a
|
||||
profile or member value that broadens it.
|
||||
- Rule 14 — the outcome set is `RestoreNodeResult.status` (`types.ts:475`,
|
||||
§1); `rebuilt` is set only when a checkpoint is written
|
||||
(`restore-orchestrator.ts:1097`).
|
||||
- Rule 15 — in `restore-orchestrator.ts`, a `resume_if_possible` seat with a
|
||||
session but no token stops as `awaiting-decision` before launch unless
|
||||
`--fresh` names it (`:812–831`); a failed resume kills the blank session
|
||||
(`rollbackToZeroSession`, `:940`) and returns `awaiting-decision`
|
||||
(`:1055`); pod-aware resume passes
|
||||
`allowFreshFallback: !(isPodAware && resumeRequested)` (`:1224`). Fresh
|
||||
launch is explicit: `rig up --existing <rig> --fresh <seats...>`
|
||||
(`packages/cli/src/commands/up.ts:99`). On a terminal, `rig up` asks
|
||||
yes or no for each `awaiting-decision` seat and re-posts with the accepted
|
||||
seats as fresh (`up.ts:589`).
|
||||
- Rule 16 — `rig down` prints the snapshot id and a restore command
|
||||
(`packages/cli/src/commands/down.ts:293–303`); `rig up` on an existing rig
|
||||
prints per-node statuses and the attach command
|
||||
(`packages/cli/src/commands/up.ts:554–587`).
|
||||
|
||||
(The full rule list lives in `architecture-rules-and-event-system.md`.)
|
||||
|
||||
## 4. Auto-snapshot and existing-rig power-on
|
||||
|
||||
(`architecture.md` §6 "Auto-snapshot and existing-rig power-on")
|
||||
- `rig down <rig>` (name or id, `packages/cli/src/commands/down.ts:144`)
|
||||
first prints the rig's recorded agent sessions and their activity, best
|
||||
effort and only without `--json` (`showAgentsBeforeDown`, `down.ts:28`), then
|
||||
auto-captures an `auto-pre-down` snapshot before teardown when the rig has
|
||||
live sessions (`packages/daemon/src/domain/rig-teardown.ts:117`, capture
|
||||
at `:128`, after refreshing resume metadata at `:119`; an already-stopped
|
||||
rig returns at `:93–115` without one).
|
||||
- `rig up <rig-name>`: a source with no `/` and no
|
||||
`.yaml`/`.yml`/`.rigbundle`/`.rigtopology` extension is a rig name
|
||||
(`packages/daemon/src/domain/up-command-router.ts:49–50`). The CLI refuses
|
||||
a name that matches only archived rigs, with `rig unarchive` guidance
|
||||
(`packages/cli/src/commands/up.ts:256`). It then checks the spec library
|
||||
unless `--existing` is given, and refuses a name that matches both
|
||||
(`up.ts:306–325`); when `first-project` resolves to the built-in `starter`
|
||||
spec, the archived and existing-rig checks also apply to `starter`
|
||||
(`up.ts:314–317`). The daemon finds the rig by name, preferring unarchived
|
||||
rigs (`packages/daemon/src/routes/up.ts:223–226`), and restores from
|
||||
`selectRestoreUsable` (`routes/up.ts:106`), which prefers the newest
|
||||
`auto-pre-down` or `auto-periodic` snapshot, then the newest other usable
|
||||
one (`snapshot-repository.ts:75`).
|
||||
- `rig up <rig-name> --plan` is a read-only restore preview that never
|
||||
captures a snapshot (`routes/up.ts:127`).
|
||||
- If no usable snapshot exists, or the chosen one names an older occupant
|
||||
(`snapshotMatchesCurrentOccupants`, `routes/up.ts:110`), `rig up` captures
|
||||
an `auto-rehydrate` snapshot of current DB state when that state is
|
||||
eligible (`routes/up.ts:137–146`). Otherwise it errors with code
|
||||
`no_snapshot` and guidance ("… current DB state is insufficient for
|
||||
rehydrate. Start fresh with: rig up <spec-path>", `routes/up.ts:118–123`).
|
||||
- Post-command handoff: `down` output includes the snapshot ID and
|
||||
`To restore: rig up <name>` (or `rig restore <snapshotId> --rig <rigId>`
|
||||
when the name is not unique); `up` output includes node statuses and an
|
||||
`Attach:` command (anchors under Rule 16 above).
|
||||
- `rig restore status <attemptId> --rig <rigId>`
|
||||
(`packages/cli/src/commands/restore.ts:120`) reads
|
||||
`GET /api/rigs/:rigId/restore/status/:attemptId`
|
||||
(`packages/daemon/src/routes/snapshots.ts:76`). `deriveRestoreAttemptReceipt`
|
||||
(`restore-attempt-receipt.ts:42`) folds that attempt's events into a
|
||||
per-node view without writing anything.
|
||||
|
||||
- `rig down <rigId>` auto-captures an `auto-pre-down` snapshot before
|
||||
teardown.
|
||||
- `rig up <rig-name>` (no file extension) searches for an existing rig by
|
||||
name and restores from the latest `auto-pre-down` snapshot.
|
||||
- If no snapshot: error with guidance ("No saved snapshot for rig 'X'. Boot
|
||||
from a spec or bundle path.").
|
||||
- Post-command handoff: `down` output includes snapshot ID + restore command;
|
||||
`up` output includes node statuses + attach command.
|
||||
### Single-seat recovery and handover
|
||||
|
||||
## 5. Daemon-side restore-check / restore-packet — AUTHORED FROM SOURCE (D15)
|
||||
- **`rig seat continue <seat>`** — when a fresh launch stops at a native
|
||||
prompt before its startup context is delivered, the startup orchestrator
|
||||
fails it as `attention_required` and adds "run: rig seat continue
|
||||
<session>" (`startup-orchestrator.ts:600`). The command
|
||||
(`packages/cli/src/commands/seat.ts:633`) calls
|
||||
`POST /api/seat/continue/:seatRef` (`routes/seat.ts:293`), which runs
|
||||
`continueFreshStartup` (`seat-lifecycle-service.ts:395`): it re-checks the
|
||||
binding, pane and runtime, then delivers the pending context to the same
|
||||
conversation without relaunching. In a restore, a pod-aware seat in this
|
||||
state reports `attention_required` (`restore-orchestrator.ts:1265`). See
|
||||
[agent-spec-and-startup.md](agent-spec-and-startup.md) for the startup side.
|
||||
- **Per-seat startup decision** — `POST /api/startup/:rigId/:logicalId`
|
||||
(`routes/startup.ts:142`) takes `resume`, `start`, `fresh` or `continue`
|
||||
for one seat. `resume` captures an eligibility-checked `auto-rehydrate`
|
||||
snapshot when none exists and goes through `launchSingleNode`.
|
||||
- **Seat handover** — `rig seat handover <seat> --source
|
||||
fresh|rebuild|fork:<id>|discovered:<id> --reason <reason> [--dry-run]`
|
||||
(the reason is required, dry run included; without it the command exits 2
|
||||
before contacting the daemon)
|
||||
(`packages/cli/src/commands/seat.ts:364`) calls
|
||||
`POST /api/seat/handover/:seatRef` (`routes/seat.ts:94`), which runs
|
||||
`SeatHandoverService.handover` (`seat-handover-service.ts:266`).
|
||||
`SuccessorSessionLauncher` (`successor-session-launcher.ts:92`) respawns
|
||||
the same pane in place, so the session name stays the same. The fresh
|
||||
source delivers a daemon-built restore packet (`buildRestorePacket`,
|
||||
`seat-handover-service.ts:1151`), which is separate from the CLI
|
||||
`rig restore-packet` below. The rebuild source primes from the seat's
|
||||
durable chain (`buildRebuildPrimingChain`, `rebuild-priming-chain.ts:22`).
|
||||
|
||||
> **D15 author bit (flagged per slice-08 §4.7 binding):** `restore-check` and
|
||||
> `restore-packet` have NO `architecture.md` narrative. This section is the
|
||||
> one author-from-source piece in this otherwise pure-split module — authored
|
||||
> at the slice-00 forensic bar from `routes/restore-check.ts` +
|
||||
> `domain/restore-check-service.ts` + `commands/restore-packet.ts`, file:line
|
||||
> cited, version-attribution forensically checked.
|
||||
>
|
||||
> **Version attribution (forensic):** restore-check is NOT a 0.3.1 feature.
|
||||
> `routes/restore-check.ts` first-created `277e279c` ("feat: native rig
|
||||
> restore-check command", 2026-04-23, pre-0.3.0); present at both `v0.3.0`
|
||||
> and `v0.3.1` git trees (`git ls-tree` confirmed). `restore-packet`
|
||||
> first-created `23f2921e` ("Restore-Packet vertical M2a", 2026-05-01),
|
||||
> likewise present at v0.3.0 and v0.3.1. The gap is missing
|
||||
> architecture.md *prose*, not a version-attribution drift.
|
||||
## 5. Daemon-side restore-check and CLI restore-packet
|
||||
|
||||
### 5.1 `rig restore-check` — readiness probe
|
||||
|
||||
`GET /api/restore-check?rig=<name>&noQueue=<bool>&noHooks=<bool>`
|
||||
(`routes/restore-check.ts:131`). The route assembles a framework-free
|
||||
`RestoreCheckDeps` (`:138-168`) over existing daemon projections —
|
||||
`listRigs` from `rigRepo`, `getNodeInventory` (joining `node_id` by
|
||||
`logical_id`, `:21-26,143-149`), `getStartupContext` (reads
|
||||
`GET /api/restore-check?rig=<name>&noQueue=true&noHooks=true&compact=1&ready=1`
|
||||
(`routes/restore-check.ts:220–221`). The route calls
|
||||
`createRestoreCheckService` (`:151`), which assembles a framework-free
|
||||
`RestoreCheckDeps` (`:155–216`) over existing daemon projections —
|
||||
`listRigs` from `rigRepo` (`:175–178`), `getNodeInventory` (joining
|
||||
`node_id` by `logical_id`, `:26–31`, `:179–185`), `getStartupContext` (reads
|
||||
`node_startup_context`, parses `projection_entries_json` /
|
||||
`resolved_files_json` / `startup_actions_json`, `:44-128`), `hasSnapshot`
|
||||
/ `getLatestSnapshot` from `snapshotRepo`, and `probeDaemonHealth`
|
||||
(self-evident: "We're inside the daemon — if this route is responding,
|
||||
daemon is healthy", `:160-163`). It then runs
|
||||
`new RestoreCheckService(deps).check(...)` (`:170-171`).
|
||||
`resolved_files_json` / `startup_actions_json`, `:49–143`), `hasSnapshot`
|
||||
/ `getLatestSnapshot` from `snapshotRepo` (`:189–195`), `getRestoreInputs`
|
||||
(`:196–207`), `probeQueueStore` (`:158–170`), `getClaudeActivityHookEvents`
|
||||
(`:171–174`), and `probeDaemonHealth` (self-evident: "We're inside the
|
||||
daemon — if this route is responding, daemon is healthy", `:208–211`). It
|
||||
then runs `service.check(...)` (`:231–232`).
|
||||
|
||||
`RestoreCheckService.check()` (`restore-check-service.ts:244`) layers:
|
||||
`RestoreCheckService.check()` (`restore-check-service.ts:259`) layers:
|
||||
|
||||
1. **Host checks** — `checkDaemonReachable` (probe-throw → `verdict:
|
||||
unknown`, NOT `not_restorable`, `:252-259`), `checkStateDirWritable`
|
||||
(`:261`), `checkHostInfraDeclaration` (`:262`, impl `:406`).
|
||||
2. **Rig enumeration** — `listRigs()` throw → `buildUnknown` (`:267-274`);
|
||||
`--rig` filter; unknown rig → red `rig.<name>.exists` (`:276-284`).
|
||||
3. **Per-rig checks** — `checkSnapshot` (`:290`, impl `:675`),
|
||||
`checkSpecPresent` (`:295`).
|
||||
4. **Per-seat checks** — `checkSeatReadiness` (`:311`), `checkStartupContext`
|
||||
(`:315`, impl `:763`; `unknownChecks` → `buildUnknown`),
|
||||
`checkTranscript` (`:325`), `checkResumePath` (`:329`), and unless opted
|
||||
out: `checkQueueFile` (`:334`, gated by `--no-queue`, impl `:870`) and
|
||||
`checkHooks` (`:339`, gated by `--no-hooks`, impl `:896`).
|
||||
5. **Verdict** — `buildResult` aggregates: any red → `not_restorable`; any
|
||||
yellow → `restorable_with_caveats`; else `restorable`; probe-uninspectable
|
||||
→ `unknown` (`:1074-1085`, `:1362-1379`). Plus a `RecoveryPlan`
|
||||
(`buildRecovery`, `:1186`) and a `RepairStep[]` packet
|
||||
(`buildRepairPacket`, `:1392`; `null` when fully restorable).
|
||||
unknown`, NOT `not_restorable`, `:270–280`), `checkStateDirWritable`
|
||||
(`:282`, impl `:498`), `checkHostInfraDeclaration` (`:283`, impl `:515`).
|
||||
2. **Rig enumeration** — `listRigs()` throw → `buildUnknown` (`:286–295`);
|
||||
`--rig` filter; unknown rig → red `rig.<name>.exists` (`:297–305`).
|
||||
3. **Per-rig checks** — `checkSnapshot` (`:311`, impl `:784`),
|
||||
`checkSpecPresent` (`:316`, impl `:1161`), and
|
||||
`rig.<name>.restore-preconditions` (`checkRestorePreconditions`, `:338`,
|
||||
impl `:422`). The last runs the same `validatePreRestore` that restore
|
||||
runs (`:446`) on the inputs from `getRestoreInputs`: red on a blocker,
|
||||
yellow on a warning, green otherwise. Without a usable snapshot it is
|
||||
yellow when current-state rehydrate is eligible and red when it isn't;
|
||||
when the inputs can't be read it is yellow and the rig's rollup becomes
|
||||
`unknown` (see Verdict). The rig-status route sets
|
||||
`recoveryOnly` (`routes/rigs.ts:272`), which skips this check when every
|
||||
seat is ready (`restore-check-service.ts:337`).
|
||||
4. **Per-seat checks** — `checkSeatReadiness` (`:346`), `checkStartupContext`
|
||||
(`:360`, impl `:886`; `unknownChecks` → `buildUnknown`, `:361–366`),
|
||||
`checkTranscript` (`:382`), `checkResumePath` (`:386`), and unless opted
|
||||
out: the daemon SQLite `queue_items` availability probe `checkQueueStore`
|
||||
(`:390–394`, gated by `--no-queue`; impl `:1007`) and `checkHooks`
|
||||
(`:395–399`, gated by `--no-hooks`; impl `:1030`). Queue continuity is
|
||||
represented by the shared daemon store; an empty queue is valid
|
||||
(`routes/restore-check.ts:160–163`). Claude hook readiness follows the
|
||||
persisted `claude_activity_hooks` runtime-resource selection
|
||||
(`restore-check-service.ts:1053–1060`) and the adapter's projection: the
|
||||
hook settings in the seat CWD and the activity relay under the OpenRig
|
||||
home's `state/claude-activity-hooks/` (`restore-check-service.ts:1071–1072`). A seat
|
||||
that deliberately omits that resource is not applicable. In compact mode
|
||||
without `ready=1`, a seat whose readiness check is green gets only the
|
||||
startup-context check (`restore-check-service.ts:358`, `:368–380`); that
|
||||
check still counts toward the verdict.
|
||||
5. **Verdict** — `buildResult` (`:1205`) aggregates: any red →
|
||||
`not_restorable`; else any rig rollup with status `unknown` → `unknown`
|
||||
(`:1223–1224`); else any yellow → `restorable_with_caveats`; else
|
||||
`restorable` (`:1220–1229`). A rig rollup is `unknown` when its restore
|
||||
inputs could not be read (`:1549–1551`). A probe that can't be inspected
|
||||
at all also gives `unknown` (`buildUnknown`, `:1238`). Plus a
|
||||
`RecoveryPlan` (`buildRecovery`, `:1342`) and a `RepairStep[]` packet
|
||||
(`buildRepairPacket`, `:1581`; `null` when fully restorable, `:1582`).
|
||||
|
||||
The result shape is `RestoreCheckResult` (`restore-check-service.ts:120-130`):
|
||||
The result shape is `RestoreCheckResult` (`restore-check-service.ts:148–160`):
|
||||
`verdict`, `readiness`, `continuity`, `rigs[]`, `hostInfra`, `recovery`,
|
||||
`counts {red,yellow,green}`, `checks[]`, `repairPacket`.
|
||||
`counts {red,yellow,green}`, `classCounts`, `checks[]`, `repairPacket`. With
|
||||
`compact=1` the route returns a reduced body (`routes/restore-check.ts:234–261`).
|
||||
|
||||
**Honest-error design (slice-00 bar):** a daemon-probe *exception* produces
|
||||
**Honest-error design:** a daemon-probe *exception* produces
|
||||
`verdict: unknown` (uninspectable state), distinct from a daemon
|
||||
definitely-down state which is `red` / `not_restorable`
|
||||
(`restore-check-service.ts:249-259`). The route's catch-all returns the same
|
||||
`unknown`-shaped body with HTTP 500 + a `probe.error` red check
|
||||
(`routes/restore-check.ts:174-222`). `CheckEntry.remediationSafe` defaults to
|
||||
`false` (conservative — unclassified remediations are NOT
|
||||
auto-execution-safe, `restore-check-service.ts:18-24`).
|
||||
(`restore-check-service.ts:270–280`, `checkDaemonReachable` `:476–496`). The
|
||||
route's catch-all returns the same `unknown`-shaped body with HTTP 500 + a
|
||||
`probe.error` red check (`routes/restore-check.ts:264–312`).
|
||||
`CheckEntry.remediationSafe` defaults to `false` (conservative —
|
||||
unclassified remediations are NOT auto-execution-safe,
|
||||
`restore-check-service.ts:23–28`, applied at `:1596`).
|
||||
|
||||
CLI surface (`cli-reference.md` `### rig restore-check`):
|
||||
`rig restore-check [--rig <name>] [--no-queue] [--no-hooks] [--json]`. Exit
|
||||
codes: `0` restorable (or with caveats), `1` not restorable (red), `2`
|
||||
unknown / probe error.
|
||||
CLI surface (`../cli-reference.md` `### restore-check`; options in
|
||||
`packages/cli/src/commands/restore-check.ts:222–227`):
|
||||
`rig restore-check [--rig <name>] [--full] [--ready] [--no-queue] [--no-hooks] [--json]`.
|
||||
Output is compact unless `--full` (`:313`). Exit codes: `0` restorable (or
|
||||
with caveats), `1` not restorable (red), `2` unknown / probe error
|
||||
(`:214–217`). When the daemon is down, the CLI builds a local
|
||||
`not_restorable` result and exits 1 (`:269`).
|
||||
|
||||
### 5.2 `rig restore-packet` — cross-runtime restore packet
|
||||
|
||||
CLI-side, no daemon route. `commands/restore-packet.ts` (528 lines)
|
||||
implements three subcommands (`cli-reference.md` `### rig restore-packet`):
|
||||
`write [options]` (generate a packet directory from a source session or JSONL
|
||||
file, with `omitted-records` accounting), `read <packet-dir> [--json]`
|
||||
(render contents; non-mutating), `validate <packet-dir> [--json]` (validate
|
||||
against the v0 schema; non-mutating). Packet shape is the cross-runtime v0
|
||||
standard — Claude Code and Codex transcripts both supported via runtime
|
||||
parsers + redaction.
|
||||
|
||||
## OPEN / carried items
|
||||
|
||||
- **D15 (author-from-source, completed):** restore-check / restore-packet
|
||||
authored from source above; flagged as the single author bit in this
|
||||
module. Version attribution forensically resolved (both pre-0.3.0; the gap
|
||||
was missing prose, not drift).
|
||||
- No slice-00 numeric drift applies to this module's split content (the
|
||||
footprint/migration/route counts land in `daemon-core.md`).
|
||||
CLI-side, no restore-packet daemon route. `commands/restore-packet.ts`
|
||||
(**541** lines, `wc -l < packages/cli/src/commands/restore-packet.ts`)
|
||||
implements **3** subcommands
|
||||
(`grep -c 'cmd.command(' packages/cli/src/commands/restore-packet.ts`;
|
||||
`../cli-reference.md` `### restore-packet`): `write [options]` (`:218`;
|
||||
generate a packet directory from a source session or JSONL file, with
|
||||
`omitted-records` accounting; `--source-session` reads the transcript from the
|
||||
daemon's `/api/transcripts/<session>/full`, `:156`), `read <packet-dir> [--json]`
|
||||
(`:373`; render contents; non-mutating), `validate <packet-dir> [--json]`
|
||||
(`:452`; validate against the v0 schema; non-mutating). Packet shape is the
|
||||
cross-runtime v0 standard — Claude Code and Codex transcripts both supported
|
||||
via runtime parsers + redaction (`packages/cli/src/restore-packet/`).
|
||||
|
||||
## See also
|
||||
|
||||
- `agent-spec-and-startup.md` — StartupOrchestrator persists the replay
|
||||
context that restore consumes.
|
||||
- `daemon-core.md` — `/api/restore-check` is one of the 49 route mounts
|
||||
(`server.ts:513`).
|
||||
- `daemon-core.md` — `/api/restore-check` is one of the **69** route mounts
|
||||
in `server.ts` (`grep -c 'app.route(' packages/daemon/src/server.ts`),
|
||||
mounted at `server.ts:836`.
|
||||
- `transport-and-transcripts.md` — transcript boundary markers written on
|
||||
restore.
|
||||
- Source roots: `packages/daemon/src/domain/{restore-orchestrator,
|
||||
snapshot-capture,snapshot-repository,checkpoint-store,
|
||||
restore-check-service}.ts`, `packages/daemon/src/routes/restore-check.ts`,
|
||||
`packages/cli/src/commands/restore-packet.ts`.
|
||||
restore-preconditions,restore-topology,restore-attempt-receipt,
|
||||
rehydrate-eligibility,snapshot-capture,snapshot-repository,checkpoint-store,
|
||||
active-occupant,restore-check-service}.ts`,
|
||||
`packages/daemon/src/routes/{restore-check,up,snapshots}.ts`,
|
||||
`packages/cli/src/commands/{restore-packet,restore-check,restore}.ts`.
|
||||
@@ -1,205 +1,205 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: Living Notes Review Surface — the intent→plan→delivered projection
|
||||
title: Living Notes — Composed Review and Frozen Exports
|
||||
status: active
|
||||
topics: [knowledge-and-context, observability, sdlc]
|
||||
domains: [engineering-advisor, operating-advisor, product-advisor]
|
||||
topics: [observability, knowledge-and-context]
|
||||
domains: [engineering-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
Working on or consuming the composed slice/mission review surface — the
|
||||
/api/review/* routes, the ComposedSliceReview contract, the on-disk SDLC
|
||||
convention it projects, staged-approval locks, proof artifacts and the C1
|
||||
header, the freeze export, media serving for review evidence, or the
|
||||
cross-host FLEET aggregate (/api/review/fleet, the fleet composer's
|
||||
union/one-count seam, v0.4.6 MH-5).
|
||||
siblings: [content-surfaces.md, workspace-primitive.md, mission-control.md]
|
||||
prerequisite-reads: [workspace-primitive.md]
|
||||
last-verified-against-source: c8341f72
|
||||
last-updated: 2026-07-08
|
||||
Tracing slice, mission, rig, or fleet review data back to scope documents,
|
||||
proof artifacts, judgments, queue state, or frozen exports.
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Living Notes review surface (v0.4.4)
|
||||
# Living Notes — Composed Review and Frozen Exports
|
||||
|
||||
The review surface is a **pure projection of on-disk markdown into one
|
||||
reviewable structure per slice**: INTENT → PLAN → DELIVERED. Agents change
|
||||
files; the daemon re-projects; nothing here writes product state except the
|
||||
two deliberate approval stamps and the freeze export. Shipped across the
|
||||
0.4.4 living-notes slices (19 signal layer, 20 composer/surfaces, 22 rig
|
||||
agents altitude, 23 SDLC operationalization) and the **corrective rebuild**
|
||||
(2026-07-05/06) that collapsed the surface to the one structure described
|
||||
here. Everything below is verified at `bb5ad219`.
|
||||
Source snapshot: `e8f0ab340db773392ec8be75b072d1c0f3068a50`. This describes the source at that commit;
|
||||
it does not establish the version or behavior of a running daemon.
|
||||
|
||||
## 1. The ONE contract — `ComposedSliceReview`
|
||||
Living Notes builds review payloads from existing documents and recorded
|
||||
state. The composition layer does not make an agent judgment or contact an
|
||||
agent to obtain one. Its output distinguishes intent, plan, delivered proof,
|
||||
attention, agents, and source lineage.
|
||||
|
||||
`packages/daemon/src/domain/review/types.ts` defines the read contract every
|
||||
consumer shares (slice Review tab, mission U5 row expansion, For-You
|
||||
expansion — never a second endpoint per consumer):
|
||||
## Read and compose boundaries
|
||||
|
||||
- **identity + phase**: `slice`, `sliceId`, `title`, `missionId`, `phase`
|
||||
(five-way, derived read-time: `locked > review > building > spec > intent`,
|
||||
`derivePhase` in `compose.ts`), `laneLabel` (SS14 vocabulary INTENT / PLAN /
|
||||
BUILD / REVIEW / LOCKED).
|
||||
- **`intent`**: `{text, media[], ssotPath, degrade}` — the slice README's
|
||||
`## Intent` section verbatim (`extractSection`), plus any media it embeds.
|
||||
- **`plan`**: `{concise: {text, media[]}, lockedArtifacts[], lock, ssotPath}` —
|
||||
the PRD's pinned `## Mini-requirements` tier + planned mockups; the pinned
|
||||
artifact set is a **frontmatter READ** (`locked-artifacts:` list on the
|
||||
slice README — no new write machinery); `lock` is the spec-scope
|
||||
staged-approval stamp (§3).
|
||||
- **`delivered`**: `{items[], extraProof[], lock, proofDirPath}` — the
|
||||
redesigned join (§4): each `## Proof contract` deliverable paired with its
|
||||
curated proof media and QA's recorded comparison signal. `lock` is the
|
||||
delivery-scope stamp; `proofDirPath` is the drill-in door to the full
|
||||
fix-loop history.
|
||||
- **KEPT orthogonal bands**: `needsYou` (two sources, one queue: agent-routed
|
||||
items ∪ derived ▲ exceptions with inline evidence), `agents`
|
||||
(scope-parameterized `slice:<id> | mission:<id> | rig` — one contract, all
|
||||
scopes), `lineage` (`VerifyLineage`: candidate/merge/tip facts + the four
|
||||
gate `VerdictCell`s rendering recorded tokens verbatim), `defects`
|
||||
(out-of-slice media refs surfaced, never silently dropped), `composedAt`.
|
||||
[`ReviewGatherer`](../../../packages/daemon/src/domain/review/gather.ts)
|
||||
performs filesystem, database, telemetry, and optional Git reads.
|
||||
[`compose.ts`](../../../packages/daemon/src/domain/review/compose.ts) turns
|
||||
those inputs into the contracts defined in
|
||||
[`types.ts`](../../../packages/daemon/src/domain/review/types.ts), including
|
||||
`ComposedSliceReview`, `ComposedMissionReview`, and `ComposedRigAgents`.
|
||||
|
||||
**Deleted by the corrective (2026-07-05, founder-blocked original):** the
|
||||
four parallel renderable structures (`sections`, `acceptance`, `compare`,
|
||||
`join`) and the coequal `green` field. They do not exist in the types, the
|
||||
composer output, the UI hook, or the fixtures; the daemon route tests assert
|
||||
the keys are ABSENT on the wire (`test/review-composer.test.ts`,
|
||||
`test/review-routes.test.ts`). Recorded-verdict green survives in exactly two
|
||||
places: the per-deliverable `verified` signal (§4) and the mission ledger's
|
||||
completion fact (§6) — never a slice-review structure.
|
||||
`gatherSlice` uses `resolveNodeFileVia` to select the scope node document,
|
||||
reads `IMPLEMENTATION-PRD.md` and `PROOF.md`, and loads the `proof/` artifacts.
|
||||
It also gathers approval stamps, locked artifacts, tagged queue attention,
|
||||
agents, workflow exceptions, and `readSliceReadiness`. A current `SPEC.md`
|
||||
is carried distinctly from a legacy `README.md`; the composer does not
|
||||
blindly treat every file as an interchangeable source.
|
||||
|
||||
## 2. Composer + gatherer split
|
||||
[`readProofArtifacts`](../../../packages/daemon/src/domain/review/proof-io.ts)
|
||||
reads sorted top-level `.md` files in `proof/`, parses their headers with
|
||||
`parseC1Header`, and uses file mtime as `droppedAt`. Missing directories and
|
||||
unreadable entries yield no artifact for those entries. No claim that all
|
||||
possible evidence was discovered follows from that read.
|
||||
|
||||
- `compose.ts` is **PURE**: `(gathered inputs) → composed doc`; identical
|
||||
inputs (including caller-supplied `nowIso`/git facts) produce byte-identical
|
||||
output — idempotence holds by construction and is pinned by tests.
|
||||
- `gather.ts` (`ReviewGatherer`) is the impure shell: reads slice docs +
|
||||
`proof/*.md` artifacts from disk, attention/agent rows from SQLite,
|
||||
approval stamps from frontmatter (cross-checked against the audit log, §3),
|
||||
git lineage facts from the workspace default repo. Every unreadable source
|
||||
degrades honestly (nulls / "unknown") — the composer renders the named
|
||||
degrade, never invented content.
|
||||
`composeMission` batches slice-index membership reads, gathers each slice,
|
||||
and composes a mission board, completion ledger, attention union, agents,
|
||||
and brief spine. `composeRig` reads queue/activity state and reports current
|
||||
holders, recent holders, overdue work, settled work, and workflow exceptions.
|
||||
"Recent" is the current UTC day; overdue is in-progress slice-tagged work past
|
||||
`closure_required_at`; settled is today's `handed_off_to` transitions; and
|
||||
attention is any active item that is `human-gate`, addressed to a `human…`
|
||||
session, or blocked on one (`gather.ts`). Of the three review reads, the TUI
|
||||
calls only `/fleet`, from its broad hydration (`packages/tui/src/hydrate.ts`);
|
||||
its client also defines methods for `/agents` and `/rig`, which nothing in the
|
||||
TUI calls.
|
||||
|
||||
## 3. The two locks = the shipped staged-approval stamps
|
||||
## Slice source selection and phase
|
||||
|
||||
`plan.lock` and `delivered.lock` are projections of the **staged-approval
|
||||
verb** (`rig scope slice|mission approve --scope spec|delivery` — see
|
||||
cli-reference "SDLC control plane verbs"): frontmatter stamps
|
||||
(`approved-spec-by/-at`, `approved-by/-at`) written daemon-side together with
|
||||
an append-only `mission_control_actions` audit row (no half-stamp). The
|
||||
gatherer cross-checks each stamp against the pinned scope-approval
|
||||
`audit_notes_json` shape (`approval_scope` + scope identity); a stamp with no
|
||||
matching row projects `auditVerified: false` and renders as an UNVERIFIED
|
||||
stamp — visible, never a block. Approval is freeze/sign-off, **never**
|
||||
proven-green (BR-6).
|
||||
In [`composeSliceReview`](../../../packages/daemon/src/domain/review/compose.ts),
|
||||
intent comes from the node document's `Intent` section. Placeholder-only
|
||||
intent is treated as absent. Mini-requirements use current `SPEC.md` when
|
||||
present; legacy selection can fall back from a pristine scaffold PRD section
|
||||
to authored README material. Proof-contract selection delegates to
|
||||
`selectProofContractBody` through `extractProofContractSelected`.
|
||||
|
||||
## 4. DELIVERED — promised ↔ curated proof ↔ verified
|
||||
The phase is derived by `derivePhase`, in this precedence order:
|
||||
|
||||
`composeDelivered` joins the PRD's `## Proof contract` checkbox items
|
||||
(`extractProofContract`; an optional markdown image on the checkbox line
|
||||
becomes the deliverable's `plannedRef` mockup) to the C1-headed proof
|
||||
artifacts in `<slice>/proof/`:
|
||||
| Phase | Signal |
|
||||
|---|---|
|
||||
| `locked` | Delivery approval stamp. |
|
||||
| `review` | Recorded verdict or claimed proof pass. |
|
||||
| `building` | Real proof artifacts, or active queue work paired with an authored/locked spec. |
|
||||
| `spec` | Authored structure or a spec approval stamp. |
|
||||
| `intent` | None of the above. |
|
||||
|
||||
- An artifact **covers** a deliverable when its C1 `evidences:` list names the
|
||||
item (exact text or 1-based index).
|
||||
- `verified` binds to the shipped C1 fields, never mere presence:
|
||||
**verified** = a covering `qa|adjudication` artifact records the comparison
|
||||
(`self_check`) AND its recorded verdict is passing; **unverified** = some
|
||||
covering artifact without a passing recorded QA comparison (QA's
|
||||
why-kicked-back `note` still surfaces); **missing** = promised, nothing
|
||||
delivered. All three are render states — fail-open by construction.
|
||||
- The curated proof set is the covering artifacts' body media refs
|
||||
(`ProofArtifact.mediaRefs`, populated by `rig proof add --media` or markdown
|
||||
refs in the artifact body), normalized slice-relative; refs escaping the
|
||||
slice dir become `defects` findings and are never served or inlined.
|
||||
Unmapped-artifact media renders bounded as `extraProof`.
|
||||
- The ▲ insufficient-proof NEEDS-YOU exception fires from the delivered
|
||||
MISSING count (`deriveExceptions`).
|
||||
File presence alone is insufficient: a scaffolded `PROOF.md` is not build
|
||||
evidence, and a tracking queue item alone does not promote an unplanned
|
||||
slice. Phase and proof readiness are separate output fields.
|
||||
|
||||
## 5. Routes, freeze, media serving
|
||||
## Delivered items and current judgments
|
||||
|
||||
- `packages/daemon/src/routes/review.ts`, mounted at `/api/review`
|
||||
(`server.ts` route mounts): `GET /slice/:name`, `GET /mission/:name`,
|
||||
`GET /rig` (the OPR.0.4.4.22 rig-agents altitude root), `GET
|
||||
/agents?scope=slice:<id>|mission:<id>|rig`, `POST /freeze`.
|
||||
- **Freeze** (`freeze.ts`): the ONE synchronous compose-and-freeze path,
|
||||
invoked after the delivery stamp + audit row commit. Renders the composed
|
||||
review to a single self-contained HTML file (`REVIEW-<id>-<date>.html` in
|
||||
the slice dir): CSS inline, images data-URI-inlined under slice-dir
|
||||
containment (resolve-prefix + realpath — a traversal/symlink ref renders
|
||||
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.
|
||||
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:
|
||||
bytes`; `.html` renders as text/html only under the explicit `?render=1`
|
||||
opt-in) and `GET /api/slices/:name/proof-asset/*` (`routes/slices.ts` —
|
||||
same range semantics via `fileAssetResponse`, added by the corrective QA
|
||||
fixback; immutable-cache posture retained).
|
||||
`extractProofContract` reads logical checkbox items, retaining authored raw
|
||||
text for identity. `proofItemIdentity` uses an explicit `proof-item` marker
|
||||
when supplied, otherwise an ID derived from the item's text. In the legacy
|
||||
artifact join, `evidences` references match a promised item's exact display
|
||||
text or its one-based index.
|
||||
|
||||
## 6. Mission + rig altitudes
|
||||
`composeDelivered` pairs each promise with curated media and a verification
|
||||
state:
|
||||
|
||||
`composeMissionReview` consumes `{review, green}` entries per slice: the
|
||||
board (stage-specific cells re-bound to the collapsed contract — spec-stamp
|
||||
state, delivered n/m, GREEN·merge pair, stamp), the completion **ledger**
|
||||
(a query over the slice set, never an authored list; `green` here is the
|
||||
recorded-verdict fact from `computeRecordedGreen` — regime 1 = four passing
|
||||
gate verdicts, regime 2 = passing adjudication), `cutComplete` (TRUE only
|
||||
when every in-cut slice is green AND merged AND has zero open needs-human
|
||||
items), and the union NEEDS-YOU (distinct identities — one item seen from N
|
||||
altitudes is one item). `composeRigAgents` serves the standalone rig
|
||||
altitude (roster ∪ recently-holding, park/health/settled from the queue
|
||||
transitions log).
|
||||
- With configured [proof readiness](../../../packages/daemon/src/domain/proof/judgments.ts),
|
||||
the matching current item must be `accepted`, and readiness must have no
|
||||
issues, to render `verified`. The note reports the current judgment state
|
||||
or why it is unavailable.
|
||||
- Without configured readiness, a winning covering `qa` or `adjudication`
|
||||
artifact with a non-null `self_check` and a passing verdict supplies legacy
|
||||
verification. The note explicitly labels it **item revision unbound**.
|
||||
- Other covering artifacts are `unverified`; no covering evidence/judgment
|
||||
is `missing`. Unmapped artifact media appears separately as `extraProof`.
|
||||
|
||||
## 7. The FLEET altitude — the cross-host aggregate SIBLING (v0.4.6, OPR.0.4.6.MH5)
|
||||
This distinction matters after a proof contract changes: a legacy PASS
|
||||
header is not automatically a current revision-bound judgment. Approval
|
||||
stamps are also not a substitute for the delivered-item join.
|
||||
|
||||
`GET /api/review/fleet` aggregates every registered host's composed ▲/● set
|
||||
into one manage-by-exception glance. It is a **sibling aggregate endpoint**,
|
||||
never a fourth `AgentsScope` value (arch Q2): the scope grammar stays
|
||||
exactly `slice:* | mission:* | rig`, and the local composer stays a pure
|
||||
function over local state. `domain/review/fleet-compose.ts` is the ONE
|
||||
review-domain module allowed to import hosts transport/registry — a
|
||||
boundary enforced mechanically by the `review-import-audit` static test
|
||||
(the zero-I/O `fanout-contract` types module is the sole allowlisted
|
||||
hosts import elsewhere).
|
||||
## Approval, attention, and lineage
|
||||
|
||||
**Fan-out-the-composed-set (arch Q1):** the ▲ kinds are time-derived on
|
||||
their own host's clock, so the fleet root fans out each host's
|
||||
ALREADY-COMPOSED rig root (`GET /api/review/rig` over `remoteJsonRequest`,
|
||||
concurrent under the named read-class deadline; the local host joins
|
||||
in-process via the same gatherer — zero self-transport) and only **unions +
|
||||
host-dimensions + counts**. It never recomputes exception truth (the
|
||||
composer is clock-free by test pin), so cross-host clock skew can never
|
||||
distort a ▲. Per-host failures degrade to the closed `PerHostStatus`
|
||||
honesty contract; a failed host's counts are ABSENT from its
|
||||
`FleetHostRollup` — absent-not-zero, in the type.
|
||||
`ReviewGatherer.gatherApproval` reads `approved-spec-by`/`approved-spec-at`
|
||||
and `approved-by`/`approved-at`, alongside matching `mission_control_actions`
|
||||
approval audit records. The resulting locks preserve who/when/audit facts.
|
||||
A frontmatter stamp with no matching `approve` audit row still locks the
|
||||
phase, but carries `auditVerified: false`, and the frozen export shows it as
|
||||
an UNVERIFIED stamp. The approval writer is the separate
|
||||
[`scope-approve` route](../../../packages/daemon/src/routes/scope-approve.ts).
|
||||
|
||||
**One-count across the fleet (arch Q4):** the dedup `Set` lives in the
|
||||
fleet composer — the one place — keyed `hostId|identity` (rendered verbatim
|
||||
on the expanded drawer); within-host multi-altitude visibility collapses to
|
||||
one row with `seenFrom` provenance; an MH-3-forwarded qitem lives only in
|
||||
its origin host's DB, so no double-count by construction.
|
||||
`composeNeedsYou` combines recorded queue attention with derived exceptions;
|
||||
`deriveWorkflowExceptions` adds workflow-specific observations. When
|
||||
readiness is not configured, a slice whose `PROOF.md` claims PASS without a
|
||||
recorded green verdict also gets a `confirm-faithful` needs-you item. Mission
|
||||
composition deduplicates attention identities across slice and mission views.
|
||||
Media references outside the slice's relative-path contract produce defect
|
||||
entries instead of disappearing silently.
|
||||
|
||||
**Composition pins the seam (arch Q3, the rule of thumb):** sidedness and
|
||||
caller pick the TRANSPORT seam (MH-4's CLI-direct verbs); when COMPOSITION
|
||||
exists — the fleet union/one-count is a correctness rule — the seam pins
|
||||
daemon-side: ONE composer, one endpoint, every consumer (the `/fleet` route
|
||||
page and the FLEET band today via the shared `useFleet` hook; the TUI/CLI
|
||||
fleet consumers as named follow-ups) reads the same one. UI cadence is the
|
||||
bounded named `FLEET_POLL_INTERVAL_MS` with the feed-cadence-class floor,
|
||||
and the ambient band gates its FETCH on fleet existence (the FS-1 amplifier
|
||||
discipline; enable-at-scale remains the FS-1 release-validation gate). Read
|
||||
+ surface only: acting on a remote host's item rides MH-3/MH-4.
|
||||
Git facts are optional. [`startup.ts`](../../../packages/daemon/src/startup.ts)
|
||||
passes `OPENRIG_REVIEW_GIT_REPO` to the gatherer. The gatherer reads that
|
||||
repository's `HEAD`, searches merge messages keyed by scope ID, and computes
|
||||
ancestry/behind facts when possible. Each git read has a 4-second timeout; the
|
||||
merge search looks for `Merge <id>` in `HEAD` history and needs the scope's
|
||||
frontmatter `id`; the behind-tip count is computed only for unmerged slices
|
||||
with a candidate SHA. Unavailable facts remain unknown; this is not a fresh
|
||||
remote GitHub merge-status query.
|
||||
|
||||
## 8. The on-disk convention it projects (SDLC control plane)
|
||||
`composeMissionReview` derives `cutComplete` from a nonempty ledger in which
|
||||
every slice is green, has a merge SHA, and has no needs-human items. Configured
|
||||
slice readiness supplies the green predicate; otherwise recorded verdicts
|
||||
do. With configured readiness, a slice's board cell also shows the proof
|
||||
readiness state and revision. That computed field is not itself a
|
||||
publication or higher-level outcome decision.
|
||||
|
||||
The surface projects the markdown-control-plane shapes operationalized by
|
||||
OPR.0.4.4.23 — conventions SSOT `docs/reference/sdlc-conventions.md`:
|
||||
`## Intent` (README) / `## Mini-requirements` + `## Proof contract` (PRD) /
|
||||
`proof/` C1-headed artifacts + `PROOF.md`, scaffolded for every template kind
|
||||
by `rig scope slice create` and checked advisory-only by `rig scope audit`
|
||||
(+ the `rig doctor` SDLC row). The UI half of this surface (the slice Review
|
||||
tab, mission board, For-You approve+chat) is documented in
|
||||
[`../ui/project-and-for-you.md`](../ui/project-and-for-you.md).
|
||||
## HTTP routes
|
||||
|
||||
[`reviewRoutes`](../../../packages/daemon/src/routes/review.ts), mounted at
|
||||
`/api/review` by [`server.ts`](../../../packages/daemon/src/server.ts):
|
||||
|
||||
| Method and suffix | Payload / behavior |
|
||||
|---|---|
|
||||
| `GET /slice/:name` | Composed slice plus proof-source observation. |
|
||||
| `GET /mission/:name` | Composed mission plus proof-source observation. |
|
||||
| `GET /agents?scope=…` | Agents band; scope is `slice:<id>`, `mission:<id>`, or `rig`. |
|
||||
| `GET /rig` | Local rig review root. |
|
||||
| `GET /fleet` | Local and registered remote host review union. |
|
||||
| `POST /freeze` | Compose and export an approved slice; body requires `scope: slice` and `name`. |
|
||||
|
||||
Missing gatherer returns `503`; invalid agents scope returns `400`; unknown
|
||||
slice/mission returns `404`. Fleet is a sibling aggregate, not another value
|
||||
of the agents-scope parameter.
|
||||
|
||||
## Freeze and media
|
||||
|
||||
Nothing invokes the freeze automatically: `POST /api/scope/approve` writes
|
||||
the stamp and audit row and always returns `freezeFired: false`
|
||||
(`domain/scope/scope-approve.ts:326`), and the route comment that says the
|
||||
approve flow invokes the freeze describes wiring that does not exist. A
|
||||
frozen export exists only after something calls `POST /api/review/freeze`.
|
||||
|
||||
[`freezeSliceExport`](../../../packages/daemon/src/domain/review/freeze.ts)
|
||||
requires a delivery lock and an allowlisted slice directory. It writes
|
||||
`REVIEW-<scope-id-or-name>-<approval-date>.html` through
|
||||
`FileWriteService.createAtomic`. An existing target is treated as already
|
||||
frozen; it is not rewritten. A failed export does not undo approval. The
|
||||
route needs the file write service (a non-empty `files.allowlist`; otherwise
|
||||
503) and returns 409 `stamp_missing` without a delivery stamp, 403
|
||||
`allowlist_missing` outside every allowlist root, and 500 otherwise; success
|
||||
returns `{ ok, path, alreadyFrozen, briefWrite }` (`routes/review.ts`).
|
||||
|
||||
The export embeds contained images as data URIs and links videos with an
|
||||
optional poster. `containedMediaPath` checks both resolved paths and existing
|
||||
symlink targets against the slice directory. Unavailable/outside media gets
|
||||
an explicit rendered message.
|
||||
|
||||
After a new freeze, the route may update the generated spine in a conforming
|
||||
`MISSION_BRIEF.md` through the conflict-checked file writer. A malformed brief
|
||||
is skipped, and brief-write failures are reported without undoing the export.
|
||||
The [`slices` routes](../../../packages/daemon/src/routes/slices.ts) separately
|
||||
serve proof assets; [`files` asset serving](content-surfaces.md) has its own
|
||||
allowlist and range behavior.
|
||||
|
||||
## Fleet composition
|
||||
|
||||
[`composeFleet`](../../../packages/daemon/src/domain/review/fleet-compose.ts)
|
||||
composes local data in-process and reads registered hosts' composed rig
|
||||
payloads. Its default fan-out concurrency is **4**, with a shared
|
||||
**4-second** read budget. Synchronous local work cannot be preempted by that
|
||||
timer; elapsed local work reduces the remaining remote budget.
|
||||
|
||||
Each remote host is read through its own `GET /api/review/rig`: SSH-only
|
||||
registry entries report `unsupported-transport`, auth refusals report
|
||||
`auth-failed`, and timeouts, malformed payloads and other errors report
|
||||
`unreachable`.
|
||||
|
||||
`unionFleet` keys attention by host plus item identity and retains host
|
||||
status. Unavailable hosts have absent counts, not synthetic zeros. A missing
|
||||
registry produces a local-only result; an unreadable existing registry is
|
||||
reported. Host-derived time/status facts are carried through, not recomputed
|
||||
as if every host shared one clock.
|
||||
@@ -1,164 +1,270 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: Mission Control — Queue Observability + 7-Verb Contract (PL-005)
|
||||
title: Mission Control — Queue Observability + Verb Contract
|
||||
status: active
|
||||
topics: [coordination, observability]
|
||||
domains: [engineering-advisor, operating-advisor, orchestrator, human-operator]
|
||||
applies-when: |
|
||||
Need to know how the daemon-backed Mission Control surface works — the
|
||||
seven views, the seven write verbs, the action audit table, the
|
||||
bearer-token middleware, or how queue observability maps to PL-004 sources.
|
||||
seven views, the eight write verbs, the action audit table, the
|
||||
bearer-token middleware, or how queue observability maps to the
|
||||
coordination primitive's sources.
|
||||
siblings: [coordination-primitive.md, workflow-runtime.md, ../ui/project-and-for-you.md]
|
||||
prerequisite-reads: [../README.md, coordination-primitive.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Mission Control — Queue Observability + 7-Verb Contract (PL-005)
|
||||
# Mission Control — Queue Observability + Verb Contract
|
||||
|
||||
Mission Control (PL-005) is the daemon-backed queue-observability surface: an
|
||||
integrated product UI inside the existing shell (a top-level `/mission-control`
|
||||
route, NOT a new managed app) over the PL-004 Phase A coordination primitive.
|
||||
Per the PRD acceptance criteria: seven views, seven verbs, first-class human
|
||||
seats, recent-ships=10, and a daemon-backed action audit table; no
|
||||
old-dashboard migration/cutover (`architecture.md` §3 L368, L390).
|
||||
Mission Control is the daemon's queue-observability and operator-action
|
||||
surface, mounted at `/api/mission-control` over the coordination primitive's
|
||||
`queue_items` and `stream_items` (see `coordination-primitive.md`). It has
|
||||
seven views, eight verbs, a recent-ships cap of 10
|
||||
(`mission-control-read-layer.ts:107`), and a daemon-backed action audit
|
||||
table. In the web UI the old `/mission-control` page now redirects to
|
||||
`/for-you`, and the For You feed carries the verb actions (§6).
|
||||
|
||||
> Verified against source at HEAD `7eaf524c`. Mission Control is PL-005 (an
|
||||
> early-0.3.0 graft); plugins and Claude auto-compaction are 0.3.1 features
|
||||
> and are NOT back-attributed here (slice-00 0.3.0-ground-truth seams a/row-7).
|
||||
> Verified against source at main `e8f0ab340db773392ec8be75b072d1c0f3068a50`. A bare file name such as
|
||||
> `mission-control-read-layer.ts:31` is in
|
||||
> `packages/daemon/src/domain/mission-control/`; `domain/…`, `routes/…`,
|
||||
> `middleware/…`, `db/…`, `index.ts`, `server.ts` and `startup.ts` are under
|
||||
> `packages/daemon/src/`. Each count sits beside the command that produces
|
||||
> it; run the command from the repository root to refresh it.
|
||||
|
||||
## 1. The seven views
|
||||
|
||||
`MISSION_CONTROL_VIEWS` is the canonical view list (re-confirmed at HEAD
|
||||
`packages/daemon/src/domain/mission-control/mission-control-read-layer.ts:32–42`):
|
||||
`my-queue`, `human-gate`, `fleet`, `active-work`, `recent-ships`,
|
||||
`recently-active`, `recent-observations`.
|
||||
`MISSION_CONTROL_VIEWS` is the canonical view list
|
||||
(`mission-control-read-layer.ts:31–39`): `my-queue`, `human-gate`, `fleet`,
|
||||
`active-work`, `recent-ships`, `recently-active`, `recent-observations`.
|
||||
|
||||
All seven return rows in the load-bearing 9-field phone-friendly content
|
||||
model (rig/mission name, current phase,
|
||||
active|idle|attention|blocked|degraded, next-action,
|
||||
Views: **7** — `sed -n '/^export const MISSION_CONTROL_VIEWS = \[/,/\] as const;/p' packages/daemon/src/domain/mission-control/mission-control-read-layer.ts | grep -c '^ "'`
|
||||
|
||||
All seven return rows in the 9-field phone-friendly content model,
|
||||
`CompactStatusRow` (`mission-control-read-layer.ts:60`): rig/mission name,
|
||||
current phase, active|idle|attention|blocked|degraded, next-action,
|
||||
pending-human-decision, read-cost, last-update timestamp,
|
||||
confidence/freshness, evidence link). The model is non-negotiable across all
|
||||
7 views; UI may render compact, JSON preserves all 9 (`architecture.md` §3
|
||||
L370).
|
||||
confidence/freshness, evidence link. The type's doc comment
|
||||
(`mission-control-read-layer.ts:43–59`) makes the model non-negotiable across
|
||||
all seven views: UI may render compact, JSON preserves all nine. Four further
|
||||
optional fields carry the queue item's id and context.
|
||||
|
||||
`MissionControlReadLayer` maps each view to its source-of-truth path
|
||||
(`mission-control-read-layer.ts:4–14`, re-confirmed at HEAD):
|
||||
Required row fields: **9** — `sed -n '/^export interface CompactStatusRow {/,/^}/p' packages/daemon/src/domain/mission-control/mission-control-read-layer.ts | grep -c -E '^ [a-zA-Z]+:'`
|
||||
|
||||
- `my-queue` / `human-gate` / `active-work` / `recent-ships` query PL-004
|
||||
Phase A `queue_items` via `QueueRepository`.
|
||||
- `fleet` consumes the per-rig CLI capability cache + queue summary.
|
||||
- `recently-active` delegates to PL-004 Phase B
|
||||
`ViewProjector.show("recently-active")`.
|
||||
- `recent-observations` reads PL-004 Phase A `stream_items` via
|
||||
`StreamStore`.
|
||||
`MissionControlReadLayer.readView` (`mission-control-read-layer.ts:143`) maps
|
||||
each view to its source:
|
||||
|
||||
Filesystem fallbacks (`~/.openrig/stream/<date>.jsonl`, raw queue file grep)
|
||||
are graceful-degradation aids, NOT the primary path (`architecture.md` §3
|
||||
L378).
|
||||
- `my-queue` / `human-gate` / `active-work` / `recent-ships` query
|
||||
`queue_items` via `QueueRepository`. `my-queue` uses the `operatorSession`
|
||||
query parameter when given, else `workspace.operator_seat_name` (default
|
||||
empty), else the only registered human; when none is found, or more than
|
||||
one, it returns no rows and a `degradedFields` note rather than widening the
|
||||
view (`mission-control-read-layer.ts:137–141`, `:166–168`;
|
||||
`routes/mission-control.ts:521`).
|
||||
- `fleet` calls `MissionControlFleetCliCapability.rollupFleet()`
|
||||
(`mission-control-read-layer.ts:197`;
|
||||
`mission-control-fleet-cli-capability.ts:109`), which walks the rig
|
||||
registry (`:110`), summarises each rig's `queue_items` (`:135`), and runs a
|
||||
per-rig CLI capability probe (`makeLocalCliCapabilityProbe`, `:77`, wired
|
||||
at `startup.ts:1597`).
|
||||
- `recently-active` delegates to the view projector's built-in
|
||||
`ViewProjector.show("recently-active")` (`mission-control-read-layer.ts:246`).
|
||||
- `recent-observations` reads the latest 50 `stream_items` via `StreamStore`
|
||||
with `direction: "latest"` (`mission-control-read-layer.ts:265–268`).
|
||||
|
||||
> Scope note (slice-00 0.3.0-ground-truth OPEN-3, carried): the exact
|
||||
> For-You verb subset is an unresolved velocity slice-01 ruling. This module
|
||||
> describes the **system-level** 7-verb vocabulary (proven, slice-00
|
||||
> §1.6/seam-c). It does NOT enumerate a For-You-surface subset — that
|
||||
> surface is described in `../ui/project-and-for-you.md` when authored.
|
||||
There is no filesystem fallback. When no `StreamStore` is wired,
|
||||
`recent-observations` returns no rows with
|
||||
`sourceFallback: "stream-store-not-wired"`
|
||||
(`mission-control-read-layer.ts:258–262`), and `fleet` always reports
|
||||
`sourceFallback: "daemon-internal-projection"`
|
||||
(`mission-control-fleet-cli-capability.ts:156`).
|
||||
|
||||
## 2. The seven verbs (the write contract)
|
||||
> Scope note: this module describes the daemon's verb vocabulary. It does
|
||||
> NOT enumerate which verbs the For You cards offer; that surface is
|
||||
> described in `../ui/project-and-for-you.md`. The terminal UI opened by bare
|
||||
> `rig` or `rig tui` is called the "mission-control TUI" in source
|
||||
> (`packages/tui/README.md:1`), but it does not call these routes.
|
||||
|
||||
The seven verbs execute through the load-bearing
|
||||
`MissionControlWriteContract` (re-confirmed at HEAD
|
||||
`packages/daemon/src/domain/mission-control/mission-control-write-contract.ts:27–36`):
|
||||
## 2. The eight verbs (the write contract)
|
||||
|
||||
`MISSION_CONTROL_VERBS` (`mission-control-action-log.ts:14–26`) is the verb
|
||||
list. The verbs execute through `MissionControlWriteContract.act`
|
||||
(`mission-control-write-contract.ts:131`); the closure mapping is
|
||||
`verbToClosure` (`:509`).
|
||||
|
||||
Verbs: **8** — `sed -n '/^export const MISSION_CONTROL_VERBS = \[/,/\] as const;/p' packages/daemon/src/domain/mission-control/mission-control-action-log.ts | grep -c '^ "'`
|
||||
|
||||
| Verb | Effect |
|
||||
|---|---|
|
||||
| `approve` | `state="done"`, `closure_reason="no-follow-on"` |
|
||||
| `deny` | `state="done"`, `closure_reason="denied"` |
|
||||
| `route` | `state="done"`, `closure_reason="handed_off_to"`, `closure_target`+`handed_off_to`=route target; creates new qitem at the route target (1-hop) |
|
||||
| `route` | `state="handed-off"`, `closure_reason="handed_off_to"`, `closure_target`+`handed_off_to`=route target; creates a new queue item at the route target (1-hop) |
|
||||
| `annotate` | no queue mutation; audit record only |
|
||||
| `hold` | `state="blocked"`, `closure_reason="blocked_on"` |
|
||||
| `drop` | `state="done"`, `closure_reason="canceled"` |
|
||||
| `handoff` | the 4-step shape (see below) |
|
||||
| `handoff` | same closure and new destination item as `route`; the 4-step shape (see below) |
|
||||
| `resolve` | only for a `blocked` item parked on a human seat: moves it to `in-progress` with the required decision text as the transition note; no closure and no new item (`:369`, `:386`, `:410–411`). After commit it sends a best-effort nudge with the decision text to the item's owner unless `notify: false` (`:450–467`); the CLI wrapper is `rig queue resolve <qitemId> --decision <text>` (`packages/cli/src/commands/queue.ts:828`) |
|
||||
|
||||
Each verb is one atomic daemon transaction: queue mutation via
|
||||
`QueueRepository.updateWithinTransaction()` (which preserves Phase A
|
||||
hot-potato closure validation — see `coordination-primitive.md` §3) + an
|
||||
audit row in `mission_control_actions` + a persisted
|
||||
`mission_control.action_executed` event, all in one `db.transaction`. The
|
||||
4-step `handoff` shape (source-update + destination-create + opt-in
|
||||
best-effort notify + audit-record append) is verified atomic; notify failure
|
||||
does NOT roll back durable mutations (PRD invariant). Failure-injection rolls
|
||||
back source closure + audit row + new qitem together (`architecture.md` §3
|
||||
L380; `mission-control-write-contract.ts:5,15`).
|
||||
`annotate` requires `annotation`, `hold` and `drop` require `reason`, `route`
|
||||
and `handoff` require `destinationSession`, and `resolve` requires a non-empty
|
||||
`decision`. A missing `annotation`, `destinationSession` or `decision` returns
|
||||
400. A `drop` without `reason` passes the queue closure check and is refused by
|
||||
the audit record instead; the wrapped `reason_required` error has no status
|
||||
mapping, so it returns 500 (`mission-control-action-log.ts:128–133`,
|
||||
`mission-control-write-contract.ts:245–248`). Items already `done` or
|
||||
`handed-off` are refused with 409 `qitem_already_terminal`, except under
|
||||
`resolve`, which answers 409 `qitem_not_leg1_parked` for any item not parked on
|
||||
a human seat. An unknown item returns 404 `qitem_not_found`
|
||||
(`routes/mission-control.ts:240–251`, `:330–341`).
|
||||
|
||||
Each verb is one atomic daemon transaction: the queue mutation via
|
||||
`QueueRepository.updateWithinTransaction()` (`domain/queue-repository.ts:2371`,
|
||||
which keeps the hot-potato closure validation — see
|
||||
`coordination-primitive.md` §3), an audit row in `mission_control_actions`
|
||||
(`mission-control-write-contract.ts:207`), and a persisted
|
||||
`mission_control.action_executed` event (`:225`), all in one
|
||||
`db.transaction`. For every verb except `annotate`, that transaction is
|
||||
opened by `EventBus.withNotifyEnvelope` (`:157`; `domain/event-bus.ts:112`,
|
||||
`:126`); `annotate` opens `db.transaction` itself
|
||||
(`mission-control-write-contract.ts:308`).
|
||||
|
||||
The 4-step `handoff` shape (source-update + destination-create + best-effort
|
||||
notify + audit-record append) is shared by `route`. The destination item is
|
||||
created in the same transaction (`createWithinTransaction`, `:173`), along
|
||||
with the successor's wake intent (`stageWakeIntent`, `:195`, checked by
|
||||
`assertTerminalClosureHasIntent`, `:242`). The wake itself runs after commit
|
||||
(`deliverWakeForSuccessor`, `:264`). It is on unless the caller passes
|
||||
`notify: false` (`domain/queue-repository.ts:1182`), and a notify failure
|
||||
does NOT roll back durable mutations (`mission-control-write-contract.ts:272–275`).
|
||||
A failed destination create rolls back the source closure, the audit row and
|
||||
the new item together
|
||||
(`packages/daemon/test/mission-control-write-contract.test.ts:220`).
|
||||
|
||||
## 3. The action audit table
|
||||
|
||||
`mission_control_actions` (`037_mission_control_actions.ts:54` `CREATE TABLE
|
||||
IF NOT EXISTS mission_control_actions`) is append-only at the API surface:
|
||||
it records every operator action through Mission Control with before/after
|
||||
qitem snapshots for forensic reconstruction. Columns include `action_verb`
|
||||
(TEXT, app-layer enum enforcement, `:56`) and `acted_at` (TEXT NOT NULL ISO
|
||||
timestamp); indexes `(acted_at DESC, action_verb)`,
|
||||
`(qitem_id, acted_at DESC)`, `(actor_session, acted_at DESC)`
|
||||
(`037_mission_control_actions.ts:28–44`). Phase B added NO migrations — this
|
||||
Phase A table is the only data source (`architecture.md` §3 L364, L392).
|
||||
`mission_control_actions` (`db/migrations/037_mission_control_actions.ts:58`
|
||||
`CREATE TABLE IF NOT EXISTS mission_control_actions`) is append-only at the
|
||||
API surface: `record()` is the only writer `MissionControlActionLog` exposes
|
||||
(`mission-control-action-log.ts:113`). It records every operator action
|
||||
through Mission Control with before/after queue item snapshots for forensic
|
||||
reconstruction. Columns include `action_verb` (TEXT, app-layer enum
|
||||
enforcement, `037_mission_control_actions.ts:60`) and `acted_at` (TEXT NOT
|
||||
NULL ISO timestamp, `037_mission_control_actions.ts:63`);
|
||||
indexes `(acted_at DESC, action_verb)`, `(qitem_id, acted_at DESC)`,
|
||||
`(actor_session, acted_at DESC)` (`037_mission_control_actions.ts:72–77`).
|
||||
One later migration adds a nullable `identity_provenance` column
|
||||
(`db/migrations/065_identity_provenance.ts:18`). The acting session comes
|
||||
from the `X-OpenRig-Session` header when present (the CLI sends it); a
|
||||
headerless browser request falls back to the body `actorSession`. The row
|
||||
records how the actor was established: `transport:v1`, `relay:v1` for a
|
||||
forwarded request, `claimed:v1` for a headerless body actor, or
|
||||
`origin-unknown:v1` (`routes/mission-control.ts:347–351`;
|
||||
`routes/require-sender-identity.ts:120`).
|
||||
|
||||
## 4. PL-005 Phase B — bearer middleware, notifications, audit browse
|
||||
Scope approvals (`POST /api/scope/approve`) also append `approve` rows through
|
||||
the same `MissionControlActionLog`, with `qitem_id` null and the scope target
|
||||
in `audit_notes_json` (`domain/scope/scope-approve.ts:292`). The audit browse
|
||||
(§4) reads only this table.
|
||||
|
||||
Phase B extends the daemon Mission Control surface:
|
||||
## 4. Bearer middleware, notifications, audit browse
|
||||
|
||||
- **Bearer-token middleware** —
|
||||
`packages/daemon/src/middleware/auth-bearer-token.ts` (re-confirmed at
|
||||
HEAD: header `auth-bearer-token.ts:1–9` "PL-005 Phase B"). Constant-time
|
||||
bearer comparison via Node `crypto.timingSafeEqual`; the daemon refuses to
|
||||
start with a non-loopback bind interface AND an empty bearer config
|
||||
(startup-side check). Bearer enforced on the write verbs:
|
||||
`app.post("/action", requireAuth)` (`routes/mission-control.ts:294`) and
|
||||
`app.post("/notifications/test", requireAuth)` (`:295`). v0 is bearer-on-write;
|
||||
no OAuth/SSO/per-user model.
|
||||
- **Bearer-token middleware** — `middleware/auth-bearer-token.ts`.
|
||||
Constant-time comparison via Node `crypto.timingSafeEqual`
|
||||
(`constantTimeEqual`, `:36`, `:50`). `authBearerTokenMiddleware` (`:93`)
|
||||
passes every request through when no token is configured (`:98`). The
|
||||
token comes from `OPENRIG_AUTH_BEARER_TOKEN` (`index.ts:288`). The daemon
|
||||
refuses to start when the bind host is set explicitly, is neither loopback
|
||||
nor Tailscale (a hostname is resolved first), and no bearer token is set
|
||||
(`assertBindAuthInvariant`, `auth-bearer-token.ts:240`, called at
|
||||
`index.ts:296`). Bearer enforced on the write routes:
|
||||
`app.post("/action", requireAuth)` (`routes/mission-control.ts:307`) and
|
||||
`app.post("/notifications/test", requireAuth)` (`:308`). Reads are not
|
||||
bearer-gated. It is one static token; no OAuth/SSO/per-user model
|
||||
(`auth-bearer-token.ts:11–14`). Every Mission Control request, read or
|
||||
write, first passes the daemon's `/api/*` browser boundary
|
||||
(`server.ts:647`; `middleware/browser-boundary.ts`): only known target host
|
||||
names, and, when an `Origin` header is present, only the daemon's own UI
|
||||
origin or an `OPENRIG_ALLOWED_ORIGINS` entry. A valid bearer token waives
|
||||
only the host-name check.
|
||||
- **Notification dispatcher** — two adapters
|
||||
(`notification-adapter-ntfy.ts` default + `notification-adapter-webhook.ts`
|
||||
alternate; selected via `OPENRIG_NOTIFICATIONS_MECHANISM` env) plus
|
||||
`notification-dispatcher.ts` (re-confirmed in
|
||||
`domain/mission-control/`). ntfy.sh recommended for the operator's phone
|
||||
via tailnet.
|
||||
- **Read-only audit-history browse** — `audit-browse.ts` over
|
||||
`mission_control_actions`, exposed at `GET /api/mission-control/audit`
|
||||
(`routes/mission-control.ts:346`) with filters and `(limit, before_id)`
|
||||
pagination cursored on SQLite `rowid` (`architecture.md` §3 L392).
|
||||
(`notification-adapter-ntfy.ts`, `notification-adapter-webhook.ts`) plus
|
||||
`notification-dispatcher.ts`. `OPENRIG_NOTIFICATIONS_MECHANISM` selects
|
||||
`ntfy`, `webhook` or `none`, and the default is `none`
|
||||
(`startup.ts:1642`). A dispatcher starts only when
|
||||
`OPENRIG_NOTIFICATIONS_TARGET` is also set (`:1643`, `:1650`) and the
|
||||
target URL passes validation (`:1661`); an unrecognized mechanism with a
|
||||
target set throws during daemon startup (`:1656–1659`). The ntfy adapter
|
||||
posts to a topic URL that the ntfy phone app subscribes to
|
||||
(`notification-adapter-ntfy.ts:1–6`). Notifications fire when a
|
||||
`human-gate` queue item is created, and also on each completed verb when
|
||||
`OPENRIG_NOTIFICATIONS_INCLUDE_VERB_COMPLETION=1` (`startup.ts:1648–1649`);
|
||||
each item, trigger and mechanism is sent at most once per daemon run
|
||||
(`notification-dispatcher.ts:119–134`, `:156–158`). `POST
|
||||
/notifications/test` returns 503 `notifications_unconfigured` when no
|
||||
dispatcher is running (`routes/mission-control.ts:481–492`).
|
||||
|
||||
Adapters: **2** — `git grep -l 'implements NotificationAdapter' -- packages/daemon/src | wc -l`
|
||||
- **Read-only audit-history browse** — `MissionControlAuditBrowse.query`
|
||||
(`audit-browse.ts:78`) over `mission_control_actions`, exposed at
|
||||
`GET /api/mission-control/audit` (`routes/mission-control.ts:441`) with
|
||||
filters (including `scope_tier`, `scope_id`, `scope_path` and
|
||||
`approval_scope` for scope-approval rows) and `(limit, before_id)`
|
||||
pagination cursored on SQLite `rowid` (`audit-browse.ts:134`); the page size
|
||||
defaults to 50 and is capped at 200 (`audit-browse.ts:61–62`).
|
||||
|
||||
## 5. Mission Control events
|
||||
|
||||
> Drift-fix D8 / OPEN-4 (carried verbatim, slice-00): `architecture.md` §3
|
||||
> L394 says "Existing 32 PL-004 events untouched" — internally inconsistent
|
||||
> with §3 L410's "20" and predates 0.3.x. **Do NOT carry either number.**
|
||||
> The current `RigEvent` union (`packages/daemon/src/domain/types.ts:94`)
|
||||
> has **73 members** total (slice-00 §1.8, re-confirmed at HEAD); the
|
||||
> additive `mission_control.*` events are described below WITHOUT asserting
|
||||
> a contested PL-004 sub-count.
|
||||
The `RigEvent` union starts at `domain/types.ts:108`; its four
|
||||
`mission_control.*` members are at `domain/types.ts:308–313`.
|
||||
|
||||
Re-confirmed `domain/types.ts:212–218`: Phase A added
|
||||
`mission_control.action_executed`, `mission_control.cli_drift_detected`,
|
||||
`mission_control.view_refreshed`; Phase B added
|
||||
`mission_control.notification_sent`, `mission_control.notification_failed`
|
||||
(5 `mission_control.*` members at HEAD).
|
||||
`RigEvent` members: **98** — `sed -n '/^export type RigEvent =/,/^export type PersistedEvent/p' packages/daemon/src/domain/types.ts | grep -c 'type: "'`
|
||||
|
||||
`mission_control.*` members: **4** — `sed -n '/^export type RigEvent =/,/^export type PersistedEvent/p' packages/daemon/src/domain/types.ts | grep -c 'type: "mission_control\.'`
|
||||
|
||||
- `mission_control.action_executed` — written by the write contract
|
||||
(`mission-control-write-contract.ts:225`).
|
||||
- `mission_control.cli_drift_detected` — emitted by the fleet capability
|
||||
probe (`mission-control-fleet-cli-capability.ts:171`).
|
||||
- `mission_control.notification_sent` / `mission_control.notification_failed`
|
||||
— emitted by the notification dispatcher (`notification-dispatcher.ts:174`,
|
||||
`:184`).
|
||||
- *(retired)* `mission_control.view_refreshed` — retired per #490; views are
|
||||
read-on-demand queries without background refresh caching.
|
||||
|
||||
## 6. Route surface
|
||||
|
||||
`missionControlRoutes({ bearerToken })` is mounted at `server.ts:498`. Key
|
||||
routes (`routes/mission-control.ts`): `GET /views` (`:245`),
|
||||
`GET /cli-capabilities` (`:250`), `POST /action` (`:298`, auth-gated),
|
||||
`GET /audit` (`:346`), `POST /notifications/test` (auth-gated), plus an SSE
|
||||
surface. The integrated UI is the `/mission-control` top-level route in
|
||||
`packages/ui/src/routes.tsx` mounting `MissionControlSurface`
|
||||
(`architecture.md` §3 L390); UI detail lands in
|
||||
`../ui/project-and-for-you.md`.
|
||||
`missionControlRoutes({ bearerToken })` is mounted at `/api/mission-control`
|
||||
(`server.ts:790–793`). Routes (`routes/mission-control.ts`): `GET /views`
|
||||
(`:259`), `GET /cli-capabilities` (`:264`), `GET /destinations` (`:276`),
|
||||
`GET /sse` and its alias `GET /watch` (`:303–304`; they forward
|
||||
`action_executed` and `cli_drift_detected` events,
|
||||
`:289–291`), `POST /action` (`:314`, auth-gated), `GET /audit` (`:441`),
|
||||
`POST /notifications/test` (`:481`, auth-gated), and `GET /views/:view-name`
|
||||
(`:509`).
|
||||
|
||||
`POST /action` accepts an optional `hostId`: for a non-local host it forwards
|
||||
the same verb to that host's daemon (http-transport host entries only; 10 s
|
||||
timeout). The origin host writes the audit row and nothing is written
|
||||
locally; a forwarding failure returns 502 `remote_action_failed` with a
|
||||
`failureClass` (`routes/mission-control.ts:13`, `:352–414`).
|
||||
|
||||
In the web UI (`packages/ui/src/routes.tsx`), `/mission-control` is a
|
||||
redirect to `/for-you` (`:495–500`). `/for-you` renders the For You `Feed`
|
||||
(`:130–134`), whose cards call `POST /action` through `VerbActions`
|
||||
(`packages/ui/src/components/for-you/FeedCard.tsx:13`), and `/search` renders
|
||||
`AuditHistoryView` over `GET /audit` (`routes.tsx:304–308`).
|
||||
`MissionControlSurface`
|
||||
(`packages/ui/src/components/mission-control/MissionControlSurface.tsx:9`)
|
||||
still exists and renders the same `Feed`, but nothing imports it. UI detail
|
||||
lands in `../ui/project-and-for-you.md`.
|
||||
|
||||
## See also
|
||||
|
||||
- `coordination-primitive.md` — the PL-004 Phase A `queue_items`/`stream_items`
|
||||
sources Mission Control reads, and the hot-potato closure contract verbs honor.
|
||||
- `workflow-runtime.md` — PL-004 Phase D transactional-scribe runtime.
|
||||
- `../ui/project-and-for-you.md` — the UI surface counterpart (authored phase).
|
||||
- `coordination-primitive.md` — the `queue_items`/`stream_items` sources
|
||||
Mission Control reads, and the hot-potato closure contract verbs honor.
|
||||
- `workflow-runtime.md` — the workflow transactional-scribe runtime.
|
||||
- `../ui/project-and-for-you.md` — the For You feed that carries the verb
|
||||
actions in the web UI.
|
||||
- Source roots: `packages/daemon/src/domain/mission-control/`,
|
||||
`packages/daemon/src/middleware/auth-bearer-token.ts`,
|
||||
`packages/daemon/src/routes/mission-control.ts`,
|
||||
|
||||
@@ -5,139 +5,310 @@ status: active
|
||||
topics: [specification-and-bundles, release-and-versioning]
|
||||
domains: [engineering-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
Need to know how rig/pod bundles are assembled (schema-version-2 vs legacy
|
||||
v1), how bundle create/inspect/install and /api/up route across source kinds,
|
||||
the staged BootstrapOrchestrator plan/apply flow, or which legacy install-
|
||||
engine seams still ship for pre-reboot data.
|
||||
Locate npm package assembly, topology-bundle creation and validation,
|
||||
source routing, bootstrap plan/apply, or the retained package install engine.
|
||||
siblings: [agent-spec-and-startup.md, plugin-agent-image-context-pack.md]
|
||||
prerequisite-reads: [../README.md, agent-spec-and-startup.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Packaging, Bootstrap, Bundles, Legacy Install Engine
|
||||
# Packaging, bootstrap and bundles
|
||||
|
||||
How OpenRig packages a topology into a shareable bundle and reconstitutes it
|
||||
on another host. Fully dual-format: schema-version-2 pod bundles plus legacy
|
||||
v1 artifacts, routed deterministically by the bootstrap orchestrator.
|
||||
This module describes source at main commit
|
||||
`e8f0ab340db773392ec8be75b072d1c0f3068a50`. Source paths below are repository-relative.
|
||||
An npm CLI artifact and a `.rigbundle` have different builders and consumers; neither
|
||||
source verification nor archive integrity establishes that a daemon has adopted an artifact.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c` (`git describe` →
|
||||
> `v0.3.1-6-g7eaf524c`). Package version **0.3.1** (slice-00 §1.1). Source
|
||||
> located by `architecture.md` headings (§5 Bundles/bootstrap/legacy
|
||||
> compatibility, §6 Bundle create/inspect/install + /api/up, §11 Compat
|
||||
> note 3) per slice-08 §10.1 — line numbers advisory only.
|
||||
## npm CLI assembly
|
||||
|
||||
## 1. Bundle, bootstrap, legacy domain services
|
||||
`scripts/build-package.sh` builds the daemon, web UI, TUI and CLI, then assembles their
|
||||
outputs under `packages/cli`. The publishable file list and binary declarations live in
|
||||
`packages/cli/package.json`.
|
||||
|
||||
(`architecture.md` §5 "Bundles, bootstrap, and legacy compatibility")
|
||||
The assembler writes generated `BUILD_INFO` modules into the daemon and CLI outputs with
|
||||
the package version, Git commit, dirty flag and build time. The dirty check covers tracked
|
||||
and untracked inputs under `packages/` and `scripts/`; it is not a whole-worktree status
|
||||
claim.
|
||||
|
||||
- `pod-bundle-assembler.ts` — schema-version-2 bundle assembler.
|
||||
Re-confirmed: emits `schemaVersion: 2` (`pod-bundle-assembler.ts:167`).
|
||||
- `bundle-types.ts` — v1 and v2 manifest types plus parse/validate/serialize.
|
||||
Re-confirmed: v2 `PodBundleManifest` type carries `schemaVersion: 2`
|
||||
(`:23`); `validatePodBundleManifest` rejects unless `schema_version === 2`
|
||||
(`:38`); `serializePodBundleManifest` writes `schema_version: 2` (`:62`);
|
||||
`parsePodBundleManifest` (`:87`); legacy path
|
||||
`validateLegacyBundleManifest` (`:143`) — both formats live in one file.
|
||||
- `bundle-source-resolver.ts` — `LegacyBundleSourceResolver`
|
||||
(`bundle-source-resolver.ts:25`) plus `PodBundleSourceResolver` (`:132`).
|
||||
- `bootstrap-orchestrator.ts` — staged bootstrap flow with direct pod-aware
|
||||
rig and v2 bundle delegation. `BootstrapMode = "plan" | "apply"`
|
||||
(`bootstrap-orchestrator.ts:26`).
|
||||
- `up-command-router.ts` — spec/bundle source classification for `/api/up`.
|
||||
`SourceKind = "rig_spec" | "rig_bundle" | "rig_name"`
|
||||
(`up-command-router.ts:6`).
|
||||
The same script:
|
||||
|
||||
All re-confirmed present in `packages/daemon/src/domain/` @HEAD.
|
||||
- Generates shipped context packs through `scripts/generate-context-packs.mjs`.
|
||||
- Copies daemon output, assets, specs, context packs, policies and `docs/reference/`.
|
||||
- Checks the specs staging input with `scripts/check-internal-leak-guard.mjs` and writes
|
||||
the staged substance-root inventory into the package.
|
||||
- Copies the web UI and TUI outputs.
|
||||
- Runs `scripts/rewrite-daemon-imports.mjs` so staged CLI/TUI JavaScript resolves the
|
||||
shipped daemon output rather than an unpublished workspace package.
|
||||
|
||||
## 2. Bundle create / inspect / install
|
||||
This is the build layout. Native dependency installation and consumer runtime behavior need
|
||||
their own checks; this document does not infer them from a successful assembly.
|
||||
|
||||
(`architecture.md` §6 "Bundle create / inspect / install")
|
||||
## Topology-bundle creation
|
||||
|
||||
`routes/bundles.ts` is fully dual-format:
|
||||
`packages/daemon/src/routes/bundles.ts` mounts create, inspect, install and history handlers
|
||||
under `/api/bundles` (the mount is in `packages/daemon/src/server.ts`).
|
||||
|
||||
- **create** — detects a pod-aware RigSpec and uses `PodBundleAssembler`
|
||||
(accepts an optional `rigRoot`); legacy create still uses
|
||||
`LegacyBundleAssembler`.
|
||||
- **inspect** — safely extracts the archive, detects `schema_version`; v2
|
||||
returns `schemaVersion: 2`, `agents[]`, and integrity data; v1 returns the
|
||||
legacy manifest shape.
|
||||
- **install** — uses the full bootstrap plan/apply; bootstrap peeks the
|
||||
manifest and routes deterministically to `pod_bundle` or `rig_bundle`.
|
||||
Re-confirmed at source: `bootstrap-orchestrator.ts:134-143` — when
|
||||
`sourceKind === "rig_bundle"` it unpacks to a temp peek dir, reads
|
||||
`bundle.yaml`, and calls `parsePodBundleManifest(...)` to detect the
|
||||
schema version before routing.
|
||||
The create handler validates the source spec before comparing it with recorded live topology.
|
||||
A divergent spec is refused unless the request explicitly allows drift; an allowed divergence
|
||||
is recorded in the bundle's provenance notes. The exported spec is not silently rewritten
|
||||
from live state.
|
||||
|
||||
## 3. `/api/up` source routing
|
||||
| Source shape | Builder and output |
|
||||
|---|---|
|
||||
| Pod-aware rig spec | `packages/daemon/src/domain/pod-bundle-assembler.ts`: `PodBundleAssembler.assemble()` produces schema-version-2 metadata, vendors resolved agent trees, and rewrites agent references to local paths. |
|
||||
| Legacy rig spec | `packages/daemon/src/domain/bundle-assembler.ts`: `LegacyBundleAssembler` (imported as `BundleAssembler` by the route) collects the referenced legacy packages and produces the legacy manifest shape. |
|
||||
|
||||
(`architecture.md` §6 "`/api/up`")
|
||||
The pod builder collects culture, docs and startup material, preserves file bytes, and skips
|
||||
vendoring the terminal sentinel as an agent. A copied file keeps its source mode with owner
|
||||
read and write added. Required document collection failures stop assembly; missing declared
|
||||
skills can instead produce warnings, which are also written into the provenance notes.
|
||||
Inspect the builder's collection helpers when changing these distinctions.
|
||||
|
||||
`UpCommandRouter` + `BootstrapOrchestrator` own:
|
||||
Both route branches can consume a source-root `bundle.yaml` to carry declared skills,
|
||||
plugins, workflow specs, context packs, agent images and preconditions into staging before
|
||||
computing integrity. Manifest types, validation and serialization are in
|
||||
`packages/daemon/src/domain/bundle-types.ts`. Provenance and compatibility are metadata;
|
||||
provenance is not an author signature.
|
||||
|
||||
- direct pod-aware rig specs,
|
||||
- legacy rig specs,
|
||||
- v1 bundle installs,
|
||||
- v2 pod-bundle installs.
|
||||
Preconditions are carried as data. Commands containing shell operators produce a warning,
|
||||
inspect reports the block, and the behaviour view lists it under needs. The install and
|
||||
bootstrap code do not reference preconditions.
|
||||
|
||||
Plan mode and apply mode both work across those source kinds.
|
||||
Two more inputs are accepted only for a pod-aware spec; the legacy branch refuses them with
|
||||
a 400:
|
||||
|
||||
> Definitional note (carried, not a numeric drift) — `architecture.md` §6
|
||||
> describes bootstrap routing to `pod_bundle` / `rig_bundle`. The
|
||||
> `UpCommandRouter` *classification* enum is `SourceKind = "rig_spec" |
|
||||
> "rig_bundle" | "rig_name"` (`up-command-router.ts:6`); the
|
||||
> `pod_bundle`-vs-`rig_bundle` distinction is resolved one layer deeper in
|
||||
> `bootstrap-orchestrator.ts` by peeking the manifest schema version
|
||||
> (`:134-143`). Both statements are accurate at different layers; documented
|
||||
> here so the layering is explicit rather than appearing contradictory.
|
||||
- `--context-pack <dir>` (`vendorContextPackDir()` in
|
||||
`packages/daemon/src/domain/bundle-carried-context-pack.ts`) carries a pack's
|
||||
`manifest.yaml` and its declared files to `context-packs/<manifest name>/`. The pack may
|
||||
sit outside the rig folder.
|
||||
- `--project-dir <dir>` (`vendorProjectDir()` in
|
||||
`packages/daemon/src/domain/bundle-carried-project.ts`) carries a folder with a
|
||||
`project.yaml` to `project/`.
|
||||
|
||||
## 4. Legacy install engine (still ships)
|
||||
Create also refuses content it must not ship. `computeIntegrity()` in
|
||||
`packages/daemon/src/domain/bundle-integrity.ts` throws on sensitive paths (`.env` files,
|
||||
`*.pem`, `*.key`, `*.p12`, `credentials.*`, `tokens.*`, `.git/` and `node_modules/`).
|
||||
`assertShippableSubstance()` in `packages/daemon/src/domain/agent-resolver.ts` runs on
|
||||
vendored content and on the final staging tree, and refuses text containing
|
||||
`substrate/shared-docs/` and files declaring `taxonomy: lore`.
|
||||
|
||||
(`architecture.md` §5 "Legacy systems that still ship" + §11 compat note 3)
|
||||
### Configurations and package identity
|
||||
|
||||
These pre-reboot seams remain active for backward compatibility:
|
||||
A bundle folder may declare alternate team configurations in `configurations.yaml` beside
|
||||
`rig.yaml` (`packages/cli/src/lib/bundle-configuration.ts`). `rig bundle configurations
|
||||
<spec>` lists them. `rig bundle create --preset <name>` or `--seat <member=runtime>` resolves
|
||||
one and stages a copy of the rig folder in a temporary directory with the chosen runtimes and
|
||||
profiles, so the author's folder is never changed. With a GitHub folder link, `rig up`,
|
||||
`rig bundle create`, `inspect` and `install` take the same two options and stage a copy of the
|
||||
whole checkout (`importGitHubBundle()` in `packages/cli/src/lib/bundle-source.ts`).
|
||||
|
||||
- package install engine: `package-install-service.ts`,
|
||||
`package-manifest.ts`, `package-repository.ts`, `install-engine.ts`,
|
||||
`conflict-detector.ts`, `role-resolver.ts` (all re-confirmed present
|
||||
@HEAD).
|
||||
- bootstrap and requirement-probe support.
|
||||
- discovery and claim services.
|
||||
- tmux/cmux adapters and resume adapters.
|
||||
`packages/daemon/src/domain/bundle-identity.ts` defines the two identity values:
|
||||
|
||||
> Drift-check D-pkg — `architecture.md` §5/§6 carries no slice-00 numeric
|
||||
> drift in this content; the v2 bundle-assembler + dual-format flows were
|
||||
> verified accurate at HEAD (§1–§3 above). The footprint/migration counts
|
||||
> that ARE stale land in `daemon-core.md`, not here.
|
||||
- `configurationId()`: every member's `pod.member=runtime`, sorted and joined with `,`.
|
||||
Create computes it from the normalized spec and returns a 400 if the client sent a
|
||||
different one.
|
||||
- `packageDigest()`: SHA-256 over the sorted `integrity.files` entries (coverage label
|
||||
`openrig.package-digest/v1`). It covers every packaged file's bytes; it does not cover
|
||||
`bundle.yaml`, the junk files the integrity walk skips, or file modes.
|
||||
|
||||
Compat note 3 (carried verbatim, `architecture.md` §11): "Legacy
|
||||
compatibility seams still ship for pre-reboot data and v1 artifacts." (The
|
||||
full §11 list lives in `architecture-rules-and-event-system.md`.)
|
||||
Create and inspect responses carry the source, configuration ID, package digest, archive
|
||||
hash and assembler version.
|
||||
|
||||
> Provenance note (carried, not asserted): `bootstrap-orchestrator.ts:3-5`
|
||||
> still imports `LegacyRigSpec` / `LegacyRigSpecCodec` / `LegacyRigSpecSchema`
|
||||
> with in-source `TODO: AS-T08b — migrate to pod-aware RigSpec` markers, and
|
||||
> `:16` `TODO: AS-T12 — migrate to pod-aware bundle source resolver`. The
|
||||
> legacy seam is intentional, in-progress migration scaffolding — recorded
|
||||
> as-is, not smoothed.
|
||||
### Archive checks
|
||||
|
||||
## OPEN / carried items
|
||||
`packages/daemon/src/domain/bundle-archive.ts` owns `pack()` and `unpack()`;
|
||||
`packages/daemon/src/domain/bundle-integrity.ts` owns the per-file integrity map.
|
||||
|
||||
- **D-pkg (resolved-as-current):** no slice-00 numeric drift in this
|
||||
module's split content; v2 assembler + dual-format flows verified accurate
|
||||
at HEAD. Bundle-routing layering documented explicitly to pre-empt an
|
||||
apparent §6-vs-source contradiction.
|
||||
- Legacy-migration TODOs carried from source verbatim (AS-T08b / AS-T12).
|
||||
Packing requires a `.rigbundle` output path, sorts entries, uses portable tar metadata, a
|
||||
fixed tar timestamp and gzip level 9, and writes a sibling SHA-256 file. Unpacking checks that
|
||||
digest, rejects archive links and unsafe entry paths before extraction, requires
|
||||
`bundle.yaml` and its integrity section, and verifies the extracted file inventory. Hashes
|
||||
establish consistency with the supplied manifest and digest; they do not authenticate
|
||||
whoever supplied both.
|
||||
|
||||
## See also
|
||||
The inspect handler does not call `unpack()`, because it must report a broken bundle rather
|
||||
than refuse it. It reports a missing or mismatched digest as `digestValid: false`, applies the
|
||||
same unsafe-entry pre-scan (`collectUnsafeArchiveEntries()`) and extraction filter, and runs
|
||||
`verifyIntegrity()` to report an `integrityResult`. A syntactically valid manifest alone is
|
||||
not an inspected archive.
|
||||
|
||||
- `agent-spec-and-startup.md` — `RigInstantiator` / `PodRigInstantiator` and
|
||||
the dual-format spec seam that bundles wrap.
|
||||
- `plugin-agent-image-context-pack.md` — the 0.3.0/0.3.1 reusable
|
||||
starter-state / content-provenance cluster (authored separately in 8.4b).
|
||||
- `daemon-core.md` — `/api/up` + `/api/bundles` are among the 49 route
|
||||
mounts; `BootstrapOrchestrator` is constructed in the createDaemon
|
||||
sequence.
|
||||
- Source roots: `packages/daemon/src/domain/{pod-bundle-assembler,
|
||||
bundle-types,bundle-source-resolver,bootstrap-orchestrator,
|
||||
up-command-router}.ts`, `packages/daemon/src/routes/{bundles,up}.ts`.
|
||||
Inspect also returns a behaviour view, `describeBundleBehaviour()` in
|
||||
`packages/daemon/src/domain/bundle-behaviour.ts` (schema `openrig.bundle-behaviour/v1`). It
|
||||
reads the extracted archive only, through `bundle-behaviour-inspect.ts` (regular UTF-8 files,
|
||||
at most 1 MiB each and 8 MiB in total, opened without following links). It lists the team,
|
||||
each seat's permission posture, the files seats are told to read, what else runs, where
|
||||
content is written, literal outside addresses, needs, and what can't be known before launch.
|
||||
A schema-1 archive gets `state: not_generated`. Posture comes from the member's or rig's
|
||||
`permission_policy`: `builtin:yolo` is full bypass and `builtin:auto` is auto, both by launch
|
||||
flag; `builtin:locked`, `builtin:standard` and `builtin:open` are configuration; a policy file
|
||||
is read from the archive or marked unresolved; no policy means the product default. A rig-level
|
||||
`non_interruptive` boolean is reported on each full-bypass Claude Code or Codex seat as
|
||||
`nonInterruptiveDefault`, and `true` drops the Claude bypass first-run warning. The CLI prints
|
||||
the view before `rig bundle install` and `rig up` as diagnostics only; it never changes the
|
||||
request or the exit code.
|
||||
|
||||
## Source routing and bootstrap
|
||||
|
||||
`packages/daemon/src/domain/up-command-router.ts` classifies a source as
|
||||
`rig_spec`, `rig_bundle`, `rig_name` or `topology`. It recognizes named rigs,
|
||||
spec paths, `.rigbundle`, `.rigtopology`, and YAML with a top-level rigs list;
|
||||
extensionless paths also have content detection.
|
||||
|
||||
`packages/daemon/src/routes/up.ts` then selects the execution path:
|
||||
|
||||
| Input | Consumer |
|
||||
|---|---|
|
||||
| Existing rig name | Existing-rig restore path; unarchived rigs of that name are preferred. |
|
||||
| Single rig spec or bundle | `BootstrapOrchestrator.bootstrap()` in `packages/daemon/src/domain/bootstrap-orchestrator.ts`. |
|
||||
| Topology manifest | `MultiRigLauncher`, with a single-rig bootstrap or remote-up leaf per entry. |
|
||||
|
||||
Topology plan mode is rejected. Placement belongs on the individual topology entries; a
|
||||
top-level host flag is rejected. The selected topology parser/route also rejects nested
|
||||
topologies, existing-rig-name entries and `.rigbundle` entries: topology entries are spec
|
||||
paths only. This is narrower than the single-rig `up` surface.
|
||||
|
||||
Bootstrap distinguishes pod-aware and legacy specs, and inspects a bundle manifest to choose
|
||||
`PodBundleSourceResolver` or `LegacyBundleSourceResolver` from
|
||||
`packages/daemon/src/domain/bundle-source-resolver.ts`. Thus the router's
|
||||
`rig_bundle` kind does not imply a legacy manifest.
|
||||
|
||||
For pod-aware specs, plan mode validates and runs preflight probes; apply delegates to
|
||||
`PodRigInstantiator`. Plan mode still records a bootstrap run/result: it is not a pure
|
||||
file read. Apply reports partial completion when nodes fail or require attention, preserving
|
||||
the created rig identity rather than claiming every member launched. Apply's non-interruptive
|
||||
choice is the request's, else the spec's top-level `non_interruptive`, else the
|
||||
`launch.non_interruptive` setting.
|
||||
|
||||
### Durable install target
|
||||
|
||||
Bundle apply requires an explicit target root at the HTTP boundary. The CLI supplies the
|
||||
current directory as the default for `rig up <file>.rigbundle` and for a GitHub link through
|
||||
`rig up` or `rig bundle install`. `rig bundle install` with a local `.rigbundle` sends no
|
||||
default, so apply without `--target` gets a 400. For a pod bundle,
|
||||
`materializePodBundle()` copies the verified extraction into that durable root before
|
||||
instantiation, so local agent references and relative working directories survive removal
|
||||
of the extraction directory. It checks destination conflicts before copying, preserves
|
||||
identical files, and refuses differing files or incompatible destination types.
|
||||
|
||||
When an unarchived or running team already has the bundle's rig name (`bundleInstallContext()`
|
||||
in `packages/daemon/src/domain/bundle-install-context.ts`), pod-bundle apply refuses before
|
||||
writing if any of them is running. If the target is that team's own install folder (its
|
||||
`bundle.yaml` names the offered schema-2 bundle, or `my-bundle` or `github-bundle`, the default
|
||||
names before 0.6.7, and it is the folder recorded in `rigs.install_root` when any of those teams
|
||||
recorded one; `isExistingBundleTarget()`), bootstrap first
|
||||
confirms the old sessions are stopped; materialization then copies each conflicting path to a
|
||||
`bundle-backups/reinstall-*` folder under the OpenRig home, with a `RESTORE.json`, before
|
||||
replacing it, and leaves unrelated files in place. Any other target keeps the conflict refusal.
|
||||
The instantiator archives the stopped earlier generation. Pod-bundle apply records the
|
||||
target's real path as the new rig's `install_root` (migration `096`).
|
||||
|
||||
Pod bundles containing service definitions are refused by bootstrap; their service path
|
||||
requires a stable spec directory. Direct spec bootstrap has separate service prelaunch
|
||||
handling. See [agent-spec-and-startup.md](agent-spec-and-startup.md) for instantiation.
|
||||
|
||||
## GitHub folder links and the bundle check
|
||||
|
||||
`packages/cli/src/lib/bundle-source.ts` lets `rig up`, `rig bundle create`, `inspect` and
|
||||
`install` take a GitHub folder link (`isGitHubBundleLink()`). Only a credential-free
|
||||
`https://github.com/owner/repo[/tree/<ref>/<folder>]` link is accepted. The ref is resolved to
|
||||
the longest matching branch or tag, or taken as a full commit hash. The import:
|
||||
|
||||
- needs a healthy local daemon whose `selfHostId` matches the local origin;
|
||||
- runs Git with credential helpers, hooks and non-HTTPS protocols disabled;
|
||||
- shallow-fetches the exact commit into `bundle-imports/` under the OpenRig home, requires a
|
||||
`rig.yaml` in the folder, and refuses symlinks or agent and package references that resolve
|
||||
outside the checkout;
|
||||
- posts the folder to `/api/bundles/create` with the source recorded in provenance, refuses a
|
||||
response without a package digest or with a different source commit, and writes
|
||||
`source.json` and `build.json` in the import folder.
|
||||
|
||||
`rig bundle check <folder>` (`checkBundleFolder()` in `packages/cli/src/lib/bundle-check.ts`)
|
||||
is an advisory, file-read-only check against `openrig.bundle-standard/v1`; it makes no daemon,
|
||||
launch or provider call. Its rules are `pod_aware_rig`, `readme_in_docs`, `referenced_files`,
|
||||
`portable_agents`, `minimum_versions`, `configurations` and `credential_paths` (a filename scan
|
||||
bounded at 10,000 entries that skips `.git`, `node_modules` and symlinks). `readme_coverage`
|
||||
and `embedded_secrets` are always reported as not checked, and host-resolved resources are
|
||||
reported as checked at launch (`host_resources`). Any finding sets exit code 1.
|
||||
|
||||
## Install checks and content routing
|
||||
|
||||
The install handler in `packages/daemon/src/routes/bundles.ts` obtains a source-path
|
||||
lock, reads validated metadata, checks compatibility, and checks a declared rig name
|
||||
against same-name rigs with running sessions before bootstrap; a stopped team of that name
|
||||
doesn't block it. A conflict returns a 400 with `status: "not_attempted"`, the installed team
|
||||
and the offered bundle, and three choices: use the existing team, stop it and retry to replace
|
||||
it, or cancel. The compatibility override `skipVersionCheck` and name-conflict override `force`
|
||||
are explicit request fields; neither skips the metadata pass, which always validates the
|
||||
manifest.
|
||||
|
||||
`packages/daemon/src/domain/bundle-conflict-detector.ts` implements the rig-name check.
|
||||
It is not a complete agent, port or filesystem collision audit; a missing rig name supplies
|
||||
no name comparison. Target-file conflicts are handled separately by materialization.
|
||||
Skipping these prechecks does not skip archive validation in bootstrap.
|
||||
|
||||
Declared content is routed by `routeBundleContents()` in
|
||||
`packages/daemon/src/domain/bundle-content-routing.ts`, which unpacks the bundle once and
|
||||
routes each kind independently. When it runs depends on the bundle schema:
|
||||
|
||||
- **Pod-aware (schema 2):** bootstrap runs it as `PodRigInstantiator`'s prelaunch hook, after
|
||||
the rig record exists and before any seat launches, so every seat's first turn can see the
|
||||
content.
|
||||
- **Legacy (schema 1):** there is no hook, so the install route routes after bootstrap, and
|
||||
only when the status is `completed`.
|
||||
|
||||
| Manifest content | Destination |
|
||||
|---|---|
|
||||
| `skills` | The OpenRig `packages` cache, stripping the legacy `packages/` prefix; this does not import a complete harness skill into the managed skill catalog. |
|
||||
| `plugins` | The OpenRig `plugins` root, through local plugin references. |
|
||||
| `workflow_specs` | `workflows/` beneath the resolved `workspaceSpecsRoot`. If that setting is empty, this kind is recorded as a routing failure. |
|
||||
| `context_packs` | The configured `context.root`, matching context-library discovery. |
|
||||
| `agent_images` | The OpenRig `agent-images` root; declarations address image directories containing a manifest. |
|
||||
| `project` | Registered in the workspace catalog (`workspace.catalog_path`) with the rig associated, and its files placed under `workspace.projects_root/<id>` (`registerBundleProject()` in `packages/daemon/src/domain/workspace/project-registration.ts`). A conflict changes nothing and is a routing failure. |
|
||||
|
||||
Context packs are never merged into an installed pack of the same name: an identical one is
|
||||
reported as `already_installed`, a different one is kept unchanged as `kept_existing`. After
|
||||
routing, the live context library rescans, and a routed pack it can't load becomes a routing
|
||||
failure.
|
||||
|
||||
The per-kind implementations are the `bundle-skills-router.ts`, `bundle-plugins-router.ts`,
|
||||
`bundle-workflow-specs-router.ts`, `bundle-context-packs-router.ts` and
|
||||
`bundle-agent-images-router.ts` files under `packages/daemon/src/domain/`. Routing is best
|
||||
effort in both cases: the hook always lets the launch continue, and a failure never rolls back
|
||||
or fails the install. Failures appear in the result's `routingFailures` and `warnings`, in a
|
||||
`route_bundle_contents` stage for the hook, and in the install audit record.
|
||||
|
||||
Install audit records are appended best effort to `bundle-audit.jsonl` under the OpenRig
|
||||
home; the history endpoint reads that audit. Bootstrap run state and action journals are
|
||||
separate, repository-backed records.
|
||||
|
||||
## Legacy package install path
|
||||
|
||||
The legacy path remains executable, not just an archive parser.
|
||||
`packages/daemon/src/routes/packages.ts` is mounted at `/api/packages`, and legacy
|
||||
bootstrap calls `PackageInstallService` in
|
||||
`packages/daemon/src/domain/package-install-service.ts`.
|
||||
|
||||
The source chain is package resolution/manifest validation, planning and conflict policy,
|
||||
then apply/verification with repository records:
|
||||
|
||||
- `resolvePackage()` in `packages/daemon/src/domain/package-resolve-helper.ts`, which reads
|
||||
and validates through `package-manifest.ts`. `package-resolver.ts` supplies the types; its
|
||||
`PackageResolver` class has no production caller.
|
||||
- `packages/daemon/src/domain/install-planner.ts`, `conflict-detector.ts` and
|
||||
`install-policy.ts`. The planner refuses any planned destination outside the install
|
||||
target.
|
||||
- `packages/daemon/src/domain/install-engine.ts`, `install-verifier.ts`,
|
||||
`install-repository.ts` and `package-repository.ts`.
|
||||
|
||||
Legacy bootstrap also probes requirements and records staged outcomes before rig
|
||||
instantiation. A failed package installation prevents that path from importing the rig.
|
||||
This compatibility implementation does not imply that pod-aware bundles use the same
|
||||
package-install stages.
|
||||
|
||||
## Related maps
|
||||
|
||||
- [plugin-agent-image-context-pack.md](plugin-agent-image-context-pack.md): content library
|
||||
consumers and their state boundaries.
|
||||
- [daemon-core.md](daemon-core.md): bootstrap construction and HTTP wiring.
|
||||
- [agent-spec-and-startup.md](agent-spec-and-startup.md): spec resolution, projection and launch.
|
||||
@@ -5,353 +5,321 @@ status: active
|
||||
topics: [extension-and-user-workspace, continuity, skill-management]
|
||||
domains: [engineering-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
Need to know how OpenRig discovers plugins, captures/forks agent images,
|
||||
assembles/sends context packs, or how the Claude auto-compaction enforcer
|
||||
decides to send /compact. Author-mode module — no prior architecture.md
|
||||
prose; every claim is sourced to file:line at HEAD.
|
||||
Locate plugin discovery and vendoring, context-pack addressing/composition,
|
||||
agent-image capture/fork/protection, or Claude guided-compaction behavior.
|
||||
siblings: [packaging-bootstrap-bundles.md, agent-spec-and-startup.md]
|
||||
prerequisite-reads: [../README.md, agent-spec-and-startup.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Content Layer — Plugins, Agent Images, Context Packs, Compaction Policy
|
||||
# Content libraries and compaction
|
||||
|
||||
Four filesystem-canonical content primitives the daemon discovers and
|
||||
serves: **plugins**, **agent images**, **context packs**, and the **Claude
|
||||
auto-compaction policy enforcer**. None adds SQLite state — all are
|
||||
filesystem-canonical with in-memory daemon caches.
|
||||
This module describes source at main commit
|
||||
`e8f0ab340db773392ec8be75b072d1c0f3068a50`. Source paths below are repository-relative.
|
||||
Context packs, plugins and agent images have filesystem-backed content and daemon-side
|
||||
discovery. Their consumers can read database identity, mutate files, deliver messages or
|
||||
launch sessions; the whole layer is not a read-only catalog.
|
||||
|
||||
> **AUTHOR-FROM-SOURCE module.** No prior `architecture.md` prose exists
|
||||
> for this. Every load-bearing claim carries `> Source: <file:line>
|
||||
> @HEAD`; ambiguity is declared as an OPEN item, never smoothed. Paths are
|
||||
> relative to `packages/daemon/src/` unless prefixed `docs/`.
|
||||
>
|
||||
> Verified at HEAD `7eaf524c` (`git describe` → `v0.3.1-6-g7eaf524c`).
|
||||
> Package version **0.3.1**; HEAD carries 6 unreleased release-0.3.2
|
||||
> commits; no `v0.3.2` tag (daemon-core.md; slice-00 §1.1).
|
||||
## Context packs
|
||||
|
||||
## 0. Release attribution (forensic seam — read first)
|
||||
`packages/daemon/src/domain/context-packs/context-pack-library-service.ts` implements
|
||||
`ContextPackLibraryService`. A pack contains `manifest.yaml` and declared members.
|
||||
Its path-like ref is the lookup identity, independently of the manifest name/version;
|
||||
the entry ID is `context-pack:<ref>`.
|
||||
|
||||
Re-verified at HEAD via `git cat-file -e <tag>:<path>`:
|
||||
### Discovery and mutation
|
||||
|
||||
| Subsystem | Release | Forensic proof @HEAD |
|
||||
|---|---|---|
|
||||
| Context packs | **0.3.0** | `v0.3.0:domain/context-packs/context-pack-library-service.ts` → present (slice-00 0.3.0-GT §1.9) |
|
||||
| Agent images | **0.3.0** | `v0.3.0:domain/agent-images/agent-image-library-service.ts` → present (slice-00 0.3.0-GT §1.9) |
|
||||
| Plugins | **0.3.1, NOT 0.3.0** | `v0.3.0:domain/plugin-discovery-service.ts` → **ABSENT**; `v0.3.1:` → present (slice-00 0.3.0-GT seam (a); map row 5) |
|
||||
| Claude auto-compaction | **0.3.1 headline, no 0.3.0 antecedent** | `v0.3.0:domain/claude-compaction-enforcer.ts` → **ABSENT**; `v0.3.1:` → present (slice-00 0.3.0-GT map row 7) |
|
||||
Startup in `packages/daemon/src/startup.ts` configures the shipped context-pack root (when
|
||||
it exists), the resolved `context.root` and its `system` root, followed by the
|
||||
workspace-local `.openrig/context-packs` root. The workspace root is registered even before
|
||||
it exists, so a folder created later is found by the next sync; it is left out only when it
|
||||
is the same path as one of the other two. The library scans recursively, stops descending
|
||||
when it finds a pack manifest, skips `.tmp-add-*` staging folders, and does not follow
|
||||
directory symlinks.
|
||||
|
||||
> Source: re-run at HEAD `7eaf524c` —
|
||||
> `git cat-file -e v0.3.0:packages/daemon/src/domain/plugin-discovery-service.ts`
|
||||
> and `...claude-compaction-enforcer.ts` both fail ("exists on disk, but
|
||||
> not in 'v0.3.0'"); the two 0.3.0 library services resolve at `v0.3.0`.
|
||||
> Code present on disk at HEAD does NOT make plugins/compaction a 0.3.0
|
||||
> feature — ancestry is truth; the same-calendar-day commit date is the
|
||||
> forensic trap slice-00 0.3.0-GT seam (a) warns about.
|
||||
The first configured root to encounter a physical pack owns that directory. For distinct
|
||||
physical packs with the same ref, the later root wins. This distinction prevents overlapping
|
||||
roots from giving the same physical pack a second address. Scan replaces the in-memory ref
|
||||
index and reports malformed packs instead of indexing them.
|
||||
|
||||
**Do not back-attribute plugins or compaction to 0.3.0.** The CLI
|
||||
reference already encodes this ("## Plugin Inspection (v0.3.1)").
|
||||
`composeFromFiles()` creates a durable pack after validating its ref, destination and
|
||||
sources. `removeByRef()` removes the selected pack directory and rescans, but refuses
|
||||
shipped builtin packs. Library mutation and lookup share ref validation.
|
||||
|
||||
> Source: `docs/as-built/cli-reference.md:943` @HEAD.
|
||||
`packages/daemon/src/domain/context-packs/manifest-parser.ts` parses manifests.
|
||||
Its supported file suffixes include Markdown/YAML/text and `.sh`, `.ts`, `.mjs`,
|
||||
`.py` helper assets. Those assets are served as text; pack assembly does not execute them.
|
||||
The parser also validates atom metadata and named profiles.
|
||||
|
||||
## 1. Context packs (PL-014) — 0.3.0
|
||||
### Assembly, addresses and profiles
|
||||
|
||||
A **context pack** is a directory of `manifest.yaml` + included markdown /
|
||||
yaml / txt files: operator-authored, library-discoverable, reviewable, and
|
||||
composable. The `rig context` noun is delivery-free. No SQLite tables;
|
||||
in-memory cache at daemon scope.
|
||||
These are separate projections:
|
||||
|
||||
> Source: `domain/context-packs/context-pack-types.ts:1-11` @HEAD.
|
||||
| Operation | Source and behavior |
|
||||
|---|---|
|
||||
| Framed pack preview | `packages/daemon/src/domain/context-packs/bundle-assembler.ts`, `assembleBundle()`: pack/file headers, file metadata, token estimates and `missingFiles`. Missing members are reported and skipped; a failed read of a present member raises an error. |
|
||||
| Plain file composition | The same file's `assemblePlainFiles()`: present files in declared order, separated by two newlines, without framing or trimming their bytes. |
|
||||
| Addressed read | `packages/daemon/src/routes/context-packs.ts`, `/library/resolve-address`: longest matching pack-ref prefix, a declared file, then optional H2/H3 heading selection through `packages/daemon/src/domain/markdown-address.ts`. |
|
||||
| Situation profile | `packages/daemon/src/domain/context-packs/profile-composer.ts`, `composeProfile()`: select atoms, close dependencies, order and resolve each piece. |
|
||||
| Named profile | The same file's `composeNamedProfile()`: declared phases select atoms or supplied work/seat context, preserving phase IDs in the result. |
|
||||
|
||||
`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 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`
|
||||
routes were removed in Slice-03 Atom 5. Resolution is by ref only through
|
||||
`getByRef` and the read/delete/preview/pieces route family. Startup accepts
|
||||
ordinary files only and rejects context-pack expansion; compose a durable ref
|
||||
first, then use a dedicated delivery verb when delivery is intended.
|
||||
Situation composition selects fresh atoms for `fresh`, fresh plus handover atoms for
|
||||
`handover`, and post-compaction plus handover atoms for `post-compaction`. Runtime
|
||||
selection is Claude or Codex, with `any` atoms applicable to either. Required atoms join
|
||||
the selection; missing dependencies or runtime-incompatible dependencies fail composition.
|
||||
The output is ordered by atom order, then ID. Profile-only atoms are excluded from ordinary
|
||||
situation selection, but the dependency closure doesn't check that flag, so one can still join
|
||||
as a dependency of a selected atom. Post-compaction composition skips an absent
|
||||
`seat:RECAP.md` and reports it as skipped instead of failing.
|
||||
|
||||
> Source: `domain/context-packs/context-pack-library-service.ts:59-94`
|
||||
> (scan; last-wins L79-81), `:31-33` (id); `startup.ts`
|
||||
> (`contextPackLibrary` construction) @HEAD.
|
||||
The profile route resolves configured tree sources through
|
||||
`packages/daemon/src/domain/context-packs/profile-source-resolver.ts`.
|
||||
Seat context requires explicit rig and seat selectors. Mission/slice selectors and named
|
||||
profile context requirements are validated before reading those trees. The default
|
||||
project/mission/slice walk is bounded to the conventional single-project workspace layout
|
||||
and fresh situation; it is not an arbitrary project-catalog resolver.
|
||||
|
||||
The parser is pure (no fs; caller passes raw YAML). It rejects malformed
|
||||
YAML, missing `name`/`version`, non-array `files`, per-file path traversal
|
||||
(`..`/leading `/`), and unsupported suffixes (`.md .markdown .yaml .yml
|
||||
.txt`); `version` coerced to string. Per-file token estimate is
|
||||
`ceil(bytes/4)`.
|
||||
Each resolved piece names its address/source and reports estimated tokens. A token budget
|
||||
reports overage and drop candidates without silently truncating the profile. Atom
|
||||
`regions` are metadata; the profile endpoint has no region-subset selector.
|
||||
|
||||
> Source: `domain/context-packs/manifest-parser.ts:11` (suffixes),
|
||||
> `:13-121` (rejects; traversal `:83-89`); token estimate
|
||||
> `context-pack-library-service.ts:46-48` @HEAD.
|
||||
### HTTP and CLI boundaries
|
||||
|
||||
`assembleBundle` concatenates files into one paste-ready string framed
|
||||
`# OpenRig Context Pack: <name> v<version>` + optional purpose + `## File:
|
||||
<path> (role: <role>)` headers. Missing files are **skipped and surfaced
|
||||
in `missingFiles`** (operator repair), not a hard fail; a present-but-
|
||||
unreadable file throws `file_read_failed`.
|
||||
`packages/daemon/src/server.ts` mounts `contextPacksRoutes()` at
|
||||
`/api/context-packs`. Its library handlers cover list, sync, compose, read/delete by ref,
|
||||
preview, pieces, addressed reads and profile composition.
|
||||
|
||||
> Source: `domain/context-packs/bundle-assembler.ts:38-39` (prefixes),
|
||||
> `:73-100` (missing skip L74-76; read-fail throw L81-87) @HEAD.
|
||||
`packages/cli/src/commands/context.ts` implements `rig context`, including `list`, `show`,
|
||||
`preview`, `get`, `profile`, `compose`, `add`, `rm` and `sync`, plus `work-install`, `trace`,
|
||||
`source inspect|update` and `recap-write`. The library read/compose routes do not themselves
|
||||
send a pack to a running seat. Several verbs write: `add` installs into `context.root` (from
|
||||
a folder, a URL, or Git with `--git`), `source update` fetches and merges a Git-installed pack
|
||||
and resyncs, `work-install --apply-skills` reconciles skill projections, and `recap-write`
|
||||
writes a seat recap. Inspect the selected CLI verb before treating a context command as
|
||||
read-only. `rig context work-install` resolves a project position and its declared context
|
||||
without the daemon; [workspace-primitive.md](workspace-primitive.md) covers project
|
||||
resolution.
|
||||
|
||||
`contextPacksRoutes()` exposes a ref-primary, delivery-free library surface:
|
||||
`GET /library`, `POST /library/sync`, `POST /library/compose`,
|
||||
`GET/DELETE /library/by-ref`, `GET /library/by-ref/preview`, and
|
||||
`GET /library/by-ref/pieces`. Preview returns the assembled review shape;
|
||||
pieces returns ordered member contents plus the whole plain text and byte size.
|
||||
There is no pack-owned delivery route. CLI library verbs are
|
||||
`rig context compose|list|show|preview|sync|add|rm`; delivery belongs to
|
||||
`rig send --context`, `rig broadcast --context`, `rig walk --through`, and
|
||||
`rig queue create --body-context`.
|
||||
On a profile read, the route adds each piece's `sha256` and provenance, with a warning when a
|
||||
piece's bytes come from outside the granted folder. Rig and seat selectors grant the seat's
|
||||
folder as a source root and supply a default `seat:LEARNED.md` atom to named profiles whose
|
||||
phases request seat context. An ordinary situation profile includes that file only when the
|
||||
pack authors a seat atom; the selectors alone don't add it.
|
||||
|
||||
> Source: `routes/context-packs.ts` (complete route table);
|
||||
> `domain/startup-validation.ts` (file-only startup contract);
|
||||
> `packages/cli/src/commands/context.ts`, `send.ts`, `broadcast.ts`,
|
||||
> `walk.ts`, and `queue.ts` @HEAD.
|
||||
### System World, seat recaps and skill loadouts
|
||||
|
||||
## 2. Agent images (PL-016) — 0.3.0
|
||||
`packages/daemon/src/domain/system-world.ts` reads the System World manifest (schema
|
||||
`openrig.system-world/v0alpha1`): an id, a version, the context packs every seat starts from,
|
||||
and system skills. The `context.system_world` setting selects the default manifest at
|
||||
`system/system-world.yaml` under `context.root`, disables it, or names another manifest file.
|
||||
The default (`openrig-default`) names the `onboarding-width` and `world-public` packs and no
|
||||
skills; it is seeded when missing.
|
||||
|
||||
An **agent image** is a snapshot bundle of a productive seat's resumable
|
||||
state: runtime-specific resume token (Claude `resume_token` / Codex
|
||||
`thread_id`), source-seat lineage, optional cwd-deltas + notes. Consumable
|
||||
by AgentSpec `session_source: mode: agent_image`. Filesystem-canonical at
|
||||
`~/.openrig/agent-images/<name>/` + workspace-local; **no SQLite tables**.
|
||||
`rig context recap-write --rig <rig> --seat <seat> --file <file>` writes the seat's
|
||||
`RECAP.md` through `packages/daemon/src/domain/context-packs/seat-recap-store.ts`. The previous
|
||||
recap is kept byte-for-byte under `recap-superseded/`. A recap that isn't addressable Markdown
|
||||
is refused; the other content checks are advisory.
|
||||
|
||||
> Source: `domain/agent-images/agent-image-types.ts:1-13` @HEAD.
|
||||
`packages/daemon/src/domain/skill-catalog.ts` resolves a seat's skill loadout from the
|
||||
catalog at `skills.root` (`catalog.yaml`, schema `openrig.skill-catalog/v1`) and the system,
|
||||
topology and project selections. It projects skills into `<cwd>/.claude/skills` or
|
||||
`<cwd>/.agents/skills` and tracks what it owns in
|
||||
`<cwd>/.openrig/skill-loadouts/<runtime>.json`. Each projection reports `current`, `missing`,
|
||||
`shadowed`, `stale` or `conflicting`. `rig skill loadout --runtime <runtime>` inspects the
|
||||
composed projection and reconciles it only with `--apply`; `rig skill audit` is a separate
|
||||
read-only provenance and freshness audit. Launch applies the selected loadout per seat.
|
||||
|
||||
`AgentImageLibraryService` mirrors `ContextPackLibraryService` (scan / list
|
||||
/ get / last-wins) with three source-stated differences: `sourceResumeToken`
|
||||
passes through to consumers (instantiator consumes; operator surfaces
|
||||
redact); `stats.json` is a separate mutable file updated atomically on
|
||||
fork-count increment; a `.pinned` sentinel pins from prune. Id
|
||||
`agent-image:<name>:<version>`.
|
||||
For a Claude Code or Codex seat, the profile resolver also adds each selected plugin's skills to
|
||||
that loadout (`packages/daemon/src/domain/profile-resolver.ts:309–330`, with
|
||||
`resolvePluginSkills()` at `skill-catalog.ts:460`). Neither runtime reads skills from the plugin
|
||||
folder projected into the working directory. The skills are projected under their plain names,
|
||||
and a skill the profile already selects keeps its source. A kept plugin copy (an edited or
|
||||
user-owned one, or OpenRig's own copy whose plugin source changed or moved after the loadout
|
||||
was resolved, `skill-catalog.ts:1060–1068`) gives a `plugin_skill_kept` warning, and an unreadable one gives
|
||||
`plugin_skill_skipped`. If plugin skills can't be projected at all, the seat starts without them
|
||||
and gets a `plugin_skills_not_projected` warning (`rigspec-instantiator.ts:2045`).
|
||||
|
||||
> Source: `domain/agent-images/agent-image-library-service.ts:5-14`
|
||||
> (3-difference comment), `:38-41` (id) @HEAD.
|
||||
## Agent images and forks
|
||||
|
||||
`discoverResumeToken(db, sourceSession)` queries `sessions` then `nodes`
|
||||
and returns a typed failure (`session_not_found`/`runtime_unsupported`)
|
||||
rather than fabricating a token; only `claude-code`/`codex` have a native
|
||||
fork primitive. Claude prefers `context_usage.session_id` over persisted
|
||||
`sessions.resume_token`; Codex uses `resume_token`, falling back to a
|
||||
binding's `external_session_name` for `external_cli` seats; returns
|
||||
`nativeId: null` honestly when none.
|
||||
`packages/daemon/src/domain/agent-images/agent-image-library-service.ts` implements
|
||||
`AgentImageLibraryService`. Startup registers the OpenRig `agent-images` root and,
|
||||
when present, the workspace-local `.openrig/agent-images` root. Entries are keyed by
|
||||
`agent-image:<name>:<version>`, with later discovery roots replacing identical IDs.
|
||||
Content is a manifest plus optional files; `stats.json` holds usage data and `.pinned`
|
||||
marks protection from ordinary deletion/pruning.
|
||||
|
||||
> Source: `domain/agent-images/resume-token-discovery.ts:37-85` (honest
|
||||
> null L84; Claude pref L64-69; external_cli L78-83); honesty comment
|
||||
> `:9-10` @HEAD.
|
||||
Capture and consumption depend on live identity as well as library files:
|
||||
|
||||
`SnapshotCapturer.capture()` routes through `discoverResumeToken`, throws
|
||||
`AgentImageError` on failure or missing native id (no fabricated token, no
|
||||
auto-fallback to fresh), writes manifest + empty `stats.json` via
|
||||
`install()`, re-scans. Captured `nodeCwd` → manifest `source_cwd` so the
|
||||
Use-as-starter snippet emits `cwd:` (fork starts in the parent's directory
|
||||
where Claude's project-dir-scoped jsonl lives); the daemon does NOT
|
||||
override cwd at fork dispatch.
|
||||
- `packages/daemon/src/domain/agent-images/resume-token-discovery.ts`:
|
||||
`discoverResumeToken()` reads session/node/binding state. It supports Claude Code and
|
||||
Codex. Claude prefers matching context-usage identity; Codex prefers the persisted resume
|
||||
token, then an external-CLI binding. Absent native identity remains absent.
|
||||
- `packages/daemon/src/domain/agent-images/snapshot-capturer.ts`:
|
||||
`SnapshotCapturer.capture()` refuses unsupported or missing native identity, writes
|
||||
an image manifest/files and rescans. It records the source working directory when known.
|
||||
- `packages/daemon/src/routes/agent-images.ts`:
|
||||
`POST /fork` composes native identity discovery with an add-member operation. The
|
||||
default path makes no image. The keep-image path captures and pins one before launch,
|
||||
and reports that retained image even if launch fails.
|
||||
|
||||
> Source: `domain/agent-images/snapshot-capturer.ts:60-104` (throw
|
||||
> L65-79; source_cwd L88-93; install+scan L101-102);
|
||||
> `routes/agent-images.ts:256-265` (no-cwd-override contract) @HEAD.
|
||||
Library/snapshot responses redact `sourceResumeToken`; the fork composer builds its
|
||||
native-ID session source inside the daemon rather than returning that field to the caller.
|
||||
This describes these response projections, not secrecy of every field in an image manifest.
|
||||
|
||||
**Evidence guard (CATASTROPHIC-bounce; fails closed).**
|
||||
`evaluateProtection()` protects an image from deletion if ANY: pinned;
|
||||
referenced by an active `agent.yaml`; referenced by a rig spec; or a
|
||||
lineage descendant of a protected image (transitive, fixed-point
|
||||
iteration). Spec-root scanning parses YAML structure (not string-grep)
|
||||
and is deliberately conservative — false positives (over-protection)
|
||||
acceptable; false negatives = catastrophic data loss. `--force` /
|
||||
`?force=true` overrides.
|
||||
`recordConsumption()` accepts a separate fork-count flag: consumption intent can update
|
||||
last-use time without incrementing successful forks. Inspect the instantiation caller as
|
||||
well as library statistics when assessing whether a launch succeeded.
|
||||
|
||||
> Source: `domain/agent-images/evidence-guard.ts:1-16` (bounce +
|
||||
> fail-closed), `:53-115` (direct + transitive; fixed point L92-112),
|
||||
> `:117-122` (conservative) @HEAD.
|
||||
`packages/daemon/src/domain/agent-images/evidence-guard.ts` implements
|
||||
`evaluateProtection()`: pins, parsed spec references and descendants of protected images
|
||||
contribute protection reasons. The route applies the guard to deletion/pruning unless
|
||||
`force` is explicit. Prune defaults to dry-run. Its protection view depends on the supplied
|
||||
spec roots; it is not an inventory of every reference anywhere on the host.
|
||||
|
||||
**Resume-token redaction at route boundary (LOAD-BEARING).**
|
||||
`/api/agent-images` redacts `sourceResumeToken` → `"(redacted)"` on every
|
||||
operator-facing path; tokens are **never returned over the wire**; only
|
||||
the in-process rigspec-instantiator consumes the real token.
|
||||
The guard keys its result by image name, not version: every version of one name gets the
|
||||
status computed for the last-listed version, carrying that version's image ID. The delete
|
||||
route looks up by image ID, so on delete only that last-listed version of a name is matched,
|
||||
and its other versions are deleted without `force` even when pinned. `rig agent-image prune`
|
||||
sends a dry run unless `--force` is given, and `--force` also overrides the guard, so a
|
||||
guarded real prune is reachable only through the HTTP route.
|
||||
|
||||
> Source: `routes/agent-images.ts:15-17` + `:44-46` (`redactResumeToken`),
|
||||
> applied `:60`,`:67`,`:94`,`:161`; slice-00 0.3.0-GT §1.9 flags this
|
||||
> LOAD-BEARING @HEAD.
|
||||
The routes mount at `/api/agent-images` and expose list/sync, snapshot/fork,
|
||||
entry/preview, pin/unpin, delete and prune.
|
||||
`packages/cli/src/commands/agent-image.ts` owns the image library commands. The top-level
|
||||
`rig fork` verb (`packages/cli/src/commands/fork.ts`) drives `POST /fork`; `--keep-image`
|
||||
also captures a pinned image. Forking a Claude Code seat needs Claude Code 2.0.73 or later,
|
||||
because the fork is launched with its own `--session-id`.
|
||||
|
||||
`agentImagesRoutes()` (mounted `server.ts:483-485`): `GET /library`,
|
||||
`POST /library/sync`, `GET /library/:id`, `GET /library/:id/preview`,
|
||||
`POST /library/:id/pin`, `POST /library/:id/unpin`,
|
||||
`DELETE /library/:id` (evidence-guarded unless `force=true`),
|
||||
`POST /snapshot`, `POST /prune` (**dry-run by default**,
|
||||
`dryRun !== false`); guard `specRoots` injected via
|
||||
`deps.agentImageSpecRoots`. CLI: `rig agent-image
|
||||
list|show|preview|create|delete|pin|unpin|prune|sync`.
|
||||
## Plugins
|
||||
|
||||
> Source: `routes/agent-images.ts:54-250` (table; prune default L112;
|
||||
> delete-guard L224-239); `server.ts:483-485`;
|
||||
> `docs/as-built/cli-reference.md:895-912` @HEAD.
|
||||
### Discovery and inspection
|
||||
|
||||
## 3. Plugins (plugin-primitive Phase 3a) — **0.3.1, NOT 0.3.0**
|
||||
`packages/daemon/src/domain/plugin-discovery-service.ts` discovers plugins from:
|
||||
|
||||
> **Forensic trap (slice-00 0.3.0-GT seam (a)):** the plugin-discovery
|
||||
> file's first-creating commit is dated the same day as the 0.3.0 release
|
||||
> commit but is NOT an ancestor of `v0.3.0`. Plugins are **0.3.1**.
|
||||
> Re-confirmed at HEAD by the §0 tag-presence test.
|
||||
| Source kind | Directory shape |
|
||||
|---|---|
|
||||
| `vendored` | OpenRig plugins root, one plugin directory per entry. |
|
||||
| `claude-cache` | Claude cache, grouped by marketplace/plugin/version. |
|
||||
| `codex-cache` | Codex cache, grouped by marketplace/plugin/version. |
|
||||
| `rig-cwd` | `.claude/plugins` and `.codex/plugins` beneath supplied working-directory roots. |
|
||||
|
||||
`PluginDiscoveryService` is a read-only filesystem aggregator — no SQLite,
|
||||
no mutation routes (SC-29 EXCEPTION #8, declared verbatim in the source
|
||||
header). It scans four source kinds, unioned with provenance labels:
|
||||
vendored (`~/.openrig/plugins/<id>/`), Claude cache
|
||||
(`~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/`), Codex cache
|
||||
(same under `~/.codex`), and rig-cwd (`<cwd>/.claude/plugins/*` +
|
||||
`<cwd>/.codex/plugins/*`). Detection = presence of
|
||||
`.claude-plugin/plugin.json` and/or `.codex-plugin/plugin.json` (declares
|
||||
`runtimes`). Slice 28 added `skillCount` (readdir of `<plugin>/skills/`)
|
||||
to the list response so the index page avoids an N+1 detail fetch
|
||||
(SC-29 EXCEPTION #11, verbatim).
|
||||
Detection uses the Claude/Codex plugin manifests. `listPlugins()` returns runtime/source
|
||||
metadata; `getPlugin()` adds manifests, skills, hooks and MCP detail.
|
||||
`findUsedBy()` scans agent YAML in configured spec-library roots for plugin declarations
|
||||
and profile references. These references describe authored specs, not proof of a running
|
||||
harness loading a plugin.
|
||||
|
||||
> Source: `domain/plugin-discovery-service.ts:1-29` (SC-29 #8 + scan
|
||||
> desc), `:39` (4-kind `PluginSourceKind`), `listPlugins` `:205-276`
|
||||
> (4 scan blocks), `scanCwdBundledPlugins` `:283-307`, `detectPlugin`
|
||||
> `:393-453` (marker rule L399-404; `skillCount` L433-451 + #11
|
||||
> L75-80) @HEAD.
|
||||
The service constructor is not read-only: it retires obsolete refocus hook registrations
|
||||
from the older bundled plugin and rejects duplicate vendored hook registrations across
|
||||
providers. Cached plugin versions are excluded from that duplicate-provider check. Startup
|
||||
constructs the service while building the daemon's dependencies and doesn't catch that
|
||||
rejection, so a duplicate vendored hook stops daemon creation.
|
||||
|
||||
`getPlugin(id)` returns detail (manifests + skills + hooks + MCP servers);
|
||||
`rig-cwd:` ids are self-resolvable (parses the cwd out of the id prefix
|
||||
and re-scans so `/api/plugins/:id` does not 404 on an id `?cwd=`
|
||||
returned). `findUsedBy(id)` walks `agent.yaml` files parsing YAML
|
||||
structure (not string-grep — comments don't false-positive) to collect
|
||||
`resources.plugins[].id` + referencing profiles.
|
||||
`packages/daemon/src/routes/plugins.ts` exposes GET-only inspection at `/api/plugins`:
|
||||
list, detail, used-by and file list/read. The file routes use the discovered plugin root as
|
||||
their path allowlist. `?cwd=` supplies rig-cwd discovery roots.
|
||||
At this pin, the route's source filter accepts vendored, Claude-cache and Codex-cache only;
|
||||
`source=rig-cwd` is not applied as a filter, even though the service supports that kind.
|
||||
|
||||
> Source: `domain/plugin-discovery-service.ts:309-369` (getPlugin;
|
||||
> rig-cwd self-resolve L310-321), `extractCwdFromRigCwdId` `:466-479`,
|
||||
> `findUsedBy` `:371-389`, `readResourcesPlugins` `:599-615`,
|
||||
> `readProfilesUsingPlugin` `:617-633` (structure-not-grep `:20-23`)
|
||||
> @HEAD.
|
||||
### Vendoring and projection
|
||||
|
||||
`PluginVendorService.ensureLatest()` runs `ensureVendored` (hash-skip
|
||||
idempotent copy from `packages/daemon/assets/plugins/<name>/` to
|
||||
`~/.openrig/plugins/<name>/`) then `attemptAutoFetch`
|
||||
(`github.com/mvschwarz/openrig-plugins`, 5s timeout,
|
||||
**404/network/timeout tolerated silently** — vendored is ALWAYS the
|
||||
fallback; upstream repo intentionally empty at v0 so 404 is normal-state;
|
||||
NO tarball extraction at v0). Wired at startup for `openrig-core`.
|
||||
`packages/daemon/src/domain/plugin-vendor-service.ts` owns `PluginVendorService`.
|
||||
`ensureVendored()` seeds absent plugins or advances an older numeric manifest version.
|
||||
Equal/newer installed versions preserve installed bytes; equal versions can reconcile file
|
||||
modes on byte-identical files. Missing target version authority is preserved rather than
|
||||
overwritten; invalid or conflicting manifest versions are errors. Advancing a version writes
|
||||
changed files but never deletes files the new version dropped.
|
||||
|
||||
> Source: `domain/plugin-vendor-service.ts:1-24`, `ensureVendored`
|
||||
> `:80-103` (hash-skip L97-99), `attemptAutoFetch` `:111-131`
|
||||
> (404-tolerant L116-117; no-extract L123-125), `ensureLatest`
|
||||
> `:138-141`; `startup.ts:434-472` @HEAD.
|
||||
`ensureSkillGlobally()` projects a named plugin skill using a vendor-version marker.
|
||||
An existing unversioned global skill remains externally owned. Startup wires this service
|
||||
and its filesystem implementation; inspect that wiring when changing projection roots. At
|
||||
startup only `openrig-core` is vendored, and its `openrig-skills` and `refocusing` skills are
|
||||
projected globally into `~/.claude/skills` and `~/.agents/skills`
|
||||
(`packages/daemon/src/startup.ts:877–886`). The packaged `rigs` skill
|
||||
(`packages/daemon/assets/skills/rigs`) is not a plugin skill: `ensureSkillDirGlobally()`
|
||||
projects it into the same roots under the daemon version, so each upgrade refreshes it
|
||||
(`startup.ts:891–899`). A failed global projection is logged and does not stop startup.
|
||||
|
||||
`pluginsRoutes()` (mounted `server.ts:478`, read-only): `GET /` (filters
|
||||
`?runtime=`, `?source=`, `?cwd=`), `GET /:id/used-by`,
|
||||
`GET /:id/files/list?path=`, `GET /:id/files/read?path=` (slice 28
|
||||
docs-browser), `GET /:id`. Literal sub-paths are mounted **before** the
|
||||
bare `/:id` catchall (route-order discipline). The files endpoints reuse
|
||||
the path-safety machinery with the discovered plugin's absolute path as a
|
||||
synthetic single-root allowlist (operator need not declare plugin paths
|
||||
in `OPENRIG_FILES_ALLOWLIST`; plugin folders read-only at v0 so the
|
||||
content hash is informational). CLI: `rig plugin
|
||||
list|show|used-by|validate`; no `install` verb at v0 (deferred to 0.3.2).
|
||||
`ensureLatest()` performs local vendoring before `attemptAutoFetch()`. The latter
|
||||
requests a release asset with a bounded timeout and logs failures; even its successful
|
||||
response path does not extract/install the fetched archive at this pin. This does not
|
||||
establish whether an upstream release asset currently exists.
|
||||
|
||||
> Source: `routes/plugins.ts:29-39` + `:107-231` (table; before-catchall
|
||||
> L128-130/L141-142; `pluginRootAllowlist` :74-92; read-only-hash
|
||||
> L182-184); `server.ts:478`;
|
||||
> `docs/as-built/cli-reference.md:943-963` @HEAD.
|
||||
`packages/cli/src/commands/plugin.ts` implements `rig plugin
|
||||
list|show|used-by|validate`. Discovery, vendoring and a runtime's projected plugin tree
|
||||
are distinct steps; see [agent-spec-and-startup.md](agent-spec-and-startup.md) for launch.
|
||||
|
||||
> **OPEN-A** — `parseSourceFilter` (`routes/plugins.ts:69-72`) accepts
|
||||
> only `vendored|claude-cache|codex-cache`; it does NOT accept `rig-cwd`
|
||||
> though `PluginSourceKind` (`domain/plugin-discovery-service.ts:39`) and
|
||||
> `ListPluginsOpts.sourceFilter` include it. `?source=rig-cwd` is
|
||||
> silently dropped at the route while `?cwd=` still surfaces rig-cwd
|
||||
> entries unfiltered. Stated as-is; intent (rig-cwd reachable only via
|
||||
> `?cwd=`) vs filter-gap is not resolvable from source alone.
|
||||
## Claude guided compaction
|
||||
|
||||
## 4. Claude auto-compaction enforcer (slice 27) — **0.3.1 headline**
|
||||
`packages/daemon/src/domain/claude-compaction-enforcer.ts` implements
|
||||
`ClaudeCompactionEnforcer`. Startup shares one instance between `ContextMonitor`
|
||||
and the manual-compaction route consumers.
|
||||
`packages/daemon/src/domain/user-settings/settings-store.ts` resolves the
|
||||
`policies.claude_compaction.*` settings; automatic compaction defaults to disabled.
|
||||
|
||||
> **No 0.3.0 antecedent (slice-00 0.3.0-GT map row 7).** The enforcer
|
||||
> file is absent at `v0.3.0`, present at `v0.3.1` (§0).
|
||||
`maybeAutoCompact()` applies the delivery guard when one exists and the runtime is Claude
|
||||
Code. Its implementation requires Claude Code, observed usage and a valid integer threshold
|
||||
(default 80). Above-threshold automatic work additionally checks enablement, the
|
||||
post-restore cooldown (10 minutes), the deduplication window (60 seconds) and the
|
||||
already-triggered latch; an active manual attempt also blocks it.
|
||||
|
||||
`ClaudeCompactionEnforcer.maybeAutoCompact()` decides per seat whether
|
||||
`ContextMonitor` should send `/compact`, driven by operator
|
||||
`policies.claude_compaction.*` settings. Decoupled from `ContextMonitor`
|
||||
scheduling; `ContextMonitor` is constructed with the enforcer at startup.
|
||||
`/compact` is held behind a preparation attempt:
|
||||
|
||||
> Source: `domain/claude-compaction-enforcer.ts:7-12`, `maybeAutoCompact`
|
||||
> `:205-331`; `startup.ts:1191-1199` (enforcer →
|
||||
> `new ContextMonitor(db, contextUsageStore, claudeAdapter,
|
||||
> compactionEnforcer)`) @HEAD.
|
||||
1. The first above-threshold tick starts an attempt with its own ID, the seat's occupant
|
||||
generation, a restore-map path and a completion marker, then sends only the prep prompt.
|
||||
An unknown occupant generation stops the attempt at once.
|
||||
2. The agent writes `RESTORE-MAP.md.tmp` with its file-edit tool, completion marker last.
|
||||
Later ticks return `preparation_pending` until that exact map is complete. The map path is under the seat's working directory
|
||||
(`.openrig/compaction/preparation/<session>/<attempt>/`, with a `.gitignore` of `*`) when
|
||||
that is writable, otherwise under `compaction/preparation/` in the OpenRig home.
|
||||
3. The daemon atomically renames the completed temp file to `RESTORE-MAP.md` in its existing
|
||||
final send checks, after rechecking the attempt and occupant. An already-complete final map
|
||||
remains compatible. Only then is `/compact` sent, with the final map path appended to the
|
||||
compact instruction. Publication failure cannot authorize `/compact`.
|
||||
4. The continuation then runs below the threshold: turn boundary, restore prompt, then a
|
||||
read-depth audit prompt (each item marked `FULL`, `PARTIAL` or `NOT_READ`), one stage per
|
||||
poll tick. A seat refreshes its usage sample only when it takes a turn, so when the sample is
|
||||
stale, unknown or has no percentage and a stage is pending, the monitor drains that stage
|
||||
without one (`context-monitor.ts:175`, `claude-compaction-enforcer.ts:436`). Only a fresh,
|
||||
known sample can start a compaction. The sample-less drain reads readiness from the pane alone
|
||||
and counts a timer-less live status row, such as `✻ Compacting conversation…`, as work
|
||||
(`session-transport.ts:153-161`); a recent hook showing work or a waiting person still
|
||||
refuses. It records no width receipt, because the only figure would be the pre-compact one.
|
||||
|
||||
**Defensive contract** (source classifies compaction lifecycle as
|
||||
load-bearing per the banked permission-layer foot-gun rule):
|
||||
An automatic attempt has a 25-minute ceiling that starts once the prep is delivered. A stale
|
||||
occupant generation, the policy becoming disabled, or the deadline passing stops it. A prep
|
||||
that clearly wasn't sent is retried at most three times; an uncertain prep or `/compact` is
|
||||
never replayed.
|
||||
|
||||
- **Opt-in, default-off:** `enabled=false` → never triggers.
|
||||
- **Runtime filter:** triggers only when `runtime === "claude-code"`.
|
||||
- **Invalid-policy = disabled:** a hand-edited non-integer / out-of-
|
||||
`[1,100]` `thresholdPercent` is treated as disabled (safer-failure).
|
||||
- **Re-arm via threshold crossing:** session must drop below threshold
|
||||
before another auto-compact fires; window state NOT persisted (restart
|
||||
resets — safer-failure direction).
|
||||
- **Send-failure graceful-degrade:** returns `{ triggered: false,
|
||||
reason: "send_failed" }`, never throws; dedup timestamp set only on
|
||||
successful send so a transient failure retries next tick.
|
||||
The restore prompt names a restore-pending marker at
|
||||
`compaction/restore-pending/<session>.json` in the OpenRig home, which the `openrig-core`
|
||||
PreCompact hook writes. A per-seat `compaction/post-compact-extra/<session>.md` is preferred
|
||||
over the global extra file, and an extra file that declares a different seat is refused.
|
||||
|
||||
> Source: `domain/claude-compaction-enforcer.ts:14-44` (risk-class +
|
||||
> contract), runtime `:206-208`, invalid-policy `:224-232`,
|
||||
> dedup/threshold `:285-300`, send-fail `:312-314`/`:323-325` @HEAD.
|
||||
`rig compact <session>` (`packages/cli/src/commands/compact.ts`) runs the same sequence
|
||||
manually through `/api/compaction/trigger`, `cancel` and `state`, which use the terminal
|
||||
bearer-token middleware; it passes every request through when no token is configured.
|
||||
`--skip-map` skips the map requirement once, `--cancel` ends the preparation, and `--state`
|
||||
shows the attempt, its expected map and its deadline. A manual attempt has 120 seconds for
|
||||
preparation and the idle wait.
|
||||
|
||||
**Active handshake state machine** (Claude hooks can supply context but
|
||||
do not create a new assistant turn): (1) **pre-compact prep** — first
|
||||
eligible above-threshold tick sends a normal user-channel prep prompt;
|
||||
state → `prep_prompt_sent`. (2) **/compact** — next eligible tick sends
|
||||
`/compact <instruction> + trust-channel bridge note`; sets dedup
|
||||
timestamp + `triggeredAboveThreshold`; queues post stage
|
||||
`turn_boundary`. (3) **post-compact, below threshold** — staged sends
|
||||
`turn_boundary` (ack-only) → `restore_prompt` (points at the pending
|
||||
marker `<openrigHome>/compaction/restore-pending/<key>.json`, transcript
|
||||
/ session-id fallbacks) → `compliance_prompt` (read-depth audit); final
|
||||
stage sets post-restore cooldown + clears the above-threshold latch.
|
||||
The below-threshold continuation checks policy enablement too, except for an explicitly
|
||||
operator-initiated manual sequence. A known occupant-generation mismatch invalidates the
|
||||
queued stages; an unknown generation does not prove a match. Failed or retained sends
|
||||
do not advance the stage. The manual trigger shares this continuation while having its
|
||||
own initiation checks.
|
||||
|
||||
> Source: `domain/claude-compaction-enforcer.ts:71-77` (compact+bridge),
|
||||
> `:79-98` (prep), `:108-145` (restore + marker path), `:147-163`
|
||||
> (compliance), `:165-171` (turn boundary), state machine `:233-330`
|
||||
> (below-threshold L233-283; above-threshold L302-330) @HEAD.
|
||||
Stage, deduplication, cooldown and preparation-attempt maps are in memory; the restore maps
|
||||
are files. Startup supplies live generation
|
||||
resolution and a post-restore callback that records a managed-width receipt, so the wider
|
||||
operation also has persistent effects. Do not infer durable lifecycle completion from
|
||||
a library read or one successfully queued message.
|
||||
|
||||
Policy fields via `SettingsStore.resolveClaudeCompactionPolicy()`:
|
||||
`enabled`, `thresholdPercent`, `preCompactInstruction`,
|
||||
`compactInstruction`, `messageInline`, `messageFilePath`,
|
||||
`postRestoreAuditInstruction` — each backed by a
|
||||
`policies.claude_compaction.*` key + `OPENRIG_POLICIES_CLAUDE_COMPACTION_*`
|
||||
env override. Defaults: dedup `60_000` ms; post-compact restore cooldown
|
||||
`10 * 60_000` ms.
|
||||
## Related maps
|
||||
|
||||
> Source: `domain/user-settings/settings-store.ts:553-565` (resolver),
|
||||
> keys `:97-103`, env `:145-151`;
|
||||
> `domain/claude-compaction-enforcer.ts:45-46` (default windows) @HEAD.
|
||||
|
||||
## 5. Cross-cutting properties
|
||||
|
||||
- **Filesystem-canonical, no new SQLite** across all four; daemon caches
|
||||
in-memory; compaction window state intentionally not persisted
|
||||
(context-pack-types.ts:8-11; agent-image-types.ts:12-13;
|
||||
routes/plugins.ts:6; claude-compaction-enforcer.ts:26-29) @HEAD.
|
||||
- **Honest failure, no fabrication** — discovery returns null; capturer
|
||||
throws; enforcer degrades + never throws; evidence guard fails closed
|
||||
(resume-token-discovery.ts:9-10; snapshot-capturer.ts:11-12;
|
||||
claude-compaction-enforcer.ts:30-33; evidence-guard.ts:16) @HEAD.
|
||||
- **503-when-unprovisioned** is the uniform route pattern when the
|
||||
backing service is absent from context (routes/context-packs.ts:31;
|
||||
routes/agent-images.ts:59; routes/plugins.ts:113) @HEAD.
|
||||
|
||||
## OPEN items (carried, not smoothed)
|
||||
|
||||
- **OPEN-A** — plugin `?source=` filter omits `rig-cwd` (§3); intent vs
|
||||
filter-gap unresolvable from source.
|
||||
- No slice-00 numeric-drift OPEN (1–5) applies — this module carries no
|
||||
migration / route-group / PL-004-event counts. Slice-00 0.3.0-GT
|
||||
OPEN-3 (For-You verb subset) is out of scope (UI surface, not
|
||||
content-layer).
|
||||
- [packaging-bootstrap-bundles.md](packaging-bootstrap-bundles.md): bundle export and
|
||||
routing into content libraries.
|
||||
- [agent-spec-and-startup.md](agent-spec-and-startup.md): context/resource projection
|
||||
and image consumption during launch.
|
||||
- [lifecycle-snapshot-restore.md](lifecycle-snapshot-restore.md): broader continuity flows.
|
||||
@@ -5,238 +5,305 @@ status: active
|
||||
topics: [coordination, observability]
|
||||
domains: [engineering-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
Need to know how rig send/capture/broadcast works, how pipe-pane transcript
|
||||
capture and rg/grep search behave, how durable rig chat (SQLite + SSE) is
|
||||
modeled, what rig ask gathers, or the exact MCP-tool-name vs tmux-metadata-key
|
||||
naming distinction.
|
||||
Need to know how rig send/capture/broadcast works, how transcript capture
|
||||
(periodic tmux capture-pane) and rg/grep search behave, how durable rig chat
|
||||
(SQLite + SSE) is modeled, what rig ask gathers, or the exact MCP-tool-name
|
||||
vs tmux-metadata-key naming distinction.
|
||||
siblings: [daemon-core.md, lifecycle-snapshot-restore.md]
|
||||
prerequisite-reads: [../README.md, daemon-core.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Transport, Transcripts, Chat, Ask
|
||||
|
||||
The communication-and-history layer: tmux is transport, not truth. Send/
|
||||
capture/broadcast wrap tmux with honest errors; transcripts are raw pipe-pane
|
||||
captures; chat is daemon-backed SQLite; `rig ask` gathers evidence but never
|
||||
calls an LLM.
|
||||
capture/broadcast wrap tmux with honest errors; transcripts are bounded
|
||||
`tmux capture-pane` snapshots written to files; chat is daemon-backed SQLite;
|
||||
the daemon's `rig ask` service gathers evidence and never calls an LLM.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c` (`git describe` →
|
||||
> `v0.3.1-6-g7eaf524c`). Package version **0.3.1** (slice-00 §1.1). Source
|
||||
> located by `architecture.md` headings (§5 Transport and communication, §6
|
||||
> Communication/Transcript/Chat flows, §11 Compat notes) per slice-08 §10.1
|
||||
> — line numbers advisory only.
|
||||
> Verified against source at main `e8f0ab340db773392ec8be75b072d1c0f3068a50`. Each count below sits beside the
|
||||
> command that produces it; run the command from the repository root to refresh
|
||||
> it.
|
||||
|
||||
## 1. The naming axes (D5 — read this first)
|
||||
## 1. The naming axes (read this first)
|
||||
|
||||
This module carries the D5 drift, which has **two independent axes** that
|
||||
must NOT be conflated or blanket-replaced (banked
|
||||
`feedback_release_prep_three_layer_depersonalization`: the rename was scoped,
|
||||
not global). Per-occurrence source verification:
|
||||
Two independent naming axes meet in this layer. They must not be conflated or
|
||||
blanket-replaced: `rigged` → `rig` is a per-occurrence question, not a global
|
||||
substitution.
|
||||
|
||||
**Axis 1 — MCP tool names: `rig_*` (architecture.md is STALE → corrected).**
|
||||
slice-00 §1.4 confirms all 17 MCP tools are `rig_*`. The `rigged_*`
|
||||
references in `architecture.md` are stale text from a rename that predates
|
||||
v0.2.0:
|
||||
**Axis 1 — MCP tool names are `rig_*`.** `packages/cli/src/mcp-server.ts`
|
||||
registers **18** tools, all named `rig_*`
|
||||
(`grep -c 'server.tool(' packages/cli/src/mcp-server.ts`), including
|
||||
`rig_rig_nodes` (`mcp-server.ts:288`), `rig_send` (`:305`),
|
||||
`rig_capture` (`:339`), `rig_chatroom_send` (`:364`) and `rig_chatroom_watch`
|
||||
(`:400`). No `rigged_*` name other than the tmux keys below remains in product
|
||||
source: **0** hits
|
||||
(`git grep -n 'rigged_' -- ':(glob)packages/*/src/**' | grep -v -c '@rigged_'`).
|
||||
|
||||
> Drift-fix D5 (MCP-tool-name axis) — `architecture.md` §5 `node-inventory.ts`
|
||||
> description says MCP `rigged_rig_nodes`; §6 chat flow says MCP
|
||||
> `rigged_chatroom_send` + `rigged_chatroom_watch`. Corrected to **`rig_*`**.
|
||||
> Re-confirmed at source: `mcp-server.ts:288` registers `"rig_rig_nodes"`,
|
||||
> `:305` `"rig_send"`, `:339` `"rig_capture"`, `:364` `"rig_chatroom_send"`,
|
||||
> `"rig_chatroom_watch"` (tool #17). Zero `rigged_chatroom_send` /
|
||||
> `rigged_send` / `rigged_rig_nodes` in non-test product source (grep clean
|
||||
> @HEAD). slice-00 §1.4; the rename landed in `b183c50c` pre-v0.2.0.
|
||||
|
||||
**Axis 2 — tmux metadata keys: `@rigged_*` (architecture.md is CORRECT → do
|
||||
NOT change).** The tmux metadata keys written at claim/bind time are a
|
||||
SEPARATE thing from MCP tool names and were NOT renamed:
|
||||
|
||||
> Drift-nuance D5 (tmux-metadata-key axis) — `architecture.md` §6 "Whoami
|
||||
> and adopted-session parity flow" lists `@rigged_node_id`,
|
||||
> `@rigged_session_name`, `@rigged_rig_id`, `@rigged_rig_name`,
|
||||
> `@rigged_logical_id`. These are **CORRECT as-is** — verified literally at
|
||||
> source: `claim-service.ts:77-81` writes exactly these five `@rigged_*`
|
||||
> keys. Do NOT blanket-sed `rigged` → `rig`; this is a per-occurrence
|
||||
> verify, not a global substitution. (The metadata-key axis is detailed in
|
||||
> `agent-spec-and-startup.md` §6.) Re-confirmed `claim-service.ts:77-81`
|
||||
> @HEAD.
|
||||
**Axis 2 — tmux metadata keys are `@rigged_*`, and are correct as-is.** The
|
||||
tmux metadata keys written at claim/bind time are a separate thing from MCP
|
||||
tool names and were not renamed. `setRiggedMetadata` in
|
||||
`packages/daemon/src/domain/claim-service.ts:182`–`186` writes exactly **5**
|
||||
keys (`grep -c '@rigged_' packages/daemon/src/domain/claim-service.ts`):
|
||||
`@rigged_node_id`, `@rigged_session_name`, `@rigged_rig_id`,
|
||||
`@rigged_rig_name`, `@rigged_logical_id`. Do not blanket-sed `rigged` → `rig`.
|
||||
The metadata-key axis is detailed in `agent-spec-and-startup.md` (identity:
|
||||
whoami, bind, adopt).
|
||||
|
||||
## 2. Transport and communication domain services
|
||||
|
||||
(`architecture.md` §5 "Transport and communication")
|
||||
All under `packages/daemon/src/domain/`:
|
||||
|
||||
- `session-transport.ts` — communication primitives: send/capture/broadcast
|
||||
with session resolution (canonical + legacy names), mid-work detection,
|
||||
honest error reporting, pod/rig/global targeting.
|
||||
- `transcript-store.ts` — pipe-pane transcript management: ANSI stripping on
|
||||
read, boundary markers, readTail, grep. Filesystem-backed, NOT SQLite.
|
||||
- `history-query.ts` — transcript + chat search. Prefers `rg` when
|
||||
available, falls back to `grep -E`, surfaces which backend was used.
|
||||
Re-confirmed: `history-query.ts:7` `backend: "rg" | "grep" | "none"`;
|
||||
`:103` execs `rg -i --no-filename -e <pattern>`; `:108` returns
|
||||
`backend: "rg"`.
|
||||
with session resolution, send-readiness classification (interactive-prompt
|
||||
and mid-work detection), honest error reporting, and list/pod/rig/global
|
||||
targeting.
|
||||
- `transcript-store.ts` — transcript file management: path convention, ANSI
|
||||
stripping for tail and grep reads, boundary markers, `readTail`, `readFull`, `grep`.
|
||||
Filesystem-backed, NOT SQLite. The capture that writes the files is
|
||||
`transcript-rotation.ts` (§4).
|
||||
- `history-query.ts` — transcript + chat search. Prefers `rg`, falls back to
|
||||
`grep -E`, surfaces which backend was used: `history-query.ts:8`
|
||||
`backend: "rg" | "grep" | "none"`; `:252` runs
|
||||
`rg -i --no-filename -e <pattern>` over the rig's transcript directory;
|
||||
`:267` falls back to `grep -E -i -h -e <pattern>`.
|
||||
- `ask-service.ts` — context-engineering evidence pack: gathers rig summary
|
||||
plus transcript excerpts, chat excerpts, insufficiency state, guidance.
|
||||
Does NOT call an external LLM.
|
||||
- `chat-repository.ts` — durable rig-scoped chat: CRUD for `chat_messages`
|
||||
table, SSE-compatible event emission.
|
||||
- `chat-repository.ts` — durable rig-scoped chat over the `chat_messages`
|
||||
table (`send`, `sendTopic`, `history`, `latest`, `searchChat`, `clear`). The
|
||||
repository emits no events: `routes/chat.ts` emits `chat.message` on the
|
||||
event bus after each send or topic, and `/watch` streams those as SSE.
|
||||
|
||||
Routes: `routes/{transport,transcripts,ask,chat,whoami}.ts` — all confirmed
|
||||
present @HEAD.
|
||||
Routes: `packages/daemon/src/routes/{transport,transcripts,ask,chat,whoami}.ts`
|
||||
— all present.
|
||||
|
||||
## 3. Communication flow
|
||||
|
||||
(`architecture.md` §6 "Communication flow")
|
||||
|
||||
`rig send <session> "message"` → CLI → `POST /api/transport/send` →
|
||||
`SessionTransport`:
|
||||
`SessionTransport.send()`:
|
||||
|
||||
1. Resolve session name (canonical or legacy; by session/rig/pod/global).
|
||||
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: 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)`).
|
||||
5. Honest result with reason on failure.
|
||||
1. Resolve the session name (`resolveBySessionName`,
|
||||
`session-transport.ts:963`): not found → 404; the same name in more than
|
||||
one rig → 409. A tmux probe of the session can also refuse it as missing or
|
||||
tmux as unavailable (`:1317`–`1334`). Pod, rig, global and `--to` list
|
||||
targets go through `POST /api/transport/broadcast`, which resolves them
|
||||
with `resolveSessions` and sends to each recipient in turn.
|
||||
2. Take the seat's delivery lease. Every write runs under a per-seat,
|
||||
serialized lease from the seat delivery guard, installed at daemon start
|
||||
(`send`, `:1155`). With the seat's typing guard on, the message is held
|
||||
instead of typed: the result is `outcome: "retained"` with HTTP 200
|
||||
(`routes/transport.ts:115`); operators use `rig seat set-typing-guard` and
|
||||
`rig seat held-messages`.
|
||||
3. Classify send readiness. Only a positive interactive-prompt reading
|
||||
(`needs_input`) refuses, with `target_needs_input`
|
||||
(`session-transport.ts:1482`), unless the caller passes
|
||||
`--dangerously-interact --reason`; that override persists an audit record
|
||||
before sending and refuses the send if it can't
|
||||
(`prompt_override_audit_unavailable`, `:1471`). A pane that reads `unknown` while the seat's
|
||||
latest runtime hook says it waits on a person reads as `needs_input` (`session-transport.ts:1878–1889`). A mid-work reading (`running`, from a
|
||||
fresh runtime hook or the pane's mid-work patterns,
|
||||
`findPatternEvidence(recentLines, [...MID_WORK_PATTERNS, CLAUDE_LIVE_STATUS_PATTERN])`,
|
||||
`:311–315`) or an `unknown` one proceeds with an advisory `warning` (`:1495`,
|
||||
`:1503`). `--force` has no effect on this path, and combining it with
|
||||
`--wait-for-idle` is refused with 400 (`routes/transport.ts:66`–`73`).
|
||||
4. Two-step tmux send: a unique file/buffer pasted with `paste-buffer -d -r -p`
|
||||
(`packages/daemon/src/adapters/tmux.ts:625`) → ~200ms delay
|
||||
(`session-transport.ts:1558`) → separate named `Enter` (`session-transport.ts:1573–1578`). The paste
|
||||
targets the seat's registered pane ID, not the session name, and is refused
|
||||
with no input written if the session no longer holds exactly that pane.
|
||||
Bracketed paste preserves multiline input in supporting TUIs; the payload
|
||||
never enters a shell argument. A `--dangerously-interact` answer is pasted
|
||||
without `-p` (`session-transport.ts:1541`) and Enter is pressed only if the
|
||||
whole answer is still staged (`:1562`–`1570`). A successful paste proves
|
||||
transport execution, not runtime consumption.
|
||||
5. Optional `--verify`: capture the last 30 pane lines before and after the
|
||||
send (after a 500 ms wait, `:1597`) and count the message's first 40
|
||||
characters in each. A higher count after the send reports
|
||||
`outcome: "delivered"`; otherwise the outcome is `rendered-unconfirmed` —
|
||||
text and Enter landed but the capture could not re-confirm the render,
|
||||
which is not a failure at the daemon (`session-transport.ts:700`
|
||||
`verify?`; `:1596` `if (opts?.verify)`; `:1608`
|
||||
`verified = postCount > preCount`). The outcome values are `delivered`,
|
||||
`rendered-unconfirmed`, `failed` and `retained` (`:772`).
|
||||
6. CLI `--verify` goes further (`packages/cli/src/commands/send.ts:596`): it
|
||||
looks for the text still unsubmitted at the prompt, makes one guarded
|
||||
Enter-only retry through the daemon's `submitOnly` path
|
||||
(`session-transport.ts:1344`, which refuses with `staged_mismatch` unless the
|
||||
pane shows the expected staged text), and ends `staged-not-consumed` with
|
||||
exit code 1 if it is still there.
|
||||
7. Honest result with reason on failure. Reasons missing from the send
|
||||
route's status map (among them the runtime-identity refusals) are returned
|
||||
with HTTP 500 (`routes/transport.ts:118`–`140`).
|
||||
|
||||
Architecture Rule 18 (carried, `architecture.md` §7): tmux is transport, not
|
||||
truth — `send/capture/broadcast` wrap tmux reliably with honest errors.
|
||||
The sender comes from the `X-OpenRig-Session` header; an unsigned send gets an
|
||||
unknown-sender notice. The CLI adds a From/To envelope, and the daemon appends
|
||||
a delivered-latency note for delays of 10 s or more.
|
||||
|
||||
### 3b. Cross-host coordination verbs (v0.4.6 — OPR.0.4.6.MH4)
|
||||
Architecture rule 18 (`architecture-rules-and-event-system.md`): tmux is
|
||||
transport, not truth — `send/capture/broadcast` wrap tmux reliably with honest
|
||||
errors.
|
||||
|
||||
### 3b. Cross-host coordination verbs
|
||||
|
||||
`rig send/capture/transcript/broadcast --host <id>` (and the `agent@rig@host`
|
||||
target sugar on the session-target verbs) cross the host boundary with **zero
|
||||
daemon-side changes** — the remote daemon's existing local routes do all the
|
||||
work; the only net-new is the CLI transport branch.
|
||||
target sugar on the session-target verbs) cross the host boundary through the
|
||||
remote daemon's ordinary local routes; the cross-host logic is in the CLI.
|
||||
|
||||
- **The seam principle (arch-ruled): SIDEDNESS + CALLER pick the seam.**
|
||||
ONE-SIDED remote ops from a BEARER-CAPABLE caller (the CLI resolves the
|
||||
registry bearer locally) go **CLI-DIRECT** via the shipped
|
||||
`runRemoteHttpOp` — one hop, no local-daemon involvement (the
|
||||
`ps --all-hosts` precedent). TWO-SIDED ops (MH-3's queue handoff closes the
|
||||
local source and creates the remote successor — the local daemon owns half
|
||||
the transaction) or BEARER-INCAPABLE callers (the browser: MC-action,
|
||||
MH-2's read-through) use the **daemon-side forward-then-strip**. MH-4's
|
||||
four verbs are pure one-sided remote ops from the CLI.
|
||||
- **Transport is dictated by the host entry (ssh XOR http), never a
|
||||
per-call choice:** ssh hosts keep the shipped shell-out byte-verbatim for
|
||||
send/capture; http hosts (the `pair` front door's kind) take the
|
||||
CLI-direct branch to `POST /api/transport/send|capture|broadcast` /
|
||||
`GET /api/transcripts/*` with the SAME bodies/paths the local CLI builds
|
||||
(one route, two callers = wrap parity by construction). transcript and
|
||||
broadcast are http-only (no ssh path exists for them); a wrong-transport
|
||||
verb dies with the structured requirement error — never a fallback.
|
||||
- **Terminal-bearer posture (named limitation, v0 — `/api/transport/*`
|
||||
ONLY):** the remote's transport routes (send/capture/broadcast) gate on
|
||||
ITS TERMINAL bearer class (`OPENRIG_TERMINAL_BEARER_TOKEN`, default null →
|
||||
pass-through; the tailnet is the auth boundary by design), while the CLI
|
||||
presents the REGISTRY bearer from `hosts.yaml` when one is configured;
|
||||
for a URL-only anonymous host the `Authorization` header is omitted.
|
||||
A remote enforcing a
|
||||
DIFFERENT terminal bearer surfaces as the structured `permission-gate`
|
||||
step (never a hang, never silent). Remedy: set the remote terminal bearer
|
||||
equal to the paired registry bearer, or rely on the tailnet boundary.
|
||||
**The transcript read is DELIBERATELY outside this class (arch n2):**
|
||||
`/api/transcripts/*` mounts UNGATED (`server.ts` — the shipped open-route
|
||||
posture; daemon-local trust boundary with route-level credential
|
||||
redaction as the protective primitive), so a wrong terminal bearer that
|
||||
permission-gates `send --host` does NOT gate `transcript --host` — the
|
||||
read keeps succeeding, and the proof matrix must not treat the auth-fail
|
||||
class as uniform across the four verbs. A coherent transcript-read auth
|
||||
policy across tail/grep/full is a named future slice per
|
||||
`routes/transcripts.ts`'s own comment (orch approved-option-a).
|
||||
Bearer-class unification is likewise a NAMED follow-up, not v0 (no
|
||||
un-asked auth machinery).
|
||||
- **BR-1 holds:** the 3-part form is CLI-edge sugar only (suffix must match
|
||||
a REGISTERED host id, else passthrough + a loud host hint); every session
|
||||
string that reaches any daemon stays `member@rig`; the host travels
|
||||
out-of-band. Durable cross-host coordination stays MH-3's queue — MH-4
|
||||
adds no queue surface.
|
||||
- **Sidedness and caller pick the seam.** One-sided remote operations from a
|
||||
caller that can resolve the registry bearer locally (the CLI) go
|
||||
**CLI-direct** through `runRemoteHttpOp`
|
||||
(`packages/cli/src/remote-host-ops.ts:25`) — one hop, no local-daemon
|
||||
involvement, as `rig ps --all-hosts` does. Two-sided operations (a
|
||||
cross-host queue handoff closes the local source and creates the remote
|
||||
successor, so the local daemon owns half the transaction) and callers
|
||||
without the bearer (the browser: Mission Control actions and the `?host=`
|
||||
read-through) go through the local daemon, which forwards to the remote.
|
||||
These four verbs are one-sided remote operations from the CLI.
|
||||
- **The host entry dictates the transport (ssh or http), never a per-call
|
||||
choice.** ssh hosts shell out over ssh for send/capture
|
||||
(`packages/cli/src/cross-host-executor.ts`); http hosts (the kind
|
||||
`rig host pair` registers) go CLI-direct to
|
||||
`POST /api/transport/send|capture|broadcast` / `GET /api/transcripts/*` with
|
||||
the same bodies and paths the local CLI uses. transcript and broadcast are
|
||||
http-only (no ssh path exists for them); on an ssh host they fail with a
|
||||
structured error naming the transport requirement
|
||||
(`remote-host-ops.ts:47`), never a fallback.
|
||||
- **Terminal-bearer posture (`/api/transport/*` only).** The remote's
|
||||
transport routes (send/capture/broadcast) gate on its terminal bearer
|
||||
(`routes/transport.ts:26`; `OPENRIG_TERMINAL_BEARER_TOKEN`,
|
||||
`packages/daemon/src/index.ts:291`). With no token configured the
|
||||
middleware passes every request through
|
||||
(`packages/daemon/src/middleware/auth-bearer-token.ts:99`) — the tailnet is
|
||||
the auth boundary by design — except that a daemon bound to an explicit
|
||||
address that is neither local nor tailnet falls back to
|
||||
`OPENRIG_AUTH_BEARER_TOKEN`
|
||||
(`index.ts:297`). The CLI presents the registry bearer from `hosts.yaml`
|
||||
when one is configured; for a URL-only anonymous host the `Authorization`
|
||||
header is omitted. A remote enforcing a different terminal bearer answers
|
||||
401, which surfaces as the structured `permission-gate` step
|
||||
(`packages/cli/src/host-registry.ts:53`) — never a hang, never silent.
|
||||
Remedy: set the remote terminal bearer equal to the paired registry bearer,
|
||||
or rely on the tailnet boundary. **The transcript read is deliberately
|
||||
outside this class:** `/api/transcripts/*` mounts with no bearer middleware
|
||||
(`packages/daemon/src/server.ts:748`, beside the gated `/api/transport` at
|
||||
`:749`), and credential-shaped text is redacted on the `/full` route only
|
||||
(`routes/transcripts.ts:292`). So a wrong terminal bearer that
|
||||
permission-gates `send --host` does NOT gate `transcript --host` — the read
|
||||
keeps succeeding for a Host name the remote's browser boundary accepts
|
||||
(`server.ts:647`; it applies to every `/api/*` route), and the auth-failure
|
||||
class is not uniform across the four verbs. The route's own comment (`routes/transcripts.ts:250`) leaves a
|
||||
coherent transcript-read auth policy across tail/grep/full for later.
|
||||
- **The 3-part form is CLI-edge sugar only.** `resolveCrossHostTarget`
|
||||
(`packages/cli/src/cross-host-target.ts:58`) strips the suffix only when it
|
||||
resolves to a registered host, or equals this host's own id, which routes
|
||||
home (`:95`; only `rig send` passes the self id,
|
||||
`packages/cli/src/commands/send.ts:398`, so `capture` and `transcript` treat
|
||||
their own id as unknown); otherwise the string passes through unchanged with a loud
|
||||
host hint. Every session string that reaches any daemon stays
|
||||
`member@rig`; the host travels out-of-band. Durable cross-host
|
||||
coordination stays the queue's cross-host routing
|
||||
(`coordination-primitive.md`); these four verbs add no queue surface.
|
||||
|
||||
## 4. Transcript flow
|
||||
|
||||
(`architecture.md` §6 "Transcript flow")
|
||||
|
||||
1. `NodeLauncher` starts `pipe-pane` immediately after tmux session creation
|
||||
(before harness boot).
|
||||
2. Raw terminal output streams to
|
||||
`~/.openrig/transcripts/{rig-name}/{session-name}.log`.
|
||||
3. `TranscriptStore` owns path convention, ANSI stripping on read, boundary
|
||||
markers, `readTail`, `grep`.
|
||||
1. `NodeLauncher` starts transcript rotation right after tmux session
|
||||
creation, before the harness boots
|
||||
(`packages/daemon/src/domain/node-launcher.ts:181`).
|
||||
2. Each rotation tick captures the trailing lines with
|
||||
`capturePaneContent` (`transcript-rotation.ts:213` →
|
||||
`adapters/tmux.ts:1082`) and atomically overwrites (temp file + rename,
|
||||
`transcript-rotation.ts:283`–`285`)
|
||||
`transcripts/{rig-name}/{session-name}.log` under the OpenRig home (or
|
||||
the `transcripts.path` setting). It skips the write when the captured bytes
|
||||
are unchanged. Defaults: 1000 trailing lines every 2 s
|
||||
(`transcript-rotation.ts:27`–`28`); an idle seat is captured less often,
|
||||
within the 10-second freshness window. Both are tunable with
|
||||
`OPENRIG_TRANSCRIPTS_LINES` and `OPENRIG_TRANSCRIPTS_POLL_INTERVAL_SECONDS`
|
||||
or the matching `transcripts.*` settings. This bounded capture replaced the
|
||||
earlier `tmux pipe-pane` stream (`transcript-rotation.ts:3`), so the file
|
||||
holds a trailing window, not the whole session.
|
||||
3. `TranscriptStore` owns path convention, ANSI stripping for `readTail` and
|
||||
`grep`, boundary
|
||||
markers, `readTail`, `readFull`, `grep`. `readFull` returns the raw bytes
|
||||
(`transcript-store.ts:378`); the `/full` route redacts them. The transcript
|
||||
routes check capture health first and can start capture lazily for a seat
|
||||
that has none.
|
||||
4. `rig transcript <session> --tail N / --grep "pattern"` provides
|
||||
agent-facing access.
|
||||
5. On restore: a boundary marker is written before re-launch; pipe-pane
|
||||
reconnects to the same file (append). (Restore-side detail in
|
||||
`lifecycle-snapshot-restore.md` §3.)
|
||||
5. On restore: a `--- SESSION BOUNDARY: … ---` marker is written before
|
||||
re-launch (`restore-orchestrator.ts:900`); each rotation tick keeps each
|
||||
distinct boundary line once, as a header above the fresh capture
|
||||
(`transcript-rotation.ts:254`–`257`). (Restore-side detail in
|
||||
`lifecycle-snapshot-restore.md`.)
|
||||
6. `rig ask` gathers rig summary plus transcript excerpts, chat excerpts,
|
||||
insufficiency state, and guidance.
|
||||
|
||||
Architecture Rule 19 (carried): transcripts are raw capture via pipe-pane,
|
||||
ANSI strip on read; `rg` preferred, `grep -E` fallback. Rule 22: `rig ask`
|
||||
is context engineering — gathers evidence, does NOT call an external LLM;
|
||||
the agent IS the LLM.
|
||||
Architecture rule 19, as the code implements it: transcripts are a bounded
|
||||
`capture-pane` snapshot (not pipe-pane), ANSI-stripped on tail and grep reads; `rg`
|
||||
preferred, `grep -E` fallback. Rule 22: the daemon's `rig ask` is context
|
||||
engineering — it gathers evidence and does NOT call an external LLM; the
|
||||
agent IS the LLM. The one exception is the explicit CLI flag `rig ask --wake`
|
||||
(`packages/cli/src/commands/ask.ts:88`), which runs a headless resume of a
|
||||
Claude or Codex session (`claude -p --resume` / `codex exec resume`,
|
||||
`packages/cli/src/ask-wake.ts:58`–`60`) to answer one question.
|
||||
|
||||
## 5. Chat flow
|
||||
|
||||
(`architecture.md` §6 "Chat flow")
|
||||
|
||||
1. `rig chatroom send <rig> "message"` → `POST
|
||||
/api/rigs/:rigId/chat/send` → `ChatRepository.addMessage()`.
|
||||
/api/rigs/:rigId/chat/send` → `ChatRepository.send()`
|
||||
(`routes/chat.ts:43`); the route then emits `chat.message` (`:45`).
|
||||
2. SSE: `GET /api/rigs/:rigId/chat/watch` delivers real-time messages.
|
||||
3. History: `GET /api/rigs/:rigId/chat/history` returns full channel
|
||||
history; `POST /api/rigs/:rigId/chat/topic` persists topic markers.
|
||||
4. UI: chat-room tab in the rig drawer.
|
||||
5. MCP: **`rig_chatroom_send` + `rig_chatroom_watch`** (D5 axis-1 correction
|
||||
— `architecture.md` §6 said `rigged_*`; source `mcp-server.ts:364` +
|
||||
tool #17).
|
||||
3. History: `GET /api/rigs/:rigId/chat/history` returns channel history in id
|
||||
order, 100 messages unless `limit` says otherwise
|
||||
(`chat-repository.ts:68`), filterable by `topic`, `after`, `since` and
|
||||
`sender` (a `since` SQLite cannot parse is refused with 400,
|
||||
`routes/chat.ts:73`); `POST /api/rigs/:rigId/chat/topic` persists topic markers.
|
||||
4. UI: a `RigChatPanel` component exists
|
||||
(`packages/ui/src/components/RigChatPanel.tsx`), but no UI view mounts it:
|
||||
**1** file under `packages/ui/src` names it, its own
|
||||
(`git grep -l 'RigChatPanel' -- packages/ui/src | wc -l`).
|
||||
5. MCP: **`rig_chatroom_send` + `rig_chatroom_watch`** (`mcp-server.ts:364`,
|
||||
`:400`).
|
||||
6. Source of truth: daemon-backed SQLite (`chat_messages` table), NOT tmux
|
||||
scrollback.
|
||||
|
||||
**ChatMessage** type (`architecture.md` §4): durable rig-scoped message —
|
||||
**ChatMessage** type (`chat-repository.ts:6`): durable rig-scoped message —
|
||||
`id`, `rigId`, `sender`, `kind`, `body`, `topic`, `createdAt`.
|
||||
|
||||
## 6. Compatibility notes (carried verbatim, `architecture.md` §11)
|
||||
## 6. Compatibility notes
|
||||
|
||||
The intentional limits in this layer:
|
||||
The intentional limits in this layer (numbered as in
|
||||
`architecture-rules-and-event-system.md`):
|
||||
|
||||
- Note 4 — `rig ask` gathers context only; does not call an external LLM.
|
||||
The agent reasons about the gathered evidence.
|
||||
- Note 4 — the daemon's `rig ask` gathers context only; it does not call an
|
||||
external LLM. The agent reasons about the gathered evidence. Only the
|
||||
explicit `--wake` flag executes a runtime (§4).
|
||||
- Note 5 — transcript search prefers `rg` but falls back to `grep -E`;
|
||||
search quality/performance varies by backend.
|
||||
- Note 6 — chat is rig-scoped only: no cross-rig channels or DMs.
|
||||
- Note 7 — `--verify` on `rig send` checks pane content for message
|
||||
visibility but can produce false positives from pre-existing matching
|
||||
content. Known limitation.
|
||||
- Note 7 — `--verify` on `rig send` checks the pane for the message by
|
||||
comparing snippet counts before and after the send (§3 step 4); when the
|
||||
capture cannot re-confirm the render it reports `rendered-unconfirmed`,
|
||||
not a failure.
|
||||
|
||||
(The full §11 compatibility-notes list lives in
|
||||
(The full compatibility-notes list lives in
|
||||
`architecture-rules-and-event-system.md`.)
|
||||
|
||||
## OPEN / carried items
|
||||
|
||||
- **D5 (the careful one — resolved per-occurrence, NOT blanket-replaced):**
|
||||
Axis 1 (MCP tool names) corrected `rigged_*` → `rig_*` (3 sites: §5
|
||||
node-inventory, §6 chat flow ×2). Axis 2 (tmux `@rigged_*` metadata keys)
|
||||
verified literally correct at `claim-service.ts:77-81` and left unchanged.
|
||||
No slice-00 numeric drift applies to this module's split content.
|
||||
|
||||
## See also
|
||||
|
||||
- `daemon-core.md` — transport/transcript/chat/ask are among the 49 route
|
||||
mounts; MCP tool count (17, `rig_*`) is anchored there.
|
||||
- `agent-spec-and-startup.md` §6 — the tmux `@rigged_*` metadata-key axis
|
||||
detail (whoami/adopt).
|
||||
- `lifecycle-snapshot-restore.md` §3 — restore-side transcript boundary
|
||||
- `daemon-core.md` — the route-mount surface these routes sit in, and the MCP
|
||||
tool count.
|
||||
- `agent-spec-and-startup.md` — the tmux `@rigged_*` metadata-key axis detail
|
||||
(whoami/adopt).
|
||||
- `lifecycle-snapshot-restore.md` — restore-side transcript boundary
|
||||
markers.
|
||||
- `coordination-primitive.md` §3b — MH-3's cross-host QUEUE routing (the
|
||||
two-sided daemon-forward twin of §3b's one-sided CLI-direct verbs).
|
||||
- `cli-reference.md` § Cross-host execution — the per-verb transport
|
||||
capability table, parse rules, and http failure taxonomy (MH-3 + MH-4).
|
||||
- Source roots: `packages/daemon/src/domain/{session-transport,
|
||||
transcript-store,history-query,ask-service,chat-repository}.ts`,
|
||||
- `coordination-primitive.md` — cross-host queue routing (the two-sided
|
||||
daemon-forward twin of §3b's one-sided CLI-direct verbs).
|
||||
- `../cli-reference.md` — each verb's registered `--host <id>` option; run
|
||||
`rig <verb> --help` for its cross-host behaviour.
|
||||
- Source roots: `packages/daemon/src/domain/{session-transport,transcript-store,transcript-rotation,history-query,ask-service,chat-repository}.ts`,
|
||||
`packages/daemon/src/routes/{transport,transcripts,ask,chat}.ts`,
|
||||
`packages/cli/src/mcp-server.ts`,
|
||||
`packages/cli/src/{remote-host-ops,cross-host-target,cross-host-executor}.ts` (v0.4.6 MH-4).
|
||||
`packages/cli/src/{remote-host-ops,cross-host-target,cross-host-executor,ask-wake}.ts`.
|
||||
@@ -1,509 +1,298 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: Workflow Runtime + Watchdog Policies (PL-004 Phase C/D)
|
||||
title: Workflow Runtime — Specs, Packets, and Lifecycle Graphs
|
||||
status: active
|
||||
topics: [coordination, runtime-control]
|
||||
topics: [orchestration, coordination]
|
||||
domains: [engineering-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
Need to know how the daemon-native Workflow Runtime works — workflow specs
|
||||
cache, instance state, step trails, the transactional-scribe projection
|
||||
contract, or the watchdog policy set including workflow-keepalive.
|
||||
siblings: [coordination-primitive.md, mission-control.md]
|
||||
prerequisite-reads: [../README.md, coordination-primitive.md]
|
||||
last-verified-against-source: slice/opr-0.4.6.wf2-spec-language tip (base d18907ed — WF-1 merged)
|
||||
last-updated: 2026-07-06
|
||||
Following workflow validation, instantiation, projection, routing, failure
|
||||
recovery, lifecycle graph revisions, or the corresponding CLI/API.
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Workflow Runtime + Watchdog Policies (PL-004 Phase C/D)
|
||||
# Workflow Runtime — Specs, Packets, and Lifecycle Graphs
|
||||
|
||||
The daemon-native Workflow Runtime (PL-004 Phase D) turns an intended
|
||||
sequence of work into durable SQLite state: declarative workflow specs, live
|
||||
instance state, append-only step trails, and the load-bearing
|
||||
transactional-scribe contract. The PRD §L4 operating model is
|
||||
owner-as-author semantically plus workflow-as-transactional-scribe
|
||||
mechanically (`architecture.md` §3 L398).
|
||||
Source snapshot: `e8f0ab340db773392ec8be75b072d1c0f3068a50`. This describes the source at that commit;
|
||||
it does not establish the version or behavior of a running daemon.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c`.
|
||||
A workflow binds a specification to durable queue packets. The daemon
|
||||
records the instance, its current frontier, and the step trail; agents supply
|
||||
step outcomes and evidence. Serial workflows and dependency graphs share
|
||||
this runtime. The engine checks declared fields and recorded state; it does
|
||||
not judge the substance of a referenced proof document.
|
||||
|
||||
## 1. The three Phase D tables (+ the diagnostic addition)
|
||||
## Source map
|
||||
|
||||
Re-confirmed at HEAD in `packages/daemon/src/db/migrations/`:
|
||||
All paths in this table are under `packages/daemon/src/`.
|
||||
|
||||
- **`workflow_specs`** (`033_workflow_specs.ts:33` `CREATE TABLE … workflow_specs`)
|
||||
— a read-through cache of human-authored markdown/YAML spec files. Sources
|
||||
are workspace-surface; the daemon caches by `(name, version)` with a
|
||||
content `source_hash` so valid operator edits to the spec file win at next
|
||||
read (workspace-surface reconciliation). Spec authoring stays
|
||||
markdown-authoritative; the cache exists for fast lookup and runtime
|
||||
resolution (`architecture.md` §3 L400).
|
||||
- **`workflow_instances`** (`034_workflow_instances.ts:45` `CREATE TABLE …
|
||||
workflow_instances`) — live state per running workflow: `status`
|
||||
(`active|waiting|completed|failed`), `current_frontier_json` (active qitem
|
||||
ids), `hop_count` (loop-guard counter), `last_continuation_decision_json`.
|
||||
Instances survive daemon restart from SQLite — no filesystem
|
||||
reconciliation (`architecture.md` §3 L402).
|
||||
- **`workflow_step_trails`** (`035_workflow_step_trails.ts:32` `CREATE TABLE …
|
||||
workflow_step_trails`) — append-only history of meaningful step
|
||||
transitions. Every closure produces one trail row pairing the prior qitem
|
||||
with the next qitem (null on terminal). `WorkflowStepTrailLog.record()` is
|
||||
the only writer (`architecture.md` §3 L404).
|
||||
- **`040_workflow_specs_diagnostic.ts`** — slice-11 (`f68f453a`,
|
||||
slice-00 §1.3 provenance). An `ALTER TABLE ADD COLUMN` adding
|
||||
parser/validator diagnostic columns to `workflow_specs` (**no new table,
|
||||
no constraint changes beyond a DEFAULT**; the cache carries the
|
||||
parse/validation diagnostic for the UI to render — `040_..._diagnostic.ts:5–25`).
|
||||
**This migration is net-new since `architecture.md` was last edited** and
|
||||
is not described in any prior as-built doc (slice-00 §1.3 — `f68f453a`
|
||||
postdates the §3 body) — authored here from source per the slice-08
|
||||
drift-to-fix register.
|
||||
| File / symbol | Responsibility |
|
||||
|---|---|
|
||||
| [`domain/workflow-types.ts`](../../../packages/daemon/src/domain/workflow-types.ts) | Spec, instance, trail, frontier, and failure-occurrence contracts. |
|
||||
| [`WorkflowSpecCache`](../../../packages/daemon/src/domain/workflow-spec-cache.ts) | YAML parsing, source-hash cache, stored specs, diagnostics and retention. |
|
||||
| [`WorkflowValidator`](../../../packages/daemon/src/domain/workflow-validator.ts) | Roles, exits, routing/dependency edges, pins, cycles, and advisories. |
|
||||
| [`WorkflowRuntime`](../../../packages/daemon/src/domain/workflow-runtime.ts) | Instantiation, inspection, routing, resume, abort, and lifecycle facade. |
|
||||
| [`WorkflowProjector`](../../../packages/daemon/src/domain/workflow-projector.ts) | Close the current step and update its successor/frontier state. |
|
||||
| [`WorkflowInstanceStore`](../../../packages/daemon/src/domain/workflow-instance-store.ts) | Instances, guarded updates, frontier bindings, and failure occurrences. |
|
||||
| [`WorkflowStepTrailLog`](../../../packages/daemon/src/domain/workflow-step-trail-log.ts) | Append/read step history. |
|
||||
| [`workflow-reconciliation.ts`](../../../packages/daemon/src/domain/workflow-reconciliation.ts) | Inspect graph changes, apply explicit revisions, recover operation receipts. |
|
||||
| [`workflow-keepalive.ts`](../../../packages/daemon/src/domain/policies/workflow-keepalive.ts) | Watchdog evaluation and reminders. |
|
||||
|
||||
`036_watchdog_policy_enum_extension.ts` is a documenting no-op that records
|
||||
the Phase D watchdog enum extension (Phase C uses application-layer
|
||||
enforcement via the `PHASE_D_POLICIES` array, so no DDL is needed —
|
||||
`architecture.md` §3 L363).
|
||||
## Specification and cache
|
||||
|
||||
## 2. The transactional-scribe contract
|
||||
`parseWorkflowSpec` parses the `workflow` document and checks known key sets.
|
||||
`WorkflowValidator.validate` then checks relationships between roles, steps,
|
||||
exits, and targets. The exits are `handoff`, `waiting`, `done`, and `failed`.
|
||||
An exit-specific `next_hop.on` mapping takes precedence over structural
|
||||
routing; without a mapping, `resolveNextStep` considers `forbid`, suggested
|
||||
roles, `require`, and then the next array element.
|
||||
|
||||
The load-bearing Phase D guarantee is implemented in
|
||||
`WorkflowProjector.project()` (`packages/daemon/src/domain/workflow-projector.ts`).
|
||||
Re-confirmed at HEAD: the projector header (`workflow-projector.ts:1–20`)
|
||||
declares "transactional-scribe contract" and `project()` runs a single
|
||||
`db.transaction` (`workflow-projector.ts:184` `const txn = this.db.transaction(...)`).
|
||||
Inside that one transaction:
|
||||
The language includes `harness`, `host`, singular `gate`, `depends_on`, typed
|
||||
`acceptance`, and waiting re-presentation settings. At this pin,
|
||||
`WORKFLOW_AGENT_HARNESSES` contains only `claude-code` and `codex`; support for
|
||||
another runtime elsewhere in OpenRig does not add it to this pin's workflow
|
||||
harness grammar. Nonlocal host pins can validate as registered names but
|
||||
`instantiate` still rejects execution with `host_pin_remote_unsupported`.
|
||||
This is a workflow-step restriction, not a claim that all queue traffic is
|
||||
local-only.
|
||||
|
||||
1. Close the current packet (state mutation on `queue_items`).
|
||||
2. Create the next-step packet (`QueueRepository.createWithinTransaction()`,
|
||||
`workflow-projector.ts:230`).
|
||||
3. Record the trail entry.
|
||||
4. Update the instance frontier + status.
|
||||
5. Persist workflow events.
|
||||
A step whose `allowed_exits` can neither finish it nor route it on is refused as
|
||||
`step_cannot_finish` (`workflow-validator.ts:223-236`): its exits leave out `done`, none is
|
||||
mapped in `next_hop.on`, and it allows no `handoff` that routes on. In a dependency graph,
|
||||
allowing `handoff` is enough, because handoff completes a sink. `instantiate` refuses any spec
|
||||
with a validation error.
|
||||
|
||||
Either everything commits or everything rolls back; lost handoffs are
|
||||
impossible by design. Post-commit, subscribers are notified and the next
|
||||
owner is nudged (`architecture.md` §3 L406).
|
||||
`allowed_exits` and `max_hops` affect validation/projection. In contrast,
|
||||
`continuation_required`, `preserve_lineage`, `closure_required`, closure
|
||||
messages, `skill_refs`, and `spawn_budget` produce
|
||||
`declared_not_enforced_v1` advisories. Legacy `gates: []` and
|
||||
`next_hop.mode: prefer` are rejected by the parser rather than silently
|
||||
accepted. Prerequisite cycles are rejected; a routing cycle requires a
|
||||
`max_hops` declaration and is distinct from a prerequisite cycle.
|
||||
`re_present_after_seconds` compiles to the queue's park timer on an unrouted
|
||||
`waiting` exit, and `re_present_max_seconds` turns it into a repeating
|
||||
reminder with backoff (`workflow-projector.ts:534`–`549`); a step that cannot
|
||||
reach `waiting`, or that maps `waiting` to a branch, fails validation
|
||||
(`waiting_re_presentation_unreachable`).
|
||||
|
||||
`WorkflowRuntime` (`packages/daemon/src/domain/workflow-runtime.ts:61`
|
||||
`export class WorkflowRuntime`) is the orchestration class above the
|
||||
projector.
|
||||
`WorkflowSpecCache.readThrough` parses the source file, hashes it, and
|
||||
updates the `(name, version)` cache row. Full `spec_json` preserves fields
|
||||
that the older scalar columns cannot represent. A same-name/same-version
|
||||
source edit can update that cache row; the version label alone is not an
|
||||
immutable content pin. `getByNameVersion` is the runtime lookup.
|
||||
Diagnostic-only rows, moved sources, and versions retained for unfinished
|
||||
work have separate paths in `readThrough`, `writeDiagnostic`, and
|
||||
`isPinnedByUnfinishedWork`.
|
||||
|
||||
**Phase D scope boundary** (`architecture.md` §3 L412): excludes multi-hop
|
||||
chaining, gate-return-sweep, and the closure-enforcement path. The daemon's
|
||||
transactional state remains the closure authority via Phase A's hot-potato
|
||||
strict-rejection (see `coordination-primitive.md` §3); the workflow runtime
|
||||
**projects on closure, it does not gate closure**.
|
||||
## Durable state
|
||||
|
||||
## 3. The workflow-keepalive watchdog policy
|
||||
The migration files record the storage evolution; they are not a count of
|
||||
all database migrations:
|
||||
|
||||
`workflow-keepalive` is the Phase C-deferred watchdog policy, a TypeScript
|
||||
port of the POC `lib/policies/workflow-keepalive.mjs` adapted to read SQLite
|
||||
(`packages/daemon/src/domain/policies/workflow-keepalive.ts:1–5`).
|
||||
Re-confirmed at HEAD (`workflow-keepalive.ts:5–16`):
|
||||
| Migration | Workflow state introduced |
|
||||
|---|---|
|
||||
| [`033`](../../../packages/daemon/src/db/migrations/033_workflow_specs.ts), [`034`](../../../packages/daemon/src/db/migrations/034_workflow_instances.ts), [`035`](../../../packages/daemon/src/db/migrations/035_workflow_step_trails.ts) | Spec cache, instances/frontier/current step, and append-only trail API. |
|
||||
| [`040`](../../../packages/daemon/src/db/migrations/040_workflow_specs_diagnostic.ts), [`050`](../../../packages/daemon/src/db/migrations/050_workflow_spec_json.ts) | Cache diagnostics and complete parsed spec JSON. |
|
||||
| [`049`](../../../packages/daemon/src/db/migrations/049_workflow_instance_version.ts), [`051`](../../../packages/daemon/src/db/migrations/051_workflow_resume.ts), [`052`](../../../packages/daemon/src/db/migrations/052_workflow_instance_bound_rig.ts) | Optimistic version guard, resume/hop baseline, and bound rig. |
|
||||
| [`079`](../../../packages/daemon/src/db/migrations/079_workflow_lifecycle_parallel.ts) | Lifecycle operation key/digest/binding, packet-to-step frontier bindings, and failure occurrences. |
|
||||
|
||||
- **LOAD-BEARING:** it MUST read `workflow_instances` directly via SQLite —
|
||||
never the markdown source.
|
||||
- Eligibility: `status === "active" || status === "waiting"`. Else
|
||||
`action=terminal, reason="workflow_not_active"`.
|
||||
- Frontier empty + no fallback target: skip with `reason="empty_frontier"`.
|
||||
- Resolves frontier qitem owners by querying `queue_items`; combines with
|
||||
explicit observer/created-by targets; sends to the first resolved target.
|
||||
Current instance states include `active`, `waiting`, `completed`, `failed`,
|
||||
and `aborted`. A multi-packet frontier needs packet-to-step bindings; a
|
||||
single `current_step_id` cannot describe all parallel work.
|
||||
|
||||
The watchdog supervision tree itself (PL-004 Phase C, `031_watchdog_jobs.ts` /
|
||||
`032_watchdog_history.ts`) records only meaningful evaluations; quiet skip
|
||||
reasons (`not_due`, `no_actionable_artifacts`, `active_wake_not_due`) are
|
||||
NOT recorded and do NOT emit `watchdog.*` events — POC parity so agents are
|
||||
not woken about scheduler polls (`architecture.md` §3 L362). The Phase D
|
||||
policy enum extends Phase C's three values with `workflow-keepalive`.
|
||||
## Instantiation and owner selection
|
||||
|
||||
## 4. Workflow events
|
||||
`WorkflowRuntime.instantiate` resolves a cached spec name or explicit path,
|
||||
validates it, resolves the rig and the entry owner, then creates the instance
|
||||
and its single entry packet (for `steps[0]`) through the transactional
|
||||
event/queue path (`workflow-runtime.ts:433`, `:757`). The entry packet gets a
|
||||
best-effort post-commit nudge with no staged wake intent (`:843`–`845`); its
|
||||
keepalive, armed in the same transaction, and the boot sweep cover a lost
|
||||
entry nudge. An explicit
|
||||
unknown `targetRig` fails; an unknown spec-default rig produces an advisory
|
||||
and falls back to unbound operation. A bound rig without a required declared
|
||||
role is a different error.
|
||||
|
||||
> Drift-fix D8 / OPEN-4 (carried verbatim, slice-00): `architecture.md` §3
|
||||
> L410 says "Existing 20 PL-004 events are unchanged" — internally
|
||||
> inconsistent with L394's "32 PL-004 events untouched". **Do NOT carry
|
||||
> either number.** The current `RigEvent` union
|
||||
> (`packages/daemon/src/domain/types.ts:94`) has **73 members** total
|
||||
> (slice-00 §1.8, re-confirmed at HEAD). The additive Phase D `workflow.*`
|
||||
> events are described below WITHOUT asserting a contested PL-004 sub-count.
|
||||
`resolveDefaultOwner` uses declared `preferred_targets` first, applying a
|
||||
harness match when pinned. With no preferred targets and a bound rig, it uses
|
||||
[`selectRoleSeat`](../../../packages/daemon/src/domain/workflow-role-resolver.ts):
|
||||
a running managed agent with the declared role and required runtime, ordered
|
||||
by pending-only backlog and then canonical coordinate. Infrastructure and
|
||||
unresolvable/adopted coordinates do not become implicit replacement agents.
|
||||
Explicit owner overrides are reconciled with harness pins. Declared
|
||||
`preferred_targets` are not checked for liveness; validate and instantiate
|
||||
only warn (`role_no_live_preferred_target`) when none of a role's targets has
|
||||
a running session. A structured gate compiles by target kind (`compileGate`):
|
||||
for a human-seat target, the packet goes to the gated step's own owner and is
|
||||
parked `blocked_on` that human seat in the same transaction (summary and
|
||||
`evidence_ref` required), and the instance waits until the park is resolved;
|
||||
for a declared role, an ordinary packet goes to that handler role's seat
|
||||
(`workflow-projector.ts:1659`–`1778`).
|
||||
|
||||
Phase D extends `RigEvent` with the additive `workflow.*` events
|
||||
(re-confirmed `domain/types.ts:196–201`): `workflow.instantiated`,
|
||||
`workflow.step_closed`, `workflow.next_qitem_projected`,
|
||||
`workflow.completed`, `workflow.failed`, `workflow.routing_table_changed`
|
||||
(6 members; a separate `workflow_spec` event also exists in the union).
|
||||
Instantiation also emits advisories for missing members in otherwise
|
||||
registered target rigs. That member check is advisory and does not imply
|
||||
all preferred targets received a live-readiness test.
|
||||
|
||||
## 5. Route surface
|
||||
## Projection, waiting, and concurrency
|
||||
|
||||
`/api/workflow` (`server.ts:495`) — `POST /validate`
|
||||
(`routes/workflow.ts:82`), `POST /instantiate` (`:93`,
|
||||
`getRuntime(c).instantiate(...)`), `POST /project` (`:118`,
|
||||
`getRuntime(c).project(...)` — the transactional-scribe entry),
|
||||
`GET /:instance_id/trace` (instance + trail), `POST /:instance_id/continue`
|
||||
(idempotent inspect). Surface enumerated `routes/workflow.ts:21–28`.
|
||||
Cross-ref: the `rig workflow` CLI surface — see `../cli-reference.md`.
|
||||
`WorkflowProjector.project` checks active/waiting status, frontier membership,
|
||||
packet-to-step identity, allowed exits, and applicable evidence requirements.
|
||||
For a successful advance, queue closure, successor creation, trail entries,
|
||||
frontier/version updates, durable events, and wake intent staging share the
|
||||
transactional notification envelope. Terminal delivery happens after the
|
||||
commit, and the runtime awaits it, so project, route, resume and exception
|
||||
responses return after the delivery attempt; a terminal close without its
|
||||
successor's staged intent rolls back. A committed packet or staged wake is
|
||||
not evidence that its recipient consumed the message.
|
||||
|
||||
## 6. The WF-1 failure envelope (OPR.0.4.6.WF1)
|
||||
Each exit closes the packet through the queue's own closure path
|
||||
(`updateWithinTransaction`, hot-potato rules intact): `handoff` records
|
||||
`handed-off` with `handed_off_to` the next owner; `waiting` records `blocked`
|
||||
with `blocked_on` the supplied blocker, or `external-gate` when none is given;
|
||||
`done` records `done` with `no-follow-on`; and `failed` records `done` with
|
||||
`denied` and the result note as target (`workflow-projector.ts:1571`–`1631`).
|
||||
A dependency-graph handoff that fans out to more than one successor, or to
|
||||
none, closes the packet `done` with `no-follow-on`.
|
||||
|
||||
Added on top of the kept Phase D core (nothing above was re-specced;
|
||||
FR-1 regression tests pin it):
|
||||
Both executors absorb an identical waiting replay by matching the stored
|
||||
closure intent; changed wait data is a new decision. A consumed terminal
|
||||
packet is no longer on the frontier and is refused. On the serial path a
|
||||
routed close moves the frontier to the new packet and leaves the instance
|
||||
`active`, or `waiting` when the target step is gated; an unrouted `waiting`
|
||||
keeps the closed packet on the frontier so the keepalive can find its owner;
|
||||
`failed` sets `failed`, and `done` with nothing left sets `completed`. A
|
||||
dependency graph is `waiting` while every frontier packet is blocked, and
|
||||
`failed` when the frontier empties with an unresolved occurrence.
|
||||
`max_hops` constrains routing relative to the current drive's baseline: a
|
||||
route that would exceed it becomes a `failed` exit with a `max_hops_exceeded`
|
||||
note and evidence, the packet closes and no successor is created
|
||||
(`workflow-projector.ts:419`–`443`); resume establishes a new bounded drive
|
||||
rather than erasing history.
|
||||
|
||||
- **Step deadlines (FR-2, derived — never stored):**
|
||||
`workflow-deadline.ts` classifies every active|waiting instance's
|
||||
frontier packet by anchor — claimed w/ `closure_required_at` ·
|
||||
claimed w/ NULL deadline (`claimed_at` + threshold; workflow packets
|
||||
ship tier `mode2`, which has no SLA entry) · never-claimed
|
||||
(`created_at` + threshold) · unclaimed-after-claim (`created_at`;
|
||||
unclaim NULLs `claimed_at`). `WORKFLOW_STEP_STUCK_THRESHOLD_SECONDS`
|
||||
(4h, = the routine-tier SLA) is THE single threshold home; WF-5
|
||||
binds to it. Stuck self-clears on normal re-projection.
|
||||
- **Keepalive auto-arm (FR-3):** instantiate + every handoff ensure
|
||||
ONE per-instance `workflow-keepalive` watchdog job INSIDE the scribe
|
||||
transaction; terminal exits disarm it. Auto-armed jobs carry
|
||||
`context.deadline_gated: true` — quiet while healthy, and their
|
||||
overdue send targets the stuck packet's owner with re-project
|
||||
steering. Operator-registered jobs keep exact POC always-send parity.
|
||||
- **Boot sweep (FR-4):** `workflow-boot-sweep.ts` at daemon startup —
|
||||
re-arms missing keepalives, reissues LOST post-commit nudges
|
||||
(pending frontier packet with `last_nudge_attempt` NULL = the
|
||||
commit-then-crash window, detected from the nudge ledger), surfaces
|
||||
stuck instances; one summary log line.
|
||||
- **Real idempotency (FR-5):** waiting-replay ABSORPTION under the
|
||||
full closure-intent identity (exit/packet/step/actor/resultNote/
|
||||
effective-blocker/evidence deep-equal) — exact replay = zero writes;
|
||||
any mismatch = a new decision via the normal path. Migration 049
|
||||
adds `workflow_instances.version`: every guarded advance bumps it
|
||||
`WHERE version = ?`; a stale writer gets structured
|
||||
`instance_version_conflict` and its whole transaction rolls back.
|
||||
- **max_hops enforced (FR-6):** compared at projection via
|
||||
`exceedsMaxHops(hopCount, baseline, maxHops)` (v1 baseline = 0; the
|
||||
baseline is WF-5's resume seam). Exceeding converts the handoff to
|
||||
an honest structured failure (packet closed, instance failed, guard
|
||||
evidence in trail + `workflow.failed`). Migration 050 adds
|
||||
`workflow_specs.spec_json` — before it, `loop_guards`/`invariants`/
|
||||
`closure`/`entry` were silently DROPPED at projection-time
|
||||
rehydration (column-only rebuild); legacy rows self-heal on
|
||||
readThrough and degrade VISIBLY (named once-per-spec advisory).
|
||||
- **Validation (FR-7):** `parseWorkflowSpec` rejects unknown keys loud
|
||||
at every level against EXPORTED closed keysets (WF-2 extends them);
|
||||
the validator walks reachability/cycles over the projector's own
|
||||
exported `resolveNextStep` — unreachable steps fail; a cycle without
|
||||
`max_hops` fails naming the fix; with it, sanctioned.
|
||||
- **`continue` honesty (FR-8):** relabeled to its real read-only
|
||||
inspector semantics everywhere (CLI description/outcome, route
|
||||
comment); `project` remains the sole advance write path.
|
||||
- **The v2 dispositions (FR-9):** every declared-but-unenforced key —
|
||||
`invariants.{continuation_required,preserve_lineage,closure_required}`,
|
||||
`closure.*`, step `gates[]`, role `skill_refs`, `next_hop.mode:
|
||||
prefer`, `loop_guards.spawn_budget` — produces the fail-open
|
||||
`declared_not_enforced_v1` validator advisory (warning; never
|
||||
blocks). `spawn_budget`'s advisory names its WF-2/WF-6
|
||||
parallel-frontier acceptance pointer (arch ruling 2026-07-06).
|
||||
`fallbackSynthesis` (instance column, never written) is dispositioned
|
||||
in the `workflow-types.ts` JSDoc.
|
||||
Presence of `depends_on` selects `projectDependencyGraph`. It uses durable
|
||||
packet bindings to advance eligible successors and record per-occurrence
|
||||
failures without treating an unrelated live branch as completed. A step's
|
||||
completion counts only while it is later, in trail append order, than its
|
||||
prerequisites' latest completions, so a prerequisite re-run through a routed
|
||||
exit sends its dependents round again (`workflow-projector.ts:1004`–`1019`).
|
||||
Inspection reports unknown bindings rather than inventing a step from trail order.
|
||||
|
||||
## 7. The WF-2 spec language (OPR.0.4.6.WF2)
|
||||
The injected [`createWorkflowFrontierPredicate`](../../../packages/daemon/src/domain/workflow-frontier-guard.ts)
|
||||
lets the queue refuse ordinary terminal closure of live workflow packets.
|
||||
Workflow mutations use the explicit workflow path; the queue does not need
|
||||
to import the workflow domain to evaluate that predicate.
|
||||
|
||||
WF-2 grows the language the ratified WF-1 engine speaks. ONE named
|
||||
engine extension (branch execution); everything else is language +
|
||||
compilation onto shipped seams.
|
||||
## Recovery operations
|
||||
|
||||
**Conditional-on-outcome branching (FR-1).** A step may declare
|
||||
`next_hop.on: {<exit>: <step-id>}` — branch keys are the recorded exit
|
||||
enum ONLY (`handoff|waiting|done|failed`; closed set, enforced at
|
||||
parse — `spec_branch_key_invalid`). A MAPPED exit routes to its target
|
||||
INSIDE the same scribe transaction: next qitem created in-txn, instance
|
||||
stays ACTIVE bound to the target, hop count + version guard bumped
|
||||
identically to a linear advance, and the taken branch recorded
|
||||
ADDITIVELY (`lastContinuationDecision.branchTaken` + the trail row's
|
||||
`closure_evidence.branch_taken`) — never in `closure_reason` (closed
|
||||
Phase-A enum). An UNMAPPED `failed`/`done` stays terminal exactly as
|
||||
before; unmapped `waiting` stays a park. The `max_hops` guard fires on
|
||||
ANY route (branch routes create the canonical remediation cycles);
|
||||
cycle detection at validation runs over the structural ∪ branch edge
|
||||
union and requires a declared `max_hops` to sanction any cycle.
|
||||
Routing seam: `resolveNextStep(spec, step, recordedExit?)` — one
|
||||
exported function, structural default when no exit supplied (the
|
||||
validator's path).
|
||||
`WorkflowRuntime` distinguishes these operations:
|
||||
|
||||
**Per-step `harness:` pin (FR-2).** `claude-code | codex` (agent
|
||||
harnesses only — `terminal` rejected at parse with a teaching error; Pi
|
||||
joins in 0.4.7). Owner resolution picks the first `preferred_target`
|
||||
whose node `runtime` column matches (`nodeRuntimeOf`: latest session →
|
||||
node join); no match = structured `harness_pin_unsatisfied` naming the
|
||||
pin + every candidate's runtime. Explicit owner overrides are
|
||||
reconciled too — an override can never silently defeat a pin. Static
|
||||
check at instantiate for every pinned step; re-checked at each route.
|
||||
| Operation | Meaning |
|
||||
|---|---|
|
||||
| `route` | Replace a current packet's owner while retaining the step. This is not step advancement. Multiple frontier packets require an explicit packet selection. |
|
||||
| `resume` | Redrive failed work to a newly resolved owner, recording the optional `--decision` text. A serial instance must be `failed` (`instance_not_failed`). Resume re-resolves the owner rather than copying it, re-bases `hops_baseline`, increments `resume_count`, and closes that occurrence's open exception items. Dependency failures use occurrence identity; multiple unresolved failures require selection. Identical occurrence/decision replay returns the recorded redrive, while changed decision bytes conflict. |
|
||||
| `abort` | Explicitly stop unfinished work with an actor and reason: cancels every frontier packet, records a `failed` trail row per packet, sets `aborted`, and emits `workflow.failed` with reason `aborted: <reason>`. Refused only for `completed` or `aborted` instances. |
|
||||
| `continue` | Read-only inspection of instance, trail, guidance, reconciliation, frontier, failures, unknowns, and boundary obligations. It does not mechanically advance a step. |
|
||||
|
||||
**Per-step `host:` pin (FR-3).** `local`/absent = full execution today.
|
||||
A registry id validates against `~/.openrig/hosts.yaml` (daemon
|
||||
read-only twin; unknown id = `host_not_registered` naming registered
|
||||
ids) but a remote pin fails loud at INSTANTIATE with
|
||||
`host_pin_remote_unsupported` naming the MH-3 boundary + workaround —
|
||||
the queue is local-only until MH-3; no qitem is ever minted into a
|
||||
queue that cannot route it, and there is no silent local fallback.
|
||||
## Deadlines and exceptions
|
||||
|
||||
**Structured step-level `gate:` (FR-5 — the socket; WF-5 owns
|
||||
semantics).** Singular per step, closed keyset `{target, summary,
|
||||
evidence_ref}`. HUMAN target (the shipped human-seat predicate) →
|
||||
compiles to a human-routed item (tier `human-gate` + summary +
|
||||
evidence_ref — the shipped 0.4.4 write path), resolved by the shipped
|
||||
`resolve` verb; HANDLER-ROLE target → an ordinary agent item to the
|
||||
role's resolved seat. Routing INTO a gated step creates the gate item
|
||||
as the frontier packet and parks the instance `waiting`; resolve/close
|
||||
continues the flow from that step (the WF-1 unpark — no restart). A
|
||||
gated ENTRY step parks from birth.
|
||||
[`evaluateStepDeadline`](../../../packages/daemon/src/domain/workflow-deadline.ts)
|
||||
derives overdue state from frontier queue rows. Claimed work uses its closure
|
||||
deadline, otherwise its claim time plus **4 hours** (creation time if the
|
||||
claim time is absent). Pending work uses creation time plus **4 hours**.
|
||||
Blocked waits are not overdue under this evaluator. This is a diagnostic,
|
||||
not a substitute for packet existence or binding checks.
|
||||
|
||||
**Dispositions (FR-4) — the inert third state is dead.** The legacy
|
||||
step `gates: [...]` string list is REMOVED at parse
|
||||
(`spec_gates_removed`, what/why/fix naming the new `gate:` object);
|
||||
`next_hop.mode: prefer` is REMOVED at parse
|
||||
(`spec_prefer_mode_removed` — it never had distinct behavior).
|
||||
`skill_refs` / `closure.*` / `invariants.{continuation_required,
|
||||
preserve_lineage,closure_required}` keep their WF-1 FR-9 explicitly-v2
|
||||
advisories; `spawn_budget` stays explicitly-v2 (WF-6/parallel-frontier
|
||||
acceptance pointer). Every key is consumed, removed, or
|
||||
machine-readably advisory — zero silently-inert keys.
|
||||
Instantiate and every routed projection arm a `workflow-keepalive` watchdog
|
||||
job in the same transaction (one per live packet for dependency graphs, one
|
||||
per instance for serial specs), evaluated every 15 minutes and quiet until the
|
||||
step is overdue (`workflow-keepalive-arming.ts:38`). At each daemon start the
|
||||
boot sweep re-arms keepalives for live instances, re-nudges pending frontier
|
||||
packets that were never nudged, and surfaces overdue instances
|
||||
(`workflow-boot-sweep.ts`). Neither path advances a step.
|
||||
|
||||
**Versioning honesty (FR-6).** New strictness lands at
|
||||
`parseWorkflowSpec` (the only seam that sees raw keys; the WF-1
|
||||
exported closed keysets extended with `harness`/`host`/`gate` +
|
||||
`next_hop.on`) and applies at validate/instantiate/re-parse. A LIVE
|
||||
instance pinned to a pre-WF-2 spec version keeps executing un-failed
|
||||
(project() has no validation gate, by design; stored `spec_json` blobs
|
||||
missing the new optional fields read fine); the same spec FILE
|
||||
re-validated fails under the new rules.
|
||||
There are three exception classes: `unmapped_failed`, `stuck_overdue` and
|
||||
`human_gate_trip` (`workflow-exception.ts:34`–`38`). An unrouted `failed`
|
||||
exit creates one urgent `workflow-exception` queue item in the same
|
||||
transaction, with evidence `rig workflow trace <instance>` and a body naming
|
||||
the `rig workflow resume` command; a human gate's own parked packet carries the
|
||||
`human_gate_trip` identity, so no second item is created.
|
||||
|
||||
**Hand-authorability (FR-6).** Three shipped example shapes at
|
||||
`packages/daemon/src/builtins/workflow-specs/`: `linear-build.yaml`
|
||||
(zero WF-2 features — the zero-regression reference),
|
||||
`gated-release.yaml` (human gate + harness pins),
|
||||
`branched-remediation.yaml` (bounded failed-path remediation loop).
|
||||
[`resolveExceptionRoute`](../../../packages/daemon/src/domain/workflow-exception-router.ts)
|
||||
selects per-class configuration, then workflow default, host default, and
|
||||
finally orchestrator-first behavior. An unresolved orchestrator route falls
|
||||
back to registered-human selection. A human gate is intrinsically human
|
||||
routed. Agent-routed exceptions use the ordinary workflow tier; human-routed
|
||||
ones use `human-gate`.
|
||||
|
||||
**The composed RSI example (OPR.0.4.6.FAC2).** `factory-rsi.yaml` is the
|
||||
single-rig recursive-self-improvement factory MVP: it composes the branch +
|
||||
gate + guard primitives into the inner loop — `plan → implement → qa_check →
|
||||
review → release_prep` — where `qa_check`/`review` branch `failed` back to
|
||||
`implement` (bounded remediation) and `release_prep` (the release-manager
|
||||
prepares artifacts, un-gated) hands off to a human-gated `release_signoff`.
|
||||
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). The remediation loops are sanctioned only by the enforceable
|
||||
`loop_guards.max_hops`; a trip is a WF-5 exception (orchestrator-first via
|
||||
`exception_routing`). It targets the shipped `factory-rsi` launch starter
|
||||
(`specs/rigs/launch/factory-rsi/`) whose seats pin 1:1 to its roles via
|
||||
`preferred_targets` — the v0 hardcode seam, no binding layer.
|
||||
[`workflow-human-destination.ts`](../../../packages/daemon/src/domain/workflow-human-destination.ts)
|
||||
handles human selection: the registered human named by
|
||||
`workspace.operator_seat_name`, or the only registered human when it is unset;
|
||||
any other case fails with `workflow_human_destination_unavailable` (409). The
|
||||
host default dial is the setting `workflow.exception_routing`
|
||||
(`orchestrator` or `human_only`), read per exception. A valid agent route need
|
||||
not have a human fallback configured. If fallback is needed, missing/ambiguous selection is an explicit
|
||||
error, not an invented destination. Failure admission is transactional;
|
||||
overdue detection is handled by boot/keepalive paths. Runtime reconciliation
|
||||
closes recovered overdue occurrences against their own packets, preserving
|
||||
unresolved siblings and unknown provenance.
|
||||
|
||||
## The CLI surface (OPR.0.4.6.WF3)
|
||||
## Project lifecycle graphs
|
||||
|
||||
WF-3 made the CLI the primary human/agent driving surface. Render-side
|
||||
by rule (BR-2): `run`/`watch` consume the shipped SSE endpoints
|
||||
(snapshot-first-then-stream, priorQitemId dedup, reconnect → announced
|
||||
poll fallback, outcome-as-exit-code: 0 completed / 3 failed);
|
||||
`trace`/`list`/`show` human modes are formatted (argo-shape tree, ps-
|
||||
mechanics columns, ATTN markers) while `--json` stays byte-stable;
|
||||
`status` composes the needs-attention rollup CLI-SIDE from the
|
||||
API-carried `instance.deadline` classification (one threshold home —
|
||||
the CLI never recomputes a class). The two daemon additions:
|
||||
`compileLifecycle` delegates to
|
||||
[`compileProjectLifecycle`](../../../packages/daemon/src/domain/project-lifecycle-compiler.ts).
|
||||
`instantiateLifecycle` requires an operation key and compiled input digest.
|
||||
An exact replay retrieves the recorded instance/entry packet; changed input
|
||||
under the same key conflicts. Compilation and an eligible result do not by
|
||||
themselves launch work.
|
||||
|
||||
- **`route`** (`POST /api/workflow/:id/route`, runtime `route()`):
|
||||
close+recreate+rebind in ONE scribe transaction — honest
|
||||
`handed_off_to` closure with provenance, successor recreate (same
|
||||
step; `current_step_id` unchanged; no hop bump — route is not an
|
||||
advance), frontier rebind under the version guard, keepalive
|
||||
re-target in-txn. Advance-authority revocation is STRUCTURAL: the
|
||||
old packet leaves the frontier in the transaction, so a zombie
|
||||
owner's stale `project` hits the shipped `packet_not_on_frontier`
|
||||
409.
|
||||
- **The frontier close-path guard** (`workflow-frontier-guard.ts` →
|
||||
INJECTED into `QueueRepository` at startup; the queue never imports
|
||||
the workflow domain): terminal closure of a live frontier packet
|
||||
from non-workflow verbs rejects `workflow_frontier_packet` with a
|
||||
what/why/fix naming `rig workflow project` / `route`. Workflow
|
||||
writers pass `viaWorkflowVerb`; non-workflow qitems see zero new
|
||||
behavior.
|
||||
`inspectGraph`, `reviseGraph`, and `recoverGraphOperation` in
|
||||
[`workflow-reconciliation.ts`](../../../packages/daemon/src/domain/workflow-reconciliation.ts)
|
||||
separate inspection from explicit graph revision and operation recovery.
|
||||
Inspection reports `current`, `source-only`, `compatible`, `incompatible`,
|
||||
`unavailable` or `unbound`. Only a live, manifest-bound instance whose specs
|
||||
are explicit dependency graphs without `next_hop.on` can be revised; a
|
||||
revision may add steps and change unstarted ones, but may not remove steps,
|
||||
touch a step with completed, live or failed work, drop a required obligation
|
||||
or its order, change workflow-wide fields other than `exception_routing`, or
|
||||
add an unstarted step with no outstanding prerequisite. A changed
|
||||
`exception_routing` applies to future occurrences only; existing obligations
|
||||
keep their owners. Applying needs the inspected version and digest, an
|
||||
operation key, an actor and a reason; the receipt is kept in the instance's
|
||||
`revisionHistory`, and replaying the same key with a different decision is
|
||||
refused with `lifecycle_revision_conflict` (`workflow-reconciliation.ts:12`,
|
||||
`:113`–`223`).
|
||||
`guidance` reads context and current packet information without interpreting
|
||||
an agent's substantive judgment.
|
||||
|
||||
## The exception + human-gate model (OPR.0.4.6.WF5)
|
||||
For lifecycle steps that require a receipt, a `done`/`handoff` projection
|
||||
requires an `evidence_ref`. Typed acceptance gates additionally compare the
|
||||
submitted candidate, allowed verdict, and evidence reference against the
|
||||
step declaration. These are mechanical identity/presence checks; the
|
||||
projector does not read the referenced prose to decide whether the work is
|
||||
good or ready to publish.
|
||||
|
||||
The deterministic engine's exception layer: the happy path stays
|
||||
orchestrator-free and PROVEN so; every exception becomes exactly one
|
||||
durable attention item the moment it exists; the responder resolves it
|
||||
and the flow resumes from where it stopped.
|
||||
## CLI and HTTP surface
|
||||
|
||||
- **The taxonomy** (`workflow-exception.ts`): three closed classes as
|
||||
pure predicates over recorded state — `unmapped_failed` (recorded
|
||||
`status=failed`; a WF-2-mapped `failed` routes to remediation and is
|
||||
NOT an exception), `stuck_overdue` (the WF-1 deadline evaluator's
|
||||
verdict consumed verbatim — the single threshold home), and
|
||||
`human_gate_trip` (WF-2 HUMAN gates; the compiled park IS the item).
|
||||
Handler-role gate-trips are deterministic handoffs, not exceptions —
|
||||
classes (a)/(b) backstop the handler's own step. The occurrence key
|
||||
is the recorded packet id of the episode: re-detections dedupe,
|
||||
resolve+resume closes, a fresh packet is a NEW occurrence.
|
||||
- **The maturity dial** (`workflow-exception-router.ts` + the
|
||||
`exception_routing` spec grammar + the `workflow.exception_routing`
|
||||
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) →
|
||||
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**: 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, 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
|
||||
RE-RESOLVED through the projection resolver (never copied from the
|
||||
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. 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.
|
||||
- **The workflow-aware ▲ band** (`review/compose.ts
|
||||
deriveWorkflowExceptions` + the gatherer source): the missing-item
|
||||
backstop row, the stuck row with evaluator evidence, the
|
||||
frontier-non-open ANOMALY row (detection behind WF-3 FR-6's
|
||||
prevention), and THE AWARENESS ROW — an orchestrator-routed
|
||||
exception renders holder + age in the human band (one identity, two
|
||||
projections, count = 1); human-routed items render nothing there
|
||||
(the ● item is the row). Recomposition clears on state-exit.
|
||||
The [CLI inventory](../cli-reference.md#workflow) lists every registered
|
||||
`rig workflow` subcommand and option. The implementation is
|
||||
[`packages/cli/src/commands/workflow.ts`](../../../packages/cli/src/commands/workflow.ts).
|
||||
The [`workflow` routes](../../../packages/daemon/src/routes/workflow.ts)
|
||||
are mounted under `/api/workflow`:
|
||||
|
||||
## The workflow ↔ rig binding layer (OPR.0.4.6.FAC1)
|
||||
| Method | Suffixes |
|
||||
|---|---|
|
||||
| `POST` | `/validate`, `/compile`, `/instantiate`, `/instantiate-lifecycle`, `/project` |
|
||||
| `GET` | `/list`, `/specs`, `/operations/:key`, `/:instance_id`, `/:instance_id/trace`, `/:instance_id/guidance`, `/:instance_id/revision` |
|
||||
| `POST` | `/:instance_id/route`, `/:instance_id/resume`, `/:instance_id/abort`, `/:instance_id/continue`, `/:instance_id/revision` |
|
||||
| `GET` | `/sse`, `/watch` (same live event stream handler) |
|
||||
|
||||
Run any workflow on any rig that can support it, without editing the
|
||||
spec — the self-driving factory's binding substrate. Three seams:
|
||||
The stream carries the 8 `workflow.*` events (`instantiated`, `step_closed`,
|
||||
`next_qitem_projected`, `completed`, `failed`, `resumed`,
|
||||
`routing_table_changed`, `revised`); it is not a general queue stream or a
|
||||
stored-history replay. Route error mapping distinguishes missing
|
||||
records, authoring errors, state/version conflicts, and internal failures.
|
||||
See the source mapping before assigning a meaning to a nonzero CLI result.
|
||||
|
||||
- **A1 — bind at instantiation** (migration 052,
|
||||
`workflow_instances.bound_rig`): the effective binding =
|
||||
`--rig`/`targetRig` override `?? spec.target.rig ?? null`; the spec
|
||||
field stays a DEFAULT (no routing path reads it — display only). The
|
||||
rig NAME persists (the durable operator-space coordinate); name→id
|
||||
re-resolves fresh at each resolution site — a vanished rig fails loud
|
||||
(`bound_rig_not_found`), never silently. NULL = unbound =
|
||||
byte-identical pre-FAC-1 behavior. **Unknown-rig validation SPLITS by
|
||||
PROVENANCE** (arch ruling 2026-07-07, target-rig zero-regression):
|
||||
an explicit operator `--rig`/`targetRig` is AUTHORITATIVE — unknown →
|
||||
`bound_rig_unknown` HARD-FAIL before any mutation; a spec-default
|
||||
`target.rig` is ADVISORY (authored under the pre-FAC-1 regime where the
|
||||
field was ignored at runtime) — unknown → DEGRADE to unbound + a LOUD
|
||||
`advisories` notice on the instantiate result (surfaced by route + CLI
|
||||
stderr), never a hard-fail. This preserves AC-1 zero-regression for
|
||||
shipped/example specs that declare a descriptive `target.rig` AND route
|
||||
via `preferred_targets` (e.g. `conveyor`): they instantiate exactly as
|
||||
pre-FAC-1. A spec that genuinely needs a bound rig still fails loudly,
|
||||
per-step (entry at instantiate, later role-only steps at projection) —
|
||||
the degrade is to honest per-step failure with a heads-up, never to
|
||||
silence.
|
||||
- **A2 — role→seat capability resolution**
|
||||
(`workflow-role-resolver.ts` pure policy +
|
||||
`workflow-role-context.ts` lazy sync snapshot): TIER 3 inside
|
||||
`resolveDefaultOwner`, activating ONLY when a role declares zero
|
||||
`preferred_targets` AND the instance is bound. Tier order is sacred:
|
||||
explicit owner → gate compile → declared `preferred_targets`
|
||||
(byte-identical, never inventory-filtered) → capability → loud null.
|
||||
The CLOSED fact set: role (`nodes.role`, declared per pod member —
|
||||
seat-side, opt-in) · nodeKind · lifecycleState (`running` only) ·
|
||||
runtime (harness-pin aware) · sync `pendingWorkCount` (pending-only
|
||||
backlog) · the derived canonical coordinate. Async
|
||||
`attachAgentActivity`/tmux probes are structurally absent from the
|
||||
transaction. Managed seats only: adopted seats are excluded LOUDLY
|
||||
(`adopted_seat_not_role_resolvable_v1`). Selection = least backlog,
|
||||
plain-codepoint coordinate tiebreak (`driver10@rig < driver2@rig`).
|
||||
Resolution runs ONCE at step-close (after the frontier + absorption
|
||||
guards — replays perform zero inventory reads) and records as the
|
||||
packet destination; the WF-5 resume is the one re-resolution point,
|
||||
now capability-aware. All SIX owner-resolution sites carry the
|
||||
context: projector next-step, human-gate parked-packet owner,
|
||||
handler-role gate destination (no-targets + bound → capability),
|
||||
runtime entry (live + recorded), the eager instantiate loop
|
||||
(STRUCTURAL zero-role-coverage check only — `bound_rig_role_uncovered`;
|
||||
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)), 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?,
|
||||
seat}` per routing decision.
|
||||
- **A3 — roles bind to SEATS, never occupants**: the ONE string rule —
|
||||
the derived canonical coordinate `{pod}-{member}@{rig}` is both the
|
||||
tiebreak key and the recorded destination, so an agent handover
|
||||
behind the seat never strands the workflow (raw occupant-era session
|
||||
names are never recorded as role-resolution destinations).
|
||||
|
||||
## The member-exists instantiate advisory (OPR.0.4.6.FAC3 — engine bit)
|
||||
|
||||
The queue transport validates the RIG of a destination, never the member
|
||||
(`topologyValidateRig` is rig-exists-only by design — hardening it would
|
||||
gate every queue write and break legitimate non-managed destinations),
|
||||
so a declared `preferred_target` naming a registered rig but a typo'd or
|
||||
stale MEMBER would mint an orphan packet, visible only later as a WF-5
|
||||
stuck exception. FAC-3 catches it at the earliest knowable moment: at
|
||||
instantiate, for every step-referenced role (each step's `actor_role`
|
||||
plus a handler gate's target role — the same reference set the
|
||||
structural coverage check walks), each declared `preferred_target` that
|
||||
parses CANONICAL and names a rig registered on this daemon is probed
|
||||
for member existence (`rigMemberExists` beside `rigDeclaresRole` —
|
||||
sync SQL, the derived canonical coordinate per the FAC-1 Q5 one-string
|
||||
rule, existence at ANY lifecycle state and node kind; liveness stays
|
||||
projection's business). An unknown member yields ONE aggregated
|
||||
advisory per unique target — naming every declaring step/role pair, the
|
||||
consequence (the work will not be claimed; it will surface as a stuck
|
||||
exception), and the fix hint — pushed into the shipped
|
||||
`InstantiateResult.advisories` list (the FAC-1 `target.rig`-degrade
|
||||
surface: one list, now two producers; rendered by the route body + CLI
|
||||
stderr, zero new surface). ADVISORY-NEVER-DENY: instantiate always
|
||||
proceeds. Skips (in order): human-seat refs (classified BEFORE parse,
|
||||
the queue-gate archetype), non-canonical raw/adopted destinations (the
|
||||
inventory cannot vouch for them), unregistered rigs (the transport
|
||||
already rejects those loudly at queue-write — no double advisory).
|
||||
|
||||
## See also
|
||||
|
||||
- `coordination-primitive.md` — PL-004 Phase A; the closure authority the
|
||||
runtime projects against.
|
||||
- `mission-control.md` — PL-005 queue observability + 7-verb contract.
|
||||
- Source roots: `packages/daemon/src/domain/{workflow-projector,
|
||||
workflow-runtime,workflow-instance-store,workflow-spec-cache,
|
||||
workflow-step-trail-log,workflow-validator}.ts`,
|
||||
`packages/daemon/src/domain/policies/workflow-keepalive.ts`,
|
||||
`packages/daemon/src/domain/{workflow-exception,workflow-exception-router,
|
||||
workflow-exception-escalation}.ts`, `packages/daemon/src/routes/workflow.ts`.
|
||||
For authoring examples and project-level boundaries, use
|
||||
[product journey SDLC](../../reference/product-journey-sdlc.md) and
|
||||
[release boundaries](../../reference/release-boundary.md).
|
||||
@@ -9,92 +9,80 @@ applies-when: |
|
||||
defaultRepo / knowledgeRoot), how that block is persisted and resolved into
|
||||
whoami / node-inventory, how per-item target_repo scope is validated, or how
|
||||
the file-backed missions/slices tree is indexed and projected into the
|
||||
Project UI. Author-mode module — no prior architecture.md prose; every
|
||||
load-bearing claim is sourced to file:line at HEAD.
|
||||
Project UI.
|
||||
siblings: [content-surfaces.md, daemon-core.md, ../ui/project-and-for-you.md]
|
||||
prerequisite-reads: [../README.md, daemon-core.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Workspace Primitive — RigSpec.workspace, Migrations 038/039, Missions/Projects/Slices
|
||||
|
||||
The **workspace primitive (PL-007)** is the typed declaration that lets a rig
|
||||
name *where its work lives* — a workspace root, a set of named repos with
|
||||
kinds, an optional default repo, and an optional knowledge root — and have
|
||||
that surface through `whoami` / node-inventory and gate per-item repo scope.
|
||||
Alongside it, a **file-backed missions/slices tree** is indexed read-only and
|
||||
projected into the Project UI's mission / slice surfaces.
|
||||
The **workspace primitive** is the typed declaration that lets a rig name
|
||||
*where its work lives* — a workspace root, a set of named repos with kinds, an
|
||||
optional default repo, and an optional knowledge root — and have that surface
|
||||
through `whoami` / node-inventory and gate per-item repo scope. Alongside it, a
|
||||
**file-backed missions/slices tree** is indexed read-only and projected into
|
||||
the Project UI's mission / slice surfaces.
|
||||
|
||||
> **AUTHOR-FROM-SOURCE module.** No prior `architecture.md` prose exists for
|
||||
> this. Every load-bearing claim carries `> Source: <file:line> @HEAD`;
|
||||
> ambiguity is declared as an OPEN item, never smoothed. Paths are relative to
|
||||
> `packages/daemon/src/` unless prefixed `packages/` or `docs/`.
|
||||
>
|
||||
> Verified at HEAD `7eaf524c` (`git describe` → `v0.3.1-6-g7eaf524c`).
|
||||
> Package version **0.3.1**; HEAD carries 6 unreleased release-0.3.2 commits;
|
||||
> no `v0.3.2` tag (daemon-core.md; slice-00 §1.1).
|
||||
>
|
||||
> **SPLIT NOTE (§10.6).** The honest source-grounded scope of
|
||||
> `workspace-and-content-primitives` (proposed-structure.md §4.9 / D13)
|
||||
> exceeds ~400 lines across two distinct subsystem clusters with distinct
|
||||
> source roots. Per the §10.6 SPLIT DIRECTIVE it is split by subsystem
|
||||
> cluster into this module (workspace primitive + migrations 038/039 +
|
||||
> missions/projects/slices) and the sibling `content-surfaces.md`
|
||||
> (files/markdown/progress/steering). This is a transparent noted deviation
|
||||
> from ratified Q7 "18", a faithful application of the founder's own
|
||||
> ratified Q3 principle ("distinct primitive / distinct source root →
|
||||
> split"); surfaced at the slice-08 review gate, not a mid-execution
|
||||
> interrupt.
|
||||
> Paths are relative to `packages/daemon/src/` unless prefixed `packages/` or
|
||||
> `docs/`. Verified against source at main `e8f0ab340db773392ec8be75b072d1c0f3068a50`. Each count sits beside
|
||||
> the command that produces it; run the commands from the repository root.
|
||||
> The files / markdown / progress / steering surfaces are in the sibling
|
||||
> `content-surfaces.md`.
|
||||
|
||||
## 0. Release attribution (forensic proof — read first)
|
||||
## 0. Release attribution
|
||||
|
||||
Per §10.8, re-verified at HEAD via `git cat-file -e <tag>:<path>`:
|
||||
Checked with `git cat-file -e <tag>:packages/daemon/src/<path>`. The table
|
||||
records when each file first appeared; most have changed since.
|
||||
|
||||
| Subsystem | Release | Forensic proof @HEAD |
|
||||
| Subsystem | First release | Check |
|
||||
|---|---|---|
|
||||
| `domain/workspace/{workspace-resolver,frontmatter-validator,default-workspace-scaffold}.ts` | **≤0.3.0** | `v0.3.0:<path>` → present |
|
||||
| migrations `038_workspace_primitive.ts`, `039_queue_target_repo.ts` | **≤0.3.0** | `v0.3.0:<path>` → present |
|
||||
| `domain/slices/{slice-indexer,slice-detail-projector}.ts` | **≤0.3.0** | `v0.3.0:<path>` → present |
|
||||
| `routes/{workspace,slices,projects}.ts` | **≤0.3.0** | `v0.3.0:<path>` → present |
|
||||
| `domain/workspace/getting-started-narrative.ts` | **0.3.1** | `v0.3.0:` → **ABSENT**; `v0.3.1:` → present |
|
||||
| `routes/missions.ts` | **0.3.1** | `v0.3.0:` → **ABSENT**; `v0.3.1:` → present |
|
||||
| `domain/workspace/{workspace-resolver,frontmatter-validator,default-workspace-scaffold}.ts` | **≤0.3.0** | present at `v0.3.0` |
|
||||
| migrations `038_workspace_primitive.ts`, `039_queue_target_repo.ts` | **≤0.3.0** | present at `v0.3.0` |
|
||||
| `domain/slices/{slice-indexer,slice-detail-projector}.ts` | **≤0.3.0** | present at `v0.3.0` |
|
||||
| `routes/{workspace,slices,projects}.ts` | **≤0.3.0** | present at `v0.3.0` |
|
||||
| `domain/workspace/getting-started-narrative.ts` | **0.3.1** | absent at `v0.3.0`; present at `v0.3.1` |
|
||||
| `routes/missions.ts` | **0.3.1** | absent at `v0.3.0`; present at `v0.3.1` |
|
||||
| `domain/workspace/workspace-doctor.ts` | **0.3.2** | absent at `v0.3.1`; present at `v0.3.2` |
|
||||
|
||||
> Source: §10.8 proof re-run at HEAD `7eaf524c` —
|
||||
> `git cat-file -e v0.3.0:packages/daemon/src/domain/workspace/getting-started-narrative.ts`
|
||||
> and `...routes/missions.ts` both fail ("exists on disk, but not in
|
||||
> 'v0.3.0'"); all other listed paths resolve at `v0.3.0`. The workspace
|
||||
> primitive itself is a **0.3.0** feature (slice-00 0.3.0-GT §1.4, PL-007,
|
||||
> migrations 038/039) — do not back-attribute it to 0.3.1. The
|
||||
> `missions` route + the getting-started narrative scaffold are the
|
||||
> **0.3.1** additions layered on top (slice 12 mission scope; slice 21
|
||||
> onboarding-conveyor).
|
||||
The workspace primitive itself (declaration, resolver, validator, migrations
|
||||
038/039) is a **0.3.0** feature — do not back-attribute it to 0.3.1. The
|
||||
`missions` route and the getting-started narrative are **0.3.1** additions;
|
||||
the workspace doctor is **0.3.2**.
|
||||
|
||||
slice-00 0.3.0-GT §1.4 independently confirms the primitive shipped 0.3.0:
|
||||
workspace-primitive merge `2ce54abc` (PL-007) with migrations
|
||||
`038_workspace_primitive` + `039_queue_target_repo`, "fresh databases apply
|
||||
migrations through `039_queue_target_repo`".
|
||||
`getting-started-narrative.ts` is still in the tree, but nothing imports it:
|
||||
**0** files reference it (`git grep -l 'workspace/getting-started-narrative' -- packages | wc -l`).
|
||||
Workspace initialization no longer seeds a getting-started mission (§3).
|
||||
|
||||
## 1. The typed workspace declaration (PL-007) — 0.3.0
|
||||
## 1. The typed workspace declaration — 0.3.0
|
||||
|
||||
`RigSpec.workspace` is an **optional** typed block. Rigs without it stay valid;
|
||||
`whoami` / node-inventory return a null workspace block in that case.
|
||||
|
||||
> Source: `domain/types.ts:762-770` (`WorkspaceSpec`), `:751-758`
|
||||
> (`WorkspaceRepoSpec`), `:780` (`RigSpec.workspace?`) @HEAD.
|
||||
> Source: `domain/types.ts:1163–1170` (`WorkspaceSpec`), `:1153–1159`
|
||||
> (`WorkspaceRepoSpec`), `:1188` (`RigSpec.workspace?`).
|
||||
|
||||
| Field | Shape | Notes |
|
||||
|---|---|---|
|
||||
| `workspaceRoot` | string | Verbatim from spec |
|
||||
| `repos[]` | `{ name, path, kind }[]` | `path` resolved to absolute at parse time; authors may declare relative to `workspaceRoot` in YAML |
|
||||
| `repos[]` | `{ name, path, kind }[]` | `path` resolved to absolute at parse time (`domain/rigspec-schema.ts:361–363`); authors may declare it relative to `workspaceRoot` in YAML |
|
||||
| `defaultRepo?` | string | active repo when no env override / cwd match |
|
||||
| `knowledgeRoot?` | string | treated as `kind=knowledge` when surfaced |
|
||||
|
||||
`WorkspaceKind` is a closed 5-member union: `user`, `project`, `knowledge`,
|
||||
`lab`, `delivery`.
|
||||
`WorkspaceKind` is a closed union of **5** members
|
||||
(`grep '^export const WORKSPACE_KINDS' packages/daemon/src/domain/types.ts | grep -o '"[a-z]*"' | wc -l`):
|
||||
`user`, `project`, `knowledge`, `lab`, `delivery`.
|
||||
|
||||
> Source: `domain/types.ts:748-749` (`WORKSPACE_KINDS` /
|
||||
> `WorkspaceKind`) @HEAD.
|
||||
> Source: `domain/types.ts:1149–1150` (`WORKSPACE_KINDS` / `WorkspaceKind`).
|
||||
|
||||
In YAML the block is spelled `workspace: { workspace_root, repos: [{ name,
|
||||
path, kind }], default_repo?, knowledge_root? }`. Validation requires a
|
||||
non-empty `workspace_root`, a `repos` array whose names are unique and whose
|
||||
`kind` is one of the five kinds, a `default_repo` that names a declared repo,
|
||||
and a non-empty `knowledge_root` when present.
|
||||
|
||||
> Source: `domain/rigspec-schema.ts:288–347`.
|
||||
|
||||
### 1.1 Persistence — migration 038 + RigRepository
|
||||
|
||||
@@ -102,9 +90,9 @@ Migration **038** adds `workspace_json TEXT` to the `rigs` table. It holds the
|
||||
typed `RigSpec.workspace` block as JSON when a rig declares one; NULL for rigs
|
||||
without a workspace block.
|
||||
|
||||
> Source: `db/migrations/038_workspace_primitive.ts:16-21`
|
||||
> (`ALTER TABLE rigs ADD COLUMN workspace_json TEXT`); doc-comment
|
||||
> `:3-14` @HEAD.
|
||||
> Source: `db/migrations/038_workspace_primitive.ts:16–21`
|
||||
> (`ALTER TABLE rigs ADD COLUMN workspace_json TEXT` at `:19`); doc comment
|
||||
> `:3–15`.
|
||||
|
||||
`RigRepository.setRigWorkspace(rigId, workspace)` persists it (UPDATE
|
||||
`workspace_json` + `updated_at`); `getRigWorkspace(rigId)` reads it back,
|
||||
@@ -113,302 +101,506 @@ are **defensive no-ops** when the column is absent (`hasRigColumn` probe) —
|
||||
older test fixtures that bypass the canonical migration list don't have the
|
||||
column; the setter's contract is "best-effort persistence".
|
||||
|
||||
> Source: `domain/rig-repository.ts:98-105` (setter, column probe
|
||||
> L99), `:106-117` (getter; parse-fail → null L114-116) @HEAD.
|
||||
> Source: `domain/rig-repository.ts:184–189` (setter; column probe `:185`),
|
||||
> `:192–202` (getter; probe `:193`; parse failure → null `:199–201`);
|
||||
> `hasRigColumn` `:347`.
|
||||
|
||||
The instantiator persists the block at rig-create time **only when declared**:
|
||||
The block is written only when declared. **3** call sites write it
|
||||
(`git grep -n 'setRigWorkspace(' -- packages/daemon/src | grep -v 'rig-repository.ts' | wc -l`):
|
||||
the instantiator's two rig-persist paths, `materializeValidatedSpec` and
|
||||
`instantiateOnce`, each guarded by `if (rigSpec.workspace)`; and the
|
||||
workspace-only import route `POST /api/rigs/import/workspace`
|
||||
(`rig import --workspace-only --target-rig <rigId>`), which validates and
|
||||
normalizes the block and writes it only when it differs from the stored one.
|
||||
|
||||
> Source: `domain/rigspec-instantiator.ts:653-655` (`if
|
||||
> (rigSpec.workspace) … setRigWorkspace(rigId, rigSpec.workspace)`) @HEAD.
|
||||
> Source: `domain/rigspec-instantiator.ts:619` (`materializeValidatedSpec`),
|
||||
> `:655–657`; `:1260` (`instantiateOnce`), `:1349–1351`;
|
||||
> `routes/rigspec.ts:134–173` (validation `:158`; compare-and-write
|
||||
> `:168–170`), mounted `server.ts:734`; CLI
|
||||
> `packages/cli/src/commands/import.ts:82`.
|
||||
|
||||
### 1.2 Runtime resolution — workspace-resolver
|
||||
|
||||
`resolveWorkspaceContext({ spec, cwd, envOverride })` (consumed by
|
||||
`whoami-service`) returns the `WhoamiWorkspaceBlock` or `null` when no spec.
|
||||
`activeRepo` resolution: an `envOverride` (non-empty, trimmed) wins
|
||||
**verbatim** — even an unknown repo name is honored, because operators set
|
||||
`OPENRIG_TARGET_REPO` consciously (PL-007 PRD § Item 3); otherwise
|
||||
`defaultRepo` is used **only if it names a declared repo**. `knowledgeKind`
|
||||
is `"knowledge"` when `knowledgeRoot` is declared, else `null`.
|
||||
`activeRepo` resolution: a non-blank `envOverride` wins **verbatim** — even an
|
||||
unknown repo name is honored, because operators set `OPENRIG_TARGET_REPO`
|
||||
consciously (the comment at `workspace-resolver.ts:35–36`); otherwise `defaultRepo` is used **only
|
||||
if it names a declared repo**. `knowledgeKind` is `"knowledge"` when
|
||||
`knowledgeRoot` is declared, else `null`. The resolver accepts `cwd` but does
|
||||
not use it (`workspace-resolver.ts:31`), so `rig whoami` reports the env
|
||||
override or the declared `defaultRepo`, never the repo containing the seat's
|
||||
working directory; node-inventory's per-node `activeRepo` (below) is the one
|
||||
that follows cwd.
|
||||
|
||||
> Source: `domain/workspace/workspace-resolver.ts:26-52` (resolver;
|
||||
> env-override-verbatim L34-43, including the "honored verbatim"
|
||||
> comment L35-36) @HEAD.
|
||||
> Source: `domain/workspace/workspace-resolver.ts:26–52` (resolver; null
|
||||
> without a spec `:32`; env override `:39–40`; declared `defaultRepo`
|
||||
> `:41–42`; `knowledgeKind` `:50`).
|
||||
|
||||
`whoami-service` reads the persisted spec and resolves with the query's
|
||||
`targetRepoOverride`, falling back to `process.env["OPENRIG_TARGET_REPO"]`:
|
||||
`targetRepoOverride`, falling back to the daemon's own
|
||||
`process.env["OPENRIG_TARGET_REPO"]`. `rig whoami` forwards the caller's
|
||||
`OPENRIG_TARGET_REPO` as `?targetRepo=`, which the route passes on as
|
||||
`targetRepoOverride`.
|
||||
|
||||
> Source: `domain/whoami-service.ts:319-324` (`getRigWorkspace` +
|
||||
> `resolveWorkspaceContext`, env fallback L323); `whoami` returns
|
||||
> `workspace` in its payload `:335` @HEAD.
|
||||
> Source: `domain/whoami-service.ts:333–338` (`getRigWorkspace` +
|
||||
> `resolveWorkspaceContext`, env fallback `:337`); `whoami` returns
|
||||
> `workspace` in its payload `:350`; `routes/whoami.ts:29`;
|
||||
> `packages/cli/src/commands/whoami.ts:281–282`.
|
||||
|
||||
`resolveNodeWorkspace({ spec, cwd })` (consumed by `node-inventory`) derives a
|
||||
per-node `NodeWorkspaceInfo` by walking the node's `cwd` up the directory
|
||||
tree for the **longest-prefix repo path** that contains it; falls back to
|
||||
`knowledge` when cwd is under `knowledgeRoot`, then to the rig's
|
||||
`defaultRepo` when cwd doesn't resolve. Containment uses a `path.relative`
|
||||
boundary check (not a string `startsWith`) so `/foo/bar` does not match
|
||||
`/foo/bar-other`.
|
||||
per-node `NodeWorkspaceInfo` by picking the **longest declared repo path that
|
||||
contains** the node's `cwd`. When no repo contains it, `kind` falls back to
|
||||
`knowledge` if cwd is under `knowledgeRoot`; `activeRepo` then falls back to
|
||||
the rig's `defaultRepo` (taking that repo's kind if `kind` is still unset).
|
||||
Containment uses a `path.relative` boundary check (not a string `startsWith`)
|
||||
so `/foo/bar` does not match `/foo/bar-other`. Node-inventory reads
|
||||
`workspace_json` with its own queries rather than through `RigRepository`.
|
||||
|
||||
> Source: `domain/workspace/workspace-resolver.ts:57-95`
|
||||
> (longest-prefix L67-73; knowledge fallback L77-79; default-repo
|
||||
> fallback L82-88), `isInside` `:97-103`;
|
||||
> `domain/node-inventory.ts:397` (`workspace: resolveNodeWorkspace(...)`),
|
||||
> `:434-437` (`NodeWorkspaceInfo`) @HEAD.
|
||||
> Source: `domain/workspace/workspace-resolver.ts:57–95` (longest match
|
||||
> `:67–73`; knowledge fallback `:77–79`; default-repo fallback `:82–88`),
|
||||
> `isInside` `:97–103`; `domain/node-inventory.ts:705`
|
||||
> (`workspace: resolveNodeWorkspace(...)`), readers `:790–799`
|
||||
> (`readRigWorkspaceJson`) and `:716–726` (`readAllRigWorkspaceJson`,
|
||||
> batched); `NodeWorkspaceInfo` `domain/types.ts:795–799`.
|
||||
|
||||
### 1.3 Per-item repo scope — migration 039 + queue validation
|
||||
|
||||
Migration **039** adds `target_repo TEXT` to `queue_items` plus
|
||||
`idx_queue_items_target_repo`. It carries the per-item typed repo scope when
|
||||
an operator passes `--target-repo <name>`; NULL when the qitem is
|
||||
unambiguous against the rig's `default_repo` or no workspace is declared.
|
||||
Mission Control views surface the field for cross-rig handoff clarity.
|
||||
`idx_queue_items_target_repo`. Per the migration's comment it carries the
|
||||
per-item typed repo scope when an operator passes `--target-repo <name>`; it
|
||||
is NULL when the item is unambiguous against the rig's `default_repo` or no
|
||||
workspace is declared, and Mission Control views surface the field for
|
||||
cross-rig handoff clarity. At M nothing in the daemon's Mission Control code
|
||||
reads it; the UI shows it as "Target repo" in the queue item viewer's full
|
||||
detail (`packages/ui/src/components/drawer-viewers/QueueItemViewer.tsx:157`).
|
||||
|
||||
> Source: `db/migrations/039_queue_target_repo.ts:16-22`
|
||||
> (`ALTER TABLE queue_items ADD COLUMN target_repo TEXT` +
|
||||
> `CREATE INDEX … idx_queue_items_target_repo`); doc-comment
|
||||
> `:3-14` @HEAD.
|
||||
> Source: `db/migrations/039_queue_target_repo.ts:16–22`
|
||||
> (`ALTER TABLE queue_items ADD COLUMN target_repo TEXT` at `:19`;
|
||||
> `CREATE INDEX … idx_queue_items_target_repo` at `039_queue_target_repo.ts:20`); doc comment
|
||||
> `:3–15`; CLI flag `packages/cli/src/commands/queue.ts:461`.
|
||||
|
||||
The queue route validates `target_repo` at the route layer against the
|
||||
**source rig's** `RigSpec.workspace.repos[]`: it parses the rig name out of
|
||||
`source_session` (`<member>@<rig>`), looks up the rig, reads
|
||||
`getRigWorkspace`, and rejects an unknown repo with `unknown_target_repo` +
|
||||
the known-repo list. It **fails open** (`{ ok: true }`) when there is no
|
||||
rigRepo, no parseable rig, the rig is unknown, or no workspace is declared —
|
||||
validation only bites when a workspace actively declares repos.
|
||||
`validateTargetRepo` checks an explicit `targetRepo` against the **source
|
||||
rig's** `RigSpec.workspace.repos[]`: it parses the source session with
|
||||
`parseSessionName`, looks the rig up by name, reads `getRigWorkspace`, and
|
||||
rejects an unknown repo with 400 `unknown_target_repo` plus the `knownRepos`
|
||||
list. It **fails open** (`{ ok: true }`) when there is no rigRepo, the session
|
||||
is not a canonical `<member>@<rig>` name, the rig is unknown, or no workspace
|
||||
is declared — validation only bites when a workspace actively declares repos.
|
||||
|
||||
> Source: `routes/queue.ts:49-57` (fail-open guards), `:55`
|
||||
> (`getRigWorkspace`), `:58-66` (`unknown_target_repo` +
|
||||
> `knownRepos`) @HEAD.
|
||||
It runs on **3** routes (`grep -c 'validateTargetRepo(c,' packages/daemon/src/routes/queue.ts`):
|
||||
`POST /create` (through the shared `createInputRefusal` check, which also
|
||||
requires `destinationSession` and `body`), `POST /:qitemId/handoff` and
|
||||
`POST /:qitemId/handoff-and-complete`, each before any cross-host forwarding,
|
||||
because the source rig's workspace lives on the source host.
|
||||
`createInputRefusal` also runs in the in-process create that finishes a
|
||||
handoff an earlier self-forward started (a `hostId` equal to this daemon's own
|
||||
id). A handoff that inherits the source row's `targetRepo` without an override
|
||||
is not re-validated.
|
||||
|
||||
## 2. Workspace HTTP route — frontmatter validator (0.3.0)
|
||||
`--target-repo` is accepted by `rig queue create`, `handoff` and
|
||||
`handoff-and-complete`. `rig queue list --target-repo <name>`
|
||||
(`GET /api/queue/list?targetRepo=`) filters rows by exact `target_repo`
|
||||
match; the filter is skipped when the column is absent.
|
||||
|
||||
`workspaceRoutes()` (mounted `server.ts:491` as `/api/workspace`) exposes a
|
||||
**single read-only endpoint** at v0:
|
||||
> Source: `routes/queue.ts:96–123` (fail-open guards `:102`, `:106`, `:109`,
|
||||
> `:112`; `getRigWorkspace` `:111`; `unknown_target_repo` + `knownRepos`
|
||||
> `:114–120`); `createInputRefusal` `:128–141` (call `:136`), used by
|
||||
> `/create` `:490` and the self-forward create `:436`; handoff calls `:679`,
|
||||
> `:758` in handlers `:650`, `:732` (`/create` handler `:450`); ordering note
|
||||
> `:482–489`; inherited value `:676–677`, `domain/queue-repository.ts:1691`,
|
||||
> `:1868`; list filter `routes/queue.ts:906`,
|
||||
> `domain/queue-repository.ts:3236–3238`, CLI
|
||||
> `packages/cli/src/commands/queue.ts:1143`; mounted `server.ts:784`.
|
||||
|
||||
`POST /api/workspace/validate` — body `{ root, workspaceKind?, recursive?,
|
||||
requireFrontmatter?, maxFiles? }`; returns a `FrontmatterValidationReport`.
|
||||
`root` is required (400 `root_required`); an out-of-vocabulary
|
||||
`workspaceKind` is rejected 400 `invalid_workspace_kind`; validator throws
|
||||
→ 500 `validate_failed`. **No filesystem mutation.**
|
||||
## 2. Workspace HTTP route — frontmatter validator and doctor
|
||||
|
||||
> Source: `routes/workspace.ts:23-65` (handler; root-required L35-37;
|
||||
> kind-enum L39-47; 500 L59-62); mounted `server.ts:491` @HEAD.
|
||||
`workspaceRoutes()` (mounted `server.ts:785` as `/api/workspace`) exposes **2**
|
||||
endpoints (`grep -c -E 'app\.(get|post)\(' packages/daemon/src/routes/workspace.ts`).
|
||||
**Neither mutates the filesystem.**
|
||||
|
||||
`validateWorkspaceFrontmatter()` walks a root, parses each `.md` file's
|
||||
YAML frontmatter (delimited `---` on the first line), and emits a structured
|
||||
gap report — **advisory only, never modifies files**. Gap kinds:
|
||||
- `POST /api/workspace/validate` — body `{ root, workspaceKind?, recursive?,
|
||||
requireFrontmatter?, maxFiles? }`; returns a `FrontmatterValidationReport`.
|
||||
`root` is required (400 `root_required`); an out-of-vocabulary
|
||||
`workspaceKind` is rejected 400 `invalid_workspace_kind`; validator throws →
|
||||
500 `validate_failed`.
|
||||
- `POST /api/workspace/doctor` — runs `runWorkspaceDoctor` against the
|
||||
daemon's resolved `workspace.root`, or a `workspaceRoot` given in the body,
|
||||
and returns its report. 503 `settings_unavailable` without a settings
|
||||
store; 500 `doctor_failed` when it throws. The doctor runs **8** checks in
|
||||
order (root reachable, missions folder, file allowlist, daemon points at
|
||||
this workspace, config changed since start, optional slice docs, mission
|
||||
notes, SDLC convention sections), each `ok`/`warn`/`fail` with a fix hint,
|
||||
plus a summary count. With a body `workspaceRoot`, missions are checked at
|
||||
`<workspaceRoot>/missions`. (The orchestrator's own doc comment still says
|
||||
7.)
|
||||
|
||||
> Source: `routes/workspace.ts:16` (no mutation), `:37–74` (validate;
|
||||
> root-required `:46–48`; kind check `:50–58`; 500 `:72`), `:85–160`
|
||||
> (doctor; 503 `:87`; missions root from the body `:115–118`;
|
||||
> `runWorkspaceDoctor` call `:145`; 500 `:158`);
|
||||
> `domain/workspace/workspace-doctor.ts:643–685` (the 8 checks; comment
|
||||
> `:636`); mounted `server.ts:785`.
|
||||
|
||||
`validateWorkspaceFrontmatter()` walks a root, parses each `.md` file's YAML
|
||||
frontmatter (delimited `---` on the first line), and emits a structured gap
|
||||
report — **advisory only, never modifies files**. There are **4** gap kinds
|
||||
(`sed -n '/^export type FrontmatterGapKind/,/;$/p' packages/daemon/src/domain/workspace/frontmatter-validator.ts | grep -c '^ | '`):
|
||||
`missing-required-field`, `unrecognized-status-value`, `parse-error`,
|
||||
`missing-frontmatter`. Per-kind required fields: `user`/`project` →
|
||||
`["doc"]`; `knowledge`/`lab`/`delivery` → `["doc","status","created","owner"]`.
|
||||
Valid `status` enum: `active|draft|archived|superseded`. Default behavior
|
||||
skips files without `---` silently (informal notes) unless
|
||||
`requireFrontmatter`; recurses by default; skips `node_modules` / `.git` /
|
||||
`.worktrees` / `dist` / `build`; hard cap `maxFiles` default 10000.
|
||||
`missing-frontmatter`. Per-kind required fields (checked only when a kind is
|
||||
given): `user`/`project` → `["doc"]`; `knowledge`/`lab`/`delivery` →
|
||||
`["doc","status","created","owner"]`. The `status` enum has **4** values
|
||||
(`grep '^const VALID_STATUS_VALUES' packages/daemon/src/domain/workspace/frontmatter-validator.ts | grep -o '"[a-z]*"' | wc -l`):
|
||||
`active|draft|archived|superseded`. Default behavior skips files without
|
||||
`---` silently (informal notes) unless `requireFrontmatter`; recurses by
|
||||
default; skips `node_modules` / `.git` / `.worktrees` / `dist` / `build`;
|
||||
hard cap `maxFiles` default 10000.
|
||||
|
||||
> Source: `domain/workspace/frontmatter-validator.ts:53` (status enum),
|
||||
> `:56-62` (per-kind required), `:23-27` (gap kinds), `:90-122` (walk;
|
||||
> skip-dirs L105-111), `:160-172` (missing-frontmatter behavior),
|
||||
> `:201-226` (required + status check) @HEAD.
|
||||
> Source: `domain/workspace/frontmatter-validator.ts:12` (advisory), `:23–27`
|
||||
> (gap kinds), `:53` (status enum), `:56–62` (per-kind required), `:81–83`
|
||||
> (defaults), `:90–121` (walk; skip-dirs `:105–111`), `:160–172`
|
||||
> (missing-frontmatter behavior), `:201–226` (required + status check), `:239`
|
||||
> (opening delimiter).
|
||||
|
||||
CLI surface: `rig workspace validate [root]` — v0 is intentionally narrow
|
||||
(`validate` only); future versions add typed-kind authoring on the same
|
||||
walker.
|
||||
CLI surface: `rig workspace validate [root]` and `rig workspace doctor`.
|
||||
|
||||
> Source: `docs/as-built/cli-reference.md:931-941` @HEAD.
|
||||
> Source: `packages/cli/src/commands/workspace.ts:86` (`validate`), `:146`
|
||||
> (`doctor`); `docs/as-built/cli-reference.md#workspace`.
|
||||
|
||||
## 3. Default project-workspace scaffold
|
||||
|
||||
`workspaceScaffoldDirs()` / `workspaceScaffoldFiles()` produce the
|
||||
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.
|
||||
`workspaceScaffoldDirs()` / `workspaceScaffoldFiles()` produce the repo-ready
|
||||
default workspace (`~/.openrig/workspace/`, the default `workspace.root`, or
|
||||
`--root`). It emits exactly **2** directories
|
||||
(`grep '^const WORKSPACE_DIRS' packages/daemon/src/domain/workspace/default-workspace-scaffold.ts | grep -o '"[a-z]*"' | wc -l`):
|
||||
`missions/`, `exhaust/`; and **4** files
|
||||
(`sed -n '/^export function workspaceScaffoldFiles/,/^}/p' packages/daemon/src/domain/workspace/default-workspace-scaffold.ts | grep -c 'relPath: "'`):
|
||||
`SPEC.md`, `project.yaml`, `workspace.yaml`, `.gitignore`. The catalog
|
||||
(`workspace.yaml`) points its single `default` project at `.`; `project.yaml`
|
||||
names `SPEC.md` as project intent, roots mission discovery at `missions`, and
|
||||
exposes empty `install.context` / `install.skills` selectors. The ignore file
|
||||
excludes `exhaust/` and local `.openrig/` projection state while keeping
|
||||
authored project context versionable.
|
||||
|
||||
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.
|
||||
`ensureDefaultWorkspace()` applies the scaffold additively: it creates only
|
||||
missing paths and never deletes or overwrites an existing file. It checks
|
||||
every managed path first; if one exists with the wrong kind (a file where a
|
||||
directory belongs, or the reverse) it writes nothing and returns `ok: false`
|
||||
with the conflicts. Its callers are the shared instance initializer
|
||||
`ensureOpenRigInstance()`, which both CLI daemon start and direct daemon start
|
||||
run (so the scaffold is reconciled on every start); `rig config
|
||||
init-workspace`; and `POST /api/config/init-workspace` (409
|
||||
`init_workspace_conflict`). The retained `--force` spelling is a
|
||||
compatibility no-op. The CLI command re-exports the daemon's owner from
|
||||
`@openrig/daemon/instance-initialization` rather than keeping its own copy,
|
||||
and a parity test pins the CLI and daemon layouts as byte-identical. 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.
|
||||
|
||||
> 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.
|
||||
> Source: `domain/workspace/default-workspace-scaffold.ts:7` (dirs),
|
||||
> `:20–30` (`project.yaml`), `:32–36` (`workspace.yaml`), `:38–41`
|
||||
> (`.gitignore`), `:43–54` (`workspaceScaffoldDirs`,
|
||||
> `workspaceScaffoldFiles`), `:100–153` (`ensureDefaultWorkspace`; conflicts
|
||||
> checked before writes `:117–135`; write-if-missing `:140`);
|
||||
> `domain/instance-initialization.ts:39` (`ensureOpenRigInstance`; default
|
||||
> workspace root `:44`; dry-run plan `:83`; apply `:103`); default
|
||||
> `workspace.root` `domain/user-settings/settings-store.ts:28–31`; daemon
|
||||
> `index.ts:250`; CLI `packages/cli/src/daemon-lifecycle.ts:658`;
|
||||
> `packages/cli/src/commands/config-init-workspace.ts:10` (import), `:13`
|
||||
> (re-export), `:28` (`--force`), `:56–67` (`runInitWorkspace`);
|
||||
> `routes/config.ts:56–65`, mounted `server.ts:772`; `rig scope`
|
||||
> `packages/cli/src/commands/scope.ts:2071`; parity
|
||||
> `packages/daemon/test/getting-started-narrative-parity.test.ts:15–25`,
|
||||
> retired entries `:43–57`.
|
||||
|
||||
### 3.1 The workspace catalog and project selection
|
||||
|
||||
`workspace.yaml` at `workspace.catalog_path` (default
|
||||
`<workspace.root>/workspace.yaml`) lists `projects[]`, each with a string `id`,
|
||||
a `root` relative to the catalog file, and an optional `rigs` list naming the
|
||||
rigs that work in that project. With one entry, that project is selected
|
||||
implicitly. With several, `rig context work-install` and the daemon's
|
||||
operating posture use an explicit `--project` first, then the calling rig's
|
||||
`rigs` association, then the unique deepest project root that contains the
|
||||
working directory, then the only entry no rig claims; otherwise they stop with
|
||||
`project_required` and the candidate ids. Duplicate ids are refused
|
||||
(`project_identity_ambiguous`), and a root that does not exist gives
|
||||
`project_root_missing`.
|
||||
|
||||
Installing a bundle that carries a project places it at
|
||||
`<workspace.projects_root>/<id>/` (default `<workspace.root>/projects`) and
|
||||
records it in the catalog before the rig's seats launch: a new entry is
|
||||
appended as text, or the rig is added to an entry's `rigs` list. Existing
|
||||
catalog bytes are never rewritten; a conflict writes nothing for that part and
|
||||
reports the exact line to add.
|
||||
|
||||
Daemon reads accept `?project=<id>`, resolved by `selectedProject()`: the id
|
||||
must be in the catalog (or be the single uncatalogued workspace project, whose
|
||||
id comes from its `project.yaml` or defaults to `workspace`), the project's
|
||||
`project.yaml` `missions.root` must stay inside the project, and a supplied
|
||||
`?projectRoot` must still match. Every failure answers 409 with its code.
|
||||
`GET /api/scopes/projects` lists the projects with their missions roots.
|
||||
|
||||
> Source: `domain/workspace/project-catalog.ts:16–25` (`readProjectCatalog`),
|
||||
> `:26–37` (`selectCatalogProject`), `:66–79` (`inferCatalogProject`, steps
|
||||
> 3–5); `domain/user-settings/settings-store.ts:489–490`
|
||||
> (`workspace.projects_root`, `workspace.catalog_path`);
|
||||
> `domain/workspace/project-registration.ts:6–29` (rules), `:36–37`
|
||||
> (projects root), `:181` (`registerBundleProject`), called from
|
||||
> `domain/bundle-content-routing.ts:184`; `domain/workspace/project-read.ts:44–60`
|
||||
> (`projectEntry`), `:61–69` (`listProjects`), `:70–81` (`selectedProject`),
|
||||
> `:83–84` (409); `routes/scopes.ts:34`, mounted `server.ts:820`.
|
||||
|
||||
## 4. File-backed missions / slices tree
|
||||
|
||||
### 4.1 SliceIndexer (Slice Story View v0) — 0.3.0
|
||||
### 4.1 SliceIndexer — 0.3.0
|
||||
|
||||
`SliceIndexer` reads slice folders from a configured filesystem root. The
|
||||
**default workspace contract** is
|
||||
`workspace/missions/<mission>/slices/<slice>`; explicitly configured flat
|
||||
roots (`workspace/slices/<slice>`) remain supported for compatibility.
|
||||
`SliceIndexer` reads slice folders from configured filesystem roots. The
|
||||
**default workspace contract** is `workspace/missions/<mission>/slices/<slice>`;
|
||||
flat roots (`workspace/slices/<slice>`) remain supported for compatibility.
|
||||
A nested `slices/` child folder makes the parent a mission
|
||||
(`missionId = <mission-folder>`); a bare folder is a flat slice
|
||||
(`missionId = null`). NO new SQLite migration, NO new event type: read-only
|
||||
projection over existing tables (`queue_items`, `queue_transitions`,
|
||||
`mission_control_actions`) + dogfood-evidence directories. Time-bounded
|
||||
listing + detail caches (`invalidate()` drops both). `isReady()` is true
|
||||
when any configured slice root exists on disk.
|
||||
(`missionId = null`). Startup builds the indexer on `workspace.slices_root`
|
||||
(default `<workspace.root>/missions`) and adds `<workspace.root>/missions` and
|
||||
`<workspace.root>/slices` as extra roots. NO new SQLite migration, NO new event
|
||||
type: read-only projection over existing tables (`queue_items`,
|
||||
`queue_transitions`, `mission_control_actions`) + dogfood-evidence
|
||||
directories. Listing, detail and mission-status caches are time-bounded (60 s
|
||||
default); `invalidate()` drops all three plus the queue-membership index.
|
||||
Besides the TTL, every unscoped list or detail read first compares a basis
|
||||
built from each mission's `mission.yaml` text and `slices/` listing and drops
|
||||
the caches when it changes. When the indexer is ready, startup also watches the
|
||||
workspace and invalidates the indexer when proof sources change, emitting
|
||||
`proof.sources_changed`.
|
||||
`isReady()` is true when any configured slice root exists on disk. A set
|
||||
`OPENRIG_SLICES_ROOT` still takes precedence over `workspace.slices_root`.
|
||||
|
||||
> Source: `domain/slices/slice-indexer.ts:1-14` (contract + no-new-state),
|
||||
> `:174-176` (`isReady`), `:179-182` (`invalidate`), `:212-256`
|
||||
> (`readSliceLocations`; nested-vs-flat L233-253), `:62-89`
|
||||
> (`SliceListEntry` shape: `missionId` / `slicePath` / `qitemIds` /
|
||||
> `proofPacket`) @HEAD.
|
||||
Slices are keyed by folder name alone: when two missions (or two roots) hold a
|
||||
slice with the same name, only the first one walked is indexed, and
|
||||
`/api/slices/:name` and `/project/slice/$sliceId` address it by that bare
|
||||
name. The project-scoped read (`?project=…&mission=…`, §4.3) is the one path
|
||||
that selects by mission. A slice's `qitemIds` come first from rows tagged
|
||||
`slice:<id>`, then from the legacy match on the rail item or slice name; in a
|
||||
project-scoped indexer only rows tagged `project:<id>` count, so untagged
|
||||
historical rows are not attributed to a selected project.
|
||||
|
||||
### 4.2 SliceDetailProjector (Slice Story View v0 + v1) — 0.3.0
|
||||
> Source: `domain/slices/slice-indexer.ts:2–15` (contract + no new state),
|
||||
> `:238` (TTL), `:297–299` (`isReady`), `:331–336` (`invalidate`),
|
||||
> `:457–472` (`reconcileComposition`), `:476–521` (`readSliceLocations`;
|
||||
> first name wins `:478–483`; nested-vs-flat `:495–517`), `:61–88`
|
||||
> (`SliceRecord`: `missionId` `:65`, `slicePath` `:67`, `qitemIds` `:77`,
|
||||
> `proofPacket` `:81`), `:90–111` (`SliceListEntry`, which carries
|
||||
> `qitemCount` / `hasProofPacket` instead), `:865–875` and `:936–946` (queue
|
||||
> membership; project filter `:870`, `:942`); `domain/workspace/project-catalog.ts:132–138`
|
||||
> (`belongsToProject`); startup `startup.ts:1705`, `:1718` (`OPENRIG_SLICES_ROOT`
|
||||
> first), `:1725–1730`, `:1743–1748`, proof-source watch `:1762–1765`
|
||||
> (`domain/proof/source-watch.ts:16`); default
|
||||
> `domain/user-settings/settings-store.ts:486`.
|
||||
|
||||
### 4.2 SliceDetailProjector — 0.3.0
|
||||
|
||||
Given a `SliceRecord`, the projector assembles the full per-slice payload
|
||||
across six tabs (Story, Acceptance, Decisions, Docs, Tests/Verification,
|
||||
Topology). Read-only; composes already-shipped tables +
|
||||
Topology) — **6** tab keys in `SliceDetailPayload`
|
||||
(`sed -n '/^export interface SliceDetailPayload/,/^}/p' packages/daemon/src/domain/slices/slice-detail-projector.ts | grep -c -E '^ (story|acceptance|decisions|docs|tests|topology):'`),
|
||||
plus a `readiness` field. Read-only; composes already-shipped tables +
|
||||
`workflow_specs`/`workflow_instances`/`workflow_step_trails` + slice docs on
|
||||
disk + dogfood-evidence. **v1 removed the v0 hardcoded legacy phase enum**
|
||||
disk + dogfood-evidence. **v1 removed the v0 hardcoded legacy phase taxonomy**
|
||||
(`discovery`/`product-lab`/`delivery`/…): `StoryEvent.phase` is now an
|
||||
open-ended string-or-null — the spec-defined `step.id` when bound to a
|
||||
`workflow_instance`, else `null` (UI groups under "Untagged"). When no
|
||||
workflow runtime is constructed the projector silently degrades to v0
|
||||
behavior (`workflowBinding=null`, `specGraph=null`).
|
||||
behavior (`workflowBinding`, `specGraph`, `phaseDefinitions` and
|
||||
`currentStep` all `null`).
|
||||
|
||||
> Source: `domain/slices/slice-detail-projector.ts:1-21` (six-tab
|
||||
> contract + v1 enrichment), `:18-21` (v0 phase enum REMOVED at v1);
|
||||
> startup degrade-path `startup.ts:1063-1076` (projector built with
|
||||
> `workflowRuntime?.specCache`; comment L1064-1071) @HEAD.
|
||||
> Source: `domain/slices/slice-detail-projector.ts:1–20` (six-tab contract +
|
||||
> v1 enrichment), `:16–20` (v0 phase taxonomy removed), `:64`
|
||||
> (`StoryEvent.phase`), `:177–203` (`SliceDetailPayload`), `:205–213`
|
||||
> (optional `workflowSpecCache`); startup degrade path
|
||||
> `startup.ts:1749–1760` (comment `:1749–1755`;
|
||||
> `workflowSpecCache: workflowRuntime?.specCache` `:1759`).
|
||||
|
||||
### 4.3 Slices routes — 0.3.0
|
||||
|
||||
`slicesRoutes()` (mounted `server.ts:501` as `/api/slices`):
|
||||
`GET /` (filter `all|active|done|blocked`, default `all`; `?refresh=1`
|
||||
invalidates; optional `?boundToWorkflow=<name>:<version>` lens narrows to
|
||||
slices bound to a workflow instance), `POST /refresh` (drops both indexer
|
||||
caches — no daemon restart), `GET /:name/proof-asset/*` (path-traversal
|
||||
guarded; immutable 1-day cache), `GET /:name/doc/*` (markdown for the Docs
|
||||
tab; traversal guarded), `GET /:name` (full per-tab payload). 503
|
||||
`slices_indexer_unavailable` when unwired; 503 `slices_root_not_configured`
|
||||
+ setup hint when not ready. **Route-order discipline:** literal `/` /
|
||||
`/refresh` / `/:name/proof-asset/*` / `/:name/doc/*` are registered BEFORE
|
||||
the dynamic `/:name` so they are not shadowed.
|
||||
`slicesRoutes()` (mounted `server.ts:803` as `/api/slices`) registers **5**
|
||||
handlers (`grep -c -E 'app\.(get|post)\(' packages/daemon/src/routes/slices.ts`):
|
||||
|
||||
> Source: `routes/slices.ts:31-177` (handlers; route-order L34-35,
|
||||
> L101-102, L109-113, L166-167; 503s L38-44; `boundToWorkflow`
|
||||
> L60-86); mounted `server.ts:501` @HEAD.
|
||||
- `GET /` — filter `all|active|done|blocked` (default `all`; anything else is
|
||||
400 `filter_invalid`); `?refresh=1` (or `true`) invalidates first; optional
|
||||
`?boundToWorkflow=<name>:<version>` lens narrows to slices bound to a
|
||||
workflow instance (400 `boundToWorkflow_invalid` without the colon). Each
|
||||
slice row carries `readiness`, and a `missions` sidecar carries authored
|
||||
mission status and readiness. Rows sort by last activity, newest first.
|
||||
- `POST /refresh` — drops the indexer caches, no daemon restart.
|
||||
- `GET /:name/proof-asset/*` — serves a proof asset from the slice's matched
|
||||
dogfood-evidence directory; rejects `..` (400); `Cache-Control: public,
|
||||
max-age=86400`; byte ranges (206 / 416).
|
||||
- `GET /:name/doc/*` — markdown for the Docs tab; rejects `..`.
|
||||
- `GET /:name` — full per-tab payload. With `?project=<id>` it also requires
|
||||
`?mission=<id>` (400 `exact_mission_and_slice_required`) and reads through
|
||||
an indexer scoped to that project's missions root.
|
||||
|
||||
### 4.4 Missions route — **0.3.1** (slice 12 + slice 13 + slice 18)
|
||||
503 `slices_indexer_unavailable` when unwired; on `GET /`, 503
|
||||
`slices_root_not_configured` + setup hint when not ready. **Route-order
|
||||
discipline:** literal `/` / `/refresh` / `/:name/proof-asset/*` /
|
||||
`/:name/doc/*` are registered BEFORE the dynamic `/:name` so they are not
|
||||
shadowed.
|
||||
|
||||
`missionsRoutes()` (mounted `server.ts:505` as `/api/missions`) is the
|
||||
mission scope data layer. `GET /:missionId` returns
|
||||
`{ missionId, missionPath, slices, workflow_spec, topology, status }` —
|
||||
slices filtered from the SliceIndexer by `missionId`; `missionPath` derived
|
||||
by going up two levels from any slice's `slicePath`; `workflow_spec` parsed
|
||||
lazily from `<missionPath>/README.md` frontmatter (same `parseWorkflowSpecRef`
|
||||
helper the slice-indexer uses); `topology.specGraph` projected via
|
||||
`projectSpecGraph(spec, null)` when the spec is cached, `{ specGraph: null }`
|
||||
when declared-but-not-cached, `null` when nothing declared; `status` read
|
||||
from README frontmatter. `POST /:missionId/complete` writes
|
||||
`status: complete` to the mission README frontmatter (idempotent; preserves
|
||||
unrelated fields) — the daemon is the audit-trail surface, the UI keeps an
|
||||
optimistic localStorage mirror. 404 `mission_not_found` when no slices match.
|
||||
> Source: `routes/slices.ts:33–198` (handlers; route-order comments
|
||||
> `:36–37`, `:118`, `:126–127`, `:175`; 503s `:40–46`; filters `:31`,
|
||||
> `:47–53`; refresh `:54–57`; `boundToWorkflow` `:67–93`; readiness +
|
||||
> `missions` sidecar `:99–110`; `/refresh` `:119–124`; proof asset
|
||||
> `:131–154`, cache header `:235`, ranges `:237–266`; doc `:158–173`;
|
||||
> project scope `:180–190`); mounted `server.ts:803`.
|
||||
|
||||
> Source: `routes/missions.ts:39-118` (handlers), `:124-142`
|
||||
> (`writeMissionStatusComplete`), `:148-150` (`computeMissionPath`
|
||||
> up-two-levels), `:171-177` (`readMissionWorkflowSpec`), `:205-215`
|
||||
> (`computeMissionTopology`); mounted `server.ts:505`; §0 proof:
|
||||
> `routes/missions.ts` [email protected] ⇒ **0.3.1** @HEAD.
|
||||
### 4.4 Missions route — 0.3.1
|
||||
|
||||
### 4.5 Projects route — Coordination L2 classifier (PL-004 Phase B) — 0.3.0
|
||||
`missionsRoutes()` (mounted `server.ts:814` as `/api/missions`) is the
|
||||
mission scope data layer, with **2** handlers
|
||||
(`grep -c -E 'app\.(get|post)\(' packages/daemon/src/routes/missions.ts`).
|
||||
|
||||
`projectsRoutes()` (mounted `server.ts:492` as `/api/projects`) backs the
|
||||
`rig project` CLI verb — this is the **PL-004 Phase B project *classifier***
|
||||
(lease lifecycle + idempotent classify + operator-verb reclaim + SSE), NOT
|
||||
the Project *workspace UI* (that is the slices/missions surface above; the
|
||||
naming overlap is a documented seam). Endpoints: `POST /lease/acquire`
|
||||
(optional `evaluateDeadnessFirst` clears stale/dead leases first),
|
||||
`POST /lease/heartbeat`, `POST /reclaim-classifier`, `POST /project`
|
||||
(idempotent on `stream_item_id`), `GET /lease`, `GET /list`,
|
||||
`GET /sse` + `GET /watch` (SSE), `GET /:projectId`. **Route-order
|
||||
discipline:** literal `/lease`, `/list`, `/sse`, `/watch` are registered
|
||||
BEFORE the bare `/:projectId` catchall (Phase A R1 SSE lesson).
|
||||
`GET /:missionId` returns
|
||||
`{ missionId, missionPath, readiness, slices, workflow_spec, topology, status }`:
|
||||
|
||||
> Source: `routes/projects.ts:18-194` (handlers; route-order note
|
||||
> L15-17, L140-141, L158-159, L185); error-code → HTTP mapping
|
||||
> `:31-52`; mounted `server.ts:492` @HEAD.
|
||||
- `slices` — filtered from the SliceIndexer by `missionId`, each with its
|
||||
`readiness`;
|
||||
- `missionPath` — two levels up from any slice's `slicePath`;
|
||||
- `workflow_spec` — parsed from the frontmatter of the mission's node file
|
||||
(`SPEC.md`, else legacy `README.md`) with the same `parseWorkflowSpecRef`
|
||||
helper the slice-indexer exports;
|
||||
- `topology.specGraph` — projected via `projectSpecGraph(spec, null)` when the
|
||||
spec is cached; `{ specGraph: null }` when declared but not cached (or no
|
||||
cache is wired); `null` when nothing is declared;
|
||||
- `readiness` — the mission's proof readiness;
|
||||
- `status` — `metadata.status` from `mission.yaml` when present, else the
|
||||
node file's `status` frontmatter.
|
||||
|
||||
## 5. UI route-reality (§10.7 — source-verified at routes.tsx@HEAD)
|
||||
`POST /:missionId/complete` sets the mission status to `complete`:
|
||||
`metadata.status` in `mission.yaml` when the manifest exists (other fields and
|
||||
comments kept), otherwise the `status` line in the node file's frontmatter
|
||||
(creating `SPEC.md` when the mission has no node file). It is idempotent.
|
||||
After a successful write it drops the indexer's mission-status cache so the
|
||||
next `/api/slices` read sees the new status. The daemon is the audit-trail surface; the UI keeps an
|
||||
optimistic localStorage mirror. 404 `mission_not_found` when no slices match;
|
||||
500 `mission_complete_write_failed` on a write error.
|
||||
|
||||
`packages/ui/src/routes.tsx` is **542 lines** and uses TanStack Router
|
||||
(`createRoute({ path, component })` objects), NOT JSX `<Route>`. The
|
||||
Phase-8.1 survey's grep-derived missions/projects expectations are
|
||||
corrected here against routes.tsx reality:
|
||||
> Source: `routes/missions.ts:45–88` (GET; 503s `:46–64`; 404 `:68–70`;
|
||||
> readiness + status `:77–78`; response `:79–87`), `:95–125` (complete; 500
|
||||
> `:110–117`; cache drop `:123`), `:132–163` (`writeMissionStatusComplete`;
|
||||
> manifest `:133–141`; node file `:145–162`), `:169–171`
|
||||
> (`computeMissionPath` up two levels), `:179–186` (`readMissionStatus`),
|
||||
> `:192–198` (`readMissionWorkflowSpec`), `:226–236`
|
||||
> (`computeMissionTopology`); node-file precedence
|
||||
> `domain/scope/node-file.ts:17`; readiness
|
||||
> `domain/proof/judgments.ts:288–295`; UI mirror
|
||||
> `packages/ui/src/hooks/useCompletedMissions.ts:15`; mounted `server.ts:814`;
|
||||
> §0: `routes/missions.ts` absent at `v0.3.0` ⇒ **0.3.1**.
|
||||
|
||||
| Survey expectation | routes.tsx@HEAD reality | routes.tsx:NN |
|
||||
### 4.5 Projects route — stream classifier — 0.3.0
|
||||
|
||||
`projectsRoutes()` (mounted `server.ts:786` as `/api/projects`) backs the
|
||||
`rig project` CLI verb — the **stream-item classifier** (lease lifecycle +
|
||||
idempotent classify + operator-verb reclaim + attempt ledger + SSE), NOT the
|
||||
Project *workspace UI* (that is the slices/missions surface above; the naming
|
||||
overlap is a documented seam). Endpoints:
|
||||
|
||||
- `GET /worker-sources` — requires `?project=<id>`, a workspace catalog
|
||||
project;
|
||||
- `GET /shadow`, `POST /shadow/drain`, `POST /shadow/stop` (the last two take
|
||||
an optional body `{ actor }`);
|
||||
- `POST /lease/acquire` (optional `evaluateDeadnessFirst` clears stale/dead
|
||||
leases first), `POST /lease/heartbeat`, `POST /reclaim-classifier`;
|
||||
- `POST /project` (idempotent on `stream_item_id`);
|
||||
- `POST /attempts/begin`, `POST /attempts/:attemptId/abstain`,
|
||||
`POST /attempts/:attemptId/fail`, `GET /eligible`;
|
||||
- `GET /lease`, `GET /list`, `GET /sse` + `GET /watch` (SSE);
|
||||
- `GET /:projectId`.
|
||||
|
||||
**Route-order discipline:** every literal path is registered BEFORE the bare
|
||||
`/:projectId` catchall, which comes last.
|
||||
|
||||
> Source: `routes/projects.ts:27–375` (handlers; route-order notes `:25`,
|
||||
> `:240`, `:309`, `:322`, `:339–340`, `:366`); worker sources `:98–109`;
|
||||
> shadow `:111–127`; lease / reclaim / project `:129–237`
|
||||
> (`evaluateDeadnessFirst` `:142–144`); attempts and eligible `:239–306`;
|
||||
> lease and list `:308–336`; SSE `:338–364`; `/:projectId` `:366–372`;
|
||||
> error-code → HTTP mapping `:58–95`; CLI
|
||||
> `packages/cli/src/commands/project.ts:46`; mounted `server.ts:786`.
|
||||
|
||||
## 5. UI route reality — source-verified at routes.tsx
|
||||
|
||||
`packages/ui/src/routes.tsx` is **605** lines (`wc -l < packages/ui/src/routes.tsx`)
|
||||
and uses TanStack Router (`createRoute({ path, component })` objects), NOT JSX
|
||||
`<Route>` (**0** matches: `grep -c '<Route' packages/ui/src/routes.tsx`). The
|
||||
mission / project / slice destinations as they exist:
|
||||
|
||||
| Path | routes.tsx reality | routes.tsx:NN |
|
||||
|---|---|---|
|
||||
| `/project` (workspace) | **REAL route** — `WorkspaceScopePage` | `:128-131` |
|
||||
| `/project/mission/$missionId` | **REAL route** — `MissionScopePage` | `:134-137` |
|
||||
| `/project/slice/$sliceId` | **REAL route** — `SliceScopePage` | `:140-143` |
|
||||
| `/files` | **REAL route** — `FilesWorkspace` (see content-surfaces.md) | `:192-195` |
|
||||
| `/mission-control` | **`<Navigate to="/for-you" />`** redirect-stub (DELETED per SC-18; the Mission Control *system* lives at daemon/PL-005, not a UI destination) | `:440-444` |
|
||||
| `/slices` | **`<Navigate to="/project" />`** redirect-stub (DELETED per project-tree.md) | `:447-451` |
|
||||
| `/slices/$name` | **`<Navigate to="/project/slice/$sliceId" />`** redirect-stub | `:453-460` |
|
||||
| `/progress` | **`<Navigate to="/project" />`** redirect-stub (folds into Project tabs, Phase 3) | `:464-468` |
|
||||
| `/steering` | **`<Navigate to="/project" />`** redirect-stub (folds into Project workspace overview tab, Phase 3) | `:470-474` |
|
||||
| `/missions` (top-level) | **NO route** — missions are reached only via `/project/mission/$missionId` | (absent) |
|
||||
| `/markdown` | **NO route** — markdown is a component in file/drawer surfaces, not a destination (see content-surfaces.md §4) | (absent) |
|
||||
| `/project` (workspace) | **REAL route** — `WorkspaceScopePage` | `:136–140` |
|
||||
| `/project/mission/$missionId` | **REAL route** — `MissionScopePage` | `:142–146` |
|
||||
| `/project/slice/$sliceId` | **REAL route** — `SliceScopePage` | `:148–152` |
|
||||
| `/files` | **REAL route** — `FilesWorkspace` (see content-surfaces.md) | `:248–252` |
|
||||
| `/mission-control` | **`<Navigate to="/for-you" />`** redirect-stub (the Mission Control *system* lives in the daemon at `/api/mission-control`, not as a UI destination) | `:496–500` |
|
||||
| `/slices` | **`<Navigate to="/project" />`** redirect-stub | `:503–507` |
|
||||
| `/slices/$name` | **`<Navigate to="/project/slice/$sliceId" />`** redirect-stub | `:509–516` |
|
||||
| `/progress` | **`<Navigate to="/project" />`** redirect-stub (folds into Project tabs) | `:519–523` |
|
||||
| `/steering` | **`<Navigate to="/project" />`** redirect-stub (folds into the Project workspace overview tab) | `:526–530` |
|
||||
| `/missions` (top-level) | **NO route** — missions are reached only via `/project/mission/$missionId` | absent: **0** (`grep -c 'path: "/missions"' packages/ui/src/routes.tsx`) |
|
||||
| `/markdown` | **NO route** — markdown is a component in file/drawer surfaces, not a destination (see content-surfaces.md) | absent: **0** (`grep -c 'path: "/markdown"' packages/ui/src/routes.tsx`) |
|
||||
|
||||
> Source: `packages/ui/src/routes.tsx:90-195` (real routes),
|
||||
> `:435-474` (redirect-stub block; each `<Navigate>` + DELETED/folds
|
||||
> comment), `:476-490` (route tree) @HEAD. Grep-confirmed absent:
|
||||
> no `path: "/missions"` / `path: "/markdown"` declaration anywhere
|
||||
> in routes.tsx @HEAD.
|
||||
> Source: `packages/ui/src/routes.tsx:136–152` (project routes), `:248–252`
|
||||
> (`/files`), `:484–530` (redirect block, which also redirects `/context` to
|
||||
> `/topology`), `:539–597` (route tree); `/api/mission-control` mounted
|
||||
> `packages/daemon/src/server.ts:790–793`.
|
||||
|
||||
Net §10.7 ruling: the daemon `/api/missions` + `/api/slices` +
|
||||
`/api/projects` routes are real and load-bearing; the **operator-facing UI**
|
||||
consumes them through `/project*` destinations only. `/mission-control`,
|
||||
`/slices`, `/progress`, `/steering` are redirect-stubs (the systems exist at
|
||||
the daemon layer, the URLs were collapsed into `/project` / `/for-you`).
|
||||
Net: the daemon `/api/missions` + `/api/slices` + `/api/projects` routes are
|
||||
real. The **operator-facing UI** consumes the missions and slices routes
|
||||
through the `/project*` destinations; it does not call `/api/projects`
|
||||
(**0** files: `git grep -l '/api/projects' -- packages/ui/src | wc -l`).
|
||||
`/mission-control`, `/slices`, `/progress`, `/steering` are redirect-stubs
|
||||
(the systems exist at the daemon layer; the URLs were collapsed into
|
||||
`/project` / `/for-you`).
|
||||
|
||||
> Source: `packages/ui/src/hooks/useMission.ts:51` (`/api/missions`),
|
||||
> `packages/ui/src/hooks/useSlices.ts:84` (`/api/slices`).
|
||||
|
||||
## 6. Cross-cutting properties
|
||||
|
||||
- **Optional + valid-without:** rigs without a `workspace` block stay valid;
|
||||
whoami / node-inventory / queue-target-repo all return null / fail-open
|
||||
(`workspace-resolver.ts:32`; `rig-repository.ts:99,107`;
|
||||
`routes/queue.ts:55`) @HEAD.
|
||||
- **Defensive column probes:** every `workspace_json` / `target_repo` access
|
||||
guards on column presence so partial test fixtures don't crash
|
||||
(`rig-repository.ts:99,107`; migrations 038/039 ship separately so a
|
||||
fixture can apply only the half it needs — `038.ts:11-14`) @HEAD.
|
||||
whoami / node-inventory return null and queue target-repo validation fails
|
||||
open (`workspace-resolver.ts:32`, `:62`; `rig-repository.ts:185`, `:193`;
|
||||
`routes/queue.ts:112`).
|
||||
- **Defensive column access:** `RigRepository` probes for `workspace_json`
|
||||
(`rig-repository.ts:185`, `:193`), node-inventory's own reads fall back to
|
||||
null on error (`node-inventory.ts:724`, `:796–798`), and the queue
|
||||
repository detects `target_repo` (`queue-repository.ts:758`), so partial
|
||||
test fixtures don't crash. Migrations 038/039 ship separately so a fixture
|
||||
can apply only the half it needs (`038_workspace_primitive.ts:12–14`).
|
||||
- **Read-only projection, no new state for the tree:** SliceIndexer /
|
||||
SliceDetailProjector add NO migration / event type; only the workspace
|
||||
declaration (038) + per-item scope (039) touch SQLite
|
||||
(`slice-indexer.ts:12-14`) @HEAD.
|
||||
- **Advisory, never mutates:** the frontmatter validator and the
|
||||
init-workspace scaffold never delete operator content
|
||||
(`frontmatter-validator.ts:12-13`; `config-init-workspace.ts:12-16`) @HEAD.
|
||||
(`slice-indexer.ts:14–15`).
|
||||
- **Advisory, never deletes:** the frontmatter validator never modifies files
|
||||
(`frontmatter-validator.ts:12`), and the workspace scaffold never deletes or
|
||||
overwrites operator content (`default-workspace-scaffold.ts:140`;
|
||||
`packages/cli/src/commands/config-init-workspace.ts:18`).
|
||||
|
||||
## OPEN items (carried, not smoothed)
|
||||
## Open items
|
||||
|
||||
- **OPEN-A** — `resolveWorkspaceContext` honors an `envOverride`
|
||||
**verbatim even when it names no declared repo** (`workspace-resolver.ts:34-43`,
|
||||
by explicit PL-007 PRD § Item 3 design), whereas the queue route
|
||||
**rejects** an unknown `target_repo` against the same `repos[]`
|
||||
(`routes/queue.ts:58-66`). These two surfaces apply opposite policies to
|
||||
an unknown repo name (whoami trusts the operator; queue validates). Stated
|
||||
as-is — the asymmetry is source-real and intentional per the cited
|
||||
comments, but the divergence is not reconciled in source.
|
||||
- **OPEN-B** — the `/api/projects` *classifier* (PL-004 Phase B) and the
|
||||
`/project*` *workspace UI* (slices/missions) share the word "project" but
|
||||
are unrelated subsystems. Documented as a naming seam; no source defect.
|
||||
- No slice-00 numeric-drift OPEN (1–5) applies — this module carries no
|
||||
migration-count / route-group / PL-004-event-count claim (those land in
|
||||
daemon-core / coordination-primitive / architecture-rules).
|
||||
- `resolveWorkspaceContext` honors an `envOverride` **verbatim even when it
|
||||
names no declared repo** (`workspace-resolver.ts:39–40`), whereas the queue
|
||||
route **rejects** an unknown `target_repo` against the same `repos[]`
|
||||
(`routes/queue.ts:114–120`). These two surfaces apply opposite policies to
|
||||
an unknown repo name (whoami trusts the operator; queue validates). The
|
||||
resolver's comment at `workspace-resolver.ts:35–36` states the verbatim behavior is intended, but
|
||||
the comment line before it (`workspace-resolver.ts:34`) and `whoami-service.ts:331–332` describe
|
||||
a repo-name check that the code does not perform. Stated as-is; the
|
||||
divergence is not reconciled in source.
|
||||
- The `/api/projects` *classifier* and the `/project*` *workspace UI*
|
||||
(slices/missions) share the word "project" but are different subsystems.
|
||||
Both accept a workspace catalog project as `?project=<id>`
|
||||
(`routes/projects.ts:105`; `routes/slices.ts:181`). Documented as a naming
|
||||
seam; no source defect.
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: Arteries — where a small change has a large effect
|
||||
status: active
|
||||
applies-when: |
|
||||
You are about to change, or review a change to, an area that many other paths depend on:
|
||||
message delivery, launch and resume, the queue, rig identity, skill projection, native
|
||||
process observation, migrations, or restore. Read this to see what depends on the area and
|
||||
what broke there before, then go and read the code.
|
||||
siblings: [README.md, test-layers.md]
|
||||
prerequisite-reads: [../../ARCHITECTURE.md]
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Arteries: where a small change has a large effect
|
||||
|
||||
## Read this first: this map is not the territory
|
||||
|
||||
- **This page is a zoomed-out view, not a description of OpenRig.** It names a few areas where
|
||||
small changes caused large regressions before. OpenRig is much larger than this page. Code,
|
||||
behaviour and risk that are not listed here exist, and some of it is riskier than anything below.
|
||||
- **It is incomplete by construction.** A pull request is not safe because its files are missing
|
||||
from this list.
|
||||
- **It may be stale.** It was checked against commit `e8f0ab34`. Files and dependencies move. If
|
||||
this page and the code disagree, the code is right; fix the page.
|
||||
- **Use it to change altitude, then go to the ground:** read the code, run the commands, check the
|
||||
behaviour. When you are lost, go and look rather than leaning on this page.
|
||||
|
||||
## The arteries
|
||||
|
||||
Paths are relative to `packages/`. The key files are entry points, not the full extent of each
|
||||
area.
|
||||
|
||||
| Artery | Key files | What depends on it | Past regressions and lessons |
|
||||
|---|---|---|---|
|
||||
| **Message delivery to seats** | `daemon/src/domain/session-transport.ts`, `daemon/src/domain/seat-delivery-guard.ts`, `daemon/src/adapters/tmux.ts` | Every `rig send`, queue wake, nudge, watchdog wake and handoff | #150 (the fix for #142) stopped typing into a bare shell. It then refused managed Codex seats (fixed by #171 before release) and every Claude seat launched with an explicit permission mode, which shipped in 0.6.2 as #197 (fixed by #220). The delivery guard's rule that a name must resolve to exactly one target, added in #109 (0.6.0), was involved in #141 (fixed by #151), the delivery half of #174 (fixed by #181) and #188 (fixed by #189). Since #310/#327, a Claude seat whose runtime can't be observed still receives ordinary messages with a warning; only a plain idle shell or the wrong recipient is refused. |
|
||||
| **Launch, relaunch, resume** | `daemon/src/adapters/claude-code-adapter.ts`, `daemon/src/domain/claude-managed-launch.ts`, `daemon/src/adapters/codex-runtime-adapter.ts`, `daemon/src/domain/codex-daemon-support.ts`, `daemon/src/domain/seat-launch-environment.ts`, `daemon/src/domain/native-resume-probe.ts`, `daemon/src/domain/startup-orchestrator.ts` | Every seat start, restore and permission mode, plus the non-interruptive, `permission_policy`, effort, kernel operational-authority and team permission-default settings that each launch and resume command carries | Always setting `CLAUDE_CONFIG_DIR` gave explicit-mode Claude seats a blank config (#154). #225 fixed which config home is selected, and #565 writes the onboarding and trust bootstrap into that home. Codex seats launch with `--no-daemon` only when `codex --help` shows support (#69, #77); when support is unknown, OpenRig refuses to launch, so a failing or slow `codex --help` blocks the seat. The readiness window defaults to 30 seconds and can time out under load (#182); since #643 it is configurable as `runtime.readiness_timeout_seconds` (env `OPENRIG_RUNTIME_READINESS_TIMEOUT_SECONDS`, 1–600). A git worktree's `.git` file broke the Codex sandbox (#121; see #126). #618 moved non-managed Claude, Codex and Pi launches behind the seat launch environment, and #628, #638 and #666 followed within days. |
|
||||
| **Queue claim, pickup, wakes** | `daemon/src/domain/queue-repository.ts`, `daemon/src/domain/queue-pickup.ts`, `daemon/src/domain/queue-wake-ladder.ts`, `daemon/src/routes/require-sender-identity.ts`, `cli/src/client.ts` | All durable work between seats | After a slow `/healthz`, the CLI host-qualified a local sender and claims failed (#131, fixed by #135). Done-and-closed items read as stalled forever (#164, fixed by #176). |
|
||||
| **Rig identity: same names, archived and deleted rigs** | `daemon/src/domain/rig-repository.ts`, `daemon/src/domain/rig-lifecycle-service.ts`, `daemon/src/domain/rig-teardown.ts`, `daemon/src/domain/seat-handover-service.ts`, `daemon/src/domain/running-name-guard.ts` | Seat resolution, `rig remove`, restore, importing a rig from YAML | Archived rigs still matched seat references, and `rig remove` killed another rig's live seat with the same name (#174, fixed by #181). Replacing stopped same-name rig generations on YAML import followed in #196. |
|
||||
| **Projection of skills and files** | `daemon/src/domain/rigspec-instantiator.ts`, `daemon/src/domain/projection-planner.ts`, and each runtime adapter's `project()` (Claude Code, Codex, Pi, OMP, stub) | What every agent is given at start | Codex seats skipped skills that a Claude sibling had already projected (#159, fixed by #162). File-shaped entries failed with ENOTDIR (fixed by #185). |
|
||||
| **Native process observation** | `daemon/src/domain/native-process-lineage.ts` (parses `ps -Ao pid,ppid,pgid,tpgid,ucomm,lstart,command`) | Liveness, identity verdicts, delivery checks | Processes whose command name contains a space were dropped (#134, fixed by #82). Non-English locales broke the date parsing (fixed by #239, which runs `ps` with `LC_ALL=C`). #220 also changed this file to recognise managed Claude seats behind launch wrappers. Claude identity proof (wrappers, launcher shims, conversation rotation, effort, version-named binaries, launch `--settings`) changed ten more times after 0.6.3. |
|
||||
| **Schema and migrations** | `daemon/src/db/migrations/`, `daemon/src/db/all-migrations.ts`, `daemon/src/db/migrate.ts` | Every install, and every upgrade of a long-lived install | Parallel pull requests pick the same next number: #112 adds `086_rig_kernel_variant` while `main` already has `086_classification_fields_and_attempts`, and #155 and #195 landed as 090 and 091. `migrate.ts` applies by migration name in name order, so a duplicate number is not skipped, but its order relative to the other migration changes; 093 is unused at `e8f0ab34`. A long-lived install upgrading across several releases applies many migrations in one start, against real data. |
|
||||
| **Restore and recovery** | `daemon/src/domain/restore-orchestrator.ts`, `daemon/src/adapters/{claude,codex,pi,omp}-resume.ts`, `daemon/src/domain/reconciler.ts`, `daemon/src/domain/restore-check-service.ts`, `daemon/src/domain/startup-orchestrator.ts`, `daemon/src/adapters/tmux.ts` | Reboots, power loss, daemon restarts | Detached seats kept stale tmux pane ids after a reboot (#141, fixed by #151 in `tmux.ts`). `restore-check` read queue files and Claude hooks from a fixed shared-docs path (#130); #139 made it use the configured root, and #328 replaced the queue-file check with a probe of the daemon's queue store and checks the selected Claude hooks. After a fresh relaunch, a stale Codex restore attention flag never clears (#116). Full-restore recovery of OMP seats is open (#41). |
|
||||
|
||||
## How reviews use this page
|
||||
|
||||
- **Find where the effect lands.** A reviewer notes which artery a pull request touches, if any,
|
||||
and what depends on it.
|
||||
- **At an artery, review the effect, not only the diff.** Whatever the diff size, the reviewer
|
||||
states the downstream effects and checks behaviour with a scratch reproduction or a test.
|
||||
- **Away from the arteries,** small pull requests keep a normal review. Scrutiny goes where the
|
||||
effect is.
|
||||
|
||||
## The scenario rule
|
||||
|
||||
**A pull request that touches an artery adds or extends a stub-agent scenario, or says in its
|
||||
description why it cannot.** Reviewers check for one or the other. Scenarios live in
|
||||
`packages/test-system/scenarios/`; [ARCHITECTURE.md](../../ARCHITECTURE.md#a-scenario) says how to
|
||||
write and run one, and [test-layers.md](test-layers.md) says what each test layer can and cannot
|
||||
catch.
|
||||
|
||||
The goal is that every artery has at least one scenario whose seeded regression fails, so a small
|
||||
change to an artery meets a real end-to-end check before it merges, not only a reviewer's reading.
|
||||
|
||||
### Scenario coverage today
|
||||
|
||||
At commit `e8f0ab34`. For why the stub cannot reach some of these yet, see "What a stub can and
|
||||
cannot prove" in [test-layers.md](test-layers.md).
|
||||
|
||||
- **Queue durability across a daemon restart: covered.** CI runs
|
||||
`queue-baton-survives-restart.yaml` healthy, then with a seeded `baton-drop` fault that must
|
||||
fail, then healthy again (#243). It runs `rig queue claim` and checks the destination after the
|
||||
restart, but pickup, wakes and claim-identity edge cases (such as the #131 sender case) have no
|
||||
scenario yet.
|
||||
- **Delivery (the #197 class): not covered.** The stub runtime has no input behaviour, so it cannot
|
||||
exercise delivery to a managed wrapper; `send-verify-means-rendered.yaml` is not admitted to CI
|
||||
for that reason (`packages/test-system/ci/README.md`).
|
||||
- **Addressing and rendered delivery: partial.** CI runs the transcript and capture stub scenarios
|
||||
in passing form, and the capture scenario catches a seat resolved by member-name prefix.
|
||||
`send-renders-in-addressed-pane.yaml` and `down-stops-every-seat.yaml` run with the local runner
|
||||
only. `stub-cli-journeys.integration.test.ts` checks that a verified send lands in the addressed
|
||||
pane and not its sibling. None of these proves a seat consumed a message.
|
||||
- **Restore (the #141 class): not covered.** The scenario runner does not bind the `restore` verb
|
||||
yet; it fails with `UnboundActionError`.
|
||||
- **Rig identity (the #174 class): not covered.** No library scenario sets up a same-name archived
|
||||
or removed rig.
|
||||
- **Projection (the #159 class): not coverable with `runtime: stub`,** which projects skills into its
|
||||
own `.openrig/stub/skills/` directory. Use adapter-level unit tests.
|
||||
- **Native process observation: unit tests only** (for example
|
||||
`packages/daemon/test/codex-process-observation.test.ts`, `native-ps-locale.test.ts` and
|
||||
`send-runtime-not-running.test.ts`).
|
||||
- **Migrations: unit guards only** (`migration-fixture-parity.test.ts`,
|
||||
`startup-migrations-mirror.test.ts`). Every scenario starts from an empty database, so upgrading
|
||||
an existing database is not covered.
|
||||
- **Launch:** every scenario's `up` goes through the real `rig up`, instantiation and startup
|
||||
orchestration with stub seats. The stub adapter types its runner directly; Claude Code, Codex
|
||||
and Pi seats launch through the seat launch environment (`seat-launch-environment.ts`), which
|
||||
no scenario reaches. Real Codex launch and handover run only in two opt-in daemon tests
|
||||
(`OPENRIG_E2E_REAL_CODEX=1`, which needs tmux, `codex` and a Codex login). The CI containers
|
||||
have no provider CLIs or credentials.
|
||||
|
||||
## Regression log
|
||||
|
||||
When a regression reaches a release, add one line: date, issue, artery, and which layer missed it
|
||||
(the layers are named in [test-layers.md](test-layers.md)). Each line should end up as a scenario.
|
||||
|
||||
- 2026-09-30, #197 (0.6.2), delivery: review saw a small diff in isolation; CI had no managed
|
||||
explicit-mode Claude seat; the project's own day-to-day seats use the default launch path.
|
||||
- 2026-09-29/30, #141, #174, #188 (after 0.6.0), delivery and rig identity: reviews of #109 did not
|
||||
see the downstream effect of the exactly-one-target rule.
|
||||
+867
-1849
File diff suppressed because it is too large.
Load diff
+91
-178
@@ -1,197 +1,110 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: OpenRig Codemap — Navigation Index / Map of Territory
|
||||
title: OpenRig Codemap — Modules and Source Entry Points
|
||||
status: active
|
||||
topics: [knowledge-and-context, observability]
|
||||
domains: [engineering-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
You know what you need to learn about the shipped OpenRig system but not
|
||||
which as-built module holds it. Use the module map, the use-case lookup,
|
||||
or the source-root table to route to the right doc — or jump straight to
|
||||
the source root the codemap points at.
|
||||
siblings: [README.md, cli-reference.md]
|
||||
You know the behavior you need to inspect but not its owning source files.
|
||||
Follow the module and source entry points below, then read the implementation.
|
||||
siblings: [README.md, arteries.md, test-layers.md, cli-reference.md]
|
||||
prerequisite-reads: [README.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# OpenRig Codemap — Navigation Index
|
||||
# OpenRig codemap
|
||||
|
||||
This is a **map of territory**, not a content dump. It tells you which
|
||||
as-built module answers a question and which `packages/*/src` root that
|
||||
module is grounded in. The deep content lives in the modules; the code lives
|
||||
in the source roots; this file points at both.
|
||||
The paths and named entry points below were checked at main
|
||||
`e8f0ab340db773392ec8be75b072d1c0f3068a50`. This is a navigation map, not a replacement for
|
||||
reading the implementation. Linked documents retain their individual verification stamps.
|
||||
[README.md](README.md) lists the complete document tree and commands to derive its inventory.
|
||||
|
||||
> The old codemap was a 425-line file-by-file `Exports:`/`Related:` flat dump
|
||||
> that drifted fastest (its own header carried a stale v0.2.0 footprint and an
|
||||
> internally-contradictory command-group count) and never fit a context
|
||||
> window. That anti-pattern is retired (slice-08 Q5, founder-ratified). The
|
||||
> genuinely-useful part — the source-root index — is preserved below as the
|
||||
> source-root pointer table; the per-file transcription is not.
|
||||
## Package entry points
|
||||
|
||||
## (a) Product frame
|
||||
Paths in backticks are relative to the repository root.
|
||||
|
||||
OpenRig is a local control plane for multi-agent coding topologies — a
|
||||
multi-agent harness that manages your Claude Code and Codex sessions as a
|
||||
single system. The daemon (`@openrig/daemon`) is the framework-free
|
||||
SQLite-backed core; the CLI (`@openrig/cli`), UI (`@openrig/ui`), and MCP
|
||||
server all sit on top of it.
|
||||
|
||||
> Source-verified at HEAD `7eaf524c` (`v0.3.1-6-g7eaf524c`); package version
|
||||
> **0.3.1**, 6 unreleased 0.3.2 commits on HEAD, no `v0.3.2` tag. Frame
|
||||
> sentence triangulated against `architecture/daemon-core.md` §1 +
|
||||
> `openrig-internal/product/positioning.md` ("a multi-agent harness, a local
|
||||
> control plane that manages your Claude Code and Codex sessions as a single
|
||||
> system").
|
||||
|
||||
## (b) Module map
|
||||
|
||||
One row per module. "Reach for this when…" mirrors each module's actual
|
||||
`applies-when` frontmatter.
|
||||
|
||||
### architecture/
|
||||
|
||||
| Module | One-sentence summary | Reach for this when… |
|
||||
|---|---|---|
|
||||
| `daemon-core.md` | The framework-free SQLite-backed core the CLI/UI/MCP all sit on. | You need to know how the daemon boots, how `createDaemon` wires the dependency graph, the SQLite schema/migration set, or the route-mount surface. |
|
||||
| `adapters-and-runtimes.md` | The five-method runtime-adapter contract + the resume-honesty layer. | You need the runtime-adapter contract, how OpenRig launches/resumes a Claude Code, Codex, or terminal harness inside tmux, or how the daemon honestly assesses whether a harness actually resumed vs fresh-launched. |
|
||||
| `coordination-primitive.md` | PL-004 Phase A: the SQLite-canonical durable-work layer (`/api/stream`, `/api/queue`). | You need the stream/queue/inbox/outbox tables, the hot-potato closure contract, the transactional handoff guarantee, or where queue closure is enforced. |
|
||||
| `workflow-runtime.md` | PL-004 Phase D: turns an intended sequence of work into durable SQLite state. | You need workflow specs cache, instance state, step trails, the transactional-scribe projection contract, or the watchdog policy set including workflow-keepalive. |
|
||||
| `mission-control.md` | PL-005: the daemon-backed queue-observability surface inside the existing shell. | You need the seven views, the seven write verbs, the action audit table, the bearer-token middleware, or how queue observability maps to PL-004 sources. |
|
||||
| `agent-spec-and-startup.md` | How authored YAML specs become a resolved, launched, identity-addressable topology. | You need the AgentSpec/RigSpec/pod-aware reboot types, profile resolution + additive startup layering, the StartupOrchestrator delivery split, or how whoami/materialize/bind/adopt resolve identity. |
|
||||
| `lifecycle-snapshot-restore.md` | The durable-state half of `down → up → handoff`. | You need how OpenRig captures a snapshot, restores a rig (resume vs rebuild vs fresh), enforces restore honesty, consults live continuity, or how restore-check / restore-packet readiness probes work. |
|
||||
| `transport-and-transcripts.md` | The communication-and-history layer; tmux is transport, not truth. | You need how rig send/capture/broadcast works, pipe-pane transcript capture + search, durable SQLite chat, what `rig ask` gathers, or the MCP-tool-name vs tmux-metadata-key distinction. |
|
||||
| `workspace-primitive.md` | PL-007: the typed declaration of where a rig's work lives. | You need how a rig declares a typed workspace (root/repos/defaultRepo/knowledgeRoot), how it persists/resolves into whoami / node-inventory, how per-item `target_repo` scope is validated, or how the file-backed missions/slices tree is indexed and projected into Project. |
|
||||
| `content-surfaces.md` | The operator-allowlisted, filesystem-canonical read/write layer Project/Steering sits on. | You need how the file browser enforces path safety, how atomic conflict-checked writes + the JSONL edit audit work, how the PROGRESS.md tree is indexed, or how the one-screen Steering surface is composed. |
|
||||
| `plugin-agent-image-context-pack.md` | Four filesystem-canonical content primitives the daemon discovers and serves. | You need how OpenRig discovers plugins, captures/forks agent images, assembles/sends context packs, or how the Claude auto-compaction enforcer decides to send `/compact`. |
|
||||
| `packaging-bootstrap-bundles.md` | How a topology is packaged into a shareable bundle and reconstituted elsewhere. | You need how rig/pod bundles are assembled (schema-v2 vs legacy v1), how bundle create/inspect/install + `/api/up` route across source kinds, the staged BootstrapOrchestrator flow, or which legacy install-engine seams still ship. |
|
||||
| `architecture-rules-and-event-system.md` | The cross-cutting invariants that belong to no single subsystem. | You need the 25 architecture rules + startup/import constraints, the shape of the RigEvent union and its SSE delivery, or the intentional compatibility limits that still describe the shipped system. |
|
||||
| `living-notes-review.md` | **v0.4.4** — the one intent→plan→delivered review projection over on-disk SDLC markdown. | You need the `ComposedSliceReview` contract, the `/api/review/*` routes, staged-approval locks (`--scope spec|delivery`), the `## Proof contract` ↔ proof-artifact join and C1-bound `verified`, the freeze export, or ranged media serving for review evidence. |
|
||||
|
||||
### ui/
|
||||
|
||||
| Module | One-sentence summary | Reach for this when… |
|
||||
|---|---|---|
|
||||
| `shell-and-routing.md` | The shell-first, route-first, primitive-driven operator surface. | You need how the UI shell (rail / Explorer / center workspace / drawer / preview stack) is assembled, the actual route tree the shipped UI mounts, or how the shared detail drawer and event consumption work. |
|
||||
| `topology.md` | The operator's live picture of host → rig → pod → seat. | You need how the topology surface is built — the host hybrid graph, the table/terminal views, the activity-ring / hot-potato visual language, terminal-preview popovers, and the navigation/overlay contracts. |
|
||||
| `project-and-for-you.md` | The three operator-facing destination surfaces (For You / Project / Dashboard). | You need how the For-You attention feed (5-card classifier + verb actions), the Project workspace/mission/slice scope pages, and the Dashboard landing on the vellum brand system are built. |
|
||||
| `library-specs-and-design-system.md` | The Library (`/specs`) destination + the design-system pointer. | You need how the Library UI is assembled — the spec/skills/plugins surfaces, the spec-review + spec-library + live-identity flows that feed it — or where the canonical visual/design-system spec lives. |
|
||||
|
||||
### root
|
||||
|
||||
| Doc | One-sentence summary | Reach for this when… |
|
||||
|---|---|---|
|
||||
| `cli-reference.md` | The full `rig` CLI surface, kept as one doc. | You need the exact rig CLI surface — command groups, subcommands, flags, JSON output, cross-host, coordination primitives. |
|
||||
| `frontmatter-schema.md` | The frontmatter convention these docs follow + the as-built-unique field. | You are authoring or updating a doc under `docs/as-built/`. |
|
||||
| `../DESIGN.md` | The canonical visual / brand / design-system spec (repo `docs/` root). | You need the visual system, brand identity, or design-system tokens. (Stays at root by design; pointer only.) |
|
||||
|
||||
## (c) Structural relationship diagram
|
||||
|
||||
Built from each module's `siblings` / `prerequisite-reads` frontmatter.
|
||||
`daemon-core.md` is the spine (prerequisite for almost every architecture
|
||||
module); `shell-and-routing.md` is the UI spine.
|
||||
|
||||
```
|
||||
README.md (entry — prerequisite for all)
|
||||
│
|
||||
┌───────────────┴───────────────┐
|
||||
▼ ▼
|
||||
architecture/ ui/
|
||||
│ │
|
||||
daemon-core.md ◀── (prerequisite spine for the column below)
|
||||
│ │ │ │
|
||||
│ │ │ └──▶ adapters-and-runtimes.md
|
||||
│ │ │ (five-method contract + resume honesty;
|
||||
│ │ │ consumed by agent-spec startup orchestration)
|
||||
│ │ └────────────────────────────┐
|
||||
│ └──────────────┐ │
|
||||
▼ ▼ ▼
|
||||
coordination- agent-spec-and- transport-and-
|
||||
primitive.md startup.md transcripts.md
|
||||
│ │ │ │
|
||||
▼ ▼ │ ▼
|
||||
workflow- mission- │ lifecycle-snapshot-restore.md
|
||||
runtime.md control.md │ ▲ (consumes persisted replay context)
|
||||
│ │ │
|
||||
└────┬─────┘ ├──▶ packaging-bootstrap-bundles.md
|
||||
▼ │ ▲
|
||||
architecture-rules- │ │ (0.3.x reusable starter-state cluster)
|
||||
and-event-system.md │ ▼
|
||||
(consumes all └──▶ plugin-agent-image-context-pack.md
|
||||
PL-004/005 events)
|
||||
|
||||
workspace-primitive.md ──▶ content-surfaces.md
|
||||
(PL-007 declaration) (filesystem read/write layer on top)
|
||||
│ │
|
||||
└──────────┬───────────────┘
|
||||
▼ (UI counterpart)
|
||||
ui/project-and-for-you.md
|
||||
|
||||
shell-and-routing.md ──▶ topology.md
|
||||
│ └──▶ project-and-for-you.md ◀──▶ architecture/mission-control.md
|
||||
└──────────────▶ library-specs-and-design-system.md ──▶ ../DESIGN.md (pointer)
|
||||
```
|
||||
|
||||
Reading-order rule: open a module's `prerequisite-reads` first
|
||||
(`README.md`, then `daemon-core.md` for most architecture modules;
|
||||
`README.md` then `shell-and-routing.md` for most ui modules).
|
||||
|
||||
## (d) Fast lookup by use-case
|
||||
|
||||
| I need to know… | → see |
|
||||
| Surface | Start here |
|
||||
|---|---|
|
||||
| How the daemon boots / `createDaemon` wiring / migration set / route mounts | `architecture/daemon-core.md` |
|
||||
| The runtime-adapter contract / how a harness is launched or resumed in tmux | `architecture/adapters-and-runtimes.md` |
|
||||
| Where Claude resume honesty lives / resumed-vs-fresh assessment | `architecture/adapters-and-runtimes.md` |
|
||||
| Where queue closure is enforced / hot-potato contract / durable handoff | `architecture/coordination-primitive.md` |
|
||||
| How workflow specs project on closure / transactional-scribe / watchdog policies | `architecture/workflow-runtime.md` |
|
||||
| The Mission Control views/verbs / queue-observability / action audit | `architecture/mission-control.md` |
|
||||
| AgentSpec/RigSpec types, profile resolution, startup layering, whoami/bind/adopt | `architecture/agent-spec-and-startup.md` |
|
||||
| Snapshot/restore, restore honesty, restore-check / restore-packet probes | `architecture/lifecycle-snapshot-restore.md` |
|
||||
| rig send/capture/broadcast, transcripts, durable chat, `rig ask`, MCP-name vs tmux-key | `architecture/transport-and-transcripts.md` |
|
||||
| The workspace primitive (root/repos), `target_repo` scope gating, missions/slices indexing | `architecture/workspace-primitive.md` |
|
||||
| The file browser path safety, atomic writes + edit audit, PROGRESS tree, Steering composer | `architecture/content-surfaces.md` |
|
||||
| The composed slice/mission review (`/api/review/*`), staged-approval locks, proof artifacts + `verified`, freeze export, ranged media | `architecture/living-notes-review.md` |
|
||||
| Plugin discovery / agent images / context packs / Claude auto-compaction enforcer | `architecture/plugin-agent-image-context-pack.md` |
|
||||
| Bundle assembly, bundle install / `/api/up`, BootstrapOrchestrator, legacy install seams | `architecture/packaging-bootstrap-bundles.md` |
|
||||
| The 25 architecture rules / RigEvent union / SSE delivery / compatibility limits | `architecture/architecture-rules-and-event-system.md` |
|
||||
| The UI shell, the real route tree, the shared detail drawer | `ui/shell-and-routing.md` |
|
||||
| The topology graph/table/terminal views, activity-ring / hot-potato visuals | `ui/topology.md` |
|
||||
| The For-You attention feed, Project scope pages, the Dashboard landing | `ui/project-and-for-you.md` |
|
||||
| The vellum brand system on the destination surfaces (0.3.1 brand identity) | `ui/project-and-for-you.md` (vellum brand = **0.3.1** per slice-00 0.3.0-GT seam (b); slice-00 §2 row 1 = vellum-primitives → brand-identity, row 2 = destination-model → polished-destinations) |
|
||||
| The Library `/specs` UI + spec-review/spec-library/live-identity flows | `ui/library-specs-and-design-system.md` |
|
||||
| The full `rig` CLI surface | `cli-reference.md` |
|
||||
| The visual / brand / design-system spec | `../DESIGN.md` (repo `docs/` root) |
|
||||
| Which frontmatter these docs use + the as-built-unique field | `frontmatter-schema.md` |
|
||||
| Daemon process | `packages/daemon/src/index.ts`; `createDaemon()` in `packages/daemon/src/startup.ts`. |
|
||||
| HTTP application | `createApp()` in `packages/daemon/src/server.ts`; route modules under `packages/daemon/src/routes/`. |
|
||||
| State and migrations | `packages/daemon/src/db/connection.ts`, `packages/daemon/src/db/migrate.ts`, `packages/daemon/src/db/all-migrations.ts`. |
|
||||
| CLI | `createProgram()` in `packages/cli/src/index.ts`; commands under `packages/cli/src/commands/`, HTTP in `packages/cli/src/client.ts`. |
|
||||
| MCP | `createMcpServer()` in `packages/cli/src/mcp-server.ts`; stdio command in `packages/cli/src/commands/mcp.ts`. |
|
||||
| Terminal UI | `packages/tui/src/main.ts`, `packages/tui/src/state.ts`, `packages/tui/src/render.ts`; HTTP in `packages/tui/src/daemon-client.ts`. |
|
||||
| Web UI | `packages/ui/src/main.tsx`, `packages/ui/src/routes.tsx`. |
|
||||
| npm assembly | `scripts/build-package.sh`, `scripts/rewrite-daemon-imports.mjs`, `packages/cli/package.json`. |
|
||||
|
||||
## (e) Source-root pointer table
|
||||
The daemon's route handlers obtain domain services from request context. `startup.ts` constructs
|
||||
those services, repositories and runtime adapters; `server.ts` wires them into the application.
|
||||
CLI and TUI clients use the daemon's HTTP projections. Package boundaries and the development
|
||||
versus bundled paths are described in [ARCHITECTURE.md](../../ARCHITECTURE.md).
|
||||
|
||||
Module → primary `packages/*/src/...` roots. This replaces the old per-file
|
||||
`Exports:`/`Related:` dump: it points at the code, it does not transcribe it.
|
||||
Source-grounding for each claim lives inside the module with file:line cites.
|
||||
## Runtime and state modules
|
||||
|
||||
| Module | Primary source roots |
|
||||
Each row names an existing module and a small set of source starting points. Directories group
|
||||
related code; they are not an exhaustive dependency list.
|
||||
|
||||
| Question | Module | Source entry points |
|
||||
|---|---|---|
|
||||
| How are the daemon, database and routes assembled? | [Daemon core](architecture/daemon-core.md) | `packages/daemon/src/startup.ts`, `packages/daemon/src/server.ts`, `packages/daemon/src/db/`. |
|
||||
| How do runtime launch, projection and resume work? | [Adapters and runtimes](architecture/adapters-and-runtimes.md) | `packages/daemon/src/domain/runtime-adapter.ts`, `packages/daemon/src/adapters/`, `packages/daemon/src/domain/native-resume-probe.ts`. |
|
||||
| How do authored specs become launched agents with identity? | [Agent spec and startup](architecture/agent-spec-and-startup.md) | `packages/daemon/src/domain/rigspec-schema.ts`, `packages/daemon/src/domain/startup-orchestrator.ts`, `packages/daemon/src/domain/whoami-service.ts`. |
|
||||
| Where are durable work, delivery and handoff handled? | [Coordination](architecture/coordination-primitive.md) | `packages/daemon/src/domain/queue-repository.ts`, `packages/daemon/src/domain/stream-store.ts`, `packages/daemon/src/domain/inbox-handler.ts`, `packages/daemon/src/domain/outbox-handler.ts`. |
|
||||
| How does a workflow retain progress and resume? | [Workflow runtime](architecture/workflow-runtime.md) | `packages/daemon/src/domain/workflow-runtime.ts`, `packages/daemon/src/domain/workflow-projector.ts`, `packages/daemon/src/domain/policies/`, `packages/daemon/src/routes/workflow.ts`. |
|
||||
| How are queue observations and actions exposed? | [Mission control](architecture/mission-control.md) | `packages/daemon/src/domain/mission-control/`, `packages/daemon/src/routes/mission-control.ts`. |
|
||||
| What is captured and checked during restore? | [Lifecycle, snapshot and restore](architecture/lifecycle-snapshot-restore.md) | `packages/daemon/src/domain/snapshot-capture.ts`, `packages/daemon/src/domain/restore-orchestrator.ts`, `packages/daemon/src/routes/restore-check.ts`. |
|
||||
| How do messages and transcript reads reach sessions? | [Transport and transcripts](architecture/transport-and-transcripts.md) | `packages/daemon/src/domain/session-transport.ts`, `packages/daemon/src/domain/transcript-store.ts`, `packages/daemon/src/routes/transport.ts`. |
|
||||
| Where do workspaces, projects and repository scope resolve? | [Workspace](architecture/workspace-primitive.md) | `packages/daemon/src/domain/workspace/`, `packages/daemon/src/domain/current-work.ts`, `packages/daemon/src/routes/projects.ts`. |
|
||||
| How are files, progress and steering read or written? | [Content surfaces](architecture/content-surfaces.md) | `packages/daemon/src/domain/files/`, `packages/daemon/src/domain/progress/`, `packages/daemon/src/domain/steering/`. |
|
||||
| How are review inputs, proof and approvals composed? | [Living notes and review](architecture/living-notes-review.md) | `packages/daemon/src/domain/review/`, `packages/daemon/src/domain/scope/`, `packages/daemon/src/routes/review.ts`. |
|
||||
| Where are plugins, agent images and context packs discovered? | [Plugins, images and context packs](architecture/plugin-agent-image-context-pack.md) | `packages/daemon/src/domain/plugin-discovery-service.ts`, `packages/daemon/src/domain/agent-images/`, `packages/daemon/src/domain/context-packs/`. |
|
||||
| How are reusable bundles assembled and installed? | [Packaging, bootstrap and bundles](architecture/packaging-bootstrap-bundles.md) | `packages/daemon/src/domain/pod-bundle-assembler.ts`, `packages/daemon/src/domain/bootstrap-orchestrator.ts`, `packages/daemon/src/routes/bundles.ts`, `packages/daemon/src/routes/up.ts`. |
|
||||
| Where are event types, publication and SSE handled? | [Architecture rules and events](architecture/architecture-rules-and-event-system.md) | `packages/daemon/src/domain/types.ts`, `packages/daemon/src/domain/event-bus.ts`, `packages/daemon/src/routes/events.ts`. |
|
||||
|
||||
For an execution projection, also read `packages/daemon/src/domain/execution-view.ts` and
|
||||
`packages/daemon/src/domain/view-projector.ts`. The terminal-side consumer starts in
|
||||
`packages/tui/src/execution/`. These sit alongside the queue and workflow sources above.
|
||||
|
||||
### Newer entry points
|
||||
|
||||
These features were added after the module pages above were last verified. Each module's own
|
||||
stamp says whether it already covers them; until then, start from the source named here.
|
||||
|
||||
| Question | Source entry points | Nearest module |
|
||||
|---|---|---|
|
||||
| How does a GitHub folder link become a bundle source? | `isGitHubBundleLink()` and the bundle source in `packages/cli/src/lib/bundle-source.ts`, used by `packages/cli/src/commands/up.ts` and `packages/cli/src/commands/bundle.ts`. | [Packaging and bundles](architecture/packaging-bootstrap-bundles.md) |
|
||||
| What does `rig bundle check` read? | `checkBundleFolder()` in `packages/cli/src/lib/bundle-check.ts`: advisory file reads against `openrig.bundle-standard/v1`, with no daemon, launch or provider call. | [Packaging and bundles](architecture/packaging-bootstrap-bundles.md) |
|
||||
| How does a member's `permission_policy` become a launch posture, including `builtin:auto`? | `builtinLaunchPosture()` and `resolvePermissionPolicyAttachment()` in `packages/daemon/src/domain/permission-policy/policy-ref.ts` (`builtin:yolo` is full bypass, `builtin:auto` is auto, the other built-ins are the floor), bound per seat in `packages/daemon/src/domain/rigspec-instantiator.ts`. `packages/daemon/src/domain/bundle-behaviour.ts` is the read-only preview of the same mapping for an unopened bundle. | [Adapters and runtimes](architecture/adapters-and-runtimes.md) |
|
||||
| What does a non-interruptive launch add? | `nonInterruptiveArgs()` in `packages/daemon/src/adapters/non-interruptive.ts` (per-launch arguments only; it never changes permissions or native settings files). The separate instance-wide YOLO setting, off by default, is in `packages/daemon/src/adapters/yolo-mode.ts`. | [Adapters and runtimes](architecture/adapters-and-runtimes.md) |
|
||||
| What launch arguments does a kernel seat get? | `operationalLaunchArgs()` and `KERNEL_CLAUDE_ALLOW` in `packages/daemon/src/adapters/kernel-authority.ts`; `NativePermissionStore.launchOverride()` in `packages/daemon/src/domain/native-permission-store.ts` decides it per launch (only the rig named `kernel`, with no explicit seat choice, permission policy or Codex profile). | [Adapters and runtimes](architecture/adapters-and-runtimes.md) |
|
||||
| What launch default does a seat in any other rig get? | Under the same conditions `launchOverride()` sets `teamPermissionDefault`: a Claude seat at the floor posture gets `TEAM_CLAUDE_ALLOW` and `TEAM_CLAUDE_ASK` from `operationalLaunchArgs()` in `packages/daemon/src/adapters/kernel-authority.ts`, whose `claudeCommandSettings()` hands the kernel or team lists to the session-only Bash hook `packages/daemon/assets/claude-team-permissions.cjs`, which returns the allow or ask decision; a workspace-write Codex seat gets `--add-dir` roots from `prepareCodexTeamWorkspace()` in `packages/daemon/src/domain/codex-team-workspace.ts`. | [Adapters and runtimes](architecture/adapters-and-runtimes.md) |
|
||||
| How does `rig seat continue` finish a fresh startup that stopped before its context was delivered? | `continueFreshStartup()` in `packages/daemon/src/domain/seat-lifecycle-service.ts`, `canContinueFresh()` in `packages/daemon/src/domain/startup-orchestrator.ts`, the route in `packages/daemon/src/routes/seat.ts`, and the command in `packages/cli/src/commands/seat.ts`. | [Agent spec and startup](architecture/agent-spec-and-startup.md) |
|
||||
| How does `rig context work-install` choose a project and position? | `resolveWorkPosition()` in `packages/cli/src/lib/work-install.ts`, `packages/cli/src/commands/context.ts`, and `packages/daemon/src/domain/workspace/project-catalog.ts`. | [Workspace](architecture/workspace-primitive.md) |
|
||||
| Where do rosters come from? | `packages/cli/src/commands/roster.ts`: `rig roster list`, `show` and `find` read JSON roster files from `<workspace.root>/rosters` (or `--folder`) and are read-only. | [Agent spec and startup](architecture/agent-spec-and-startup.md), "Specialist rosters" |
|
||||
| What telemetry can be read? | `packages/daemon/src/domain/finite-telemetry.ts` (the `events`, `queue-transitions` and `tenures` streams), `/api/telemetry` in `packages/daemon/src/routes/telemetry.ts`, and `packages/cli/src/commands/telemetry.ts`. | [Architecture rules and events](architecture/architecture-rules-and-event-system.md), "Telemetry read surface" |
|
||||
|
||||
## Change and verification guides
|
||||
|
||||
| Need | Read |
|
||||
|---|---|
|
||||
| `architecture/daemon-core.md` | `packages/daemon/src/{startup.ts,server.ts,index.ts}`, `packages/daemon/src/db/migrations/`, `packages/cli/src/index.ts` |
|
||||
| `architecture/adapters-and-runtimes.md` | `packages/daemon/src/domain/runtime-adapter.ts`, `packages/daemon/src/adapters/{claude-code-adapter,codex-runtime-adapter,terminal-adapter}.ts`, `packages/daemon/src/domain/{native-resume-probe,resume-metadata-refresher,codex-thread-id}.ts` |
|
||||
| `architecture/coordination-primitive.md` | `packages/daemon/src/domain/{stream-store,queue-repository,queue-transition-log,hot-potato-enforcer,inbox-handler,outbox-handler}.ts`, `packages/daemon/src/routes/{stream,queue}.ts` |
|
||||
| `architecture/workflow-runtime.md` | `packages/daemon/src/domain/{workflow-projector,workflow-runtime,workflow-instance-store,workflow-spec-cache,workflow-step-trail-log,workflow-validator}.ts`, `packages/daemon/src/domain/policies/workflow-keepalive.ts`, `packages/daemon/src/routes/workflow.ts` |
|
||||
| `architecture/mission-control.md` | `packages/daemon/src/domain/mission-control/`, `packages/daemon/src/middleware/auth-bearer-token.ts`, `packages/daemon/src/routes/mission-control.ts`, `packages/daemon/src/db/migrations/037_mission_control_actions.ts` |
|
||||
| `architecture/agent-spec-and-startup.md` | `packages/daemon/src/domain/{agent-manifest,rigspec-schema,profile-resolver,startup-orchestrator,whoami-service,claim-service}.ts`, `packages/daemon/src/routes/{rigspec,whoami}.ts` |
|
||||
| `architecture/lifecycle-snapshot-restore.md` | `packages/daemon/src/domain/{restore-orchestrator,snapshot-capture,snapshot-repository,checkpoint-store}.ts`, `packages/daemon/src/routes/restore-check.ts`, `packages/cli/src/commands/restore-packet.ts` |
|
||||
| `architecture/transport-and-transcripts.md` | `packages/daemon/src/domain/{session-transport,transcript-store,history-query,ask-service,chat-repository}.ts`, `packages/daemon/src/routes/{transport,transcripts,ask,chat}.ts`, `packages/cli/src/mcp-server.ts` |
|
||||
| `architecture/workspace-primitive.md` | `packages/daemon/src/domain/workspace/`, `packages/cli/src/commands/config-init-workspace.ts`, `packages/daemon/src/db/migrations/{038,039}*`, `packages/ui/src/routes.tsx` |
|
||||
| `architecture/content-surfaces.md` | `packages/daemon/src/domain/{files,progress,steering}*`, `packages/daemon/src/routes/{files,progress,steering}.ts`, `packages/ui/src/routes.tsx` |
|
||||
| `architecture/living-notes-review.md` | `packages/daemon/src/domain/review/{types,compose,gather,freeze,brief-spine}.ts`, `packages/daemon/src/routes/{review,files,slices}.ts`, `packages/cli/src/commands/{scope,proof}.ts`, `packages/ui/src/components/review/` |
|
||||
| `architecture/plugin-agent-image-context-pack.md` | `packages/daemon/src/domain/plugin-discovery-service.ts`, `packages/daemon/src/domain/{agent-images,context-packs}/`, `packages/daemon/src/domain/claude-compaction-enforcer.ts` |
|
||||
| `architecture/packaging-bootstrap-bundles.md` | `packages/daemon/src/domain/{pod-bundle-assembler,bootstrap-orchestrator,bundle-*,package-*,install-*}.ts`, `packages/daemon/src/routes/{bundles,up}.ts` |
|
||||
| `architecture/architecture-rules-and-event-system.md` | `packages/daemon/src/domain/types.ts` (RigEvent union), `packages/daemon/src/routes/{stream,queue}.ts` (SSE watch), `packages/daemon/src/server.ts` (`/api/events`) |
|
||||
| `ui/shell-and-routing.md` | `packages/ui/src/routes.tsx`, `packages/ui/src/components/AppShell.tsx` |
|
||||
| `ui/topology.md` | `packages/ui/src/components/topology/`, `packages/ui/src/lib/{graph,hybrid,multi-rig}-layout.ts` |
|
||||
| `ui/project-and-for-you.md` | `packages/ui/src/routes.tsx`, `packages/ui/src/components/dashboard/vellum/` |
|
||||
| `ui/library-specs-and-design-system.md` | `packages/ui/src/components/specs/`, `packages/daemon/src/domain/{spec-review-service,spec-library-service}.ts`, `../DESIGN.md` (pointer) |
|
||||
| `cli-reference.md` | `packages/cli/src/index.ts`, `packages/cli/src/commands/*` |
|
||||
| Contributor setup and a small change recipe | [CONTRIBUTING.md](../../CONTRIBUTING.md), [ARCHITECTURE.md](../../ARCHITECTURE.md). |
|
||||
| What depends on a sensitive source path | [arteries.md](arteries.md). |
|
||||
| Unit, route, scenario, package and CI coverage | [test-layers.md](test-layers.md), `packages/test-system/README.md`, `.github/workflows/tests.yml`. |
|
||||
| Exact CLI syntax | [cli-reference.md](cli-reference.md), then `packages/cli/src/index.ts`, its command module and current command help. |
|
||||
| As-built metadata | [frontmatter-schema.md](frontmatter-schema.md). |
|
||||
| An older architecture link | [architecture.md](architecture.md), which redirects into this module tree. |
|
||||
| Visual specification | [DESIGN.md](../DESIGN.md). |
|
||||
|
||||
> No per-file flat dump. File-level detail belongs in the code; this codemap
|
||||
> points at the source root and the module that explains it. The per-file
|
||||
> index anti-pattern (old codemap) is retired, not relocated.
|
||||
## Existing web UI references
|
||||
|
||||
These historical documents are still indexed; this pass checks their locations, not their
|
||||
current implementation claims. Consult each page's stamp.
|
||||
|
||||
| Reference | Source area |
|
||||
|---|---|
|
||||
| [UI compatibility pointer](ui.md) | `packages/ui/src/`. |
|
||||
| [Shell and routing](ui/shell-and-routing.md) | `packages/ui/src/routes.tsx`, `packages/ui/src/components/AppShell.tsx`. |
|
||||
| [Topology](ui/topology.md) | `packages/ui/src/components/topology/`. |
|
||||
| [Project and For You](ui/project-and-for-you.md) | `packages/ui/src/routes.tsx`. |
|
||||
| [Library/specs and design system](ui/library-specs-and-design-system.md) | `packages/ui/src/components/specs/`. |
|
||||
@@ -1,65 +1,73 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: As-Built Frontmatter — Pointer + As-Built-Unique Field
|
||||
title: As-Built Frontmatter
|
||||
status: active
|
||||
topics: [knowledge-and-context, frontmatter]
|
||||
domains: [engineering-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
Authoring or updating a doc under docs/as-built/. Tells you which
|
||||
frontmatter convention governs these docs and the one field that is
|
||||
unique to as-built docs.
|
||||
siblings: [README.md]
|
||||
prerequisite-reads: [README.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
Writing or refreshing a source-grounded document under docs/as-built/.
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# As-Built Frontmatter — Pointer + As-Built-Unique Field
|
||||
# As-Built Frontmatter
|
||||
|
||||
Every doc under `docs/as-built/` carries YAML frontmatter. The shape is
|
||||
**not** defined here. It is the substrate-wide context-frontmatter
|
||||
convention; this doc only points to it and documents the one field that
|
||||
is unique to as-built docs.
|
||||
Source snapshot: `e8f0ab340db773392ec8be75b072d1c0f3068a50`. This describes the source at that commit;
|
||||
it does not establish the version or behavior of a running daemon.
|
||||
|
||||
## Canonical convention (do not duplicate — read it)
|
||||
Frontmatter makes an as-built page discoverable and records the source it
|
||||
was checked against. This page describes the convention visible in this
|
||||
repository. It does not require an unpublished schema or an internal
|
||||
workspace to interpret the fields.
|
||||
|
||||
`openrig-work/conventions/frontmatter-for-context/README.md` is the
|
||||
authoritative schema: required floor (`kind` + `title` + `status` +
|
||||
`applies-when`), recommended fields (`topics`, `domains`, `siblings`,
|
||||
`prerequisite-reads`), and the controlled vocabularies for `kind:`,
|
||||
`topics:`, and `domains:`. Author against that convention. This doc adds
|
||||
nothing to it except the field below.
|
||||
## Fields
|
||||
|
||||
## `kind: as-built`
|
||||
The corpus uses these fields; see the [module index](README.md) and
|
||||
[CLI reference](cli-reference.md) for examples.
|
||||
|
||||
These docs use `kind: as-built`. That value is in the convention's
|
||||
controlled `kind:` vocabulary (first row of its "Controlled vocabulary —
|
||||
`kind:`" table, source `openrig/docs/as-built/*`). Using it is conformant,
|
||||
not an extension.
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| `kind: as-built` | Identifies a description of implemented behavior. |
|
||||
| `title` | Human-readable subject of the page. |
|
||||
| `status` | Document status, such as `active`; not a daemon or feature status. |
|
||||
| `applies-when` | Short retrieval cue explaining when to open the page. |
|
||||
| `topics`, `domains` | Lists of indexing labels used in this corpus. They do not select permissions or runtime behavior. |
|
||||
| `siblings`, `prerequisite-reads` | Related pages and reading order, when useful. Paths are relative to the containing page. |
|
||||
| `last-verified-against-source` | A resolvable Git commit whose source was used to check the page. |
|
||||
| `last-updated` | Date of the document update, in `YYYY-MM-DD` form. |
|
||||
|
||||
## `last-verified-against-source: <sha>` (as-built-unique)
|
||||
## Verification stamp
|
||||
|
||||
As-built docs describe the running system. They drift when the source
|
||||
drifts. The as-built-unique field records the exact commit the doc was
|
||||
last verified against:
|
||||
Use a commit ID, preferably full length, rather than a branch name or a
|
||||
moving `HEAD` label. A source stamp identifies what was read; it is not proof
|
||||
that an installed package or a running system uses that source. Keep runtime
|
||||
observations separately qualified.
|
||||
|
||||
```yaml
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
```
|
||||
|
||||
- **Value:** the short SHA of the source HEAD the doc was verified
|
||||
against at its last edit. Pair with `last-updated: <iso-date>`.
|
||||
- **Why it is here and not in the convention:** the convention is
|
||||
substrate-wide; most context docs do not track a source SHA. This
|
||||
field is meaningful only for docs that mirror code, i.e. as-built
|
||||
docs. It is the per-module drift-detection anchor the
|
||||
context-architecture-v1 mission dogfoods on its own production.
|
||||
- **Discipline:** when re-verifying a module against a newer HEAD,
|
||||
bump this field (and `last-updated`) and carry an inline
|
||||
`re-confirmed <file:line> @HEAD` annotation on each load-bearing
|
||||
claim that was re-checked.
|
||||
Check that the commit resolves with `git cat-file -t <commit>` in a checkout
|
||||
containing that history. Verify the paths, symbols, and numeric claims cited
|
||||
by the page at that commit before changing its stamp. A newer date alone
|
||||
does not refresh the content. Missing history in a shallow checkout and an
|
||||
invalid commit ID are different conditions.
|
||||
|
||||
## See also
|
||||
Use repository links and named symbols for source references. For numeric
|
||||
inventories, state what is counted and how it was obtained; for example, the
|
||||
CLI reference distinguishes registered command objects from aliases and
|
||||
Commander-generated help.
|
||||
|
||||
- `openrig-work/conventions/frontmatter-for-context/README.md` — the canonical schema
|
||||
- `README.md` — the as-built corpus map of territory
|
||||
## What the repository guard checks
|
||||
|
||||
[`scripts/check-docs-guard.mjs`](../../scripts/check-docs-guard.mjs)
|
||||
checks allowed tracked documentation paths. Its `findBlockedDocsPaths`
|
||||
function does **not** validate this frontmatter or prove the prose matches
|
||||
source. Passing that guard should not be reported as a schema or factual
|
||||
accuracy check.
|
||||
|
||||
[As-built pages](README.md) and [operator reference pages](../reference/help.md)
|
||||
serve different purposes. `docs/reference/` is staged by
|
||||
[`scripts/build-package.sh`](../../scripts/build-package.sh); placing content
|
||||
there changes what is distributed in the package.
|
||||
@@ -0,0 +1,549 @@
|
||||
---
|
||||
kind: as-built
|
||||
title: Test layers — what to run before you push, and what each layer proves
|
||||
status: active
|
||||
applies-when: |
|
||||
You changed OpenRig and want to know which checks to run before opening a pull
|
||||
request, what each check actually proves, and what CI will run for you. Also
|
||||
read it before claiming a stub-agent scenario covers a behaviour.
|
||||
siblings: [arteries.md, README.md, codemap.md]
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# Test layers: what to run before you push, and what each layer proves
|
||||
|
||||
> **This page is a map, not the territory.** It was checked against source at `e8f0ab34`
|
||||
> and it will drift. It is also incomplete on purpose. Before you rely on a command or a
|
||||
> claim here, read the script it names or run it. If a behaviour is missing from this page
|
||||
> (or from [arteries.md](arteries.md)), that tells you nothing about whether a change to it
|
||||
> is safe.
|
||||
|
||||
## Short version
|
||||
|
||||
For every change, run what the pull-request template asks for:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
npm run lint # typecheck only; there is no formatter or style linter
|
||||
npm test # = npm run test:repo && npm run test:workspaces
|
||||
```
|
||||
|
||||
Then add the layers your change calls for, using the ladder below. CI also runs a
|
||||
network-denied macOS test pass and a containerised installed-package scenario on every PR
|
||||
to `main`. In the PR description, say which layers you ran and which you could not run.
|
||||
|
||||
## The ladder
|
||||
|
||||
| Layer | Command | What it proves | What it cannot prove | When to run it |
|
||||
|---|---|---|---|---|
|
||||
| Typecheck | `npm run lint` | Builds the daemon, then `tsc --noEmit` passes for daemon, ui, cli and tui | Any runtime behaviour. Formatting. | Every change |
|
||||
| Build | `npm run build` | All four workspaces compile (the UI also runs `vite build`) | That the *published* package is complete (see Packaging) | Every change |
|
||||
| Repo checks | `npm run test:repo` | Builds the daemon, then runs `node --test --test-concurrency=1 scripts/*.test.mjs`, `scripts/check-first-run-references.mjs`, `scripts/check-docs-guard.mjs`, `mirror-skills --check` and `generate-context-packs --check` | Package behaviour | Every change. It matters most when you touch `scripts/`, `docs/` or skills. |
|
||||
| Package suites | `npm run test:workspaces`. One package: `npm run test -w packages/cli`. One file, from the repo root: `npx vitest run packages/cli/test/<file>.test.ts` | Vitest unit and integration tests for daemon, cli and tui. Some of them start real scenario daemons, tmux sessions and stub runners. | The installed package. Real Claude Code or Codex. CI's sandbox conditions (see CI). | Every change (`npm test` covers it) |
|
||||
| Web UI suite | `npm run test:ui` | jsdom unit tests for `packages/ui` | Real browser rendering | When you touch `packages/ui`, or a daemon API the UI reads. The web UI is in maintenance mode, so this suite is advisory locally and is not part of `npm test`. |
|
||||
| Generated files | `npm run mirror-skills:check`, `npm run generate-context-packs:check` (both part of `npm test`) | Every copy of each shipped skill matches its recorded hash, and every static context pack passes the daemon's own manifest parser and the leak scan | Whether the skill content is any good | After you edit a shipped skill: edit the same file in every copy that carries it, run `node scripts/regen-edge-digests.mjs`, then the check. The full `npm run mirror-skills` apply needs maintainer-only inputs. See "A shipped skill" in [ARCHITECTURE.md](../../ARCHITECTURE.md#a-shipped-skill). |
|
||||
| Local gate | `npm run gate` | Lint plus `npm test` on a clean tree, tied to `HEAD`, one run per machine at a time, with a JSON receipt | Anything that lint and `npm test` don't already prove | Optional. Useful when several agents share one machine, or when you want a receipt tied to a commit. |
|
||||
| Packaging | `npm run build:package`, `npm run test:tui-package`, `bash scripts/smoke-fresh-install.sh` | The publishable `@openrig/cli` assembles, the packed TUI launches, and a fresh install starts a daemon | Behaviour beyond startup | When you touch dependencies, bin entries, build scripts, or files a package ships |
|
||||
| Stub scenarios, host mode | `node --import tsx packages/daemon/scripts/run-scenarios.mjs [scenario.yaml …]` | A scenario's steps and assertions pass against a scratch daemon, real tmux and `runtime: stub` seats | The installed package. Seeded-regression pairing. Everything under [What a stub can and cannot prove](#what-a-stub-can-and-cannot-prove). | While you write or debug a scenario |
|
||||
| Stub scenarios, installed package in a container | CI job `installed-scenario`. Before pushing: `DOCKER_HOST=ssh://<your-host> bash scripts/run-pr-scenarios.sh --remote …` | A clean Linux install starts. A claimed queue item survives a daemon restart. A planted fault is caught at the intended assertion. Two stub seats' scripted output reads back through `rig transcript` and `rig capture` (passing form only). | Native runtimes. Most command families (see [Help wanted](#help-wanted-command-families-without-a-behavioural-scenario)). | Changes that can affect daemon startup, packaging, queue durability or the scenario harness |
|
||||
| Live-model evals | `npm run eval -w packages/daemon -- --provider rig …` | Whether a real Claude Code seat runs the expected `rig context get <entry>` for natural prompts | Determinism. Codex. Anything without a case. | Changes to shipped skills or context entries meant to steer agents. **Uses provider credits.** |
|
||||
| Testbed runbooks | `docker/testbed/runbooks/` (manual) | The testbed image itself works as a test environment | Product behaviour | Only when you change the testbed image or the container plumbing |
|
||||
|
||||
Some package tests are gated. `OPENRIG_E2E_REAL_CODEX=1` runs two daemon e2e tests against a
|
||||
real Codex (they also need tmux, `codex` and `~/.codex/auth.json`, and they use provider
|
||||
credits). `OPENRIG_REAL_CLAUDE_INTEGRATION=1` enables only a placeholder: it requires
|
||||
`OPENRIG_PARENT_NATIVE_ID` and checks its format, and no real-Claude fork test exists yet.
|
||||
Without tmux, some tmux tests skip and others (such as `transcript-seat-env-native.test.ts`)
|
||||
fail. The CLI's release-tag tests skip without local tags unless `CI=true`, so a green local
|
||||
run without tags has skipped them. CI installs tmux and fetches full history.
|
||||
|
||||
The TUI has its own vitest suite (`npm run test -w packages/tui`, which builds the TUI first)
|
||||
and runs in the `tui` leg of `package-tests`; `npm run test:tui-package` launches the packed
|
||||
TUI in demo mode. The `tui_socket` scenario surface exists, but the only scenarios that use it
|
||||
are not yet runnable checks.
|
||||
|
||||
### Gotchas that cost people time
|
||||
|
||||
- **Running inside a seat.** The root `vitest.config.ts` makes a root-level file run use its
|
||||
package's config. The cli, daemon and TUI configs all run the shared root
|
||||
`test/hermetic-env.setup.ts` before any test file. It clears the inherited `OPENRIG_*` and `RIGGED_*` instance selectors (connection,
|
||||
database, workspace and topology roots, seat identity, bearer tokens), forces a fixture
|
||||
`OPENRIG_HOME` and fails if the home is not fixture-scoped. It also wraps `fetch`, so a real
|
||||
request to anything other than a loopback ephemeral-port fixture or a registered target fails.
|
||||
Unless `OPENRIG_E2E_REAL_CODEX=1`, it points `HOME` and the XDG directories at a fixture user
|
||||
home and clears `CODEX_HOME`; that opt-in keeps the real homes. A test can still set its own
|
||||
overrides after setup. The UI suite (jsdom) uses its own `packages/ui/test/setup.ts`.
|
||||
|
||||
- **Stale vendored daemon.** `npm run build:package` leaves an assembled copy of the daemon at
|
||||
`packages/cli/daemon/` (gitignored). If you change daemon source afterwards,
|
||||
`scripts/check-cli-daemon-freshness.test.mjs` inside `test:repo` can fail on the stale copy.
|
||||
Re-run `npm run build:package` or delete `packages/cli/daemon/`. `npm run gate` deletes it for you.
|
||||
- **Local passes, CI fails.** CI runs the package suites with a scratch `HOME`, no
|
||||
credentials, and outbound network blocked except to localhost (see below). A test that reads
|
||||
your real home directory, a running daemon, the network or a credential can pass on your
|
||||
machine and fail in CI.
|
||||
- **Docs location.** `check-docs-guard` fails if a tracked file under `docs/` sits outside
|
||||
`docs/as-built/`, `docs/reference/` or `docs/releases/`. `docs/DESIGN.md` is the one allowed
|
||||
exception.
|
||||
|
||||
## What CI runs on your pull request
|
||||
|
||||
`.github/workflows/tests.yml` runs on pull requests to `main` and on manual dispatch, all on
|
||||
Node 22. Each suite is its own job, so one failure doesn't hide the others. No provider
|
||||
credentials are supplied.
|
||||
|
||||
| Job | Runner | What it does |
|
||||
|---|---|---|
|
||||
| `build-and-package` | ubuntu | `npm ci` and `npm run build`. Then `npm pack --dry-run` for cli, daemon and tui, with each file list uploaded as the `pack-lists` artifact. The job only records the lists; reviewers read them. |
|
||||
| `typecheck` | ubuntu | `npm run lint` |
|
||||
| `repo-checks` | ubuntu | `npm run test:repo` |
|
||||
| `package-tests` | macOS, matrix `daemon`, `cli`, `tui`, `ui` (fail-fast off) | Installs tmux, runs `npm ci` and `npm run build`. Then runs `npm run test -w packages/<pkg>` under `env -i` with a scratch `HOME`/`TMPDIR`/`TMUX_TMPDIR` and a `sandbox-exec` profile that blocks outbound network except localhost and the job's own socket directory. `scripts/ci-macos-process-probe.mjs` runs first. |
|
||||
| `installed-scenario` | ubuntu | `bash scripts/run-pr-scenarios.sh` (eight container runs, described below). Uploads `dist/pr-scenarios/` as `installed-scenario-evidence`, including on failure. |
|
||||
|
||||
A second workflow, `.github/workflows/portability-report.yml` (job `portability-report`),
|
||||
lists lines your PR adds that contain machine-specific values: credentials, home or temp
|
||||
paths, network addresses, email addresses. It never fails because of a finding. To see the
|
||||
matched text locally (credentials show only their first characters), run
|
||||
`node scripts/portability-report.mjs`. By default it compares the
|
||||
merge-base with `origin/main` against `HEAD`; pass `--staged` to check staged changes instead.
|
||||
|
||||
The UI suite is a CI matrix leg even though it is advisory locally. Check your PR's status to
|
||||
see whether a red UI leg is treated as blocking. Branch-protection settings are not in the
|
||||
repository.
|
||||
|
||||
## The local gate: `npm run gate`
|
||||
|
||||
`scripts/gate-lane.mjs` runs two legs: `npm run lint` and `npm run test`. It does not run
|
||||
`test:ui`, the scenarios or the packaging checks. On top of those legs it:
|
||||
|
||||
- holds a machine-wide lock on localhost port 40404 (override with `OPENRIG_GATE_LANE_PORT`).
|
||||
A second gate exits 2 straight away instead of waiting.
|
||||
- refuses a dirty worktree. Refuses a `node_modules` that is a symlink, or `@openrig/*`
|
||||
workspace links that resolve outside this worktree. Both are common with shared git worktrees.
|
||||
- deletes the stale vendored daemon bundle at `packages/cli/daemon/` before the legs run.
|
||||
- writes `gate-lane-verdict.json` at the repo root, or at `OPENRIG_GATE_VERDICT`, which must
|
||||
point inside the current worktree (a path outside it exits 3 before the legs run). The file holds the `HEAD` SHA, per-leg results and durations, and a note of other
|
||||
`node`/`vitest`/`tsc` processes running at the time. It fails if `HEAD` changes during the
|
||||
run. The file is not gitignored, so don't commit it.
|
||||
|
||||
Exit codes: 0 pass, 1 a leg failed, 2 lane busy, 3 the runner itself errored.
|
||||
`OPENRIG_GATE_LANE_SMOKE=1` skips the legs to test the wiring only, and the verdict records
|
||||
`smoke: true`.
|
||||
|
||||
## Stub-agent scenarios
|
||||
|
||||
### What's in the box
|
||||
|
||||
| Piece | Where |
|
||||
|---|---|
|
||||
| `runtime: stub` adapter | `packages/daemon/src/adapters/stub-runtime-adapter.ts`, with `stub-runner.ts` (the process that runs in the pane), `stub-script.ts` (the launch-script format) and `stub-compaction.ts` / `stub-restore.ts` |
|
||||
| Scenario format and runner | `packages/daemon/test/helpers/scenario-*.ts`. Step verbs: `up`, `down`, `send`, `restart`, `daemon`, `seed_regression` are bound. `restore`, `emit`, `mutate`, `policy` parse but are unbound. Assertion surfaces: `ps`, `queue`, `stream`, `scope`, `pane`, `transcript`, `tui_socket`, `policy_provenance`. |
|
||||
| Scenario library | `packages/test-system/scenarios/`: 16 scenarios, 7 stub topologies, `agents/`, `culture.md`, per-seat stub scripts in `scripts/` |
|
||||
| Runner fixtures | `packages/daemon/test/fixtures/scenarios/` (`scenario-01-per-seat-scripts`, `scenario-02-baton`, `scenario-10-one-view-state`) |
|
||||
| Host runner | `packages/daemon/scripts/run-scenarios.mjs` |
|
||||
| In-container entry and result check | `packages/test-system/ci/run.mjs`, `packages/test-system/ci/result.mjs` |
|
||||
| PR orchestrator | `scripts/run-pr-scenarios.sh` |
|
||||
| Image | `docker/testbed/Dockerfile`, built by `scripts/build-testbed-image.sh`; `docker/testbed/Dockerfile.scenarios` is layered on top by `scripts/run-pr-scenarios.sh` |
|
||||
|
||||
### Host mode: while you're writing a scenario
|
||||
|
||||
```sh
|
||||
npm run build # the runner drives packages/cli/dist/bin-wrapper.js
|
||||
node --import tsx packages/daemon/scripts/run-scenarios.mjs # the fixture scenario-*.yaml files
|
||||
node --import tsx packages/daemon/scripts/run-scenarios.mjs packages/daemon/test/fixtures/scenarios/scenario-02-baton.yaml
|
||||
```
|
||||
|
||||
- The runner creates a scratch `HOME`/`OPENRIG_HOME`, its own tmux server and its own
|
||||
daemon. It builds the scenario environment from `HOME`, `PATH` and `TERM` only, so your
|
||||
shell's daemon-target variables (`OPENRIG_URL`, `OPENRIG_PORT` and similar), `TMUX` and
|
||||
`OPENRIG_TEST_CLOCK_NOW` are dropped rather than inherited. It does not touch the daemon or
|
||||
tmux server your own session uses.
|
||||
- It accepts YAML paths only. Any flag, including `--container`, is refused before anything runs.
|
||||
- It supplies no fault controller, so any scenario with a `seed_regression` step fails loudly
|
||||
at that step. That includes the library's `queue-baton-survives-restart`. Use the container
|
||||
path to run the seeded pair.
|
||||
- `scenario-10-one-view-state` declares its own normaliser for its cross-surface `equals`
|
||||
check. Whether it passes in host mode has not been verified.
|
||||
|
||||
### Container mode: the scenario pack CI runs
|
||||
|
||||
In CI the `installed-scenario` job runs `scripts/run-pr-scenarios.sh` with no arguments:
|
||||
both baton cases (`fixture` = `scenario-02-baton.yaml`, `library` = `queue-baton-survives-restart.yaml`),
|
||||
each run three times as healthy, then lost-baton, then healthy. Then the two stub-script
|
||||
cases (`transcript` = `transcript-reads-addressed-seat.yaml`, `capture` =
|
||||
`capture-returns-addressed-seat.yaml`) run once each, healthy only. That is eight fresh
|
||||
containers.
|
||||
|
||||
To run a case before you push, point `DOCKER_HOST` at a Docker engine you control over SSH:
|
||||
|
||||
```sh
|
||||
npm ci
|
||||
DOCKER_HOST=ssh://<your-host> bash scripts/run-pr-scenarios.sh \
|
||||
--remote --case library --mode healthy --out dist/scenario-check
|
||||
```
|
||||
|
||||
- Options: `--case fixture|library|transcript|capture` (default: all four), `--mode
|
||||
healthy|lost-baton` (default: healthy, lost-baton, healthy) and `--out <dir>` (default
|
||||
`dist/pr-scenarios`). `--mode` applies to `fixture` and `library`; `transcript` and `capture`
|
||||
always run once, healthy. `--mode lost-baton` leaves them out of a full run, and
|
||||
`--case transcript|capture --mode lost-baton` is refused with exit 2. Use a fresh `--out`
|
||||
directory for each run you want to keep.
|
||||
- `--remote` accepts only `ssh://` values for `DOCKER_HOST`, and it refuses if
|
||||
`DOCKER_CONTEXT` is set. The engine must be Linux on amd64 or arm64. The script reads the
|
||||
*server's* platform, so a macOS/arm64 laptop driving a Linux/amd64 engine works.
|
||||
- Without `--remote`, the script refuses to run anywhere except GitHub Actions on Linux.
|
||||
- The source build and pack run on your machine. The image build and the containers run on
|
||||
the engine. Building the image needs network access for the pinned base image, Node and
|
||||
dependencies. The scenario containers have no network.
|
||||
- The image is built from your working tree but tagged with `git rev-parse HEAD`. Commit
|
||||
first, so the tag names what you actually tested.
|
||||
- It never pushes images. It removes only the containers it named. If the SSH connection
|
||||
drops, it prints the name of any container it could not remove.
|
||||
|
||||
What happens, in order:
|
||||
|
||||
1. `scripts/build-testbed-image.sh` packs `@openrig/cli` from your tree. It installs the
|
||||
package into a digest-pinned Debian image with Node, checks `rig --version`, and checks
|
||||
that the installed daemon loads. The result is tagged `openrig-testbed:<sha>`.
|
||||
2. `docker/testbed/Dockerfile.scenarios` adds a bundled copy of
|
||||
`packages/test-system/ci/run.mjs` plus the fixture and library scenarios.
|
||||
3. Each run uses a fresh container: `--network none`, read-only root plus a 512 MB `/tmp`
|
||||
tmpfs, `--cap-drop ALL`, `no-new-privileges`, non-root, 2 CPUs, 2 GiB, 256 PIDs and a
|
||||
300-second deadline.
|
||||
4. In lost-baton mode, the runner stops the daemon, changes exactly one queue row in the
|
||||
scratch database from `in-progress` to `pending`, and restarts the daemon.
|
||||
`packages/test-system/ci/result.mjs` then accepts only one outcome: a failure at the
|
||||
post-restart queue assertion, with `baton-1` observed as `pending` at the expected
|
||||
destination. A startup error, a timeout or any other failure does not count as a caught
|
||||
regression. Healthy runs must pass with no fault armed.
|
||||
5. The `transcript` and `capture` runs are passing form only: `result.mjs` accepts only a
|
||||
clean PASS of the named scenario, with no seed or fault. Their planted failures are
|
||||
product-code mutations, so they stay per-PR evidence and are not CI runs. Stub scripts
|
||||
work here because the runner executes the pipeline in host mode inside the image.
|
||||
|
||||
A single healthy run proves that one leg only. The paired fault run is what shows the
|
||||
assertion can catch the failure.
|
||||
|
||||
Adding a new case to this pack today means changing three files:
|
||||
`scripts/run-pr-scenarios.sh` (the `--case` list), `CASES` in
|
||||
`packages/test-system/ci/result.mjs`, and the fault controller in
|
||||
`packages/test-system/ci/run.mjs`, which is currently specific to the baton row. A
|
||||
passing-only case needs no fault controller: add it to `PASSING_CASES` and the `--case` list
|
||||
in `scripts/run-pr-scenarios.sh`, and to `PASSING_CASES` in `result.mjs`.
|
||||
|
||||
### What a green stub scenario proves
|
||||
|
||||
- The packaged CLI installs into a clean Linux container and starts a daemon.
|
||||
- `rig up` and `rig down` of a stub topology go through the real daemon, tmux, SQLite and CLI.
|
||||
- Queue state, read through the shipped `rig queue list` path, survives a daemon restart.
|
||||
- With the paired seeded run: the assertion fails when that state is broken.
|
||||
|
||||
### What a stub can and cannot prove
|
||||
|
||||
The stub does not fabricate product outputs. It runs the real daemon, tmux and CLI. But it is
|
||||
not Claude Code or Codex, and it differs from them in ways that decide what a stub green
|
||||
means:
|
||||
|
||||
| Area | What the stub does at `e8f0ab34` | What that means for your test |
|
||||
|---|---|---|
|
||||
| Consuming a message | `stub-runner.ts` runs its launch script once and then idles. It has no stdin reader, socket or other input channel. The default script prints `[stub] scripted reply: acknowledged` at boot, before anything has been sent. | A pane showing a reply, or your echoed text, does not prove the message was consumed. No stub scenario can currently prove "delivered and answered". Separately, `rig send --verify` means "appeared in the pane", not acknowledgement (see `rig send --help`), and the scenario `send` step doesn't pass `--verify` at all. |
|
||||
| Launch path | `StubRuntimeAdapter` types `node <stub-runner> …` into the pane (`tmux.sendText`, then Enter). Claude Code with an explicit permission mode launches through a managed launch (`ClaudeManagedLaunch.prepare`, `tmux.sendShellCommand`). Otherwise Claude Code, Codex and Pi launch through `SeatLaunchEnvironment.command` and `tmux.sendShellCommand`; the stub adapter does not get that environment. The shell-foreground check in `session-transport.ts` (`unverifiedShellForeground`) runs for every runtime except `terminal` and `claude-code`, and only `codex` has a native-process proof; Claude Code ordinary delivery applies its own uncertainty policy at the input boundary. | A stub seat doesn't exercise the seat launch environment, managed launch, wrappers or native-process identity (the class behind #197). Wrapping the stub in a shell wouldn't change that. |
|
||||
| Permissions | `validateNativePermissionSelection` accepts only `codex` and `claude-code`. The stub carries the resolved `floor` / `full_bypass` posture, including one set by a declarative `permission_policy`, into its runner's `--posture` argument and READY line. It ignores non-interruptive launches, and it doesn't exercise Claude or Codex native permission behaviour. | Testing Claude permission modes or per-seat permission selection needs a real runtime. |
|
||||
| Queue pickup and wake | No stub worker reacts to a nudge by claiming an item, working on it and handing it back. | The baton scenarios prove the claim *survives a restart*. They don't prove delivery, pickup or wake. |
|
||||
| Reboot / tmux reset | `daemon: {op: restart}` restarts only the scenario daemon, and the tmux server keeps running. No step resets tmux. | Recycled pane IDs and stale bindings after a real reboot (the class behind #141) are not covered by a daemon restart. |
|
||||
| Multi-rig and same-name identities | The `up` step always uses the scenario's top-level `topology` and ignores a per-step override. No step archives or removes a rig. | Cross-rig scope, same-name generations and archive/remove routing (the class behind #174) need runner work before they can be tested. |
|
||||
| Step-time restore, compaction, mutation, policy | `restore`, `emit`, `mutate` and `policy` parse, but the runner throws `UnboundActionError` when it reaches them. Behaviours run only from a per-seat launch script (`env.stub_scripts`). A seat `restart` step maps to `rig launch`, which is not native resume or fork. | Step-time compaction and restore, and native resume and fork, are not covered. |
|
||||
| Skill projection | The stub projects skills into `<cwd>/.openrig/stub/skills/`. Claude Code projects into `<cwd>/.claude/skills/`. | A stub green says nothing about provider-specific projection (the class behind #159). |
|
||||
| Model and provider | No model, no tokens. Stub scripts refuse `usage_limit` because it only exists on real runtimes. | Model behaviour, provider usage limits and real context usage need live evals or a real runtime. |
|
||||
| Platform | CI's scenario job runs in a Linux container. | macOS process behaviour is covered by the macOS `package-tests` jobs, not by scenarios. |
|
||||
|
||||
The most useful next pieces of harness work, roughly in dependency order:
|
||||
|
||||
1. A stub that consumes input and answers a fresh nonce.
|
||||
2. A model-free test entry point shaped like a provider, that goes through the real native
|
||||
adapter and managed launch. Package tests such as `claude-interactive-launch.test.ts` and
|
||||
the `*-native.test.ts` files already drive the real adapters through tmux against an
|
||||
offline recorder; they prove command resolution and arguments, not readiness, history or
|
||||
answers.
|
||||
3. Lifecycle and identity steps (tmux reset, archive/remove).
|
||||
4. Then pickup, wake and restore scenarios.
|
||||
|
||||
### Library status
|
||||
|
||||
Three scenarios run in CI: `queue-baton-survives-restart` (since #243) with its seeded pair,
|
||||
and `transcript-reads-addressed-seat` and `capture-returns-addressed-seat` in passing form
|
||||
only. Three more, `down-stops-every-seat`, `send-renders-in-addressed-pane` and
|
||||
`up-relaunch-restores-a-two-seat-team`, run with the local runner but not in CI. Those five
|
||||
later scenarios are not in this table; see "Status
|
||||
today" under [Help wanted](#help-wanted-command-families-without-a-behavioural-scenario). The
|
||||
other ten scenarios are authored but not yet runnable as meaningful checks:
|
||||
|
||||
| Scenario | Why it doesn't run as a check yet | Smallest useful next step |
|
||||
|---|---|---|
|
||||
| `queue-baton-survives-restart` | Runs in CI: healthy / seeded `baton-drop` / healthy | Extend it to handoff, block, resolve and unclaim |
|
||||
| `clean-lifecycle-no-residue` | The post-`down` "nothing left over" assertion is commented out, because the format has no way to say "empty" | Add a real absence assertion |
|
||||
| `ps-scope-honesty` | The second `up` reuses the first topology, and the expected match tolerates the other rig | Make `up` honour the per-step topology, then assert the exclusion |
|
||||
| `send-verify-means-rendered` | The `send` step drops `verify`, and the stub has no input loop | Build the input-consuming stub and assert a response derived from the nonce |
|
||||
| `home-divergence-preseed-visible` | The pipeline ignores `env.pre_existing_tmux`, and the default script never prints the expected `trust reached` | Recreate the real HOME split and whatever consumes it |
|
||||
| `compaction-restore-resumes-role` | Step-time `emit` is unbound | Input-triggered hooks plus evidence specific to one seat |
|
||||
| `stream-emit-durable-replay-live` | Step-time `emit` is unbound | Assert the actual stream contract |
|
||||
| `policy-posture-survives-structural-edit` | `policy` and `mutate` are unbound | Rewrite around current per-seat permission selection and an observed launch |
|
||||
| `locked-restore-never-permissive` | `policy` and `restore` are unbound | Keep the invariant (the selection survives a restore) and use current modes |
|
||||
| `one-view-state-after-mutation-storm` | The validator rejects its bare-list `equals` (`EQUALS_NOT_DECLARATIVE`), and `mutate` is unbound | Declare the comparable fields and real mutations |
|
||||
| `kill-daemon-mid-handoff` | A pane `send` doesn't create the queue item the next assertion expects, and later assertions use a shape the queue reader doesn't return | Rebuild the setup before treating a red result as meaningful |
|
||||
|
||||
In most of these files, `seed_regression` steps that come *after* the assertions are
|
||||
historical markers, not executable fault injections.
|
||||
|
||||
### Ordinary CLI journeys with stub seats
|
||||
|
||||
`packages/daemon/test/stub-cli-journeys.integration.test.ts` reuses the scenario
|
||||
helpers with one private daemon and two stub rigs. It checks three contracts
|
||||
through the real CLI: verified send appears in the addressed pane but not its
|
||||
sibling; current-rig and explicit rig listings contain exactly the expected seats;
|
||||
and a filtered stream item survives daemon restart and an idempotent emit retry.
|
||||
|
||||
After building, run the focused file with:
|
||||
|
||||
```sh
|
||||
npm test -w @openrig/daemon -- test/stub-cli-journeys.integration.test.ts
|
||||
```
|
||||
|
||||
The file also runs in the daemon package-test job. It covers these command
|
||||
contracts without extending the YAML grammar. The corresponding library scenarios
|
||||
above remain pending: pane rendering does not prove input consumption, durable
|
||||
stream replay does not prove live subscription, and this test does not exercise
|
||||
native providers, restore or policy changes.
|
||||
|
||||
## If your change touches an artery, add or extend a scenario (or say why not)
|
||||
|
||||
[arteries.md](arteries.md) lists the behaviours that everything else depends on. If your
|
||||
diff can affect one, your PR description should do one of these:
|
||||
|
||||
- link the scenario that covers it, and say which layer you ran it in;
|
||||
- add or extend a scenario. For a regression scenario, show that the fixed code passes and
|
||||
that the planted old defect fails *at the intended assertion*. A setup crash or a timeout
|
||||
doesn't count;
|
||||
- or say precisely why the stub can't exercise it. Use the table above, which covers
|
||||
message consumption, native launch and permissions, tmux reset and so on. Then say what
|
||||
you ran instead, for example a manual run with the real runtime.
|
||||
|
||||
This is part of normal review, not an extra approval step. It also isn't a claim that
|
||||
changes outside the arteries map are safe.
|
||||
|
||||
## Live-model evals
|
||||
|
||||
`packages/test-system/evals/` is a separate check from scenarios. A real seat receives a
|
||||
natural prompt and decides what to do. A case passes when the seat's transcript shows the
|
||||
expected `rig context get <entry>` command, in the expected order. Cases live in
|
||||
`cases/selection.yaml` (10) and `cases/loading.yaml` (4).
|
||||
|
||||
```sh
|
||||
# Harness check: a fake provider fed a JSON map of prompt -> transcript.
|
||||
# Any prompt without a transcript counts as an error, never as a pass.
|
||||
npm run eval -w packages/daemon -- --transcripts <transcripts.json> --out <result.json>
|
||||
|
||||
# Live run: drives a real Claude Code seat
|
||||
npm run eval -w packages/daemon -- --provider rig --seat <session> --out <result.json>
|
||||
npm run eval -w packages/daemon -- --provider rig --seat-spec "$PWD/<one-seat-rig.yaml>" --out <result.json>
|
||||
```
|
||||
|
||||
- **Live runs use provider credits, and the results vary from run to run.** CI never runs them.
|
||||
- `--provider rig` needs `OPENRIG_HOME` set, and it supports Claude Code seats only. It
|
||||
refuses a Codex seat.
|
||||
- `--seat-spec` runs `rig up` of a scratch rig against **your current daemon**, then
|
||||
`rig down` at the end. `--seat` attaches to an existing seat and leaves it running.
|
||||
- `evals/rig-seat/example-scratch-rig.yaml` sets `permission_policy: builtin:yolo` and
|
||||
`cwd: "."`. It also references `local:agents/blank`, and no such agent directory ships
|
||||
beside it. Read the file and expect to adapt it before you use it.
|
||||
- Before any run, the runner builds the production context package in a temp directory and
|
||||
refuses if any case's entry doesn't resolve in it.
|
||||
- `npm test` covers the harness without a model: the case schema, the grader, the fixtures
|
||||
and production entry resolution (`packages/daemon/test/eval-*.test.ts`).
|
||||
|
||||
## Docker testbed runbooks
|
||||
|
||||
`docker/testbed/runbooks/` holds manual procedures, L0 to L6, that you run on a machine with
|
||||
Docker. They check the testbed image itself:
|
||||
|
||||
- L0: resolve the pinned base image and the stub asset list
|
||||
- L1: PTY allocation
|
||||
- L2: tmux server lifecycle
|
||||
- L3: the daemon inside a container, settling a stub rig
|
||||
- L4: the scenario environment still refuses a foreign daemon target inside a container
|
||||
- L5: several containers acting as named hosts on one Docker network
|
||||
- L6: the scenario runner driving a scenario through a container
|
||||
|
||||
Use them when you change `docker/testbed/`, `scripts/build-testbed-image.sh` or the
|
||||
container staging helpers. Ordinary PRs don't need them. They were written before the CI
|
||||
scenario job and contain internal planning references, so treat them as procedures to adapt,
|
||||
not as current status.
|
||||
|
||||
## Help wanted: command families without a behavioural scenario
|
||||
|
||||
> This list may be stale. It was taken from the `rig --help` tree of 0.6.3: 85 visible
|
||||
> top-level families. At `e8f0ab34`, `packages/cli/src/index.ts` registers 87 top-level
|
||||
> commands; `roster` and `telemetry` were added since and have no row yet. Before picking one up, check `packages/test-system/scenarios/`, the cases in
|
||||
> `scripts/run-pr-scenarios.sh`, and the current `rig --help`.
|
||||
> One scenario doesn't cover a family's every option, sequence or platform. A `--help`
|
||||
> check only proves the command is discoverable. Existing unit tests are not counted here.
|
||||
> They exist, but this list is about behavioural scenarios.
|
||||
|
||||
Status today: `queue` has partial coverage, namely create, claim and list, plus the claim
|
||||
surviving a restart. `down` has partial coverage in
|
||||
`packages/test-system/scenarios/down-stops-every-seat.yaml`: after `rig down`, `rig ps` reports
|
||||
every seat stopped and the rig still listed. `send` has partial coverage in
|
||||
`send-renders-in-addressed-pane.yaml` beside it: the envelope and the message body render in
|
||||
the addressed seat's pane. `transcript` has partial coverage in
|
||||
`transcript-reads-addressed-seat.yaml`: each of two seats' scripted output is readable through
|
||||
that seat's `rig transcript --tail 200 --json`. `capture` has partial coverage in
|
||||
`capture-returns-addressed-seat.yaml`: in a rig whose member names overlap by prefix (`impl`,
|
||||
`impl2`; `dev-impl`, `dev-impl2` with the pod), `rig capture <seat>` returns each seat's own
|
||||
pane. The scenario catches a capture that resolves the seat by a prefix match on its member
|
||||
name (so `dev-impl` also matches `dev-impl2`) and takes the first hit, or any resolver that
|
||||
returns the same pane for both seats; it does not exercise a prefix match on the full seat
|
||||
name. All five run with the local runner (`run-scenarios.mjs`). The transcript and capture
|
||||
scenarios also run in the CI job, in passing form only; the down, send and up scenarios don't.
|
||||
They do not prove that panes are gone or that a seat consumed a message. The transcript
|
||||
case does not establish ordering, uniqueness, sibling-token absence, tail limits or restart
|
||||
persistence. `up` has partial coverage in `up-relaunch-restores-a-two-seat-team.yaml`: a
|
||||
two-seat stub team comes up, goes down, and the same spec starts a new team in place of the
|
||||
stopped one with the same name, exactly two seats, both running and not archived, so a
|
||||
relaunch that re-records the old seats into the team's row fails on the final `ps` counts.
|
||||
It does not prove that conversations survive the round trip (that is the `--existing`
|
||||
restore path), that a seat consumed a message, or that the panes are gone after `down`.
|
||||
`daemon` is used by the existing scenarios but has no scenario of its own. Everything else
|
||||
has none.
|
||||
|
||||
Before you start, two honest constraints:
|
||||
|
||||
- Today's step verbs can't run arbitrary `rig` commands. Many rows below need a runner
|
||||
binding first. One proposal (not implemented) is a single structured-argv step that runs
|
||||
the installed CLI through the existing `rigBin`, instead of one new verb per command.
|
||||
- Rows that need a seat to *answer* (anything with "nonce") depend on the input-consuming
|
||||
stub described above.
|
||||
- `expect` has no negative, absence or count form. `match` is a structural subset and
|
||||
`contains` is a substring, so "the sibling received nothing", "the panes are gone" or "no
|
||||
duplicates" can't be asserted yet. An exact field value, such as `runningCount: 0` on the
|
||||
`ps` surface, is the closest available form.
|
||||
- The `restart` step runs `rig launch <rig> <node> --json` and passes no other flag; the node
|
||||
is the logical ID (for example `dev.qa`). A stub seat has no resume token. `rig launch` has
|
||||
no `--fresh` option: a deliberate fresh start of a stub seat after `down` goes through
|
||||
`rig up --existing <rig> --fresh <seat>` or `rig seat launch <seat> --fresh --reason "<why>"`, so the
|
||||
`launch` row needs a runner binding to one of those first.
|
||||
|
||||
| Group | Family | Proposed first check (observable result) |
|
||||
|---|---|---|
|
||||
| lifecycle | `start` | Restore two stopped stub seats. Both answer distinct fresh nonces, and no second daemon starts. |
|
||||
| lifecycle | `daemon` | start/status/stop/restart. Port, process and persisted queue identity agree through public commands. |
|
||||
| lifecycle | `bootstrap` | Bootstrap a minimal stub spec. Seats come up ready and the selected spec is the one used. |
|
||||
| lifecycle | `up` (partial) | Relaunch-not-duplicating-identities is covered by `up-relaunch-restores-a-two-seat-team.yaml`. Next: each seat consumes its own send, which needs the input-consuming stub. |
|
||||
| lifecycle | `down` | (partial) Next: its panes are gone, a sibling rig still answers, retained data follows the docs. |
|
||||
| lifecycle | `create` | Create a one-seat rig through the public path. It appears and answers without a hand-written topology. |
|
||||
| lifecycle | `launch` | Fresh, relaunch, resume and fork on provider-shaped fixtures. Correct restored token, one live process. |
|
||||
| reads | `status` | Two rigs in different states. Status matches their exact identities and transitions. |
|
||||
| reads | `ps` | Two rigs with overlapping seat names. Rig and node filters return exactly the intended members. |
|
||||
| reads | `usage` | Deterministic stub telemetry. top and series respect seat and time bounds. Missing data isn't reported as zero. |
|
||||
| reads | `health` | Known fresh, stale and cleared findings. Scope, explain and diagnosis values match the fixture. |
|
||||
| continuity | `snapshot` | Snapshot two seats. Rig, membership and snapshot bytes survive a daemon restart. |
|
||||
| continuity | `restore` | Restore after a daemon and tmux reset. Seat identity and nonce answers survive. |
|
||||
| continuity | `agent-image` | create/show/pin/unpin/delete for a provider-shaped fixture. References and identity match. |
|
||||
| continuity | `fork` | Fork at a known history token. The child keeps the prefix with its own identity, and the parent still answers. |
|
||||
| continuity | `restore-check` | One restorable and one missing transcript. Readiness tells them apart without relaunching. |
|
||||
| continuity | `restore-packet` | write/read/validate round trip. A malformed packet fails clearly. |
|
||||
| continuity | `compact-plan` | Scripted history and usage fixtures. Candidates and exclusions match thresholds, and nothing is compacted. |
|
||||
| continuity | `compact` | Input-driven compact and restore keep the role and token. Messages before and after reach the same seat. |
|
||||
| continuity | `handover` | Scripted predecessor and successor. Context is delivered before the rebind, and the successor answers. |
|
||||
| recovery | `crash-cart` | Stop only the scratch daemon. The output names paths and state, and the command changes nothing. |
|
||||
| recovery | `preflight` | Ready and missing-dependency fixtures produce named checks without launching seats. |
|
||||
| recovery | `doctor` | Healthy and damaged scratch installs. Exact diagnosis, no automatic repair. |
|
||||
| recovery | `destroy` | In a disposable scenario HOME only. Removes the intended state and keeps explicitly retained markers. |
|
||||
| registry | `gateway` | Add, show, update and remove a synthetic human. Reads match exact fields, and removal sticks. |
|
||||
| registry | `host` | add/list/select/rename isolated entries that resolve to fixture endpoints. Pairing needs two container daemons. |
|
||||
| coordination | `parked` | An owed item with a stopped worker is reported. A held worker with a live wake is distinguished. |
|
||||
| coordination | `stream` | emit/list/show/archive an exact item. A restart and a bounded watch keep the order with no duplicates. |
|
||||
| coordination | `queue` | (partial) Next: handoff, block, resolve and unclaim, each with exact items and destinations. |
|
||||
| coordination | `project` | Acquire a lease, classify one item twice, read one projection. The wrong holder cannot act. |
|
||||
| coordination | `view` | Register a view over known items. list/show return the exact filtered result after a mutation. |
|
||||
| coordination | `watchdog` | Register one short wake, see one response, stop it, prove no further deliveries. |
|
||||
| coordination | `workflow` | validate/compile/instantiate a tiny flow. It transitions and resumes after a restart with no duplicate queue work. |
|
||||
| coordination | `chatroom` | send/history/topic/clear in a scratch room. A bounded watcher sees exactly the ordered messages. |
|
||||
| coordination | `heartbeat` | Known execution-proof and missing-closure fixtures report the right status. Empty output isn't completion. |
|
||||
| authoring | `export` | Export a stub rig, then validate and re-import it. Membership and configuration are equivalent. |
|
||||
| authoring | `import` | Import a minimal spec, and the export matches it. Malformed input leaves state unchanged. |
|
||||
| authoring | `package` | validate/plan/install/rollback in scratch. Installed files and prior state match. |
|
||||
| authoring | `requirements` | Complete and missing capability fixtures produce the right named requirements. |
|
||||
| authoring | `bundle` | create/inspect/install a local bundle and round-trip it. A missing asset gives a named failure. |
|
||||
| authoring | `agent` | Validate a good and a malformed stub AgentSpec. The diagnostic names the bad field. |
|
||||
| authoring | `spec` | validate/audit/preflight/show a minimal spec, with predictable diagnostics for a malformed field. |
|
||||
| authoring | `specs` | add/show/preview/rename/remove a library fixture. A missing reference fails explicitly. |
|
||||
| authoring | `workspace` | validate/doctor known roots plus one missing-frontmatter case. Reports the exact affected path. |
|
||||
| authoring | `config` | set/get/reset a scratch key and init a workspace. Readback and files match the requested root. |
|
||||
| presentation | `ui` | Open the URL through a fake browser executable and read a local UI route. Rendering is out of scope. |
|
||||
| presentation | `tui` | Drive the TUI command bar and socket over two stub rigs. select, mutate and back show exact identities. |
|
||||
| presentation | `terminal` | open/status/views against a fake terminal-app endpoint. Correct target and layout arguments. |
|
||||
| adoption | `discover` | One unmanaged and one owned scratch tmux session. Only the intended candidate is discovered. |
|
||||
| adoption | `attach` | Attach to the intended seat. `whoami` reports it without duplicating the process. |
|
||||
| adoption | `bind` | Bind one discovered pane. A different, already-occupied identity is refused. |
|
||||
| adoption | `adopt` | Adopt two scratch sessions. PIDs and history are preserved, and the identities become addressable. |
|
||||
| adoption | `reconcile-session` | Rebind a hand-resumed provider-shaped fixture without starting or typing into another process. |
|
||||
| adoption | `unclaim` | Release an adopted seat. The tmux process and its history stay alive. |
|
||||
| adoption | `release` | Release one rig's claimed sessions. Processes survive, and a sibling rig is unchanged. |
|
||||
| identity | `archive` | Archive an old same-name generation. The current one stays routable, and the old data is kept. |
|
||||
| identity | `unarchive` | Unarchive a retained generation without taking over another live seat. |
|
||||
| identity | `remove` | Remove an archived generation or one member. Only that identity goes, and a reused name still routes. |
|
||||
| identity | `whoami` | Inside each stub seat, it returns that seat, not a sibling or the caller. |
|
||||
| identity | `seat` | Status, held-message guard, stop/launch and resume token keep the right process and permissions. |
|
||||
| integration | `mcp` | Serve against a scratch daemon. One listed tool's value matches CLI state. |
|
||||
| integration | `slack` | Manifest and config round trip, plus deterministic inbound and outbound fixture requests. No real Slack account. |
|
||||
| integration | `ask` | Search fixture transcripts via a fake model endpoint. Citations resolve to the stored lines. |
|
||||
| integration | `file` | Copy a fixture payload through isolated transport endpoints. Verify bytes and paths. |
|
||||
| integration | `env` | A fixture service-backed rig. status/logs/down reflect that service and stop only it. |
|
||||
| delivery | `transcript` (partial) | Own scripted output from two addressed seats is covered by `transcript-reads-addressed-seat.yaml`. Remaining: ordering, uniqueness, no other seat's token, tail limits and restart persistence; absence/count assertions need a runner binding. |
|
||||
| delivery | `send` | (partial) Next: send a fresh nonce and require an answer derived from it. Boot text or echo can't pass. |
|
||||
| delivery | `capture` | (partial) Each seat's own token, under prefix-overlapping member names. Next: the not-found, tail (`--lines`), sibling-absence and `--rig`/`--pod` cases, which need an error form, a tail option, an absence form and a multi-target surface in the runner. |
|
||||
| delivery | `broadcast` | Broadcast a nonce to a rig or pod. Each intended seat answers once, and an excluded seat gets nothing. |
|
||||
| delivery | `walk` | Walk two pieces through an input-consuming stub. Order is kept, and a bad piece isn't reported as delivered. |
|
||||
| context | `context` | add/get/compose/trace/profile over a fixture project. Output bytes, order and roots match. |
|
||||
| context | `plugin` | list/show/used-by/validate a fixture plugin. A missing resource is not the same as a valid empty selection. |
|
||||
| context | `skill` | Resolve and audit a fixture skill, then inspect the projection the consumer actually gets. |
|
||||
| context | `mode` | set/effective/cite/unset at nested scopes. Precedence holds, and the inherited value returns after unset. |
|
||||
| context | `startup-proof` | Submit a proof from a real stub identity. Check the stored attribution and the duplicate behaviour. |
|
||||
| permissions | `policy` | Apply and read an explicit permission selection using a provider-shaped fixture. It survives a restore, and the default is unchanged. |
|
||||
| accounts | `auth` | Synthetic auth files in scratch: save, list, validate, switch. No provider contact, no real credentials. |
|
||||
| accounts | `provider` | Synthetic account, binding and signal state. The precheck refuses where it should, and a switch keeps seat history. |
|
||||
| topology | `expand` | Add a pod to a live stub rig. Old seats survive, and new members answer nonces. |
|
||||
| topology | `add` | Add one seat. Exact binding, siblings preserved, and the duplicate-name behaviour is explicit. |
|
||||
| topology | `grow` | Grow two seats. Identities are unique, config is inherited, and existing history is kept. |
|
||||
| topology | `shrink` | Remove a pod. Unrelated seats still answer, and the removed membership is gone. |
|
||||
| onboarding | `setup` | Scripted newcomer answers against synthetic providers. No install or login for an unused provider. |
|
||||
| work | `scope` | Create a mission and a slice. move/progress/notes/graph readback keep identity and paths. |
|
||||
| work | `proof` | add/judge/show evidence in a fixture scope. Readiness updates without overwriting earlier proof. |
|
||||
|
||||
How to make a case useful:
|
||||
|
||||
- Assert actual state or content, not just exit code 0. Where ordinary use includes bad
|
||||
input, add an invalid or missing input case too.
|
||||
- Reuse the existing runner and the installed CLI. Don't add a second scenario engine.
|
||||
- If a scenario exposes an existing product bug, keep the failing case and open an issue.
|
||||
Include the exact source revision, the shortest failing sequence, and the intended versus
|
||||
observed result. Don't weaken the assertion to make it pass. Keep "the harness setup
|
||||
failed" separate from "the product misbehaved".
|
||||
- A check that only covers the success path is still useful. Just don't call it a
|
||||
regression detector until you have seen its negative case fail.
|
||||
|
||||
## Existing docs in this area that are out of date
|
||||
|
||||
Read these with care. This page notes them and proposes no changes.
|
||||
|
||||
- `packages/test-system/ci/README.md` describes the CI scenario job, except its
|
||||
"Run one case" section, which still says both cases and six runs; the script runs four cases
|
||||
and eight containers by default.
|
||||
- The headers of `transcript-reads-addressed-seat.yaml` and `capture-returns-addressed-seat.yaml`
|
||||
say "host mode only". Both run in the CI container job, which executes the host-mode pipeline
|
||||
inside the image.
|
||||
- `packages/test-system/scripts/README.md` and `packages/test-system/ROUTING.md` are
|
||||
dependency-routing notes from when the scenarios were first written. Several of the items
|
||||
they list have since landed. Treat them as history.
|
||||
- `docker/testbed/runbooks/`: see above. They are manual procedures that predate the CI job.
|
||||
+2
-2
@@ -11,8 +11,8 @@ applies-when: |
|
||||
named ui/ module.
|
||||
siblings: [README.md, codemap.md, architecture.md]
|
||||
prerequisite-reads: []
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# ui.md was reorganized into the modular as-built tree
|
||||
|
||||
@@ -6,12 +6,13 @@ topics: [specification-and-bundles, observability]
|
||||
domains: [engineering-advisor, product-advisor]
|
||||
applies-when: |
|
||||
Need to know how the Library (`/specs`) UI is assembled — the spec/skills/
|
||||
plugins surfaces, the spec-review + spec-library + live-identity flows that
|
||||
feed it — or where the canonical visual/design-system spec lives.
|
||||
plugins surfaces, the spec-review + spec-library flows that feed it, and
|
||||
how seat details open — or where the canonical visual/design-system spec
|
||||
lives.
|
||||
siblings: [shell-and-routing.md]
|
||||
prerequisite-reads: [../README.md, shell-and-routing.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# UI Library/Specs Surfaces + Design-System Pointer
|
||||
@@ -20,101 +21,122 @@ The Library destination keeps `/specs` as the route and presents the product
|
||||
label "Library": specs, applications, context packs, agent specs, agent
|
||||
images, plugins, and skills.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c`; package version **0.3.1**
|
||||
> (slice-00 §1.1). Component/primitive names below are reconciled against
|
||||
> `packages/ui/src/components/` at HEAD — NOT taken on trust from `ui.md`'s
|
||||
> or `DESIGN.md`'s lists.
|
||||
> Verified against source at `e8f0ab34`; package version 0.6.7. Component and
|
||||
> primitive names below are reconciled against `packages/ui/src/components/`,
|
||||
> not taken on trust from `DESIGN.md`'s lists. The web UI is in maintenance
|
||||
> mode (`docs/reference/developing.md`).
|
||||
|
||||
## 1. Library / specs surfaces
|
||||
|
||||
Components in `packages/ui/src/components/specs/` (re-confirmed at HEAD):
|
||||
Components in `packages/ui/src/components/specs/`:
|
||||
|
||||
- `SpecsLibraryPage.tsx` — the `/specs` Library page (unified list:
|
||||
All / Apps / Rigs / Agents; service-backed entries render as `APP`).
|
||||
- `SpecsTable.tsx`, `SpecsTreeView.tsx` — list + Explorer tree.
|
||||
- `SkillsIndexPage.tsx`, `SkillDetailPage.tsx` — folder-based skills.
|
||||
The skill tree treats each skill as a folder; clicking a skill opens a
|
||||
detail route defaulting to `skill.md` case-insensitively, rendered via the
|
||||
shared `FileViewer` in the center workspace (no second in-page file
|
||||
browser — file navigation belongs in the Explorer tree).
|
||||
- `SpecsLibraryPage.tsx` — the `/specs` Library page (also mounted at
|
||||
`/specs/applications`): a sectioned grid of Rig Specs, Workspace Specs
|
||||
(placeholder), Workflow Specs, Context Packs, Agent Specs, Agent Images
|
||||
and Applications, then full-width Plugins and Skills sections.
|
||||
Service-backed rigs (`hasServices`) appear under Applications, not Rig
|
||||
Specs.
|
||||
- `SpecsTreeView.tsx` — the Explorer tree. (`SpecsTable.tsx` is still in
|
||||
the folder but nothing imports it.)
|
||||
- `SkillsIndexPage.tsx`, `SkillDetailPage.tsx` — skills. The Explorer
|
||||
groups skills into category folders; each skill is a single row, not
|
||||
expandable into files. Opening a skill goes to a detail route that
|
||||
defaults to `SKILL.md` (case-insensitive) and renders its own docs
|
||||
browser: a file tree with breadcrumbs beside a viewer, `MarkdownViewer`
|
||||
for `.md`/`.mdx`, `SyntaxHighlight` for other text, preformatted text
|
||||
otherwise. Files come from `/api/skills/:id/files/list` and `/files/read`
|
||||
(`hooks/useSkillFiles.ts`).
|
||||
- `PluginsIndexPage.tsx`, `PluginDetailPage.tsx`, `AgentPluginsList.tsx` —
|
||||
the plugin surfaces.
|
||||
|
||||
> Drift-note (slice-00 0.3.0-GT seam a): the plugin primitive is a **0.3.1**
|
||||
> feature (git-ancestry proven; absent at `v0.3.0`, present at `v0.3.1`). Do
|
||||
> NOT back-attribute the `/specs/plugins`, `/plugins/$pluginId` routes or
|
||||
> these plugin components to 0.3.0. `ui.md` (which predates 0.3.x) has **no
|
||||
> narrative for plugins at all** — these surfaces are authored from source +
|
||||
> the slice-00 0.3.0-GT seam, not migrated.
|
||||
Library routes: `/specs`, `/specs/applications`, `/specs/skills`,
|
||||
`/specs/skills/$skillToken`, `/specs/skills/$skillToken/file/$fileToken`,
|
||||
`/specs/plugins`, `/plugins/$pluginId`, `/specs/library/$entryId` (see
|
||||
[`shell-and-routing.md`](shell-and-routing.md) §3).
|
||||
|
||||
## 2. Spec review / library / live-identity flows
|
||||
> The plugin primitive is a **0.3.1** feature (absent at `v0.3.0`, present
|
||||
> at `v0.3.1`). Do NOT back-attribute the `/specs/plugins`,
|
||||
> `/plugins/$pluginId` routes or these plugin components to 0.3.0.
|
||||
|
||||
These daemon-backed flows feed the Library UI (`architecture.md` §6
|
||||
"Spec review and spec library flow", "Live identity / specs UI flow",
|
||||
re-confirmed accurate at HEAD):
|
||||
## 2. Spec review / library flows and seat details
|
||||
|
||||
These daemon-backed flows feed the Library UI:
|
||||
|
||||
- **Raw YAML preview:** UI/CLI posts YAML to `/api/specs/review/rig` or
|
||||
`/api/specs/review/agent`; `SpecReviewService` parses/validates/returns
|
||||
structured review models; UI renders them through `RigSpecDisplay` /
|
||||
`AgentSpecDisplay` (the same primitives reused for draft preview, library
|
||||
review, and live full-details).
|
||||
review, and live details).
|
||||
- **Filesystem-backed library:** `SpecLibraryService` scans builtin + user
|
||||
roots (`packages/daemon/specs`, `~/.openrig/specs`, legacy fallback
|
||||
`~/.rigged/specs`); each YAML is classified via structured review;
|
||||
service-backed rigs are marked `hasServices`; `/api/specs/library` serves
|
||||
list/get/review/sync.
|
||||
- **CLI mirror:** `rig specs ls/show/preview/add/sync`; `rig specs add`
|
||||
installs a single YAML spec or a full spec directory; `rig up` /
|
||||
`rig bootstrap` resolve library names before other source kinds.
|
||||
- **Live identity:** Explorer/graph selection opens the shared right drawer
|
||||
(runtime-first node detail: live identity, peers, directional edges,
|
||||
transcript helpers, compact spec summary); `Open Full Details` navigates to
|
||||
`/rigs/$rigId/nodes/$logicalId` in the center workspace.
|
||||
service-backed rigs are marked `hasServices`. Each list request also
|
||||
merges workflow entries from the workflow-spec cache.
|
||||
`/api/specs/library` serves list, get, review and sync, plus
|
||||
`DELETE /:id`, `POST /:id/rename` and GET/POST/DELETE `/active-lens`.
|
||||
On a non-local host, list and review reads carry `?host=<id>`; the
|
||||
active lens stays local.
|
||||
- **CLI mirror:** `rig specs ls/show/preview/add/sync/remove/rename`;
|
||||
`rig specs add` installs a single YAML spec or a full spec directory;
|
||||
`rig up` / `rig bootstrap` resolve name-shaped sources through the
|
||||
library.
|
||||
- **Seat details:** graph, tree and table clicks navigate to
|
||||
`/topology/seat/$rigId/$logicalId`, which renders `LiveNodeDetails`
|
||||
(Overview and Details tabs). The shared drawer no longer has a
|
||||
seat-detail kind; its kinds are system, log, status, discovery, qitem,
|
||||
file and sub-spec. The legacy `/rigs/$rigId/nodes/$logicalId` route also
|
||||
renders `LiveNodeDetails`, but nothing in the UI links to it.
|
||||
|
||||
> Source: `packages/daemon/src/routes/spec-library.ts`;
|
||||
> `packages/ui/src/hooks/useSpecLibrary.ts`; `packages/cli/src/commands/specs.ts`
|
||||
> (`remove`, `rename`); `packages/ui/src/components/SharedDetailDrawer.tsx`
|
||||
> (viewer kinds; seat-detail retired).
|
||||
|
||||
## 3. UI primitive inventory (reconciled against source)
|
||||
|
||||
> Drift-fix — `ui.md` "Design Primitives" (L75–107) omits `ProjectPill`.
|
||||
> `docs/DESIGN.md` L195 lists it; re-confirmed at HEAD it is a real export
|
||||
> (`packages/ui/src/components/project/ProjectMetaPrimitives.tsx:196`
|
||||
> `export function ProjectPill`). Reconciled inventory below is from source,
|
||||
> not from either doc's list.
|
||||
|
||||
- **Vellum/base** (`components/ui/`): `VellumCard`, `VellumSheet`,
|
||||
`RegistrationMarks`, `StatusPip`, `SectionHeader`, `EmptyState`,
|
||||
`Button`, `Tabs`, `Table`, form controls, plus `rig-stamp.tsx`
|
||||
(`RigStamp`) — present at HEAD; not in ui.md's list.
|
||||
- **Graphics** (`components/graphics/RuntimeMark.tsx`, exports re-confirmed):
|
||||
`RuntimeMark`, `RuntimeBadge`, `ToolMark`, `ToolBadge`, `ActorMark`,
|
||||
(`RigStamp`).
|
||||
- **Graphics** (`components/graphics/RuntimeMark.tsx`): `RuntimeMark`,
|
||||
`RuntimeBadge`, `ToolMark`, `ToolBadge`, `ActorMark`,
|
||||
`OperatorMoodMark`; normalization in `lib/runtime-brand.ts` +
|
||||
`lib/tool-brand.ts`.
|
||||
- **Project metadata** (`components/project/ProjectMetaPrimitives.tsx`,
|
||||
exports re-confirmed): `ProjectPill` (`:196`), `EventBadge` (`:224`),
|
||||
`QueueStateBadge` (`:228`), `TagPill` (`:274`), `ActorChip` (`:283`),
|
||||
`DateChip` (`:303`), `FlowChips` (`:315`), `ProofThumbnailGrid` (`:334`),
|
||||
`ProofPacketHeader` (`:380`) — plus `QueueCountIcon` / `StatusDot` (in
|
||||
source, not in ui.md/DESIGN.md primitive lists).
|
||||
`lib/tool-brand.ts`. Runtime brands are Claude Code, Codex, Pi ("Pi"),
|
||||
OMP ("Oh My Pi", short "OMP"), terminal and unknown; `ActorMark` reuses
|
||||
the runtime marks for recognized runtimes.
|
||||
- **Project metadata** (`components/project/ProjectMetaPrimitives.tsx`):
|
||||
`ProjectPill` (`:199`), `EventBadge` (`:227`), `QueueStateBadge` (`:231`),
|
||||
`TagPill` (`:277`), `ActorChip` (`:286`), `DateChip` (`:306`),
|
||||
`FlowChips` (`:318`), `ProofThumbnailGrid` (`:337`), `ProofPacketHeader`
|
||||
(`:383`) — plus `QueueCountIcon` / `StatusDot`.
|
||||
- **Viewers**: `FileViewer` (`components/drawer-viewers/FileViewer.tsx`),
|
||||
`MarkdownViewer` (`components/markdown/MarkdownViewer.tsx`),
|
||||
`ProofImageViewer`, `SessionPreviewPane` (`components/preview/`).
|
||||
`ProofImageViewer` and `Lightbox` (`components/project/`),
|
||||
`SessionPreviewPane` (`components/preview/`).
|
||||
- **Themes** (`lib/theme.ts`): Vellum Light, Vellum Dark and System
|
||||
(default `system`, following the OS colour scheme), stored under the
|
||||
`openrig.theme` localStorage key. `applyTheme` toggles a `.dark` class
|
||||
on `<html>`, which switches to the `.dark` token block in `globals.css`.
|
||||
`ThemeProvider` is mounted in `main.tsx`, `ThemeSelector` in the
|
||||
AppShell top bar, and a pre-paint script in `index.html` mirrors the
|
||||
storage key.
|
||||
|
||||
## 4. Design-system pointer (NOT a copy — Q1)
|
||||
|
||||
The canonical OpenRig visual/design system is **`docs/DESIGN.md`** (at the
|
||||
repo `docs/` root, NOT under `docs/as-built/`). Q1 is ratified: DESIGN.md
|
||||
stays at root, byte-identical, and this module **points to it rather than
|
||||
copying it**. DESIGN.md is the source for: Vellum-paper vs Black-glass
|
||||
surface languages, colour tokens (`packages/ui/src/globals.css` +
|
||||
`tailwind.config.ts`), typography (`font-body` Inter / `font-headline` Space
|
||||
Grotesk / `font-mono` JetBrains Mono), layout (48px rail + Explorer + center
|
||||
+ drawer + preview stack), the core-primitive list, the graphics system, and
|
||||
the interaction/motion/accessibility/do-and-do-not rules.
|
||||
stays at root (the as-built reorganization left it byte-identical), and this
|
||||
module **points to it rather than copying it**. DESIGN.md is the source for: Vellum-paper vs
|
||||
Black-glass surface languages, colour tokens (`packages/ui/src/globals.css`
|
||||
+ `tailwind.config.ts`), typography (`font-body` Inter / `font-headline`
|
||||
Space Grotesk / `font-mono` JetBrains Mono), layout (48px rail + Explorer +
|
||||
center + drawer + preview stack), the core-primitive list, the graphics
|
||||
system, and the interaction/motion/accessibility/do-and-do-not rules.
|
||||
|
||||
> Reconciliation note: DESIGN.md's primitive lists were independently
|
||||
> verified against `packages/ui/src/components/` at HEAD — every primitive
|
||||
> DESIGN.md names (Vellum, Graphics, Project-metadata, Topology, Preview)
|
||||
> resolves to a real export at HEAD. DESIGN.md is accurate and carries no
|
||||
> slice-00 numeric drift, so it is referenced as-is. Do not duplicate its
|
||||
> content here; read `docs/DESIGN.md` directly for the visual spec.
|
||||
> Reconciliation note: every primitive DESIGN.md names (Vellum, Graphics,
|
||||
> Project-metadata, Topology, Preview) resolves to a real export at the
|
||||
> stamped commit. Do not duplicate its content here; read `docs/DESIGN.md`
|
||||
> directly for the visual spec.
|
||||
|
||||
## See also
|
||||
|
||||
|
||||
@@ -6,428 +6,367 @@ topics: [observability, coordination]
|
||||
domains: [engineering-advisor, product-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
Need to know how the operator-facing destination surfaces are built —
|
||||
the For-You attention feed (5-card classifier + verb actions), the
|
||||
Project workspace/mission/slice scope pages (tabbed rollups), and the
|
||||
Dashboard landing surface on the vellum brand system. Author-mode
|
||||
module — ui.md prose for these surfaces predates 0.3.x; every claim is
|
||||
sourced to file:line at HEAD.
|
||||
the For-You attention feed (5-card classifier, attention merge, verb
|
||||
actions), the Project workspace/mission/slice scope pages (tabbed
|
||||
rollups), and the Dashboard launcher. The web UI is in maintenance mode;
|
||||
claims are sourced to file and symbol (and line where verified) at the
|
||||
stamped commit.
|
||||
siblings: [shell-and-routing.md, ../architecture/mission-control.md]
|
||||
prerequisite-reads: [../README.md, shell-and-routing.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-07-06
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# UI Project Observability, For You, Dashboard
|
||||
|
||||
The three operator-facing destination surfaces: **For You** (`/for-you`,
|
||||
the attention feed), **Project** (`/project*`, the workspace/mission/slice
|
||||
scope pages), and the **Dashboard** (`/`, the landing surface on the
|
||||
vellum brand system). All read live daemon state; none adds UI-local
|
||||
persistence beyond per-event soft-dismiss.
|
||||
scope pages), and the **Dashboard** (`/`, the launcher). All read live
|
||||
daemon state. For You persists two dismissal sets in localStorage; shared
|
||||
shell preferences include the theme choice (see
|
||||
[`library-specs-and-design-system.md`](library-specs-and-design-system.md))
|
||||
and the maintenance-notice dismissal (see
|
||||
[`shell-and-routing.md`](shell-and-routing.md)).
|
||||
|
||||
> **AUTHOR-HEAVY module.** `ui.md`'s Project Observability / For You /
|
||||
> Graphics-Layer sections are thin and predate the 0.3.1 dashboard/vellum
|
||||
> brand refresh. Every load-bearing claim carries `> Source: <file:line>
|
||||
> @HEAD`; ambiguity is declared as an OPEN item, never smoothed. Paths
|
||||
> are relative to `packages/ui/src/` unless prefixed `docs/`.
|
||||
>
|
||||
> Verified at HEAD `7eaf524c` (`git describe` → `v0.3.1-6-g7eaf524c`).
|
||||
> Package version **0.3.1**; HEAD carries 6 unreleased release-0.3.2
|
||||
> commits; no `v0.3.2` tag (daemon-core.md; slice-00 §1.1).
|
||||
> Paths are relative to `packages/ui/src/` unless prefixed `docs/` or
|
||||
> `packages/`. Verified at `e8f0ab34` (package version 0.6.7). The web UI
|
||||
> is in maintenance mode (`docs/reference/developing.md`); this page
|
||||
> records what ships, not a roadmap.
|
||||
|
||||
## 0. Release attribution (forensic seam — read first)
|
||||
## 0. Release attribution (forensic seam)
|
||||
|
||||
Re-verified at HEAD via `git cat-file -e <tag>:<path>`:
|
||||
Checked via `git cat-file -e <tag>:<path>`:
|
||||
|
||||
| Layer | Release | Forensic proof @HEAD |
|
||||
| Layer | Release | Forensic proof |
|
||||
|---|---|---|
|
||||
| Project-observability foundation (For-You feed, feed-classifier, project scope pages, the Mission Control 7-verb vocabulary) | **0.3.0** | `v0.3.0:components/for-you/Feed.tsx`, `v0.3.0:lib/feed-classifier.ts`, `v0.3.0:components/project/ScopePages.tsx`, `v0.3.0:components/mission-control/components/VerbActions.tsx` all resolve at `v0.3.0` (slice-00 0.3.0-GT §1.5/§1.6/§1.7) |
|
||||
| Vellum **brand-identity system** the Dashboard/For-You now render on (`dashboard/vellum/*`: CornerBracket, VellumDestinationCard, marks, graphics, single-source-of-truth barrel) | **0.3.1, NOT 0.3.0** | `git cat-file -e v0.3.0:components/dashboard/vellum/index.ts` → **ABSENT** ("exists on disk, but not in 'v0.3.0'"); `v0.3.1:` → present (slice-00 0.3.0-GT seam (b); §2 rows 1/2) |
|
||||
| Project-observability foundation (For-You feed, feed-classifier, project scope pages, the Mission Control 7-verb vocabulary) | **0.3.0** | `v0.3.0:components/for-you/Feed.tsx`, `v0.3.0:lib/feed-classifier.ts`, `v0.3.0:components/project/ScopePages.tsx`, `v0.3.0:components/mission-control/components/VerbActions.tsx` all resolve at `v0.3.0` |
|
||||
| Vellum **brand-identity system** (`dashboard/vellum/*`: CornerBracket, VellumDestinationCard, marks, graphics, barrel) | **0.3.1, NOT 0.3.0** | `git cat-file -e v0.3.0:packages/ui/src/components/dashboard/vellum/index.ts` fails; `v0.3.1:` resolves |
|
||||
|
||||
> Source: re-run at HEAD `7eaf524c` —
|
||||
> `git cat-file -e v0.3.0:packages/ui/src/components/dashboard/vellum/index.ts`
|
||||
> fails; `v0.3.0:.../for-you/Feed.tsx`, `.../lib/feed-classifier.ts`,
|
||||
> `.../project/ScopePages.tsx`, `.../mission-control/components/VerbActions.tsx`
|
||||
> all resolve at `v0.3.0`. Vellum surface *primitives*
|
||||
> (`components/ui/vellum-*.tsx`) shipped 0.3.0; the coherent vellum
|
||||
> *brand system* under `dashboard/vellum/` is 0.3.1 — slice-00 0.3.0-GT
|
||||
> seam (b) is the exact split. **Do not back-attribute the vellum brand
|
||||
> system to 0.3.0.**
|
||||
Vellum surface *primitives* (`components/ui/vellum-*.tsx`) shipped 0.3.0;
|
||||
the brand system under `dashboard/vellum/` is 0.3.1. Do not back-attribute
|
||||
the brand system to 0.3.0.
|
||||
|
||||
## 1. Route reality (source-verified at `routes.tsx`@HEAD — BINDING)
|
||||
## 1. Route reality
|
||||
|
||||
Before narrating any surface, each route was verified at
|
||||
`packages/ui/src/routes.tsx`@HEAD (542 lines). The Phase-8.1 survey's
|
||||
"missing route" expectations are grep-derived and partially wrong; this
|
||||
table is authored from `routes.tsx` reality, not the survey:
|
||||
Verified at `routes.tsx` (605 lines):
|
||||
|
||||
| Path | Reality @HEAD | Component | Source |
|
||||
| Path | Reality | Component | Source |
|
||||
|---|---|---|---|
|
||||
| `/for-you` | **REAL destination route** | `Feed` | `routes.tsx:122-126` |
|
||||
| `/project` | **REAL destination route** | `WorkspaceScopePage` | `routes.tsx:128-132` |
|
||||
| `/project/mission/$missionId` | **REAL destination route** | `MissionScopePage` | `routes.tsx:134-138` |
|
||||
| `/project/slice/$sliceId` | **REAL destination route** | `SliceScopePage` | `routes.tsx:140-144` |
|
||||
| `/` (index) | **REAL destination route** | `Dashboard` | `routes.tsx:88-92` |
|
||||
| `/mission-control` | **DELETED `<Navigate to="/for-you">` redirect-stub** | `() => <Navigate to="/for-you">` | `routes.tsx:440-444` |
|
||||
| `/progress` | **`<Navigate to="/project">` redirect-stub** (folds into Project tabs) | `() => <Navigate to="/project">` | `routes.tsx:463-467` |
|
||||
| `/slices` | **DELETED `<Navigate to="/project">` redirect-stub** | `() => <Navigate to="/project">` | `routes.tsx:447-451` |
|
||||
| `/slices/$name` | **`<Navigate to="/project/slice/$sliceId">` redirect-stub** | `useParams` → `<Navigate>` | `routes.tsx:453-460` |
|
||||
| `/steering` | **`<Navigate to="/project">` redirect-stub** | `() => <Navigate to="/project">` | `routes.tsx:470-474` |
|
||||
| `/markdown` | **NOT a route** — `MarkdownViewer` is a component used inside `MissionScopePage` | (no route) | grep `routes.tsx`@HEAD: zero `path: "/markdown"` |
|
||||
| `/` (index) | destination route | `Dashboard` | `routes.tsx:96-100` |
|
||||
| `/for-you` | destination route | `Feed` | `routes.tsx:130-134` |
|
||||
| `/project` | destination route | `WorkspaceScopePage` | `routes.tsx:136-140` |
|
||||
| `/project/mission/$missionId` | destination route | `MissionScopePage` | `routes.tsx:142-146` |
|
||||
| `/project/slice/$sliceId` | destination route | `SliceScopePage` | `routes.tsx:148-152` |
|
||||
| `/mission-control` | redirect stub to `/for-you` | `() => <Navigate to="/for-you">` | `routes.tsx:496-500` |
|
||||
| `/slices` | redirect stub to `/project` | `() => <Navigate to="/project">` | `routes.tsx:503-507` |
|
||||
| `/slices/$name` | redirect stub to `/project/slice/$sliceId` | `useParams` → `<Navigate>` | `routes.tsx:509-516` |
|
||||
| `/progress` | redirect stub to `/project` | `() => <Navigate to="/project">` | `routes.tsx:519-523` |
|
||||
| `/steering` | redirect stub to `/project` | `() => <Navigate to="/project">` | `routes.tsx:526-530` |
|
||||
| `/markdown` | **not a route** — `MarkdownViewer` is a component | (no route) | no `path: "/markdown"` in `routes.tsx` |
|
||||
|
||||
> Source: `routes.tsx:88-92`,`:122-144`,`:440-474` @HEAD; component
|
||||
> imports `routes.tsx:40-41` (`Dashboard`, `Feed`), `:60-63`
|
||||
> (`WorkspaceScopePage`/`MissionScopePage`/`SliceScopePage` from
|
||||
> `components/project/ScopePages.js`) @HEAD.
|
||||
> Source: component imports `routes.tsx:42-43` (`Dashboard`, `Feed`),
|
||||
> `:61-65` (`WorkspaceScopePage`/`MissionScopePage`/`SliceScopePage` from
|
||||
> `components/project/ScopePages.js`).
|
||||
|
||||
> **Route-reality contract (slice-08 §10.7):** the **Mission Control
|
||||
> *system*** lives at daemon/PL-005 (see `../architecture/mission-control.md`)
|
||||
> — `/mission-control` is **NOT a UI destination**; it is a deleted
|
||||
> redirect-stub (SC-18). The 7-verb *action vocabulary* surfaces in this
|
||||
> module via `VerbActions` embedded in For-You cards, but the system
|
||||
> itself is not this module's subject. `/progress`, `/slices`,
|
||||
> `/steering` all fold into `/project` tabs — they are redirect-stubs,
|
||||
> not surfaces to narrate.
|
||||
The review altitudes `/agents` (`RigAgentsPage`) and `/fleet` (`FleetPage`)
|
||||
are listed in [`shell-and-routing.md`](shell-and-routing.md).
|
||||
|
||||
> **Route-reality contract:** the **Mission Control *system*** lives in the
|
||||
> daemon (see `../architecture/mission-control.md`); `/mission-control` is
|
||||
> a redirect stub, not a UI destination. The 7-verb *action vocabulary*
|
||||
> surfaces here via `VerbActions` in For-You cards. `/progress`, `/slices`
|
||||
> and `/steering` fold into `/project` tabs.
|
||||
|
||||
## 2. For You — the attention feed (`/for-you` → `Feed`)
|
||||
|
||||
`Feed` is the operator's attention surface: a chronologically-sorted,
|
||||
subscription-filtered, soft-dismissable card stream over the live
|
||||
activity feed. Header reads `Attention` / `For You`; max width 720px.
|
||||
`Feed` is the operator's attention surface: decision cards first, then the
|
||||
rest newest-first, subscription- and lens-filtered and soft-dismissable.
|
||||
Header reads `Attention` / `For You`; max width 720px. A plain-language
|
||||
`LevelControl` (All activity / Highlights / Needs you) sits at the top of
|
||||
the feed over the same subscription toggles.
|
||||
|
||||
> Source: `components/for-you/Feed.tsx:364-369` (`data-testid="for-you-feed"`,
|
||||
> max-w-720 + `For You` header), feed-centerpiece design note `:3-12`
|
||||
> (PRIMARY UX = the feed; subscriptions NOT dominating — LOAD-BEARING
|
||||
> SC-16) @HEAD.
|
||||
> Source: `components/for-you/Feed.tsx` (`data-testid="for-you-feed"`,
|
||||
> max-w-720 header; `LevelControl` in `feed-level-control`; design note
|
||||
> `:3-12`); `lib/feed-levels.ts` `FEED_LEVEL_LABELS`.
|
||||
|
||||
### 2.1 The 5-card classifier (0.3.0 spine)
|
||||
|
||||
`classifyFeed(events)` maps every `ActivityEvent` to exactly one of five
|
||||
`classifyFeed(events)` maps every `ActivityEvent` to one of five
|
||||
`FeedCardKind` values — `action-required`, `approval`, `shipped`,
|
||||
`progress`, `observation` — then sorts by `receivedAt` descending.
|
||||
**Nothing is silently dropped**: any unmatched event type falls through
|
||||
to `observation`. Queue-visibility events are sub-classified by
|
||||
`queueKind(type, state)` (e.g. `*.closed` → shipped; `human-gate` /
|
||||
`pending-approval` → action-required; `closeout-pending-ratify` →
|
||||
approval); a human-seat destination forces `action-required`.
|
||||
`progress`, `observation` — sorted by `receivedAt` descending. **Nothing is
|
||||
silently dropped**: an unmatched event type falls through to
|
||||
`observation`. Queue events are sub-classified by
|
||||
`queueKind(type, state, tier)`: `*.closed` → shipped;
|
||||
`qitem.closure_overdue` / `inbox.denied` → action-required; tier
|
||||
`human-gate` → **approval**; state `pending-approval` → action-required;
|
||||
`closeout-pending-ratify` → approval; `done|closed|completed|shipped` →
|
||||
shipped; otherwise progress. A human-seat destination forces
|
||||
`action-required` unless the card is already an approval.
|
||||
|
||||
> Source: `lib/feed-classifier.ts:9-14` (`FeedCardKind` union),
|
||||
> `classifyEvent` `:145-218` (default-observation fallthrough L216-217),
|
||||
> `queueKind` `:93-110`, `classifyFeed` `:220-223` (receivedAt-desc sort),
|
||||
> `isHumanSeat` `:141-144` @HEAD.
|
||||
> Source: `lib/feed-classifier.ts` — `FeedCardKind` `:10-15`, `queueKind`
|
||||
> `:105-129`, `isHumanSeat`, `classifyEvent` (approval-preserving
|
||||
> human-seat override; default-observation fallthrough), `classifyFeed`,
|
||||
> `sortFeedByDecisionBand`.
|
||||
|
||||
The feed pipeline (`Feed.tsx`): `classifyFeed` → cap at
|
||||
`HISTORY_LIMIT = 50` → hydrate each card's kind from the live queue-item
|
||||
map + action-audit (`hydratedCardKind`: a recorded outcome on an
|
||||
action/approval card → `approval`; a `done|closed|completed` qitem →
|
||||
`shipped`) → **subscription filter** (`isCardKindSubscribed` against the
|
||||
5 `feed.subscriptions.*` config keys; `action-required` is always
|
||||
visible, `observation` only when the audit subscription is on) → the
|
||||
transient **lens-chip** filter (All / Action req / Approvals / Shipped /
|
||||
Progress / Audit; not persisted) → per-event-seq soft-dismiss filter.
|
||||
### 2.2 The feed pipeline
|
||||
|
||||
> Source: `components/for-you/Feed.tsx:63` (`HISTORY_LIMIT = 50`),
|
||||
> `:238` (`classifyFeed(events).slice(0, HISTORY_LIMIT)`),
|
||||
> `hydratedCardKind` `:156-180`, subscription+lens+dismiss pipeline
|
||||
> `:269-285` (subscription FIRST L280-282; lens L283; dismiss L284),
|
||||
> `LENS_CHIPS` `:54-61` @HEAD;
|
||||
> subscription forced/default rules `Feed.tsx:3-12` (action_required
|
||||
> forced ON L9; observation gated on audit_log L10) @HEAD.
|
||||
1. **Event cards:** `classifyFeed(events)` capped at `HISTORY_LIMIT = 50`.
|
||||
2. **Attention merge:** daemon attention rows (`useAttentionItems(50, …)`:
|
||||
`GET /api/queue/list?attention=1…`, or `GET /api/queue/attention-aggregate`
|
||||
when a remote host subscription is on) and needs-input seats
|
||||
(`useNeedsInputSeats`) become cards, and `mergeAttentionIntoFeed` lets a
|
||||
queue-derived card supersede a matching event-derived **action-required
|
||||
or approval** card; shipped, progress and observation event cards for the
|
||||
same qitem remain.
|
||||
3. **Decision-band sort:** `sortFeedByDecisionBand` lifts every
|
||||
action-required and approval card above the rest, newest-first within
|
||||
each band.
|
||||
4. **Hydration:** `hydratedCardKind` re-reads each card against the live
|
||||
queue item and the action audit: a recorded outcome on an
|
||||
action/approval card → `approval`; tier `human-gate` → `approval`; a
|
||||
`done|closed|completed` item → `shipped`; a human-seat destination →
|
||||
`action-required`.
|
||||
5. **Filters, in order:** subscription (`isCardKindSubscribed`;
|
||||
`action-required` always visible, `observation` only with the audit
|
||||
subscription), the transient lens chips (All / Action req / Approvals /
|
||||
Shipped / Progress / Audit), a per-host filter, then dismissal.
|
||||
Event cards dismiss by event seq (`forYou.dismissedSeqs`); queue-derived
|
||||
cards dismiss by card id (`forYou.dismissedCardIds`); both are
|
||||
localStorage-backed.
|
||||
|
||||
### 2.2 Queue-item hydration + proof previews
|
||||
> Source: `components/for-you/Feed.tsx` — `LENS_CHIPS` `:60-67`,
|
||||
> `HISTORY_LIMIT` `:69`, `hydratedCardKind` `:167-186`, attention merge and
|
||||
> sort `:244-276`, dismiss hooks `:285`/`:294`, filter pipeline `:334-356`;
|
||||
> `hooks/useAttentionItems.ts`, `hooks/useNeedsInputSeats.ts`,
|
||||
> `lib/attention-feed.ts` (`mergeAttentionIntoFeed`, `attentionKindFor`),
|
||||
> `hooks/useDismissedSeqs.ts`, `hooks/useDismissedCardIds.ts`.
|
||||
|
||||
For cards carrying a `qitemId` payload, `useQueueItemMap` hydrates the
|
||||
full queue-item body; for `shipped` cards `sliceForCard` matches a slice
|
||||
by tag/text and `proofPreviewForSlice` pulls the first proof packet with
|
||||
screenshots so the card renders an inline `ProofThumbnailGrid` →
|
||||
`ProofImageViewer`. This is the slice-00 §1.5 "queue cards hydrate qitem
|
||||
bodies + proof previews" foundation, live-wired.
|
||||
With one or more remote host subscriptions enabled, the feed shows host
|
||||
chips and status rows, cards carry a host chip, and verbs forward the
|
||||
card's `hostId` (`Feed.tsx` host filter; `hooks/useFeedSubscriptions.ts`
|
||||
key `feed.subscriptions.<hostId>.enabled`; `VerbActions.tsx`).
|
||||
|
||||
> Source: `components/for-you/Feed.tsx:263` (`useQueueItemMap`),
|
||||
> `sliceForCard` `:182-201`, `proofPreviewForSlice` `:203-213`,
|
||||
> per-card proof wiring `:421-427`; `FeedCard.tsx` proof block
|
||||
> `:453-471` (`ProofPacketHeader`+`ProofThumbnailGrid`),
|
||||
> `ProofImageViewer` `:521` @HEAD.
|
||||
### 2.3 Queue-item hydration + proof previews
|
||||
|
||||
### 2.3 Verb actions on action/approval cards (the 7-verb system; OPEN-3)
|
||||
For cards carrying a `qitemId`, `useQueueItemMap` hydrates the queue item;
|
||||
for `shipped` cards `sliceForCard` matches a slice by tag/text and
|
||||
`proofPreviewForSlice` pulls the first proof packet with screenshots, so
|
||||
the card renders an inline `ProofThumbnailGrid` → `ProofImageViewer`.
|
||||
|
||||
An actionable card (`action-required` or `approval`, no recorded
|
||||
outcome, non-terminal qitem) embeds `VerbActions`. The **canonical
|
||||
Mission Control action vocabulary is the 7-verb system** —
|
||||
> Source: `components/for-you/Feed.tsx` (`useQueueItemMap`,
|
||||
> `sliceForCard`, `proofPreviewForSlice`); `FeedCard.tsx` proof block and
|
||||
> `<ProofImageViewer>`.
|
||||
|
||||
### 2.4 Verb actions on action/approval cards
|
||||
|
||||
The **canonical Mission Control action vocabulary is the 7-verb system** —
|
||||
`MISSION_CONTROL_VERBS = [approve, deny, route, annotate, hold, drop,
|
||||
handoff]` — defined at the Mission Control system level (slice-00
|
||||
0.3.0-GT §1.6/seam (c): the vocabulary *originates in 0.3.0 source*).
|
||||
handoff]`. `VerbActions` defaults to all seven
|
||||
(`enabledVerbs = [...MISSION_CONTROL_VERBS]`); route/handoff need a
|
||||
destination, annotate an annotation, hold/drop a reason.
|
||||
|
||||
> Source: `components/mission-control/hooks/useMissionControlAction.ts:5-15`
|
||||
> (`MISSION_CONTROL_VERBS` 7-element const `:5-13` + `MissionControlVerb`
|
||||
> type `:15`), `components/mission-control/components/VerbActions.tsx:82`
|
||||
> (defaults to all 7 via `enabledVerbs = [...MISSION_CONTROL_VERBS]`),
|
||||
> per-verb input needs `:97-99` (route/handoff→destination L97;
|
||||
> annotate→annotation L98; hold/drop→reason L99) @HEAD; slice-00
|
||||
> 0.3.0-GT §1.6 + seam (c).
|
||||
> Source: `components/mission-control/hooks/useMissionControlAction.ts:5-15`;
|
||||
> `components/mission-control/components/VerbActions.tsx` (`enabledVerbs`
|
||||
> default `:108`).
|
||||
|
||||
> **OPEN-3 RESOLVED (v0.4.4 living-notes corrective, founder ruling
|
||||
> N-1, 2026-07-05).** The For-You actionable-card surface is **bare
|
||||
> one-tap APPROVE + CHAT — nothing else**. `FeedCard` passes
|
||||
> `enabledVerbs={["approve"]}` + `oneClickVerbs={["approve"]}` + the
|
||||
> v0.4.4 `bare` prop (JSX-only: skips the "Choose response" header
|
||||
> chrome; the mutation/receipt/error paths are byte-identical — pinned
|
||||
> by a raw-body test), beside a CHAT button that opens the **shared
|
||||
> `ProgressiveTerminal`** seeded via `buildChatPreamble` (terminal,
|
||||
> never a chat panel — BR-12). Deny/route are RETIRED from this
|
||||
> surface (including the action-required lens's empty-state copy:
|
||||
> "one-tap approve and chat with the owning agent"); the card-level
|
||||
> kind tag is the status label "Action required", never instruction
|
||||
> chrome. The 7-verb vocabulary above remains the MISSION-CONTROL
|
||||
> system-level vocabulary; For-You offers the two-action subset.
|
||||
>
|
||||
> Source @`bb5ad219`: `FeedCard.tsx:547-593` (bare `VerbActions`
|
||||
> `enabledVerbs=["approve"]` :561-566 + chat button + inline
|
||||
> `ProgressiveTerminal`), `:87-94` (`resolveCardTerminalSession` —
|
||||
> human-action cards chat with the SENDER), `Feed.tsx:76-82`
|
||||
> (`EMPTY_COPY["action-required"]`), `VerbActions.tsx` (`bare` prop),
|
||||
> `review/chat.ts:21` (`buildChatPreamble`); tests
|
||||
> `test/foryou-bare-approve-chat.test.tsx` (byte-identical mutation +
|
||||
> chatBtns:1/denyRoute:0 + retired-copy source-scan).
|
||||
**The For-You actionable-card surface is bare one-tap APPROVE + CHAT.**
|
||||
`FeedCard` passes `bare`, `enabledVerbs={["approve"]}` and
|
||||
`oneClickVerbs={["approve"]}`, beside a CHAT button that opens the shared
|
||||
`ProgressiveTerminal` seeded via `buildChatPreamble` (human-action cards
|
||||
chat with the sender). Deny/route are not offered here, and the
|
||||
action-required empty state reads "one-tap approve and chat with the
|
||||
owning agent". The 7-verb vocabulary remains the Mission Control
|
||||
system-level vocabulary.
|
||||
|
||||
> Source: `components/for-you/FeedCard.tsx:558-570` (`bare` `:561`,
|
||||
> `enabledVerbs` `:563`, `oneClickVerbs` `:564`),
|
||||
> `resolveCardTerminalSession`; `Feed.tsx:76-82` (`EMPTY_COPY`);
|
||||
> `review/chat.ts:21` (`buildChatPreamble`); test
|
||||
> `test/foryou-bare-approve-chat.test.tsx`.
|
||||
|
||||
On mutation success `VerbActions` fires `onOptimisticOutcome`; `Feed`
|
||||
keeps an optimistic-outcome map keyed by `qitemId` so the
|
||||
`ActionOutcomePanel` receipt renders instantly without waiting for the
|
||||
audit-log re-fetch (the audit re-fetch eventually surfaces the same
|
||||
shape). A terminal qitem with no recorded outcome derives a fallback
|
||||
receipt from its closure reason.
|
||||
`ActionOutcomePanel` receipt renders before the audit re-fetch. A terminal
|
||||
qitem with no recorded outcome derives a fallback receipt from its closure
|
||||
reason. Card footers also carry a "show context" queue-item trigger, a
|
||||
terminal drill, the evidence reference, and a "review →" link to the
|
||||
slice's Review tab.
|
||||
|
||||
> Source: `components/for-you/Feed.tsx:224-236` (optimistic-outcome
|
||||
> map + `setOptimisticOutcome`), `:426-428` (optimistic-first, audit
|
||||
> fallback: `optimisticOutcomes.get ?? actionOutcomes.get ?? null`);
|
||||
> `FeedCard.tsx:473-500` (`isActionableCard` gate L473 + `VerbActions`
|
||||
> wiring L489-499), `isActionableCard` `:166-175`,
|
||||
> `fallbackOutcomeFromQueueItem` `:177-198`, `ActionOutcomePanel`
|
||||
> `:271-310`; `VerbActions.tsx:138-145` (`onOptimisticOutcome` on
|
||||
> mutation `onSuccess`) @HEAD.
|
||||
> Source: `Feed.tsx` (optimistic map; optimistic-first lookup);
|
||||
> `FeedCard.tsx` (`isActionableCard`, `fallbackOutcomeFromQueueItem`,
|
||||
> `ActionOutcomePanel`, `QueueItemTrigger`, `FeedCardTerminalDrill`).
|
||||
|
||||
### 2.4 Card surface — vellum-coherent (0.3.1 brand layer)
|
||||
### 2.5 Card surface (0.3.1 brand layer)
|
||||
|
||||
`FeedCard` renders on the **0.3.1 vellum brand system**: a
|
||||
`bg-stone-100/45 backdrop-blur` surface with a 3-stop ambient box-shadow
|
||||
(no border), four `CornerBracket` marks registering the bounds through
|
||||
the vellum, a mono-uppercase kind tag + colored dot, and runtime
|
||||
graphics marks (`ActorMark` for sessions) — the slice-00 §1.7 "shared
|
||||
graphics marks across drawers / proof rows / queue refs / story rows"
|
||||
foundation, now expressed in the matured vellum vocabulary. Cards are
|
||||
keyboard-dismissable (Backspace/Delete) and swipe-dismissable, with an
|
||||
`UndoToast`.
|
||||
`FeedCard` renders a `bg-surface-low/45 backdrop-blur-[10px]` surface with
|
||||
a 3-stop ambient box-shadow (no border), four `CornerBracket` marks, a
|
||||
mono-uppercase kind tag with a tone-coloured icon (a dot only when a kind
|
||||
has no icon), and `ActorMark` runtime marks. Cards dismiss by keyboard
|
||||
(Backspace/Delete) or swipe, with an `UndoToast`.
|
||||
|
||||
> Source: `components/for-you/FeedCard.tsx:72-73` (`CARD_SURFACE_CLASS`),
|
||||
> `:74-80` (`CARD_SHADOW_STYLE` 3-stop box-shadow), corner brackets
|
||||
> `:401-404` (4× `<CornerBracket position=…>`), `KIND_DOT` `:33-39`,
|
||||
> kind tag span `:410-414`, `ActorMark` import `:25` + usage `:229`;
|
||||
> `CornerBracket` from the 0.3.1 brand barrel
|
||||
> `dashboard/vellum/index.ts:17`; slice-00 0.3.0-GT seam (b) + §1.7 @HEAD.
|
||||
> Source: `components/for-you/FeedCard.tsx` — `CARD_SURFACE_CLASS`
|
||||
> `:108-109`, `CARD_SHADOW_STYLE`, `KIND_TOKEN`, corner brackets
|
||||
> `:452-455`, `ActorMark` `:265`; `CornerBracket` from
|
||||
> `dashboard/vellum/index.ts`.
|
||||
|
||||
### 2.5 Storytelling preview band
|
||||
|
||||
Above the legacy card list `Feed` renders a `StorytellingFeed` preview
|
||||
built by `buildStorytellingFeedItems` from discovered missions
|
||||
(`useMissionDiscovery` → `ProgressCard`, first 2) + slices
|
||||
(`useSlices` → `ShippedCard` for shipped/done, `IncidentCard`
|
||||
otherwise, capped at 3). Mission rows carry a daemon-derived
|
||||
`status` so the "Getting Started"-style complete-and-hide filters
|
||||
durably (`status === "complete"`), with an optimistic local hide +
|
||||
best-effort `POST /api/missions/:id/complete` audit write that swallows
|
||||
network errors so a partial-air-gapped daemon does not block the hide.
|
||||
|
||||
> Source: `components/for-you/Feed.tsx:319-343` (mission/slice adapters:
|
||||
> `useMissionDiscovery` L319, `missionsWithStatus` L329-335,
|
||||
> `buildStorytellingFeedItems` L336-343; adapter comment L305-318),
|
||||
> `handleMarkMissionComplete` `:351-361` (best-effort `void fetch` L354,
|
||||
> error-swallowed L357), preview render `:399-409`;
|
||||
> `components/feed/cards/storytelling-cards.tsx:36` (`CardKind`),
|
||||
> `ShippedCard` `:184`, `IncidentCard` `:235`, `ProgressCard` `:289`
|
||||
> @HEAD.
|
||||
The 0.3.1 storytelling preview band is **gone**: `Feed.tsx:375-380` records
|
||||
its removal. The `components/feed/cards/storytelling-cards.tsx` primitives
|
||||
remain for the `/lab/card-previews` gallery. No UI code calls
|
||||
`POST /api/missions/:id/complete` any more; the daemon route still exists
|
||||
(`packages/daemon/src/routes/missions.ts`).
|
||||
|
||||
## 3. Project — workspace / mission / slice scope pages (0.3.0 spine)
|
||||
|
||||
`/project*` mounts three scope pages, all built on a shared
|
||||
`ScopeShell` + tabbed-rollup pattern (slice-00 §1.5 project
|
||||
observability foundation). A `SHARED_TABS` set —
|
||||
`overview / story / progress / artifacts / tests / queue / topology` —
|
||||
drives `WorkspaceScopePage` and `MissionScopePage`; `SLICE_TABS`
|
||||
reorders `story` first for `SliceScopePage` but `SliceScopePage`'s
|
||||
`useState` default is `overview` (README + readiness first, not a
|
||||
metric grid).
|
||||
`/project*` mounts three scope pages on a shared `ScopeShell` +
|
||||
`TabNav` tabbed pattern. Tab sets:
|
||||
|
||||
> Source: `components/project/ScopePages.tsx:75-76` (`SharedTab` /
|
||||
> `SliceTab` types), `SHARED_TABS` `:78-86`, `SLICE_TABS` `:88-96`,
|
||||
> `ScopeShell` `:137-166`, `TabNav` `:98-135` @HEAD.
|
||||
| Page | Tabs | Default |
|
||||
|---|---|---|
|
||||
| `WorkspaceScopePage` | `SHARED_TABS`: Overview, Story, Progress, Artifacts, Proof, Queue, Workflow (`topology`) | `overview` |
|
||||
| `MissionScopePage` | `MISSION_TABS`: Overview, Steering, Review, Story, Progress, Artifacts, Proof, Queue, Workflow | `steering` |
|
||||
| `SliceScopePage` | `SLICE_TABS`: Review, Story, Overview, Progress, Artifacts, Proof, Queue, Workflow | `review` |
|
||||
|
||||
> Source: `components/project/ScopePages.tsx` — `SharedTab`/`SliceTab`
|
||||
> `:83-84`, `SHARED_TABS` `:86-94`, `MISSION_TABS` `:98-110`, `SLICE_TABS`
|
||||
> `:112-123`, `TabNav`, `ScopeShell`; defaults `useState` at `:698`,
|
||||
> `:798`, `:1226`.
|
||||
|
||||
Off the local host, the workspace and mission headers show an `ON <host>`
|
||||
chip (the slice header does not), the Review tab is local-only, and
|
||||
file-backed sections explain why local files are not shown.
|
||||
|
||||
### 3.1 WorkspaceScopePage (`/project`)
|
||||
|
||||
Reads the live workspace name (`useWorkspaceName`); honest empty-state
|
||||
(`NO WORKSPACE CONNECTED` with an Open-settings action) when unset. The
|
||||
`overview` tab renders `WorkspaceOverviewPanel`: slices grouped into
|
||||
missions, then `partitionProjectMissions` splits them into a
|
||||
two-column **Current Work / Archive** layout (slice-00 §1.5
|
||||
"current/archive grouping"). Each mission card lists its slices as
|
||||
`Link`s to `/project/slice/$sliceId` with a `QueueCountIcon` +
|
||||
`StatusDot`. The other tabs are the scope rollups (§3.4).
|
||||
Reads the live workspace name; honest empty state (`NO WORKSPACE
|
||||
CONNECTED` with an Open-settings action) when unset. Tabs: Overview →
|
||||
`WorkspacePortfolioPanel`; Story/Progress/Artifacts/Proof/Queue → the
|
||||
scope rollups (§3.4); Workflow → `HostMultiRigGraph`.
|
||||
(`WorkspaceOverviewPanel`, the older Current Work / Archive grid, is still
|
||||
defined but not rendered.)
|
||||
|
||||
> Source: `components/project/ScopePages.tsx:676-749` (`WorkspaceScopePage`;
|
||||
> no-workspace empty-state guard `:682-693`, `workspace-scope-no-workspace`
|
||||
> `:689`), `WorkspaceOverviewPanel` `:534-674` (mission bucketing
|
||||
> `:536-551`, `partitionProjectMissions` `:552`, two-column panel `:631`,
|
||||
> Current section `:632-651`, Archive section `:652-671`) @HEAD.
|
||||
> Source: `ScopePages.tsx` `WorkspaceScopePage` (no-workspace guard;
|
||||
> `WorkspacePortfolioPanel` `:745`; `HostMultiRigGraph` `:788`).
|
||||
|
||||
### 3.2 MissionScopePage (`/project/mission/$missionId`)
|
||||
|
||||
Same `ScopeShell`/`SHARED_TABS`. `overview` renders the mission README
|
||||
(`useScopeMarkdown(missionPath, "README.md")` via `MarkdownViewer`)
|
||||
above a slice rail; `progress` renders a `MissionProgressHeatmap` + the
|
||||
mission `PROGRESS.md` + the shared `ScopeProgressRollup`. The `topology`
|
||||
tab renders the **projected workflow spec graph** via `TopologyTab`
|
||||
when the mission's README frontmatter declares a `workflow_spec` that is
|
||||
in the `WorkflowSpecCache` (daemon returns `topology.specGraph`),
|
||||
falling back to session-name aggregation otherwise.
|
||||
Lands on **Steering** (`SteeringTab`). Review → `MissionReviewTab` (§3.5).
|
||||
Overview renders the mission README (`MarkdownViewer`) above a slice rail.
|
||||
Progress renders `MissionProgressHeatmap` plus the mission `PROGRESS.md`
|
||||
(the per-slice rollup cards were removed). Artifacts → `ArtifactsNavigator`;
|
||||
Proof → `ScopeProofRollup`. Workflow renders the **projected workflow
|
||||
spec graph** via `TopologyTab` when the mission README declares a
|
||||
`workflow_spec` that is in the workflow-spec cache (the daemon returns
|
||||
`topology.specGraph`), otherwise `ScopeTopologyRollup`.
|
||||
|
||||
> Source: `components/project/ScopePages.tsx:751-895` (`MissionScopePage`;
|
||||
> `useMission` `:759`, `missionPath` `:760-761`, README/PROGRESS
|
||||
> markdown `:762-763`, overview README section `:775`, progress
|
||||
> heatmap `:832`, `mission-progress-readme` `:838`, spec-graph topology
|
||||
> branch `:875-891` — `missionTopology` `:875`, `specGraph` check
|
||||
> `:879`, session-name fallback `:890`) @HEAD.
|
||||
> Source: `ScopePages.tsx` `MissionScopePage` (`SteeringTab` `:834`,
|
||||
> `MissionReviewTab` `:843`, `MissionProgressHeatmap` `:906`,
|
||||
> rollup-cards removal note `:979-983`, `ArtifactsNavigator` `:990`,
|
||||
> spec-graph branch `:1010-1036`).
|
||||
|
||||
### 3.3 SliceScopePage (`/project/slice/$sliceId`)
|
||||
|
||||
Default tab `overview` (`SliceOverviewTab` — README + current step +
|
||||
readiness). Loading/error states are honest empty-states (the error
|
||||
state names `rig config get workspace.slices_root` as the likely
|
||||
misconfiguration). `progress` folds in `AcceptanceTab` (acceptance is
|
||||
the canonical slice-scope progress proof); `story` renders `TimelineTab`
|
||||
with curated `timeline.md` (`useSliceTimelineMarkdown`) above the
|
||||
auto-captured event feed; `topology` renders the slice's
|
||||
workflow-instance-aware `TopologyTab`; **`review` (v0.4.4) renders the
|
||||
Living Notes `SliceReviewTab`** (§3.5).
|
||||
Lands on **Review** (`SliceReviewTab`, §3.5). Overview →
|
||||
`SliceOverviewTab` (a 4-metric summary — status, progress, qitems, last
|
||||
activity — then the README). Story → `ScopeStoryRollup` (the queue-lineage
|
||||
story graph). Progress → `AcceptanceTab`. Artifacts → `ArtifactsNavigator`;
|
||||
Proof → `SliceProofTab`; Queue → `SliceQueueTab`; Workflow → `TopologyTab`.
|
||||
Loading and error states are honest (the error names
|
||||
`rig config get workspace.slices_root` as the likely misconfiguration).
|
||||
|
||||
> Source: `components/project/ScopePages.tsx:1187-1297` (`SliceScopePage`;
|
||||
> default `overview` `:1192`, timeline md `:1204`, loading-state
|
||||
> guard `:1206`, error-state guard `:1225` (slices_root remediation
|
||||
> hint `:1239`), story=`TimelineTab` `:1258-1265`,
|
||||
> progress=`AcceptanceTab` `:1269-1273`, topology `:1294`) @HEAD.
|
||||
> Source: `ScopePages.tsx` `SliceScopePage` (`SliceReviewTab` `:1307`,
|
||||
> `ScopeStoryRollup` `:1317`, `SliceOverviewTab` `:1325`, `AcceptanceTab`
|
||||
> `:1354`, `ArtifactsNavigator` `:1363`, `SliceProofTab` `:1366`,
|
||||
> `TopologyTab` `:1379`, slices_root hint `:1276`), `SliceOverviewTab`
|
||||
> summary grid `:1157-1162`.
|
||||
|
||||
### 3.4 Shared scope rollups
|
||||
|
||||
`ScopeStoryRollup`, `ScopeProgressRollup`, `ScopeArtifactsRollup`,
|
||||
`ScopeTestsRollup`, `ScopeQueueRollup`, `ScopeTopologyRollup` (the
|
||||
non-overview tabs) all read from one `useProjectScopeRollup(missionId,
|
||||
loadDetails)` hook so workspace and mission scopes share identical
|
||||
rollup behavior over their respective slice sets — the slice-00 §1.5
|
||||
"workspace/mission rollups" foundation. Detail fetching is gated on the
|
||||
active tab (`active !== "overview"`) so the overview path stays cheap.
|
||||
`ScopeQueueRollup` and `ScopeTopologyRollup` read from one
|
||||
`useProjectScopeRollup(missionId, loadDetails)` hook so workspace and
|
||||
mission scopes share rollup behavior; `ScopeProofRollup`
|
||||
(`project/ProofTab.tsx`) covers Proof. Detail fetching is gated on the
|
||||
active tab (`active !== "overview"` for the workspace;
|
||||
`active !== "overview" && active !== "steering"` for a mission).
|
||||
|
||||
> Source: `components/project/ScopePages.tsx:198-217`
|
||||
> (`useProjectScopeRollup`; `rowsForScope` `:193-196`),
|
||||
> rollup components `:219-532`; gate `:679`,`:754` (`active !== "overview"`)
|
||||
> @HEAD.
|
||||
> Source: `ScopePages.tsx` `useProjectScopeRollup`, rollup components,
|
||||
> gates `:700`, `:803`.
|
||||
|
||||
### 3.5 Review tabs (v0.4.4 — the Living Notes surface)
|
||||
### 3.5 Review tabs (the Living Notes surface)
|
||||
|
||||
Both the slice and mission scope pages mount a `Review` tab
|
||||
(`SLICE_TABS`/mission tabs, `ScopePages.tsx:99-113`). The slice tab
|
||||
(`review/SliceReviewTab.tsx`) renders the ONE reviewable structure per
|
||||
slice — bands in order **NEEDS YOU → AGENTS → INTENT / PLAN / DELIVERED →
|
||||
verify-lineage → SETTLED** — from `GET /api/review/slice/:name`
|
||||
(`hooks/useReview.ts`):
|
||||
The slice tab (`review/SliceReviewTab.tsx`) renders one reviewable
|
||||
structure per slice — bands in order **NEEDS YOU → AGENTS → INTENT / PLAN
|
||||
/ DELIVERED → verify-lineage → SETTLED** — from `GET /api/review/slice/:name`
|
||||
(`hooks/useReview.ts`), with a proof-readiness status line and a defects
|
||||
box above NEEDS YOU:
|
||||
|
||||
- **DELIVERED** pairs each planned deliverable with its curated proof down
|
||||
the column (planned mockup above delivered artifact), media inline at text
|
||||
height expanding on tap one-at-a-time (`?item=` deep link); `verified`
|
||||
renders QA's recorded comparison in plain words (✓ QA-verified · ◇
|
||||
unverified · ✗ missing + QA's note) — visible, never blocking. "See all
|
||||
proof" drills into `proof/`.
|
||||
- **Media actually plays**: video renders inline with native controls
|
||||
(`?seek`/`?play` deep links for capture verification); images open the
|
||||
shipped `Lightbox`; evidence and the "full PRD →" door open the shipped
|
||||
`FileViewer` in the shared **right-edge** drawer (the v0.4.4 corrective
|
||||
reverted the FR-11.1 left flip at its three tokens).
|
||||
- **Two locks** (§4 of the architecture module) render as deliberate stamps:
|
||||
plan-lock in PLAN, both stamps in SETTLED; an unaudited stamp renders
|
||||
loudly as UNVERIFIED.
|
||||
- Review cards ride ONE vellum recipe (`review/vellum.ts` —
|
||||
background-matched translucency + backdrop blur, token-driven for both
|
||||
themes); DELIVERED rows stack vertically below the `sm` breakpoint
|
||||
(phone-first single-column scan).
|
||||
- **DELIVERED** pairs each planned deliverable with its curated proof,
|
||||
media inline and expanding one at a time (`?item=` deep link). Without a
|
||||
configured readiness policy, `verified` renders as `✓ legacy QA-verified
|
||||
(item revision unbound)` · `◇ unverified — no PASSING QA comparison` ·
|
||||
`✗ missing — promised, nothing delivered`; with one, as `✓ accepted under
|
||||
selected policy` / `◇ current acceptance absent`. A note renders as
|
||||
"Judgment note:". Visible, never blocking. "See all proof" drills into
|
||||
`proof/`.
|
||||
- **Media plays**: video inline with native controls (`?seek`/`?play` deep
|
||||
links); images open `Lightbox`; evidence and the "full PRD →" door open
|
||||
`FileViewer` in the shared right-edge drawer.
|
||||
- **Two locks** render as stamps: plan-lock in PLAN, both stamps in
|
||||
SETTLED; an unaudited stamp renders as UNVERIFIED.
|
||||
- Review cards share one vellum recipe (`review/vellum.ts`); DELIVERED rows
|
||||
stack below the `sm` breakpoint.
|
||||
- The mission tab (`review/MissionReviewTab.tsx`) is board-first: stage
|
||||
cells from the collapsed contract, the completion ledger, cut-complete,
|
||||
and a U5 row expansion reading the SAME contract (intent verbatim + a
|
||||
bounded per-item verified summary; the full pairing lives on the slice
|
||||
page). The rig altitude (`review/RigAgentsPage.tsx`) reads
|
||||
`GET /api/review/rig`.
|
||||
cells, the completion ledger, cut-complete, and a row expansion reading
|
||||
the same contract. The rig altitude (`review/RigAgentsPage.tsx`, route
|
||||
`/agents`) reads `GET /api/review/rig`.
|
||||
|
||||
Backend contract + composition: see
|
||||
Backend contract: see
|
||||
[`../architecture/living-notes-review.md`](../architecture/living-notes-review.md).
|
||||
|
||||
> Source @`bb5ad219`: `components/review/{SliceReviewTab,MissionReviewTab,
|
||||
> Source: `components/review/{SliceReviewTab,MissionReviewTab,
|
||||
> NeedsYouAccordion,AgentsBandView,VerifyLineageCard,EvidenceOpener,
|
||||
> RigAgentsPage}.tsx`, `review/vellum.ts`, `hooks/useReview.ts`,
|
||||
> `SharedDetailDrawer.tsx` (right-edge anchor + pre-flip z);
|
||||
> tests `test/{fileviewer-resolvable-target,drawer-primitives}.test.tsx`.
|
||||
> The rejected three-column compare and the separate item-join table are
|
||||
> DELETED files (corrective delete-not-demote; zero references remain).
|
||||
> `SharedDetailDrawer.tsx`; verified labels `SliceReviewTab.tsx:131-135`,
|
||||
> `:164`, `:194`.
|
||||
|
||||
## 4. Dashboard — the landing surface (`/` → `Dashboard`)
|
||||
## 4. Dashboard — the launcher (`/` → `Dashboard`)
|
||||
|
||||
`Dashboard` is a thin composition over the **0.3.1 vellum brand
|
||||
system**: it imports `MidLayerContent`, `TopLayerContent`,
|
||||
`DestinationsLayer` from `dashboard/vellum/index.js` — the SAME barrel
|
||||
`/lab/vellum-lab` imports, so the production dashboard tracks the design
|
||||
lab exactly (single source of truth). Real-data wiring: `useRigSummary`
|
||||
→ totalRigs/totalAgents, `usePsEntries` → activeAgents, `useSpecLibrary`
|
||||
→ librarySize, `window.location.hostname` → classification eyebrow. Per
|
||||
a 2026-05-15 founder dispatch the heavy `BackLayerContent` /
|
||||
`BackVellumSheet` back-layer was removed so the dashboard sits on the
|
||||
page-level cream paper-grid; the barrel still exports them (used by the
|
||||
lab).
|
||||
`Dashboard` is the paper-draft launcher (OPR.0.4.1.14). It renders, in
|
||||
order: a header with the station online state; `FieldEnvironment` (live
|
||||
rows: RIGS from `useRigSummary`, AGENTS as the sum of `nodeCount` from
|
||||
`usePsEntries`, STATION from `window.location.hostname`, OPERATOR ID from
|
||||
the `agents.operator_session` setting with an `OPERATOR` fallback, and
|
||||
VERSION from `useDaemonVersion` via `/api/health-summary/version`);
|
||||
`KernelStatusCard` (from `/api/kernel/status`); `HostConfigCard`; a grid of
|
||||
six destinations (Topology, Project, For You, Library, Search & Audit,
|
||||
Settings); and `DashboardFooter`. Its glyphs come from
|
||||
`dashboard/vellum/fidelity-glyphs.js`.
|
||||
|
||||
> Source: `components/dashboard/Dashboard.tsx:1-52` (vellum-barrel
|
||||
> import `:12-16`, single-source-of-truth note `:2-5`, real-data hooks
|
||||
> `:29-39`, back-layer-removal note `:21-27`, render `:40-50`);
|
||||
> `components/dashboard/vellum/index.ts:1-26` (barrel; `BackLayerContent`
|
||||
> `:5` / `BackVellumSheet` `:6` still exported); slice-00 0.3.0-GT
|
||||
> seam (b) + §2 rows 1/2 @HEAD.
|
||||
The vellum barrel `dashboard/vellum/index.ts` still exports
|
||||
`BackLayerContent` and `BackVellumSheet`; `/lab/vellum-lab`
|
||||
(`lab/VellumLab.tsx`) imports from it.
|
||||
|
||||
> Source: `components/dashboard/Dashboard.tsx` (header comment `:1-24`,
|
||||
> glyph import, `DESTINATIONS`, data wiring, render order);
|
||||
> `hooks/useDaemonVersion.ts`; `components/dashboard/vellum/index.ts:5-6`.
|
||||
|
||||
## 5. Cross-cutting properties
|
||||
|
||||
- **Live daemon state, no UI-local persistence beyond soft-dismiss** —
|
||||
every surface reads daemon hooks; the only UI-local state is the
|
||||
per-event-seq dismiss set + transient lens chips + optimistic-outcome
|
||||
map (`Feed.tsx:217` lens, `:224` optimistic map, `:240-241` dismiss
|
||||
set; `ScopePages.tsx` `useState` tab state `:677`,`:753`,`:1192`)
|
||||
@HEAD.
|
||||
- **Honest empty/error states** — `WorkspaceScopePage` no-workspace
|
||||
(`ScopePages.tsx:682-693`), `WorkspaceOverviewPanel` index-unavailable
|
||||
(`:565-573`, label `:568`), `SliceScopePage` not-available
|
||||
(`:1225-1245`, label `:1235`) all surface the cause + a remediation
|
||||
pointer, never a blank screen @HEAD.
|
||||
- **Live daemon state** — every surface reads daemon hooks. UI-local state
|
||||
on these surfaces is the two For You dismissal sets (localStorage), the
|
||||
lens, host filter and optimistic-outcome map (transient), and each scope
|
||||
page's tab state.
|
||||
- **Honest empty/error states** — `WorkspaceScopePage` no-workspace and
|
||||
`SliceScopePage` not-available surface the cause and a remediation
|
||||
pointer, never a blank screen.
|
||||
- **0.3.0 observability spine on the 0.3.1 vellum brand layer** — the
|
||||
classifier / verb-action / scope-rollup *logic* is the 0.3.0
|
||||
foundation (§0); the *visual surface* (vellum cards, corner brackets,
|
||||
graphics marks, dashboard) is the 0.3.1 brand maturation. The split is
|
||||
slice-00 0.3.0-GT seam (b); do not collapse either direction @HEAD.
|
||||
classifier / verb-action / scope-rollup logic is the 0.3.0 foundation
|
||||
(§0); the visual surface is the 0.3.1 brand layer and later refreshes.
|
||||
|
||||
## OPEN items (carried, not smoothed)
|
||||
|
||||
- **OPEN — For-You verb subset (slice-00 0.3.0-GT OPEN-3).** The exact
|
||||
shipped For-You-surface verb subset is an unresolved velocity slice-01
|
||||
ruling (CHANGELOG `[0.3.1]` "all 7" vs source-material §6 "subset"
|
||||
discrepancy). This module describes the **7-verb system-level
|
||||
vocabulary only** and reports the literal current `enabledVerbs`
|
||||
prop value (§2.3) as source state, NOT as a resolution. Defer
|
||||
For-You-subset enumeration to the pending velocity ruling.
|
||||
- **OPEN — `/project` redirect-stub consolidation.** `/progress`,
|
||||
`/slices`, `/steering` are `<Navigate to="/project">` stubs and
|
||||
`/slices/$name` redirects into `/project/slice/$sliceId` (§1). The
|
||||
former standalone surfaces fold into Project tabs; whether the stubs
|
||||
are permanent or transitional is a product decision not resolvable
|
||||
from source.
|
||||
- No slice-00 numeric-drift OPEN (1–5) applies — this module carries no
|
||||
migration / route-group / PL-004-event counts.
|
||||
`/slices/$name` redirects into `/project/slice/$sliceId` (§1). Whether
|
||||
the stubs are permanent or transitional is a product decision not
|
||||
resolvable from source.
|
||||
@@ -10,8 +10,8 @@ applies-when: |
|
||||
mounts, or how the shared detail drawer and event consumption work.
|
||||
siblings: [topology.md, project-and-for-you.md, library-specs-and-design-system.md]
|
||||
prerequisite-reads: [../README.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# UI Shell, Routing, Drawer System
|
||||
@@ -21,15 +21,18 @@ operator surface. Brand/visual rules live in `docs/DESIGN.md` (pointer from
|
||||
`library-specs-and-design-system.md`); this module is how the shell is
|
||||
assembled and what routes it mounts.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c`; package version **0.3.1**
|
||||
> (slice-00 §1.1). UI footprint is **235** source files
|
||||
> (`packages/ui/src`, `.ts`+`.tsx`, non-test; slice-00 §1.6 / D6).
|
||||
> Verified against source at `e8f0ab34`; package version 0.6.7. UI
|
||||
> footprint is **304** source files (`packages/ui/src`, `.ts`+`.tsx`,
|
||||
> non-test; 235 at the previous stamp `7eaf524c`). The web UI is in
|
||||
> maintenance mode (`docs/reference/developing.md`).
|
||||
|
||||
## 1. Package shape
|
||||
|
||||
UI package: `packages/ui`. Primary implementation areas:
|
||||
|
||||
- `src/routes.tsx` — TanStack Router route tree.
|
||||
- `src/App.tsx`, `src/main.tsx` — app root (`ThemeProvider`, the
|
||||
maintenance notice, the router).
|
||||
- `src/components/AppShell.tsx` — global shell: rail, Explorer, drawer,
|
||||
preview stack.
|
||||
- `src/components/ui/` — reusable vellum + base UI primitives.
|
||||
@@ -37,6 +40,8 @@ UI package: `packages/ui`. Primary implementation areas:
|
||||
- `src/components/topology/` — graph/table/terminal topology surfaces.
|
||||
- `src/components/project/`, `for-you/`, `dashboard/`, `feed/` —
|
||||
observability surfaces.
|
||||
- `src/components/review/`, `workflow/` — review altitudes (`/agents`,
|
||||
`/fleet`) and workflow pages.
|
||||
- `src/components/specs/` — Library / skills / plugins surfaces.
|
||||
- `src/components/{preview,markdown,drawer-viewers}/` — viewers.
|
||||
- `src/hooks/`, `src/lib/` — route data hooks, classifiers, formatters,
|
||||
@@ -44,13 +49,23 @@ UI package: `packages/ui`. Primary implementation areas:
|
||||
|
||||
## 2. Shell model
|
||||
|
||||
`AppShell` wraps every route. `AppShell.tsx` lays a 48px icon rail + a
|
||||
route-aware Explorer sidebar + center workspace + shared right drawer +
|
||||
preview stack + topology overlay provider + shared drawer-selection /
|
||||
discovery-placement contexts.
|
||||
The root route wraps `AppShell` in `QueryClientProvider` and
|
||||
`DaemonHealthProvider` (`routes.tsx:80-90`). Above the router, `App.tsx`
|
||||
renders a dismissible `UiMaintenanceNotice` (localStorage key
|
||||
`openrig.uiMaintenanceNoticeDismissed`); `main.tsx` mounts `ThemeProvider`.
|
||||
|
||||
The rail destinations (`AppShell.tsx` `RAIL_ICONS`, re-confirmed at HEAD)
|
||||
are exactly six destination icons plus Advisor/Operator entries:
|
||||
`AppShell.tsx` lays a 48px icon rail + a route-aware Explorer sidebar +
|
||||
center workspace + shared right drawer + preview stack + topology overlay
|
||||
provider + shared drawer-selection / discovery-placement / specs-workspace
|
||||
contexts. The top bar's right slot holds `HostIndicator` and
|
||||
`ThemeSelector`. When the shared event connection is down, a "Live updates
|
||||
unavailable…" status line shows. Switching the selected host clears the
|
||||
discovery placement (`useClearPlacementOnHostSwitch`). Below the `lg`
|
||||
breakpoint a 3-slot `MobileBottomNav` (For You / Project / Topology) also
|
||||
shows.
|
||||
|
||||
The rail destinations (`AppShell.tsx` `RAIL_ICONS`) are exactly six
|
||||
destination icons plus Advisor/Operator entries:
|
||||
|
||||
- Dashboard `/` (`rail-dashboard`)
|
||||
- Topology `/topology` (`rail-topology`)
|
||||
@@ -64,95 +79,115 @@ rather than acting as a generic file browser.
|
||||
|
||||
## 3. Route model
|
||||
|
||||
> Drift-fix D16 — `ui.md` "Route Model" (the L52–73 list) and
|
||||
> `architecture.md` §2 "UI architecture" predate 0.3.x. The route table
|
||||
> below is **re-derived from `packages/ui/src/routes.tsx` at HEAD** (542
|
||||
> lines). The route tree is assembled at `routes.tsx:480`
|
||||
> (`rootRoute.addChildren([...])`).
|
||||
> The route table below is derived from `packages/ui/src/routes.tsx` at
|
||||
> `e8f0ab34` (605 lines). Each line number is the route's
|
||||
> `createRoute(` line. The exported route tree is assembled at
|
||||
> `routes.tsx:539` (`export const routeTree = rootRoute.addChildren([`);
|
||||
> it holds 54 routes.
|
||||
>
|
||||
> Correction to the D16 expectation itself (source-verified at HEAD): the
|
||||
> survey expected `/mission-control`, `/progress`, `/markdown` as net-new
|
||||
> *real* routes. At HEAD `/mission-control` and `/progress` are **redirect
|
||||
> stubs**, not destination routes, and there is **no `/markdown` route**.
|
||||
> There IS a real `/files` route (markdown is rendered by the `MarkdownViewer`
|
||||
> component inside file/drawer surfaces, not via a `/markdown` route). The
|
||||
> route table is reported as source says, not as the survey predicted.
|
||||
> `/mission-control` and `/progress` are **redirect stubs**, not
|
||||
> destination routes, and there is **no `/markdown` route** (markdown is
|
||||
> rendered by the `MarkdownViewer` component). There is a real `/files`
|
||||
> route.
|
||||
|
||||
### Primary destination routes
|
||||
|
||||
| Path | Component | Notes |
|
||||
|---|---|---|
|
||||
| `/` | Dashboard (`indexRoute` `routes.tsx:88`) | rail destination |
|
||||
| `/topology` | host topology (`:98`) | + rig/pod/seat scopes (see `topology.md`) |
|
||||
| `/topology/rig/$rigId` | rig scope (`:104`) | |
|
||||
| `/topology/pod/$rigId/$podName` | pod scope (`:110`) | |
|
||||
| `/topology/seat/$rigId/$logicalId` | seat scope (`:116`) | live node details |
|
||||
| `/for-you` | attention feed (`:122`) | rail destination |
|
||||
| `/project` | workspace project scope (`:128`) | rail destination |
|
||||
| `/project/mission/$missionId` | mission scope (`:134`) | |
|
||||
| `/project/slice/$sliceId` | slice scope (`:140`) | |
|
||||
| `/specs` | Library (`:146`) | rail destination |
|
||||
| `/specs/applications` | applications section (`:152`) | |
|
||||
| `/specs/skills` | skills index (`:160`) | |
|
||||
| `/specs/skills/$skillToken` | skill viewer (`:166`) | defaults to `skill.md` |
|
||||
| `/specs/skills/$skillToken/file/$fileToken` | skill file viewer (`:175`) | |
|
||||
| `/specs/plugins` | plugins index (`:186`) | 0.3.1 (see `library-specs…`) |
|
||||
| `/plugins/$pluginId` | plugin detail (`:201`) | 0.3.1 |
|
||||
| `/specs/$specKind/$specName` | generic spec → library redirect (`:213`) | |
|
||||
| `/files` | Files workspace (`:192`) | net-new vs ui.md/D16 |
|
||||
| `/settings` | settings center (`:222`) | rail destination |
|
||||
| `/settings/policies` | Policies (`:232`) | slice 27 Claude-compaction form |
|
||||
| `/settings/log` | Log (`:237`) | slice 26 4-item Settings explorer |
|
||||
| `/settings/status` | Status (`:242`) | |
|
||||
| `/search` | audit/history view (`:248`) | |
|
||||
| `/` | Dashboard (`indexRoute` `routes.tsx:96`) | rail destination |
|
||||
| `/topology` | host topology (`:106`) | + rig/pod/seat scopes (see `topology.md`) |
|
||||
| `/topology/rig/$rigId` | rig scope (`:112`) | |
|
||||
| `/topology/pod/$rigId/$podName` | pod scope (`:118`) | |
|
||||
| `/topology/seat/$rigId/$logicalId` | seat scope (`:124`) | live node details |
|
||||
| `/for-you` | attention feed (`:130`) | rail destination |
|
||||
| `/project` | workspace project scope (`:136`) | rail destination |
|
||||
| `/project/mission/$missionId` | mission scope (`:142`) | |
|
||||
| `/project/slice/$sliceId` | slice scope (`:148`) | |
|
||||
| `/specs` | Library (`:202`) | rail destination |
|
||||
| `/specs/applications` | applications section (`:208`) | |
|
||||
| `/specs/skills` | skills index (`:216`) | |
|
||||
| `/specs/skills/$skillToken` | skill viewer (`:222`) | defaults to `SKILL.md` |
|
||||
| `/specs/skills/$skillToken/file/$fileToken` | skill file viewer (`:231`) | |
|
||||
| `/specs/plugins` | plugins index (`:242`) | 0.3.1 (see `library-specs…`) |
|
||||
| `/plugins/$pluginId` | plugin detail (`:257`) | 0.3.1 |
|
||||
| `/specs/$specKind/$specName` | generic spec → library redirect (`:269`) | |
|
||||
| `/files` | Files workspace (`:248`) | |
|
||||
| `/settings` | settings center (`:278`) | rail destination |
|
||||
| `/settings/policies` | Policies (`:288`) | Claude-compaction form |
|
||||
| `/settings/log` | Log (`:293`) | 4-item Settings explorer |
|
||||
| `/settings/status` | Status (`:298`) | |
|
||||
| `/search` | audit/history view (`:304`) | |
|
||||
|
||||
### Zoom-addressed routes (no rail entry)
|
||||
|
||||
Reached from other views and deep links, deliberately not on the rail:
|
||||
|
||||
| Path | Component | Notes |
|
||||
|---|---|---|
|
||||
| `/agents` | `RigAgentsPage` (`:159`) | rig review altitude; separate from legacy `/agents/validate` |
|
||||
| `/fleet` | `FleetPage` (`:171`) | fleet attention altitude above host |
|
||||
| `/workflows` | `WorkflowsPage` (`:181`) | |
|
||||
| `/workflow/instance/$instanceId` | `WorkflowInstancePage` (`:187`) | optional `?step=<id>` anchor |
|
||||
|
||||
### Lab routes (design experiments)
|
||||
|
||||
`/lab/project-graphics-preview` (`:254`), `/lab/card-previews` (`:262`),
|
||||
`/lab/vellum-lab` (`:272`), `/lab/vellum-bg/{a-large,b-small,c-allover}`
|
||||
(`:283`–`:293`). The vellum-lab + vellum-bg routes are the 0.3.1 vellum
|
||||
brand-system experiment surface (slice-00 0.3.0-GT seam b: vellum brand
|
||||
identity matured in 0.3.1; the `dashboard/vellum/*` system is 0.3.1).
|
||||
`/lab/project-graphics-preview` (`:310`), `/lab/card-previews` (`:318`),
|
||||
`/lab/vellum-lab` (`:328`), `/lab/vellum-bg/{a-large,b-small,c-allover}`
|
||||
(`:339`–`:349`). The vellum-lab + vellum-bg routes are the 0.3.1 vellum
|
||||
brand-system experiment surface (the `dashboard/vellum/*` system is 0.3.1).
|
||||
|
||||
### Legacy / compatibility routes
|
||||
|
||||
`/rigs/$rigId` (`:318`), `/rigs/$rigId/nodes/$logicalId` (`:324`),
|
||||
`/import` (`:333`), `/packages`, `/packages/install`, `/packages/$packageId`
|
||||
(`:339`–`:351`), `/bootstrap` (`:357`), `/agents/validate` (`:363`),
|
||||
`/specs/rig` + `/specs/agent` review (`:372`/`:378`),
|
||||
`/specs/library/$entryId` (`:384`), `/discovery` + `/discovery/inventory`
|
||||
(`:395`/`:410`), `/bundles/inspect` + `/bundles/install` (`:416`/`:422`).
|
||||
`/rigs/$rigId` (`:374`), `/rigs/$rigId/nodes/$logicalId` (`:380`),
|
||||
`/import` (`:389`), `/packages`, `/packages/install`, `/packages/$packageId`
|
||||
(`:395`–`:407`), `/bootstrap` (`:413`), `/agents/validate` (`:419`),
|
||||
`/specs/rig` + `/specs/agent` review (`:428`/`:434`),
|
||||
`/specs/library/$entryId` (`:440`), `/discovery` + `/discovery/inventory`
|
||||
(`:451`/`:466`), `/bundles/inspect` + `/bundles/install` (`:472`/`:478`).
|
||||
|
||||
### Redirect stubs (deleted routes)
|
||||
|
||||
`routes.tsx:432`–`:474` mount `<Navigate>` redirects for removed routes —
|
||||
`routes.tsx:488`–`:530` mount `<Navigate>` redirects for removed routes —
|
||||
NOT destination pages: `/context` → `/topology`; `/mission-control` →
|
||||
`/for-you` (SC-18: `/mission-control` deleted, For-You replaces it);
|
||||
`/slices` + `/slices/$name` → `/project`; `/progress` → `/project`;
|
||||
`/steering` → `/project`. The Mission Control *system* still exists at the
|
||||
daemon/PL-005 layer (`../architecture/mission-control.md`); only the old UI
|
||||
*route* was retired in favour of For-You.
|
||||
`/for-you` (For-You replaces it); `/slices` → `/project`; `/slices/$name` →
|
||||
`/project/slice/$sliceId` (with `sliceId` set to the name); `/progress` →
|
||||
`/project`; `/steering` → `/project`. The Mission Control *system* still
|
||||
exists in the daemon (`../architecture/mission-control.md`); only the old
|
||||
UI *route* was retired in favour of For-You.
|
||||
|
||||
## 4. Drawer and viewer system
|
||||
|
||||
`SharedDetailDrawer` is the shared transient detail surface for queue-item
|
||||
viewer, file viewer, subspec preview, and other detail selections. Drawer
|
||||
triggers carry root/path provenance so the viewer fetches/render the correct
|
||||
content; the drawer supports outside-click dismissal. Image proof viewing is
|
||||
handled by `ProofImageViewer` with black-glass styling honouring the shell
|
||||
layout. Skill detail uses Library Explorer navigation + `FileViewer`
|
||||
(`components/drawer-viewers/FileViewer.tsx`) in the center workspace, not a
|
||||
nested three-column browser.
|
||||
viewer, file viewer (`FileViewer`), subspec preview, and other detail
|
||||
selections. Drawer triggers carry root/path provenance so the viewer
|
||||
fetches and renders the correct content; the drawer supports outside-click
|
||||
dismissal.
|
||||
|
||||
Image proof viewing: For-You cards use `ProofImageViewer`
|
||||
(`components/project/ProofImageViewer.tsx`), which stays inside the shell
|
||||
layout. The Project Proof tab and the Review surfaces use the full-viewport
|
||||
`Lightbox` (`components/project/Lightbox.tsx`; mounted in
|
||||
`project/ProofTab.tsx`, `review/EvidenceOpener.tsx`,
|
||||
`review/SliceReviewTab.tsx`).
|
||||
|
||||
Skill detail (`components/specs/SkillDetailPage.tsx`) renders its own
|
||||
two-pane docs browser — a file tree beside a viewer — and draws content
|
||||
with `MarkdownViewer` (`.md`/`.mdx`) or `SyntaxHighlight` (other text). It
|
||||
does not use `FileViewer`, which only the shared drawer renders.
|
||||
|
||||
## 5. Event and activity consumption
|
||||
|
||||
UI event consumption is centralized through shared event hooks so the app
|
||||
avoids duplicate SSE connections for related surfaces. Current consumers: For
|
||||
You feed hydration, topology activity rings + hot-potato movement, the
|
||||
activity feed/system surfaces, and rig event surfaces. Reduced-motion
|
||||
preference is honoured for pulse/packet animation. (Daemon SSE surfaces:
|
||||
`/api/events`, `/api/stream/watch`, `/api/queue/watch` —
|
||||
`architecture-rules-and-event-system.md` §2.4.)
|
||||
UI event consumption is centralized through one shared `/api/events`
|
||||
connection (`lib/topology-events.ts`) so related surfaces avoid duplicate
|
||||
SSE connections. Current consumers: For You feed hydration, topology
|
||||
activity rings + hot-potato movement, the activity feed/system surfaces,
|
||||
and rig event surfaces. `useGlobalEvents` reports whether that connection
|
||||
is up and refetches review, slice and mission data on reconnect and on
|
||||
`proof.*` events. Reduced-motion preference is honoured for pulse/packet
|
||||
animation. (Daemon SSE surfaces: `/api/events`, `/api/stream/watch`,
|
||||
`/api/queue/watch` —
|
||||
[`../architecture/architecture-rules-and-event-system.md`](../architecture/architecture-rules-and-event-system.md)
|
||||
§2, "Emission and delivery".)
|
||||
|
||||
## See also
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@ topics: [observability, coordination]
|
||||
domains: [engineering-advisor, operating-advisor]
|
||||
applies-when: |
|
||||
Need to know how the topology surface is built — the host hybrid graph,
|
||||
the table/terminal views, the activity-ring / hot-potato visual language,
|
||||
terminal-preview popovers, and the topology navigation/overlay contracts.
|
||||
the rig/pod graphs, the table/terminal views, the activity-ring /
|
||||
hot-potato visual language, terminal-preview popovers and launchers, and
|
||||
the topology navigation/overlay contracts.
|
||||
siblings: [shell-and-routing.md, project-and-for-you.md]
|
||||
prerequisite-reads: [../README.md, shell-and-routing.md]
|
||||
last-verified-against-source: 7eaf524c
|
||||
last-updated: 2026-05-16
|
||||
last-verified-against-source: e8f0ab340db773392ec8be75b072d1c0f3068a50
|
||||
last-updated: 2026-10-08
|
||||
---
|
||||
|
||||
# UI Topology — Graph/Table/Terminal, HotPotato, ActivityRing
|
||||
@@ -20,84 +21,117 @@ Topology is a scoped workspace with graph, table, and terminal views at the
|
||||
`/topology` route family (`shell-and-routing.md` §3). It is the operator's
|
||||
live picture of host → rig → pod → seat.
|
||||
|
||||
> Verified against source at HEAD `7eaf524c`; package version **0.3.1**
|
||||
> (slice-00 §1.1). All component names below are re-confirmed against
|
||||
> `packages/ui/src/components/topology/` at HEAD — `ui.md` and `DESIGN.md`
|
||||
> name *exported symbols*; the source file that defines the hybrid nodes is
|
||||
> `HybridTopologyNodes.tsx` (DESIGN.md L344 implementation reference).
|
||||
> Verified against source at `e8f0ab34` (package version 0.6.7). The web UI
|
||||
> is in maintenance mode (`docs/reference/developing.md`). The source file
|
||||
> that defines the hybrid nodes is `HybridTopologyNodes.tsx` (DESIGN.md's
|
||||
> implementation reference).
|
||||
|
||||
## 1. Topology pieces
|
||||
|
||||
> Drift-fix — `ui.md` "Topology" (L108–137) lists `HybridAgentNode` /
|
||||
> `HybridPodGroupNode` as if top-level files. Re-confirmed at HEAD: both are
|
||||
> exports inside `packages/ui/src/components/topology/HybridTopologyNodes.tsx`
|
||||
> (`HybridPodGroupNode` is `memo(...)` at `HybridTopologyNodes.tsx:94`;
|
||||
> `HybridAgentNode` is `memo(HybridAgentNodeInner, ...)` at `:272`). The
|
||||
> component names are accurate; their file location is consolidated.
|
||||
> `HybridAgentNode` and `HybridPodGroupNode` are exports inside
|
||||
> `packages/ui/src/components/topology/HybridTopologyNodes.tsx`, not
|
||||
> top-level files (`HybridPodGroupNode = memo(...)` at
|
||||
> `HybridTopologyNodes.tsx:101`; `HybridAgentNode = memo(HybridAgentNodeInner,
|
||||
> ...)` at `:321`).
|
||||
|
||||
Components in `packages/ui/src/components/topology/` (re-confirmed at HEAD):
|
||||
Components in `packages/ui/src/components/topology/`:
|
||||
|
||||
- `HostMultiRigGraph.tsx` — host-level hybrid React Flow graph (multi-rig
|
||||
single canvas).
|
||||
single canvas). Also mounted on the `/project` workspace Workflow tab.
|
||||
- `HybridTopologyNodes.tsx` — exports `HybridAgentNode` (compact agent cards:
|
||||
runtime badges, context %, token totals, activity state, terminal preview,
|
||||
CMUX actions) and `HybridPodGroupNode` (soft dashed pod frames).
|
||||
per-card CMUX open via `useCmuxLaunch`) and `HybridPodGroupNode` (soft
|
||||
dashed pod frames).
|
||||
- `RigGroupNode.tsx` — soft rig frames with registration marks + aggregate
|
||||
activity.
|
||||
- `ActivityRing.tsx` — active / needs-input / blocked activity ring; card
|
||||
activity classes in `activity-card-visuals.ts`.
|
||||
- `HotPotatoEdge.tsx` — directional queue-movement edge animation.
|
||||
- `TopologyTableView.tsx` — dense table mirror of topology data.
|
||||
- `TopologyTerminalView.tsx` — terminal-oriented topology state.
|
||||
- `TopologyTreeView.tsx` — tree navigation view.
|
||||
- `TopologyViewModeTabs.tsx` — graph/table/terminal/tree mode switch.
|
||||
- `TerminalPreviewPopover.tsx` — black-glass quick terminal preview, portaled
|
||||
above the graph.
|
||||
- `LaunchCmuxButton.tsx` — hover/focus CMUX launch action.
|
||||
- `TopologyTableView.tsx` — dense table mirror of topology data (with
|
||||
`topology-table-shimmer.css`).
|
||||
- `TopologyTerminalView.tsx` — pinned-card terminal grid for host, rig or
|
||||
pod scope; host scope shows 12 cards by default (`SAFE_N`) with a "show
|
||||
all" toggle.
|
||||
- `TopologyTreeView.tsx` — the topology tree, rendered in the Explorer
|
||||
sidebar (`components/Explorer.tsx`), not as a center view mode.
|
||||
- `TopologyViewModeTabs.tsx` — host tabs Graph / Table / Terminal; rig and
|
||||
pod tabs add Overview. Tabs switch in place (React state), not separate
|
||||
routes. (`SEAT_SCOPE_TABS` is defined but unused; the seat scope renders
|
||||
`LiveNodeDetails`.)
|
||||
- `TerminalPreviewPopover.tsx` — smoked-glass terminal preview, portaled
|
||||
above the graph. On the graph and table it is `progressive`: a static
|
||||
mirror first, click to go live, under a page-wide live-terminal cap.
|
||||
- `TerminalLauncher.tsx` — the rig-scope tab-bar launcher: choose a
|
||||
provider (herdr or cmux) and a view (this rig, a pod, a mission's or
|
||||
slice's agents, a saved view), then open it via
|
||||
`POST /api/terminal/open`. Shown only when the selected host is local.
|
||||
- `LaunchCmuxButton.tsx` — the older rig-scope "Launch in CMUX" button.
|
||||
`TerminalLauncher` replaced it in the tab bar, and no production
|
||||
component imports it now (tests still do).
|
||||
- `ScopePages.tsx` — host/rig/pod/seat scope page wrappers.
|
||||
- `topology-overlay-context.tsx` — `TopologyOverlayProvider` (expanded-rig
|
||||
state).
|
||||
- `topology-overlay-context.tsx` — `TopologyOverlayProvider`: expanded-rig
|
||||
state, the rig named in the URL auto-expanded, and the Explorer mode
|
||||
(`overlay` on the graph tab, `opaque` on the others).
|
||||
|
||||
> Note (vs `DESIGN.md` "Topology" L205–216): DESIGN.md lists the same
|
||||
> exported primitives (`HostMultiRigGraph`, `HybridAgentNode`,
|
||||
> `HybridPodGroupNode`, `RigGroupNode`, `ActivityRing`, `HotPotatoEdge`,
|
||||
> `TerminalPreviewPopover`, `TopologyTableView`, `TopologyTerminalView`) —
|
||||
> reconciled against source: all present at HEAD. `TopologyTreeView` /
|
||||
> `TopologyViewModeTabs` / `LaunchCmuxButton` exist in source but are not in
|
||||
> DESIGN.md's primitive list (DESIGN.md is the brand primitive list, not an
|
||||
> exhaustive component inventory; not a drift, a scope difference). DESIGN.md
|
||||
> stays byte-identical (Q1).
|
||||
The rig and pod graphs are not the hybrid graph: rig and pod scopes render
|
||||
`components/RigGraph.tsx` (`RigNode` cards, `graph-layout.ts` layout),
|
||||
sharing `HotPotatoEdge` and `useTopologyActivity`. Rig scope's Overview tab
|
||||
renders `RigOverviewTab` (`RigSpecDisplay`); pod scope's is a placeholder.
|
||||
On narrow viewports the graph tab falls back to the table.
|
||||
|
||||
> Source: `topology/TopologyViewModeTabs.tsx` (`HOST_SCOPE_TABS`,
|
||||
> `RIG_POD_SCOPE_TABS`, `SEAT_SCOPE_TABS`); `topology/ScopePages.tsx`
|
||||
> (`TerminalLauncher` `:270`, `RigGraph` `:322`/`:437`, `RigOverviewTab`
|
||||
> `:342`, `useOverlayForActiveTab`); `topology/TerminalPreviewPopover.tsx`
|
||||
> (`progressive`); `topology/TopologyTerminalView.tsx` (`SAFE_N` `:35`).
|
||||
|
||||
> Note (vs `DESIGN.md` "Topology"): DESIGN.md lists the exported brand
|
||||
> primitives (`HostMultiRigGraph`, `HybridAgentNode`, `HybridPodGroupNode`,
|
||||
> `RigGroupNode`, `ActivityRing`, `HotPotatoEdge`, `TerminalPreviewPopover`,
|
||||
> `TopologyTableView`, `TopologyTerminalView`); all are present in source.
|
||||
> `TopologyTreeView`, `TopologyViewModeTabs`, `TerminalLauncher` and
|
||||
> `LaunchCmuxButton` exist in source but are not in DESIGN.md's primitive
|
||||
> list (a scope difference, not drift).
|
||||
|
||||
## 2. Topology contracts
|
||||
|
||||
From `architecture.md` §2 "Current topology UI" + `ui.md` "Important
|
||||
topology contracts", re-confirmed against source behaviour at HEAD:
|
||||
Re-confirmed against source:
|
||||
|
||||
- Graph / table / tree navigation all resolve to seat detail URLs
|
||||
(`/topology/seat/$rigId/$logicalId`).
|
||||
- The host-level graph uses cross-rig node-ID prefixing for multi-rig single
|
||||
canvas.
|
||||
- Expanded-rig state is owned by `TopologyOverlayProvider`
|
||||
(`topology-overlay-context.tsx`); graph data is lazy-fetched for expanded
|
||||
rigs only.
|
||||
- The host-level graph prefixes node IDs per rig for the multi-rig single
|
||||
canvas (`lib/hybrid-layout.ts`: `PREFIX_DELIMITER = "::"`,
|
||||
`prefixedHybridNodeId`).
|
||||
- Expanded-rig state is owned by `TopologyOverlayProvider`. Rigs default to
|
||||
**expanded** (`DEFAULT_RIG_EXPANDED = true` in `HostMultiRigGraph.tsx`),
|
||||
with "Expand all" / "Collapse all" controls; graph data is fetched for
|
||||
expanded rigs only.
|
||||
- Compact agent cards show context percentage, token totals, and activity
|
||||
card tint; `ActivityRing` + activity classes surface active / needs-input /
|
||||
blocked.
|
||||
- `HotPotatoEdge` animates directional queue movement at graph zoom levels;
|
||||
reduced-motion preference removes pulse/travel animation and keeps static
|
||||
state signals.
|
||||
- `HotPotatoEdge` animates directional queue movement with non-scaling
|
||||
strokes; reduced-motion preference removes pulse/travel animation and
|
||||
keeps static state signals.
|
||||
- Terminal-preview actions are hover/focus-visible; the
|
||||
`TerminalPreviewPopover` must escape React Flow stacking contexts (portaled
|
||||
above the canvas) and stay within the viewport.
|
||||
`TerminalPreviewPopover` escapes React Flow stacking contexts (portaled
|
||||
to `document.body`) and stays within the viewport.
|
||||
- With a non-local host selected, the host graph fetch carries the host
|
||||
parameter; terminal-preview and CMUX buttons are hidden on cards and in
|
||||
the table; and the rig page shows a read-only marker instead of the
|
||||
launcher.
|
||||
|
||||
## 3. Layout helpers
|
||||
## 3. Layout and activity helpers
|
||||
|
||||
Topology layout is computed by `src/lib/` helpers (re-confirmed at HEAD):
|
||||
`graph-layout.ts`, `hybrid-layout.ts`, `multi-rig-layout.ts`,
|
||||
`topology-activity.ts`, `activity-visuals.ts`. Runtime/tool identity on the
|
||||
cards comes from the central `runtime-brand.ts` / `tool-brand.ts` +
|
||||
`RuntimeMark.tsx` (do not duplicate brand logic — DESIGN.md "Do not";
|
||||
restated here as the topology-card brand-source contract).
|
||||
In `src/lib/`: `hybrid-layout.ts` lays out the host graph
|
||||
(`HostMultiRigGraph`); `graph-layout.ts` (`applyTreeLayout`) lays out the
|
||||
rig/pod graph (`RigGraph`); `multi-rig-layout.ts` has no production
|
||||
importer (tests only). `topology-activity.ts` computes ring states and
|
||||
hot-potato edges, and `activity-visuals.ts` the rollups and activity dots.
|
||||
Runtime/tool identity on the cards comes from the central
|
||||
`lib/runtime-brand.ts` / `lib/tool-brand.ts` and
|
||||
`components/graphics/RuntimeMark.tsx` (runtimes: Claude Code, Codex, Pi,
|
||||
OMP, terminal, unknown). Do not duplicate brand logic (DESIGN.md "Do
|
||||
not").
|
||||
|
||||
## See also
|
||||
|
||||
@@ -106,4 +140,5 @@ restated here as the topology-card brand-source contract).
|
||||
- `../architecture/coordination-primitive.md` — the queue movement the
|
||||
hot-potato edge visualizes.
|
||||
- Source root: `packages/ui/src/components/topology/`,
|
||||
`packages/ui/src/lib/{graph,hybrid,multi-rig}-layout.ts`.
|
||||
`packages/ui/src/components/RigGraph.tsx`,
|
||||
`packages/ui/src/lib/{hybrid,graph}-layout.ts`.
|
||||
+146
-65
@@ -1,8 +1,8 @@
|
||||
# AgentSpec Reference
|
||||
|
||||
Version: 1.0
|
||||
Last validated against code: 2026-04-11
|
||||
Source of truth: `packages/daemon/src/domain/agent-manifest.ts`, `packages/daemon/src/domain/types.ts`
|
||||
Last validated against code: 2026-10-07, at the 0.6.6 cut `2620dea8` (whole document at `fcaf1f8e`; changes to its sources and the shipped agent specs since then checked)
|
||||
Source of truth: `packages/daemon/src/domain/agent-manifest.ts`, `packages/daemon/src/domain/types.ts`, `packages/daemon/src/domain/agent-resolver.ts`, `packages/daemon/src/domain/profile-resolver.ts`, `packages/daemon/src/domain/skill-catalog.ts`
|
||||
|
||||
This is the canonical reference for the AgentSpec YAML format (`agent.yaml`). Every field, validation rule, and default documented here was traced from the actual parser and validator code.
|
||||
|
||||
@@ -20,7 +20,7 @@ profiles:
|
||||
skills: []
|
||||
guidance: []
|
||||
subagents: []
|
||||
hooks: []
|
||||
plugins: []
|
||||
runtime_resources: []
|
||||
|
||||
resources: {}
|
||||
@@ -35,7 +35,7 @@ startup:
|
||||
```yaml
|
||||
name: implementer
|
||||
version: "1.0"
|
||||
description: Implementation agent — writes code following TDD discipline
|
||||
description: Implementation agent — implements and verifies coherent changes
|
||||
|
||||
defaults:
|
||||
runtime: claude-code
|
||||
@@ -46,11 +46,11 @@ imports:
|
||||
profiles:
|
||||
default:
|
||||
uses:
|
||||
skills: [openrig-user, development-team, test-driven-development, systematic-debugging]
|
||||
skills: [development-team, systematic-debugging, verification-before-completion]
|
||||
guidance: []
|
||||
subagents: []
|
||||
hooks: []
|
||||
runtime_resources: []
|
||||
plugins: [shared:openrig-core]
|
||||
runtime_resources: [shared:claude-default-settings, shared:claude-default-mcp, shared:codex-default-config, shared:claude-activity-hooks]
|
||||
|
||||
resources:
|
||||
guidance:
|
||||
@@ -75,6 +75,7 @@ description: Vault specialist agent — manages HashiCorp Vault for this managed
|
||||
defaults:
|
||||
runtime: claude-code
|
||||
model: claude-opus-4-6
|
||||
effort: high
|
||||
lifecycle:
|
||||
execution_mode: interactive_resident
|
||||
compaction_strategy: default-compaction
|
||||
@@ -91,11 +92,11 @@ profiles:
|
||||
preferences:
|
||||
runtime: claude-code
|
||||
uses:
|
||||
skills: [openrig-user, systematic-debugging, vault-user]
|
||||
skills: [systematic-debugging, vault-user]
|
||||
guidance: []
|
||||
subagents: []
|
||||
hooks: []
|
||||
runtime_resources: []
|
||||
plugins: [shared:openrig-core, vault-tools]
|
||||
runtime_resources: [claude-settings]
|
||||
startup:
|
||||
files:
|
||||
- path: guidance/profile-specific.md
|
||||
@@ -111,10 +112,11 @@ resources:
|
||||
guidance:
|
||||
- id: role
|
||||
path: guidance/role.md
|
||||
hooks:
|
||||
- id: pre-commit
|
||||
path: hooks/pre-commit.sh
|
||||
runtimes: [claude-code]
|
||||
plugins:
|
||||
- id: vault-tools
|
||||
source:
|
||||
kind: local
|
||||
path: plugins/vault-tools
|
||||
runtime_resources:
|
||||
- id: claude-settings
|
||||
path: runtime/claude-settings.fragment.json
|
||||
@@ -147,12 +149,12 @@ startup:
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `name` | string | yes | — | Agent name. Used in spec library identification and validation messages. |
|
||||
| `version` | string | yes | — | Spec version. Informational — not used for compatibility gating. |
|
||||
| `version` | string | yes | — | Spec version. Quote it (`"1.0"`): an unquoted number fails as a non-string. An importing spec that pins `imports[].version` must match it exactly. |
|
||||
| `description` | string | no | — | Human-readable description. Shown in spec library and review surfaces. |
|
||||
| `defaults` | Defaults | no | — | Default runtime, model, and lifecycle settings. Applied when not overridden by the rig spec or profile. |
|
||||
| `imports` | Import[] | no | `[]` | Other AgentSpecs to import. Resources from imported specs become available for profile `uses` references. |
|
||||
| `profiles` | map<string, Profile> | no | `{}` | Named profiles. Each profile selects resources and can override startup/lifecycle. The rig spec member's `profile` field selects which profile to use. |
|
||||
| `resources` | Resources | no | all empty | Declared resources (skills, guidance, subagents, hooks, runtime resources). These are the available pool that profiles select from via `uses`. |
|
||||
| `resources` | Resources | no | all empty | Declared resources (skills, guidance, subagents, plugins, runtime resources). These are the available pool that profiles select from via `uses`. |
|
||||
| `startup` | StartupBlock | no | `{ files: [], actions: [] }` | Agent-level startup files and actions. Applied to all profiles via the startup layering model. |
|
||||
|
||||
---
|
||||
@@ -163,6 +165,7 @@ startup:
|
||||
defaults:
|
||||
runtime: claude-code
|
||||
model: claude-opus-4-6
|
||||
effort: high
|
||||
lifecycle:
|
||||
execution_mode: interactive_resident
|
||||
compaction_strategy: default-compaction
|
||||
@@ -171,8 +174,9 @@ defaults:
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `runtime` | string | no | — | Default runtime for this agent. Can be overridden by the rig spec member's `runtime` field. |
|
||||
| `model` | string | no | — | Default model. Can be overridden by the rig spec member's `model` field. |
|
||||
| `runtime` | string | no | — | Default runtime for this agent. Precedence: the rig spec member's `runtime`, then the profile's `preferences.runtime`, then this, then `claude-code`. |
|
||||
| `model` | string | no | — | Default model, with the same precedence. |
|
||||
| `effort` | string | no | — | Default reasoning effort, with the same precedence. Claude gets `--effort`, Codex `-c model_reasoning_effort=…`. A blank or non-string value is ignored with an advisory. |
|
||||
| `lifecycle` | Lifecycle | no | see below | Lifecycle behavior defaults. |
|
||||
|
||||
### Lifecycle Defaults
|
||||
@@ -182,6 +186,7 @@ defaults:
|
||||
| `execution_mode` | string | `interactive_resident` | `interactive_resident` (only value in v1; `wake_on_demand` is explicitly rejected) |
|
||||
| `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` |
|
||||
| `mechanic` | string | — | A canonical `seat@rig` session address; needed by `apprentice-handover` |
|
||||
|
||||
---
|
||||
|
||||
@@ -197,15 +202,18 @@ imports:
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `ref` | string | yes | Reference to another AgentSpec directory. Must start with `local:` (relative) or `path:` (absolute). The referenced directory must contain an `agent.yaml`. |
|
||||
| `version` | string | no | Optional version constraint. Must be an exact version — no ranges (`~`, `^`, `>=`, etc.). |
|
||||
| `version` | string | no | Optional version constraint. Must be an exact version — no ranges (`~`, `^`, `>=`, etc.). When set, the imported spec's `version` must equal it, or resolution fails with `version_mismatch`. |
|
||||
|
||||
### Import Resolution
|
||||
|
||||
- `local:` paths resolve relative to the importing spec's directory
|
||||
- `path:` paths are absolute filesystem paths
|
||||
- Imported resources become available for `uses` references in profiles
|
||||
- When referencing imported resources in `uses`, use qualified `namespace:id` format (e.g., `shared:openrig-user`)
|
||||
- Unqualified references (just `id`) resolve against the spec's own local resources first
|
||||
- A qualified reference `namespace:id` (e.g., `shared:openrig-core`) names the imported spec by its `name` field
|
||||
- An unqualified reference (just `id`) resolves against the spec's own resources first, then against the one import that
|
||||
declares it. If two imports declare it, the reference is ambiguous and fails: qualify it
|
||||
- An imported spec may not have imports of its own, and its `name` may not contain `:`. Two imports that resolve to the
|
||||
same spec name are refused, and import cycles are detected
|
||||
|
||||
### The Shared Import Pattern
|
||||
|
||||
@@ -216,7 +224,11 @@ imports:
|
||||
- ref: local:../../shared
|
||||
```
|
||||
|
||||
This gives access to the full pool of shared skills (openrig-user, systematic-debugging, development-team, etc.) and agents select the ones they need via profile `uses`.
|
||||
This gives access to the shared pool: skills such as `systematic-debugging`, `verification-before-completion`,
|
||||
`development-team` and `test-driven-development`; the `openrig-core` plugin, which carries `openrig-user` and OpenRig's
|
||||
other skills; and runtime resources such as `claude-default-settings`, `claude-default-mcp`, `codex-default-config` and
|
||||
`claude-activity-hooks`. Agents select the ones they need via profile `uses`, for example
|
||||
`plugins: [shared:openrig-core]`.
|
||||
|
||||
---
|
||||
|
||||
@@ -230,10 +242,10 @@ profiles:
|
||||
runtime: claude-code
|
||||
model: claude-opus-4-6
|
||||
uses:
|
||||
skills: [openrig-user, systematic-debugging, vault-user]
|
||||
skills: [systematic-debugging, vault-user]
|
||||
guidance: [role]
|
||||
subagents: []
|
||||
hooks: []
|
||||
plugins: [shared:openrig-core]
|
||||
runtime_resources: []
|
||||
startup:
|
||||
files: []
|
||||
@@ -244,13 +256,14 @@ profiles:
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `summary` | string | no | — | Profile description. |
|
||||
| `preferences` | object | no | — | Runtime/model preferences for this profile. |
|
||||
| `summary` | string | no | — | Profile description. Spec review surfaces read a profile `description` instead. |
|
||||
| `preferences` | object | no | — | Runtime/model/effort preferences for this profile. |
|
||||
| `preferences.runtime` | string | no | — | Preferred runtime. |
|
||||
| `preferences.model` | string | no | — | Preferred model. |
|
||||
| `preferences.effort` | string | no | — | Preferred reasoning effort. |
|
||||
| `uses` | Uses | no | all empty | Selects which declared resources are active for this profile. |
|
||||
| `startup` | StartupBlock | no | — | Profile-level startup files and actions. Merged with agent-level startup via layering. |
|
||||
| `lifecycle` | Lifecycle | no | — | Profile-level lifecycle overrides. |
|
||||
| `lifecycle` | Lifecycle | no | — | Profile-level lifecycle overrides. `restore_policy` can only narrow (`resume_if_possible` → `relaunch_fresh` → `checkpoint_only`); `compaction_strategy` and `mechanic` take the most specific value (defaults, then profile, then member). |
|
||||
|
||||
### Uses
|
||||
|
||||
@@ -258,18 +271,28 @@ The `uses` block selects which resources from the `resources` pool (including im
|
||||
|
||||
```yaml
|
||||
uses:
|
||||
skills: [openrig-user, systematic-debugging, vault-user]
|
||||
skills: [systematic-debugging, vault-user]
|
||||
guidance: [role]
|
||||
subagents: []
|
||||
hooks: [pre-commit]
|
||||
plugins: [shared:openrig-core]
|
||||
runtime_resources: [claude-settings]
|
||||
```
|
||||
|
||||
Each array contains resource IDs. These can be:
|
||||
- **Unqualified** (`vault-user`) — resolves against the spec's own `resources` first, then imported specs
|
||||
- **Qualified** (`shared:openrig-user`) — resolves against a specific imported spec's resources
|
||||
- **Qualified** (`shared:systematic-debugging`) — resolves against a specific imported spec's resources
|
||||
|
||||
The `uses` categories are: `skills`, `guidance`, `subagents`, `hooks`, `runtime_resources`.
|
||||
The `uses` categories are: `skills`, `guidance`, `subagents`, `plugins`, `runtime_resources`. A `uses.hooks` key, even an
|
||||
empty list, is refused: hooks ship inside plugins.
|
||||
|
||||
**Skills resolve more widely than other resources.** For Claude Code and Codex seats, a skill ID that no declared
|
||||
resource provides is looked up at launch in the seat's runtime skill folder (`<cwd>/.claude/skills` or
|
||||
`<cwd>/.agents/skills`), then the `skills/` folder beside the RigSpec, then the home skill folder, then the managed
|
||||
skills root. Pi looks only in the `skills/` folder beside the RigSpec. OMP does no filesystem discovery, so its
|
||||
`uses.skills` references need declared resources. A discovered skill's ID is its `SKILL.md` frontmatter `name`, and a declared resource wins over a discovered skill with the
|
||||
same ID. When a profile selects a skill from inside the spec's own folder and the managed catalog holds a different
|
||||
copy, the spec's copy is used and launch warns `skill_bundle_precedence`. Any other differing copy fails launch with
|
||||
`skill_identity_conflict`.
|
||||
|
||||
---
|
||||
|
||||
@@ -286,10 +309,12 @@ resources:
|
||||
subagents:
|
||||
- id: helper
|
||||
path: subagents/helper
|
||||
hooks:
|
||||
- id: pre-commit
|
||||
path: hooks/pre-commit.sh
|
||||
runtimes: [claude-code]
|
||||
plugins:
|
||||
- id: vault-tools
|
||||
source:
|
||||
kind: local
|
||||
path: plugins/vault-tools
|
||||
plugin_type: auto
|
||||
runtime_resources:
|
||||
- id: claude-settings
|
||||
path: runtime/claude-settings.fragment.json
|
||||
@@ -297,26 +322,65 @@ resources:
|
||||
type: claude_settings_fragment
|
||||
```
|
||||
|
||||
Resources are the available pool. They are NOT automatically delivered to agents — profiles select them via `uses`. The only resources delivered are those that the active profile's `uses` block references.
|
||||
Resources are the available pool. They are NOT automatically delivered to agents — profiles select them via `uses`.
|
||||
The only declared resources delivered are those that the active profile's `uses` block references. Two sources add
|
||||
skills outside `uses`: the managed skill catalog's system selection and a project's `install.skills`.
|
||||
|
||||
`resources.hooks` is refused at validation: hooks now ship inside plugins, declared under `resources.plugins`.
|
||||
|
||||
### Plugin paths
|
||||
|
||||
A local plugin's `source.path` supports these forms:
|
||||
|
||||
| Form | Resolution |
|
||||
|------|------------|
|
||||
| `/absolute/plugin` | The exact absolute path. |
|
||||
| `~/plugins/example` or `~` | The operating-system user home. |
|
||||
| `openrig-home:plugins/openrig-core` | Relative to the daemon's configured OpenRig home, including a non-default `OPENRIG_HOME`. |
|
||||
| `plugins/example` or `./plugins/example` | Relative to the agent spec directory. |
|
||||
|
||||
For a plugin seeded by OpenRig, use the home-relative form so the spec selects
|
||||
that daemon's copy:
|
||||
|
||||
```yaml
|
||||
resources:
|
||||
plugins:
|
||||
- id: openrig-core
|
||||
source:
|
||||
kind: local
|
||||
path: "openrig-home:plugins/openrig-core"
|
||||
profiles:
|
||||
default:
|
||||
uses:
|
||||
plugins: [openrig-core]
|
||||
```
|
||||
|
||||
`openrig-home:` is resolved by the daemon from its configured state root. It is
|
||||
not shell interpolation and does not consult a seat's shell startup files. Its
|
||||
suffix must be relative. Existing absolute, tilde and spec-relative paths keep
|
||||
their meaning; `~user` is still a literal relative segment, not a user lookup.
|
||||
This form needs OpenRig 0.6.5 or later; keep bundles using it pinned to such a
|
||||
version.
|
||||
|
||||
### Resource Categories
|
||||
|
||||
| Category | Fields | Description |
|
||||
|----------|--------|-------------|
|
||||
| `skills` | `id`, `path` | Skill directories containing a SKILL.md. Delivered via `skill_install`. |
|
||||
| `guidance` | `id`, `path`, `target`*, `merge`* | Guidance files. Delivered via `guidance_merge` into CLAUDE.md/AGENTS.md. |
|
||||
| `subagents` | `id`, `path` | Subagent definitions. |
|
||||
| `hooks` | `id`, `path`, `runtimes`* | Hook scripts. Optional `runtimes` array restricts to specific runtimes. |
|
||||
| `runtime_resources` | `id`, `path`, `runtime`, `type` | Runtime-specific resources. `runtime` and `type` are required. |
|
||||
| `skills` | `id`, `path` | Skill directories containing a SKILL.md. Projected by the runtime adapter: Claude to `<cwd>/.claude/skills/<id>`, Codex to `<cwd>/.agents/skills/<id>`, Pi and OMP to the seat's own agent directory under OpenRig's state. |
|
||||
| `guidance` | `id`, `path`, `target`*, `merge`* | Guidance files, merged as a managed block (see "Guidance Resources"). |
|
||||
| `subagents` | `id`, `path` | Subagent definitions. Claude copies them to `<cwd>/.claude/agents/`, Codex to `<cwd>/.agents/`; Pi and OMP don't project them. |
|
||||
| `plugins` | `id`, `source`, `plugin_type`* | Local plugin directories; selected with `uses.plugins`. `source.kind` must be `local`. `plugin_type` is `claude`, `codex` or `auto` (default): Claude projects a plugin to `<cwd>/.claude/plugins/<id>` when it is `claude`, or `auto` with a `.claude-plugin/plugin.json`; Codex projects to `<cwd>/.codex/plugins/<id>` when it is `codex`, or `auto` with a `.codex-plugin/plugin.json`. Pi and OMP don't project plugins. Neither Claude Code nor Codex reads skills from that plugin folder, so when a seat is instantiated a selected plugin's skills are also projected into the seat's skill folder (`.claude/skills/<name>` or `.agents/skills/<name>`) under their plain names. A folder already there with the same name, ignoring letter case, that OpenRig doesn't own is left alone; unless it already holds the same bytes, launch warns `plugin_skill_kept`. A skill the seat already selects by that name (from its profile, the System World or the project) keeps that source, an edited OpenRig-owned copy is kept with `plugin_skill_kept`, a retained plugin copy whose remembered source can no longer be projected is also kept with `plugin_skill_kept` (a seat selecting that plugin refreshes it at its next launch), and if the plugin skills can't be projected the seat launches without them and warns `plugin_skills_not_projected`. Hooks ship inside plugins. |
|
||||
| `runtime_resources` | `id`, `path`, `runtime`, `type` | Runtime-specific resources. `runtime` and `type` are required. An entry whose `runtime` isn't the seat's runtime is skipped. Pi and OMP project none. |
|
||||
|
||||
*Fields marked with `*` are optional.
|
||||
|
||||
Recognized runtime resource types:
|
||||
- `claude_settings_fragment` — merge a JSON object into `<cwd>/.claude/settings.local.json`.
|
||||
- `claude_mcp_fragment` — merge a JSON object into `<cwd>/.mcp.json`.
|
||||
- `codex_config_fragment` — upsert a TOML fragment into `~/.codex/config.toml` inside an OpenRig-managed block.
|
||||
- `claude_activity_hooks` — reconcile OpenRig's activity hooks into `<cwd>/.claude/settings.local.json`.
|
||||
- `codex_config_fragment` — upsert a TOML fragment into `$CODEX_HOME/config.toml` (default `~/.codex/config.toml`) inside an OpenRig-managed block.
|
||||
|
||||
Unknown runtime resource types are still copied to the runtime extension directory for agent-visible context.
|
||||
Unknown runtime resource types are still copied to the Claude or Codex runtime extension directory for agent-visible context.
|
||||
|
||||
#### Writing a `codex_config_fragment`
|
||||
|
||||
@@ -353,7 +417,8 @@ 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)
|
||||
- All resource paths must be safe relative paths (no `..` traversal, no absolute paths), except a plugin's
|
||||
`source.path`, which takes the forms under "Plugin paths"
|
||||
- Paths resolve relative to the agent spec's directory
|
||||
- Resource IDs must be unique within their category
|
||||
- Resource IDs are the identifiers used in `uses` references
|
||||
@@ -364,16 +429,15 @@ overwritten, and the rest of the fragment still applies.
|
||||
guidance:
|
||||
- id: role
|
||||
path: guidance/role.md
|
||||
target: CLAUDE.md # optional — where to merge
|
||||
merge: managed_block # optional — how to merge (default: managed_block)
|
||||
merge: managed_block # optional — the only strategy applied
|
||||
```
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `id` | string | yes | — | Resource identifier. |
|
||||
| `id` | string | yes | — | Resource identifier. A `rig-role` guidance resource is never merged; deliver a role with a `send_text` startup file. |
|
||||
| `path` | string | yes | — | Relative path to the guidance file. |
|
||||
| `target` | string | no | — | Target file for merging (e.g., `CLAUDE.md`). |
|
||||
| `merge` | string | no | `managed_block` | Merge strategy. One of: `managed_block`, `append`. |
|
||||
| `target` | string | no | — | Parsed but not used: the runtime decides the file. Claude merges into the rig's `managed_blocks` file (`CLAUDE.md` by default, or `CLAUDE.local.md`); Codex, Pi and OMP merge into `<cwd>/AGENTS.md`. |
|
||||
| `merge` | string | no | `managed_block` | Only `managed_block` is applied. Any other value, such as `append`, merges nothing. |
|
||||
|
||||
---
|
||||
|
||||
@@ -393,7 +457,7 @@ restore behavior, and the distinction between readiness and verified proof.
|
||||
|
||||
| Hint | When Delivered | Mechanism |
|
||||
|------|---------------|-----------|
|
||||
| `auto` | Before harness boot | System chooses based on file type |
|
||||
| `auto` | Depends on the file | A path ending in `SKILL.md`, or content beginning with `# SKILL`, is `skill_install`; otherwise a `.md` file is `guidance_merge`, and any other file is `send_text` |
|
||||
| `guidance_merge` | Before harness boot | Merged into CLAUDE.md/AGENTS.md as managed block |
|
||||
| `skill_install` | Before harness boot | Installed to runtime skill directory |
|
||||
| `send_text` | After harness is ready | Sent as text to agent terminal via tmux |
|
||||
@@ -407,17 +471,26 @@ restore behavior, and the distinction between readiness and verified proof.
|
||||
3. Import `ref` must start with `local:` (relative) or `path:` (absolute).
|
||||
4. Import `version` must be an exact version (no ranges).
|
||||
5. `profiles` must be a map (object), not an array.
|
||||
6. Profile `uses` references must resolve to declared resources (local or imported).
|
||||
6. Profile `uses` references other than skills must resolve to declared resources (local or imported). Skill
|
||||
references are checked at launch, not at validation (see "Uses").
|
||||
7. Unqualified `uses` references that don't resolve to local resources require imports to be present.
|
||||
8. Qualified `uses` references must be in `namespace:id` format.
|
||||
9. All resource paths must be safe relative paths.
|
||||
10. Resource IDs must be unique within their category.
|
||||
11. `runtime_resources` entries require `runtime` field.
|
||||
9. All resource paths must be safe relative paths (a plugin's `source.path` excepted).
|
||||
10. Resource IDs must be unique within their category, and each `resources.<category>` must be an array.
|
||||
11. `runtime_resources` entries require `runtime` and `type` fields.
|
||||
12a. `resources.hooks` and `profiles.<name>.uses.hooks` are refused: "removed in plugin-primitive (Phase 3a)". Declare
|
||||
a plugin instead.
|
||||
12b. A plugin needs a unique non-empty `id`, a `source` object with `kind: local` and a non-empty `path`, and a
|
||||
`plugin_type` of `claude`, `codex` or `auto` when given.
|
||||
12c. Lifecycle `mechanic` must be a canonical `seat@rig` session address.
|
||||
12. Lifecycle `execution_mode` must be `interactive_resident`.
|
||||
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.
|
||||
|
||||
Unknown keys are ignored, not refused. `rig agent validate <path> [--json]` runs these syntax checks through the running
|
||||
daemon; it doesn't resolve imports, find skills or check that files exist, so a missing skill shows up only at launch.
|
||||
|
||||
---
|
||||
|
||||
## File System Layout
|
||||
@@ -434,8 +507,8 @@ my-agent/
|
||||
skills/
|
||||
my-skill/
|
||||
SKILL.md # skill content
|
||||
hooks/
|
||||
pre-commit.sh # hook script
|
||||
plugins/
|
||||
my-plugin/ # plugin (skills, hooks) with .claude-plugin/ and/or .codex-plugin/
|
||||
runtime/
|
||||
claude-settings.fragment.json # runtime-specific resource
|
||||
```
|
||||
@@ -446,12 +519,20 @@ The only required file is `agent.yaml`. Everything else is referenced by paths i
|
||||
|
||||
## Shipped Examples
|
||||
|
||||
| Agent | Location | Imports | Profile Skills | Purpose |
|
||||
|-------|----------|---------|---------------|---------|
|
||||
| `shared` | `specs/agents/shared/` | none | — (resource pool only) | Shared skill pool for all built-in agents |
|
||||
| `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 |
|
||||
| `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 |
|
||||
Every agent below except `shared` imports `shared` and selects `plugins: [shared:openrig-core]`, which carries
|
||||
`openrig-user`. Paths are under `packages/daemon/specs/agents/`; the kernel's own agents are under
|
||||
`packages/daemon/specs/rigs/launch/kernel/agents/`.
|
||||
|
||||
| Agent | Location | Default runtime | Profile skills | Purpose |
|
||||
|-------|----------|-----------------|----------------|---------|
|
||||
| `shared` | `shared/` | — | — (resource pool only) | Shared skills, the `openrig-core` plugin and runtime resources |
|
||||
| `implementer` | `development/implementer/` | claude-code | development-team, systematic-debugging, verification-before-completion | Implementation agent |
|
||||
| `qa` | `development/qa/` | codex | development-team, systematic-debugging, verification-before-completion, agent-browser, dogfood | Quality assurance agent |
|
||||
| `orchestrator` | `orchestration/orchestrator/` | claude-code | orchestration-team, systematic-debugging, verification-before-completion | Rig orchestration lead |
|
||||
| `independent-reviewer` | `review/independent-reviewer/` | claude-code | review-team, systematic-debugging, verification-before-completion | Independent code reviewer |
|
||||
| `vault-specialist` | `apps/vault-specialist/` | claude-code | systematic-debugging, verification-before-completion, vault-user | Vault domain specialist |
|
||||
| `product-designer` | `design/product-designer/` | claude-code | development-team, frontend-design, verification-before-completion | Product designer |
|
||||
| `pm` | `product-management/pm/` | claude-code | office-hours, context-builder, requirements-writer, ui-mockup, plan-review, exec-summary, backlog-capture; `advisor` profile: orchestration-team | Product manager; its `advisor` profile is the factory's advisor |
|
||||
| `analyst`, `synthesizer` | `research/analyst/`, `research/synthesizer/` | claude-code, codex | — | Research team |
|
||||
| `conveyor-lead`, `-planner`, `-builder`, `-reviewer` | `conveyor/` | claude-code, codex, claude-code, codex | per role | Kept for the `factory-rsi` world bundle (its planner); no built-in team uses them |
|
||||
| `factory-rsi-release-manager`, `factory-rsi-dogfood` | `factory-rsi/` | claude-code | per role | Used by the `factory-rsi` world bundle (not a built-in team) |
|
||||
@@ -1,13 +1,43 @@
|
||||
# Agent Startup Guide
|
||||
|
||||
Version: 0.2.0
|
||||
Last validated: 2026-04-11
|
||||
Applies to: OpenRig 0.1.x
|
||||
Last validated: 2026-10-05, against main `9b88b118`
|
||||
Applies to: OpenRig 0.6.8
|
||||
|
||||
This guide teaches you how to think about what goes into an agent's startup experience — what files to write, where to put them, and how the layering model delivers them. It is an authoring guide, not a schema reference. For field-level details, see `rig-spec.md` and `agent-spec.md`.
|
||||
|
||||
---
|
||||
|
||||
## Continue after a native consent prompt
|
||||
|
||||
If a fresh Claude seat is waiting at its bypass-permissions warning, review and
|
||||
answer that warning in the seat's native pane. Unless the rig was launched with
|
||||
`--non-interruptive` (see [non-interruptive mode](non-interruptive-mode.md)),
|
||||
OpenRig does not accept it for you, and it never sends configured startup context
|
||||
into the active dialog. The seat reports `attention_required` and retains that
|
||||
context for the same occupant.
|
||||
|
||||
After accepting, run `rig seat continue <seat>` (or press `c` on the seat in the
|
||||
TUI, shown while its context is pending). This delivers the pending context
|
||||
without relaunching Claude, and its first prompt reminds the seat that this is a
|
||||
fresh conversation and how to find its work. It does not replay a delivery that
|
||||
already started. A timeout, or a refusal because nothing is pending, reports an
|
||||
unknown outcome; inspect `rig seat status <seat>` before taking another action.
|
||||
It is also refused while the prompt is still showing (`attention_required`), or
|
||||
when the seat's binding or runtime has changed since launch.
|
||||
Runtime readiness, startup delivery, and orientation proof remain separate;
|
||||
a missing proof does not block this continuation.
|
||||
|
||||
Other fresh-launch prerequisites, such as login or workspace trust, use the same
|
||||
continuation: resolve the prerequisite in the native pane, then run the displayed
|
||||
command. `rig ps --nodes` shows it under "Startup details (<seat>)", and
|
||||
`rig up` and `rig bundle install` print it as "Startup attention (<seat>)":
|
||||
"After resolving it in <session>, run: rig seat continue <session>".
|
||||
|
||||
The attention codes that stop startup this way are `trust_gate`,
|
||||
`hook_trust_gate`, `update_gate`, `login_required`, `mcp_gate`,
|
||||
`bypass_consent_gate`, `codex_auth_refusal` and `codex_client_incompatible`.
|
||||
They are recognized from each harness's English prompt text.
|
||||
|
||||
## Two Categories of Startup
|
||||
|
||||
Everything an agent receives at boot time falls into one of two categories:
|
||||
@@ -26,14 +56,14 @@ Context loading shapes what the agent knows, believes, and is capable of:
|
||||
### Category 2: Deterministic Configuration
|
||||
|
||||
The rig spec declaratively installs things into the agent's runtime environment:
|
||||
- Hooks (git hooks, pre-commit scripts)
|
||||
- Hooks (shipped inside plugins)
|
||||
- Permissions (`.claude/settings.json` allowlists, approval modes)
|
||||
- MCPs (Model Context Protocol servers)
|
||||
- System dependencies (tools, packages)
|
||||
|
||||
See the **Current Support Matrix** section at the end of this guide for what is reliable today vs experimental.
|
||||
|
||||
**The v0.2.0 recommendation:** Put as much of your setup logic as possible into Category 1 (context loading via markdown files). Describe the desired end state in startup files and let the agent handle the configuration. The deterministic path exists in the spec and will become more reliable over time, but for now the context-loading path is the one that works consistently across runtimes.
|
||||
**The recommendation:** Put as much of your setup logic as possible into Category 1 (context loading via markdown files). Describe the desired end state in startup files and let the agent handle the configuration. The deterministic path exists in the spec and will become more reliable over time, but for now the context-loading path is the one that works consistently across runtimes.
|
||||
|
||||
---
|
||||
|
||||
@@ -118,30 +148,35 @@ Startup content is merged additively through layers. Each layer adds to what the
|
||||
```
|
||||
1. Agent layer — from the AgentSpec's top-level startup block
|
||||
2. Profile layer — from the active profile's startup block
|
||||
3. Rig layer — from the RigSpec's top-level startup block
|
||||
4. Culture layer — from the RigSpec's culture_file
|
||||
3. Culture layer — OpenRig's default culture, then the RigSpec's culture_file
|
||||
4. Rig layer — from the RigSpec's top-level startup block
|
||||
5. Pod layer — from the pod's startup block
|
||||
6. Member layer — from the member's startup block in the RigSpec
|
||||
7. Operator layer — injected at runtime (openrig-start overlay, context collector, etc.)
|
||||
7. Operator layer — added by OpenRig: openrig-start.md and, on a fresh start, the onboarding pack
|
||||
```
|
||||
|
||||
A member with a `starter_ref` gets that agent starter's files in front of the agent layer. Terminal seats
|
||||
(`builtin:terminal`) skip the agent and profile layers.
|
||||
|
||||
### What Each Layer Is For
|
||||
|
||||
| Layer | Authored by | Purpose | Example content |
|
||||
|-------|-------------|---------|-----------------|
|
||||
| Agent | Agent spec author | Core identity and capabilities that travel with this agent type | Role guidance, default skills |
|
||||
| Profile | Agent spec author | Profile-specific variations | Different skill sets for "default" vs "minimal" profiles |
|
||||
| Rig | Rig spec author | Rig-wide context for all agents | Team norms, project documentation |
|
||||
| Culture | Rig spec author | Rig constitution | `CULTURE.md` — communication, quality, operating philosophy |
|
||||
| Culture | OpenRig, then the rig spec author | Team norms | OpenRig's `CULTURE-default.md` for every seat, then the rig's own `CULTURE.md` |
|
||||
| Rig | Rig spec author | Rig-wide context for all agents | Project documentation |
|
||||
| Pod | Rig spec author | Pod-specific coordination context | Pod SOP, intra-pod workflow |
|
||||
| Member | Rig spec author | Individual member overrides | Member-specific instructions, cwd-specific context |
|
||||
| Operator | OpenRig system | System-injected runtime content | `openrig-start.md`, context collector |
|
||||
| Operator | OpenRig system | System-injected runtime content | `openrig-start.md` (optional delivery); on a fresh start, `openrig-onboarding-01.md` and `-02.md` unless `onboarding.default_pack.enabled` is false |
|
||||
|
||||
After the files, OpenRig sends a session-identity message (rig, pod, member, logical ID) once the seat is ready.
|
||||
|
||||
### Practical Guidance
|
||||
|
||||
**Most rigs only need three layers:** agent (role.md), rig (culture), and operator (openrig-start). Start simple. Add pod and member layers only when agents in the same pod need different startup content.
|
||||
**Most rigs only need three layers:** agent (role.md), culture, and the built-in operator layer. Start simple. Add pod and member layers only when agents in the same pod need different startup content.
|
||||
|
||||
**The culture layer is high-value and often skipped.** A rig without a `CULTURE.md` relies on agents to guess how the team communicates and coordinates. Write one. Even a short culture file dramatically improves team coherence.
|
||||
**The rig's own culture file is high-value and often skipped.** Every seat gets OpenRig's default culture, but that says nothing about how this team communicates and coordinates. Write a `CULTURE.md`. Even a short one dramatically improves team coherence.
|
||||
|
||||
**Member-level startup is for exceptions, not the rule.** If every member has its own startup block, the layering model is being used as a configuration dump. Refactor shared content up to the pod or rig level.
|
||||
|
||||
@@ -153,8 +188,8 @@ Startup content is merged additively through layers. Each layer adds to what the
|
||||
|
||||
| Delivery Hint | When | How | Use For |
|
||||
|---------------|------|-----|---------|
|
||||
| `auto` | Before harness boot | System chooses | Default — let OpenRig decide |
|
||||
| `guidance_merge` | Before harness boot | Merged into `CLAUDE.md` / `AGENTS.md` as a managed block | Role guidance, culture, project context |
|
||||
| `auto` | Depends on the file | Resolved per file: a `SKILL.md` (or content starting `# SKILL`) is `skill_install`, any other `.md` is `guidance_merge`, and any other file is `send_text` | Default — let OpenRig decide |
|
||||
| `guidance_merge` | Before harness boot | Merged as a managed block into `AGENTS.md` (Codex, Pi) or `CLAUDE.md` (Claude; `CLAUDE.local.md` with the rig's `managed_blocks: { claude-code: CLAUDE.local.md }`) | Role guidance, culture, project context |
|
||||
| `skill_install` | Before harness boot | Copied to runtime's skill directory | Skills |
|
||||
| `send_text` | After harness is ready | Sent as text to agent's terminal via tmux | Boot-time grounding, identity hints, instructions to read skills |
|
||||
|
||||
@@ -167,11 +202,65 @@ Files delivered via `send_text` happen AFTER the harness is ready. The agent rec
|
||||
- Instructions to load skills (the files are already projected, the message tells the agent to read them)
|
||||
- Context that should feel like an operator briefing, not pre-loaded content
|
||||
|
||||
On a fresh launch the first message is one turn: the session identity, the first `send_text` file and, when a startup
|
||||
proof is selected, its challenge (below).
|
||||
|
||||
A file's `required` field defaults to `true`: a required file that can't be delivered fails startup, and an optional
|
||||
one is dropped without a message.
|
||||
|
||||
### Readiness, startup proof and failure
|
||||
|
||||
**Readiness.** After launch OpenRig polls the pane until the harness is interactive (backing off 1, 2, 4, 8 and 16
|
||||
seconds), for up to 30 seconds by default. Change the window with
|
||||
`rig config set runtime.readiness_timeout_seconds <1-600>` (or `OPENRIG_RUNTIME_READINESS_TIMEOUT_SECONDS`); it
|
||||
applies to the next launch. A recognized prompt (the attention codes above) ends the wait at once as
|
||||
`attention_required`. A timeout ends startup as `failed` ("Readiness timeout after Ns — harness did not become
|
||||
interactive"), and the post-launch files aren't sent.
|
||||
|
||||
**Claude submission check.** For each startup message to a Claude seat, OpenRig makes a bounded check that the prompt
|
||||
was submitted. When the capture can't tell, it records the submission as unverified and carries on. When it sees the
|
||||
same prompt still sitting in the input, it presses Enter once more; if it's still there, it warns "Startup prompt still
|
||||
staged in <session>; press Enter in that pane." The seat can still end `ready` with its submission `unverified` or
|
||||
`staged`, so `ready` doesn't prove the prompt was submitted. After all files and actions are delivered, readiness is
|
||||
checked once more, so a prompt that appears by then gives `attention_required`.
|
||||
|
||||
**Startup proof.** A `startup_proof` action selects `authenticated` or `none` (the default is none; the last applicable
|
||||
one wins, and it needs `idempotent: true`). With `authenticated`, a fresh launch of an agent seat includes a challenge,
|
||||
and the seat answers it with `rig startup-proof submit --challenge-id <id> --answer <answer>`. `rig ps --nodes --full`
|
||||
shows the result in the ORIENTED column: `verified`, `missing`, `rejected` or `n-a`. A missing proof doesn't block startup.
|
||||
|
||||
**After a failure.** Delivery, launch and action failures, and readiness timeouts, give `failed`. Every 30 seconds the
|
||||
context monitor marks a `failed` or `attention_required` seat `ready` once its pane reads ready, unless fresh context
|
||||
is still pending for `rig seat continue`. A seat that timed out can therefore read `ready` without having received its
|
||||
post-launch files. If it needs them, first look at what that occupant has done; then
|
||||
`rig seat launch <seat> --fresh --reason <text>` starts a blank conversation for the seat. If the occupant is still
|
||||
live, the command refuses with `session_live` unless you also pass `--stop`, which replaces that occupant deliberately.
|
||||
|
||||
**Preview before replacing a seat.** `rig seat launch <seat> --fresh --plan` inspects the selected
|
||||
seat without starting tmux, stopping or launching a process, projecting startup files, or changing
|
||||
lifecycle/queue records. Add `--json` for structured output. `--fresh` is still required;
|
||||
`--reason` is optional for the preview and required when executing. Pass `--stop` to preview the
|
||||
intended replacement of a live managed occupant. Adopted and unmanaged occupants remain blocked.
|
||||
|
||||
The plan identifies the target, current occupant, stop requirements, blockers, startup counts,
|
||||
and each sibling seat's unchanged effect. Siblings stay attached and untouched. Launch composes
|
||||
a new command through the runtime adapter using current settings; it does not replay a stored
|
||||
wrapper command. The seat's launch environment and managed executable selection still apply.
|
||||
The plan does not construct or display the launch command.
|
||||
|
||||
A preview is a snapshot, not a reservation or a native-readiness check. Unreadable tmux produces
|
||||
`complete: false` and `launchable: null` (unknown), unless a known blocker already proves
|
||||
`launchable: false`. A successful preview exits 0, including blocked or incomplete plans; scripts
|
||||
must inspect `launchable === true` before deciding to execute. Request failures still exit nonzero.
|
||||
Execution always resolves and checks the current state again. An older daemon without the preview
|
||||
endpoint refuses the request; the CLI never falls back to executing a launch.
|
||||
|
||||
### The `applies_on` Field
|
||||
|
||||
Each startup file and action specifies when it applies:
|
||||
- `fresh_start` — delivered on first launch only
|
||||
- `restore` — delivered on restore from snapshot
|
||||
- `fresh_start` — delivered when a new conversation starts: the first launch, `rig seat launch --fresh`,
|
||||
`rig seat continue`, and seats restored with `--fresh`
|
||||
- `restore` — delivered when a seat is restored, including a restore whose resume falls back to a fresh conversation
|
||||
- Default: `[fresh_start, restore]` (both)
|
||||
|
||||
Use this to avoid re-sending context that the agent already has from its resumed conversation. For example, a one-time project briefing might only apply on `fresh_start`, while identity grounding should apply on both.
|
||||
@@ -184,15 +273,8 @@ The AgentSpec and RigSpec allow declaring deterministic environment configuratio
|
||||
|
||||
### What the Spec Supports
|
||||
|
||||
**Hooks** (in `resources.hooks`):
|
||||
```yaml
|
||||
resources:
|
||||
hooks:
|
||||
- id: pre-commit
|
||||
path: hooks/pre-commit.sh
|
||||
runtimes: [claude-code]
|
||||
```
|
||||
Hooks are scripts copied to the agent's workspace. They can be git hooks, automation scripts, or environment setup.
|
||||
**Hooks** ship inside plugins: declare the plugin under `resources.plugins[]`. AgentSpec validation refuses
|
||||
`resources.hooks`.
|
||||
|
||||
**Runtime resources** (in `resources.runtime_resources`):
|
||||
```yaml
|
||||
@@ -214,23 +296,26 @@ startup:
|
||||
phase: after_ready
|
||||
idempotent: true
|
||||
```
|
||||
Commands sent to the agent's terminal after it's ready. Can install MCPs, run setup commands, etc.
|
||||
Each value is typed into the harness's input and submitted, so it is a prompt or a slash command, not a shell command.
|
||||
Types are `send_text`, `slash_command` and `startup_proof` (see "Startup proof" above); `shell` is refused.
|
||||
`idempotent` is required, and an action that isn't idempotent must not list `restore` in `applies_on`. Both phases
|
||||
run after the harness is ready: `after_files` (the default) after the post-launch files, then `after_ready`.
|
||||
|
||||
### Current Support Matrix (OpenRig 0.1.x)
|
||||
### Current Support Matrix
|
||||
|
||||
| Capability | Status | Notes |
|
||||
|------------|--------|-------|
|
||||
| Guidance file projection (`guidance_merge`) | **Supported** | Reliable. Primary delivery mechanism. |
|
||||
| Skill projection (`skill_install`) | **Supported** | Reliable. Skills are copied to workspace. |
|
||||
| `send_text` delivery after ready | **Supported** | Reliable. Requires harness to be ready. |
|
||||
| Hook projection | **Experimental** | Files are copied but execution/integration varies by runtime. |
|
||||
| Hooks | **Through plugins** | Declare a plugin under `resources.plugins[]`; `resources.hooks` is refused. |
|
||||
| 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 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. |
|
||||
| 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; a seat with no policy, selection or named Codex profile also gets the per-launch [team default](rig-spec.md#team-launch-defaults), and the `kernel` rig's seats get a wider operational default ([rig spec, "At launch"](rig-spec.md)). Neither is written to a settings file. Use [the first-user permission guide](getting-started.md#opt-in-permissive-operation) for opt-in and custom choices. |
|
||||
| MCP installation | **Supported for Claude fragments** | A selected `claude_mcp_fragment` is merged into the project's `.mcp.json`. Claude asks to approve new servers found there; that prompt stops startup as `mcp_gate` until someone answers it and runs `rig seat continue`. Otherwise use `/mcp` or `claude mcp add`, or describe the servers in startup files for the agent to configure. |
|
||||
| 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. |
|
||||
|
||||
### The v0.2.0 Approach: Describe, Then Let the Agent Handle It
|
||||
### The Approach: Describe, Then Let the Agent Handle It
|
||||
|
||||
For anything beyond `guidance_merge`, `skill_install`, and `send_text`, the recommended approach is:
|
||||
|
||||
@@ -250,12 +335,18 @@ OpenRig performs best-effort deterministic runtime configuration for managed ses
|
||||
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.
|
||||
Explicit permission modes use the launch-selected `HOME/.claude.json`, or
|
||||
`<CLAUDE_CONFIG_DIR>/.claude.json` when that variable is set. Classic startup
|
||||
retains the daemon-home path.
|
||||
- Claude project-local config: `.claude/settings.local.json`
|
||||
Purpose: apply context collector/activity hooks and selected `claude_settings_fragment` resources inside the project without committing them to git.
|
||||
- Claude project-local MCP config: `.mcp.json`
|
||||
Purpose: apply selected `claude_mcp_fragment` resources for Claude in that project.
|
||||
- Codex global config: `~/.codex/config.toml`
|
||||
- Codex global config: `$CODEX_HOME/config.toml`, or `~/.codex/config.toml` when unset
|
||||
Purpose: pre-trust managed workspaces and apply selected `codex_config_fragment` resources. Codex currently has no equivalent project-local MCP config path for global profile settings.
|
||||
- Global skill folders: `~/.claude/skills` and `~/.agents/skills`
|
||||
Purpose: daemon startup seeds the `openrig-skills` and `refocusing` skills and the person-facing `rigs` skill. Each
|
||||
upgrade refreshes the `rigs` skill; a copy installed another way is left as it is.
|
||||
|
||||
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
|
||||
@@ -266,6 +357,39 @@ 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).
|
||||
|
||||
### Separate Codex homes for separate installations
|
||||
|
||||
Set an **absolute `CODEX_HOME` in the environment that starts the daemon** when
|
||||
you want a separate Codex configuration and state root. OpenRig uses that root
|
||||
for its startup hook setup/removal and trust records, feature flag, workspace
|
||||
trust and selected config fragments. Startup can write those files even when
|
||||
the daemon has no seats.
|
||||
|
||||
The explicit selection also applies to future managed Codex fresh, resume and
|
||||
fork launches, their capability/profile/configuration probes, native thread and
|
||||
context reads, and plugin-cache discovery. Launch commands reassert it after
|
||||
pane shell startup, so an rc file cannot silently switch that launch to another
|
||||
Codex home. Executable and PATH selection stay the same. Existing running
|
||||
sessions are not moved or restarted; a thread in another home is not searched
|
||||
as a fallback for an explicitly selected home.
|
||||
|
||||
**With `CODEX_HOME` unset, a second install still shares `~/.codex`.** Setting only
|
||||
`OPENRIG_HOME` separates OpenRig state, not provider configuration. The default
|
||||
launch behavior is unchanged, including existing pane-shell overrides.
|
||||
|
||||
OpenRig does not copy or migrate authentication, history, rules, trust or
|
||||
settings into the selected home. A separate file-backed home needs its intended
|
||||
authentication provisioned if absent; OS credential stores and other config
|
||||
layers can behave differently, so a new login is not universally required.
|
||||
Explicit Codex auth commands resolve their **caller's** `CODEX_HOME`, otherwise
|
||||
`$HOME/.codex`; contacting a daemon with `OPENRIG_URL` does not retarget auth
|
||||
save/switch/registry writes to that daemon's selection. Set the intended root
|
||||
on those commands as well.
|
||||
|
||||
This separates provider files, not OS users or accounts. Project-local resources
|
||||
still use the selected project directory; shared project and system settings
|
||||
remain shared.
|
||||
|
||||
### Runtime Differences
|
||||
|
||||
**Claude Code:**
|
||||
@@ -283,8 +407,25 @@ rules were translated or applied. See [permission precedence and limits](getting
|
||||
- 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`.
|
||||
- On that plain `-s workspace-write` launch (fresh, resume, fork or restore), a
|
||||
short-lived `codex app-server` first reads Codex's own configuration with the
|
||||
seat's executable, home and working directory. OpenRig adds
|
||||
`-c sandbox_workspace_write.network_access=true` only when no configuration
|
||||
layer sets network access and no managed requirement could restrict it. A
|
||||
timeout, error or unrecognized answer adds nothing. Named profiles and full
|
||||
bypass are not read. The read may write Codex's own state files in
|
||||
`CODEX_HOME`, read its login and fetch managed policy, as a Codex start does.
|
||||
- Managed launches pass `-c check_for_update_on_startup=false`, so Codex skips its startup update check; your Codex
|
||||
config is not changed.
|
||||
- Can self-install dependencies from instructions but timer/recurring behavior is not reliably available
|
||||
|
||||
**Pi and OMP:**
|
||||
- Read `AGENTS.md` in the working directory; guidance is merged there, added to Pi's own system prompt
|
||||
- Selected skills are projected into the seat's own agent directory under OpenRig's state, not into a shared
|
||||
workspace folder
|
||||
|
||||
**Terminal seats** (`builtin:terminal`) get no agent or profile layers and no startup proof.
|
||||
|
||||
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."
|
||||
|
||||
---
|
||||
@@ -301,6 +442,41 @@ profile → uses.skills: [openrig-user]
|
||||
```
|
||||
This is the minimum effective startup. The agent knows who it is, how the team works, and how to use the rig.
|
||||
|
||||
For seats sharing a working directory, keep each role in its own AgentSpec startup entry:
|
||||
|
||||
```yaml
|
||||
startup:
|
||||
files:
|
||||
- path: guidance/role.md
|
||||
orientation: role
|
||||
delivery_hint: send_text
|
||||
required: true
|
||||
```
|
||||
|
||||
`rig queue whoami --json` reports only the calling seat's explicitly marked role
|
||||
bindings, independently of its current work. The `role.state` is `no-record`,
|
||||
`not-declared`, `missing`, `present`, or `unknown` when identity, storage or the
|
||||
observation is unavailable. Files retain their original `path`, `absolutePath`
|
||||
and `ownerRoot` plus the currently resolved `resolvedPath` and
|
||||
`resolvedOwnerRoot`. Built-in packaged paths follow the running installation;
|
||||
existing development-checkout paths stay where they were recorded.
|
||||
|
||||
`recordedAt` is the startup-context record write/attempt, which happens before
|
||||
launch succeeds. Exact resume can retain an older record. `present` means the
|
||||
resolved file currently exists, not that its bytes still match the spec or that
|
||||
the agent read it. Unmarked startup files and startup actions are not exposed.
|
||||
|
||||
Refocus includes the role path and a re-read reminder with topology or both trees,
|
||||
even if the trace fails. Work-only mode omits it; disabling refocus still disables
|
||||
the entire hook. Custom refocus prose keeps its existing precedence. The hook
|
||||
reuses its work lookup, or makes one bounded lookup when the work node is explicit;
|
||||
a failed or skipped lookup reports `unknown`.
|
||||
|
||||
The shipped kernel seats no longer project their role into shared guidance.
|
||||
Existing user files and legacy role blocks are left intact; this is not a cleanup
|
||||
of already-installed shared blocks. General user-authored guidance projection is
|
||||
unchanged.
|
||||
|
||||
**Separate project context from role**
|
||||
|
||||
Don't put project documentation inside the role guidance. The role is about the agent's function; project context is about what the agent is working on. Use rig-level or pod-level startup files for project context.
|
||||
@@ -320,7 +496,7 @@ This prompts the agent to actually invoke the skills, not just have them as pass
|
||||
|
||||
After identity recovery, verify:
|
||||
1. `rig ps --nodes` shows your rig running (scoped to your session's rig by default; outside a managed session name it explicitly: `rig ps --nodes --rig <name>`)
|
||||
2. `rig env status` shows services healthy (if applicable)
|
||||
2. `rig env status <rig>` shows services healthy (if applicable)
|
||||
3. Your working directory is correct
|
||||
4. Required tools are available (node, npm, git, etc.)
|
||||
|
||||
@@ -356,7 +532,7 @@ If your rig REQUIRES a hook to function and the hook installation fails silently
|
||||
When creating a new agent's startup experience:
|
||||
|
||||
- [ ] Write a `guidance/role.md` — who is this agent?
|
||||
- [ ] Reference it in the agent spec's `resources.guidance` AND `startup.files`
|
||||
- [ ] Reference it in `startup.files` with `orientation: role` and `delivery_hint: send_text`. Avoid projecting distinct seat roles into one shared `AGENTS.md` or `CLAUDE.md`.
|
||||
- [ ] Write a rig `CULTURE.md` if the rig doesn't have one
|
||||
- [ ] Choose skills from the shared pool via profile `uses`
|
||||
- [ ] Write a `startup/context.md` if the agent needs environment grounding
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# 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.
|
||||
The formal activity language OpenRig's surfaces render from — the TUI and the `rig ps`
|
||||
ACTIVITY column. One oracle computes it; surfaces render it. Two exceptions remain: the web
|
||||
UI's graph node still derives its own running/needs-input/idle/unknown from the legacy
|
||||
`agentActivity`, and `rig ps --active` / `--filter agentActivity.state` filter on that legacy
|
||||
field. Startup, orientation, restore and attention states are separate vocabularies, listed
|
||||
under "Other state on the same surfaces" below.
|
||||
|
||||
**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
|
||||
@@ -16,6 +19,12 @@ truth is `packages/daemon/src/domain/activity-taxonomy.ts`.
|
||||
| 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 |
|
||||
|
||||
Only the activity axis is served today. The session and resumability values are declared in
|
||||
`activity-taxonomy.ts` but no surface reads them: `rig ps --nodes --full` shows the raw session
|
||||
STATUS, both node views show a LIFECYCLE of `running`, `detached`, `recoverable` or `attention_required` (`run`, `det`, `rec`,
|
||||
`att`), and node inventory serves `occupantLifecycle` (`active`, `retiring`, `retired`,
|
||||
`context_walled`, `compacted`, `crashed`, `unknown`).
|
||||
|
||||
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.
|
||||
@@ -35,15 +44,18 @@ 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).
|
||||
(open obligations OR unhealthy HELD rows)`. A dropped baton or an unanswered block. When
|
||||
activity is `unknown` the verdict is `indeterminate`, never parked or not-parked. Queryable
|
||||
at rig and seat level with `rig parked [seat] [--rig <name>] [--json]`; the join lives in the
|
||||
parked query, 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.
|
||||
PARKED. A HELD row is healthy only while its wake is live, and not fired-but-unconsumed
|
||||
unless the stuck sweep owns its recovery; an unhealthy HELD row on a stopped seat makes
|
||||
the seat 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.
|
||||
idle ∧ unseen). Declared as a concept only; no surface computes it yet.
|
||||
|
||||
## Reconciliation with prior art
|
||||
|
||||
@@ -65,8 +77,10 @@ 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
|
||||
- **r2 — lifecycle hooks** (Claude SessionStart/UserPromptSubmit/Stop/Notification;
|
||||
Codex SessionStart/UserPromptSubmit/Stop/PermissionRequest): standing for Claude; Codex's
|
||||
hook rung starts at trial and is promoted after 50 agreeing observations over at least an
|
||||
hour. 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
|
||||
@@ -76,8 +90,9 @@ time-bounded evidence. Rungs, top down, with each rung's retirement condition:
|
||||
- **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).
|
||||
pass admits a rung to TRIAL, where it is measured but not consulted; production agreement
|
||||
promotes it). Undeclared and partial-coverage rungs are identity-only, which is also where
|
||||
a degraded rung goes.
|
||||
|
||||
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
|
||||
@@ -88,6 +103,22 @@ generation swap is its own visible event — never an activity transition — an
|
||||
rung-inventory re-declaration: a successor never inherits its predecessor's rung
|
||||
authority (hooks live in occupant config while the pane is seat-persistent).
|
||||
|
||||
## Other state on the same surfaces
|
||||
|
||||
These are separate vocabularies, not activity values. `rig ps --nodes --full` shows them in
|
||||
its STARTUP, ORIENTED, LIFECYCLE and RESTORE columns (the default compact view shows only
|
||||
LIFECYCLE); see the
|
||||
[startup guide](agent-startup-guide.md) for how each arises.
|
||||
|
||||
- **Startup status:** `pending`, `ready`, `attention_required`, `failed`. `ready` means
|
||||
startup finished, never that the seat is oriented, and a seat isn't marked `ready` while
|
||||
its startup context is still pending for `rig seat continue`.
|
||||
- **Oriented:** `verified`, `missing`, `rejected`, `n-a`, from the startup proof.
|
||||
- **Attention codes:** `trust_gate`, `hook_trust_gate`, `update_gate`, `login_required`,
|
||||
`mcp_gate`, `bypass_consent_gate`, `codex_auth_refusal`, `codex_client_incompatible`.
|
||||
- **Restore outcome:** `resumed`, `fresh-primed`, `awaiting-decision`, `attention_required`,
|
||||
`failed` (plus `rebuilt`, `operator_recovered` and the legacy `fresh`).
|
||||
|
||||
## 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
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
## Browser access and allowed addresses
|
||||
|
||||
The OpenRig daemon answers its API only when a request is addressed to this machine and, if it
|
||||
comes from a web page, only when that page is the OpenRig UI or a page you allowed.
|
||||
|
||||
### What works with no setup
|
||||
|
||||
- The CLI, the TUI and agents on this machine, addressing the daemon by `localhost` or an IP
|
||||
address.
|
||||
- Other OpenRig hosts and clients that reach this daemon by its IP address, its own hostname, or
|
||||
its own Tailscale name (for example `my-machine.your-tailnet.ts.net`, or the short `my-machine`).
|
||||
The daemon learns its own Tailscale name automatically from Tailscale on this machine.
|
||||
- The web UI, when it is turned on (`rig config set ui.enabled true`, then restart the daemon) and
|
||||
opened over `http` on the daemon's own port at any address the daemon accepts, including its
|
||||
Tailscale name. A UI page served any other way, such as over `https` through your own domain,
|
||||
also needs its exact origin in `OPENRIG_ALLOWED_ORIGINS`, then a daemon restart.
|
||||
|
||||
### When you see a refusal
|
||||
|
||||
The daemon answers `403` with a sentence that says what to do. There are two kinds:
|
||||
|
||||
- **The address is not recognized** (`untrusted_host`). You reached the daemon by a name it does not
|
||||
know as its own, such as a custom DNS name, an alias in `/etc/hosts`, or a reverse proxy domain.
|
||||
Use `localhost`, an IP address or the machine's own name instead, or list the name you use:
|
||||
`OPENRIG_ALLOWED_HOSTS=rig.example.com` in the daemon's environment, then restart the daemon.
|
||||
A client that presents the daemon's configured bearer token is not refused for its address
|
||||
(it still needs whatever access each route requires).
|
||||
- **The web page is not allowed** (`browser_origin_refused`). A browser page that is not the
|
||||
OpenRig UI at the daemon's own address called the API. This includes another local app or
|
||||
development server on a different port. To allow a page, list its exact origin:
|
||||
`OPENRIG_ALLOWED_ORIGINS=http://localhost:5173` in the daemon's environment, then restart the
|
||||
daemon. A bearer token does not override this.
|
||||
|
||||
`OPENRIG_ALLOWED_HOSTS` lists names you use to reach the daemon. `OPENRIG_ALLOWED_ORIGINS` lists
|
||||
web pages allowed to call it. They are separate: a UI served through your own domain usually needs
|
||||
both. Separate several entries with commas. Pages on other origins cannot read the refusal; the
|
||||
message is for you, the CLI and agents.
|
||||
|
||||
### Limits
|
||||
|
||||
- If you rename the machine in Tailscale, the daemon keeps accepting its previous name until it
|
||||
restarts or until it next looks its name up after seeing an unrecognized one.
|
||||
- If the daemon has not reached Tailscale's resolver since it started, its Tailscale name is
|
||||
refused until a lookup succeeds. A later failed lookup keeps the name it already learned.
|
||||
`localhost` and IP addresses keep working.
|
||||
- This protects against unknown web pages and unknown names. It is not complete browser isolation:
|
||||
a page on another site can still cause a simple read request to an address the daemon accepts,
|
||||
although it cannot read the answer. Keep the daemon on loopback or your tailnet.
|
||||
@@ -0,0 +1,209 @@
|
||||
# Bundle formats (v1)
|
||||
|
||||
The shared data formats for sharing rig bundles: the configurations a bundle offers, the before-install view of what it
|
||||
will do, run records and the public status file, and registry entries. The schemas are in [`schemas/`](schemas/), with
|
||||
fixtures that pass and fail them in [`schemas/fixtures/`](schemas/fixtures/). Tools outside this repository read them
|
||||
at a pinned commit.
|
||||
|
||||
**A v1 format only grows:** new optional properties may appear, and consumers ignore properties they don't know. Any
|
||||
other change is a v2. The schemas are open to new properties, with three closed on purpose: the package digest (an exact
|
||||
value), a run record's subject (a harness check can never carry a team's identity), and the public status file, where
|
||||
nothing outside the schema may appear, so a private field can't leak in. A new property in a closed part is a v2. The
|
||||
status schema also rejects labels that contradict their evidence: a tested label needs records and a package, tested
|
||||
with help needs its count, tested can't carry a count above zero, and an unreadable listing carries only Status
|
||||
unavailable.
|
||||
|
||||
## Three identities, kept apart
|
||||
|
||||
| What | Name | Defined by |
|
||||
|---|---|---|
|
||||
| What someone chose | configuration ID | the mapping of every seat to a runtime |
|
||||
| What was packaged | package digest | the built archive's files |
|
||||
| What actually ran | execution binding | a run record (resolved resources, settings, environment, unknowns) |
|
||||
|
||||
None stands in for another. A package digest doesn't describe what ran, and a configuration ID doesn't describe what was
|
||||
packaged.
|
||||
|
||||
### Source identity
|
||||
|
||||
A bundle on GitHub is named by `repository` (`https://github.com/<owner>/<repo>`, no credentials), `folder` (the
|
||||
repo-relative path of the folder holding `rig.yaml`, or `.` for the repository root) and `resolvedCommit` (40 lowercase
|
||||
hex).
|
||||
- `requestedRef` (the branch, tag or commit someone gave, or `HEAD` for a link without `/tree/…`) and `canonicalUrl`
|
||||
(`…/tree/<resolvedCommit>/<folder>`, with each folder segment percent-encoded and no folder part for the root)
|
||||
explain it, and aren't part of it.
|
||||
- A built bundle records its source in its manifest's provenance, which is outside the package digest.
|
||||
|
||||
### Configuration ID
|
||||
|
||||
Every member's `pod.member=runtime`, sorted by `pod.member` in UTF-16 code-unit order, joined with `,`. There's no
|
||||
whitespace, and runtimes are spelled as in `rig.yaml`. For example:
|
||||
|
||||
```text
|
||||
dev.build=claude-code,dev.qa=claude-code,dev.review=codex,orch.lead=codex
|
||||
```
|
||||
|
||||
Preset names such as `recommended` or `all-claude` are aliases shown beside the ID, never part of it.
|
||||
|
||||
### Package digest
|
||||
|
||||
`{ algorithm: "sha256", value, coverage: "openrig.package-digest/v1" }`:
|
||||
- `value` is SHA-256 over UTF-8 lines `<quoted path>\t<sha256>\n`, one for each entry of the built archive's
|
||||
`integrity.files`, sorted by path in UTF-16 code-unit order;
|
||||
- `<quoted path>` is the path as a JSON string, exactly as JavaScript's `JSON.stringify(path)` writes it, so a tab or
|
||||
newline in a file name can't make two different file lists hash the same. In words: wrapped in `"`; `"` and `\`
|
||||
escaped with a backslash; U+0008, U+0009, U+000A, U+000C and U+000D written `\b`, `\t`, `\n`, `\f` and `\r`; any
|
||||
other character below U+0020, and any lone surrogate, written `\uXXXX` in lowercase hex; everything else as itself;
|
||||
- it's the same when the same folder is rebuilt by the same OpenRig, while the archive's own hash changes with every
|
||||
build (`createdAt`).
|
||||
|
||||
**Coverage, shown wherever the digest is:** the bytes of every packaged file. It doesn't cover:
|
||||
- `bundle.yaml`, so no manifest field, provenance or `createdAt`;
|
||||
- the names the integrity walk skips (`.DS_Store`, `Thumbs.db`, `.gitkeep`);
|
||||
- file modes;
|
||||
- anything resolved on the installing machine at launch, for example an `openrig-home:` plugin, catalog skills, or a
|
||||
model or effort nothing declares.
|
||||
|
||||
**A build result** carries `source` (`null` unless the bundle was built from a GitHub link), `configurationId`,
|
||||
`packageDigest`, `archiveHash` and `assembler`. `rig bundle inspect` reports the same fields for an archive, and any the
|
||||
archive doesn't state are `null`. `assembler` is `{ openrigVersion }`: the format also allows a `commit`, which this
|
||||
OpenRig doesn't write. `configurationId()` and `packageDigest()` are exported from `@openrig/daemon/bundle-identity`.
|
||||
Shared test vectors are in `schemas/fixtures/identity-vectors.json`.
|
||||
|
||||
## The formats
|
||||
|
||||
| Format | Schema | Lives | Written by |
|
||||
|---|---|---|---|
|
||||
| Declared configurations | `bundle-configurations.v1` | `configurations.yaml` beside `rig.yaml` | the bundle's author |
|
||||
| Before-install view | `bundle-behaviour.v1` (`openrig.bundle-behaviour/v1`) | the registry, one file per configuration and assembler version; never inside the archive | the daemon's inspect: `rig bundle inspect`, under `behaviour` with `--json` |
|
||||
| Run record | `run-record.v1` | beside its receipt, private | Fleet, Dev QA, and maintainers for community reports |
|
||||
| Public status | `bundle-status.v1` | `openrig-registry` status, generated | the status generator only |
|
||||
| Registry entry | `registry-entry.v1` | `openrig-registry/registry/<slug>.yaml` | maintainers, through review |
|
||||
|
||||
### Declared configurations
|
||||
|
||||
`seats` gives a `pod.member` the runtimes it may use, and the profile each runtime uses. A member it doesn't list keeps
|
||||
the runtime `rig.yaml` gives it. `presets` names full mappings, and `recommended` names one of them, which must be
|
||||
`rig.yaml` as written. `rig bundle create`, `rig bundle configurations` and `rig bundle check` refuse a file that breaks
|
||||
these rules.
|
||||
|
||||
`--preset` and `--seat` choose a configuration (see the [rig bundle reference](rig-bundle.md)). Any mix of each seat's
|
||||
declared runtimes can be built, not only the presets; a runtime a seat doesn't declare is refused, and nothing is built.
|
||||
Without either flag, create builds `rig.yaml` as written. The built archive's manifest records
|
||||
`configuration: { id, preset }` outside the package digest; `preset` is there only when `--preset` or `--seat` chose a
|
||||
mapping that matches a declared preset.
|
||||
|
||||
### Before-install view
|
||||
|
||||
It's derived from the archive's files alone. Nothing is launched, probed or fetched. It's a view, never a gate, and
|
||||
carries no tested status.
|
||||
- **`identity`:** source, configuration ID, package digest, the archive's assembler, the generator that made the view,
|
||||
stated compatibility, stated provenance (not verified), and integrity (self-consistency, not authorship). A source,
|
||||
configuration ID, package digest or assembler the archive doesn't state is `null`; unstated compatibility and
|
||||
provenance are empty objects. None of it is filled from the inspecting OpenRig.
|
||||
- **`team`, `posture`, `toldFiles`, `alsoRuns`, `writes`, `outsideAddresses`, `needs` and `unknownBeforeLaunch`**, in
|
||||
that order. Facts cite the archive file they come from (`sourceRefs`), except `team` entries and the bundle-wide
|
||||
unknowns. `toldFiles` and `alsoRuns` also say how each resolves: `archive`, `host_at_launch` or `unresolved`.
|
||||
- Posture separates what the archive declares from the product's default. Its effect on the host is `unknown`,
|
||||
because host settings decide it.
|
||||
Optional `posture[].permissionPrompts` is `off` for declared `full_bypass` (including rig- or member-level
|
||||
`builtin:yolo`) on any runtime; for Claude/Codex, `on` for a declared flag-surface floor policy or `default` when
|
||||
no policy is declared (prompts on unless the installing machine's settings turn them off); for Codex, `on` for
|
||||
declared `auto`. Omit it for Claude under auto (naming auto mode in `selection`), config-surface policies
|
||||
(`builtin:locked`, `builtin:standard`, `builtin:open` or an archived `surface: config` policy), host-resolved
|
||||
Codex profiles, `permission_policy: none`, a policy the archive can't resolve, and every runtime other than Claude
|
||||
and Codex (Pi, terminal) except declared full bypass. `selection` names the declared policy.
|
||||
An absent field means not stated, never `on`. For `off`, Claude
|
||||
bypasses permissions, Codex runs with full access and never asks, and Pi gets full resource trust (`--approve`,
|
||||
not a permission mode). These are archive facts; `nativeEffect` remains `unknown`.
|
||||
Optional `posture[].firstRunWarnings.claudeBypass: "harness_asks_once"` names the first-run bypass warning for a
|
||||
declared full-bypass Claude seat, conditional on whether the harness has remembered acceptance.
|
||||
`posture[].nonInterruptive: "available"` says a full-bypass Claude/Codex seat can use
|
||||
[non-interruptive launch flags](non-interruptive-mode.md). It does not select the option, read host acceptance
|
||||
state or prove a prompt-free launch. Both fields are omitted where inapplicable or unresolved; older v1 views
|
||||
remain valid without them. Optional `posture[].nonInterruptiveDefault` (boolean) carries the rig's authored
|
||||
`non_interruptive` for those seats; an explicit launch choice overrides it. When it is `true`, `firstRunWarnings`
|
||||
is omitted. Pi has no additional warning-suppression flag. `nativeEffect` stays `unknown`.
|
||||
- An empty list means none are known. Something unknown goes in `unknownBeforeLaunch`.
|
||||
- Per-seat facts carry `seat` (`pod.member`) and human output labels them with that identity. It's required on
|
||||
`team` and `posture`. Elsewhere it's optional: bundle-wide facts (services, library and project writes, setup and
|
||||
version needs) omit it, and older generators may not have stated it.
|
||||
- **`not_generated`:** a combination without a generated view, or an archive the generator can't describe: schema 1, no
|
||||
readable pod-aware rig spec, a member missing its pod, id or agent reference, a runtime that depends on an AgentSpec
|
||||
the archive can't read, or an error while reading. It carries a reason and the local command that shows it, and no
|
||||
sections.
|
||||
|
||||
### Declared setup preconditions
|
||||
|
||||
The author's `bundle.yaml`, beside `rig.yaml`, may declare setup that the user must do before installing:
|
||||
|
||||
```yaml
|
||||
preconditions:
|
||||
- name: Run inside an OpenRig source clone with dependencies installed and packages built.
|
||||
commands:
|
||||
- git clone https://github.com/mvschwarz/openrig.git
|
||||
- cd openrig
|
||||
- npm ci
|
||||
- npm run build
|
||||
- name: Sign in to the selected coding runtimes before starting the team.
|
||||
```
|
||||
|
||||
Local-folder creation and GitHub-link imports carry these declarations into the archive manifest. A generated
|
||||
before-install view lists each as `needs[]` with `kind: precondition`, the author's `name`, optional ordered `commands`,
|
||||
`status: not_checked`, and a `sourceRefs` entry naming `bundle.yaml` and `preconditions[index]`. Inspect, plan and apply
|
||||
previews show the same declaration. A legacy schema-1 archive retains it in the manifest, but its view remains
|
||||
`not_generated`. Missing preconditions mean none were declared, not that no setup is needed.
|
||||
|
||||
Each command is one plain command line, without shell operators (`;`, `&&`, pipes, command substitution, backticks or
|
||||
redirection). Run them in order in **one shell**, then run the install command in that same shell: it inherits the
|
||||
working directory left by the setup commands. There is no separate `workdir` field. Commands are untrusted author data;
|
||||
a site building a copyable block must validate every line and bound the number of entries, omitting all setup lines
|
||||
if any fails its plain-command rule. OpenRig displays these declarations; it does not execute them, verify that setup
|
||||
was done, or add a prerequisite gate. The archive hash covers the manifest; `packageDigest` excludes `bundle.yaml`
|
||||
and therefore does not cover these declarations.
|
||||
|
||||
At creation, malformed preconditions produce a warning and the whole block is omitted; other author-field validation
|
||||
is unchanged. Shell operators in otherwise valid command lines produce a warning, but the declarations are retained
|
||||
as data. An already-built archive with malformed preconditions reports them as unknown before launch.
|
||||
|
||||
### Run records and status
|
||||
|
||||
**A run record's subject** is either a team (source, configuration ID, package digest, assembler) or a harness check (a
|
||||
runtime). A harness check never upgrades a team label.
|
||||
|
||||
**A run record's `environment.platform` and `arch`** are Node's `process.platform` (`linux`, `darwin` or `win32`) and
|
||||
`process.arch` (`x64` or `arm64`): the values a status platform key can carry, so every valid record can be labelled.
|
||||
|
||||
**Records are private.** Only `outcome.publicNote` (a short public cause) reaches the status file.
|
||||
|
||||
**Relations:** `supersedes`, `withdraws` and `resolves`. Reusing earlier evidence for a changed package is a reviewer's
|
||||
decision, recorded in the registry entry's `evidenceReuse`.
|
||||
|
||||
**The status file** is generated from records by rule `openrig.status-rule/v1`:
|
||||
- **Labels:** `known_problem`, `tested`, `tested_with_help`, `partly_tested`, `not_tested` and `status_unavailable`.
|
||||
They're shown as Known problem, Tested by OpenRig, Tested with help (N), Partly tested, Not tested by OpenRig
|
||||
and Status unavailable.
|
||||
- **A label belongs to one configuration ID.** No configuration is ever shown another configuration's label.
|
||||
- **Empty `platforms`** means Not tested by OpenRig. So does a custom configuration, one the listing doesn't offer.
|
||||
- **Status unavailable is only for a listed configuration** whose records can't be read, or that's missing from the
|
||||
status file; never Not tested by OpenRig. A listing whose records can't be read is `status_unavailable` as a whole.
|
||||
- **Platform keys** are `<os>-<arch>` (`linux`, `darwin` or `win32`; `x64` or `arm64`).
|
||||
- **`tested` means no help was needed:** it can't carry an assistance count above zero.
|
||||
- **An assist** is one action a person or the test runner took to get past a problem during the run, such as restarting a seat or re-running a command; following the bundle's own documented steps doesn't count.
|
||||
- **The file holds no private paths, host names, row IDs, account names or receipt text,** and regenerating it gives
|
||||
identical bytes.
|
||||
- **`bodyDigest`** is SHA-256, lowercase hex, over the RFC 8785 (JCS) canonical JSON, as UTF-8, of an object holding
|
||||
every other top-level field. The site import recomputes it and rejects a hand-edited file. `harnessChecks` has its own
|
||||
`state`, so an unreadable harness-check record reads Status unavailable too.
|
||||
|
||||
### Registry entries
|
||||
|
||||
Each entry records:
|
||||
- the source at its reviewed commit;
|
||||
- each offered configuration with its package digest, assembler and behaviour-view file;
|
||||
- optional `evidenceReuse`;
|
||||
- the review date, and `listed` or `withdrawn` (with `withdrawnOn`).
|
||||
|
||||
v1 has no author field: a listing shows the owner of `source.repository`.
|
||||
|
||||
A listing shows "Reviewed for listing on `<date>` at commit `<short>`. Review is not a security audit."
|
||||
@@ -50,19 +50,25 @@ Established chain names on the topology tree:
|
||||
- `LEARNED.md` — what a POSITION has learned; never shared or shipped as-is.
|
||||
A seat's LEARNED is identified by its PATH: the same seat name in two rigs
|
||||
is two different files.
|
||||
- `CULTURE.md` — norms layered into members at startup.
|
||||
- `CRAFT.md` — how work is done at that altitude (shipped at instance, rig and
|
||||
seat), with `ORCHESTRATION-CRAFT.md` at rig level for orchestration practice.
|
||||
- `CULTURE.md` — team norms. Note that startup delivers the rig spec's own
|
||||
`culture_file` (a path beside `rig.yaml`), not a `CULTURE.md` under
|
||||
`topology.root`.
|
||||
|
||||
### The project tree
|
||||
|
||||
Rooted at the workspace keys (`workspace.root`, `workspace.slices_root`).
|
||||
Chain names there: `SPEC.md` (intent in frontmatter composes up the chain;
|
||||
specification is a leaf property), `PROOF.md`, `PROGRESS.md`.
|
||||
specification is a leaf property), `PROOF.md`, `PROGRESS.md`. The walkers also
|
||||
read `NOTES.md` (or `MISSION_NOTES.md`), and fall back to `README.md` for a node
|
||||
without a `SPEC.md`.
|
||||
|
||||
**Project-tree context cannot ship.** It describes what *you* are building —
|
||||
mission intent, slice specifications, proof contracts. No vendor can author it
|
||||
in advance, and shipping a default would install someone else's project as
|
||||
your context. The demo project carries a worked example to copy from; real
|
||||
projects author their own. (Topology-tree context is the opposite: how a team
|
||||
your context. Real projects author their own; `rig scope mission create` and
|
||||
`rig scope slice create` start the tree. (Topology-tree context is the opposite: how a team
|
||||
of agents runs well is largely general, which is why included rigs ship
|
||||
defaults.)
|
||||
|
||||
@@ -70,8 +76,11 @@ defaults.)
|
||||
|
||||
Before this convention the topology tree lived at `~/.openrig/shared-docs/rigs/`
|
||||
(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
|
||||
path). That location **stays readable** as a per-level fallback for
|
||||
`rig context trace`, at the rig and seat levels, so existing rigs migrate instead
|
||||
of breaking. The refocus trace, `seat:` profile atoms and handover priming read
|
||||
only `topology.root`, so until migration they report those files as missing. Every
|
||||
read that resolves to the legacy location emits a
|
||||
named advisory (`legacy-topology-read: …`) stating the legacy source, the
|
||||
canonical destination under `topology.root`, and the config key. A silent
|
||||
legacy read is a defect. The legacy literal lives in exactly one helper per
|
||||
@@ -93,9 +102,9 @@ 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
|
||||
applicable instance, rig, pod, and seat altitudes, installed under
|
||||
`topology.root` at rig-up.
|
||||
Included rigs ship sensible-default chain files under a `topology/` folder,
|
||||
installed under `topology.root` at rig-up. Today only `factory` ships one,
|
||||
with instance, rig and seat defaults; the installer also supports pod defaults.
|
||||
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,8 +1,18 @@
|
||||
# Developing OpenRig — gates and lanes
|
||||
|
||||
This is the contributor-facing statement of which checks BLOCK a change and which are
|
||||
advisory. There is no external CI at this tip: the root `package.json` script chain IS
|
||||
the gate, and the release checklists invoke it.
|
||||
advisory. Every pull request to `main` runs `.github/workflows/tests.yml` on a clean GitHub
|
||||
runner: build and packaging, typecheck, the repository scripts (`npm run test:repo`), each
|
||||
workspace's test suite (daemon, cli, tui and ui) on macOS without ambient credentials or
|
||||
external network, and an installed-package scenario (`scripts/run-pr-scenarios.sh`). The root
|
||||
`package.json` scripts below run the same build, typecheck, repository and test checks locally,
|
||||
with one difference: `npm test` does not include the UI suite, which CI runs as
|
||||
`package-tests (ui)`. Run `npm run test:ui` for it.
|
||||
|
||||
**In a fresh clone or worktree, run `npm ci`, then `npm run build`,** as CI's build-and-package and
|
||||
package-test jobs do (typecheck and repo-checks build only the daemon, through their scripts; see
|
||||
`CONTRIBUTING.md` and [worktree builds](worktree-builds.md)).
|
||||
Locally, `npm test` builds only the daemon, and `test:workspaces` and `test:ui` build nothing.
|
||||
|
||||
## Blocking gates (must pass before a candidate moves)
|
||||
|
||||
@@ -10,23 +20,28 @@ the gate, and the release checklists invoke it.
|
||||
|---|---|---|
|
||||
| Typecheck | `npm run lint` | daemon + **ui** + cli + tui tsconfigs — UI typecheck STAYS blocking |
|
||||
| Build | `npm run build` | all workspaces — the UI dist ships in the package, so its build STAYS blocking |
|
||||
| Repo scripts | `npm run test:repo` | script self-tests, docs guard, skill mirror check |
|
||||
| Repo scripts | `npm run test:repo` | daemon build, script self-tests, first-run reference check, docs guard, skill mirror check, context-pack generation check |
|
||||
| Unit tests | `npm run test:workspaces` | `packages/daemon` + `packages/cli` + `packages/tui` |
|
||||
| UI unit tests | `npm run test:ui` | `packages/ui` vitest. NOT part of `npm test`, but every pull request runs it as `package-tests (ui)` (the `package-tests` matrix in `.github/workflows/tests.yml`) |
|
||||
| Installed-package scenario | CI only: `scripts/run-pr-scenarios.sh` | the installed package, a daemon restart and a seeded durability failure. The script refuses to run outside GitHub Actions unless given `--remote` with `DOCKER_HOST=ssh://…` |
|
||||
| Package file lists | CI only | records each package's published file list (`npm pack --dry-run`) |
|
||||
|
||||
`npm test` runs `test:repo` and `test:workspaces` — the blocking set is readable in the
|
||||
script itself.
|
||||
`npm test` runs `test:repo` and `test:workspaces`; `npm run test:ui` is the separate UI
|
||||
suite. Both sets are readable in the root `package.json` scripts.
|
||||
|
||||
## Advisory lane
|
||||
|
||||
| Lane | Command | Meaning |
|
||||
|---|---|---|
|
||||
| UI unit tests | `npm run test:ui` | runs `packages/ui` vitest at will; NOT part of `npm test` |
|
||||
| Portability report | `node scripts/portability-report.mjs` | lists added lines with machine-specific values; findings never fail it (`.github/workflows/portability-report.yml`) |
|
||||
|
||||
## The norm (web-UI freeze at 0.5.0)
|
||||
|
||||
Daemon API changes no longer require UI sync or UI verification; the contract mirrors
|
||||
under `packages/ui/src/hooks/` are no longer proactively maintained; a new `test:ui`
|
||||
failure signals a moved API contract, not a broken gate.
|
||||
Daemon API changes no longer require browser or interaction verification of the UI, and
|
||||
the contract mirrors under `packages/ui/src/hooks/` are no longer proactively maintained. A
|
||||
new `test:ui` failure usually signals a moved API contract. Because pull-request CI runs the
|
||||
UI suite as `package-tests (ui)`, the change that moves the contract has to update the affected
|
||||
UI test or mirror to pass that check.
|
||||
|
||||
Browser/interaction testing of the web UI is not a contributor gate. (The packaged
|
||||
starter-rig agent skills that exercise the UI are product content for user rigs, not
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# Edge Types Reference
|
||||
|
||||
Version: 0.2.0
|
||||
Last validated against code: 2026-04-11
|
||||
Source of truth: `packages/daemon/src/domain/rigspec-schema.ts`, `packages/daemon/src/domain/rigspec-instantiator.ts`
|
||||
Last validated against code: 2026-10-07, at the 0.6.6 cut `2620dea8` (whole document at `fcaf1f8e`; its sources unchanged in edge behaviour since then)
|
||||
Source of truth: `packages/daemon/src/domain/rigspec-schema.ts`, `packages/daemon/src/domain/rigspec-instantiator.ts`, `packages/daemon/src/domain/queue-owner.ts`
|
||||
|
||||
---
|
||||
|
||||
@@ -17,17 +16,18 @@ See `docs/reference/rig-spec.md` for the YAML syntax.
|
||||
|
||||
## Edge Kinds
|
||||
|
||||
Five edge kinds are accepted by the validator:
|
||||
Five edge kinds are accepted by the pod-aware validator (the legacy flat-node format accepts only `delegates_to`,
|
||||
`spawned_by` and `can_observe`):
|
||||
|
||||
| Kind | Accepted | Has Runtime Behavior | Description |
|
||||
|------|----------|---------------------|-------------|
|
||||
| `delegates_to` | yes | **yes — affects launch order** | Source delegates work to target |
|
||||
| `spawned_by` | yes | **yes — affects launch order** | Target was spawned by source |
|
||||
| `delegates_to` | yes | **yes — launch order and queue escalation** | Source delegates work to target |
|
||||
| `spawned_by` | yes | **yes — affects launch order** | Source was spawned by target |
|
||||
| `can_observe` | yes | no | Source can observe target's output |
|
||||
| `collaborates_with` | yes | no | Peer collaboration |
|
||||
| `escalates_to` | yes | no | Source escalates to target |
|
||||
|
||||
## What Edges Actually Do Today (OpenRig 0.1.x)
|
||||
## What Edges Actually Do Today
|
||||
|
||||
### Launch ordering
|
||||
|
||||
@@ -36,10 +36,16 @@ Only `delegates_to` and `spawned_by` affect runtime behavior. They constrain the
|
||||
- **`delegates_to`**: source launches BEFORE target. The delegator must be up before the delegate.
|
||||
- **`spawned_by`**: target (parent) launches BEFORE source (child). The parent must be up before the child it spawned.
|
||||
|
||||
This ordering is enforced in both `PodRigInstantiator` (initial launch) and `RestoreOrchestrator` (restore from snapshot). The code uses topological sort over the dependency graph — if there's a cycle, instantiation fails.
|
||||
This ordering is enforced in both `PodRigInstantiator` (initial launch) and `RestoreOrchestrator` (restore from snapshot). The code uses topological sort over the dependency graph — if there's a cycle, instantiation fails with `cycle_error` before the rig is created. A `delegates_to` or `spawned_by` edge from a member to itself counts as a cycle. Restore uses the same sort but has no cycle check of its own.
|
||||
|
||||
All other edge kinds (`can_observe`, `collaborates_with`, `escalates_to`) do NOT constrain launch order.
|
||||
|
||||
### Queue escalation
|
||||
|
||||
`delegates_to` also names a seat's orchestrator for the queue: the source of the `delegates_to` edge into a seat. The
|
||||
wake ladder escalates an unacknowledged row to that orchestrator, and stuck-row findings and recovery go to it. A seat
|
||||
with no incoming `delegates_to` edge has no default orchestrator. `spawned_by` plays no part here.
|
||||
|
||||
### Graph visualization
|
||||
|
||||
All edges are rendered in the UI topology graph. They create visual connections between nodes, helping the operator understand the team structure. Pod-local edges appear within the pod group; cross-pod edges connect across groups.
|
||||
@@ -50,13 +56,15 @@ All edges appear in `rig whoami --json` output under `edges.outgoing` and `edges
|
||||
|
||||
### Attach hint heuristic
|
||||
|
||||
The post-command handoff (the "Attach:" line after `rig up` and `rig restore`) uses edges to prefer the orchestrator as the default attach target. The heuristic looks for the first node with `delegates_to` outgoing edges.
|
||||
The post-command handoff (the "Attach:" line after `rig up` and `rig restore`) doesn't use edges. It names the first
|
||||
node, in creation order, whose session is running, or else the first node with a session name.
|
||||
|
||||
## What Edges Do NOT Do Today
|
||||
|
||||
Edges do NOT currently:
|
||||
|
||||
- **Route messages** — `rig send` can target any session, regardless of edges
|
||||
- **Route messages** — `rig send` can target any session, regardless of edges (queue escalation, above, is the one
|
||||
daemon path that follows `delegates_to`)
|
||||
- **Enforce delegation** — an agent can communicate with any peer, not just its edge targets
|
||||
- **Control permissions** — there's no edge-based access control
|
||||
- **Affect transport** — `rig capture`, `rig broadcast`, etc. work based on session identity, not edge topology
|
||||
|
||||
+598
-135
@@ -1,64 +1,123 @@
|
||||
# Getting started: one useful change in your repository
|
||||
# Getting started: meet the kernel, then make one useful change
|
||||
|
||||
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.
|
||||
Install OpenRig, use a working provider login, and meet the kernel operator with
|
||||
the advisor and TUI beside it. No starter team is needed to reach that view.
|
||||
Tell the operator what you want to do, then choose a project team for a bounded
|
||||
change you can exercise. Reuse the accounts and terminal tools you already have.
|
||||
|
||||
You need Node.js 22 or 24 and tmux, on macOS or Linux. On a Mac with Apple
|
||||
After installation and sign-in, your agent opens the **OpenRig TUI and operator** for you. The path is `rig daemon start` (if stopped),
|
||||
then `rig terminal open saved:kernel --window`. The second command opens the
|
||||
desktop window itself. An agent can run it from its shell on the daemon's
|
||||
desktop; it does not need to type into your current terminal. See
|
||||
[Open the kernel conversations](#open-the-kernel-conversations).
|
||||
`rig tui` is the team dashboard; installation ends when you are talking to the
|
||||
operator in the OpenRig view.
|
||||
|
||||
## Before you start
|
||||
|
||||
You need Node.js 22 or 24 and tmux, on macOS or Linux. On Linux, the
|
||||
distribution's own Node.js can be older (Ubuntu 24.04's is 18); install a
|
||||
supported version with [nvm](https://github.com/nvm-sh/nvm) (`nvm install 22`)
|
||||
or NodeSource. 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.
|
||||
Windows isn't supported. On Windows, use WSL2: it's the Windows route OpenRig
|
||||
supports, though OpenRig's automated tests don't run on it yet. One user's
|
||||
[working setup](#wsl2-a-reported-working-setup) is below. Node 20 and
|
||||
odd-numbered releases (23, 25 and so on) are not supported; Node 26 and later
|
||||
even-numbered releases 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
|
||||
rules and prompts. A team seat with no permission policy, per-seat choice or
|
||||
(for Codex) named profile also gets a per-launch
|
||||
[team default](rig-spec.md#team-launch-defaults): Claude runs ordinary `rig`
|
||||
commands, project reads and common tests without prompting, while lifecycle
|
||||
commands such as `rig up` and `rig down` still ask; Codex also gets the OpenRig
|
||||
workspace root and its pod's state directory as writable directories. The
|
||||
kernel's own seats get a wider operational default. Codex's
|
||||
sandbox normally blocks network access, including the local OpenRig daemon, so
|
||||
before that plain launch OpenRig asks Codex for its own configuration and adds
|
||||
network access inside the sandbox only when Codex answers that no configuration
|
||||
layer sets `sandbox_workspace_write.network_access` and no managed requirement
|
||||
could restrict it; if Codex can't be asked or doesn't answer in time, the launch
|
||||
is left unchanged. To keep it off, set `network_access = false` under
|
||||
`[sandbox_workspace_write]` in your Codex configuration. If Codex does not answer
|
||||
within a few seconds, the seat starts without it. 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.
|
||||
Agent-guided setup [recommends keeping the team default](#have-your-agent-configure-permissions)
|
||||
and offers to remember selected OpenRig commands in your native settings only if
|
||||
you want that. An existing explicit choice is reused; No or no answer keeps the
|
||||
team default and leaves settings unchanged. Broader access is separate.
|
||||
|
||||
If permission prompts are slowing you down, consider the
|
||||
[workshop bundle](https://openrig.dev/rigs/workshop). It ships with broad access
|
||||
and [non-interruptive mode](non-interruptive-mode.md): its agents bypass
|
||||
permission prompts and supported harness warning dialogs. The listing and
|
||||
before-install view explain that access before you choose it.
|
||||
|
||||
> 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.
|
||||
|
||||
## Prepare and launch
|
||||
## WSL2: a reported working setup
|
||||
|
||||
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.
|
||||
WSL2 is the Windows route OpenRig supports. OpenRig's automated tests don't run on
|
||||
it yet; this is one user's working setup.
|
||||
[dajiaohuang](https://github.com/dajiaohuang)
|
||||
[ran OpenRig 0.6.6 under WSL2](https://github.com/mvschwarz/openrig/issues/88#issuecomment-6043925546)
|
||||
on Ubuntu 24.04, with Node.js 24.14.1, tmux 3.4, Codex CLI 0.161.0 and Pi
|
||||
(`@earendil-works/pi-coding-agent`) 1.0.4, headless, as the distribution's root
|
||||
user.
|
||||
|
||||
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.
|
||||
- **What worked**, with one Codex seat and one Pi seat: `rig doctor`, starting
|
||||
the daemon (`rig daemon start --no-kernel`), launching both seats, replies to
|
||||
`rig send` and `rig capture`, and stopping with `rig down --snapshot`
|
||||
followed by a warm restore with `rig up --existing`.
|
||||
- **What mattered:**
|
||||
- the install and the OpenRig instance on the Linux filesystem;
|
||||
- Linux-native Node.js and harnesses, not the Windows `npm` or `pi` that WSL
|
||||
inherits;
|
||||
- Pi credentials the managed seat can reach, because a global Pi login isn't
|
||||
shared with managed seats. This user put them in the seat's agent
|
||||
directory. For `openrouter`, `zai`, `kimi-coding` or `minimax`, a key in the
|
||||
daemon environment also works (`OPENROUTER_API_KEY`, `ZAI_API_KEY`,
|
||||
`KIMI_API_KEY` or `MINIMAX_API_KEY`): name it in
|
||||
`recovery.provider_auth_env_allowlist`, give the seat a `<provider>/<id>`
|
||||
model, then restart the daemon and relaunch the seat. Any other provider
|
||||
goes in the seat's agent directory;
|
||||
- the current `@earendil-works/pi-coding-agent` package in its own npm prefix,
|
||||
not the deprecated `@mariozechner` one.
|
||||
- **Not tested:** the kernel and operator, herdr, reboot, suspend and resume,
|
||||
cross-host operation and long-running work.
|
||||
|
||||
## Install and sign in
|
||||
|
||||
**Agents:** if you're setting OpenRig up for someone, load the `rigs` skill and follow it. It carries the install, the
|
||||
OpenRig view and the handoff to the operator as one procedure. From 0.6.7, OpenRig installs it as `rigs` in
|
||||
`~/.claude/skills` and `~/.agents/skills` when the daemon starts. Before that, add it with
|
||||
`npx skills add mvschwarz/openrig --skill rigs`, or read [`skills/rigs/SKILL.md`](../../skills/rigs/SKILL.md).
|
||||
|
||||
**One command, for the full setup.** It needs Node.js 22 or 24 with npm already installed. The preview prints what the
|
||||
script will do and changes nothing:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.6.8/scripts/install.sh | sh -s -- --dry-run
|
||||
```
|
||||
|
||||
The same command without `--dry-run`,
|
||||
`curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.6.8/scripts/install.sh | sh`, installs the
|
||||
latest published `@openrig/cli` with `npm install -g`,
|
||||
runs the Node.js and SQLite check, then `rig setup --dry-run` and `rig setup`. `rig setup` checks both Claude Code and
|
||||
Codex and may install a missing one, as described below. A failed step prints
|
||||
`FAILED [n/4] <command or check> (exit <code>)`. Where a provider isn't signed in yet, step 4 ends that way and
|
||||
`rig setup` lists each sign-in under "Some steps need attention". If the only remaining failures are provider
|
||||
sign-ins, the install steps finished: sign in to each selected provider as below, then continue at
|
||||
[Start the kernel](#start-the-kernel-and-check-its-state). To install only what the selected providers need, go step
|
||||
by step instead:
|
||||
|
||||
### Choose your providers
|
||||
|
||||
@@ -66,20 +125,7 @@ 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**:
|
||||
Install OpenRig (`npm install -g @openrig/cli`) and check `tmux -V`. With npm 11 or later, an `npm warn install-scripts` line for `@openrig/cli` is expected: only the postinstall Node.js and SQLite check was skipped, and `node "$(npm root -g)/@openrig/cli/scripts/check-abi.mjs"` runs it. 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.”
|
||||
@@ -87,42 +133,307 @@ Install OpenRig and check `tmux -V`. Check **only the selected providers**:
|
||||
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
|
||||
The other provider's CLI/login is optional. Herdr is installed by default unless
|
||||
you decline it; cmux remains 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.
|
||||
`rig setup` checks **both** harnesses and installs a missing one with npm, unless
|
||||
you pass the person's choice: `rig setup --providers claude` (or `codex`) leaves
|
||||
the other out, so it reads not selected instead of failed. On
|
||||
macOS it uses an existing Homebrew (it does not install Homebrew) to install a
|
||||
missing tmux (elsewhere tmux is checked, not installed). Herdr installs on
|
||||
macOS and Linux; pass `--no-herdr` to decline it. Setup also
|
||||
writes an OpenRig-managed block (mouse on, a longer history) into `~/.tmux.conf`.
|
||||
It is optional for this selected-provider path, not a requirement to fix an unused
|
||||
provider.
|
||||
|
||||
### Kernel startup stays automatic
|
||||
## Start the kernel and check its state
|
||||
|
||||
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.
|
||||
With a working selected login, start the daemon if it is stopped, then read its
|
||||
state and the kernel seats:
|
||||
|
||||
### Launch the two project seats
|
||||
```sh
|
||||
rig daemon start # only if the daemon is stopped
|
||||
rig status
|
||||
rig ps --nodes --rig kernel
|
||||
```
|
||||
|
||||
Set `starter` to the chosen name from the table; the existing Codex route remains:
|
||||
Starting the kernel, including its operator and advisor, is part of installing
|
||||
OpenRig. Keep it for a normal install: it helps you choose and start your project
|
||||
team. `rig daemon start --no-kernel` is for automation or an explicit request to
|
||||
omit the kernel; it leaves you without that operator.
|
||||
|
||||
On a fresh instance, explicit daemon startup chooses the kernel variant from
|
||||
successful native auth probes: Claude alone, Codex alone, or both. It starts the
|
||||
kernel in the background without a project starter. An absent unused provider is
|
||||
fine; if both accounts are authenticated, the kernel uses both even when your
|
||||
later project uses just one. A request to use only one provider for everything
|
||||
is a separate choice.
|
||||
|
||||
**Started is not ready.** Read the `Kernel:` boot state in `rig status` and the
|
||||
individual seat state in `rig ps --nodes --rig kernel`. The view may open while
|
||||
agents finish starting; describe that state, without claiming they are ready.
|
||||
A missing or unknown readiness signal is not proof of readiness. Follow
|
||||
[Incomplete setup and restart](#incomplete-setup-and-restart) for login, trust,
|
||||
startup or missing-seat problems.
|
||||
|
||||
`rig setup` itself does not start the daemon. Preserve a daemon and kernel that
|
||||
already exist: ordinary daemon startup skips a managed kernel, and
|
||||
`OPENRIG_NO_KERNEL=1` skips automatic kernel startup. If neither runtime is
|
||||
authenticated, kernel boot reports `auth_blocked`. Do not restart or recreate an
|
||||
existing kernel merely to open its view. The startup TUI (`rig`) offers its own
|
||||
kernel setup and individual-seat recovery; it does not automatically boot agents
|
||||
when it starts the daemon.
|
||||
|
||||
## Open the kernel conversations
|
||||
|
||||
This is the **OpenRig view**, in a new terminal window or tab.
|
||||
Below 120 measured columns, the operator fills the first page; from 120, the dashboard and operator share it equally.
|
||||
The advisor always has a separate tab or tmux window.
|
||||
**Agents:** the `rigs` skill says when to open it, what to check, what to do when it can't open, and how to hand the
|
||||
person's goal to the operator; [Install and sign in](#install-and-sign-in) says how to load it.
|
||||
|
||||
`rig setup` installs Herdr by default on macOS and Linux. If the person declines
|
||||
Herdr, use `rig setup --no-herdr`; the OpenRig view can use plain tmux. Setup
|
||||
checks both PATH and the installer's default `~/.local/bin/herdr` location. It
|
||||
leaves existing cmux settings alone and does not open a view during setup.
|
||||
|
||||
On a Mac, the installing agent also asks once, "Install Ghostty for the OpenRig
|
||||
view?" On Yes it reruns setup with `--ghostty`, preserving earlier choices such
|
||||
as `--no-herdr` and `--providers`. This installs the
|
||||
[documented Homebrew cask](https://ghostty.org/docs/install/binary) and checks for
|
||||
the app. On No it uses `--no-ghostty`; Terminal.app remains available. Plain setup
|
||||
reports this offer without installing Ghostty or waiting for input. The person
|
||||
types no command.
|
||||
|
||||
This command opens it in a new terminal tab or window itself, so nobody copies or types commands. Over SSH or
|
||||
without a local display it can't, and the [headless handoff](#headless-or-ssh-handoff) applies:
|
||||
|
||||
```sh
|
||||
rig terminal open saved:kernel --window --json
|
||||
```
|
||||
|
||||
Run this on the daemon's desktop, as the same user. On macOS, the command
|
||||
opens a new tab or window in the hosting Ghostty (1.3 or newer), or a new
|
||||
window when hosted by Terminal. For Claude Desktop, iTerm or VS Code, it checks
|
||||
for a local macOS desktop session and opens a new Ghostty window if its scripting
|
||||
support is confirmed (1.3 or newer), otherwise Terminal.app with an explanation.
|
||||
That window uses the app's own profile defaults because
|
||||
there is no hosting window to copy. Before running the command, the agent should say that clicking **Allow**
|
||||
on macOS's one-time control prompt is fine: it lets OpenRig open the requested
|
||||
OpenRig view. A denied or uncertain action is reported once, not replayed in
|
||||
another app. If macOS remembers a denial, enable the calling app in System Settings
|
||||
→ Privacy & Security → Automation, or use the printed command in a terminal yourself.
|
||||
Unknown width keeps the operator-only layout.
|
||||
|
||||
When the agent already runs inside Herdr, the command focuses a space with the
|
||||
same view and plan, or creates one if none matches, preserving its config and existing spaces. The
|
||||
caller's Herdr endpoint must match the daemon's endpoint; a mismatch is reported
|
||||
without opening a window or putting the view in another session.
|
||||
|
||||
On Linux, a local graphical display and a supported hosting terminal are required
|
||||
(Ghostty, GNOME Terminal, Konsole or xterm). Linux reports a window request; verify
|
||||
what actually appeared. SSH, headless, CI and unsupported Linux hosts get a
|
||||
no-window result with the reason, a command for the person to run, and any
|
||||
follow-up the agent needs to perform.
|
||||
|
||||
OpenRig's Herdr launch and its printed manual command keep your personal Herdr
|
||||
config when present. If it is absent, OpenRig creates private sidebar defaults;
|
||||
the new desktop terminal selects them from its measured width. The manual command
|
||||
uses the collapsed default. If private settings cannot be prepared, OpenRig
|
||||
reports why and keeps the ordinary launch or manual command with Herdr's usual settings.
|
||||
|
||||
The command runs Herdr when installed, using the daemon's configured session and
|
||||
socket. Otherwise it creates a plain tmux viewing session. Explicitly choose the
|
||||
latter with `--provider tmux --window`. Neither route types into or replaces the
|
||||
installing agent's terminal. Existing conversations continue in their original
|
||||
sessions.
|
||||
|
||||
Opening the same Herdr view with the same current plan selects its existing
|
||||
OpenRig workspace in the new window, preserving its tabs and contents. The receipt
|
||||
reports reuse and any currently absent or degraded seats; it does not claim to
|
||||
refresh the layout or create new tiles. Changed membership or attachment targets
|
||||
create a fresh workspace. A note identifies older workspaces and gives the command
|
||||
to close them after inspection; nothing is closed automatically. If the workspace
|
||||
inventory cannot be read, the command creates a fresh workspace and reports the
|
||||
inventory problem.
|
||||
|
||||
Terminal.app copies the person's front-window bounds to the new view without
|
||||
changing the original. Ghostty keeps the current window size when adding a tab.
|
||||
OpenRig keeps an existing personal Herdr config unchanged. Only when that config
|
||||
is absent, its private default starts the sidebar open at 160 columns or more,
|
||||
and collapsed below that width. Unknown widths use the collapsed default.
|
||||
|
||||
### Confirm the view
|
||||
|
||||
For Claude-only, Codex-only and mixed kernels, the default `saved:kernel` layout
|
||||
uses the viewing terminal's measured width. Below 120 columns (or when width is
|
||||
unknown), **the operator** fills the first page, followed by dashboard and advisor
|
||||
pages. From 120 columns, dashboard and operator share the first page equally, with
|
||||
the advisor on a separate page. Select a Herdr tab to switch; in plain tmux, press
|
||||
**Ctrl-b, then w** and choose a `view-*` window; `view-1` is the OpenRig view's first page.
|
||||
Missing roles are reported rather than filled with another conversation. The queue worker remains
|
||||
reachable through the dashboard. The composition uses the installed kernel's current
|
||||
bindings; no YAML edit, seat launch or daemon restart is needed. A custom saved
|
||||
view named `kernel` still takes precedence.
|
||||
|
||||
Inspect `opened`, `absent`, `degraded`, `window` and any notes. Confirm the new
|
||||
surface visibly shows the operator, with the dashboard and advisor reachable,
|
||||
and that the original terminal remains intact. The person sees Herdr only in a terminal they can see.
|
||||
Creating its workspace or switching the shared TUI to `:terminals` does not open
|
||||
that terminal. A created window or successful CLI response alone is not visual
|
||||
confirmation. A partial view remains partial. If the shared TUI tile shows a
|
||||
shell, the installing agent starts `rig tui` in that new tile.
|
||||
|
||||
Opening the view can happen while the kernel finishes starting. Report its actual
|
||||
state; do not start or restore seats just to obtain a view. If the kernel is absent
|
||||
or blocked, follow [Incomplete setup and restart](#incomplete-setup-and-restart).
|
||||
|
||||
### What can interrupt installation and the OpenRig view
|
||||
|
||||
For an installing agent, a blocked step needs an explanation, not silence. Name
|
||||
the command, the reported reason and what remains unfinished. These checks cover
|
||||
different points in the same journey:
|
||||
|
||||
| What you see | Why and what to do next |
|
||||
| --- | --- |
|
||||
| Codex offers to switch to a cheaper model near a rate limit | This is Codex's own menu. Ask the person to choose in the seat's terminal; do not silently change the team's configured model. OpenRig disables Codex's startup update check for managed launches, but does not answer this model-choice menu. |
|
||||
| A missing Node.js, npm or tmux; an install download or command fails | The named prerequisite or install step did not finish. Read its error and the [platform and provider instructions](#install-and-sign-in), resolve that step, then recheck it. `FAILED [4/4]` with only selected-provider sign-ins outstanding means the package installed but those logins still need the person. |
|
||||
| The installing harness refuses a tool call or asks for permission | Its native rules, sandbox or automatic permission decision apply before OpenRig can run. Claude Code's [auto mode can deny calls](https://code.claude.com/docs/en/auto-mode-config) as well as approve them. Show the specific refusal and ask the person to review it in the harness permission controls or run that named step themselves. Keep their chosen restrictions; a refused call does not prove the OpenRig command failed. |
|
||||
| Herdr or Ghostty installation is unavailable or declined | Herdr is optional for the view: `--window` uses plain tmux when Herdr is absent, and its install failure is a warning. On macOS, Terminal.app is the fallback when supported Ghostty is absent. An explicitly requested Ghostty install can fail setup; resolve that install or decline it with `--no-ghostty`, preserving earlier choices. Address separate required-tool or login failures. Existing cmux remains supported. |
|
||||
| macOS asks for Automation access, or the window action is denied | The command asks the desktop terminal app to open a new surface. Let the person answer the system prompt. Read the error and inspect any new window before retrying an uncertain action; do not silently switch apps and open a duplicate. |
|
||||
| No local display, or the configured daemon is remote | `--window` needs the daemon's desktop. An SSH shell or remote daemon address alone does not give the agent that desktop. Explain the limit and use the [manual](#talk-to-the-operator-in-any-terminal) or [SSH handoff](#headless-or-ssh-handoff) with the actual operator binding. |
|
||||
| Herdr shows its introduction or agent-integration panel | These are Herdr's first-run panels. Follow the displayed controls: Return continues the intro and Esc closes the integration panel. Opening this view does not require installing Herdr's agent integrations or changing your Claude/Codex settings. |
|
||||
| A saved layout is cramped in an 80×24 window | The default kernel view gives each role a full-width page. A custom saved `kernel` view still controls its own layout; adjust that saved view if needed. Changing or restarting the team is unnecessary. |
|
||||
| The command returned, but visibility is unconfirmed | With authorized desktop tools, inspect the new window and a screenshot of its contents. A window listing alone proves only that a window exists. If the agent has only shell access, report what the command confirmed and ask the person to confirm the visible operator and that the dashboard and advisor tabs or windows are available. Do not report visual success without seeing it. |
|
||||
|
||||
After a failure, inspect any newly opened surface before retrying. A tmux failure
|
||||
may name a newly created viewing session for inspection. Do not replace a failed
|
||||
local desktop action with commands for the person to copy; finish or explain the
|
||||
failed action. Close only the viewing window, or use **Ctrl-b, then d** in plain
|
||||
tmux, to leave the underlying conversations running.
|
||||
|
||||
### Existing provider workspaces
|
||||
|
||||
The existing provider-only commands remain available when a terminal is already
|
||||
open. They create a workspace inside that provider, without opening an OS window:
|
||||
|
||||
```sh
|
||||
rig terminal views --json
|
||||
rig terminal open saved:kernel --provider herdr --json
|
||||
rig terminal open saved:kernel --provider cmux --json
|
||||
```
|
||||
|
||||
Use `rig terminal status --json` to inspect provider availability and liveness.
|
||||
For the first desktop handoff, use `--window`. Only if the window cannot open,
|
||||
`rig tui --shared` is the dashboard-only fallback, not the operator's conversation.
|
||||
|
||||
### Talk to the operator in any terminal
|
||||
|
||||
For an explicitly requested manual attachment, inspect the current bindings:
|
||||
|
||||
```sh
|
||||
rig ps --nodes --rig kernel --json --fields logicalId,canonicalSessionName,tmuxAttachCommand
|
||||
```
|
||||
|
||||
Find `logicalId: operator.agent` and run its `tmuxAttachCommand`, after `env -u TMUX`, in a new
|
||||
terminal on the same host and account:
|
||||
|
||||
```sh
|
||||
env -u TMUX <tmuxAttachCommand>
|
||||
```
|
||||
|
||||
When the daemon was started inside tmux on a non-default socket, its sessions live on that
|
||||
server, and `tmuxAttachCommand` names it (`tmux -L <name> …` or `tmux -S <path> …`). The installing
|
||||
agent fills in the actual command. This optional manual route
|
||||
is separate from the first-install desktop action above.
|
||||
|
||||
### Plain terminal: a new viewing session
|
||||
|
||||
`rig terminal open saved:kernel --provider tmux --window --json` creates and opens
|
||||
the same width-based OpenRig view layout, with the advisor in a separate window. It reuses
|
||||
the daemon's composition and preserves existing sessions. The result names the viewing session. No pane names or shell
|
||||
commands need to be assembled by the person.
|
||||
|
||||
### Headless or SSH handoff
|
||||
|
||||
A provider running on the server opens no window on the person's desktop. From a new terminal window or tab on their
|
||||
own machine, connect over SSH with the known host and account, then attach:
|
||||
- **the operator's conversation**, with the command in
|
||||
[Talk to the operator in any terminal](#talk-to-the-operator-in-any-terminal);
|
||||
- **the Herdr view**, with the command that `rig terminal open saved:kernel --window` prints when it can't open a
|
||||
window. It names the installed Herdr, wherever setup put it, and the daemon's socket. Then
|
||||
`rig terminal open saved:kernel --provider herdr --json` selects the view in it.
|
||||
|
||||
No new account or credential provisioning is part of this. **Agents:** the `rigs` skill gives the person this as one
|
||||
filled-in command.
|
||||
|
||||
### Installing-agent handoff
|
||||
|
||||
The kernel's operator takes the person's goal and project folder, helps them choose and start a team, and hands the
|
||||
goal to the team's lead. The person can type the goal in the operator's pane (attach with the command
|
||||
[above](#talk-to-the-operator-in-any-terminal)), or the agent that installed OpenRig sends it. The `rig tui --shared`
|
||||
fallback shows the dashboard, not this conversation. If the operator needs attention, use the
|
||||
[recovery routes](#incomplete-setup-and-restart).
|
||||
|
||||
**Agents:** the `rigs` skill's step 2 has the exact `rig send` to the operator and says when installation is finished.
|
||||
|
||||
Stopping OpenRig keeps a team's work, and the branch a team makes is your
|
||||
change: an agent tidying up says what's on it before offering to remove it. The
|
||||
operator starts a team when you ask for one in its conversation, so an agent
|
||||
reporting back checks `rig ps` or the team's tasks before saying a team started
|
||||
on its own.
|
||||
|
||||
## Choose your first team
|
||||
|
||||
Tell the kernel operator what you want to do. It asks about your goal, presents
|
||||
three teams with one recommendation, and fits the team to the providers you have.
|
||||
Review that choice before launching it.
|
||||
|
||||
`rig specs preview starter --kind rig` and the factory preview include the three
|
||||
choices and their uses, in text and JSON. The reminder respects a team you already
|
||||
picked; previewing a spec does not launch it.
|
||||
|
||||
| Team | Talk to | Agents and purpose |
|
||||
| --- | --- | --- |
|
||||
| `starter` | `dev-build@starter` | `dev-build` (Claude Code) and `dev-review` (Codex, pinned `gpt-6-astra`) for one bounded change |
|
||||
| `workshop` | `orch-lead@workshop` | A lead, builder, QA and reviewer for ongoing work in one repository. It's a rig bundle, not built in: the operator installs it from its pinned listing |
|
||||
| `factory` | `orch-lead@factory` | Seven: a lead, advisor, build, QA, design and two independent reviewers for sustained product work. It uses the most concurrent capacity |
|
||||
|
||||
As shipped, `starter` needs both Claude Code and Codex. With only one of them, the
|
||||
operator writes an adapted copy of the team for your providers and keeps its name;
|
||||
there are no per-provider variants. `first-project` is starter's old name and
|
||||
still starts it, so an old `rig up first-project` now starts a Claude builder and a
|
||||
Codex reviewer. If you already have a rig named `first-project` (or `starter`),
|
||||
`rig up first-project` refuses instead and names the `rig up <name> --existing`
|
||||
command that brings that rig back.
|
||||
|
||||
Claude seats use the native default model, as the Claude kernel does: OpenRig
|
||||
does not pass a model override. Read the selected harness's configured model and
|
||||
show it alongside the team 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.
|
||||
|
||||
### Launch the starter team
|
||||
|
||||
```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 specs preview starter --kind rig
|
||||
rig up starter --cwd . --plan
|
||||
rig up starter --cwd .
|
||||
rig status
|
||||
rig ps --nodes --rig "$starter"
|
||||
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.
|
||||
preflight for the working directory. The kernel is already your entry point; this step starts the chosen project
|
||||
team. Read readiness for the project seats, not only daemon health. Resolve a named authentication, trust or permission prompt before giving
|
||||
that seat work. When a seat stops at such a prompt, `rig up` reports
|
||||
`Status: partial`, prints `Startup attention (<seat>): <reason>` and exits 1;
|
||||
`rig ps --nodes` repeats it as "Startup details". The reason ends with the command
|
||||
to run once the prompt is answered: `rig seat continue <seat>`, which delivers the
|
||||
seat's startup context in the same conversation. 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
|
||||
@@ -133,38 +444,43 @@ 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
|
||||
the corresponding task and its history 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.
|
||||
not progress. If startup is still waiting for context delivery, use **c** (or
|
||||
`rig seat continue <seat>`) for the same occupant, then **r** to refresh. If the
|
||||
command times out, check `rig seat status <seat>` before trying again. 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.
|
||||
Starter is a deliberately small starting point. For a bigger team, `factory` is
|
||||
built in (seven agents, using more concurrent capacity), and so are the specialist
|
||||
teams `code-review`, `research` and `pm`. Inspect `rig specs ls --kind rig` and
|
||||
`rig specs preview <name> --kind rig` before selecting one (`pm` is also an agent
|
||||
spec's name, so a preview without `--kind rig` reports it as ambiguous). A team published on GitHub, such
|
||||
as workshop, installs from its folder link with `rig up <link>` (see
|
||||
[publishing a rig bundle](publishing-a-rig-bundle.md)).
|
||||
|
||||
## Give the owner an outcome
|
||||
## Give the builder 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.'
|
||||
rig send dev-build@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-review 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
|
||||
user should observe, a boundary and how success can be checked. The builder
|
||||
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
|
||||
Seat addresses carry the rig name (for example, `dev-build@starter`). 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.
|
||||
`rig queue list --destination dev-build@starter --limit 1000` and the same
|
||||
command for `dev-review@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
|
||||
@@ -173,9 +489,13 @@ Read the artifact, exercise its behavior, and check the candidate reviewed.
|
||||
|
||||
## Share the dashboard and return to it
|
||||
|
||||
```sh
|
||||
rig tui --shared
|
||||
```
|
||||
After install, and whenever the person wants their agents again, the installing agent runs
|
||||
`rig terminal open saved:kernel --window` and checks the result. It does not
|
||||
finish by suggesting a dashboard command for the person to type.
|
||||
|
||||
**Only if that window cannot open**, `rig tui --shared` is the dashboard-only
|
||||
fallback. Explain the window failure and help with this fallback if chosen;
|
||||
it does not show the advisor or operator conversation.
|
||||
|
||||
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**
|
||||
@@ -190,19 +510,31 @@ 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
|
||||
Herdr, use `rig terminal open starter --provider herdr`; for the shared
|
||||
dashboard together with the kernel conversations, use the
|
||||
[saved first view](#open-the-kernel-conversations). 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.
|
||||
|
||||
For team views with interactive panes, the open result distinguishes the dashboard
|
||||
overview from the team's lead conversation and asks whether you can see the team.
|
||||
This reminder is omitted from the default kernel view and entirely read-only watch
|
||||
views. If the provider cannot open, each labelled fallback command joins a different
|
||||
seat. Keep all returned commands
|
||||
complete when sharing them; a created workspace alone does not confirm visibility.
|
||||
|
||||
## 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.
|
||||
`rig context work-install` lists what the project declares (intent, context files,
|
||||
skills). Without `--project` it picks the project by the seat's rig, then by the
|
||||
working directory, and stops with `project_required` and the exact `--project`
|
||||
commands only if several still match.
|
||||
|
||||
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
|
||||
@@ -224,26 +556,127 @@ 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.
|
||||
|
||||
## Stop your teams
|
||||
|
||||
`rig down <rig-name>` ends that rig's agent sessions and work in progress.
|
||||
Read `rig ps --nodes --rig <rig-name>` first so you can see who will stop;
|
||||
`rig down` also prints the rig's recorded agent sessions and their activity
|
||||
before stopping them.
|
||||
For example, to stop starter:
|
||||
|
||||
```sh
|
||||
rig down starter
|
||||
```
|
||||
|
||||
For "stop everything", list the rigs with `rig ps --json` and run `rig down`
|
||||
for each one you want stopped. Include `kernel` last if you want its operator,
|
||||
advisor and queue worker stopped too; its operator cannot continue helping
|
||||
after its own session ends. Check each result before calling the shutdown done.
|
||||
|
||||
`rig daemon stop` stops only the background service and preserves agent tmux
|
||||
sessions. If you also want that service stopped, run it after stopping the rigs.
|
||||
If the operator is handling this full shutdown and kernel is included, it gives you this
|
||||
last step before running `rig down kernel`: once kernel is down, run
|
||||
`rig daemon stop` in your own shell and check the result. The operator's session
|
||||
ends with kernel, so it cannot run that final command for you afterwards.
|
||||
If the daemon is already stopped, use `rig daemon start --no-kernel` to restore
|
||||
the lifecycle API without booting a new kernel, then stop the remaining rigs.
|
||||
|
||||
## 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. |
|
||||
| "Startup attention" or "Startup details" names a prompt | Answer the login, trust or bypass prompt in that seat's terminal, then run the `rig seat continue <seat>` it shows. |
|
||||
| "Readiness timeout after 30s — harness did not become interactive" | The harness took longer than the readiness window. Raise it for the next launch with `rig config set runtime.readiness_timeout_seconds <1-600>`, then relaunch that seat. |
|
||||
| Daemon is healthy, kernel is still starting | Read `rig status` and `rig ps --nodes --rig kernel`; kernel readiness is separate. |
|
||||
| Kernel seats fail within a second of the first start, and tmux reports "server exited unexpectedly" | Check that your tmux can capture a pane, then start the failed seats again; see the tmux check below this table. |
|
||||
| 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. |
|
||||
| Viewing terminal was closed | Open the conversations again with `rig terminal open saved:kernel --window`; `rig tui --shared` is only the dashboard fallback if that window cannot open. 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. |
|
||||
| Host reboot lost tmux sessions | Open `rig`, start the daemon if needed (press **S** if it opens on the work views), 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. |
|
||||
|
||||
**When the kernel's seats all fail at the first start.** A report on Oracle Linux 10
|
||||
([#980](https://github.com/mvschwarz/openrig/issues/980)) traced this to the distribution's packaged tmux, whose
|
||||
`tmux -V` prints `next-3.4`: its server exited on every `capture-pane -p`, also outside OpenRig. To check your tmux on
|
||||
a throwaway socket, without touching your own sessions:
|
||||
|
||||
```sh
|
||||
tmux -L "openrig-check-$$" -f /dev/null new-session -d -s check
|
||||
tmux -L "openrig-check-$$" capture-pane -p -t check >/dev/null && echo "capture-pane works"
|
||||
tmux -L "openrig-check-$$" kill-server
|
||||
```
|
||||
|
||||
If the second command fails because the server exited, put a tmux that passes this check first on your `PATH`; the
|
||||
reporter built tmux 3.5a into `/usr/local/bin`. Then, from a shell where `tmux -V` shows that tmux, restart the daemon
|
||||
and start the failed seats again, as the reporter did:
|
||||
|
||||
```sh
|
||||
rig daemon stop
|
||||
rig start --last
|
||||
rig ps --nodes --rig kernel --full # the failed seats show "failed" under STARTUP
|
||||
rig up kernel --existing --fresh <failed seats> --yes
|
||||
```
|
||||
|
||||
This recovery is for the case the reporter hit: the kernel's seats failed and none of them is still running.
|
||||
`rig up kernel --existing` refuses a kernel that still has a live session. Name the failed seats by their logical
|
||||
IDs, for example `advisor.lead operator.agent operator.human queue.worker` when all four failed. `--fresh` starts a
|
||||
new conversation for each seat you name. Seats you don't name follow their normal recovery policy and may remain
|
||||
awaiting a decision if their old conversation can't be resumed. A later `rig daemon start` doesn't retry the failed
|
||||
seats by itself: once a kernel exists, startup skips the built-in kernel boot, and `rig status` then reads
|
||||
`Kernel: skipped` whatever its seats' state, so check `rig ps --nodes --rig kernel --full`.
|
||||
|
||||
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.
|
||||
`rig setup` prints the short form of this path after ready, incomplete and dry-run
|
||||
results; JSON output includes it as `nextSteps`. This guidance does not mean the
|
||||
kernel is ready: retain any named failures, check the actual seat state, then
|
||||
offer `rig terminal open saved:kernel --window`. If the person chooses a manual
|
||||
attachment after a failure, derive the exact command from the current operator
|
||||
binding [above](#talk-to-the-operator-in-any-terminal); not every failure prints
|
||||
one. Hand the goal and project folder to the ready
|
||||
operator rather than implementing the project during installation. `rig status`
|
||||
points back here while no rigs are registered.
|
||||
|
||||
### Use the startup and work TUI
|
||||
|
||||
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. Once connected, the TUI opens the ordinary work
|
||||
views by itself unless you have already pressed a key; **S** returns to startup.
|
||||
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.
|
||||
|
||||
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 (from a shell, `rig seat continue <seat>` does the
|
||||
same). **r** reads actual state again; **d** expands details.
|
||||
|
||||
## Kernel framing (what `rig setup` does and does not do)
|
||||
|
||||
@@ -252,7 +685,7 @@ 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
|
||||
- Starting the daemon (`rig daemon start`, or implicitly via `rig up` or `rig context add`) 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.
|
||||
@@ -276,7 +709,7 @@ Two related primitives, often confused by new operators:
|
||||
|
||||
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
|
||||
instantiate a named workflow with `rig workflow instantiate <name> --root-objective <text> --created-by <session>` (both options are needed), 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)
|
||||
@@ -284,22 +717,26 @@ 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:
|
||||
For a team with no permission policy, your agent recommends keeping the
|
||||
[team default](rig-spec.md#team-launch-defaults), unless you already made an
|
||||
explicit choice for these harnesses and this scope. It offers additional
|
||||
remembered allowances only if you want them:
|
||||
|
||||
> Allow your agents to run OpenRig commands without repeated permission prompts?
|
||||
> **Yes — recommended** / **No — keep prompts**
|
||||
> Remember these selected OpenRig commands in your native settings for this
|
||||
> project? This is separate from the team launch default; stricter rules and
|
||||
> Claude lifecycle asks remain. **Yes / No — keep the team default**
|
||||
|
||||
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.
|
||||
A remembered allowance can cover the whole `rig` family, but on Claude team
|
||||
seats lifecycle commands such as `rig up` and `rig down` still ask. 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
|
||||
settings alone and keeps the team default. 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
|
||||
@@ -334,18 +771,18 @@ 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
|
||||
starter, run `rig specs show starter --kind rig` and find its `Path`, ending
|
||||
in `specs/rigs/launch/starter/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
|
||||
The examples below use `./openrig-specs/rigs/launch/starter/rig.yaml`. If you
|
||||
already have a running `starter`, 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
|
||||
`~/.codex/starter-permissive.config.toml` (under `CODEX_HOME` instead if you
|
||||
set it for the daemon's launch environment):
|
||||
|
||||
```toml
|
||||
@@ -353,23 +790,23 @@ 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`:
|
||||
In the copied rig, add this field to **each Codex member** that should use it (in
|
||||
starter, that's `dev.review`); keep the member's existing `profile: default`:
|
||||
|
||||
```yaml
|
||||
codex_config_profile: first-project-permissive
|
||||
codex_config_profile: starter-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
|
||||
override. OpenRig then passes `-p starter-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 .
|
||||
rig policy current --spec ./openrig-specs/rigs/launch/starter/rig.yaml
|
||||
rig up ./openrig-specs/rigs/launch/starter/rig.yaml --cwd . --plan
|
||||
rig up ./openrig-specs/rigs/launch/starter/rig.yaml --cwd .
|
||||
```
|
||||
|
||||
OpenRig's `permission_policy: builtin:yolo` setting selects
|
||||
@@ -378,6 +815,12 @@ 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.
|
||||
At full access, Codex can show its full-access and GPT-5.1 migration notices at
|
||||
first launch. When OpenRig itself selects full bypass for the seat (`builtin:yolo`,
|
||||
a `full_bypass` flag policy, or an explicit seat `full_bypass`), `rig up <spec> --non-interruptive` hides them
|
||||
with per-launch `-c` overrides (see [non-interruptive mode](non-interruptive-mode.md)).
|
||||
Full access chosen inside a native Codex profile, with `permission_policy` absent or
|
||||
`none`, doesn't qualify.
|
||||
|
||||
To return to a restricted next launch, change the selected profile to:
|
||||
|
||||
@@ -395,8 +838,8 @@ 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
|
||||
rig seat set-permissions dev-build@starter --mode full_bypass --reason "Operator selected broader access"
|
||||
rig seat status dev-build@starter --json
|
||||
```
|
||||
|
||||
This records the actor, reason and old/new choice on that seat. It does not
|
||||
@@ -405,7 +848,9 @@ 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.
|
||||
rewrite a native profile or force its approval settings. An explicit `floor`
|
||||
also replaces any team or kernel launch default with the plain floor flags;
|
||||
`inherit` brings the default back.
|
||||
|
||||
Codex and Claude accept `floor` and `full_bypass`. Additional Claude native modes,
|
||||
including `auto`, require support advertised by the managed executable's help.
|
||||
@@ -442,10 +887,13 @@ typing guard are separate controls.
|
||||
|
||||
### Claude Code: a different launch flag
|
||||
|
||||
The shipped `first-project` uses Codex. For a user-owned **Claude Code** rig,
|
||||
In the shipped `starter`, `dev-build` runs Claude Code. For a **Claude Code** seat,
|
||||
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:
|
||||
other actions follow native rules and prompts. For a seat with no permission
|
||||
policy or per-seat choice it also passes the per-launch team default (`--settings` allowing ordinary
|
||||
`rig` commands, project reads and common tests, with a session hook asking for
|
||||
lifecycle commands while allowing literal help forms such as `rig down --help`);
|
||||
personal and project rules still apply. Nothing is written to your settings files. To explicitly select the bypass launch flag for that rig:
|
||||
|
||||
```sh
|
||||
rig policy apply yolo --spec ./my-claude-rig/rig.yaml
|
||||
@@ -453,9 +901,24 @@ 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
|
||||
`--dangerously-skip-permissions`. Member-level policies take precedence.
|
||||
|
||||
On a machine where nobody has accepted it yet, Claude Code then shows its
|
||||
bypass-permissions warning in each new seat, and OpenRig reports the seat as
|
||||
needing attention without sending its startup context. Either accept the warning
|
||||
once beforehand (run `claude --dangerously-skip-permissions` in your repository,
|
||||
accept, then `/exit`; Claude Code remembers it), or accept it in the seat and run
|
||||
`rig seat continue <seat>`. Or launch with `rig up <spec> --non-interruptive`,
|
||||
which accepts it with a launch flag and writes nothing to your settings; see
|
||||
[non-interruptive mode](non-interruptive-mode.md).
|
||||
|
||||
`rig policy apply auto --spec ./my-claude-rig/rig.yaml` (also `rig setup --policy
|
||||
auto --spec ./my-claude-rig/rig.yaml`) records `builtin:auto` instead: Claude seats launch with
|
||||
`--permission-mode auto`, and Codex and Pi seats, which have no auto mode, launch
|
||||
at the floor. To return future launches to the team default, remove
|
||||
`permission_policy` from the spec along with any member-level bypass override.
|
||||
`rig policy apply none --spec ./my-claude-rig/rig.yaml` instead records plain
|
||||
`acceptEdits` without the team allowances. 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).
|
||||
|
||||
|
||||
@@ -115,9 +115,12 @@ 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,
|
||||
no outcomes and names the missing fact in `missingFacts`. `established` needs
|
||||
`boundedAuthority` set to true or false (not null) and no missing facts; while an
|
||||
established census still confirms the signal, a `false positive`, `insufficient
|
||||
evidence` or `resolved` verdict is refused. Required references must resolve
|
||||
inside the finding's resolved project folder (the configured workspace when no
|
||||
project resolves); 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.
|
||||
@@ -131,10 +134,15 @@ 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
|
||||
This bounded source reads every handoff family touched in the window; only
|
||||
explicitly linked project or mission work resolves a scope. Its bounds: at most 2,000 touched
|
||||
qitems, 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
|
||||
refuses visibly. When more than 200 families (roots) were touched, it evaluates the
|
||||
200 with the most transitions in the window, then by lineage ID, and reports the
|
||||
result as partial coverage with the total and omitted counts. Omitted families
|
||||
were not evaluated, so having no finding for them is not a healthy verdict.
|
||||
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.
|
||||
@@ -370,7 +378,12 @@ 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
|
||||
For a source that bounds its input, list metadata also carries `coverage`
|
||||
(`source`, `evaluatedAt`, `unit`, `total`, `evaluated`, `omitted`, `limit`,
|
||||
`order`, `partial`). A source that failed or hit a limit appears instead as
|
||||
`status: "unavailable"` with its `reason`, and its findings are left out.
|
||||
`rig health policy` reports the scheduled evaluation's coverage under
|
||||
`engine.lastEvaluation.coverage`. 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
|
||||
@@ -392,7 +405,9 @@ 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
|
||||
Untagged work is still read: a family whose scope can't be resolved shows as an
|
||||
indeterminate instance-scope finding that names the unresolved scope and can't
|
||||
start a diagnosis. 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.
|
||||
|
||||
+74
-14
@@ -1,13 +1,14 @@
|
||||
# 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
|
||||
copy installed with OpenRig matches that installed version. Read it with `rig context get help`, which needs a running
|
||||
daemon. If `rig` won't run or the daemon is down, 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/`.
|
||||
shown beside its link where there is one; every linked file also sits 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.
|
||||
@@ -23,7 +24,10 @@ 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.
|
||||
instructions to reset the machine. `rig doctor` checks Claude and Codex authentication using the same local checks
|
||||
as setup, including a configured Codex provider credential variable. A set variable does not prove that the provider accepts it or that managed
|
||||
seats receive it. Doctor does not run an agent task: also inspect `rig ps --nodes --rig <rig>`. If the daemon is involved, `rig daemon status` and `rig daemon logs` show its state
|
||||
and recent output.
|
||||
|
||||
**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
|
||||
@@ -32,17 +36,58 @@ 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.
|
||||
contain changes that haven't reached your user's version. After an upgrade, start with the short note on what changed
|
||||
in the installed version: `rig context get reference/whats-new.md`. For full release notes and known limitations, open
|
||||
`https://github.com/mvschwarz/openrig/blob/v<version>/docs/releases/v<version>.md`, using the version number from
|
||||
`rig --version` (without the commit it may show in parentheses). When that file doesn't exist, use the version's
|
||||
section of `https://github.com/mvschwarz/openrig/blob/v<version>/CHANGELOG.md`. If neither is 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.
|
||||
Supported platforms are macOS and Linux, and WSL2 on Windows; native Windows isn't supported. OpenRig's automated tests
|
||||
don't run on WSL2 yet; one user's [reported working setup](getting-started.md#wsl2-a-reported-working-setup) lists what
|
||||
mattered. OpenRig needs
|
||||
Node.js 22 or 24 and tmux. A Linux distribution's own Node.js can be older; check `node --version`. With npm 11 or
|
||||
later, an `npm warn install-scripts` line for `@openrig/cli` means only the postinstall Node.js and SQLite check was
|
||||
skipped; `node "$(npm root -g)/@openrig/cli/scripts/check-abi.mjs"` runs it. A WSL error needs its actual versions,
|
||||
commands and error text; don't assume a Windows-related pull request fixes it.
|
||||
|
||||
The one-command install ([getting-started](getting-started.md#install-and-sign-in)) prints its plan with
|
||||
`--dry-run` and changes nothing. When a step fails it prints `FAILED [n/4] <command or check> (exit <code>)` and
|
||||
stops. Read its diagnostic: if it names a runnable command, run that command by hand for the full error and record it
|
||||
in a report; otherwise follow the accompanying diagnostic. If the only remaining failures are provider sign-ins under
|
||||
"Some steps need attention", the install steps finished: sign in to each selected provider and continue.
|
||||
|
||||
### Installation finished, but there is nobody to talk to
|
||||
|
||||
Load the `rigs` skill and follow its step 2: it opens the OpenRig view (`rig terminal open saved:kernel --window`),
|
||||
gives the attach commands when the window can't open or over SSH, and hands the person's goal to the operator.
|
||||
[Install and sign in](getting-started.md#install-and-sign-in) says how to load it. The reference for the view, manual
|
||||
attachment and the table of what can interrupt it is
|
||||
[Open the kernel conversations](getting-started.md#open-the-kernel-conversations)
|
||||
(`rig context get reference/getting-started.md#open-the-kernel-conversations`). Started is not ready: the view can
|
||||
open while the kernel's agents finish starting, and opening it creates no other kernel or account.
|
||||
|
||||
### A step interrupted setup or the OpenRig view
|
||||
|
||||
Name the exact command and result, the reason it stopped and the next useful step. A harness's permission rules,
|
||||
sandbox or automatic permission decision can refuse an installing agent's tool call before OpenRig runs.
|
||||
Claude Code's [auto mode can deny calls](https://code.claude.com/docs/en/auto-mode-config); explain the reported
|
||||
refusal and let the person review that step in their harness controls or run it themselves, within their chosen
|
||||
permissions. Do not silently claim installation failed or completed from a refused call.
|
||||
|
||||
For missing tools, downloads or selected logins, use the specific setup hint. A Herdr install warning leaves plain
|
||||
tmux available. A requested Ghostty install can fail setup; resolve it or decline with `--no-ghostty` and keep the
|
||||
earlier choices. Terminal.app remains available; required tools and logins still matter. macOS may ask for
|
||||
Automation permission; a missing display or remote daemon needs the documented headless/manual route.
|
||||
Herdr may show an intro (Return to continue) and an agent-integration panel (Esc to close); the view does not
|
||||
require installing those integrations. Enlarge a cramped 80×24 window. Use authorized desktop tools to inspect the
|
||||
window contents, or state that visibility is unconfirmed and ask the person to check. A successful command or
|
||||
window listing alone is not visual proof. The [OpenRig view guide and friction table](getting-started.md#open-the-kernel-conversations)
|
||||
connects each observation to its next step. Preserve existing conversations while resolving it.
|
||||
|
||||
### The team did not start, or a terminal is missing
|
||||
|
||||
@@ -58,6 +103,7 @@ Read [Have your agent configure permissions](getting-started.md#have-your-agent-
|
||||
(`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.
|
||||
For a `403` naming `untrusted_host` or `browser_origin_refused`, see [Browser access and allowed addresses](browser-access.md).
|
||||
|
||||
### A seat shows attention, waiting or a failed restore
|
||||
|
||||
@@ -66,6 +112,14 @@ and `rig ps --nodes --rig <rig-name>`, then compare the reported state with what
|
||||
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.
|
||||
|
||||
A fresh seat stopped at a native prompt (Claude's bypass-permissions warning, a login or a folder-trust question)
|
||||
keeps its startup context. `rig up`, `rig bundle install` and `rig ps --nodes` print "Startup attention" or "Startup
|
||||
details" for it, ending with the command to run. Once your user has answered the prompt in that seat's terminal,
|
||||
`rig seat continue <seat>` delivers the context in the same conversation, without relaunching. If it reports an
|
||||
unknown outcome, check `rig seat status <seat>` before trying again. For a rig whose seats bypass permissions,
|
||||
`--non-interruptive` on `rig up` or `rig bundle install` avoids Claude's warning and Codex's notices in the first
|
||||
place; see `non-interruptive-mode.md` beside this file.
|
||||
|
||||
### You can't tell which instance or configuration is involved
|
||||
|
||||
Read [instance layout](instance-layout.md) (`rig context get reference/instance-layout.md`) and
|
||||
@@ -77,12 +131,18 @@ involved before proposing changes.
|
||||
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).
|
||||
- Claude seats reported as needing attention after a restore: [#86](https://github.com/mvschwarz/openrig/issues/86),
|
||||
[#273](https://github.com/mvschwarz/openrig/issues/273).
|
||||
- Claude usage limits may not be detected: [#99](https://github.com/mvschwarz/openrig/issues/99).
|
||||
- Codex prompt variants not recognised as idle: [#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.
|
||||
- A Codex seat on the default `workspace-write` sandbox starts without network access, so it can't reach the local
|
||||
daemon, when its Codex configuration sets network access off, a managed requirement could restrict it, or Codex
|
||||
doesn't answer OpenRig's configuration read in time: [#275](https://github.com/mvschwarz/openrig/issues/275).
|
||||
- Kernel seats fail within a second of the first start on a distribution tmux whose server exits on `capture-pane`
|
||||
(`tmux -V` prints `next-3.4`): [#980](https://github.com/mvschwarz/openrig/issues/980). The check and recovery
|
||||
are in [Incomplete setup and restart](getting-started.md#incomplete-setup-and-restart).
|
||||
|
||||
For everything else, search [open issues](https://github.com/mvschwarz/openrig/issues).
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Host load and transcript capture
|
||||
|
||||
Use `rig ps --resources` to inspect the serving host, or add `--host <id>`
|
||||
for one registered HTTP/SSH host. `--json` returns the same measurements for
|
||||
operators and monitoring tools. This view is host-scoped; rig/node filters and
|
||||
multi-host fan-out do not apply. Ordinary `rig ps` output is unchanged.
|
||||
|
||||
The view reports available CPU count, host load averages (1/5/15 minutes),
|
||||
load divided by CPU count, running seats, the number of rotating seats and how
|
||||
many of them are backed off, and transcript capture attempts, failures, UTF8
|
||||
bytes and elapsed capture time for currently rotating seats.
|
||||
Load is not CPU utilization; capture elapsed time includes waiting for tmux,
|
||||
and is not daemon CPU time. Load averages are unavailable on Windows, rather
|
||||
than reported as zero. Counters reset when a rotation is restarted/stopped or
|
||||
the daemon restarts, so they are observations rather than a durable billing log.
|
||||
When no seat is capturing, the active interval, idle ceiling and line count are
|
||||
unavailable (`null` in JSON), not zero.
|
||||
|
||||
## Reduce idle capture cost
|
||||
|
||||
Transcript rotation still captures the bounded trailing buffer. When successive
|
||||
captures are unchanged and tmux exposes an unchanged current-window activity
|
||||
hint, capture backs off exponentially to a derived ceiling. With the default
|
||||
2-second active interval and 1-second settings timer, the effective ceiling is
|
||||
3 seconds: the 10-second freshness window minus the 1-second activity-hint
|
||||
deadline, 5-second capture-result deadline and 1-second timer spacing. The
|
||||
ceiling is capped at 6 seconds but never below the configured active interval.
|
||||
This scheduling budget is not a CPU or child-process lifetime bound; the result
|
||||
deadline does not cancel an unfinished capture. One shared tmux
|
||||
activity read wakes idle rotations; a changed hint restores the active cadence.
|
||||
Missing/failed hints retain the configured full-capture cadence. A hint read
|
||||
that takes longer than one second becomes unknown without starting additional
|
||||
metadata probes while its underlying call is still pending. Periodic full
|
||||
capture reconciles output even when a hint fails to change (tmux timestamps have
|
||||
one-second resolution). Longer configured active intervals retain that interval
|
||||
instead of silently increasing capture frequency.
|
||||
|
||||
This does not change provider readiness, delivery guards, seat activity or queue
|
||||
state. Capture activity is not proof an agent is healthy or finished. As before,
|
||||
transcripts are bounded trailing snapshots, not lossless terminal archives: an
|
||||
output burst larger than retained tmux scrollback or the configured line limit
|
||||
can exceed the saved trail. Interactive terminal streaming is unchanged.
|
||||
|
||||
## Change capture settings without restarting
|
||||
|
||||
```bash
|
||||
rig config set transcripts.poll_interval_seconds 3
|
||||
rig config set transcripts.lines 2000
|
||||
rig ps --resources
|
||||
rig config reset transcripts.poll_interval_seconds
|
||||
```
|
||||
|
||||
Running rotations reread shared settings at most once per second and adopt the
|
||||
next valid policy without starting overlapping captures. Intervals accept whole
|
||||
seconds from 1 to 3600; trailing lines accept integers from 1 to 1000000. The
|
||||
existing environment variables take precedence over file settings. Malformed
|
||||
intermediate JSON retains the last usable policy, is shown in the resource view,
|
||||
and is retried; normal config
|
||||
writes reject invalid values. Changing `transcripts.enabled` or
|
||||
`transcripts.path` still requires a daemon restart. This feature does not impose
|
||||
CPU quotas or allocate build slots.
|
||||
@@ -79,6 +79,11 @@ 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.
|
||||
The catalog is a Git checkout. A skill folder with uncommitted or untracked content is
|
||||
skipped by itself and named. With `--runtime`, work-install reports selected skips with
|
||||
exit 1. It inspects by default; `--apply-skills` projects the remaining clean skills while
|
||||
keeping any previously projected copy of the skipped skill. An uncommitted change directly
|
||||
in the catalog root, such as `catalog.yaml`, still makes the whole catalog unavailable.
|
||||
|
||||
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
|
||||
@@ -104,3 +109,14 @@ 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.
|
||||
|
||||
Config updates publish a complete replacement file atomically where directory
|
||||
permissions and the filesystem allow it. On POSIX systems, replacement preserves
|
||||
the existing owner, group, and read/write/execute permission bits. If creating or
|
||||
preparing the replacement, or renaming it, fails with `EACCES`, `EPERM`, or `EBUSY`,
|
||||
the stores write the writable target in place, preserving compatibility with
|
||||
unwritable directories and single-file bind mounts. This fallback is not atomic
|
||||
and retains the previous partial-write risk. Read-only config files are refused.
|
||||
Other errors, including `ENOSPC`, leave the original file intact on the atomic
|
||||
path. Atomic replacement does not carry extended ACLs or other inode metadata;
|
||||
a hard-linked second name continues to refer to the previous file.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Inventory request diagnostics
|
||||
|
||||
The private slow-operation log can correlate explicitly marked inventory reads.
|
||||
This diagnoses future observations; it does not repair a timeout or establish the
|
||||
cause of an earlier uncorrelated request.
|
||||
|
||||
Send exactly one `X-OpenRig-Diagnostic-Attempt` header containing a fresh lowercase
|
||||
UUIDv4 on a `GET /api/rigs` or `GET /api/rigs/:id/nodes` request. The token is only a
|
||||
correlation hint. It grants no authority, triggers no retry, and changes neither
|
||||
the response nor its timeout. Invalid or duplicate tokens are ignored by the
|
||||
observer. Do not copy the token to incidental identity or health checks.
|
||||
|
||||
Records use `schema: openrig.request-phase/v1` in the existing
|
||||
`logs/slow-operations.jsonl`. Each process has an observation `epoch` and sequence
|
||||
`seq`. An activation record carries the installed build stamp (`semver`,
|
||||
`commit`, `dirty`, `builtAt`; each is null in an unstamped development build).
|
||||
Each admitted request gets a distinct
|
||||
`serverRequestId`, even if a caller reuses an `attemptId`. Routes are recorded as
|
||||
`rigs_list` or `rig_nodes`, never a rig name or full URL.
|
||||
|
||||
The phases are:
|
||||
|
||||
- `node_arrival`: the existing Node HTTP request listener ran. This is after HTTP
|
||||
parsing and JavaScript scheduling, not a socket-arrival timestamp.
|
||||
- `hono_enter` / `hono_exit`: application middleware started/finished.
|
||||
- `handler_enter` / `handler_exit`: the actual inventory handler started/finished.
|
||||
Gate rejection or read-through may have no local handler entry.
|
||||
- `sql_begin` / `sql_end`: the root list's SQLite prepare/read, excluding row mapping.
|
||||
Other node-inventory queries are not individually traced.
|
||||
- `response_finish`: Node completed its server-side response writes; this does not
|
||||
prove that the client consumed the body.
|
||||
- `response_close`: records `headersSent`, `writableFinished` and status. A normal
|
||||
close after finish is not failure. A premature close does not identify its cause.
|
||||
- `request_aborted`: only when Node emits that incoming-request event. A GET client
|
||||
timeout often appears as response close instead. Handler work can finish later.
|
||||
- `trace_expired`: diagnostic state expired; the request was not canceled.
|
||||
|
||||
`elapsedMs` is process-local monotonic time since observation activation;
|
||||
`requestElapsedMs` is since the request event. `utc` is a dated anchor. Never
|
||||
subtract monotonic times across processes. A client's useful counterparts are its
|
||||
logical start, actual fetch invocation, headers received, body completion, signal
|
||||
abort and final outcome. Fetch invocation does not prove a socket write.
|
||||
|
||||
The existing 250 ms event-loop tick provides bounded recent timing context and
|
||||
`event_loop_gap` records for tick intervals of at least 500 ms. The gap is reported
|
||||
when the loop resumes. An indefinite stall or killed process cannot report its
|
||||
end. This does not change the existing lifetime histogram or health verdict.
|
||||
|
||||
Diagnostic limits: 128 admitted requests per monotonic minute, 128 active traces,
|
||||
16 distinct phases per trace, 60-second observation lifetime, 40 recent ticks,
|
||||
1 KiB per record and 1024 outstanding diagnostic writes. Overflow drops diagnostic
|
||||
observations, never requests. The existing 1 MiB log plus three rotated files and
|
||||
0600 file modes apply. The worker writes asynchronously; HTTP observation never
|
||||
waits for fsync or the synchronous slow-operation BEGIN barrier.
|
||||
|
||||
Records carry cumulative `rejected` (admission refused), `expired` and
|
||||
`observerErrors` counters and writer
|
||||
`coverage` counters (`offered`, `enqueued`, `dropped`, `acknowledged`, `failed`,
|
||||
`pending`, `recorderHealthy`). Writer counters describe the state sampled for that
|
||||
record, not an acknowledgment of that record itself. Retained epoch/sequence
|
||||
ranges can be read from the current and rotated logs; the log is not an archive.
|
||||
A missing activation/tail, sequence loss, recorder failure, overflow, expiry or
|
||||
rotated-away interval makes the corresponding coverage incomplete. Source tests
|
||||
without the Node raw-request binding cannot establish Node arrival.
|
||||
|
||||
Missing arrival is unknown by default. Even a complete covered interval only
|
||||
establishes that no matching JavaScript request event was observed there; it does
|
||||
not distinguish a client that never sent from network, parser or event-loop delay.
|
||||
HTTP status at handler exit is a prepared response, and status at finish/close is
|
||||
server-side state. Neither replaces the client's header/body receipt.
|
||||
|
||||
The new format contains no payloads, auth or raw identity headers, URL parameters,
|
||||
SQL text, bind values, native identifiers or raw exception messages. Legacy
|
||||
slow-operation records keep their existing format: their `outcome: ok` indicates
|
||||
completion of the observed operation and is not an HTTP-success assertion.
|
||||
@@ -0,0 +1,156 @@
|
||||
# Keeping a Watch for a Person
|
||||
|
||||
**How an operator turns "remind me in 20 minutes" or "tell me when X happens, check every hour" into a watch it
|
||||
keeps, using today's queue commands.** The person speaks in sentences. They never see YAML, a job ID or a policy name
|
||||
unless they ask.
|
||||
|
||||
> **Status (0.7.0, not on by default).** No agent's guidance points here yet, and no profile loads it. It's switched
|
||||
> on by adding one line to the kernel operator's role guidance, after a native run on a real install shows the
|
||||
> pattern holds. Until then, hand this page to an operator by hand (`docs/reference/keeping-a-watch.md`) to try it.
|
||||
|
||||
## The shape
|
||||
|
||||
A watch is a **queue task the operator owns**, set aside with a **one-shot timer**:
|
||||
|
||||
1. The person asks. The operator creates one task for itself and sets it aside with `--wake-after`.
|
||||
2. The timer fires once and wakes the operator. The operator reads the task and checks the condition.
|
||||
3. Then it tells the person, re-arms the timer, or both. **A timer fires once: a watch that isn't re-armed goes
|
||||
quiet.**
|
||||
|
||||
OpenRig keeps the record and the timer. The operator does the judgment: what to check, and whether it's worth telling
|
||||
the person.
|
||||
|
||||
## Before you set one: can you reach the person?
|
||||
|
||||
Run `rig gateway human list --json`. A watch tells the person through the registered human channel (Slack today,
|
||||
also shown in the TUI Feed), at `<entityId>@external`. **A terminal is never a person's address.** If no human is
|
||||
registered, say so now, before setting anything up: a watch that can't reach them is a watch they'll never hear from.
|
||||
|
||||
## Setting a watch
|
||||
|
||||
**1. Write the task body for a successor.** You may be restarted, compacted or replaced before the watch ends. A new
|
||||
operator must be able to act from the task alone. Put these in the body:
|
||||
|
||||
```markdown
|
||||
Person's words: "Tell me when the release PR merges; check every hour."
|
||||
Check: `gh pr view <n> --json state` — met when state is MERGED.
|
||||
Interval: 1h.
|
||||
Tell them: <entityId>@external (human channel), once, when met.
|
||||
Stop: after telling them once, or when they say stop.
|
||||
Unknown: not said yet.
|
||||
```
|
||||
|
||||
**2. Create it for yourself,** with a `watch` tag so it can be listed later (tags can't be changed after creation).
|
||||
`<your session>` is your session name from `rig whoami --json`:
|
||||
|
||||
```bash
|
||||
rig queue create --destination <your session> --tags watch \
|
||||
--summary "Watch: tell <person> when the release PR merges (hourly)" --body-file <file>
|
||||
```
|
||||
|
||||
**3. Set it aside with the timer.** A new pending task can be set aside directly; no claim is needed:
|
||||
|
||||
```bash
|
||||
rig queue block <task> --on external:<what you check> --wake-after 1h \
|
||||
--continuation "check <what>; tell <person> if met, else re-arm 1h"
|
||||
```
|
||||
|
||||
`--on` is always required; `external:<what>` names the outside thing you're waiting on. The timer first fires after
|
||||
the interval, not now.
|
||||
|
||||
**4. Say back what you set, in one or two plain lines:** what you'll check, how often, how they'll be told, and when
|
||||
it stops. For example: "I'll check the release PR every hour and message you here once it merges. Then I'll stop."
|
||||
|
||||
A plain reminder ("remind me in 20 minutes to look at the release notes") is the same with no condition. Set it aside
|
||||
with `--wake-after 20m`, and when it fires, tell them and close it.
|
||||
|
||||
## Every time the timer fires: read the record first
|
||||
|
||||
When the timer's message arrives, **run `rig queue show <task> --full --json` before doing anything.** A message already
|
||||
on its way can still arrive after the watch was stopped or changed. If the task is no longer set aside (done, canceled, or replaced),
|
||||
do nothing.
|
||||
|
||||
Otherwise, check the condition, then do exactly one of these:
|
||||
|
||||
| What you found | Do |
|
||||
|---|---|
|
||||
| **Met, and the watch was "once"** | Tell the person (below), then close: `rig queue update <task> --state done --closure-reason no-follow-on --note "<what you saw>"` |
|
||||
| **Met, and it's "every time"** | Tell the person, then re-arm |
|
||||
| **Not met** | Re-arm. Say nothing to the person: never send "nothing yet" |
|
||||
| **Can't tell** (no fresh reading) | The first time, tell them once that you can't tell, and why. Record that in a note (`Unknown: said <time>`), then re-arm. **Never say "all fine" for an unknown.** Stay quiet on later unknowns until the reading returns or changes |
|
||||
|
||||
**Re-arm** by setting it aside again with a fresh timer:
|
||||
|
||||
```bash
|
||||
rig queue block <task> --on external:<what> --wake-after 1h --note "checked <time>: not met"
|
||||
```
|
||||
|
||||
A new set-aside replaces the old one and retires any earlier timer, so a task never carries two live timers.
|
||||
|
||||
**Put what changed in `--note` or `--continuation`, not both.** When `rig queue block` gets `--continuation`, that is
|
||||
the note it records, and `--note` is dropped.
|
||||
|
||||
Each check costs one of your turns on the person's subscription. **Use the longest interval that serves the ask.**
|
||||
|
||||
## Telling the person
|
||||
|
||||
```bash
|
||||
rig queue create --destination <entityId>@external --human-intent update \
|
||||
--summary "<subject>" --body-file <brief> --evidence-ref <watch task id> --verify --json
|
||||
```
|
||||
|
||||
- Always pass `--evidence-ref`; the watch task's ID is a durable pointer the person can open.
|
||||
- Later messages about the same watch can thread with `--reply-to <earlier message's id>`.
|
||||
- **`posted` means the connector posted it, not that they read it.** Don't say they saw it.
|
||||
- **If delivery fails or is indeterminate,** inspect that task before anything else. Never resend blindly.
|
||||
|
||||
## "What are you watching for me?"
|
||||
|
||||
List **every active state**, not just set-aside ones. A watch being checked can be in progress, and a new one can be
|
||||
pending:
|
||||
|
||||
```bash
|
||||
rig queue list --owned --state pending,in-progress,blocked --full --limit 200 -o json
|
||||
```
|
||||
|
||||
Then keep the tasks tagged `watch`. Two rules:
|
||||
|
||||
- **Check that the number of tasks returned is below the limit.** The limit applies before you filter by tag. If you got
|
||||
exactly the limit, raise it and list again. **Never report "no watch" from a page that was cut off.**
|
||||
- **Use `--full`.** Compact output leaves out the body and the waiting view.
|
||||
|
||||
For each watch, say what it checks, how often, how they'll be told, and the next check. The next check is
|
||||
`waiting.nextBackstop.dueAt` when `waiting.nextBackstop.mechanism` starts with `watchdog:`. If a set-aside watch shows
|
||||
no timed backstop instead, its timer has fired and wasn't re-armed. Check it now and re-arm it.
|
||||
|
||||
Answer from the records, so a second reader would give the same list.
|
||||
|
||||
## Changing or stopping a watch
|
||||
|
||||
| The person says | Do |
|
||||
|---|---|
|
||||
| **"Check every 2 hours instead"** | Re-arm with the new interval and a note saying what changed: `rig queue block <task> --on external:<what> --wake-after 2h --note "<person's words>"` |
|
||||
| **"Watch Y instead"** (what's checked changes) | A task's body can't be edited. Create the new watch, then retire the old one: `rig queue update <old> --state canceled --closure-reason superseded --closure-target <new> --note "<person's words>"` |
|
||||
| **"Stop watching that"** | `rig queue update <task> --state canceled --note "<person's words>"`. Leaving the set-aside state retires its timer. A plain cancel takes no other closure fields |
|
||||
|
||||
Tell the person in one line what changed. After a stop, take no further action for that watch.
|
||||
|
||||
## Across a restart or handover
|
||||
|
||||
A set-aside watch's current timer is **kept** when the operator is restarted or replaced, and it fires at whoever is
|
||||
the operator next. That's why the body must stand alone.
|
||||
|
||||
**One gap:** a timer fires once. If the operator is replaced **after a fire and before the re-arm**, the successor
|
||||
inherits a set-aside watch with no live timer. OpenRig's idle-owner check may remind the new operator once, but only
|
||||
under some conditions, with no delay guarantee. So:
|
||||
|
||||
- **Re-arm promptly** after every fire.
|
||||
- **After a restart or handover, list your watches** (above) and re-arm any that show no timed backstop.
|
||||
|
||||
## What not to use
|
||||
|
||||
- **`rig watchdog register --policy periodic-reminder` for a person's watch.** It repeats every interval until it's
|
||||
stopped (the first fire comes one interval after you register it), and it stops when the agent that registered it
|
||||
is replaced. It's fine for agent-to-agent reminders.
|
||||
- **Transcript bytes as "usage".** `context-usage-threshold` measures an agent's transcript, not an account's
|
||||
allowance.
|
||||
@@ -91,7 +91,9 @@ 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
|
||||
valid values — `rig scope slice stage` and `rig scope mission stage` reject invented stages
|
||||
(shape, shaped, draft) and a `superseded` without a successor; context-pack manifests and
|
||||
lore frontmatter aren't checked. Items minted under
|
||||
new conventions carry a stage from birth: labeling from day one is free, retrofitting
|
||||
is archaeology.
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
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>/`). 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.
|
||||
legacy tree is left in place, `rig context trace` keeps reading it at the rig and
|
||||
seat levels throughout (with an advisory), and every copy is no-clobber. The refocus
|
||||
trace, `seat:` profile atoms and handover priming read only `topology.root`, so
|
||||
they see these files only after migration.
|
||||
|
||||
## The path, with exact commands
|
||||
|
||||
|
||||
@@ -84,7 +84,9 @@ 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.
|
||||
Lore uses the existing `seat:` grammar and an explicit rig-and-seat read grant
|
||||
(`rig context profile <pack> --situation <fresh|handover|post-compaction> --rig <rig> --seat <seat>`,
|
||||
where the grant needs both `--rig` and `--seat`).
|
||||
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.
|
||||
|
||||
|
||||
@@ -196,7 +196,9 @@ The layers compose from surfaces that already exist; no new mechanism is require
|
||||
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;
|
||||
--situation <situation> ...` applies the already-inspected profile (walk's
|
||||
seat grant is `--seat-grant`, and its `--runtime` takes `claude` or `codex`,
|
||||
defaulting to `claude`, so a Codex profile needs `--runtime codex`);
|
||||
`--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.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Non-interruptive mode
|
||||
|
||||
Use `rig up <source> --non-interruptive` or `rig bundle install <archive-or-link> --non-interruptive`
|
||||
to accept supported harness first-launch warnings for this rig. Sessions remain interactive.
|
||||
This does not sign in to a provider or change any seat's permission policy.
|
||||
|
||||
The option applies only to seats whose resolved launch posture is `full_bypass`.
|
||||
This must be declared through `builtin:yolo`, a `full_bypass` flag policy, or an explicit `full_bypass` selection;
|
||||
ambient `OPENRIG_YOLO` alone without an attached policy is not covered. One exception: kernel Codex seats on the
|
||||
kernel's operational default always get the Codex notice overrides below, without this option.
|
||||
|
||||
- Claude Code receives `--settings '{"skipDangerousModePermissionPrompt":true}'`, accepting its bypass-permissions warning.
|
||||
A seat with an explicitly selected native permission mode other than `bypassPermissions` doesn't get it.
|
||||
- Codex receives per-launch `-c notice.…=true` overrides for the full-access and GPT-5.1 migration notices.
|
||||
The separate GPT-5.1-Codex-Max migration notice is not suppressed.
|
||||
- Pi's existing launch flags are unchanged.
|
||||
|
||||
OpenRig saves the choice on the rig. Later launches, restores, forks and handovers keep it, including
|
||||
when the option is omitted. To turn it off, first stop the rig with `rig down <rig-name>`, then run
|
||||
`rig up <rig-name> --existing --no-non-interruptive`. The choice is cleared for this and subsequent
|
||||
launches. If the rig is still running, the command is refused before the choice changes. `--plan`
|
||||
changes nothing. Disabling the mode does not erase acceptance that a person previously saved in the harness.
|
||||
Turning it on for an existing rig works the same way: `rig down <rig-name>`, then
|
||||
`rig up <rig-name> --existing --non-interruptive`. Rigs created before this option existed start with it off.
|
||||
|
||||
For an operator default on **new rigs**, use `rig config set launch.non_interruptive true`, or set
|
||||
`OPENRIG_LAUNCH_NON_INTERRUPTIVE` in the daemon's environment (it overrides the config file).
|
||||
The default is false; a rig spec's `non_interruptive` overrides it (see below), and an explicit positive or
|
||||
negative command-line flag overrides both. Existing rigs
|
||||
keep their saved choice when the operator default changes. On a remote install the receiving daemon's
|
||||
default applies.
|
||||
|
||||
`rig up` and `rig bundle install` print what happened as warnings: "Non-interruptive mode is saved for this rig…"
|
||||
when it is on (also on each restore), "Non-interruptive mode is off for this rig's launches…" when you turn it off,
|
||||
and, for each affected seat, "Non-interruptive: OpenRig accepted Claude's bypass-permissions warning" or "…hid
|
||||
Codex's full-access and GPT-5.1 migration notices… no warning-acceptance settings were written."
|
||||
|
||||
A bundle's before-install view (`rig bundle inspect`) says when this option is available for its full-bypass
|
||||
Claude and Codex seats, and that Claude Code asks once to accept its warning otherwise. When the bundle's `rig.yaml`
|
||||
declares `non_interruptive: true`, it says instead that the bundle's launches are non-interruptive by default, and
|
||||
leaves out the asks-once note. See [bundle formats](bundle-formats.md).
|
||||
|
||||
This feature writes no warning-acceptance settings to Claude or Codex files. It uses the launch-flag
|
||||
surfaces checked in Claude Code 2.1.282 and Codex 0.153.4. Other notices introduced by later harness
|
||||
versions, sign-in, and harness preconditions can still require attention. Ordinary launches without
|
||||
an opt-in keep their existing behaviour.
|
||||
|
||||
## Bundle or rig default
|
||||
|
||||
A pod-aware `rig.yaml` may declare `non_interruptive: true` alongside its
|
||||
permission policy. This saves the choice on the installed rig without requiring
|
||||
a CLI flag. Explicit `--non-interruptive` or `--no-non-interruptive` wins over
|
||||
the spec; a declared boolean wins over the machine's `launch.non_interruptive`
|
||||
default. A declaration of `false` is distinct from omission. Exports include
|
||||
`non_interruptive: true` when enabled; disabled or unset choices omit the key
|
||||
because saved state does not distinguish their origin. Restores of the existing
|
||||
rig retain its saved choice.
|
||||
|
||||
The before-install view reports this declaration as
|
||||
`posture[].nonInterruptiveDefault` for full-bypass Claude/Codex seats, separately
|
||||
from `nonInterruptive: available`. It leaves `nativeEffect: unknown`: explicit
|
||||
launch choices, sign-in and unsupported harness notices still apply. This field
|
||||
does not broaden the rig's permission policy or add warning flags to Pi.
|
||||
@@ -50,4 +50,5 @@ 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.
|
||||
shape the spec. Record the chosen rung in the slice's frontmatter as
|
||||
`approved-spec-dial:`; `rig view show execution` shows it as `planning_dial`.
|
||||
@@ -1,8 +1,8 @@
|
||||
# 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
|
||||
mission or slice selects only the components its work needs and records them,
|
||||
with any explicit edges between them, 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`.
|
||||
|
||||
@@ -215,7 +215,8 @@ possible explanation.
|
||||
## Composition rules
|
||||
|
||||
1. `project.yaml` may declare project defaults; `mission.yaml` may select the
|
||||
catalog and override those defaults for the mission.
|
||||
catalog and override those defaults for the mission. Some file in that chain
|
||||
must name the catalog: there is no default catalog.
|
||||
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.
|
||||
@@ -305,15 +306,19 @@ On a fresh start, restore, or new assignment:
|
||||
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
|
||||
replaces the broader component list and its edges; 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
|
||||
references resolve from the Git root of the repository that holds the YAML
|
||||
declaring the catalog, which may not be the code checkout; 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.
|
||||
For work running as a workflow instance, `rig workflow guidance <instance>`
|
||||
reads the effective selection, the selected catalog sections and the original
|
||||
intent; add `--component <id>` or `--full` to expand.
|
||||
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
|
||||
|
||||
@@ -31,7 +31,9 @@ measuring stick:
|
||||
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.
|
||||
4. **MINT SLICES AT PLAN TIME** with real one-paragraph intents
|
||||
(`rig scope slice create <mission> <slug> --intent "..."`; without `--intent`
|
||||
the SPEC.md intent defaults to the title).
|
||||
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
|
||||
|
||||
@@ -31,6 +31,23 @@ is the project-location catalog. The project manifest exposes empty
|
||||
addresses and stable managed-catalog skill IDs, but neither skill source nor a
|
||||
System World belongs in this tree.
|
||||
|
||||
Installing a bundle that carries a project (`rig bundle create --project-dir`)
|
||||
copies the project folder to `<workspace.projects_root>/<id>/`, adds its catalog
|
||||
entry, and lists the bundle's rig under that entry's `rigs` (see "Work-install
|
||||
project selection"), before any seat launches. Every byte already in the
|
||||
catalog stays: the entry is appended in the file's own indentation and line
|
||||
endings, and a later rig joins it by a one-line edit of its `rigs: [...]`.
|
||||
Reinstalling changes nothing. If the file's shape doesn't allow that (for
|
||||
example a flow-style list), if the id is already taken by another root, if the
|
||||
folder is registered under a different id, or if the rig is already listed
|
||||
under another project, install writes nothing to the catalog and prints what to
|
||||
change, and the folder isn't copied either. An existing folder at
|
||||
`<workspace.projects_root>/<id>/` is never overwritten: if its files differ from
|
||||
the bundle's, install keeps it and says so. Without a catalog, the one it writes
|
||||
keeps an entry for the workspace root beside the bundle's, under the id from the
|
||||
workspace's own `project.yaml` (`default` when it declares none); if that id is the
|
||||
bundle's project id, install reports a conflict and writes no catalog.
|
||||
|
||||
## Project-world install
|
||||
|
||||
`project.yaml` may select project context and managed skills together:
|
||||
@@ -46,6 +63,24 @@ install:
|
||||
- repository-maintenance
|
||||
```
|
||||
|
||||
A project can also name world packs to read after the System World, in order:
|
||||
|
||||
```yaml
|
||||
install:
|
||||
worlds:
|
||||
- ref: openrig-world
|
||||
- ref: project-world
|
||||
profiles: { claude: guided }
|
||||
```
|
||||
|
||||
Each `install.worlds` entry has the same `{ ref, profiles }` shape as a System
|
||||
World `context` entry. `work-install` lists them as `context world <ref>` lines
|
||||
and `worlds` in `--json`; it never delivers their content, so read each with
|
||||
`rig context get <ref>`. An invalid entry, or a ref the System World or an
|
||||
earlier entry already lists, is ignored with a warning. Without the key, the
|
||||
output is unchanged. The list holds pack names only, resolved from the local
|
||||
library when read, so naming a world copies none of its content.
|
||||
|
||||
`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`),
|
||||
@@ -73,6 +108,43 @@ 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."
|
||||
|
||||
## Work-install project selection
|
||||
|
||||
`rig context work-install` picks one project from the catalog at
|
||||
`workspace.catalog_path` (default `workspace.yaml`), in the order below. Without a
|
||||
catalog, the workspace root itself is the project (`selectedBy: workspace`, or
|
||||
`explicit` with a matching `--project`), and `--project` must match its
|
||||
`project.yaml` id. Operating posture uses steps 3-5
|
||||
the same way when a task names no project (see
|
||||
[scoped operating posture](scoped-operating-posture.md)).
|
||||
|
||||
1. `--project <id>`;
|
||||
2. the only declared project;
|
||||
3. the project whose entry lists the calling seat's rig under `rigs`;
|
||||
4. the deepest declared project root that contains the working directory
|
||||
(`--cwd`, else the current directory);
|
||||
5. the only project whose entry lists no `rigs`, so a rig that isn't listed
|
||||
anywhere keeps its project after a claimed project is added beside it;
|
||||
6. otherwise it stops with `project_required` and prints each candidate's exact
|
||||
command.
|
||||
|
||||
```yaml
|
||||
schema: openrig.workspace/v0alpha1
|
||||
projects:
|
||||
- id: default
|
||||
root: .
|
||||
- id: contributor
|
||||
root: projects/contributor
|
||||
rigs: [openrig-dev]
|
||||
```
|
||||
|
||||
`rigs` is optional. A `rigs` value that isn't a list of rig names is ignored
|
||||
with a warning; the entry can still be chosen by `--project`, as the only entry
|
||||
or by working directory, but never counts as unclaimed in step 5. A rig listed under two projects, or two projects sharing the
|
||||
deepest root, leave the choice to `--project`. Step 3 reads the seat's
|
||||
`OPENRIG_SESSION_NAME`, so a plain shell skips it. `--json` reports the step
|
||||
that chose the project as `position.selectedBy`.
|
||||
|
||||
## TUI project selection
|
||||
|
||||
Open **PROJECTS** (or enter `projects`) to choose an ID and root from the
|
||||
@@ -88,7 +160,7 @@ 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
|
||||
identity. Unscoped historical tasks 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
|
||||
@@ -116,14 +188,19 @@ ambiguity.
|
||||
|
||||
## Queue Mapping
|
||||
|
||||
Queue items attach to a slice when their body or tags mention one of:
|
||||
A `slice:<id>` tag is the authoritative way to attach a queue item to a slice;
|
||||
with a project selected, typed rows also need the `project:<id>` tag. As a
|
||||
fallback, for slices with no typed rows, items attach when their body or tags
|
||||
mention one of:
|
||||
|
||||
- the slice id;
|
||||
- the mission id;
|
||||
- the legacy `rail-item` value in slice frontmatter.
|
||||
|
||||
For new work, include both mission and slice ids in the queue item body or
|
||||
tags. Example:
|
||||
Once any slice in a mission has typed rows, the fallback stops matching on the
|
||||
mission id (and on a `rail-item` defaulted from it) for that mission's other
|
||||
slices too. For new work, tag rows with `slice:<id>` (and `project:<id>`), and
|
||||
include the mission and slice ids in the body as well. Example:
|
||||
|
||||
```text
|
||||
Mission: idea-ledger
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
# Publishing a rig bundle
|
||||
|
||||
A rig bundle is a whole OpenRig team that someone else can install and run: the rig spec, its agents, their
|
||||
instructions and skills, and its documentation. This guide walks through building one, checking it, sharing it as a
|
||||
link, installing from a link, and submitting it to openrig.dev/rigs. The last section is a block of instructions you can
|
||||
give your agent to build one for you.
|
||||
|
||||
For every flag, see the [rig bundle reference](rig-bundle.md); for the file formats, see
|
||||
[bundle formats](bundle-formats.md). This guide doesn't repeat them.
|
||||
|
||||
## 1. Lay out the folder
|
||||
|
||||
A shareable bundle is one folder, kept on GitHub: in its own repository, or as a subfolder of one you already have.
|
||||
The folder holds the rig spec (`rig.yaml`), the agents it uses, and a README. Choose
|
||||
[names that describe the team, domains and roles](topology-naming.md) before wiring its references.
|
||||
The rules are the bundle standard
|
||||
(`openrig.bundle-standard/v1`), summarized under "Advisory author check" in the
|
||||
[rig bundle reference](rig-bundle.md#advisory-author-check).
|
||||
|
||||
Two rules matter most for strangers:
|
||||
- **Keep everything inside the folder.** Every `agent_ref` should be `local:` and point inside it. A path that exists on
|
||||
your machine won't exist on anyone else's.
|
||||
- **Never put credentials in it.** No tokens, keys or `.env` files. `rig bundle create` refuses well-known sensitive
|
||||
file names (such as `.env`, `.pem` and `.key`) anyway.
|
||||
|
||||
Two optional files sit beside `rig.yaml`:
|
||||
- **`bundle.yaml`** declares what people need before installing: setup steps (`preconditions`, shown before install
|
||||
and never run), the oldest OpenRig that works (`compatibility.min_cli_version`), and skills shared by several
|
||||
agents.
|
||||
- **`configurations.yaml`** offers other harness mixes (for example all-Claude), as presets. The recommended preset
|
||||
must be `rig.yaml` as written. `rig bundle configurations ./my-team/rig.yaml` lists them with their configuration
|
||||
IDs.
|
||||
|
||||
## 2. Check it
|
||||
|
||||
```sh
|
||||
rig bundle check ./my-team # checks the folder against the standard; launches nothing
|
||||
```
|
||||
|
||||
Fix what it reports, or say in your README why a finding doesn't apply. Any finding exits 1, and the output names the
|
||||
file a finding is about when there is one. It checks the folder on disk, including files you haven't committed, so
|
||||
check again at the commit you share. README completeness and embedded secrets always read `not_checked`: those are yours to review.
|
||||
|
||||
You can also build and look inside the archive:
|
||||
|
||||
```sh
|
||||
rig bundle create ./my-team/rig.yaml -o my-team.rigbundle --name my-team
|
||||
rig bundle inspect my-team.rigbundle
|
||||
```
|
||||
|
||||
`create` and `inspect` don't launch anything. A local build like this packages a minimum OpenRig version only from
|
||||
`--min-cli-version`; a build from your link reads it from your `bundle.yaml`. To preview what installing would do,
|
||||
`rig bundle install my-team.rigbundle --plan` writes nothing to a target, but it isn't free of side effects: it records
|
||||
a planned run, and it can run harness checks such as `pi --version` for Pi seats.
|
||||
|
||||
## 3. Share it as a link
|
||||
|
||||
Share a link to the folder **at a full commit**, so everyone gets exactly the version you checked:
|
||||
|
||||
```text
|
||||
https://github.com/<you>/<repo>/tree/<commit>/<path-to-folder>
|
||||
```
|
||||
|
||||
A branch name moves when you push again; a commit doesn't. `rig bundle inspect` on a branch link prints the
|
||||
commit-pinned link to share (`Source:`). Use the full 40-character commit ID: a shorter one is looked up as a branch or
|
||||
tag name and isn't found.
|
||||
|
||||
## 4. Install from a link
|
||||
|
||||
```sh
|
||||
rig bundle inspect https://github.com/<you>/<repo>/tree/<commit>/<path> # see what it will do first
|
||||
rig up https://github.com/<you>/<repo>/tree/<commit>/<path> --target <an empty folder> --cwd <your project>
|
||||
```
|
||||
|
||||
Look before you run. `inspect` shows which agents start and with what access, what they're told, what gets written
|
||||
where, and what the bundle needs, including the author's setup steps. Its integrity check tells you the archive is
|
||||
self-consistent; it doesn't tell you who made it. `rig up` prints the same view before it installs, but doesn't stop to
|
||||
ask.
|
||||
|
||||
Links need `git`, a running local OpenRig daemon, and a public GitHub link without credentials; they don't work with
|
||||
`--host`. If the team uses `permission_policy: builtin:yolo` (no permission prompts), `--non-interruptive` lets OpenRig accept the harnesses'
|
||||
first-launch warnings for you (see [non-interruptive mode](non-interruptive-mode.md)). As the author, you can make that
|
||||
the install default by declaring `non_interruptive: true` in `rig.yaml`; `--no-non-interruptive` overrides it.
|
||||
|
||||
Installing writes the bundle's files into the install target (`--target`, the current directory if you leave it
|
||||
out) and launches the team from there, with the seats working in `--cwd`. If the target already
|
||||
has a different file at the same path, install refuses and writes nothing, so use an empty or dedicated directory.
|
||||
Installing again over a stopped team of the same name replaces it, and in its own install folder the changed files are
|
||||
backed up first under `~/.openrig/bundle-backups/`. A running team of the same name is refused, so stop it with
|
||||
`rig down` first. The reference's "Install a bundle" section has the details.
|
||||
|
||||
## 5. Choose your harnesses (when a bundle offers configurations)
|
||||
|
||||
```sh
|
||||
rig up <link> --preset all-claude
|
||||
rig up <link> --seat orch.lead=pi --seat dev.qa=codex
|
||||
```
|
||||
|
||||
On openrig.dev/rigs, pick a configuration and the page gives you one command to paste, which fetches exactly that
|
||||
configuration from GitHub. Every run prints the configuration it used, as its configuration ID.
|
||||
|
||||
The page also shows how much each configuration has been tested, with a label such as Tested by OpenRig, Partly tested
|
||||
or **"Not tested by OpenRig"**, which means exactly that. You're welcome to run it and tell us how it went.
|
||||
|
||||
## 6. Submit it to openrig.dev/rigs
|
||||
|
||||
Open a pull request to [mvschwarz/openrig-registry](https://github.com/mvschwarz/openrig-registry) that adds one file,
|
||||
`registry/submissions/<your-team>.yaml`, with three fields: `repository` (your GitHub repository URL), `folder` (the
|
||||
folder holding `rig.yaml`, or `.` for the repository root) and `ref` (a branch, tag or commit). No pull requests? Open
|
||||
an issue there with the same three things. A maintainer then writes the full entry, in the `registry-entry.v1` format
|
||||
defined in the [bundle formats reference](bundle-formats.md), with the parts only a review can produce, such as the
|
||||
pinned commit and each configuration's package digest.
|
||||
|
||||
Rig names are unique on the site. If `registry/<your-team>.yaml` already exists, openrig-registry's registry check says the name is taken;
|
||||
choose a distinct one, for example `<taken-name>-<your-name>`.
|
||||
|
||||
What happens next:
|
||||
- a maintainer pins your link to an exact commit, runs the same check, and reads everything your bundle gives to an
|
||||
agent;
|
||||
- if it's useful to people running OpenRig and meets the standard, it's listed, and appears on the site
|
||||
automatically. The listing shows the commit we reviewed, the date, and "Review is not a security audit". Listing
|
||||
doesn't mean OpenRig tested it;
|
||||
- if it isn't listed, you get a short reply saying why.
|
||||
|
||||
**Updating:** push your change, then open a pull request that updates your entry. The site keeps showing the reviewed
|
||||
commit until the update is merged.
|
||||
|
||||
**Removing:** ask in an issue or a pull request, and we'll take it down.
|
||||
|
||||
---
|
||||
|
||||
## Give this to your agent
|
||||
|
||||
> Build an OpenRig rig bundle for: <one sentence on what the team should do>.
|
||||
>
|
||||
> Read the bundle guide and reference for the OpenRig version installed here (`rig --version`): the installed copies
|
||||
> are `publishing-a-rig-bundle.md` and `rig-bundle.md` in `$OPENRIG_HOME/reference/` (default `~/.openrig/reference/`).
|
||||
> Follow the bundle standard in the reference:
|
||||
> - one folder with `rig.yaml`, a README listed in its `docs`, and every agent inside the folder, with each
|
||||
> `agent_ref` as `local:`;
|
||||
> - no credentials, tokens or absolute paths anywhere in the folder.
|
||||
>
|
||||
> Run `rig bundle check <folder>` and fix every finding, or explain in the README why it doesn't apply.
|
||||
>
|
||||
> Preview with `rig bundle create <folder>/rig.yaml -o <scratch>/team.rigbundle`, then `rig bundle install
|
||||
> <scratch>/team.rigbundle --plan`. Don't launch the team for real unless I say so.
|
||||
>
|
||||
> When it's clean, show me the check output, the folder tree, and the README's sections on what to do before
|
||||
> installing and on permissions. I decide whether to push it and submit it.
|
||||
@@ -14,7 +14,7 @@ it is the mechanism that makes shipped chain files (see
|
||||
|---|---|---|---|
|
||||
| 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 |
|
||||
| PostCompact | ✓ | ✓ | retain exact compaction due-state; deliver on the next actionable prompt |
|
||||
|
||||
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
|
||||
@@ -25,12 +25,27 @@ 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
|
||||
runtimes retain `PostCompact` due-state for the next prompt. After a managed Claude
|
||||
compaction, the acknowledgement-only boundary and other prompts (for up to 10 minutes)
|
||||
are skipped without consuming that state, so the hook delivers on the restore request
|
||||
(after 10 minutes, on the next prompt);
|
||||
that request asks the seat to read the refocusing skill and consume the delivered
|
||||
traces rather than rerun them, and the read-depth audit accounts for them. Otherwise the hook
|
||||
is a no-op (it writes a stderr advisory when the transcript shrinks or the
|
||||
session has no identity), and it degrades to silence on unrelated hook errors.
|
||||
Both runtimes stop the hook after 5 seconds, and the content REF lookup gets 2;
|
||||
a slow REF shows as `REFOCUS CONTENT REF FAILED`. 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.
|
||||
|
||||
Failed trace or configured-content resolution keeps refocus due. After three
|
||||
consecutive failed attempts for the same seat and occupant, the hook emits one
|
||||
idempotent issue-stream item tagged `issue,refocus`, visible through `rig stream
|
||||
list --tag refocus`. A successful delivery resets that failure episode. An
|
||||
unconfirmed stream write keeps the same item ID for the next failed attempt; it
|
||||
does not send a wake, change seat status, or prove the agent read the content.
|
||||
|
||||
Because the hook runs at the seat's own turn boundaries, delivery to a
|
||||
RUNNING seat needs no relaunch, no operator action, and no message traffic:
|
||||
the next prompt the seat processes carries the content. The latency bound is
|
||||
@@ -44,8 +59,9 @@ Resolution order:
|
||||
1. `OPENRIG_REFOCUS_CONTENT_REF` — a path-like context-library ref resolved
|
||||
through `rig context get`, so refocus receives the same assembled bytes as
|
||||
on-demand pull. This wins when REF and FILE are both set.
|
||||
2. `OPENRIG_REFOCUS_CONTENT_FILE` — an operator-authored file (per-seat or
|
||||
per-rig via spec env).
|
||||
2. `OPENRIG_REFOCUS_CONTENT_FILE` — an operator-authored file, set in the
|
||||
environment the seat's harness runs in (rig and agent specs have no `env`
|
||||
key).
|
||||
3. `$OPENRIG_HOME/refocus/REFOCUS.md` — the instance's standing content.
|
||||
4. The generic default at
|
||||
`skills/refocusing/references/refocus.md`: three project-neutral orientation
|
||||
@@ -73,12 +89,50 @@ 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.
|
||||
|
||||
## The work packet (switched off by default)
|
||||
|
||||
Set `OPENRIG_REFOCUS_WORK_PACKET=1` in a seat's harness environment so its
|
||||
refocus carries the work it is actually doing as text rather than as pointers.
|
||||
With the switch on:
|
||||
|
||||
- The hook asks `rig queue whoami --work-candidates` for labelled evidence.
|
||||
That is each in-progress row's own `mission:` tag (a slice tag is optional),
|
||||
or for an untagged row the nearest local handoff ancestor's mission, plus
|
||||
blocked rows as held work and pending rows as possible next work. The
|
||||
strict `currentWork` derivation is unchanged.
|
||||
- One direct, resolved tag is shown as the work, labelled with its source so
|
||||
the agent can correct a stale tag. Anything less certain lists every
|
||||
candidate's one-line intent and asks the agent to name its mission (or to say
|
||||
it has no current work), giving the `--work-start` route that then shows
|
||||
that mission's intent as text. The project-level chain appears only as
|
||||
labelled orientation.
|
||||
- A tag that does not resolve on this host is named, never replaced. A
|
||||
retargeted handoff or a remote ancestor gives no assumed mission. A mission
|
||||
named only in body text is not evidence.
|
||||
- The seat's `LEARNED.md` contributes its `MY JOB HERE` and `STANDING DUTIES`
|
||||
sections, and notes and each node's `PROGRESS.md` contribute their
|
||||
`Current state` section. When a file has
|
||||
no such heading, the packet says so and shows a bounded excerpt. No heading
|
||||
is required.
|
||||
- A notes or `LEARNED.md` file older than
|
||||
`OPENRIG_REFOCUS_NOTES_MAX_AGE_DAYS` (default 14) is named with its age, the
|
||||
age's source (`updated:` frontmatter, else mtime) and the threshold: a cue
|
||||
for judgment, not a staleness verdict.
|
||||
|
||||
`trace-to-root.py --packet --work-candidates <path|->` renders the same packet
|
||||
by hand.
|
||||
|
||||
## Relation to chain files
|
||||
|
||||
The chain files are the durable, altitude-addressed home of orientation
|
||||
content; the refocus channel is its delivery schedule. A practice added to a
|
||||
rig's `CRAFT.md` today reaches running seats through their next refocus
|
||||
pointer, and future installs through the shipped defaults
|
||||
rig's `LEARNED.md` today reaches running seats through their next refocus
|
||||
trace, which reads `LEARNED.md` at each topology level. The default light trace
|
||||
shows only about the first 800 characters of each `LEARNED.md` body, so a
|
||||
practice appended to a longer file reaches seats only with
|
||||
`OPENRIG_REFOCUS_DEPTH=full` or through the configured refocus content. A `CRAFT.md` practice
|
||||
reaches them only through the configured refocus content (REF or FILE), and future
|
||||
installs through the shipped defaults
|
||||
(discovery → curation → ship, per the convention doc).
|
||||
|
||||
## What a refocus is NOT
|
||||
|
||||
@@ -88,7 +88,11 @@ naming that exact delta AND a distinct successor delta file existing. A header a
|
||||
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.
|
||||
absorption is not proof of publication or served production adoption. The delta is
|
||||
`CAPABILITY-DELTA-v<version>.md` in the release mission folder (scaffolded by
|
||||
`rig scope mission create release-X.Y.Z`); its `expiry.canon_path` and
|
||||
`expiry.successor_path` name the two inputs, and `rig scope audit --mission <name>`
|
||||
reports `expired_capability_delta` once both hold.
|
||||
|
||||
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
|
||||
@@ -107,8 +111,8 @@ obligations start at ITS cut.
|
||||
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
|
||||
- A missing route is a release finding. Repair the smallest existing seam or record
|
||||
the evidenced gap as scoped follow-up work in the project; do not create a second
|
||||
capability registry, search service, or world store at the boundary.
|
||||
|
||||
## 8. What deliberately does NOT happen at the boundary
|
||||
|
||||
+292
-43
@@ -1,11 +1,71 @@
|
||||
# RigBundle Reference
|
||||
|
||||
Version: 2 (pod-aware)
|
||||
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`
|
||||
Last validated against code: 2026-10-07, at the 0.6.6 cut `2620dea8` (whole document at `a350c59b`; changes to its sources since then checked)
|
||||
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`, `packages/daemon/src/routes/bundles.ts`, `packages/cli/src/commands/bundle.ts`, `packages/daemon/src/domain/bundle-install-context.ts`, `packages/daemon/src/domain/bundle-source-resolver.ts`, `packages/daemon/src/domain/bootstrap-orchestrator.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.
|
||||
|
||||
## GitHub folder links
|
||||
|
||||
`rig bundle create`, `inspect`, `install`, and `rig up` accept a public HTTPS GitHub folder link:
|
||||
|
||||
```sh
|
||||
rig bundle create https://github.com/example/teams/tree/main/rigs/team -o team.rigbundle
|
||||
rig bundle inspect https://github.com/example/teams/tree/v1/rigs/team --json
|
||||
rig up https://github.com/example/teams/tree/COMMIT/rigs/team --target ./project
|
||||
```
|
||||
|
||||
Replace `COMMIT` with a full commit ID. Branches and tags are also accepted, including names containing `/`: they
|
||||
resolve once to a full commit before fetching. Each result names the resolved source, configuration ID, package digest
|
||||
and assembler. Save the printed commit-pinned URL to repeat the same source selection. Only a full 40-character commit
|
||||
ID counts as a commit; a shorter one is looked up as a branch or tag name. `--preset` and repeatable
|
||||
`--seat pod.member=runtime` select only configurations the folder declares. A link build takes the rig's `name` from
|
||||
its `rig.yaml` unless `--name` says otherwise, and is named `github-bundle` when `rig.yaml` has no readable name.
|
||||
|
||||
Link import needs a running, verified local daemon and a credential-free, publicly readable GitHub URL. It does not
|
||||
upload files to remote daemons. For a remote target, run the link command on that host, or create an archive, transfer
|
||||
it with its sibling digest, and use the existing path command there. Existing local path/name commands keep their
|
||||
dispatch. Importing does not install dependencies or execute repository scripts. Before packaging a link, it checks
|
||||
the checkout's symlinks (excluding Git metadata), member agent refs, agent imports and legacy package refs (including
|
||||
additional `--include-packages` entries): every target must resolve inside the fetched checkout. Shared agents and
|
||||
legacy packages outside the selected folder are allowed within that repository. Preset staging
|
||||
preserves that directory layout. Unresolved or escaping references stop the import and remove its temporary source.
|
||||
|
||||
The selected source is packaged through the existing bundler once. Link `up`/`install` then use the existing bundle
|
||||
install path, including compatibility and target-conflict checks. For a link, the default install target is the current
|
||||
directory; `--cwd` separately overrides seat working directories. Imported archives and source/build receipts remain
|
||||
under the selected OpenRig home's `bundle-imports/` directory for install history. If a response is lost, inputs stay
|
||||
available and the outcome is reported as unknown; inspect `rig ps` and `rig bundle history` before retrying.
|
||||
|
||||
An author's root `bundle.yaml` can declare `compatibility.min_cli_version` and `compatibility.min_daemon_version`.
|
||||
Link import passes these into create; explicit create minimum-version flags override them. A local-path create doesn't
|
||||
read them: it packages a minimum only from `--min-cli-version` or `--min-daemon-version`.
|
||||
|
||||
The package digest covers the packaged `integrity.files` entries, excluding `bundle.yaml`, ignored junk basenames,
|
||||
and file modes. It does not cover resources resolved on the installing host or prove what ran. The archive hash is
|
||||
separate. See [bundle formats](bundle-formats.md) for the exact coverage and identity definitions. Older archives can
|
||||
have unknown source, configuration or assembler; inspection does not fill those gaps with the inspecting daemon's
|
||||
identity.
|
||||
|
||||
## Advisory author check
|
||||
|
||||
```sh
|
||||
rig bundle check ./my-team --json
|
||||
```
|
||||
|
||||
This local, read-only check reports `openrig.bundle-standard/v1` with `pass`, `finding` or `not_checked` per rule. It
|
||||
checks pod-aware rig validity, README inclusion in `docs`, readable declared files, in-folder agent refs/imports,
|
||||
minimum-version shape, declared preset consistency and known sensitive filenames. It does not contact a daemon,
|
||||
build an archive, run preflight or launch anything. Findings give exit status 1; they are never an install gate. It
|
||||
reads the folder on disk, not a link or a commit, so files that aren't committed are checked too. Human output gives
|
||||
each rule's status, ID and reason, then the file it's about in parentheses when there is one; `--json` carries the
|
||||
same as a `path` field.
|
||||
|
||||
README completeness and arbitrary embedded secrets need human review and are reported as `not_checked`. A bounded
|
||||
or unreadable scan is also `not_checked`, never a clean scan. The check is advice for authors, not a security audit or
|
||||
listing approval.
|
||||
|
||||
---
|
||||
|
||||
## Archive Format
|
||||
@@ -34,7 +94,7 @@ The packer produces deterministic output:
|
||||
- Portable mode normalizes uid/gid/mode
|
||||
- Maximum gzip compression (level 9)
|
||||
|
||||
This means the same inputs always produce the same archive hash.
|
||||
Identical staged bytes (including manifest timestamps and provenance) produce the same archive hash. Use the package digest to compare packaged file content independently of manifest metadata.
|
||||
|
||||
---
|
||||
|
||||
@@ -55,6 +115,10 @@ agents/
|
||||
SKILL.md — skill files
|
||||
startup/
|
||||
context.md — startup files
|
||||
context-packs/<name>/ — a pack carried with --context-pack (its manifest.yaml and declared files)
|
||||
project/ — the project carried with --project-dir (project.yaml and files beside it)
|
||||
skills/<name>/ — a shared skill the author's bundle.yaml declares; other declared
|
||||
plugins, workflow specs, context packs and agent images keep their paths
|
||||
```
|
||||
|
||||
### Key rules
|
||||
@@ -98,6 +162,15 @@ agents:
|
||||
original_ref: "local:../../shared"
|
||||
hash: "abc123..."
|
||||
culture_file: CULTURE.md
|
||||
provenance:
|
||||
created_at: "2026-04-11T22:32:48.570Z"
|
||||
daemon_version: "0.6.6"
|
||||
cli_version: "0.6.6"
|
||||
# ... and, for a GitHub link, the source it was built from
|
||||
assembler:
|
||||
openrigVersion: "0.6.6"
|
||||
configuration:
|
||||
id: "dev.lead=claude-code,dev.researcher=codex"
|
||||
integrity:
|
||||
algorithm: sha256
|
||||
files:
|
||||
@@ -118,7 +191,14 @@ integrity:
|
||||
| `rig_spec` | string | yes | Relative path to the rig spec within the archive. Safe relative path. |
|
||||
| `agents` | AgentEntry[] | yes | Array of vendored agent entries. |
|
||||
| `culture_file` | string | no | Relative path to the culture file if present. |
|
||||
| `integrity` | Integrity | no | Per-file SHA-256 checksums for content verification. |
|
||||
| `integrity` | Integrity | no | Per-file SHA-256 checksums for content verification. Validation doesn't require it, but install refuses an archive without it and inspect reports integrity FAIL. |
|
||||
| `provenance` | object | no | Stated, not verified: `created_at`, `daemon_version`, `cli_version`, `source_host`, `author_session`, `source_rig_id`, `source_rig_name`, `notes`, and `source` for a GitHub link build. |
|
||||
| `assembler` | object | no | `openrigVersion` of the OpenRig that built the archive. |
|
||||
| `configuration` | object | no | The configuration ID the archive was built in, and `preset` when it matches a declared preset. Outside the package digest. |
|
||||
| `compatibility` | object | no | `min_cli_version` and `min_daemon_version`, checked on install. |
|
||||
| `preconditions` | list | no | The author's setup declarations, shown before install and never run (see [bundle formats](bundle-formats.md#declared-setup-preconditions)). |
|
||||
| `skills`, `plugins`, `workflow_specs`, `context_packs`, `agent_images` | lists | no | Contents from the author's `bundle.yaml` and `--context-pack`, routed into your libraries on install. |
|
||||
| `project` | object | no | `id` and `path` of the project carried with `--project-dir`. |
|
||||
|
||||
### Agent Entry Fields
|
||||
|
||||
@@ -127,9 +207,9 @@ integrity:
|
||||
| `name` | string | yes | Agent name (from agent.yaml). |
|
||||
| `version` | string | no | Agent version. |
|
||||
| `path` | string | yes | Relative path to the vendored agent directory. Safe relative path. |
|
||||
| `original_ref` | string | yes | The original `agent_ref` before rewriting. |
|
||||
| `original_ref` | string | written by create | The original `agent_ref` before rewriting. Validation doesn't require it. |
|
||||
| `hash` | string | yes | SHA-256 hash of the agent.yaml content. |
|
||||
| `import_entries` | ImportEntry[] | yes | Vendored imports for this agent (may be empty). |
|
||||
| `import_entries` | ImportEntry[] | written by create | Vendored imports for this agent (may be empty). Validation doesn't require it. |
|
||||
|
||||
### Import Entry Fields
|
||||
|
||||
@@ -146,7 +226,7 @@ integrity:
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `algorithm` | string | yes | Must be `sha256`. |
|
||||
| `files` | map<string, string> | yes | Map of archive-relative file path → SHA-256 hex hash. Every file in the archive (except `bundle.yaml` itself) should be listed. |
|
||||
| `files` | map<string, string> | yes | Map of archive-relative file path → SHA-256 hex hash. Every file in the archive except `bundle.yaml`, `.DS_Store`, `Thumbs.db` and `.gitkeep` is listed; an extra file that isn't listed fails verification. |
|
||||
|
||||
---
|
||||
|
||||
@@ -169,13 +249,15 @@ Future enhancement: cryptographic signing (Ed25519) for author authentication.
|
||||
The unpacker enforces these safety rules before extraction:
|
||||
|
||||
1. **No symlinks or hardlinks** — `SymbolicLink` and `Link` entries are rejected
|
||||
2. **No absolute paths** — entries starting with `/` are rejected
|
||||
3. **No path traversal** — entries containing `..` segments are rejected
|
||||
2. **No absolute paths** — entries starting with `/`, or carrying a Windows drive letter such as `C:\`, are rejected
|
||||
3. **No path traversal** — entries containing `..` segments are rejected, on either slash direction (`..` or `..\`)
|
||||
4. **Digest verification** — archive SHA-256 must match the sibling `.sha256` file
|
||||
5. **Content integrity** — after extraction, per-file hashes are verified against the manifest
|
||||
|
||||
If any check fails, extraction is aborted and an error is thrown.
|
||||
|
||||
`rig bundle inspect` applies the same unsafe-entry scan (rules 1-3) before it extracts into its temporary directory, so an archive the installer refuses for an unsafe entry is never reported as inspectable. Digest and content-integrity failures are a different case: inspect reports those in its result (`digestValid`, `integrityResult`) instead of refusing, which is what lets it describe a broken bundle rather than throw.
|
||||
|
||||
---
|
||||
|
||||
## CLI Surface
|
||||
@@ -190,78 +272,226 @@ where the daemon runs.
|
||||
### Create a bundle
|
||||
|
||||
```bash
|
||||
rig bundle create <spec-path> -o <output.rigbundle> [--rig-root <dir>] [--name <name>] [--bundle-version <ver>]
|
||||
rig bundle create <spec-path-or-github-link> -o <output.rigbundle> [--rig-root <dir>] [--preset <name>] [--seat <pod.member=runtime>]... [--context-pack <dir>]... [--project-dir <dir>] [--name <name>] [--bundle-version <ver>] [--min-cli-version <ver>] [--min-daemon-version <ver>] [--notes <text>] [--include-packages <refs...>] [--allow-drift] [--json]
|
||||
```
|
||||
|
||||
| Flag | Required | Default | Description |
|
||||
|------|----------|---------|-------------|
|
||||
| `<spec-path>` | yes | — | Path to the rig spec YAML file. |
|
||||
| `<spec-path-or-github-link>` | yes | — | Path to the rig spec YAML file, or a GitHub folder link (see "GitHub folder links" above). |
|
||||
| `-o, --output` | yes | — | Output path. Must end with `.rigbundle`. |
|
||||
| `--rig-root` | no | spec directory | Root directory for resolving `agent_ref` and other relative paths. |
|
||||
| `--name` | no | `my-bundle` | Bundle name in the manifest. |
|
||||
| `--preset <name>` | no | — | Build one of the configurations the bundle declares in `configurations.yaml` beside `rig.yaml` (see [bundle-formats.md](bundle-formats.md)). The chosen runtimes and profiles are applied to an owned copy of the rig folder, never to yours. |
|
||||
| `--seat <pod.member=runtime>` | no | — | Use this runtime for one seat, within what `configurations.yaml` allows. Repeatable; applied after `--preset`. An undeclared choice is refused with the allowed set, and nothing is built. |
|
||||
| `--context-pack <dir>` | no | — | Carry the context pack in `<dir>`. Repeatable. The directory may be outside the rig folder, for example a world pack whose `manifest.yaml` is at its repository root. Only `manifest.yaml` and the files it declares are carried, the same set `rig context add --git` installs, and the pack lands in the bundle at `context-packs/<manifest name>/`. Needs a pod-aware spec. |
|
||||
| `--project-dir <dir>` | no | — | Carry the project the rig works in: the folder holding its `project.yaml` (which must declare an `id`) and files beside it, such as `SPEC.md`. On install the project is registered in the workspace catalog and the rig is associated with it, before any seat launches (see [project-workspace.md](project-workspace.md)). Needs a pod-aware spec. |
|
||||
| `--name` | no | the rig's `name` in `rig.yaml`; `my-bundle` (`github-bundle` for a link) when it has no readable name | Bundle name in the manifest. |
|
||||
| `--bundle-version` | no | `0.1.0` | Bundle version in the manifest. |
|
||||
| `--min-cli-version`, `--min-daemon-version` | no | — | The oldest OpenRig CLI or daemon that may install the bundle, checked on install. For a link they override the author's `bundle.yaml` minimums. |
|
||||
| `--notes <text>` | no | — | Notes stored in the manifest's provenance. |
|
||||
| `--include-packages <refs...>` | no | all from the spec | Legacy (schema 1) specs only: the package refs to include. |
|
||||
| `--allow-drift` | no | off | Build a spec that differs from the running rig of the same name. Without it, create refuses; with it, the difference is written into the manifest's provenance notes. |
|
||||
| `--json` | no | off | Machine-readable output. |
|
||||
|
||||
`--context-pack` and `--project-dir` exist only on create. `rig up` and `rig bundle install` have neither, so a bundle
|
||||
installed straight from a GitHub link carries no project, and carries a context pack only when the folder's own
|
||||
`bundle.yaml` declares one inside the folder.
|
||||
|
||||
The create command:
|
||||
1. Validates the rig spec
|
||||
2. Resolves all `agent_ref` paths and their imports
|
||||
3. Vendors all agent specs, resources, and startup files into a staging directory
|
||||
4. Rewrites `agent_ref` paths to bundle-relative `local:` refs
|
||||
5. Collects culture file, docs files, and rig-level startup files
|
||||
6. Computes per-file integrity hashes
|
||||
7. Writes the manifest (`bundle.yaml`)
|
||||
8. Packs into a deterministic `.tar.gz`
|
||||
9. Writes the sibling `.sha256` digest
|
||||
1. Validates the rig spec, and refuses a spec that differs from the running rig of the same name (see `--allow-drift`)
|
||||
2. Collects the culture file, docs files, and rig-, pod- and member-level startup files
|
||||
3. Resolves all `agent_ref` paths and their imports, vendors the agent specs and their resources, and rewrites the refs to bundle-relative `local:` paths
|
||||
4. Adds the author's `bundle.yaml` contents (preconditions, skills, plugins, workflow specs, context packs, agent images), the carried packs and project, and the configuration and assembler stamps
|
||||
5. Computes per-file integrity hashes, refusing sensitive paths (such as `.env`, `*.pem`, `*.key`, `*.p12`, `credentials.*` and `tokens.*`), and writes the manifest (`bundle.yaml`)
|
||||
6. Refuses staged content that can't be shipped publicly
|
||||
7. Packs into a deterministic `.tar.gz` and writes the sibling `.sha256` digest
|
||||
|
||||
### List a bundle's configurations
|
||||
|
||||
```bash
|
||||
rig bundle configurations <spec-path> [--json]
|
||||
```
|
||||
|
||||
Lists the presets that `configurations.yaml` declares, each with its configuration ID, which one is recommended, and which one matches `rig.yaml` as written.
|
||||
|
||||
### Inspect a bundle
|
||||
|
||||
```bash
|
||||
rig bundle inspect <bundle-path> [--json]
|
||||
rig bundle inspect <bundle-path-or-github-link> [--preset <name>] [--seat <pod.member=runtime>]... [--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 or launch anything.
|
||||
Shows the manifest, the archive's identity (source and commit for a link, configuration, package SHA-256 and its
|
||||
coverage, assembler, archive SHA-256), digest validity, the integrity result, any carried context packs and project,
|
||||
and then the behaviour view. Inspect extracts the archive into a temporary directory for safe validation, then cleans
|
||||
that directory up. It does not install or launch anything. It exits 2 when the digest or integrity check fails.
|
||||
`--preset` and `--seat` choose a declared configuration of a GitHub link.
|
||||
|
||||
The behaviour view describes the selected archive: its team and configured models, declared permission posture,
|
||||
files agents receive, startup actions and hooks, managed writes, literal outside domains, prerequisites and what
|
||||
remains unknown until launch. It reads packaged declarations; it does not run scripts, contact providers, probe
|
||||
runtimes or create a bootstrap run. Source and assembler identity come from the archive, separately from the view
|
||||
generator. Configured models are not observations of a running model, and literal domains are not predicted traffic.
|
||||
|
||||
Under "Needs (not checked)" the view lists the author's setup preconditions, with their commands marked "not run; one
|
||||
shell, in order". For a declared full-bypass Claude seat it notes that Claude Code asks once to accept its bypass
|
||||
warning, and when any Claude or Codex seat is full bypass it says that `--non-interruptive` is available (see
|
||||
[non-interruptive mode](non-interruptive-mode.md)). It doesn't select the mode or check whether you've already
|
||||
accepted the warning. When the bundle's `rig.yaml` declares `non_interruptive: true`, the view instead says that the
|
||||
bundle declares non-interruptive launches for those seats, with broad file, command and network access, and that
|
||||
`--no-non-interruptive` overrides it; the asks-once note is left out.
|
||||
|
||||
`rig bundle install` and bundle-form `rig up` print this same view to stderr before the existing action, including
|
||||
`--plan`. With `--json`, stdout remains one result; its optional `behaviour` field carries the structured view.
|
||||
GitHub links reuse the single prepared archive. The header is informational: missing views and inspection errors
|
||||
print a diagnostic, then the original installation checks and result determine success. Legacy schema-1 archives
|
||||
report `not_generated`, rather than an empty team. A local archive can change between inspection and installation;
|
||||
the header does not bind a later action atomically to those bytes.
|
||||
|
||||
Agents can run shell commands as the launching user, subject to runtime and host policy. Explicit permission bypass
|
||||
is distinguished from conditional launch floors; Pi resource trust is separate from native permission policy.
|
||||
Host settings, accounts, plugins, runtime support and effective permissions remain unknown. Text scanning is bounded
|
||||
and reports unread members; dynamically computed commands and addresses cannot be predicted. Installation targets,
|
||||
`--cwd` and configured library roots resolve separately from the archive's declarations. See
|
||||
[bundle formats](bundle-formats.md) for the structured view and digest coverage.
|
||||
|
||||
**Integrity means the archive is self-consistent, not who made it. Provenance is stated by the bundle and not verified.**
|
||||
|
||||
|
||||
### Install a bundle
|
||||
|
||||
```bash
|
||||
rig bundle install <bundle-path> [--plan] [--yes] [--target <root>] [--json]
|
||||
rig bundle install <bundle-path-or-github-link> [--plan] [--yes] [--target <root>] [--cwd <dir>] [--preset <name>] [--seat <pod.member=runtime>]... [--non-interruptive | --no-non-interruptive] [--skip-version-check] [--force] [--json]
|
||||
```
|
||||
|
||||
| Flag | Required | Default | Description |
|
||||
|------|----------|---------|-------------|
|
||||
| `<bundle-path>` | yes | — | Path to the `.rigbundle` file. |
|
||||
| `--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. |
|
||||
| `<bundle-path-or-github-link>` | yes | — | Path to the `.rigbundle` file, or a GitHub folder link. |
|
||||
| `--plan` | no | `false` | Preview without installing or launching. Not side-effect free: it records a bootstrap run and runs the preflight, which for a pod-aware bundle runs `pi --version` or `omp --version` for Pi or OMP seats and `codex -p <profile> mcp list` for a Codex seat that names a `codex_config_profile`. It writes nothing to the target. |
|
||||
| `--yes` | no | `false` | Auto-approve trusted actions during apply mode. |
|
||||
| `--target <root>` | yes in apply mode | — | Directory the bundle is installed into and launched from. Required unless `--plan` is used. |
|
||||
| `--target <root>` | yes in apply mode, for an archive path | the current directory for a link | Directory the bundle is installed into and launched from. Required for an archive path unless `--plan` is used. |
|
||||
| `--cwd <dir>` | no | — | Working directory for every launched member, for this install only (for example, the repository the rig works on). Does not change the install target. |
|
||||
| `--preset <name>`, `--seat <pod.member=runtime>` | no | — | For a GitHub link, the declared configuration to build and install. |
|
||||
| `--non-interruptive` | no | the bundle `rig.yaml`'s `non_interruptive`, else the operator setting `launch.non_interruptive`, else off | Accept the harnesses' first-launch warnings for this rig's full-bypass Claude and Codex seats with launch flags, writing nothing to your settings. The choice is saved on the rig. `--no-non-interruptive` turns a saved choice off; stop a running rig with `rig down` first. See [non-interruptive mode](non-interruptive-mode.md). |
|
||||
| `--skip-version-check` | no | off | Skip the compatibility check below. Not for routine use. |
|
||||
| `--force` | no | off | Skip the running-team check below. Not for routine use. A pod-aware bundle is still refused before target files are written when a team with the same name is running; a legacy (schema 1) bundle can be left partly installed. |
|
||||
| `--json` | no | `false` | Emit machine-readable JSON. |
|
||||
|
||||
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`.
|
||||
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>` for an archive path 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:
|
||||
Before it plans or applies, install runs two checks on the archive, both also under `--plan`:
|
||||
- **compatibility:** the manifest's `min_daemon_version` and `min_cli_version` against this daemon and CLI. A failure
|
||||
reports "Bundle compatibility check failed" with the versions involved.
|
||||
- **running team:** a running team with the bundle's rig name refuses the install with "Bundle install conflict check
|
||||
failed". The error names the installed and the offered team and lists three choices: use the existing team, stop it
|
||||
with `rig down` and retry to replace it, or cancel. This check does not refuse a stopped team with that name.
|
||||
|
||||
- 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.
|
||||
Each error lists its fixes; the compatibility error also names `--skip-version-check`. `--skip-version-check` and
|
||||
`--force` skip only these two checks: the manifest safety check of its `rig_spec` and `project` entries always runs.
|
||||
|
||||
Legacy (schema version 1) bundles keep their old behavior: `--target` is only where packages are installed.
|
||||
For a pod-aware (schema version 2) bundle, installing a team whose rig name matches a stopped team replaces it: the
|
||||
earlier team is archived when the new one is created, and the result gives the `rig unarchive <id>` command that
|
||||
restores it. If OpenRig cannot confirm the earlier team is stopped, install refuses with `generation_unconfirmed` and
|
||||
creates no replacement team. For an install into that team's own install folder, OpenRig makes this check before
|
||||
writing any files; for any other target, bundle files already copied there stay.
|
||||
|
||||
A legacy (schema version 1) bundle does not replace a stopped team. Its approved external and package install steps run
|
||||
first, and then its rig-name check fails preflight with "Rig name '<name>' already exists".
|
||||
|
||||
For a pod-aware (schema version 2) bundle, apply copies every extracted file (`bundle.yaml`, `rig.yaml`, `agents/`, culture and docs files, and any carried `project/`, `context-packs/` and author-declared contents) 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: "."` starts in the target (every pod-aware member must declare a `cwd`);
|
||||
- an absolute member `cwd` stays as authored, and `--cwd <dir>` (on `rig bundle install` or `rig up`) 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 target files don't cause a target-content conflict; the other installation checks above, including the running-team check, still apply. Use an empty or dedicated directory as the target.
|
||||
- exception: reinstalling over a stopped team into that team's own install folder (the target's `bundle.yaml` is schema version 2 with the same bundle name, or with `my-bundle` or `github-bundle`, the default names before 0.6.7) replaces its files there. Each differing path is first copied under `<OpenRig home>/bundle-backups/reinstall-*/files/`, with a `RESTORE.json` beside it; other files in the folder are kept. The files stay installed even if a later launch fails.
|
||||
|
||||
The bundle's declared skills, plugins, workflow specs, context packs and agent images are routed into your libraries **before any member launches**, and the context-pack library is rescanned, so a member's first turn can already read a pack the bundle carried. A routing failure does not stop the install: it is printed as a warning, returned in `routingFailures`, and recorded in the install audit. The human output lists what each declared kind routed and any entry that was not routed.
|
||||
|
||||
A carried pack is routed into `context.root` under its folder name in the archive: for a pack carried with `--context-pack`, that is its manifest name (`context-packs/<name>/`); for a pack the author's `bundle.yaml` declares, it is the basename of the declared folder, whatever the manifest says. If a pack of that name is already installed, install never merges into it: an identical pack is reported as `already_installed`, and a different one (for example a `rig context add --git` install at another revision) is kept unchanged and reported as `kept_existing`, with the `rig context rm <name>` command to use the bundle's copy instead.
|
||||
|
||||
A carried project is registered as `registered` (a new catalog entry), `associated` (the rig added to an existing entry), `already_registered` or `conflict`. A conflict writes nothing to the catalog and is reported as a routing failure; the install continues. A project folder that already exists with different files is kept unchanged.
|
||||
|
||||
### Skills shared by bundle profiles
|
||||
|
||||
For skills shared by several agents, place each complete skill directory at
|
||||
`skills/<name>/` beside the rig spec and declare its entry point in the source
|
||||
`bundle.yaml`:
|
||||
|
||||
```yaml
|
||||
skills:
|
||||
- skills/review-work/SKILL.md
|
||||
```
|
||||
|
||||
`rig bundle create` includes that directory's helper scripts and references as
|
||||
well as `SKILL.md`. The skill needs valid `name` and `description` frontmatter;
|
||||
the name is the ID a profile selects with `uses.skills: [review-work]`.
|
||||
Undeclared sibling directories are not bundled. Other declared skill file paths
|
||||
retain their single-file behavior.
|
||||
|
||||
Claude, Codex and Pi profiles can select this installed bundle pool even when
|
||||
their working directory is elsewhere. Explicit AgentSpec resources keep priority
|
||||
over a discovered skill with the same ID. Pi projects selected skills into its
|
||||
seat's agent directory; it does not scan Claude or Codex home/workspace pools.
|
||||
Installing the bundle does not add these skills to the operator's managed
|
||||
`skills.root` catalog. Profiles that already get a skill from a plugin should not
|
||||
also select its bundle copy.
|
||||
|
||||
When a profile selects a skill from inside its agent spec's folder (for example the
|
||||
installed bundle) and the managed catalog holds a different copy with the same ID,
|
||||
the bundle's copy is used. Launch warns `skill_bundle_precedence` and leaves the
|
||||
catalog unchanged. The skill's `SKILL.md` `name` must equal the selected ID. A
|
||||
differing copy from anywhere else fails launch with `skill_identity_conflict`.
|
||||
|
||||
Legacy (schema version 1) bundles keep their old behavior: `--target` is only where packages are installed, and their declared contents are routed after a completed install.
|
||||
|
||||
### Launch directly
|
||||
|
||||
```bash
|
||||
rig up <bundle-path> [--target <root>] [--cwd <dir>]
|
||||
rig up <bundle-path-or-github-link> [--target <root>] [--cwd <dir>] [--plan] [--yes] [--preset <name>] [--seat <pod.member=runtime>]... [--non-interruptive | --no-non-interruptive] [--host <id>] [--json]
|
||||
```
|
||||
|
||||
`rig up` auto-detects `.rigbundle` files and routes them through the bundle bootstrap path.
|
||||
`rig up` auto-detects `.rigbundle` files and GitHub folder links and prints the behaviour view before it acts. The two
|
||||
take different routes:
|
||||
- **A GitHub link** builds the archive and installs it through the `rig bundle install` path, with its compatibility
|
||||
and rig-name checks and the install audit.
|
||||
- **A local `.rigbundle` path** goes through the bundle bootstrap path directly. It skips the compatibility check,
|
||||
isn't recorded in the install audit (so `rig bundle history` doesn't list it), and doesn't print the routing summary.
|
||||
For a pod-aware archive, a running team with the same name is still refused before target files are written, and a
|
||||
stopped one is replaced as described under "Install a bundle". A legacy (schema 1) archive runs its approved install
|
||||
steps before its rig-name check, which then refuses a running team and fails preflight for a stopped one; it also
|
||||
gets no post-install routing this way. To get the checks and the audit for a local archive, use `rig bundle install`
|
||||
or `rig bootstrap`.
|
||||
|
||||
`rig bootstrap <bundle-path>` also accepts archives and uses the ordinary bundle
|
||||
install path, including compatibility checks and the install audit. Use `--plan`
|
||||
to preview, or `--target <root>` to choose the persistent install directory. Its
|
||||
default target is the caller's current directory; `--cwd` only changes the agents'
|
||||
working directory. YAML files and library spec names keep the spec bootstrap path.
|
||||
It prints no behaviour view and has no `--non-interruptive` option; a bundle's declared `non_interruptive` still applies.
|
||||
|
||||
- `--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
|
||||
- if `--target` is omitted for a `.rigbundle` or a link, 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, and an omitted `--target` is refused there ("targetRoot is required for bundle apply mode"). GitHub links don't work with `--host`.
|
||||
- `--cwd <dir>` does **not** change the install target; it only overrides the launched members' working directory for that run
|
||||
- `--non-interruptive` works as on `rig bundle install`
|
||||
- a schema-version-2 bundle's declared contents are routed before any member launches, as with `rig bundle install`
|
||||
|
||||
### Install history
|
||||
|
||||
```bash
|
||||
rig bundle history [--rig <name>] [--since <iso>] [--json]
|
||||
```
|
||||
|
||||
Lists the install audit records in `bundle-audit.jsonl` in the OpenRig home (default `~/.openrig`): when, the outcome,
|
||||
the rig and the bundle path.
|
||||
`--rig` filters by target rig name and `--since` by install time. Installs through `rig bundle install`,
|
||||
`rig bootstrap <archive>` and `rig up <link>` are recorded; `rig up <local archive>` isn't.
|
||||
|
||||
---
|
||||
|
||||
## Assembly Process
|
||||
|
||||
When `rig bundle create` runs, the `PodBundleAssembler` performs these steps:
|
||||
When `rig bundle create` runs, the `PodBundleAssembler` stages the files (steps 1-4). The create route then adds the
|
||||
author's `bundle.yaml` contents, the carried packs and project, and the configuration and assembler stamps, and does
|
||||
steps 5-7:
|
||||
|
||||
1. **Parse and validate** the rig spec
|
||||
2. **Collect rig-level files:**
|
||||
@@ -305,8 +535,11 @@ When an agent spec has `imports`, each import is resolved, vendored into the bun
|
||||
5. `rig_spec` is required and must be a safe relative path
|
||||
6. `agents` must be an array
|
||||
7. Each agent must have `name`, `path` (safe relative), and `hash`
|
||||
8. Integrity `algorithm` must be `sha256`
|
||||
9. Integrity `files` must be a non-empty map of safe-relative-path → 64-char hex hash
|
||||
8. Optional blocks, when present, must be well formed: `provenance`, `compatibility`, `skills`, `plugins`,
|
||||
`workflow_specs`, `context_packs`, `agent_images`, and `project` (an `id` made of letters, digits, `.`, `_` and `-`,
|
||||
and a safe relative `path`)
|
||||
|
||||
The manifest validator doesn't check `integrity`; unpack does (below).
|
||||
|
||||
### Archive safety (enforced on unpack)
|
||||
|
||||
@@ -314,7 +547,7 @@ When an agent spec has `imports`, each import is resolved, vendored into the bun
|
||||
2. No absolute paths
|
||||
3. No `..` path traversal
|
||||
4. Archive digest must match sibling `.sha256`
|
||||
5. Per-file content hashes must match integrity section
|
||||
5. The manifest must have an integrity section, and per-file content hashes must match it
|
||||
|
||||
### Assembly validation
|
||||
|
||||
@@ -322,6 +555,10 @@ When an agent spec has `imports`, each import is resolved, vendored into the bun
|
||||
2. All `agent_ref` paths must resolve to valid agent specs
|
||||
3. All declared `docs` files must exist on disk (missing docs fail assembly)
|
||||
4. Culture file and startup files are collected best-effort (missing = skipped)
|
||||
5. A declared skill that's missing or unreadable is a warning, written into the manifest's provenance notes; it
|
||||
doesn't stop the build
|
||||
6. Sensitive paths ("Sensitive paths detected in bundle") and content that can't be shipped publicly ("Public artifact
|
||||
substance refusal") stop the build
|
||||
|
||||
---
|
||||
|
||||
@@ -336,6 +573,9 @@ Key differences from v2:
|
||||
- No `import_entries` in package entries
|
||||
- Legacy rig spec format (flat nodes, not pods)
|
||||
|
||||
The v1 validator checks a `project` block the same way as v2, so a v1 bundle can't write outside the projects root or
|
||||
read outside the bundle.
|
||||
|
||||
---
|
||||
|
||||
## Example: Creating and Using a Bundle
|
||||
@@ -360,11 +600,18 @@ Bundle created: my-team.rigbundle
|
||||
rig bundle inspect my-team.rigbundle
|
||||
```
|
||||
|
||||
Output:
|
||||
The manifest summary and identity lines are followed by the behaviour view:
|
||||
```
|
||||
Bundle: my-team v1.0.0
|
||||
Configuration: dev.lead=claude-code,dev.researcher=codex
|
||||
Package SHA-256: 3f9a...
|
||||
Coverage: openrig.package-digest/v1; packaged files only; excludes bundle.yaml, ignored junk and file modes; not host-resolved resources or execution.
|
||||
Assembler: OpenRig 0.6.6
|
||||
Archive SHA-256: a1b2c3d4...
|
||||
Digest valid: true
|
||||
Integrity: PASS
|
||||
What this bundle declares before installation:
|
||||
...
|
||||
```
|
||||
|
||||
### Install and launch
|
||||
@@ -380,4 +627,6 @@ Equivalent explicit form:
|
||||
rig up /path/to/my-team.rigbundle --target ~/projects/my-project
|
||||
```
|
||||
|
||||
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.
|
||||
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 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.
|
||||
|
||||
A running team must be stopped before reinstalling. Reinstalling into a stopped team's recorded install folder preserves conflicting files in the instance's `bundle-backups` directory; the result names the backup. The folder must still contain that bundle's installed manifest. A copied same-name manifest in another folder does not authorize replacing its conflicting files. Other targets retain first-install behavior: different files at the same paths are refused before writing. New installations record the canonical target folder for each generation, independently of `--cwd`. Older generations and new generations created directly from `rig.yaml` have no recorded folder and retain the manifest-only check until their next successful bundle installation, so a copied same-name manifest can still qualify for those generations.
|
||||
+217
-38
@@ -1,13 +1,16 @@
|
||||
# RigSpec Reference
|
||||
|
||||
Version: 0.2 (pod-aware)
|
||||
Last validated against code: 2026-04-11
|
||||
Source of truth: `packages/daemon/src/domain/rigspec-schema.ts`, `packages/daemon/src/domain/types.ts`
|
||||
Last validated against code: 2026-10-07, at the 0.6.6 cut `2620dea8` (whole document at `fcaf1f8e`; changes to its sources since then checked)
|
||||
Source of truth: `packages/daemon/src/domain/rigspec-schema.ts`, `packages/daemon/src/domain/types.ts`, `packages/daemon/src/domain/startup-validation.ts`, `packages/daemon/src/domain/permission-policy/policy-ref.ts`, `packages/daemon/src/domain/profile-resolver.ts`, `packages/daemon/src/domain/rigspec-preflight.ts`, `packages/daemon/src/adapters/kernel-authority.ts`, `packages/daemon/src/domain/native-permission-store.ts`, `packages/daemon/src/domain/codex-team-workspace.ts`
|
||||
|
||||
This is the canonical reference for the pod-aware RigSpec YAML format. Every field, validation rule, and default documented here was traced from the actual parser and validator code, not from prior documentation.
|
||||
|
||||
---
|
||||
|
||||
For choosing IDs, see [naming rigs, pods and seats](topology-naming.md). Keep the same
|
||||
logical names in edges, startup instructions, workflow targets and runtime presets.
|
||||
|
||||
## Minimal Valid Example
|
||||
|
||||
```yaml
|
||||
@@ -18,7 +21,7 @@ pods:
|
||||
- id: dev
|
||||
label: Development
|
||||
members:
|
||||
- id: impl
|
||||
- id: build
|
||||
agent_ref: "local:agents/impl"
|
||||
profile: default
|
||||
runtime: claude-code
|
||||
@@ -105,7 +108,7 @@ pods:
|
||||
required: true
|
||||
actions: []
|
||||
members:
|
||||
- id: impl
|
||||
- id: build
|
||||
agent_ref: "local:agents/impl"
|
||||
profile: default
|
||||
runtime: claude-code
|
||||
@@ -121,7 +124,7 @@ pods:
|
||||
applies_on: [fresh_start]
|
||||
actions:
|
||||
- type: send_text
|
||||
value: "Load the implementation-pair skill and begin."
|
||||
value: "Read your guidance and begin."
|
||||
phase: after_ready
|
||||
idempotent: true
|
||||
- id: qa
|
||||
@@ -131,7 +134,7 @@ pods:
|
||||
cwd: "."
|
||||
edges:
|
||||
- kind: delegates_to
|
||||
from: impl
|
||||
from: build
|
||||
to: qa
|
||||
|
||||
- id: rev
|
||||
@@ -152,13 +155,13 @@ pods:
|
||||
edges:
|
||||
- kind: delegates_to
|
||||
from: orch.lead
|
||||
to: dev.impl
|
||||
to: dev.build
|
||||
- kind: delegates_to
|
||||
from: orch.peer
|
||||
to: dev.qa
|
||||
- kind: can_observe
|
||||
from: rev.r1
|
||||
to: dev.impl
|
||||
to: dev.build
|
||||
- kind: can_observe
|
||||
from: rev.r2
|
||||
to: dev.qa
|
||||
@@ -170,15 +173,17 @@ edges:
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `version` | string | yes | — | Must be `"0.2"` for pod-aware specs. |
|
||||
| `version` | string | yes | — | Use `"0.2"`. Any non-empty string validates; a spec is treated as pod-aware because `pods` is an array. |
|
||||
| `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`. |
|
||||
| `summary` | string | no | — | Human-readable description. Shown in the spec library, review surfaces and `rig specs preview` (in `rig specs show` only with `--json`). |
|
||||
| `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: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. |
|
||||
| `non_interruptive` | boolean | no | — | Pod-aware rig launch-warning default. With full-bypass Claude/Codex seats, true accepts or hides supported harness warnings using launch flags. Explicit CLI choices override this field; this field overrides the machine default. It does not grant permissions, change native settings files, or bypass login. |
|
||||
| `permission_policy` | string | no | — | Permission policy attached to the rig. Either a built-in (`builtin:locked`, `builtin:standard`, `builtin:open`, `builtin:yolo`, `builtin:auto`) or a safe relative path to a custom policy file (resolved from this spec's directory; no `..`, no absolute, no empty segments, each segment `[A-Za-z0-9][A-Za-z0-9._-]*`), or `none`, a recorded choice of the floor. Absent uses the default floor with the scoped team launch allowances described below. A bare built-in name such as `yolo` is refused ("use 'builtin:yolo'"), and so is an explicit `null`. A custom file that is missing, unreadable or invalid isn't a validation error: it resolves to the floor, and preflight warns. A member may set its own `permission_policy` (not on a terminal member), which takes precedence over the rig-level one; there is no pod level. 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, Pi and OMP members use `AGENTS.md`. See "Choosing the Claude instruction file" below. |
|
||||
| `workspace` | object | no | — | The rig's workspace: `workspace_root` (required), `repos[]` of `{name, path, kind}` with `kind` one of `user`, `project`, `knowledge`, `lab` or `delivery` and unique names, an optional `default_repo` naming one of them, and an optional `knowledge_root`. Relative repo paths resolve against `workspace_root`. |
|
||||
| `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. |
|
||||
| `services` | ServicesBlock | no | — | Optional managed services (Docker Compose). When present, `rig up` boots them before any agent launches; `rig import` doesn't boot them. |
|
||||
| `pods` | Pod[] | yes | — | At least one pod required. Each pod is a bounded context containing members and pod-local edges. |
|
||||
| `edges` | CrossPodEdge[] | no | `[]` | Cross-pod edges connecting members in different pods. Must use fully-qualified `pod.member` IDs. |
|
||||
|
||||
@@ -194,7 +199,7 @@ permission_policy: builtin:standard
|
||||
permission_policy: policies/my-cautious-dev.policy.md
|
||||
```
|
||||
|
||||
Built-in policies (`locked` / `standard` / `open` / `yolo`) are read-only and are
|
||||
Built-in policies (`locked` / `standard` / `open` / `yolo` / `auto`) 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
|
||||
@@ -211,6 +216,124 @@ 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).
|
||||
|
||||
### Team launch defaults
|
||||
|
||||
A non-kernel Claude or Codex seat with no authored permission policy, explicit
|
||||
seat permission selection, or named Codex profile receives the team launch
|
||||
default. An explicit policy, including `none` or `builtin:locked`, keeps its
|
||||
existing meaning. The default is derived again for fresh launch, resume, fork
|
||||
and handover; it is not saved as a user choice. Kernel, Pi and terminal behavior
|
||||
is unchanged.
|
||||
|
||||
Claude keeps `acceptEdits` and receives inline session settings allowing ordinary
|
||||
`rig` commands, project reads and common project test commands (for example,
|
||||
`npm test`, `pnpm test`, `pytest`, `go test` and `cargo test`). Lifecycle commands
|
||||
such as `rig up`, `rig down`, `rig restore`, `rig bundle install` and seat stop or
|
||||
handover ask through a session `PreToolUse` hook. Their literal help forms, such
|
||||
as `rig down --help`, `rig bundle install -h` and `rig help down`, run without
|
||||
that lifecycle prompt. Native personal, project and managed deny/ask rules still
|
||||
apply; OpenRig does not remove or override them.
|
||||
|
||||
For allowances, the hook recognizes literal command words (including quoted or absolute paths),
|
||||
leading environment assignments that do not change executable lookup or startup,
|
||||
and `env`, `command` and `exec` wrappers without options other than `--`.
|
||||
Relative executable paths and assignments such as `PATH` or `NODE_OPTIONS` stay
|
||||
with native checks. The same existing team allowances apply after spelling
|
||||
normalization; project `node_modules/.bin/vitest` and `jest` paths match their
|
||||
existing `npx` allowances. It does not automatically allow pipelines, command
|
||||
substitutions, redirects, heredocs, shell functions or other unrecognized syntax.
|
||||
Lifecycle prefixes are checked at every word position, including after wrappers,
|
||||
control-flow words and CLI argument separators. A `--` separator before or after
|
||||
the subcommand ends option parsing, so a later `--help` is an operand and still
|
||||
asks. Ask detection also scans with comment text retained, so a `#` inside a
|
||||
substitution does not hide a later lifecycle command. Unquoted prose or comments
|
||||
containing a lifecycle command may also ask; ordinary trailing comments do not
|
||||
change allowance matching. Quoted messages and quoted heredoc bodies remain data.
|
||||
Arithmetic shifts are not treated as heredoc operators. These are command
|
||||
allowances, not containment: a project's test command can execute code. The
|
||||
hook and allowances are passed with `--settings` at launch, including resume
|
||||
and fork; nothing is written to personal or project permission files.
|
||||
|
||||
If the helper asset is missing at launch, OpenRig falls back to the native lifecycle
|
||||
ask rules; help can prompt in that fallback. A helper that disappears or times out
|
||||
after launch cannot supply a decision, leaving Claude's remaining native rules
|
||||
in effect. The hook is a convenience policy, not a containment boundary.
|
||||
|
||||
Codex keeps `workspace-write` with its existing approval policy and receives the
|
||||
configured OpenRig workspace root plus its pod's shared state directory as
|
||||
additional writable directories. The team workspace preparer creates missing
|
||||
directory ancestors and excludes the home directory and its ancestors from its
|
||||
prepared scopes. Unavailable optional directories are warned about and omitted
|
||||
from that list without blocking launch. When no prepared scopes remain, the
|
||||
runtime adapter retains its existing pod-state `--add-dir` fallback, which this
|
||||
step does not prepare. This does not modify Codex permission files or enable
|
||||
non-interruptive mode.
|
||||
|
||||
**Codex lifecycle commands do not gain a new approval rule.** With Codex's
|
||||
existing `on-request` policy, non-escalated sandboxed commands, including
|
||||
`rig up` and `rig down`, can run without asking. This default addresses writable
|
||||
workspace access; it does not promise a lifecycle confirmation prompt.
|
||||
|
||||
### Built-in permission policies
|
||||
|
||||
The five built-ins live in `packages/daemon/policies/builtin/`. Action names are the
|
||||
policies' own semantic classes.
|
||||
|
||||
| Policy | Surface | Runs without asking | Asks a person | Denied |
|
||||
|--------|---------|---------------------|---------------|--------|
|
||||
| `builtin:locked` | config | `run_toolchain` (npm, node, tsc, tests, lint), `rig_up`, `rig_down` | nothing | everything else (`default_posture: deny`) |
|
||||
| `builtin:standard` | config | everything not listed, including `push_to_remote` (`default_posture: allow`) | `create_pr`, `publish_package`, `merge_or_release`, `force_push`, and the destructive class | nothing |
|
||||
| `builtin:open` | config | everything, including PRs, publishing, merges and force pushes (`default_posture: allow`) | the destructive class only | nothing |
|
||||
| `builtin:yolo` | flag (`launch_posture: full_bypass`) | everything; the runtime's permission prompts are bypassed at launch | nothing | nothing |
|
||||
| `builtin:auto` | flag (`launch_posture: auto`) | Claude runs with `--permission-mode auto`; Codex, Pi and OMP launch at the floor | Claude: decided by auto mode; others: as at the floor | Claude: decided by auto mode; others: as at the floor |
|
||||
|
||||
The destructive class is `delete_everything`, `drop_persistent_store` and
|
||||
`reset_or_discard_vcs`; `builtin:locked`'s list also names `delete_files` and `force_push`.
|
||||
|
||||
**At launch.** `builtin:yolo` selects Claude `--dangerously-skip-permissions`, Codex
|
||||
`-s danger-full-access -a never`, and Pi `--approve`. `builtin:auto` selects Claude
|
||||
`--permission-mode auto`, while Codex and Pi do not have an auto mode and launch at the floor.
|
||||
Every other seat launches at the floor (a non-kernel seat with no policy also gets the
|
||||
[team launch default](#team-launch-defaults) above):
|
||||
- Claude `--permission-mode acceptEdits`;
|
||||
- Codex `-s workspace-write`, or `-p <profile>` when the member sets
|
||||
`codex_config_profile`, in which case the profile governs its own sandbox;
|
||||
- Pi `--no-approve` by default.
|
||||
|
||||
Seats of the rig named `kernel` are the exception. With no member or rig policy, no
|
||||
per-seat choice and (for Codex) no named profile, Claude launches in `acceptEdits` with a
|
||||
per-launch `--settings` allow list for its file tools and operational commands, and Codex
|
||||
launches with `-s danger-full-access -a never`, plus per-launch `-c` overrides that hide its
|
||||
full-access and GPT-5.1 migration notices, whatever the rig's non-interruptive choice.
|
||||
|
||||
**Config-surface policies are recorded, not applied at launch.** The seat still starts at
|
||||
the floor. The `allow`, `ask` and `deny` rules take effect once they are translated into the
|
||||
runtime's native settings. The `applying-a-permission-policy` skill in `openrig-core` does
|
||||
that translation, with per-runtime limits. For example, Claude's prefix rules can't
|
||||
reliably tell `git push --force` from `git push`. An `ask` waits for a person, so it pauses
|
||||
an autonomous seat. Standard suits interactive work, and the policy files point
|
||||
autonomous teams to Open or YOLO.
|
||||
|
||||
**Pi has no permission policy.** `--approve` and `--no-approve` set Pi's resource trust. The
|
||||
OMP variant maps that trust to its approval mode: `yolo` under `--approve`, otherwise
|
||||
`always-ask`.
|
||||
|
||||
**A custom policy file** is Markdown with frontmatter: `policy_schema_version: 1`, `name`,
|
||||
`source: custom`, `description`, and `surface`.
|
||||
- `surface: flag` adds `launch_posture` (`floor`, `full_bypass` or `auto`).
|
||||
- `surface: config` adds `default_posture` (`allow`, `ask` or `deny`) and the `allow`, `ask`,
|
||||
`deny` and `destructive_class` lists (`[]` for none).
|
||||
|
||||
**Claude Code's own checks.** The first time Claude Code starts an interactive session
|
||||
with permissions bypassed, it shows a warning dialog asking you to accept responsibility;
|
||||
declining exits. OpenRig reports such a seat as `attention_required` and holds its startup
|
||||
context for `rig seat continue`; `rig up --non-interruptive` or `rig bundle install
|
||||
--non-interruptive` accepts the warning with a launch flag instead (see
|
||||
[non-interruptive mode](non-interruptive-mode.md)). That choice is saved on the rig, not in
|
||||
`rig.yaml`. On Linux and macOS Claude Code refuses that mode when run as root or under `sudo`,
|
||||
except inside a recognized sandbox. See Claude Code's
|
||||
[Choose a permission mode](https://code.claude.com/docs/en/permission-modes#skip-all-checks-with-bypasspermissions-mode).
|
||||
|
||||
### Choosing the Claude instruction file
|
||||
|
||||
OpenRig writes its instructions for Claude Code members into managed blocks in
|
||||
@@ -247,17 +370,18 @@ directory's `CLAUDE.md`, including blocks written by other rigs.
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `id` | string | yes | — | Pod identifier. Must not contain dots. Must be unique within the rig. Used as the first segment of session names and logical IDs. |
|
||||
| `id` | string | yes | — | Pod identifier. Must not contain dots or `@`. Must be unique within the rig. Used as the first segment of session names and logical IDs. |
|
||||
| `label` | string | yes | — | Human-readable pod name. Shown in UI explorer, graph groupings, and detail surfaces. |
|
||||
| `summary` | string | no | — | Pod description. |
|
||||
| `continuity_policy` | ContinuityPolicy | no | — | Pod-level continuity/restore policy. Controls compaction recovery, artifact management, and peer-driven restoration. |
|
||||
| `continuity_policy` | ContinuityPolicy | no | — | Pod-level continuity/restore policy, validated and stored with the pod (see "Continuity Policy"). |
|
||||
| `startup` | StartupBlock | no | — | Pod-level startup files and actions. Applied to all members in this pod via the startup layering model. |
|
||||
| `members` | Member[] | yes | — | At least one member required (enforced by pods needing content). |
|
||||
| `members` | Member[] | yes | — | The pod's members. It must be an array; validation doesn't refuse an empty one. |
|
||||
| `edges` | PodLocalEdge[] | no | `[]` | Edges between members within this pod. Must use unqualified member IDs (not `pod.member`). |
|
||||
|
||||
### Pod ID Rules
|
||||
|
||||
- Must not contain dots (`.`)
|
||||
- Must not contain `@`, which separates the pod/member portion from the rig name in session addresses
|
||||
- Must be unique across all pods in the rig
|
||||
- Becomes the first segment of the qualified logical ID: `{podId}.{memberId}`
|
||||
- Becomes the first segment of the canonical session name: `{podId}-{memberId}@{rigName}`
|
||||
@@ -268,17 +392,42 @@ directory's `CLAUDE.md`, including blocks written by other rigs.
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `id` | string | yes | — | Member identifier. Must not contain dots. Must be unique within the pod. |
|
||||
| `id` | string | yes | — | Member identifier. Must not contain dots or `@`. 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`. |
|
||||
| `runtime` | string | yes | — | Agent runtime. Current supported values: `claude-code`, `codex`, `pi`, `omp`, `terminal`, `stub`. `stub` is a deterministic test harness, not an agent: a Node runner in the seat's tmux pane goes through the normal launch, skill projection, startup-file and readiness path, follows `<cwd>/.openrig/stub/script.json` when present, and calls no model. See "What a stub can and cannot prove" in `docs/as-built/test-layers.md`. |
|
||||
| `cwd` | string | yes | — | Working directory for the agent. A relative path resolves against the rig root (the directory containing the rig spec). If that path exists as written, it is used. Otherwise a leading `~` or `~/` expands to the home directory (`~user` does not) and `$VAR` / `${VAR}` expand from the daemon host's environment; a relative result still resolves against the rig root. A reference to an unset variable stays as written, so the cwd is that literal path and works only if it exists. Use `"."` for the rig root itself. Can be overridden at launch time with `rig up --cwd`. A cwd inside the OpenRig installation fails preflight unless `--cwd` is given. |
|
||||
| `label` | string | no | — | Human-readable member name. Shown in UI when present. |
|
||||
| `model` | string | no | — | Model override. Runtime-specific (e.g., `claude-opus-4-6` for Claude Code). |
|
||||
| `restore_policy` | string | no | `resume_if_possible` | Restore behavior. One of: `resume_if_possible`, `relaunch_fresh`, `checkpoint_only`. |
|
||||
| `effort` | string | no | — | Reasoning effort override, trimmed. A non-string value is ignored with an advisory. |
|
||||
| `permission_policy` | string | no | — | Member-level permission policy; overrides the rig's. Not valid on terminal members. |
|
||||
| `role` | string | no | — | The seat's role, letters, digits, `_`, `.` and `-`. Not valid on terminal members. |
|
||||
| `restore_policy` | string | no | the AgentSpec's | Restore behavior. One of: `resume_if_possible`, `relaunch_fresh`, `checkpoint_only`. The default comes from the AgentSpec's `defaults.lifecycle` (else `resume_if_possible`) as narrowed by its profile; a member value may only narrow it further, in that order. |
|
||||
| `compaction_strategy` | string | no | — | `default-compaction`, `managed-compaction`, `handover` or `apprentice-handover`; `harness_native` and `pod_continuity` are accepted as deprecated aliases, and `custom_prompt` is refused. |
|
||||
| `mechanic` | string | no | — | A canonical `seat@rig` session address, for `apprentice-handover`. |
|
||||
| `session_source` | object | no | — | Start the seat from an existing session: `fork` with `native_id`, `rebuild` with `artifact_set`, or `agent_image` with `image_name`. A Claude Code `fork` needs Claude Code 2.0.73 or later. Not valid on terminal members. |
|
||||
| `starter_ref` | object | no | — | `{ name }` of an agent starter (`^[a-zA-Z0-9][a-zA-Z0-9_-]*$`) whose files lead the seat's startup. Not valid on terminal members or with a fork. |
|
||||
| `startup` | StartupBlock | no | — | Member-level startup files and actions. Applied only to this member. |
|
||||
|
||||
### Pi (`runtime: pi`)
|
||||
|
||||
`runtime: pi` launches the Pi coding agent (`pi --mode rpc`) through OpenRig's RPC runner.
|
||||
|
||||
- **State:** Each seat uses `$OPENRIG_HOME/state/pi/<session>/agent` as its Pi agent directory, with `sessions/` beside it, instead of your default `~/.pi/agent`. The runner sets `PI_CODING_AGENT_DIR` to that directory. `<session>` is the seat's session name, unchanged; for a pod member it is `{podId}-{memberId}@{rigName}`. `$OPENRIG_HOME` defaults to `~/.openrig`. OpenRig creates these directories at launch and does not copy anything from `~/.pi/agent`.
|
||||
- **Custom models:** Pi reads `models.json` from its agent directory, so a seat reads `$OPENRIG_HOME/state/pi/<session>/agent/models.json`, not `~/.pi/agent/models.json`. Custom provider and model definitions for a seat go in that file. A seat that uses only providers Pi already includes does not need one. OpenRig does not create or write `models.json`.
|
||||
- **Credentials:** The Pi process receives only `PATH`, `HOME`, `USER`, `LOGNAME`, `TERM`, `LANG`, `LC_ALL`, `SHELL`, `TMPDIR`, a fixed set of OpenRig seat and instance variables, `PI_CODING_AGENT_DIR`, `PI_CODING_AGENT_SESSION_DIR`, and at most one provider key. That key is `OPENROUTER_API_KEY`, `ZAI_API_KEY`, `KIMI_API_KEY` or `MINIMAX_API_KEY`, passed only when it is set in the seat's environment and the seat's `model` is written as `openrouter/<id>`, `zai/<id>`, `kimi-coding/<id>` or `minimax/<id>`. Naming that variable in `recovery.provider_auth_env_allowlist` (empty by default) makes the daemon add it, when set in the daemon's environment, to the seat's launch environment. No other variable reaches Pi, including other providers' keys and a custom provider's own key variable.
|
||||
|
||||
### Oh My Pi (`runtime: omp`)
|
||||
|
||||
`runtime: omp` launches Oh My Pi through OpenRig's RPC runner. It is separate from `runtime: pi`; OMP does not use Pi's `--name` or `--approve` flags.
|
||||
|
||||
- **State:** Each seat uses `$OPENRIG_HOME/state/omp/<seat>/agent` and `sessions/` instead of your default `~/.omp` profile, and runs with that seat directory as `HOME`. OpenRig does not copy OMP credentials. The runner finds the real `omp` binary before switching `HOME`, so a version-manager shim such as mise on the daemon's `PATH` still works.
|
||||
- **Credentials:** Provision each seat separately, or put the provider's key variable in `recovery.provider_auth_env_allowlist` (for example `ANTHROPIC_API_KEY` or `MISTRAL_API_KEY`). The allowlist accepts the key variable of every provider in OpenRig's OMP provider map, from `anthropic` through `litellm`. A seat receives a key only when its `model` is written as `provider/id`, such as `anthropic/claude-sonnet-4-5`. Short names such as `opus` pass no key. A provider whose key is scoped to one endpoint also accepts that endpoint's variable: `anthropic`, `openai` and `litellm` each take their `<PROVIDER>_BASE_URL` — `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`, `LITELLM_BASE_URL`. The allowlist admits each variable on its own, so an endpoint named without its key is admitted too, which suits a keyless local proxy. The launch gate admits these endpoint names for OMP seats; the match against the seat's declared `provider/id` happens at the OMP child, which forwards a matching endpoint already in the runner's environment just as it forwards a key. OMP also reads the launch directory's `.env`, so use a trusted working directory.
|
||||
- **Approval posture:** The default floor is `--approval-mode always-ask`. Because the runner is headless, OMP approval requests are cancelled and the seat stays in needing-attention after the turn ends, until the next agent run starts. A `full_bypass` permission policy selects `--approval-mode yolo`.
|
||||
- **Model errors:** A rejected prompt, a provider or authentication error during a turn, or exhausted automatic retries is printed in the pane and keeps the seat in needing-attention until the next agent run starts.
|
||||
- **Restore:** OMP creates its session file after the first persisted turn. A new seat with no persisted turn has no resume token; restoring it requires `rig up --existing <rig> --fresh <seat>`. After that file exists, OpenRig restores that exact session file. If a full rig restore leaves an OMP seat in `attention_required` or `failed`, `rig seat clear-attention` cannot yet reconcile it to `operator_recovered`, even with `--reason`, because restore reconciliation only verifies Claude Code and Codex processes ([#41](https://github.com/mvschwarz/openrig/issues/41)). Relaunch that seat with `rig up --existing <rig> --fresh <seat>`, or restore it manually.
|
||||
|
||||
### Terminal Nodes
|
||||
|
||||
Terminal nodes are infrastructure processes (servers, log tails, build watchers) that are not agent runtimes. They require an exact triple:
|
||||
@@ -307,7 +456,10 @@ The canonical session name is derived from the pod ID, member ID, and rig name:
|
||||
{podId}-{memberId}@{rigName}
|
||||
```
|
||||
|
||||
Example: pod `dev`, member `impl`, rig `my-team` → session `dev-impl@my-team`
|
||||
Example: pod `dev`, member `build`, rig `my-team` → session `dev-build@my-team`
|
||||
|
||||
Pod and member IDs cannot contain `@` because the first `@` separates their portion
|
||||
of the session address from the rig name. Rig names may still contain `@`.
|
||||
|
||||
This is human-authored (you choose the pod/member IDs) and system-validated (the system enforces the format).
|
||||
|
||||
@@ -320,7 +472,7 @@ This is human-authored (you choose the pod/member IDs) and system-validated (the
|
||||
| Kind | Meaning | Use When |
|
||||
|------|---------|----------|
|
||||
| `delegates_to` | Source delegates work to target. Constrains launch order. | Orchestrator → implementer, lead → worker |
|
||||
| `spawned_by` | Target was spawned by source. Constrains launch order. | Parent → child in hierarchical topologies |
|
||||
| `spawned_by` | Source was spawned by target; the target launches first. Constrains launch order. | Child → parent in hierarchical topologies |
|
||||
| `can_observe` | Source can observe target's output. Does NOT constrain launch order. | Reviewer → implementer, monitor → worker |
|
||||
| `collaborates_with` | Peer collaboration relationship. Does NOT constrain launch order. | Co-equal peers working together |
|
||||
| `escalates_to` | Source escalates to target for decisions. Does NOT constrain launch order. | Worker → lead for escalation |
|
||||
@@ -333,13 +485,13 @@ Edges within a pod use **unqualified member IDs** (just the member `id`, not `po
|
||||
pods:
|
||||
- id: dev
|
||||
members:
|
||||
- id: impl
|
||||
- id: build
|
||||
# ...
|
||||
- id: qa
|
||||
# ...
|
||||
edges:
|
||||
- kind: delegates_to
|
||||
from: impl # NOT dev.impl
|
||||
from: build # NOT dev.build
|
||||
to: qa # NOT dev.qa
|
||||
```
|
||||
|
||||
@@ -353,7 +505,7 @@ Edges between pods use **fully-qualified `pod.member` IDs**:
|
||||
edges:
|
||||
- kind: delegates_to
|
||||
from: orch.lead # pod.member format
|
||||
to: dev.impl # pod.member format
|
||||
to: dev.build # pod.member format
|
||||
```
|
||||
|
||||
Cross-pod edges must reference different pods. An edge where both `from` and `to` are in the same pod is a validation error — use pod-local edges instead.
|
||||
@@ -362,13 +514,14 @@ Cross-pod edges must reference different pods. An edge where both `from` and `to
|
||||
|
||||
## Startup Block
|
||||
|
||||
Startup blocks can appear at three levels: rig, pod, and member. They are merged additively via the startup layering model (see `docs/reference/startup-layering.md`).
|
||||
Startup blocks can appear at three levels: rig, pod, and member. They are merged additively via the startup layering model (see [the layering model](agent-startup-guide.md#the-layering-model)). The rig's `culture_file` joins as a required file with the `auto` hint in the culture layer, on both fresh start and restore.
|
||||
|
||||
### Files
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `path` | string | yes | — | Relative path to the file. Must be a safe relative path. |
|
||||
| `orientation` | string | no | — | Set to `role` to identify a per-seat role file for `rig queue whoami --json` and refocus. Does not change delivery or replay startup. |
|
||||
| `delivery_hint` | string | no | `auto` | How the file is delivered. One of: `auto`, `guidance_merge`, `skill_install`, `send_text`. |
|
||||
| `required` | boolean | no | `true` | Whether startup fails if this file cannot be delivered. |
|
||||
| `applies_on` | string[] | no | `[fresh_start, restore]` | When this file is delivered. Subset of: `fresh_start`, `restore`. |
|
||||
@@ -378,7 +531,7 @@ Startup blocks can appear at three levels: rig, pod, and member. They are merged
|
||||
| Hint | Behavior |
|
||||
|------|----------|
|
||||
| `auto` | System chooses based on file type and context. |
|
||||
| `guidance_merge` | Merged into the runtime's guidance file (`CLAUDE.md` or `AGENTS.md`) as a managed block. Delivered before harness boot. |
|
||||
| `guidance_merge` | Merged as a managed block into the runtime's guidance file: for Claude, the `managed_blocks` file (`CLAUDE.md` or `CLAUDE.local.md`); for Codex, Pi and OMP, `AGENTS.md`. Delivered before harness boot. |
|
||||
| `skill_install` | Installed as a skill in the runtime's skill directory. Delivered before harness boot. |
|
||||
| `send_text` | Sent as text to the agent's terminal after the harness is ready. Requires the agent TUI to be active. |
|
||||
|
||||
@@ -431,6 +584,13 @@ 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.
|
||||
|
||||
The harness readiness window defaults to 30 seconds. On a loaded machine, set
|
||||
`rig config set runtime.readiness_timeout_seconds 60` to give new seats and
|
||||
handover successors longer to become interactive. The setting accepts 1–600
|
||||
seconds, applies to the next launch without a daemon restart, and can also be
|
||||
set with `OPENRIG_RUNTIME_READINESS_TIMEOUT_SECONDS`. It does not change the
|
||||
time spent by the runtime adapter before readiness polling starts.
|
||||
|
||||
---
|
||||
|
||||
## Services Block
|
||||
@@ -441,7 +601,7 @@ The services block is optional. When present, services boot before any agent nod
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `kind` | string | yes | — | Service backend. Only `compose` is supported in v1. |
|
||||
| `compose_file` | string | yes | — | Relative path to the Docker Compose file. Must be a safe relative path. Resolved relative to rig root. |
|
||||
| `project_name` | string | no | derived from rig name | Docker Compose project name. Must match `[a-z0-9][a-z0-9_-]*`. If omitted, derived by sanitizing the rig name. |
|
||||
| `project_name` | string | no | stored predecessor project or unique rig-ID default | Docker Compose project name. Must match `[a-z0-9][a-z0-9_-]*`. An explicit value takes precedence. A new rig uses a stable, unique rig-ID-derived name. A same-name replacement inherits the stored project of the exact generations it replaces; conflicting predecessor projects require an explicit value. Existing stored names remain unchanged. |
|
||||
| `profiles` | string[] | no | — | Compose profiles to activate. |
|
||||
| `down_policy` | string | no | `down` | What happens on `rig down`. One of: `leave_running`, `down`, `down_and_volumes`. |
|
||||
| `wait_for` | WaitTarget[] | no | — | Health targets that must pass before agent launch. |
|
||||
@@ -454,7 +614,8 @@ Each target must define exactly one of `service`, `url`, or `tcp`:
|
||||
|
||||
```yaml
|
||||
wait_for:
|
||||
# HTTP probe — hits the URL, expects 2xx
|
||||
# HTTP probe — any 2xx or 3xx response passes the HTTP wait target; redirects are not followed.
|
||||
# This proves the server answered, not application readiness.
|
||||
- url: http://127.0.0.1:8200/v1/sys/health
|
||||
|
||||
# TCP probe — connects to host:port
|
||||
@@ -484,7 +645,7 @@ surfaces:
|
||||
command: "vault status -address=http://127.0.0.1:8200"
|
||||
```
|
||||
|
||||
Surfaces are metadata only. They are displayed in the UI and in `rig env status` output but are NOT executed by OpenRig.
|
||||
Surfaces are metadata only. They are displayed in the UI and in `rig env status --json` output (the text output lists only services) but are NOT executed by OpenRig.
|
||||
|
||||
### Checkpoint Hooks
|
||||
|
||||
@@ -498,10 +659,10 @@ checkpoints:
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | string | yes | Unique identifier for this checkpoint. |
|
||||
| `export` | string | yes | Shell command to export state. `{{artifacts_dir}}` is replaced with a daemon-managed path. |
|
||||
| `export` | string | yes | Shell command to export state. |
|
||||
| `import` | string | no | Shell command to import state on restore. |
|
||||
|
||||
Checkpoint hooks are shell commands run by the daemon. They are best-effort — a failed export does not block snapshot, but continuity is classified as `receipt_only` instead of `checkpointed`.
|
||||
Checkpoint hooks are validated and stored with the spec. In this version the daemon doesn't run them during snapshot or restore, and doesn't substitute `{{artifacts_dir}}`.
|
||||
|
||||
---
|
||||
|
||||
@@ -532,11 +693,24 @@ continuity_policy:
|
||||
| `restore_protocol.peer_driven` | boolean | no | — | Whether peers drive the restore process. |
|
||||
| `restore_protocol.verify_via_quiz` | boolean | no | — | Whether to verify restoration via quiz. |
|
||||
|
||||
These fields are validated, stored on the pod and written back on export. In this version nothing acts on
|
||||
`sync_triggers`, `artifacts` or `restore_protocol`.
|
||||
|
||||
---
|
||||
|
||||
## Validation Rules Summary
|
||||
|
||||
These rules are enforced by the validator. A spec that violates any of these will be rejected by `rig spec validate` and `rig up`.
|
||||
Unknown keys at the rig, pod, member and edge levels are refused ("unknown key … refusing the spec because
|
||||
normalization would otherwise discard it"). Keys inside `startup`, `services`, `continuity_policy` and `workspace` are
|
||||
not checked this way. Some rules are enforced at preflight rather than validation: the runtime must be one of the
|
||||
supported values, rig, pod and member names may use only letters, digits, `-`, `_`, `.` and `@` where allowed above,
|
||||
each `agent_ref` must have an `agent.yaml`, Codex profiles must load, and a cwd may not be inside the OpenRig
|
||||
installation.
|
||||
|
||||
Schema validation checks the structural rules below. Pod and member IDs containing
|
||||
`@` are additionally rejected during preflight and member creation or launch; a
|
||||
spec can pass `rig spec validate` and then fail at preflight or import. Run
|
||||
`rig spec preflight` before creation or import.
|
||||
|
||||
1. `version` and `name` are required non-empty strings.
|
||||
2. `pods` must be a non-empty array.
|
||||
@@ -573,8 +747,13 @@ 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/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 |
|
||||
| `starter` | `packages/daemon/specs/rigs/launch/starter/rig.yaml` | dev | 2 (build, review) | no |
|
||||
| `factory` | `packages/daemon/specs/rigs/launch/factory/rig.yaml` | orch, dev, review | 7 (lead, advisor, build, qa, design, r1, r2) | no |
|
||||
| `code-review` | `packages/daemon/specs/rigs/focused/code-review/rig.yaml` | orch, review | 3 (lead, r1, r2) | no |
|
||||
| `research` | `packages/daemon/specs/rigs/focused/research/rig.yaml` | orch, research | 3 (lead, analyst, synthesizer) | no |
|
||||
| `pm` | `packages/daemon/specs/rigs/focused/pm/rig.yaml` | pm, dev | 3 (lead, researcher, build) | no |
|
||||
| `secrets-manager` | `packages/daemon/specs/rigs/launch/secrets-manager/rig.yaml` | vault | 1 (specialist) | yes (Vault) |
|
||||
|
||||
Also shipped, in the same tree: `launch/kernel`, OpenRig's own team, which starts automatically. `first-project` is
|
||||
starter's old name and still resolves to it. `workshop`, the four-agent middle team, is a rig bundle rather than a
|
||||
built-in. List the built-ins with `rig specs ls --kind rig`.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Find specialists with rosters
|
||||
|
||||
A roster records who to involve for a purpose and why. The same seat can appear in several
|
||||
rosters with different capabilities and engagement. Membership is a recommendation, not an
|
||||
assignment or a grant of authority. Contact someone with the existing `rig send` or `rig queue` commands.
|
||||
|
||||
Put JSON files in `<workspace.root>/rosters/`; the included starter and factory leads write
|
||||
their team's `<rig>.json` there at first start and never replace an existing one. Read `workspace.root` with
|
||||
`rig config get workspace.root`, or use `--folder` to read another folder in place.
|
||||
|
||||
```sh
|
||||
rig roster list
|
||||
rig roster show production --json
|
||||
rig roster find 'colour grading'
|
||||
rig roster find 'editor@studio' --folder ./rosters --json
|
||||
```
|
||||
|
||||
`list` reads authored purposes and curators without contacting daemons. `show` and `find` read the
|
||||
configured daemon and registered HTTP hosts through `rig ps`. Each command reads current node
|
||||
listings once per route, not once per member. `find` matches part of a capability tag or seat
|
||||
address, ignoring case. A seat in two rosters produces two recommendations with their own reasons.
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"id": "production",
|
||||
"name": "Production",
|
||||
"purpose": "Make a film",
|
||||
"curator": { "seat": "lead@studio", "host": "host-a" },
|
||||
"updated_at": "2026-10-01",
|
||||
"members": [{
|
||||
"seat": "editor@studio",
|
||||
"host": "host-a",
|
||||
"capabilities": ["colour grading"],
|
||||
"engagement": ["consult", "review"],
|
||||
"use_when": "Choose a look or review a grade",
|
||||
"why": "Knows the footage and the delivery requirements",
|
||||
"caveat": "Ask about availability first"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Use the serving daemon's `hostSelfId` and the exact seat address from `rig ps --nodes -A --json`.
|
||||
A caller's local host alias is not necessarily that identity. Other hosts must already be
|
||||
registered; the roster command does not register them or dispatch work. Existing HTTP fan-out
|
||||
reports SSH-only hosts as unsupported. JSON retains per-host failures and partial-read warnings.
|
||||
|
||||
Authored files contain recommendations, not runtime facts. Extra fields are ignored, including
|
||||
any authored runtime, model or activity. At read time the command joins by exact host self-id
|
||||
and seat. Unmatched or conflicting observations are `unknown`; a seat not reported running is
|
||||
`stale`, with current runtime/model unknown. The configured model is the registry declaration.
|
||||
The observed native model remains unknown: these commands do not open native histories or verify
|
||||
what model the provider is running. Activity includes its sample time when the daemon supplies it;
|
||||
activity and running status do not promise that someone is available for new work.
|
||||
|
||||
The JSON `observations.readAt` is when the inventory read completed, not a native observation
|
||||
timestamp. Rosters are never rewritten by these commands. Malformed files produce warnings while
|
||||
valid rosters remain usable; duplicate IDs make `show` report ambiguity instead of selecting one.
|
||||
Non-regular file targets, such as FIFOs and directories, are skipped with a warning; links to
|
||||
regular files remain readable. Inventory uses `ps --no-cleanup` to preserve even stale local
|
||||
daemon state files. Ordinary `rig ps` keeps its existing cleanup behavior unless given that flag.
|
||||
@@ -0,0 +1,546 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://openrig.dev/schemas/bundle-behaviour.v1.json",
|
||||
"title": "Before-install behaviour view (openrig.bundle-behaviour/v1)",
|
||||
"description": "Derived from the archive's files alone, for one configuration and one assembler version; kept outside the archive, so it never feeds the package digest. A view, never a gate; carries no tested status. Consumers ignore unknown properties; producers add only optional properties within v1. An empty list means none are known; something unknown goes in unknownBeforeLaunch.",
|
||||
"$defs": {
|
||||
"sourceRef": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"field": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "Archive-relative file, and optionally the field within it."
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"schema",
|
||||
"state",
|
||||
"identity"
|
||||
],
|
||||
"properties": {
|
||||
"schema": {
|
||||
"const": "openrig.bundle-behaviour/v1"
|
||||
},
|
||||
"state": {
|
||||
"enum": [
|
||||
"generated",
|
||||
"not_generated"
|
||||
]
|
||||
},
|
||||
"reason": {
|
||||
"type": "string"
|
||||
},
|
||||
"localInspectCommand": {
|
||||
"type": "string",
|
||||
"description": "For not_generated: the existing command that shows this view locally, for example rig bundle inspect <archive> --json."
|
||||
},
|
||||
"identity": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"configurationId",
|
||||
"packageDigest",
|
||||
"assembler",
|
||||
"generator"
|
||||
],
|
||||
"properties": {
|
||||
"source": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/source"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configurationId": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/configurationId"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "null when the archive doesn't state it (a local or legacy archive)."
|
||||
},
|
||||
"packageDigest": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/packageDigest"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"assembler": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/assembler"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "The OpenRig that built the archive, as the archive states it; null when unstated. Never the inspecting OpenRig."
|
||||
},
|
||||
"generator": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/assembler",
|
||||
"description": "The OpenRig that produced this view."
|
||||
},
|
||||
"compatibility": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"minDaemonVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"minCliVersion": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"statedProvenance": {
|
||||
"type": "object",
|
||||
"description": "As the bundle states it; not verified."
|
||||
},
|
||||
"integrity": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"digestValid",
|
||||
"filesVerified"
|
||||
],
|
||||
"properties": {
|
||||
"digestValid": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"filesVerified": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"description": "Self-consistency of the archive; not authorship."
|
||||
}
|
||||
}
|
||||
},
|
||||
"team": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"seat",
|
||||
"pod",
|
||||
"member",
|
||||
"agentRef",
|
||||
"runtime",
|
||||
"cwd"
|
||||
],
|
||||
"properties": {
|
||||
"seat": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/member"
|
||||
},
|
||||
"pod": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"member": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"agentRef": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"profile": {
|
||||
"type": "string"
|
||||
},
|
||||
"runtime": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/runtime"
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"posture": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"seat",
|
||||
"shellAccess",
|
||||
"selection",
|
||||
"basis",
|
||||
"nativeEffect"
|
||||
],
|
||||
"properties": {
|
||||
"seat": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/member"
|
||||
},
|
||||
"shellAccess": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"selection": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"basis": {
|
||||
"enum": [
|
||||
"explicit",
|
||||
"product_default",
|
||||
"unresolved"
|
||||
],
|
||||
"description": "Declared in the archive, the product's conditional default, or not resolvable before launch."
|
||||
},
|
||||
"nativeEffect": {
|
||||
"const": "unknown",
|
||||
"description": "Host settings (for example OPENRIG_YOLO, persisted or native settings) decide the effective permission at launch."
|
||||
},
|
||||
"permissionPrompts": {
|
||||
"enum": ["off", "on", "default"],
|
||||
"description": "Archive declaration per seat: off for full_bypass (including builtin:yolo) on any runtime; on for a Claude/Codex flag-surface floor policy, or Codex under auto; default for Claude/Codex with no declared policy. Omitted for Claude under auto, config-surface policies, host-resolved Codex profiles, and Pi except declared full bypass. Absent means not stated, never on. Host settings may differ; nativeEffect remains unknown."
|
||||
},
|
||||
"sourceRefs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/sourceRef"
|
||||
}
|
||||
},
|
||||
"firstRunWarnings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"claudeBypass": {
|
||||
"const": "harness_asks_once"
|
||||
}
|
||||
},
|
||||
"description": "Declared full-bypass Claude seat: the harness asks for bypass-warning acceptance if it has not been remembered. No host acceptance state is read; nativeEffect stays unknown."
|
||||
},
|
||||
"nonInterruptive": {
|
||||
"const": "available",
|
||||
"description": "This declared full-bypass Claude/Codex seat can use --non-interruptive launch flags. Availability is not the operator choice or proof that no prompt will appear. Omitted for other postures, unresolved policies and runtimes with no such flags."
|
||||
},
|
||||
"nonInterruptiveDefault": {
|
||||
"type": "boolean",
|
||||
"description": "Optional authored rig-level default for supported full-bypass Claude/Codex launches. An explicit launch option overrides it; absent is not an off or on claim. Native effect remains unknown."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"toldFiles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"pathOrRef",
|
||||
"resolution"
|
||||
],
|
||||
"properties": {
|
||||
"seat": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/member"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"pathOrRef": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"delivery": {
|
||||
"type": "string"
|
||||
},
|
||||
"resolution": {
|
||||
"enum": [
|
||||
"archive",
|
||||
"host_at_launch",
|
||||
"unresolved"
|
||||
]
|
||||
},
|
||||
"sourceRefs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/sourceRef"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"alsoRuns": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"pathOrRef",
|
||||
"resolution"
|
||||
],
|
||||
"properties": {
|
||||
"seat": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/member"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"pathOrRef": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"trigger": {
|
||||
"type": "string"
|
||||
},
|
||||
"resolution": {
|
||||
"enum": [
|
||||
"archive",
|
||||
"host_at_launch",
|
||||
"unresolved"
|
||||
]
|
||||
},
|
||||
"sourceRefs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/sourceRef"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"writes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"destinationBase",
|
||||
"path",
|
||||
"operation",
|
||||
"phase"
|
||||
],
|
||||
"properties": {
|
||||
"seat": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/member"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"destinationBase": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"phase": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"condition": {
|
||||
"type": "string"
|
||||
},
|
||||
"sourceRefs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/sourceRef"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"outsideAddresses": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"domain",
|
||||
"sourceRefs"
|
||||
],
|
||||
"properties": {
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"sourceRefs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/sourceRef"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"needs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind",
|
||||
"name",
|
||||
"status"
|
||||
],
|
||||
"properties": {
|
||||
"seat": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/member"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"versionConstraint": {
|
||||
"type": "string"
|
||||
},
|
||||
"commands": {
|
||||
"type": "array",
|
||||
"description": "Unexecuted author setup lines, in order in one shell; the install command follows in the resulting working directory.",
|
||||
"items": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"status": {
|
||||
"const": "not_checked"
|
||||
},
|
||||
"sourceRefs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/sourceRef"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"unknownBeforeLaunch": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"subject",
|
||||
"reason"
|
||||
],
|
||||
"properties": {
|
||||
"seat": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/member"
|
||||
},
|
||||
"subject": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"sourceRefs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/sourceRef"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"state": {
|
||||
"const": "not_generated"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"reason",
|
||||
"localInspectCommand"
|
||||
],
|
||||
"not": {
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"team"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"posture"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"toldFiles"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"alsoRuns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"writes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"outsideAddresses"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"needs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"unknownBeforeLaunch"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"state": {
|
||||
"const": "generated"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"team",
|
||||
"posture",
|
||||
"toldFiles",
|
||||
"alsoRuns",
|
||||
"writes",
|
||||
"outsideAddresses",
|
||||
"needs",
|
||||
"unknownBeforeLaunch"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://openrig.dev/schemas/bundle-common.v1.json",
|
||||
"title": "Shared bundle identity definitions (v1)",
|
||||
"description": "Source identity, configuration ID, package digest and assembler, shared by every bundle format. See docs/reference/bundle-formats.md.",
|
||||
"$defs": {
|
||||
"source": {
|
||||
"type": "object",
|
||||
"description": "Source identity: repository + resolvedCommit + folder. requestedRef and canonicalUrl explain it and are not part of it.",
|
||||
"required": [
|
||||
"repository",
|
||||
"folder",
|
||||
"resolvedCommit"
|
||||
],
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string",
|
||||
"pattern": "^https://github\\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"
|
||||
},
|
||||
"folder": {
|
||||
"type": "string",
|
||||
"pattern": "^(\\.|(?!/)(?!.*(^|/)\\.\\.(/|$))[^\\\\]+)$",
|
||||
"description": "Repo-relative POSIX path of the folder holding rig.yaml; '.' is the repository root."
|
||||
},
|
||||
"resolvedCommit": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-f]{40}$"
|
||||
},
|
||||
"requestedRef": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"canonicalUrl": {
|
||||
"type": "string",
|
||||
"pattern": "^https://github\\.com/.+/tree/[0-9a-f]{40}(/.*)?$"
|
||||
}
|
||||
}
|
||||
},
|
||||
"member": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$",
|
||||
"description": "pod.member"
|
||||
},
|
||||
"runtime": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
||||
"description": "A runtime as rig.yaml spells it, for example claude-code, codex, pi."
|
||||
},
|
||||
"configurationId": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+=[a-z0-9][a-z0-9-]*(,[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+=[a-z0-9][a-z0-9-]*)*$",
|
||||
"description": "Every member's pod.member=runtime, sorted by pod.member in code-unit order, joined with ','."
|
||||
},
|
||||
"packageDigest": {
|
||||
"type": "object",
|
||||
"description": "SHA-256 over UTF-8 lines JSON.stringify(path) + '\\t' + sha256 + '\\n', one per integrity.files entry, sorted by path in code-unit order. See docs/reference/bundle-formats.md.",
|
||||
"required": [
|
||||
"algorithm",
|
||||
"value",
|
||||
"coverage"
|
||||
],
|
||||
"properties": {
|
||||
"algorithm": {
|
||||
"const": "sha256"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-f]{64}$"
|
||||
},
|
||||
"coverage": {
|
||||
"const": "openrig.package-digest/v1"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"assembler": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"openrigVersion"
|
||||
],
|
||||
"properties": {
|
||||
"openrigVersion": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"commit": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-f]{7,40}$"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sha256": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"algorithm",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"algorithm": {
|
||||
"const": "sha256"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-f]{64}$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://openrig.dev/schemas/bundle-configurations.v1.json",
|
||||
"title": "Declared configurations (configurations.yaml beside rig.yaml)",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"schema",
|
||||
"recommended",
|
||||
"seats",
|
||||
"presets"
|
||||
],
|
||||
"properties": {
|
||||
"schema": {
|
||||
"const": "openrig.bundle-configurations/v1"
|
||||
},
|
||||
"recommended": {
|
||||
"type": "string",
|
||||
"description": "The preset name of the recommended configuration."
|
||||
},
|
||||
"seats": {
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"description": "pod.member -> the runtimes that seat may use, each with the profile it uses.",
|
||||
"propertyNames": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/member"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"runtimes"
|
||||
],
|
||||
"properties": {
|
||||
"runtimes": {
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"propertyNames": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/runtime"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"presets": {
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"description": "Preset name -> a runtime for every member. Names are aliases; the configuration ID is computed from the mapping.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"propertyNames": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/member"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/runtime"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://openrig.dev/schemas/bundle-status.v1.json",
|
||||
"title": "Public status file (generated; do not edit)",
|
||||
"description": "Built from run records by rule openrig.status-rule/v1, from an allowlist of output fields: no private paths, host names, row IDs, account names or receipt text. Regenerating gives identical bytes. An empty platforms object means Not tested. Closed on purpose: no property outside this schema may appear, so a private field can't leak in.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"generated",
|
||||
"listings",
|
||||
"harnessChecks",
|
||||
"bodyDigest"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"generated": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"by",
|
||||
"notice",
|
||||
"rule",
|
||||
"generatorVersion"
|
||||
],
|
||||
"properties": {
|
||||
"by": {
|
||||
"type": "string"
|
||||
},
|
||||
"notice": {
|
||||
"const": "Generated file. Do not edit by hand; regenerate it."
|
||||
},
|
||||
"rule": {
|
||||
"const": "openrig.status-rule/v1"
|
||||
},
|
||||
"generatorVersion": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"listings": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"pattern": "^[a-z0-9][a-z0-9-]*$"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"state",
|
||||
"configurations"
|
||||
],
|
||||
"properties": {
|
||||
"state": {
|
||||
"enum": [
|
||||
"ok",
|
||||
"status_unavailable"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/configurationId"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"platforms",
|
||||
"communityReports"
|
||||
],
|
||||
"properties": {
|
||||
"platforms": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"label",
|
||||
"date",
|
||||
"openrigVersion",
|
||||
"recordIds",
|
||||
"packageDigests"
|
||||
],
|
||||
"properties": {
|
||||
"label": {
|
||||
"enum": [
|
||||
"known_problem",
|
||||
"tested",
|
||||
"tested_with_help",
|
||||
"partly_tested",
|
||||
"not_tested",
|
||||
"status_unavailable"
|
||||
]
|
||||
},
|
||||
"assistanceCount": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"openrigVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"recordIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
|
||||
}
|
||||
},
|
||||
"packageDigests": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/packageDigest"
|
||||
}
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"label": {
|
||||
"enum": [
|
||||
"tested",
|
||||
"tested_with_help",
|
||||
"partly_tested",
|
||||
"known_problem"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"recordIds": {
|
||||
"minItems": 1
|
||||
},
|
||||
"packageDigests": {
|
||||
"minItems": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"label": {
|
||||
"const": "tested_with_help"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"assistanceCount"
|
||||
],
|
||||
"properties": {
|
||||
"assistanceCount": {
|
||||
"minimum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"label": {
|
||||
"const": "tested"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"assistanceCount": {
|
||||
"const": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"propertyNames": {
|
||||
"pattern": "^(linux|darwin|win32)-(x64|arm64)$"
|
||||
}
|
||||
},
|
||||
"communityReports": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"state": {
|
||||
"const": "status_unavailable"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"configurations": {
|
||||
"additionalProperties": {
|
||||
"properties": {
|
||||
"platforms": {
|
||||
"additionalProperties": {
|
||||
"properties": {
|
||||
"label": {
|
||||
"const": "status_unavailable"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"harnessChecks": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"state",
|
||||
"harnesses"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"state": {
|
||||
"enum": [
|
||||
"ok",
|
||||
"status_unavailable"
|
||||
],
|
||||
"description": "status_unavailable: some harness-check record could not be read."
|
||||
},
|
||||
"harnesses": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"$ref": "https://openrig.dev/schemas/bundle-common.v1.json#/$defs/runtime"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"result",
|
||||
"date",
|
||||
"openrigVersion",
|
||||
"harnessVersion",
|
||||
"recordIds"
|
||||
],
|
||||
"properties": {
|
||||
"result": {
|
||||
"enum": [
|
||||
"PASS",
|
||||
"FAIL",
|
||||
"BLOCKED",
|
||||
"NOT_RUN"
|
||||
]
|
||||
},
|
||||
"date": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"openrigVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"harnessVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"recordIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
|
||||
}
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"propertyNames": {
|
||||
"pattern": "^(linux|darwin|win32)-(x64|arm64)$"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"state": {
|
||||
"const": "status_unavailable"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"harnesses": {
|
||||
"maxProperties": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"bodyDigest": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-f]{64}$",
|
||||
"description": "SHA-256, lowercase hex, over the RFC 8785 (JCS) canonical JSON, as UTF-8, of an object holding every other top-level field."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"configurationId": [
|
||||
{
|
||||
"mapping": {
|
||||
"check.review": "codex",
|
||||
"build.lead": "claude-code",
|
||||
"check.qa": "codex",
|
||||
"build.impl": "claude-code"
|
||||
},
|
||||
"expected": "build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex"
|
||||
},
|
||||
{
|
||||
"mapping": {
|
||||
"b.x": "pi",
|
||||
"B.y": "codex",
|
||||
"a.z": "claude-code"
|
||||
},
|
||||
"expected": "B.y=codex,a.z=claude-code,b.x=pi"
|
||||
},
|
||||
{
|
||||
"mapping": {
|
||||
"a.b-c": "pi",
|
||||
"a.b": "codex"
|
||||
},
|
||||
"expected": "a.b=codex,a.b-c=pi"
|
||||
}
|
||||
],
|
||||
"packageDigest": [
|
||||
{
|
||||
"integrityFiles": {
|
||||
"rig.yaml": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"agents/openrig-dev/agent.yaml": "2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"README.md": "3333333333333333333333333333333333333333333333333333333333333333",
|
||||
"agents/openrig-dev/guidance/lead.md": "4444444444444444444444444444444444444444444444444444444444444444"
|
||||
},
|
||||
"expected": {
|
||||
"algorithm": "sha256",
|
||||
"value": "cda24a7d4904e89e29c9513fc1e4916bee782a57b2b6ee43b859a84f4b94fb6a",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"note": "Two files, x and y. Unquoted, their lines were the same bytes as the next vector's one file, whose name holds a tab and a newline.",
|
||||
"integrityFiles": {
|
||||
"x": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"y": "2222222222222222222222222222222222222222222222222222222222222222"
|
||||
},
|
||||
"expected": {
|
||||
"algorithm": "sha256",
|
||||
"value": "7ba71a4097dae6dbfb358af6b0b601a438f748eec578756f97144fe9f7fadaea",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"note": "One file whose name holds a tab and a newline; quoting keeps it apart from the two-file vector above.",
|
||||
"integrityFiles": {
|
||||
"x\t1111111111111111111111111111111111111111111111111111111111111111\ny": "2222222222222222222222222222222222222222222222222222222222222222"
|
||||
},
|
||||
"expected": {
|
||||
"algorithm": "sha256",
|
||||
"value": "413a88836f2b10561d9272a6d272e5c610a122d76d327d16bc2a6f1af44a0826",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"note": "Escaping: a double quote, a backslash, a control character (\\u0001) and a non-ASCII letter written as itself.",
|
||||
"integrityFiles": {
|
||||
"docs/a \"quoted\" name.md": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"docs/back\\slash.md": "2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"docs/control\u0001char.md": "3333333333333333333333333333333333333333333333333333333333333333",
|
||||
"docs/café.md": "4444444444444444444444444444444444444444444444444444444444444444"
|
||||
},
|
||||
"expected": {
|
||||
"algorithm": "sha256",
|
||||
"value": "9055bef9907afbafec3401bbba55625ed0ddf1924390259c6dd95a128b3d50e8",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"schema": "openrig.bundle-behaviour/v1",
|
||||
"state": "not_generated",
|
||||
"identity": {
|
||||
"source": {
|
||||
"repository": "https://github.com/mvschwarz/openrig-world",
|
||||
"folder": "rigs/openrig-dev",
|
||||
"resolvedCommit": "894ad0e301ab46ac6ddd74dea784b7f6311d69a2",
|
||||
"requestedRef": "main",
|
||||
"canonicalUrl": "https://github.com/mvschwarz/openrig-world/tree/894ad0e301ab46ac6ddd74dea784b7f6311d69a2/rigs/openrig-dev"
|
||||
},
|
||||
"configurationId": "build.impl=pi,build.lead=claude-code,check.qa=codex,check.review=codex",
|
||||
"packageDigest": null,
|
||||
"assembler": null,
|
||||
"generator": {
|
||||
"openrigVersion": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
}
|
||||
},
|
||||
"reason": "No view has been generated for this combination.",
|
||||
"localInspectCommand": "rig bundle inspect <the archive built for this combination> --json",
|
||||
"team": [
|
||||
{
|
||||
"seat": "build.lead",
|
||||
"pod": "build",
|
||||
"member": "lead",
|
||||
"agentRef": "local:agents/openrig-dev",
|
||||
"profile": "lead",
|
||||
"runtime": "claude-code",
|
||||
"cwd": "--cwd (the user's openrig clone)"
|
||||
},
|
||||
{
|
||||
"seat": "build.impl",
|
||||
"pod": "build",
|
||||
"member": "impl",
|
||||
"agentRef": "local:agents/openrig-dev",
|
||||
"profile": "implementer",
|
||||
"runtime": "claude-code",
|
||||
"cwd": "--cwd (the user's openrig clone)"
|
||||
},
|
||||
{
|
||||
"seat": "check.review",
|
||||
"pod": "check",
|
||||
"member": "review",
|
||||
"agentRef": "local:agents/openrig-dev",
|
||||
"profile": "reviewer",
|
||||
"runtime": "codex",
|
||||
"cwd": "--cwd (the user's openrig clone)"
|
||||
},
|
||||
{
|
||||
"seat": "check.qa",
|
||||
"pod": "check",
|
||||
"member": "qa",
|
||||
"agentRef": "local:agents/openrig-dev",
|
||||
"profile": "qa",
|
||||
"runtime": "codex",
|
||||
"cwd": "--cwd (the user's openrig clone)"
|
||||
}
|
||||
]
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
schema: openrig.bundle-configurations/v1
|
||||
recommended: r
|
||||
seats:
|
||||
'build lead': { runtimes: { codex: lead } }
|
||||
presets:
|
||||
r: { 'build lead': codex }
|
||||
@@ -0,0 +1,21 @@
|
||||
schema: openrig.registry-entry/v1
|
||||
slug: openrig-dev
|
||||
name: OpenRig contributor team
|
||||
summary: A four-seat team that turns one OpenRig issue into one pull request.
|
||||
source:
|
||||
repository: https://github.com/mvschwarz/openrig-world
|
||||
folder: rigs/openrig-dev
|
||||
resolvedCommit: 894ad0e301ab46ac6ddd74dea784b7f6311d69a2
|
||||
requestedRef: main
|
||||
configurations:
|
||||
- id: build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex
|
||||
alias: recommended
|
||||
recommended: true
|
||||
packageDigest: { algorithm: sha256, value: 5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29, coverage: openrig.package-digest/v1 }
|
||||
assembler: { openrigVersion: 0.6.6, commit: 910bf9f1 }
|
||||
behaviour: views/openrig-dev/build.impl-claude-code_build.lead-claude-code_check.qa-codex_check.review-codex.0.6.6.json
|
||||
review:
|
||||
date: 2026-10-04
|
||||
note: Reviewed for listing. Review is not a security audit.
|
||||
status: listed
|
||||
withdrawnOn: 2026-10-05
|
||||
@@ -0,0 +1,16 @@
|
||||
schema: openrig.registry-entry/v1
|
||||
slug: openrig-dev
|
||||
name: OpenRig contributor team
|
||||
summary: A four-seat team that turns one OpenRig issue into one pull request.
|
||||
configurations:
|
||||
- id: build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex
|
||||
alias: recommended
|
||||
recommended: true
|
||||
packageDigest: { algorithm: sha256, value: 5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29, coverage: openrig.package-digest/v1 }
|
||||
assembler: { openrigVersion: 0.6.6, commit: 910bf9f1 }
|
||||
behaviour: views/openrig-dev/build.impl-claude-code_build.lead-claude-code_check.qa-codex_check.review-codex.0.6.6.json
|
||||
review:
|
||||
date: 2026-10-04
|
||||
note: Reviewed for listing. Review is not a security audit.
|
||||
status: listed
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
schema: openrig.registry-entry/v1
|
||||
slug: openrig-dev
|
||||
name: OpenRig contributor team
|
||||
summary: A four-seat team that turns one OpenRig issue into one pull request.
|
||||
source:
|
||||
repository: https://github.com/mvschwarz/openrig-world
|
||||
folder: ../rigs/openrig-dev
|
||||
resolvedCommit: 894ad0e301ab46ac6ddd74dea784b7f6311d69a2
|
||||
requestedRef: main
|
||||
configurations:
|
||||
- id: build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex
|
||||
alias: recommended
|
||||
recommended: true
|
||||
packageDigest: { algorithm: sha256, value: 5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29, coverage: openrig.package-digest/v1 }
|
||||
assembler: { openrigVersion: 0.6.6, commit: 910bf9f1 }
|
||||
behaviour: views/openrig-dev/build.impl-claude-code_build.lead-claude-code_check.qa-codex_check.review-codex.0.6.6.json
|
||||
review:
|
||||
date: 2026-10-04
|
||||
note: Reviewed for listing. Review is not a security audit.
|
||||
status: listed
|
||||
@@ -0,0 +1,20 @@
|
||||
schema: openrig.registry-entry/v1
|
||||
slug: openrig-dev
|
||||
name: OpenRig contributor team
|
||||
summary: A four-seat team that turns one OpenRig issue into one pull request.
|
||||
source:
|
||||
repository: https://github.com/mvschwarz/openrig-world
|
||||
folder: rigs/openrig-dev
|
||||
resolvedCommit: 894ad0e
|
||||
requestedRef: main
|
||||
configurations:
|
||||
- id: build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex
|
||||
alias: recommended
|
||||
recommended: true
|
||||
packageDigest: { algorithm: sha256, value: 5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29, coverage: openrig.package-digest/v1 }
|
||||
assembler: { openrigVersion: 0.6.6, commit: 910bf9f1 }
|
||||
behaviour: views/openrig-dev/build.impl-claude-code_build.lead-claude-code_check.qa-codex_check.review-codex.0.6.6.json
|
||||
review:
|
||||
date: 2026-10-04
|
||||
note: Reviewed for listing. Review is not a security audit.
|
||||
status: listed
|
||||
@@ -0,0 +1,20 @@
|
||||
schema: openrig.registry-entry/v1
|
||||
slug: withdrawn
|
||||
name: OpenRig contributor team
|
||||
summary: A four-seat team that turns one OpenRig issue into one pull request.
|
||||
source:
|
||||
repository: https://github.com/mvschwarz/openrig-world
|
||||
folder: rigs/openrig-dev
|
||||
resolvedCommit: 894ad0e301ab46ac6ddd74dea784b7f6311d69a2
|
||||
requestedRef: main
|
||||
configurations:
|
||||
- id: build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex
|
||||
alias: recommended
|
||||
recommended: true
|
||||
packageDigest: { algorithm: sha256, value: 5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29, coverage: openrig.package-digest/v1 }
|
||||
assembler: { openrigVersion: 0.6.6, commit: 910bf9f1 }
|
||||
behaviour: views/openrig-dev/build.impl-claude-code_build.lead-claude-code_check.qa-codex_check.review-codex.0.6.6.json
|
||||
review:
|
||||
date: 2026-10-04
|
||||
note: Reviewed for listing. Review is not a security audit.
|
||||
status: withdrawn
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"schema": "openrig.run-record/v1",
|
||||
"id": "rr-20261004-004",
|
||||
"subject": {
|
||||
"kind": "harness_check",
|
||||
"harness": "pi",
|
||||
"configurationId": "build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex"
|
||||
},
|
||||
"execution": {
|
||||
"resources": [
|
||||
{
|
||||
"kind": "plugin",
|
||||
"ref": "openrig-core",
|
||||
"version": "0.1.3"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"permissionPolicy": "none declared"
|
||||
},
|
||||
"unknowns": [
|
||||
"model chosen by the runtime default"
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"openrig": {
|
||||
"version": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
},
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"harnesses": [
|
||||
{
|
||||
"name": "claude-code",
|
||||
"version": "2.1.0",
|
||||
"knownBy": "claude --version"
|
||||
},
|
||||
{
|
||||
"name": "codex",
|
||||
"version": "0.50.0",
|
||||
"knownBy": "codex --version"
|
||||
}
|
||||
]
|
||||
},
|
||||
"evidence": {
|
||||
"kind": "native-workflow",
|
||||
"receipt": {
|
||||
"ref": "receipt.md",
|
||||
"sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
}
|
||||
},
|
||||
"journey": {
|
||||
"id": "harness-check",
|
||||
"version": 1
|
||||
},
|
||||
"outcome": {
|
||||
"steps": [
|
||||
{
|
||||
"id": "harness-check",
|
||||
"result": "BLOCKED"
|
||||
}
|
||||
],
|
||||
"assistance": {
|
||||
"count": 0,
|
||||
"description": ""
|
||||
},
|
||||
"recordedBy": "fleet",
|
||||
"at": "2026-10-04T12:00:00Z",
|
||||
"publicNote": "Pi was not signed in on the test machine."
|
||||
},
|
||||
"relations": {
|
||||
"supersedes": [],
|
||||
"withdraws": [],
|
||||
"resolves": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"schema": "openrig.run-record/v1",
|
||||
"id": "rr-20261004-001",
|
||||
"subject": {
|
||||
"kind": "team",
|
||||
"source": {
|
||||
"repository": "https://github.com/mvschwarz/openrig-world",
|
||||
"folder": "rigs/openrig-dev",
|
||||
"resolvedCommit": "894ad0e301ab46ac6ddd74dea784b7f6311d69a2",
|
||||
"requestedRef": "main",
|
||||
"canonicalUrl": "https://github.com/mvschwarz/openrig-world/tree/894ad0e301ab46ac6ddd74dea784b7f6311d69a2/rigs/openrig-dev"
|
||||
},
|
||||
"configurationId": "build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex",
|
||||
"packageDigest": {
|
||||
"algorithm": "sha256",
|
||||
"value": "5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
},
|
||||
"assembler": {
|
||||
"openrigVersion": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
}
|
||||
},
|
||||
"execution": {
|
||||
"resources": [
|
||||
{
|
||||
"kind": "plugin",
|
||||
"ref": "openrig-core",
|
||||
"version": "0.1.3"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"permissionPolicy": "none declared"
|
||||
},
|
||||
"unknowns": [
|
||||
"model chosen by the runtime default"
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"openrig": {
|
||||
"version": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
},
|
||||
"platform": "macos",
|
||||
"arch": "x64",
|
||||
"harnesses": [
|
||||
{
|
||||
"name": "claude-code",
|
||||
"version": "2.1.0",
|
||||
"knownBy": "claude --version"
|
||||
},
|
||||
{
|
||||
"name": "codex",
|
||||
"version": "0.50.0",
|
||||
"knownBy": "codex --version"
|
||||
}
|
||||
]
|
||||
},
|
||||
"evidence": {
|
||||
"kind": "native-workflow",
|
||||
"receipt": {
|
||||
"ref": "receipt.md",
|
||||
"sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
}
|
||||
},
|
||||
"journey": {
|
||||
"id": "contributor-team",
|
||||
"version": 1
|
||||
},
|
||||
"outcome": {
|
||||
"steps": [
|
||||
{
|
||||
"id": "install",
|
||||
"result": "PASS"
|
||||
},
|
||||
{
|
||||
"id": "orient",
|
||||
"result": "PASS"
|
||||
},
|
||||
{
|
||||
"id": "pull-request",
|
||||
"result": "PASS"
|
||||
}
|
||||
],
|
||||
"assistance": {
|
||||
"count": 0,
|
||||
"description": ""
|
||||
},
|
||||
"recordedBy": "fleet",
|
||||
"at": "2026-10-04T12:00:00Z"
|
||||
},
|
||||
"relations": {
|
||||
"supersedes": [],
|
||||
"withdraws": [],
|
||||
"resolves": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"generated": {
|
||||
"by": "openrig-world tools/status.mjs",
|
||||
"notice": "edited by hand",
|
||||
"rule": "openrig.status-rule/v1",
|
||||
"generatorVersion": 1
|
||||
},
|
||||
"listings": {
|
||||
"openrig-dev": {
|
||||
"state": "ok",
|
||||
"configurations": {
|
||||
"build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex": {
|
||||
"platforms": {
|
||||
"linux-x64": {
|
||||
"label": "tested",
|
||||
"date": "2026-10-04",
|
||||
"openrigVersion": "0.6.6",
|
||||
"recordIds": [
|
||||
"rr-20261004-002"
|
||||
],
|
||||
"packageDigests": [
|
||||
{
|
||||
"algorithm": "sha256",
|
||||
"value": "5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"communityReports": 0
|
||||
},
|
||||
"build.impl=pi,build.lead=pi,check.qa=pi,check.review=pi": {
|
||||
"platforms": {},
|
||||
"communityReports": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"unreadable-example": {
|
||||
"state": "status_unavailable",
|
||||
"configurations": {}
|
||||
}
|
||||
},
|
||||
"harnessChecks": {
|
||||
"state": "ok",
|
||||
"harnesses": {
|
||||
"pi": {
|
||||
"linux-x64": {
|
||||
"result": "BLOCKED",
|
||||
"date": "2026-10-04",
|
||||
"openrigVersion": "0.6.6",
|
||||
"harnessVersion": "unknown",
|
||||
"recordIds": [
|
||||
"rr-20261004-004"
|
||||
],
|
||||
"note": "Pi was not signed in on the test machine."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bodyDigest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"generated": {
|
||||
"by": "openrig-world tools/status.mjs",
|
||||
"notice": "Generated file. Do not edit by hand; regenerate it.",
|
||||
"rule": "openrig.status-rule/v1",
|
||||
"generatorVersion": 1
|
||||
},
|
||||
"listings": {
|
||||
"openrig-dev": {
|
||||
"state": "ok",
|
||||
"configurations": {
|
||||
"build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex": {
|
||||
"platforms": {
|
||||
"linux-x64": {
|
||||
"label": "tested",
|
||||
"date": "2026-10-04",
|
||||
"openrigVersion": "0.6.6",
|
||||
"recordIds": [
|
||||
"rr-20261004-002"
|
||||
],
|
||||
"packageDigests": [
|
||||
{
|
||||
"algorithm": "sha256",
|
||||
"value": "5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
}
|
||||
],
|
||||
"assistanceCount": 1
|
||||
}
|
||||
},
|
||||
"communityReports": 0
|
||||
},
|
||||
"build.impl=pi,build.lead=pi,check.qa=pi,check.review=pi": {
|
||||
"platforms": {},
|
||||
"communityReports": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"unreadable-example": {
|
||||
"state": "status_unavailable",
|
||||
"configurations": {}
|
||||
}
|
||||
},
|
||||
"harnessChecks": {
|
||||
"state": "ok",
|
||||
"harnesses": {
|
||||
"pi": {
|
||||
"linux-x64": {
|
||||
"result": "BLOCKED",
|
||||
"date": "2026-10-04",
|
||||
"openrigVersion": "0.6.6",
|
||||
"harnessVersion": "unknown",
|
||||
"recordIds": [
|
||||
"rr-20261004-004"
|
||||
],
|
||||
"note": "Pi was not signed in on the test machine."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bodyDigest": "548f7ce51c9cf66f34fd0b613dd3754240350cd856ac51f7f98140300766dd57"
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"generated": {
|
||||
"by": "openrig-world tools/status.mjs",
|
||||
"notice": "Generated file. Do not edit by hand; regenerate it.",
|
||||
"rule": "openrig.status-rule/v1",
|
||||
"generatorVersion": 1
|
||||
},
|
||||
"listings": {
|
||||
"openrig-dev": {
|
||||
"state": "ok",
|
||||
"configurations": {
|
||||
"build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex": {
|
||||
"platforms": {
|
||||
"linux-x64": {
|
||||
"label": "tested",
|
||||
"date": "2026-10-04",
|
||||
"openrigVersion": "0.6.6",
|
||||
"recordIds": [],
|
||||
"packageDigests": [
|
||||
{
|
||||
"algorithm": "sha256",
|
||||
"value": "5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"communityReports": 0
|
||||
},
|
||||
"build.impl=pi,build.lead=pi,check.qa=pi,check.review=pi": {
|
||||
"platforms": {},
|
||||
"communityReports": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"unreadable-example": {
|
||||
"state": "status_unavailable",
|
||||
"configurations": {}
|
||||
}
|
||||
},
|
||||
"harnessChecks": {
|
||||
"state": "ok",
|
||||
"harnesses": {
|
||||
"pi": {
|
||||
"linux-x64": {
|
||||
"result": "BLOCKED",
|
||||
"date": "2026-10-04",
|
||||
"openrigVersion": "0.6.6",
|
||||
"harnessVersion": "unknown",
|
||||
"recordIds": [
|
||||
"rr-20261004-004"
|
||||
],
|
||||
"note": "Pi was not signed in on the test machine."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bodyDigest": "c6ceabe8326538cc3c662c923085f403888a7a06c8c4cf4823fda5e159bc99c1"
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"generated": {
|
||||
"by": "openrig-world tools/status.mjs",
|
||||
"notice": "Generated file. Do not edit by hand; regenerate it.",
|
||||
"rule": "openrig.status-rule/v1",
|
||||
"generatorVersion": 1
|
||||
},
|
||||
"listings": {
|
||||
"openrig-dev": {
|
||||
"state": "ok",
|
||||
"configurations": {
|
||||
"build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex": {
|
||||
"platforms": {
|
||||
"linux-x64": {
|
||||
"label": "tested",
|
||||
"date": "2026-10-04",
|
||||
"openrigVersion": "0.6.6",
|
||||
"recordIds": [
|
||||
"rr-20261004-002"
|
||||
],
|
||||
"packageDigests": [
|
||||
{
|
||||
"algorithm": "sha256",
|
||||
"value": "5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"communityReports": 0
|
||||
},
|
||||
"build.impl=pi,build.lead=pi,check.qa=pi,check.review=pi": {
|
||||
"platforms": {},
|
||||
"communityReports": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"unreadable-example": {
|
||||
"state": "status_unavailable",
|
||||
"configurations": {}
|
||||
}
|
||||
},
|
||||
"harnessChecks": {
|
||||
"state": "status_unavailable",
|
||||
"harnesses": {
|
||||
"pi": {
|
||||
"linux-x64": {
|
||||
"result": "BLOCKED",
|
||||
"date": "2026-10-04",
|
||||
"openrigVersion": "0.6.6",
|
||||
"harnessVersion": "unknown",
|
||||
"recordIds": [
|
||||
"rr-20261004-004"
|
||||
],
|
||||
"note": "Pi was not signed in on the test machine."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bodyDigest": "c23b3fae312e39667da2ec9fbb0a44c9d68048e0ca4c392301bf884ca30a44e8"
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"generated": {
|
||||
"by": "openrig-world tools/status.mjs",
|
||||
"notice": "Generated file. Do not edit by hand; regenerate it.",
|
||||
"rule": "openrig.status-rule/v1",
|
||||
"generatorVersion": 1
|
||||
},
|
||||
"listings": {
|
||||
"openrig-dev": {
|
||||
"state": "status_unavailable",
|
||||
"configurations": {
|
||||
"build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex": {
|
||||
"platforms": {
|
||||
"linux-x64": {
|
||||
"label": "tested",
|
||||
"date": "2026-10-04",
|
||||
"openrigVersion": "0.6.6",
|
||||
"recordIds": [
|
||||
"rr-20261004-002"
|
||||
],
|
||||
"packageDigests": [
|
||||
{
|
||||
"algorithm": "sha256",
|
||||
"value": "5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"communityReports": 0
|
||||
},
|
||||
"build.impl=pi,build.lead=pi,check.qa=pi,check.review=pi": {
|
||||
"platforms": {},
|
||||
"communityReports": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"unreadable-example": {
|
||||
"state": "status_unavailable",
|
||||
"configurations": {}
|
||||
}
|
||||
},
|
||||
"harnessChecks": {
|
||||
"state": "ok",
|
||||
"harnesses": {
|
||||
"pi": {
|
||||
"linux-x64": {
|
||||
"result": "BLOCKED",
|
||||
"date": "2026-10-04",
|
||||
"openrigVersion": "0.6.6",
|
||||
"harnessVersion": "unknown",
|
||||
"recordIds": [
|
||||
"rr-20261004-004"
|
||||
],
|
||||
"note": "Pi was not signed in on the test machine."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bodyDigest": "c6ceabe8326538cc3c662c923085f403888a7a06c8c4cf4823fda5e159bc99c1"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"schema": "openrig.bundle-behaviour/v1",
|
||||
"state": "not_generated",
|
||||
"identity": {
|
||||
"source": {
|
||||
"repository": "https://github.com/mvschwarz/openrig-world",
|
||||
"folder": "rigs/openrig-dev",
|
||||
"resolvedCommit": "894ad0e301ab46ac6ddd74dea784b7f6311d69a2",
|
||||
"requestedRef": "main",
|
||||
"canonicalUrl": "https://github.com/mvschwarz/openrig-world/tree/894ad0e301ab46ac6ddd74dea784b7f6311d69a2/rigs/openrig-dev"
|
||||
},
|
||||
"configurationId": "build.impl=pi,build.lead=claude-code,check.qa=codex,check.review=codex",
|
||||
"packageDigest": null,
|
||||
"assembler": null,
|
||||
"generator": {
|
||||
"openrigVersion": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
}
|
||||
},
|
||||
"reason": "No view has been generated for this combination.",
|
||||
"localInspectCommand": "rig bundle inspect <the archive built for this combination> --json"
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"schema": "openrig.bundle-behaviour/v1",
|
||||
"state": "generated",
|
||||
"identity": {
|
||||
"source": null,
|
||||
"configurationId": null,
|
||||
"packageDigest": {
|
||||
"algorithm": "sha256",
|
||||
"value": "5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
},
|
||||
"assembler": null,
|
||||
"generator": {
|
||||
"openrigVersion": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
},
|
||||
"integrity": {
|
||||
"digestValid": true,
|
||||
"filesVerified": true
|
||||
}
|
||||
},
|
||||
"team": [
|
||||
{
|
||||
"seat": "build.lead",
|
||||
"pod": "build",
|
||||
"member": "lead",
|
||||
"agentRef": "local:agents/openrig-dev",
|
||||
"profile": "lead",
|
||||
"runtime": "claude-code",
|
||||
"cwd": "--cwd (the user's openrig clone)"
|
||||
}
|
||||
],
|
||||
"posture": [
|
||||
{
|
||||
"seat": "build.lead",
|
||||
"shellAccess": "runs shell commands in its working folder under your logged-in account",
|
||||
"selection": "set by a host setting",
|
||||
"basis": "unresolved",
|
||||
"nativeEffect": "unknown"
|
||||
}
|
||||
],
|
||||
"toldFiles": [],
|
||||
"alsoRuns": [],
|
||||
"writes": [],
|
||||
"outsideAddresses": [],
|
||||
"needs": [],
|
||||
"unknownBeforeLaunch": [
|
||||
{
|
||||
"subject": "source and configuration",
|
||||
"reason": "the archive does not state them"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"schema": "openrig.bundle-behaviour/v1",
|
||||
"state": "generated",
|
||||
"identity": {
|
||||
"source": null,
|
||||
"configurationId": null,
|
||||
"packageDigest": null,
|
||||
"assembler": null,
|
||||
"generator": {
|
||||
"openrigVersion": "0.6.6"
|
||||
},
|
||||
"integrity": {
|
||||
"digestValid": false,
|
||||
"filesVerified": false
|
||||
}
|
||||
},
|
||||
"team": [
|
||||
{
|
||||
"seat": "team.claude-code",
|
||||
"pod": "team",
|
||||
"member": "claude-code",
|
||||
"agentRef": "local:agent",
|
||||
"profile": "default",
|
||||
"runtime": "claude-code",
|
||||
"cwd": "."
|
||||
},
|
||||
{
|
||||
"seat": "team.codex",
|
||||
"pod": "team",
|
||||
"member": "codex",
|
||||
"agentRef": "local:agent",
|
||||
"profile": "default",
|
||||
"runtime": "codex",
|
||||
"cwd": "."
|
||||
},
|
||||
{
|
||||
"seat": "team.pi",
|
||||
"pod": "team",
|
||||
"member": "pi",
|
||||
"agentRef": "local:agent",
|
||||
"profile": "default",
|
||||
"runtime": "pi",
|
||||
"cwd": "."
|
||||
}
|
||||
],
|
||||
"posture": [
|
||||
{
|
||||
"seat": "team.claude-code",
|
||||
"shellAccess": "Can run shell commands as the launching user, subject to runtime and host policy.",
|
||||
"selection": "acceptEdits (conditional launch floor)",
|
||||
"basis": "product_default",
|
||||
"permissionPrompts": "default",
|
||||
"nativeEffect": "unknown",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml",
|
||||
"field": "pods.team.members.claude-code"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"seat": "team.codex",
|
||||
"shellAccess": "Can run shell commands as the launching user, subject to runtime and host policy.",
|
||||
"selection": "workspace-write (conditional launch floor); approval policy resolved at launch",
|
||||
"basis": "product_default",
|
||||
"permissionPrompts": "default",
|
||||
"nativeEffect": "unknown",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml",
|
||||
"field": "pods.team.members.codex"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"seat": "team.pi",
|
||||
"shellAccess": "Can run shell commands as the launching user, subject to runtime and host policy.",
|
||||
"selection": "resource trust resolved at launch; not a native permission policy",
|
||||
"basis": "product_default",
|
||||
"nativeEffect": "unknown",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml",
|
||||
"field": "pods.team.members.pi"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"toldFiles": [],
|
||||
"alsoRuns": [],
|
||||
"writes": [],
|
||||
"outsideAddresses": [],
|
||||
"needs": [],
|
||||
"unknownBeforeLaunch": [
|
||||
{
|
||||
"subject": "host and native settings",
|
||||
"reason": "Effective native permissions are not observed before launch.",
|
||||
"sourceRefs": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"schema": "openrig.bundle-behaviour/v1",
|
||||
"state": "generated",
|
||||
"identity": {
|
||||
"source": null,
|
||||
"configurationId": null,
|
||||
"packageDigest": null,
|
||||
"assembler": null,
|
||||
"generator": {
|
||||
"openrigVersion": "0.6.6"
|
||||
},
|
||||
"integrity": {
|
||||
"digestValid": false,
|
||||
"filesVerified": false
|
||||
}
|
||||
},
|
||||
"team": [
|
||||
{
|
||||
"seat": "team.claude-code",
|
||||
"pod": "team",
|
||||
"member": "claude-code",
|
||||
"agentRef": "local:agent",
|
||||
"profile": "default",
|
||||
"runtime": "claude-code",
|
||||
"cwd": "."
|
||||
},
|
||||
{
|
||||
"seat": "team.codex",
|
||||
"pod": "team",
|
||||
"member": "codex",
|
||||
"agentRef": "local:agent",
|
||||
"profile": "default",
|
||||
"runtime": "codex",
|
||||
"cwd": "."
|
||||
},
|
||||
{
|
||||
"seat": "team.pi",
|
||||
"pod": "team",
|
||||
"member": "pi",
|
||||
"agentRef": "local:agent",
|
||||
"profile": "default",
|
||||
"runtime": "pi",
|
||||
"cwd": "."
|
||||
}
|
||||
],
|
||||
"posture": [
|
||||
{
|
||||
"seat": "team.claude-code",
|
||||
"shellAccess": "Can run shell commands as the launching user, subject to runtime and host policy.",
|
||||
"selection": "Permission prompts: off; Claude bypasses permissions (full_bypass)",
|
||||
"basis": "explicit",
|
||||
"permissionPrompts": "off",
|
||||
"nativeEffect": "unknown",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml",
|
||||
"field": "permission_policy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"seat": "team.codex",
|
||||
"shellAccess": "Can run shell commands as the launching user, subject to runtime and host policy.",
|
||||
"selection": "Permission prompts: off; Codex runs with full access and never asks (full_bypass)",
|
||||
"basis": "explicit",
|
||||
"permissionPrompts": "off",
|
||||
"nativeEffect": "unknown",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml",
|
||||
"field": "permission_policy"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"seat": "team.pi",
|
||||
"shellAccess": "Can run shell commands as the launching user, subject to runtime and host policy.",
|
||||
"selection": "Permission prompts: off; Pi gets full resource trust (full_bypass / approve); not a native permission policy",
|
||||
"basis": "explicit",
|
||||
"permissionPrompts": "off",
|
||||
"nativeEffect": "unknown",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml",
|
||||
"field": "permission_policy"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"toldFiles": [],
|
||||
"alsoRuns": [],
|
||||
"writes": [],
|
||||
"outsideAddresses": [],
|
||||
"needs": [],
|
||||
"unknownBeforeLaunch": [
|
||||
{
|
||||
"subject": "host and native settings",
|
||||
"reason": "Effective native permissions are not observed before launch.",
|
||||
"sourceRefs": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
{
|
||||
"schema": "openrig.bundle-behaviour/v1",
|
||||
"state": "generated",
|
||||
"identity": {
|
||||
"source": {
|
||||
"repository": "https://github.com/mvschwarz/openrig-world",
|
||||
"folder": "rigs/openrig-dev",
|
||||
"resolvedCommit": "894ad0e301ab46ac6ddd74dea784b7f6311d69a2",
|
||||
"requestedRef": "main",
|
||||
"canonicalUrl": "https://github.com/mvschwarz/openrig-world/tree/894ad0e301ab46ac6ddd74dea784b7f6311d69a2/rigs/openrig-dev"
|
||||
},
|
||||
"configurationId": "build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex",
|
||||
"packageDigest": {
|
||||
"algorithm": "sha256",
|
||||
"value": "5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
},
|
||||
"assembler": {
|
||||
"openrigVersion": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
},
|
||||
"generator": {
|
||||
"openrigVersion": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
},
|
||||
"integrity": {
|
||||
"digestValid": true,
|
||||
"filesVerified": true
|
||||
},
|
||||
"statedProvenance": {}
|
||||
},
|
||||
"team": [
|
||||
{
|
||||
"seat": "build.lead",
|
||||
"pod": "build",
|
||||
"member": "lead",
|
||||
"agentRef": "local:agents/openrig-dev",
|
||||
"profile": "lead",
|
||||
"runtime": "claude-code",
|
||||
"cwd": "--cwd (the user's openrig clone)"
|
||||
},
|
||||
{
|
||||
"seat": "build.impl",
|
||||
"pod": "build",
|
||||
"member": "impl",
|
||||
"agentRef": "local:agents/openrig-dev",
|
||||
"profile": "implementer",
|
||||
"runtime": "claude-code",
|
||||
"cwd": "--cwd (the user's openrig clone)"
|
||||
},
|
||||
{
|
||||
"seat": "check.review",
|
||||
"pod": "check",
|
||||
"member": "review",
|
||||
"agentRef": "local:agents/openrig-dev",
|
||||
"profile": "reviewer",
|
||||
"runtime": "codex",
|
||||
"cwd": "--cwd (the user's openrig clone)"
|
||||
},
|
||||
{
|
||||
"seat": "check.qa",
|
||||
"pod": "check",
|
||||
"member": "qa",
|
||||
"agentRef": "local:agents/openrig-dev",
|
||||
"profile": "qa",
|
||||
"runtime": "codex",
|
||||
"cwd": "--cwd (the user's openrig clone)"
|
||||
}
|
||||
],
|
||||
"posture": [
|
||||
{
|
||||
"seat": "build.lead",
|
||||
"shellAccess": "runs shell commands in its working folder under your logged-in account",
|
||||
"selection": "none declared",
|
||||
"basis": "product_default",
|
||||
"nativeEffect": "unknown",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"seat": "build.impl",
|
||||
"shellAccess": "runs shell commands in its working folder under your logged-in account",
|
||||
"selection": "none declared",
|
||||
"basis": "product_default",
|
||||
"nativeEffect": "unknown",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"seat": "check.review",
|
||||
"shellAccess": "runs shell commands in its working folder under your logged-in account",
|
||||
"selection": "none declared",
|
||||
"basis": "product_default",
|
||||
"nativeEffect": "unknown",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"seat": "check.qa",
|
||||
"shellAccess": "runs shell commands in its working folder under your logged-in account",
|
||||
"selection": "none declared",
|
||||
"basis": "product_default",
|
||||
"nativeEffect": "unknown",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"toldFiles": [
|
||||
{
|
||||
"kind": "culture",
|
||||
"pathOrRef": "CULTURE.md",
|
||||
"delivery": "guidance_merge",
|
||||
"resolution": "archive",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml",
|
||||
"field": "culture_file"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "startup",
|
||||
"pathOrRef": "startup/context.md",
|
||||
"delivery": "send_text",
|
||||
"resolution": "archive",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml",
|
||||
"field": "startup.files"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"seat": "build.lead",
|
||||
"kind": "role",
|
||||
"pathOrRef": "agents/openrig-dev/guidance/lead.md",
|
||||
"resolution": "archive",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "agents/openrig-dev/agent.yaml",
|
||||
"field": "profiles.lead"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"alsoRuns": [
|
||||
{
|
||||
"kind": "plugin",
|
||||
"pathOrRef": "openrig-home:plugins/openrig-core",
|
||||
"resolution": "host_at_launch",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "agents/openrig-dev/agent.yaml",
|
||||
"field": "resources.plugins"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "hook",
|
||||
"pathOrRef": "agents/openrig-dev/runtime/claude-activity-hooks.json",
|
||||
"trigger": "Claude Code activity events",
|
||||
"resolution": "archive",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "agents/openrig-dev/agent.yaml"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"writes": [
|
||||
{
|
||||
"kind": "managed_block",
|
||||
"destinationBase": "each seat's working folder",
|
||||
"path": "CLAUDE.local.md",
|
||||
"operation": "add OpenRig block",
|
||||
"phase": "launch",
|
||||
"condition": "Claude Code seats",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml",
|
||||
"field": "managed_blocks"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "managed_block",
|
||||
"destinationBase": "each seat's working folder",
|
||||
"path": "AGENTS.md",
|
||||
"operation": "add OpenRig block",
|
||||
"phase": "launch",
|
||||
"condition": "Codex seats"
|
||||
}
|
||||
],
|
||||
"outsideAddresses": [
|
||||
{
|
||||
"domain": "github.com",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "README.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"needs": [
|
||||
{
|
||||
"kind": "runtime",
|
||||
"name": "claude-code",
|
||||
"status": "not_checked",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "runtime",
|
||||
"name": "codex",
|
||||
"status": "not_checked",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "rig.yaml"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "account",
|
||||
"name": "GitHub account with push credentials",
|
||||
"status": "not_checked",
|
||||
"sourceRefs": [
|
||||
{
|
||||
"path": "README.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"unknownBeforeLaunch": [
|
||||
{
|
||||
"subject": "effective permissions",
|
||||
"reason": "host settings decide them at launch"
|
||||
},
|
||||
{
|
||||
"subject": "model and effort",
|
||||
"reason": "not declared; the runtime's defaults apply"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
schema: openrig.bundle-configurations/v1
|
||||
recommended: recommended
|
||||
seats:
|
||||
build.lead: { runtimes: { claude-code: lead, codex: lead, pi: lead } }
|
||||
build.impl: { runtimes: { claude-code: implementer, codex: implementer, pi: implementer } }
|
||||
check.review: { runtimes: { codex: reviewer, claude-code: reviewer, pi: reviewer } }
|
||||
check.qa: { runtimes: { codex: qa, claude-code: qa, pi: qa } }
|
||||
presets:
|
||||
recommended: { build.lead: claude-code, build.impl: claude-code, check.review: codex, check.qa: codex }
|
||||
all-claude: { build.lead: claude-code, build.impl: claude-code, check.review: claude-code, check.qa: claude-code }
|
||||
all-codex: { build.lead: codex, build.impl: codex, check.review: codex, check.qa: codex }
|
||||
all-pi: { build.lead: pi, build.impl: pi, check.review: pi, check.qa: pi }
|
||||
@@ -0,0 +1,20 @@
|
||||
schema: openrig.registry-entry/v1
|
||||
slug: openrig-dev
|
||||
name: OpenRig contributor team
|
||||
summary: A four-seat team that turns one OpenRig issue into one pull request.
|
||||
source:
|
||||
repository: https://github.com/mvschwarz/openrig-world
|
||||
folder: rigs/openrig-dev
|
||||
resolvedCommit: 894ad0e301ab46ac6ddd74dea784b7f6311d69a2
|
||||
requestedRef: main
|
||||
configurations:
|
||||
- id: build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex
|
||||
alias: recommended
|
||||
recommended: true
|
||||
packageDigest: { algorithm: sha256, value: 5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29, coverage: openrig.package-digest/v1 }
|
||||
assembler: { openrigVersion: 0.6.6, commit: 910bf9f1 }
|
||||
behaviour: views/openrig-dev/build.impl-claude-code_build.lead-claude-code_check.qa-codex_check.review-codex.0.6.6.json
|
||||
review:
|
||||
date: 2026-10-04
|
||||
note: Reviewed for listing. Review is not a security audit.
|
||||
status: listed
|
||||
@@ -0,0 +1,21 @@
|
||||
schema: openrig.registry-entry/v1
|
||||
slug: withdrawn
|
||||
name: OpenRig contributor team
|
||||
summary: A four-seat team that turns one OpenRig issue into one pull request.
|
||||
source:
|
||||
repository: https://github.com/mvschwarz/openrig-world
|
||||
folder: rigs/openrig-dev
|
||||
resolvedCommit: 894ad0e301ab46ac6ddd74dea784b7f6311d69a2
|
||||
requestedRef: main
|
||||
configurations:
|
||||
- id: build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex
|
||||
alias: recommended
|
||||
recommended: true
|
||||
packageDigest: { algorithm: sha256, value: 5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29, coverage: openrig.package-digest/v1 }
|
||||
assembler: { openrigVersion: 0.6.6, commit: 910bf9f1 }
|
||||
behaviour: views/openrig-dev/build.impl-claude-code_build.lead-claude-code_check.qa-codex_check.review-codex.0.6.6.json
|
||||
review:
|
||||
date: 2026-10-04
|
||||
note: Reviewed for listing. Review is not a security audit.
|
||||
status: withdrawn
|
||||
withdrawnOn: 2026-10-05
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"schema": "openrig.run-record/v1",
|
||||
"id": "rr-20261004-004",
|
||||
"subject": {
|
||||
"kind": "harness_check",
|
||||
"harness": "pi"
|
||||
},
|
||||
"execution": {
|
||||
"resources": [
|
||||
{
|
||||
"kind": "plugin",
|
||||
"ref": "openrig-core",
|
||||
"version": "0.1.3"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"permissionPolicy": "none declared"
|
||||
},
|
||||
"unknowns": [
|
||||
"model chosen by the runtime default"
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"openrig": {
|
||||
"version": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
},
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"harnesses": [
|
||||
{
|
||||
"name": "claude-code",
|
||||
"version": "2.1.0",
|
||||
"knownBy": "claude --version"
|
||||
},
|
||||
{
|
||||
"name": "codex",
|
||||
"version": "0.50.0",
|
||||
"knownBy": "codex --version"
|
||||
}
|
||||
]
|
||||
},
|
||||
"evidence": {
|
||||
"kind": "native-workflow",
|
||||
"receipt": {
|
||||
"ref": "receipt.md",
|
||||
"sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
}
|
||||
},
|
||||
"journey": {
|
||||
"id": "harness-check",
|
||||
"version": 1
|
||||
},
|
||||
"outcome": {
|
||||
"steps": [
|
||||
{
|
||||
"id": "harness-check",
|
||||
"result": "BLOCKED"
|
||||
}
|
||||
],
|
||||
"assistance": {
|
||||
"count": 0,
|
||||
"description": ""
|
||||
},
|
||||
"recordedBy": "fleet",
|
||||
"at": "2026-10-04T12:00:00Z",
|
||||
"publicNote": "Pi was not signed in on the test machine."
|
||||
},
|
||||
"relations": {
|
||||
"supersedes": [],
|
||||
"withdraws": [],
|
||||
"resolves": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"schema": "openrig.run-record/v1",
|
||||
"id": "rr-20261004-002",
|
||||
"subject": {
|
||||
"kind": "team",
|
||||
"source": {
|
||||
"repository": "https://github.com/mvschwarz/openrig-world",
|
||||
"folder": "rigs/openrig-dev",
|
||||
"resolvedCommit": "894ad0e301ab46ac6ddd74dea784b7f6311d69a2",
|
||||
"requestedRef": "main",
|
||||
"canonicalUrl": "https://github.com/mvschwarz/openrig-world/tree/894ad0e301ab46ac6ddd74dea784b7f6311d69a2/rigs/openrig-dev"
|
||||
},
|
||||
"configurationId": "build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex",
|
||||
"packageDigest": {
|
||||
"algorithm": "sha256",
|
||||
"value": "5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
},
|
||||
"assembler": {
|
||||
"openrigVersion": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
}
|
||||
},
|
||||
"execution": {
|
||||
"resources": [
|
||||
{
|
||||
"kind": "plugin",
|
||||
"ref": "openrig-core",
|
||||
"version": "0.1.3"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"permissionPolicy": "none declared"
|
||||
},
|
||||
"unknowns": [
|
||||
"model chosen by the runtime default"
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"openrig": {
|
||||
"version": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
},
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"harnesses": [
|
||||
{
|
||||
"name": "claude-code",
|
||||
"version": "2.1.0",
|
||||
"knownBy": "claude --version"
|
||||
},
|
||||
{
|
||||
"name": "codex",
|
||||
"version": "0.50.0",
|
||||
"knownBy": "codex --version"
|
||||
}
|
||||
]
|
||||
},
|
||||
"evidence": {
|
||||
"kind": "native-workflow",
|
||||
"receipt": {
|
||||
"ref": "receipt.md",
|
||||
"sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
}
|
||||
},
|
||||
"journey": {
|
||||
"id": "contributor-team",
|
||||
"version": 1
|
||||
},
|
||||
"outcome": {
|
||||
"steps": [
|
||||
{
|
||||
"id": "install",
|
||||
"result": "PASS"
|
||||
},
|
||||
{
|
||||
"id": "orient",
|
||||
"result": "PASS"
|
||||
},
|
||||
{
|
||||
"id": "pull-request",
|
||||
"result": "PASS"
|
||||
}
|
||||
],
|
||||
"assistance": {
|
||||
"count": 1,
|
||||
"description": "pointed the lead at the issue"
|
||||
},
|
||||
"recordedBy": "fleet",
|
||||
"at": "2026-10-04T12:00:00Z"
|
||||
},
|
||||
"relations": {
|
||||
"supersedes": [],
|
||||
"withdraws": [],
|
||||
"resolves": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"schema": "openrig.run-record/v1",
|
||||
"id": "rr-20261004-001",
|
||||
"subject": {
|
||||
"kind": "team",
|
||||
"source": {
|
||||
"repository": "https://github.com/mvschwarz/openrig-world",
|
||||
"folder": "rigs/openrig-dev",
|
||||
"resolvedCommit": "894ad0e301ab46ac6ddd74dea784b7f6311d69a2",
|
||||
"requestedRef": "main",
|
||||
"canonicalUrl": "https://github.com/mvschwarz/openrig-world/tree/894ad0e301ab46ac6ddd74dea784b7f6311d69a2/rigs/openrig-dev"
|
||||
},
|
||||
"configurationId": "build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex",
|
||||
"packageDigest": {
|
||||
"algorithm": "sha256",
|
||||
"value": "5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
},
|
||||
"assembler": {
|
||||
"openrigVersion": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
}
|
||||
},
|
||||
"execution": {
|
||||
"resources": [
|
||||
{
|
||||
"kind": "plugin",
|
||||
"ref": "openrig-core",
|
||||
"version": "0.1.3"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"permissionPolicy": "none declared"
|
||||
},
|
||||
"unknowns": [
|
||||
"model chosen by the runtime default"
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"openrig": {
|
||||
"version": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
},
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"harnesses": [
|
||||
{
|
||||
"name": "claude-code",
|
||||
"version": "2.1.0",
|
||||
"knownBy": "claude --version"
|
||||
},
|
||||
{
|
||||
"name": "codex",
|
||||
"version": "0.50.0",
|
||||
"knownBy": "codex --version"
|
||||
}
|
||||
]
|
||||
},
|
||||
"evidence": {
|
||||
"kind": "native-workflow",
|
||||
"receipt": {
|
||||
"ref": "receipt.md",
|
||||
"sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
}
|
||||
},
|
||||
"journey": {
|
||||
"id": "contributor-team",
|
||||
"version": 1
|
||||
},
|
||||
"outcome": {
|
||||
"steps": [
|
||||
{
|
||||
"id": "install",
|
||||
"result": "PASS"
|
||||
},
|
||||
{
|
||||
"id": "orient",
|
||||
"result": "PASS"
|
||||
},
|
||||
{
|
||||
"id": "pull-request",
|
||||
"result": "PASS"
|
||||
}
|
||||
],
|
||||
"assistance": {
|
||||
"count": 0,
|
||||
"description": ""
|
||||
},
|
||||
"recordedBy": "fleet",
|
||||
"at": "2026-10-04T12:00:00Z"
|
||||
},
|
||||
"relations": {
|
||||
"supersedes": [],
|
||||
"withdraws": [],
|
||||
"resolves": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"schema": "openrig.run-record/v1",
|
||||
"id": "rr-20261004-003",
|
||||
"subject": {
|
||||
"kind": "team",
|
||||
"source": {
|
||||
"repository": "https://github.com/mvschwarz/openrig-world",
|
||||
"folder": "rigs/openrig-dev",
|
||||
"resolvedCommit": "894ad0e301ab46ac6ddd74dea784b7f6311d69a2",
|
||||
"requestedRef": "main",
|
||||
"canonicalUrl": "https://github.com/mvschwarz/openrig-world/tree/894ad0e301ab46ac6ddd74dea784b7f6311d69a2/rigs/openrig-dev"
|
||||
},
|
||||
"configurationId": "build.impl=claude-code,build.lead=claude-code,check.qa=codex,check.review=codex",
|
||||
"packageDigest": {
|
||||
"algorithm": "sha256",
|
||||
"value": "5f2c0a1e9b8d7c6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29",
|
||||
"coverage": "openrig.package-digest/v1"
|
||||
},
|
||||
"assembler": {
|
||||
"openrigVersion": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
}
|
||||
},
|
||||
"execution": {
|
||||
"resources": [
|
||||
{
|
||||
"kind": "plugin",
|
||||
"ref": "openrig-core",
|
||||
"version": "0.1.3"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"permissionPolicy": "none declared"
|
||||
},
|
||||
"unknowns": [
|
||||
"model chosen by the runtime default"
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"openrig": {
|
||||
"version": "0.6.6",
|
||||
"commit": "910bf9f1"
|
||||
},
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"harnesses": [
|
||||
{
|
||||
"name": "claude-code",
|
||||
"version": "2.1.0",
|
||||
"knownBy": "claude --version"
|
||||
},
|
||||
{
|
||||
"name": "codex",
|
||||
"version": "0.50.0",
|
||||
"knownBy": "codex --version"
|
||||
}
|
||||
]
|
||||
},
|
||||
"evidence": {
|
||||
"kind": "native-workflow",
|
||||
"receipt": {
|
||||
"ref": "receipt.md",
|
||||
"sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
}
|
||||
},
|
||||
"journey": {
|
||||
"id": "contributor-team",
|
||||
"version": 1
|
||||
},
|
||||
"outcome": {
|
||||
"steps": [
|
||||
{
|
||||
"id": "install",
|
||||
"result": "PASS"
|
||||
},
|
||||
{
|
||||
"id": "orient",
|
||||
"result": "PASS"
|
||||
},
|
||||
{
|
||||
"id": "pull-request",
|
||||
"result": "PASS"
|
||||
}
|
||||
],
|
||||
"assistance": {
|
||||
"count": 0,
|
||||
"description": ""
|
||||
},
|
||||
"recordedBy": "fleet",
|
||||
"at": "2026-10-04T12:00:00Z"
|
||||
},
|
||||
"relations": {
|
||||
"supersedes": [],
|
||||
"withdraws": [
|
||||
"rr-20261004-001"
|
||||
],
|
||||
"resolves": []
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1452 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user