Compare commits
3134
Commits
aba7809fb9
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d69c367285 | ||
|
|
aa8dd4f89e | ||
|
|
b12e0785e2 | ||
|
|
24291a4545 | ||
|
|
92d073ff36 | ||
|
|
bc57ef38c1 | ||
|
|
ae8a0316d8 | ||
|
|
2cfbb1caea | ||
|
|
703398bd2c | ||
|
|
10c80ae514 | ||
|
|
285763f4ae | ||
|
|
8030045602 | ||
|
|
8c85b93e7d | ||
|
|
4017992c7d | ||
|
|
9dc6d8f144 | ||
|
|
779ced82b1 | ||
|
|
30e4985f9a | ||
|
|
1a1e3c286f | ||
|
|
71c906e04d | ||
|
|
e6bfb27fa9 | ||
|
|
2a3ece0364 | ||
|
|
61d174b174 | ||
|
|
c4814115de | ||
|
|
f84377b2fe | ||
|
|
19f506f8bc | ||
|
|
0a9d09b958 | ||
|
|
83c6d290b4 | ||
|
|
831db34acf | ||
|
|
4a276b0af0 | ||
|
|
c2b82a2591 | ||
|
|
99daaf31b6 | ||
|
|
94e51ea6d1 | ||
|
|
f2d2ab0102 | ||
|
|
6f42f23d2b | ||
|
|
8f54fa2a00 | ||
|
|
c110965911 | ||
|
|
2a48dfc41a | ||
|
|
0451142d41 | ||
|
|
65f18b0cdb | ||
|
|
ea31c7ca81 | ||
|
|
dd47dba5b0 | ||
|
|
cbedc76d06 | ||
|
|
a4aa1a1848 | ||
|
|
ee8ee360ef | ||
|
|
72cae33ea6 | ||
|
|
0cd5ca11cc | ||
|
|
ccb9d03865 | ||
|
|
41cd2d044a | ||
|
|
85d1815dcf | ||
|
|
9989aed916 | ||
|
|
3f2ba9df47 | ||
|
|
cd9f0f009e | ||
|
|
b49abce798 | ||
|
|
51b381a701 | ||
|
|
bea121ade0 | ||
|
|
18112d29a6 | ||
|
|
8bedfcda84 | ||
|
|
402543d617 | ||
|
|
a21ef31ee7 | ||
|
|
e8c159247a | ||
|
|
5dd9392575 | ||
|
|
da2296bc9c | ||
|
|
8e4f35eb3f | ||
|
|
9917c09b19 | ||
|
|
4445501f6c | ||
|
|
6fe36f3e46 | ||
|
|
cfb173c570 | ||
|
|
ad729af592 | ||
|
|
17abe1c40c | ||
|
|
4b8dc302ee | ||
|
|
cf2e74404d | ||
|
|
a0e161c653 | ||
|
|
614157424f | ||
|
|
f5f80fcd48 | ||
|
|
7dd4539f50 | ||
|
|
e66876249e | ||
|
|
d39eb43419 | ||
|
|
674b897321 | ||
|
|
d7e54ed181 | ||
|
|
3e833b5295 | ||
|
|
9194f0a1ba | ||
|
|
6945b7b3c3 | ||
|
|
560226dea2 | ||
|
|
4583b512b3 | ||
|
|
223a6ed011 | ||
|
|
a82234a75e | ||
|
|
92594488da | ||
|
|
2315c69f0a | ||
|
|
9d003a5c98 | ||
|
|
53ec914a52 | ||
|
|
d052cedc7d | ||
|
|
de72afd9a1 | ||
|
|
80ffff642f | ||
|
|
97960d4e3f | ||
|
|
a96038d79f | ||
|
|
1ca36d6b66 | ||
|
|
bb8eda379f | ||
|
|
2858e8ceba | ||
|
|
e35b5797a3 | ||
|
|
25baeedc03 | ||
|
|
17d81e29cc | ||
|
|
08bce5b630 | ||
|
|
84f1b229ba | ||
|
|
856ea7119a | ||
|
|
5f2798458e | ||
|
|
af3decce51 | ||
|
|
1cb6cd4e98 | ||
|
|
21bd089a23 | ||
|
|
88f463e633 | ||
|
|
ce62e09919 | ||
|
|
fe74d7c4b8 | ||
|
|
73902b03b6 | ||
|
|
9aeaa52bdb | ||
|
|
89ee5e48a5 | ||
|
|
5b5396599d | ||
|
|
1e33b2945c | ||
|
|
50b05051bb | ||
|
|
4208b6228e | ||
|
|
bb558bad2b | ||
|
|
fcc7c49ff1 | ||
|
|
fb5f49d2a2 | ||
|
|
82eaa986d8 | ||
|
|
17d6789b41 | ||
|
|
d24d50cac9 | ||
|
|
44b3c78761 | ||
|
|
08daf782b9 | ||
|
|
d7e35ea9ee | ||
|
|
39aa465a51 | ||
|
|
382b5e57f2 | ||
|
|
0d011ea0cd | ||
|
|
b740b2d1e2 | ||
|
|
c97bde9ee0 | ||
|
|
bb742e253d | ||
|
|
a10507c54f | ||
|
|
6a607ccbed | ||
|
|
4ac79b3665 | ||
|
|
a4fdf9cc45 | ||
|
|
981c422122 | ||
|
|
ab0f57c00a | ||
|
|
b723c64fa1 | ||
|
|
f86ae6d52f | ||
|
|
9a548d2b5e | ||
|
|
4e738ac5eb | ||
|
|
a6f3e30652 | ||
|
|
6ca5dfbe11 | ||
|
|
bc835b8503 | ||
|
|
46767daf49 | ||
|
|
99170d47ab | ||
|
|
796fa2ee85 | ||
|
|
200c24bc00 | ||
|
|
0a2b24bf5e | ||
|
|
eec2be87ad | ||
|
|
71cd58f868 | ||
|
|
58ed04ac59 | ||
|
|
c6a476d65d | ||
|
|
4c31ea2228 | ||
|
|
f9e5fca67d | ||
|
|
a2cd860199 | ||
|
|
f5fdce2d07 | ||
|
|
8c60921c99 | ||
|
|
956b453e6a | ||
|
|
063f203efe | ||
|
|
5f44b10ff9 | ||
|
|
e3dc8ee327 | ||
|
|
645458498a | ||
|
|
6c4119e2c6 | ||
|
|
7e9cae5f39 | ||
|
|
1fe1b7463f | ||
|
|
9e48cae759 | ||
|
|
aeb2727bea | ||
|
|
298c20012a | ||
|
|
7507412f1c | ||
|
|
45b7d0764d | ||
|
|
7508d428b0 | ||
|
|
977c8e7b21 | ||
|
|
4964583868 | ||
|
|
14aa1aabea | ||
|
|
76c427c887 | ||
|
|
ff7d874138 | ||
|
|
e881c8fad8 | ||
|
|
06cc6056e5 | ||
|
|
c15999b7a6 | ||
|
|
0b954c1ab6 | ||
|
|
eb0dd67d16 | ||
|
|
97828f8bd5 | ||
|
|
8be2cfd2a3 | ||
|
|
f41ab0e277 | ||
|
|
46a44b232b | ||
|
|
2cf4c57813 | ||
|
|
41534b215a | ||
|
|
5ea2792df7 | ||
|
|
1479148f84 | ||
|
|
7256d80514 | ||
|
|
8e73d755d3 | ||
|
|
027f60d262 | ||
|
|
4f042cae84 | ||
|
|
0b8924eda7 | ||
|
|
eaaf2f6dcc | ||
|
|
f5a0e14991 | ||
|
|
11e4d536c5 | ||
|
|
0fd2486baf | ||
|
|
8294a476d2 | ||
|
|
9922b654d7 | ||
|
|
b910efd945 | ||
|
|
20854edca2 | ||
|
|
2a23a5d574 | ||
|
|
4cf34375a8 | ||
|
|
404809ab6e | ||
|
|
c5c89795e1 | ||
|
|
99b08e0f0c | ||
|
|
83a99541b2 | ||
|
|
1ed835fb79 | ||
|
|
ace134e2f0 | ||
|
|
ecbd003579 | ||
|
|
93e784a3ed | ||
|
|
a1f6a6bad5 | ||
|
|
a6f92104fa | ||
|
|
0ad7d6d210 | ||
|
|
48ff977d06 | ||
|
|
93eea24420 | ||
|
|
e582babae3 | ||
|
|
46ffde19a6 | ||
|
|
6446eb1302 | ||
|
|
ad0f6c68d5 | ||
|
|
6bcd59fcc6 | ||
|
|
1fad5fc8ed | ||
|
|
5c57e10de9 | ||
|
|
c0f4e80320 | ||
|
|
4857910410 | ||
|
|
c2510495ef | ||
|
|
f8baa1edb7 | ||
|
|
7a8bd7717e | ||
|
|
c98048b97b | ||
|
|
38dad4e865 | ||
|
|
d039359386 | ||
|
|
8c68129d69 | ||
|
|
348ac51011 | ||
|
|
544abbbf56 | ||
|
|
fa9bd8207f | ||
|
|
0a7e67e373 | ||
|
|
9cd1c1c448 | ||
|
|
dfea679f0b | ||
|
|
0689116e4d | ||
|
|
02a09ae2d7 | ||
|
|
f9d328f2db | ||
|
|
8c6dcb9483 | ||
|
|
c8b57a6a5d | ||
|
|
7aa4d3067e | ||
|
|
e47eca53a2 | ||
|
|
8d061a8734 | ||
|
|
9c8ab6441c | ||
|
|
dfede080d2 | ||
|
|
cf32d871de | ||
|
|
fe373b5656 | ||
|
|
a44a4bc4f8 | ||
|
|
cb683beef8 | ||
|
|
1d4ffa875a | ||
|
|
297a7ddd9d | ||
|
|
be698c2239 | ||
|
|
e80581d139 | ||
|
|
4e7eaac7d5 | ||
|
|
92f7f3fca3 | ||
|
|
1e10cdecc8 | ||
|
|
ce6b8f65cc | ||
|
|
f60c2d5834 | ||
|
|
795f26fb51 | ||
|
|
8ae930c5fc | ||
|
|
ebe0f93744 | ||
|
|
8cc0aaf8d2 | ||
|
|
86be3a6865 | ||
|
|
5e5ce73fd0 | ||
|
|
01971807cb | ||
|
|
da8313fa1a | ||
|
|
09fd17e38b | ||
|
|
34f8949e85 | ||
|
|
33d3d1a43f | ||
|
|
1814c35701 | ||
|
|
6df5fe5b2a | ||
|
|
c292c01da3 | ||
|
|
133fbbe038 | ||
|
|
7f0d025312 | ||
|
|
9dccf99c50 | ||
|
|
1d498d13c3 | ||
|
|
e88b0cda18 | ||
|
|
8d2b8b690f | ||
|
|
38b8f26a50 | ||
|
|
64ced7dbad | ||
|
|
b9dadb6a08 | ||
|
|
068ba9afa5 | ||
|
|
c0532fda4e | ||
|
|
ff50baec99 | ||
|
|
a9bb806387 | ||
|
|
8fe0525295 | ||
|
|
e6703ed18a | ||
|
|
ee75272917 | ||
|
|
620ecbafcb | ||
|
|
cf394403a6 | ||
|
|
8f0d7fa3c0 | ||
|
|
cc7266c801 | ||
|
|
e0ac732769 | ||
|
|
c79db24016 | ||
|
|
485918ebe3 | ||
|
|
d9f399b97b | ||
|
|
b8a5c60ff9 | ||
|
|
2d4b93dde7 | ||
|
|
bd893f271a | ||
|
|
2c8e617b2a | ||
|
|
8207e560e9 | ||
|
|
22b6f4e71d | ||
|
|
5c5921fcd2 | ||
|
|
a2781f57e9 | ||
|
|
fd391ef705 | ||
|
|
36df79e561 | ||
|
|
636dc14616 | ||
|
|
fb115fbb7e | ||
|
|
ba009c0a20 | ||
|
|
50726e4cf3 | ||
|
|
f98a123e40 | ||
|
|
dd2ca54874 | ||
|
|
da90ac74b4 | ||
|
|
e6a2da548f | ||
|
|
e5f0c4168f | ||
|
|
05e8b00bf0 | ||
|
|
0ffaa6c741 | ||
|
|
ddadc830ac | ||
|
|
0fa36395e7 | ||
|
|
606cd5fa31 | ||
|
|
e5f3c20f64 | ||
|
|
e530150e43 | ||
|
|
0f9f06048a | ||
|
|
24f7267d55 | ||
|
|
11f26a1090 | ||
|
|
72cef5ed9e | ||
|
|
b977c4cbad | ||
|
|
5d4deb258a | ||
|
|
f46c82d171 | ||
|
|
42c69f9f6c | ||
|
|
9308f93de7 | ||
|
|
ecd5751a67 | ||
|
|
ec262f0238 | ||
|
|
21cd672f64 | ||
|
|
9dfcddf40b | ||
|
|
81e631e640 | ||
|
|
3412f1c0ed | ||
|
|
48556e1a2c | ||
|
|
06a98fe30c | ||
|
|
4cf110b28e | ||
|
|
df34d43a23 | ||
|
|
e9a6269d9f | ||
|
|
005f6cb498 | ||
|
|
32d3f8f75e | ||
|
|
cb4b588598 | ||
|
|
20a0f7a454 | ||
|
|
e720af38a6 | ||
|
|
1c2d284578 | ||
|
|
3e217adc14 | ||
|
|
d94ef81b43 | ||
|
|
ac6c8b275d | ||
|
|
3dda06cbe3 | ||
|
|
0ff8a95da3 | ||
|
|
be7a96a825 | ||
|
|
7265041e55 | ||
|
|
0b64eab148 | ||
|
|
fb285b17e4 | ||
|
|
699290ccb1 | ||
|
|
406104babd | ||
|
|
297c56c72d | ||
|
|
8ec940f624 | ||
|
|
cbbc860366 | ||
|
|
acc7281414 | ||
|
|
bffbe6b551 | ||
|
|
b6f83d81bb | ||
|
|
98c1599d1a | ||
|
|
450e0cddbd | ||
|
|
44e7014d83 | ||
|
|
9125cee6fd | ||
|
|
7a1b5e97c1 | ||
|
|
6114cc9018 | ||
|
|
5d1950647e | ||
|
|
1dc6429b87 | ||
|
|
e20c8a1d0b | ||
|
|
3cb056c523 | ||
|
|
55204478c6 | ||
|
|
0f2289c539 | ||
|
|
20dcf429d4 | ||
|
|
9a14f1b6fc | ||
|
|
1378581940 | ||
|
|
d994268a6b | ||
|
|
006762f900 | ||
|
|
ff905d4a22 | ||
|
|
3369dbd1eb | ||
|
|
a5d207821d | ||
|
|
e751a11a92 | ||
|
|
faaf258a96 | ||
|
|
23e47e6097 | ||
|
|
386152749a | ||
|
|
82ffc14d65 | ||
|
|
0eb840ab4b | ||
|
|
e653f8f08b | ||
|
|
5b1b0688bc | ||
|
|
18b5cead7a | ||
|
|
2200f60b94 | ||
|
|
e997aa2fc6 | ||
|
|
a44673fa6b | ||
|
|
4de801f8d0 | ||
|
|
7caf6cd6e6 | ||
|
|
1d64618b4a | ||
|
|
6b824d9018 | ||
|
|
b22f7d06e7 | ||
|
|
54bb7209a4 | ||
|
|
1c2cb01882 | ||
|
|
51e2b721ff | ||
|
|
862f8f7f98 | ||
|
|
342c3dabb7 | ||
|
|
f7482d6151 | ||
|
|
b8cc58ac29 | ||
|
|
b9067a8110 | ||
|
|
7a28395d75 | ||
|
|
d3d2b28adc | ||
|
|
e0b092dbc8 | ||
|
|
b1570aba63 | ||
|
|
4ff599c011 | ||
|
|
19d5396897 | ||
|
|
e975c648c2 | ||
|
|
60460b23dc | ||
|
|
62c1e4303b | ||
|
|
97397a1c4d | ||
|
|
6e53df0303 | ||
|
|
f848a96343 | ||
|
|
fb4d3c3f1f | ||
|
|
b3854004a1 | ||
|
|
308e1ffdbc | ||
|
|
2bd69f9403 | ||
|
|
61b1735292 | ||
|
|
11663830e3 | ||
|
|
2285277060 | ||
|
|
cd6468bb92 | ||
|
|
28304f13c3 | ||
|
|
7740191194 | ||
|
|
7dda898830 | ||
|
|
681034db1e | ||
|
|
554a784639 | ||
|
|
026e4cf90b | ||
|
|
4f8a2357a5 | ||
|
|
ba0289b820 | ||
|
|
ed8110ae45 | ||
|
|
41da9c91b2 | ||
|
|
44deacb3f4 | ||
|
|
fe2a39c12d | ||
|
|
aa542b38d9 | ||
|
|
c1ee1ff9e9 | ||
|
|
a8236ff3b4 | ||
|
|
c75baacd9b | ||
|
|
a8c27f5752 | ||
|
|
1ff02151b8 | ||
|
|
c7c838b4e5 | ||
|
|
ad93047a25 | ||
|
|
b9f4c06c68 | ||
|
|
945716d010 | ||
|
|
a164017432 | ||
|
|
fdf1f43281 | ||
|
|
8aafd388fa | ||
|
|
8841d063be | ||
|
|
70a26a3042 | ||
|
|
bfa9346de2 | ||
|
|
c9c1d0bb72 | ||
|
|
58e9fd17cc | ||
|
|
4a08b69b4c | ||
|
|
2e2d7b7290 | ||
|
|
f79892baef | ||
|
|
2b307b8040 | ||
|
|
06d4adf198 | ||
|
|
00d6c3a7c4 | ||
|
|
1251edae04 | ||
|
|
2572dde691 | ||
|
|
2e0cd3d161 | ||
|
|
8a3c7b4191 | ||
|
|
7f7d2fe7fc | ||
|
|
064965d350 | ||
|
|
2c01e7672b | ||
|
|
a40d9c2027 | ||
|
|
556adb429c | ||
|
|
39be7a5a0a | ||
|
|
0a5270a2cc | ||
|
|
edc495ac01 | ||
|
|
156dbfda64 | ||
|
|
18d9b96cac | ||
|
|
2b0901eb62 | ||
|
|
1173e3af38 | ||
|
|
23234b96ca | ||
|
|
5f651755d8 | ||
|
|
de3416cf3b | ||
|
|
76f35a7126 | ||
|
|
5e91f98ae0 | ||
|
|
60410f29a1 | ||
|
|
7e43c10e2d | ||
|
|
92a825fd15 | ||
|
|
2dd9ef95dd | ||
|
|
39fa8827da | ||
|
|
37c44a7942 | ||
|
|
62dbed8075 | ||
|
|
88b91a2c55 | ||
|
|
ff04becfab | ||
|
|
2352f34c4e | ||
|
|
444e6bee42 | ||
|
|
7c1fb65947 | ||
|
|
c5eb28af75 | ||
|
|
9a1a75de48 | ||
|
|
d0e514704e | ||
|
|
c34a50f7c9 | ||
|
|
31798fb285 | ||
|
|
08c4547a2e | ||
|
|
5aa88f7d0d | ||
|
|
a9c86297aa | ||
|
|
594e4140bf | ||
|
|
65dc67a7a1 | ||
|
|
8163c54a44 | ||
|
|
57bd5a5902 | ||
|
|
1611e04d17 | ||
|
|
888ec11455 | ||
|
|
3d5c24cc4c | ||
|
|
4ddfccee2d | ||
|
|
85153cf8a0 | ||
|
|
a92457ea05 | ||
|
|
62ef89a163 | ||
|
|
1abf56dbc2 | ||
|
|
9e0f8e9aad | ||
|
|
05c50e32cf | ||
|
|
076e01adbb | ||
|
|
f279eb11e8 | ||
|
|
d4665ee8e5 | ||
|
|
8681cfa063 | ||
|
|
9f527f5ea2 | ||
|
|
fae9bbe84a | ||
|
|
ff8e3c1114 | ||
|
|
20654f9c40 | ||
|
|
da912980fd | ||
|
|
63676b9ae3 | ||
|
|
4f5cc1e884 | ||
|
|
ad66650369 | ||
|
|
09230633a5 | ||
|
|
149497b823 | ||
|
|
ca10a130a7 | ||
|
|
f786e01997 | ||
|
|
f2106407be | ||
|
|
66b3a38f7d | ||
|
|
2f260029d4 | ||
|
|
7cd5585a4e | ||
|
|
9dc27a10bb | ||
|
|
bc48094dde | ||
|
|
ad1cbbc77f | ||
|
|
e0f14d402e | ||
|
|
d64a42125f | ||
|
|
5030888cb5 | ||
|
|
2bd3ccd36c | ||
|
|
0b56052a44 | ||
|
|
0e51461ab6 | ||
|
|
5c79bc7609 | ||
|
|
21296a10df | ||
|
|
f772977784 | ||
|
|
d30dca2d99 | ||
|
|
7a71d9c114 | ||
|
|
6d9885aa5d | ||
|
|
88f8796cdc | ||
|
|
d801b2698b | ||
|
|
1676018ce7 | ||
|
|
a8ac4cc56a | ||
|
|
2417ac1960 | ||
|
|
74f02a0e58 | ||
|
|
2b73cbfbce | ||
|
|
724fc037ed | ||
|
|
29db1b6270 | ||
|
|
83ad7506d7 | ||
|
|
6ce8fdb578 | ||
|
|
b62946845f | ||
|
|
e3f4fdb478 | ||
|
|
dec678e9b4 | ||
|
|
f32496286e | ||
|
|
a8f7ea9aed | ||
|
|
3679362f0f | ||
|
|
45783b7488 | ||
|
|
05cf6e883f | ||
|
|
884ced7ecb | ||
|
|
40f2114541 | ||
|
|
f5c340a120 | ||
|
|
1473e874a8 | ||
|
|
b1e3a2ad33 | ||
|
|
7f9b6c01b5 | ||
|
|
88c2edefd4 | ||
|
|
ed825db4e9 | ||
|
|
c68ed1fdc5 | ||
|
|
5c8a7f363c | ||
|
|
16063c636d | ||
|
|
7efe13747a | ||
|
|
724a5da67b | ||
|
|
983ffe6017 | ||
|
|
66abd53e51 | ||
|
|
f9f1f85b07 | ||
|
|
839fc7b40c | ||
|
|
e298856c85 | ||
|
|
520c10d807 | ||
|
|
1505c5e8a3 | ||
|
|
ddbcd595ef | ||
|
|
6ca0d48327 | ||
|
|
ec951ee5c0 | ||
|
|
de93d4c3b7 | ||
|
|
1b99b3b4d2 | ||
|
|
1837865b33 | ||
|
|
bedf087455 | ||
|
|
f93e734c57 | ||
|
|
2e66655652 | ||
|
|
bbb79afdd8 | ||
|
|
cdf46bd8eb | ||
|
|
34856b9900 | ||
|
|
f5b200d36b | ||
|
|
9c3677ff67 | ||
|
|
498337317c | ||
|
|
655cdf9533 | ||
|
|
1b9d8c91c3 | ||
|
|
59524679c1 | ||
|
|
daa06f98fb | ||
|
|
2061c810db | ||
|
|
80a84b854c | ||
|
|
f88ee692c9 | ||
|
|
b583af4dd3 | ||
|
|
81ceb1ceff | ||
|
|
da0fe7c81f | ||
|
|
72c7beb9c7 | ||
|
|
490d5ed674 | ||
|
|
60c772ec38 | ||
|
|
fb0d5aeaa9 | ||
|
|
ec4b404580 | ||
|
|
6b91d83fe2 | ||
|
|
b82407816e | ||
|
|
f54ba6950c | ||
|
|
a5e38f5964 | ||
|
|
49f085916e | ||
|
|
6fedf81785 | ||
|
|
6af7f61247 | ||
|
|
4a90267ebc | ||
|
|
c948b669c1 | ||
|
|
c56b8c04ea | ||
|
|
8b235b6cf5 | ||
|
|
59b1b3df79 | ||
|
|
21802d68f8 | ||
|
|
52cccc7f2f | ||
|
|
3009bb4ad9 | ||
|
|
d8c0853d55 | ||
|
|
c5aae4b234 | ||
|
|
664f8693a0 | ||
|
|
88d819591e | ||
|
|
593d3309d9 | ||
|
|
32d1ae597e | ||
|
|
30d15daddf | ||
|
|
d8412d022b | ||
|
|
d53bfd8087 | ||
|
|
2d2c29a775 | ||
|
|
69560addf8 | ||
|
|
b6bb22f956 | ||
|
|
6823f0e72d | ||
|
|
b877c92975 | ||
|
|
13f9e0fab8 | ||
|
|
7d3b364728 | ||
|
|
391f11fc23 | ||
|
|
5d4cb9b3fe | ||
|
|
0736de6451 | ||
|
|
142b60e1b3 | ||
|
|
a98e25eda8 | ||
|
|
10762a3c8b | ||
|
|
74b07bd787 | ||
|
|
14e704b858 | ||
|
|
2f7b809401 | ||
|
|
50eec7885a | ||
|
|
54eaa44681 | ||
|
|
7223cab0b2 | ||
|
|
10267869ae | ||
|
|
1d7765d9ee | ||
|
|
7d393f8c98 | ||
|
|
8a3fca65f2 | ||
|
|
83c9d8844d | ||
|
|
3a0600be0f | ||
|
|
0e01e5c84b | ||
|
|
69607d02bc | ||
|
|
33ae73e7ee | ||
|
|
db5c1d64d1 | ||
|
|
0f2d7263a8 | ||
|
|
c29e913511 | ||
|
|
19a52742a3 | ||
|
|
5d07e9b9d5 | ||
|
|
44410a0fdd | ||
|
|
b50b94612a | ||
|
|
89ba205cac | ||
|
|
dbc223e9e0 | ||
|
|
2a6930a946 | ||
|
|
a5417320eb | ||
|
|
a38fcdf04d | ||
|
|
4b2e03d7cc | ||
|
|
603a717e40 | ||
|
|
e6b5013224 | ||
|
|
3ee9469c1a | ||
|
|
bbcc91db01 | ||
|
|
366af8c544 | ||
|
|
505bbd7cc9 | ||
|
|
e4653f7a2d | ||
|
|
f7191056b7 | ||
|
|
db3a7165f7 | ||
|
|
5c228a7fae | ||
|
|
e40445b07a | ||
|
|
3df1c2dcfa | ||
|
|
6fa3d9d21f | ||
|
|
358fb32fe7 | ||
|
|
632a3310ea | ||
|
|
9d6400cf2f | ||
|
|
c44bdb286a | ||
|
|
cab6a5300d | ||
|
|
254360f1ab | ||
|
|
156f8ad044 | ||
|
|
1262b6022a | ||
|
|
4970a58c5a | ||
|
|
1b4d336d80 | ||
|
|
c961185635 | ||
|
|
361569a69e | ||
|
|
29ddc168a3 | ||
|
|
85da6a6c33 | ||
|
|
ba44391acf | ||
|
|
5f762a6ce3 | ||
|
|
dcab396e69 | ||
|
|
03652f8210 | ||
|
|
763709fdf8 | ||
|
|
26e4efc990 | ||
|
|
239f93b084 | ||
|
|
d9b3eb0e8b | ||
|
|
e6b52b045a | ||
|
|
8695089fba | ||
|
|
522c7043b1 | ||
|
|
210f41e020 | ||
|
|
1ed0501f8e | ||
|
|
234d62bbef | ||
|
|
5f669b90ef | ||
|
|
db8cfa8142 | ||
|
|
c949e1b4f2 | ||
|
|
c2afaefb5f | ||
|
|
3e5546ea33 | ||
|
|
c88283dbe5 | ||
|
|
1564d7e411 | ||
|
|
452d07273c | ||
|
|
86f652a72f | ||
|
|
651422e0c8 | ||
|
|
fe66437589 | ||
|
|
04cb2f700d | ||
|
|
24156947ac | ||
|
|
47e03bd54b | ||
|
|
a3cb59af43 | ||
|
|
d149f7e936 | ||
|
|
611f1656a3 | ||
|
|
69fd11a233 | ||
|
|
9f6abb675c | ||
|
|
c3afcc7491 | ||
|
|
ef0799c278 | ||
|
|
d216b10100 | ||
|
|
fa233ba025 | ||
|
|
36454e831b | ||
|
|
9a2e57fb0e | ||
|
|
dccfe539ef | ||
|
|
ae227c7e4d | ||
|
|
13c6548b46 | ||
|
|
2dfad3500f | ||
|
|
c5a9f8a0ab | ||
|
|
941b91261b | ||
|
|
0458af721f | ||
|
|
129079cad4 | ||
|
|
81abfa630a | ||
|
|
ce2ccce98e | ||
|
|
7da14f1a8d | ||
|
|
afc43780a7 | ||
|
|
b06e1b3dc2 | ||
|
|
23ab100d20 | ||
|
|
1400ebd2f1 | ||
|
|
292ad2c202 | ||
|
|
981f0501a3 | ||
|
|
f3cd632b64 | ||
|
|
5c862ca12a | ||
|
|
0bf3638c64 | ||
|
|
137234fae0 | ||
|
|
bfba61cee6 | ||
|
|
0c2ca1eafb | ||
|
|
1a8824613b | ||
|
|
e8048e6dc3 | ||
|
|
3eefd33334 | ||
|
|
950427a135 | ||
|
|
3abaf51a66 | ||
|
|
620e37da8b | ||
|
|
c903beee69 | ||
|
|
a645ee5b79 | ||
|
|
01f94b7591 | ||
|
|
fd1ab4bad8 | ||
|
|
237c75448c | ||
|
|
e716ae44f0 | ||
|
|
a0ef9a9f82 | ||
|
|
595da68751 | ||
|
|
57e96d3be8 | ||
|
|
352be1f4c7 | ||
|
|
98f6bd14b3 | ||
|
|
813af7b4c4 | ||
|
|
92adee2ada | ||
|
|
0c8b5b962c | ||
|
|
2109ad1021 | ||
|
|
1a2097b10d | ||
|
|
ffc16deac2 | ||
|
|
0334c5725e | ||
|
|
e17c7a5b82 | ||
|
|
7f81c6cdf4 | ||
|
|
a2833dadca | ||
|
|
27fa1cc815 | ||
|
|
ab55238d29 | ||
|
|
81cf73e452 | ||
|
|
0dc5fc687f | ||
|
|
89fdc6b443 | ||
|
|
a823d414d0 | ||
|
|
048b0af614 | ||
|
|
461ae6a89e | ||
|
|
dccd8d03af | ||
|
|
63f3129eb7 | ||
|
|
56cac3c74a | ||
|
|
190f6a2a8f | ||
|
|
e5fb26ca74 | ||
|
|
1688f2a640 | ||
|
|
061e52c35c | ||
|
|
bbe6694122 | ||
|
|
a84e968699 | ||
|
|
fb25f1a33d | ||
|
|
ebc3804ebd | ||
|
|
25900f31cf | ||
|
|
5c33b2f63d | ||
|
|
fa219e40b3 | ||
|
|
9745bf041c | ||
|
|
a6fe68301c | ||
|
|
40463b6133 | ||
|
|
0cad2308cc | ||
|
|
b52986e55a | ||
|
|
7acf06ad23 | ||
|
|
55105564bf | ||
|
|
9adb0fae82 | ||
|
|
a6b93ceabc | ||
|
|
05a592d80b | ||
|
|
41cc66b11b | ||
|
|
684b19e87c | ||
|
|
57a5af8efd | ||
|
|
ecf10c72ab | ||
|
|
9f1c17d4e9 | ||
|
|
6db5751bbf | ||
|
|
ce773d0fb4 | ||
|
|
fbc9ba411e | ||
|
|
7f6a057e22 | ||
|
|
1d88b333a9 | ||
|
|
13e756e74f | ||
|
|
f3fdc98319 | ||
|
|
3315fa5458 | ||
|
|
71340b8999 | ||
|
|
a2f2e37585 | ||
|
|
55f93fd987 | ||
|
|
f6ad9cfcd3 | ||
|
|
ab33c30d7c | ||
|
|
34836d97ae | ||
|
|
384d162b74 | ||
|
|
76153b64c7 | ||
|
|
72ace6e913 | ||
|
|
376a43b8ae | ||
|
|
c4cdf1c37e | ||
|
|
ab19882b8e | ||
|
|
673fbeed09 | ||
|
|
c90ebf2468 | ||
|
|
6c4df18da1 | ||
|
|
5ef8e4e8ba | ||
|
|
504090ac99 | ||
|
|
8b7a5da031 | ||
|
|
9a064117bd | ||
|
|
50c80e7747 | ||
|
|
4e72bc6be3 | ||
|
|
3cee4116d1 | ||
|
|
1f69061956 | ||
|
|
b6029220ff | ||
|
|
5279f8fc0d | ||
|
|
8528c9057a | ||
|
|
a786fd85a7 | ||
|
|
b0334e042b | ||
|
|
7163ba7197 | ||
|
|
14e63ca5b6 | ||
|
|
a62653e5bc | ||
|
|
6717ced46a | ||
|
|
2f0d1cee19 | ||
|
|
6c2f2e0df3 | ||
|
|
416d67ca06 | ||
|
|
f4d7631f19 | ||
|
|
23d3782f38 | ||
|
|
078f2137b3 | ||
|
|
1ebdf7cf7a | ||
|
|
b3dac8ea68 | ||
|
|
296da3a440 | ||
|
|
da047d5686 | ||
|
|
f28d4d5d3a | ||
|
|
540e55d499 | ||
|
|
4edaa73dde | ||
|
|
c2c594411d | ||
|
|
50016b122c | ||
|
|
47ed0ff825 | ||
|
|
f28be76ab6 | ||
|
|
64f1780ede | ||
|
|
f2fead7ebd | ||
|
|
4267084d5a | ||
|
|
2eb90470bb | ||
|
|
69563cc2f9 | ||
|
|
dc1f68903d | ||
|
|
9f2c9ac048 | ||
|
|
73f76170ef | ||
|
|
d276e27e81 | ||
|
|
578ea261a4 | ||
|
|
d1e8333551 | ||
|
|
9f9c65bea6 | ||
|
|
fdad94afbf | ||
|
|
fca13aab7a | ||
|
|
02fc884fad | ||
|
|
9006eb8228 | ||
|
|
c0c6880b1a | ||
|
|
b2f8f16949 | ||
|
|
bcf71f588d | ||
|
|
fee175fc72 | ||
|
|
109b74da42 | ||
|
|
7a0eb1566a | ||
|
|
8e449acd59 | ||
|
|
da631029cb | ||
|
|
5002be498f | ||
|
|
a123db83ad | ||
|
|
8b1e4b721f | ||
|
|
5a8d18ec27 | ||
|
|
52b445e6bc | ||
|
|
9ae9b0a043 | ||
|
|
11d94d3866 | ||
|
|
3a41581c5c | ||
|
|
0bdb55e658 | ||
|
|
83d433bf7e | ||
|
|
9582a1761d | ||
|
|
9b0fe97cd9 | ||
|
|
fdd902d534 | ||
|
|
c8a5b8d53d | ||
|
|
95523dc66f | ||
|
|
c46e880b50 | ||
|
|
5e08d328d5 | ||
|
|
4f57beda7a | ||
|
|
094a728b8a | ||
|
|
a5be6b758c | ||
|
|
17a9488a4a | ||
|
|
1fa85fb89c | ||
|
|
0fd99075f0 | ||
|
|
9eda90067e | ||
|
|
44be40451b | ||
|
|
888e7b68e5 | ||
|
|
ac42a6477b | ||
|
|
24118a0f3c | ||
|
|
6871c19f39 | ||
|
|
736b05c670 | ||
|
|
6a448de618 | ||
|
|
e3ad8b6e08 | ||
|
|
d65bc9395e | ||
|
|
88ef627b6b | ||
|
|
bdb339fab8 | ||
|
|
b246cd97f7 | ||
|
|
9546542c15 | ||
|
|
ba7f9d2ee8 | ||
|
|
7f0255baf5 | ||
|
|
4dfe2394c5 | ||
|
|
c29d10b67b | ||
|
|
f340c6badc | ||
|
|
ea9f637747 | ||
|
|
c060f5fe50 | ||
|
|
14bb4934a6 | ||
|
|
eaf17ecb1b | ||
|
|
4df3585190 | ||
|
|
7d751abc0f | ||
|
|
df8ada7003 | ||
|
|
300a032562 | ||
|
|
b34bbf6482 | ||
|
|
445a40d5e7 | ||
|
|
539ee7ef72 | ||
|
|
c2941af88a | ||
|
|
9c8fb7dea6 | ||
|
|
262fc7f257 | ||
|
|
ecc876b6c1 | ||
|
|
91e5fb41b5 | ||
|
|
0602efd3b7 | ||
|
|
05d39e05c7 | ||
|
|
e7d56bba73 | ||
|
|
e5b539faee | ||
|
|
9c54bfe997 | ||
|
|
eb06b8a923 | ||
|
|
a21a62301d | ||
|
|
169a8d501e | ||
|
|
3be193223c | ||
|
|
f00252c8aa | ||
|
|
d09a6f5da1 | ||
|
|
ee25cfbcfd | ||
|
|
dc8d8a0df5 | ||
|
|
05f1e1ed39 | ||
|
|
b43e6b7eee | ||
|
|
865d1092a0 | ||
|
|
d91168825c | ||
|
|
0c5a769abb | ||
|
|
9c3347f0ec | ||
|
|
235cd88db9 | ||
|
|
c3ed223dfd | ||
|
|
1ab270ba46 | ||
|
|
a45797baff | ||
|
|
7e29ff5ec9 | ||
|
|
d02007c993 | ||
|
|
d549b4bd08 | ||
|
|
9069b03504 | ||
|
|
af1a1eb836 | ||
|
|
ca4498e001 | ||
|
|
2d275de3cc | ||
|
|
18526ee362 | ||
|
|
1c4e7e5198 | ||
|
|
e1e9fcb326 | ||
|
|
435863a07f | ||
|
|
655c0a3ec6 | ||
|
|
0753e155a5 | ||
|
|
d7d1fbbdcb | ||
|
|
f7448c832e | ||
|
|
761b60c857 | ||
|
|
b6bfad7f8b | ||
|
|
4240bd29bb | ||
|
|
c242841957 | ||
|
|
2d59717384 | ||
|
|
5420265236 | ||
|
|
9929d1c704 | ||
|
|
62ce777cf8 | ||
|
|
6bfeb6d945 | ||
|
|
e1f14a920d | ||
|
|
3836639825 | ||
|
|
d86b1fb06d | ||
|
|
2fb7514daa | ||
|
|
67df7d1a53 | ||
|
|
135667417b | ||
|
|
62420b7cc4 | ||
|
|
7d90cb644d | ||
|
|
18aa07446e | ||
|
|
29d6209ea4 | ||
|
|
864efe32e4 | ||
|
|
867a8ee55b | ||
|
|
10322595c6 | ||
|
|
a1083908b6 | ||
|
|
f98dc7d7ca | ||
|
|
a342dfb9d2 | ||
|
|
c3fed59109 | ||
|
|
f070b9e56f | ||
|
|
f64e11b854 | ||
|
|
816c79290f | ||
|
|
22a2350126 | ||
|
|
971d2f832f | ||
|
|
390b914268 | ||
|
|
aaf3cc8391 | ||
|
|
3f6d5f29b8 | ||
|
|
0a683bb227 | ||
|
|
bd211d55fd | ||
|
|
5cfe1d7f4b | ||
|
|
63ec9f9572 | ||
|
|
94a2c94cc4 | ||
|
|
65efde7651 | ||
|
|
0ba188889a | ||
|
|
d9e6913791 | ||
|
|
bf834e8352 | ||
|
|
b94fb8167d | ||
|
|
95c992ddbd | ||
|
|
f3ad9c96b3 | ||
|
|
d6fe8b8d8a | ||
|
|
f39036032b | ||
|
|
32e1360802 | ||
|
|
2df7a98dbf | ||
|
|
7e8a8cfa4b | ||
|
|
d2dfc186b1 | ||
|
|
b6d7a34677 | ||
|
|
4867ab21bf | ||
|
|
0f8c6b80e3 | ||
|
|
be396e59d9 | ||
|
|
abab1af2f0 | ||
|
|
0c83d57727 | ||
|
|
9a09ebd9ac | ||
|
|
3e550874ac | ||
|
|
6c03220c1c | ||
|
|
bbb5d68c4c | ||
|
|
f19ed64feb | ||
|
|
2d263278e6 | ||
|
|
38ff7d8f80 | ||
|
|
1bdb8f8a73 | ||
|
|
11f53c197a | ||
|
|
aeb12b3b8e | ||
|
|
71198821ae | ||
|
|
ec4ada9464 | ||
|
|
815b169757 | ||
|
|
5c592938f4 | ||
|
|
e0cc7acf13 | ||
|
|
3d03ebccf8 | ||
|
|
4616eb7254 | ||
|
|
c820928592 | ||
|
|
a533d15bc6 | ||
|
|
7f312f1f6e | ||
|
|
bb8e09afa1 | ||
|
|
35a5cf2887 | ||
|
|
ae0f0d1d96 | ||
|
|
009d59b3ee | ||
|
|
9eaaedd08c | ||
|
|
8cc9a594f7 | ||
|
|
e67d884d62 | ||
|
|
6aba7dbbe0 | ||
|
|
9807accaf0 | ||
|
|
d3c9995e25 | ||
|
|
0c6d603128 | ||
|
|
5e7cd8f3a8 | ||
|
|
82850c344a | ||
|
|
660b07e8d7 | ||
|
|
15b3f002a3 | ||
|
|
336f607536 | ||
|
|
d0db32fa6a | ||
|
|
a9fe199510 | ||
|
|
526ef640f0 | ||
|
|
5b0d6551fa | ||
|
|
f43a6b8401 | ||
|
|
8f67bd4d3e | ||
|
|
fb023aab53 | ||
|
|
749fe5d090 | ||
|
|
7ea5568114 | ||
|
|
15c2c38710 | ||
|
|
523b04d391 | ||
|
|
75a458ddd9 | ||
|
|
22733deb15 | ||
|
|
3a76967365 | ||
|
|
7803a170b5 | ||
|
|
52f0099f74 | ||
|
|
ee8fa17abd | ||
|
|
471dc5bc52 | ||
|
|
127bc18bd7 | ||
|
|
51d558a7a5 | ||
|
|
15abcf6782 | ||
|
|
243fb40092 | ||
|
|
f89552cafd | ||
|
|
7110e078cd | ||
|
|
36ff72385c | ||
|
|
8d7ab0c053 | ||
|
|
d7c4396cd3 | ||
|
|
fb8dc9fe51 | ||
|
|
4071343996 | ||
|
|
150d5c49eb | ||
|
|
f6fe9fba7f | ||
|
|
f6fd7b6323 | ||
|
|
928074fd64 | ||
|
|
7d9fed8144 | ||
|
|
40d4138068 | ||
|
|
07a913f51f | ||
|
|
31c8d99187 | ||
|
|
56bdf95580 | ||
|
|
138b1fd860 | ||
|
|
6a17366fcf | ||
|
|
fbd358a195 | ||
|
|
9877a20778 | ||
|
|
579ab635cd | ||
|
|
f6159dc16e | ||
|
|
fb6f0ce068 | ||
|
|
f3538dcd1f | ||
|
|
1245ce027e | ||
|
|
37c96b6ebd | ||
|
|
76b6b1dc4f | ||
|
|
be8d190138 | ||
|
|
313cbd8bd8 | ||
|
|
ae6076a5aa | ||
|
|
750ed74106 | ||
|
|
f858e015b1 | ||
|
|
02423dad01 | ||
|
|
593db95175 | ||
|
|
fc4153ae54 | ||
|
|
1966ec614d | ||
|
|
0b665cd176 | ||
|
|
1e9ca19313 | ||
|
|
9b2cae32ea | ||
|
|
d6c240af35 | ||
|
|
e64deceac7 | ||
|
|
f8d3b1cca9 | ||
|
|
30363e5ed9 | ||
|
|
53c6799d4d | ||
|
|
089840e707 | ||
|
|
5dceaf9a72 | ||
|
|
b3db803c73 | ||
|
|
2a7e877584 | ||
|
|
21e8d99494 | ||
|
|
b5e5f73071 | ||
|
|
60dbd724c5 | ||
|
|
712425e3fc | ||
|
|
0b7a3c2392 | ||
|
|
cb0c52e787 | ||
|
|
f75052eb8e | ||
|
|
8e8f4f5a6d | ||
|
|
da96d06f25 | ||
|
|
638bd7bcd6 | ||
|
|
6e638d1035 | ||
|
|
94c7aa793a | ||
|
|
0d296c72a9 | ||
|
|
07b4cffc54 | ||
|
|
ebf50baa94 | ||
|
|
47e555ba80 | ||
|
|
befbabe13a | ||
|
|
137673045f | ||
|
|
6c59fe927b | ||
|
|
ed0c22f959 | ||
|
|
4c677640f4 | ||
|
|
0bf5b117da | ||
|
|
d93dbc0533 | ||
|
|
9ee77b44ae | ||
|
|
20cfcfaca3 | ||
|
|
b367abd76a | ||
|
|
6ab0d64fcb | ||
|
|
254ecccba2 | ||
|
|
4d8e022465 | ||
|
|
50e2754e5e | ||
|
|
292fc4ea5d | ||
|
|
230b379979 | ||
|
|
22598710fd | ||
|
|
1d6125e57d | ||
|
|
193c868146 | ||
|
|
1aa3a409cd | ||
|
|
159704dc6f | ||
|
|
6492f10f42 | ||
|
|
a525ba4d01 | ||
|
|
c261aea021 | ||
|
|
8d4fee231b | ||
|
|
307d38453f | ||
|
|
d2a8a79ac6 | ||
|
|
6c8998878d | ||
|
|
41283c8dd9 | ||
|
|
fa29cc2c95 | ||
|
|
7a4fd97526 | ||
|
|
103077dfae | ||
|
|
cd2006305e | ||
|
|
a453c6e2da | ||
|
|
16247ce7c5 | ||
|
|
b9e786e106 | ||
|
|
ccd4d83d43 | ||
|
|
3a9ac1b1b7 | ||
|
|
f2c51ffe39 | ||
|
|
ef17955369 | ||
|
|
62e467c035 | ||
|
|
4950749c5d | ||
|
|
dc2f8b409d | ||
|
|
d2aa92a729 | ||
|
|
86017a5abc | ||
|
|
799998639a | ||
|
|
755d460f0d | ||
|
|
07f9793bc6 | ||
|
|
89a40db79e | ||
|
|
84a8423611 | ||
|
|
8e79c1dc96 | ||
|
|
000afbbe19 | ||
|
|
db1a2f567b | ||
|
|
bdd05dce4d | ||
|
|
4e138b7e36 | ||
|
|
1839acb3d0 | ||
|
|
f26c7e0d09 | ||
|
|
437ef5b56b | ||
|
|
7d64b443f2 | ||
|
|
449745ee24 | ||
|
|
e66efc746f | ||
|
|
436bcc812d | ||
|
|
6086099fe4 | ||
|
|
390f468471 | ||
|
|
ef1d8d9af2 | ||
|
|
ceb7b95096 | ||
|
|
237c985f2c | ||
|
|
66c5be16f8 | ||
|
|
bedbb670e4 | ||
|
|
0591fd528c | ||
|
|
19ff3724ed | ||
|
|
741d71327a | ||
|
|
27117f3246 | ||
|
|
1b5a39dbc9 | ||
|
|
f65f0e3b8f | ||
|
|
e2df9f9493 | ||
|
|
959b497135 | ||
|
|
ef08b873ce | ||
|
|
2f975808bb | ||
|
|
273f10e954 | ||
|
|
f349738257 | ||
|
|
577bf75051 | ||
|
|
79df31ccf6 | ||
|
|
72812878c0 | ||
|
|
cea115ecd9 | ||
|
|
4c3b15d8d6 | ||
|
|
45d21ac032 | ||
|
|
4e713fce19 | ||
|
|
428b7d0fef | ||
|
|
5ddc8dceac | ||
|
|
f901b9bee3 | ||
|
|
2f9604a12f | ||
|
|
893122781d | ||
|
|
f367d73231 | ||
|
|
5fa480904d | ||
|
|
0618de21eb | ||
|
|
d5012d3e16 | ||
|
|
28d53aadf2 | ||
|
|
4fb75ec324 | ||
|
|
091ee764a4 | ||
|
|
439f967cb8 | ||
|
|
80d6861aba | ||
|
|
d2ec533585 | ||
|
|
b52b7c963c | ||
|
|
89910a1a29 | ||
|
|
7ee2b78bbb | ||
|
|
0b2ce6ca1f | ||
|
|
bca8ba6ed9 | ||
|
|
5c9331e848 | ||
|
|
bb6bc9f6a1 | ||
|
|
845817d3bf | ||
|
|
1d98921a45 | ||
|
|
7ede927d5a | ||
|
|
6081448a7e | ||
|
|
70162d5001 | ||
|
|
b975812c18 | ||
|
|
1251c0ca70 | ||
|
|
dd40c41f45 | ||
|
|
428d255b27 | ||
|
|
38d25582b2 | ||
|
|
cdc0a5da33 | ||
|
|
c96cc49d0a | ||
|
|
9bd1550715 | ||
|
|
81fa035a1c | ||
|
|
371fd7c6e5 | ||
|
|
d231f41300 | ||
|
|
97555bb5a1 | ||
|
|
d62ab6e1de | ||
|
|
217a4828d7 | ||
|
|
bc2b8513f7 | ||
|
|
73122c10fd | ||
|
|
b84db7fac7 | ||
|
|
911df3df77 | ||
|
|
acf1f5fb53 | ||
|
|
1044b39c3f | ||
|
|
a729d68600 | ||
|
|
b83886554f | ||
|
|
5a8bcebdf4 | ||
|
|
a479d3e32d | ||
|
|
f399d7383c | ||
|
|
e8e019eb76 | ||
|
|
3c2fd5d760 | ||
|
|
1ca5663298 | ||
|
|
b28d64c3c6 | ||
|
|
76c800542c | ||
|
|
cca073f8aa | ||
|
|
c0f4c184ca | ||
|
|
8c8fb01426 | ||
|
|
052408392d | ||
|
|
cfb215ab0c | ||
|
|
3501e0dffe | ||
|
|
af683af2d2 | ||
|
|
90b1a1fccb | ||
|
|
5954021cc5 | ||
|
|
108088d811 | ||
|
|
ca2ad18ded | ||
|
|
d2e6a2a1a7 | ||
|
|
e275a80f4c | ||
|
|
2745f3d516 | ||
|
|
8f7b87a29e | ||
|
|
2a7b659be4 | ||
|
|
3dd680683c | ||
|
|
49c9e19074 | ||
|
|
ddd2f86e26 | ||
|
|
fac79dc962 | ||
|
|
71a39002fb | ||
|
|
58904c441a | ||
|
|
1e812b793b | ||
|
|
ff8aa6bcbf | ||
|
|
2b213a8add | ||
|
|
021aca8d5e | ||
|
|
21b14ba440 | ||
|
|
5c242d9620 | ||
|
|
31565c9b9e | ||
|
|
99f31e0055 | ||
|
|
4cda83b748 | ||
|
|
13e76d3544 | ||
|
|
85b6d9f027 | ||
|
|
8daf9eacb7 | ||
|
|
48672e4317 | ||
|
|
eb998f0ad4 | ||
|
|
9cbb5b9b71 | ||
|
|
108666664a | ||
|
|
5abf16f9e6 | ||
|
|
78c98a34d1 | ||
|
|
68f1ddbd5a | ||
|
|
71284cdc92 | ||
|
|
03ad525fcc | ||
|
|
af9e940873 | ||
|
|
b547203fde | ||
|
|
9728b533b4 | ||
|
|
9c4d1559fd | ||
|
|
e0ec8ce78a | ||
|
|
9de04f7266 | ||
|
|
eea26f9174 | ||
|
|
0b1e9fdc5b | ||
|
|
277af3dec3 | ||
|
|
3bfd1ca07d | ||
|
|
0e635ba6b4 | ||
|
|
017ac70876 | ||
|
|
5428837605 | ||
|
|
87482df956 | ||
|
|
a13fb6933b | ||
|
|
d775e09688 | ||
|
|
f1ab40bf01 | ||
|
|
c92bc447cc | ||
|
|
6f68bb8d95 | ||
|
|
f39127b752 | ||
|
|
a6f9019edd | ||
|
|
2865abb65a | ||
|
|
f6eb11e567 | ||
|
|
ee750363d2 | ||
|
|
d8f467e30f | ||
|
|
746c51c701 | ||
|
|
f9ca777afe | ||
|
|
4189b80004 | ||
|
|
6013048f68 | ||
|
|
1d626cdea6 | ||
|
|
615c02501a | ||
|
|
7a7891d467 | ||
|
|
38bd122dd0 | ||
|
|
9c0c7badcf | ||
|
|
c3798559d2 | ||
|
|
d89ace5b9c | ||
|
|
1994d2d668 | ||
|
|
1c3ec71361 | ||
|
|
e7333f2e4d | ||
|
|
5149ab703f | ||
|
|
1e0f2158ba | ||
|
|
f17a458a04 | ||
|
|
5f06af81cb | ||
|
|
8407ce22b4 | ||
|
|
c6ddff159f | ||
|
|
b7c890d3f6 | ||
|
|
4b1f1e593d | ||
|
|
7ee702b162 | ||
|
|
680b2a4160 | ||
|
|
076c504640 | ||
|
|
2c76675110 | ||
|
|
ceb1ee3b56 | ||
|
|
de43209643 | ||
|
|
0f7e78c164 | ||
|
|
eb2e5907ea | ||
|
|
c29eba0c70 | ||
|
|
613f412659 | ||
|
|
5d55e47e9b | ||
|
|
8066f71b51 | ||
|
|
5ad588f059 | ||
|
|
4ab696b434 | ||
|
|
d3b8bdfddc | ||
|
|
a607a1f20d | ||
|
|
520209f38c | ||
|
|
ae2d80ba5a | ||
|
|
8652041855 | ||
|
|
04f15623f2 | ||
|
|
a4ed5fb082 | ||
|
|
c884d51702 | ||
|
|
ea47e54399 | ||
|
|
d953049d7d | ||
|
|
2c7ef24a29 | ||
|
|
48c09fd709 | ||
|
|
6ebe4f7752 | ||
|
|
d4de8e26ce | ||
|
|
42c9e9144c | ||
|
|
06a9f2f5fd | ||
|
|
58143ead83 | ||
|
|
b193e3e088 | ||
|
|
e1f02ffca3 | ||
|
|
bd5f2b75c3 | ||
|
|
2bad74046e | ||
|
|
dfbfd6ed82 | ||
|
|
de1a20c007 | ||
|
|
7abc3c7751 | ||
|
|
e8103477a4 | ||
|
|
354f1e1081 | ||
|
|
8a5b341e5e | ||
|
|
2232149be0 | ||
|
|
a766048f29 | ||
|
|
168951b668 | ||
|
|
519730e7d3 | ||
|
|
27f6b3366c | ||
|
|
07782704d4 | ||
|
|
e58355e7e3 | ||
|
|
ce62d23502 | ||
|
|
4c1b8c3d0a | ||
|
|
8578bc1c29 | ||
|
|
77b5276fd3 | ||
|
|
b21638f56c | ||
|
|
08edc767b5 | ||
|
|
4cd4ae9cb5 | ||
|
|
061136d798 | ||
|
|
ecdc52fce9 | ||
|
|
406c057025 | ||
|
|
3eac7f8eae | ||
|
|
79a0e45dbc | ||
|
|
2e2fdae8ef | ||
|
|
d802778104 | ||
|
|
de5f3ba49e | ||
|
|
74aca6f6c5 | ||
|
|
2a23b8d770 | ||
|
|
54d325aeb4 | ||
|
|
6dc78e3f2a | ||
|
|
108b6dc787 | ||
|
|
395de19639 | ||
|
|
cc25201c4f | ||
|
|
66f04e0424 | ||
|
|
5f9797fdd6 | ||
|
|
b4d2b3a442 | ||
|
|
155e039e66 | ||
|
|
b4786b407a | ||
|
|
a59d935bd3 | ||
|
|
6e95e497f7 | ||
|
|
ef12c6b185 | ||
|
|
e6f68496b4 | ||
|
|
be517417ff | ||
|
|
3fc0dd0bde | ||
|
|
89eb59505b | ||
|
|
2601bfa9f0 | ||
|
|
8a15cca567 | ||
|
|
ece35c391c | ||
|
|
6c3ac08c54 | ||
|
|
0e14e7c14e | ||
|
|
2704b8c4bd | ||
|
|
d5338f9244 | ||
|
|
e447f177e0 | ||
|
|
962b769989 | ||
|
|
52a786c780 | ||
|
|
f33415c7e2 | ||
|
|
3e03e53627 | ||
|
|
794e43a534 | ||
|
|
9f721ba437 | ||
|
|
4a5e28067e | ||
|
|
ab7658c1f2 | ||
|
|
6cc0065d15 | ||
|
|
4cd4a06e98 | ||
|
|
f164483e62 | ||
|
|
faf9bb0a82 | ||
|
|
df150647c4 | ||
|
|
6434df13aa | ||
|
|
9f664c751a | ||
|
|
1d27f6c90a | ||
|
|
98c85a1d9e | ||
|
|
1f198ccb43 | ||
|
|
5fb8c393c5 | ||
|
|
7c424e7d38 | ||
|
|
5fa0846dcf | ||
|
|
19c3ec45eb | ||
|
|
bdc11c771d | ||
|
|
35fba2b6fb | ||
|
|
bf4bf4da09 | ||
|
|
d25ca6ff3c | ||
|
|
79ee5c4388 | ||
|
|
2081fd5bda | ||
|
|
8fcfa6e016 | ||
|
|
c14083a45a | ||
|
|
861c351a96 | ||
|
|
50b261e7e2 | ||
|
|
54245e3292 | ||
|
|
a63b40f460 | ||
|
|
8684344e92 | ||
|
|
5f52c72b45 | ||
|
|
839783b2e6 | ||
|
|
717ff8db01 | ||
|
|
249b55ec3f | ||
|
|
5885850726 | ||
|
|
bc484338df | ||
|
|
43c9216ef8 | ||
|
|
ec3a1c621d | ||
|
|
2edffafc2b | ||
|
|
627c8f36ff | ||
|
|
01ba4c157a | ||
|
|
6f790faed9 | ||
|
|
79ca0f7f81 | ||
|
|
8fd75228e4 | ||
|
|
dcbd04aacd | ||
|
|
870bcc76a5 | ||
|
|
c383178f7f | ||
|
|
97df2c8a28 | ||
|
|
6eda265bf2 | ||
|
|
147a600577 | ||
|
|
0dd5be8e7a | ||
|
|
8c42729e5b | ||
|
|
5e0b023a7b | ||
|
|
09f0ec5ebf | ||
|
|
c326c45d70 | ||
|
|
ebf6beaaf1 | ||
|
|
c91f5fc9b8 | ||
|
|
673c739909 | ||
|
|
3bc0de1762 | ||
|
|
9af2ad7cd9 | ||
|
|
12d7e69f07 | ||
|
|
142fdffb00 | ||
|
|
c4687b6816 | ||
|
|
9b161d251e | ||
|
|
a8f058c792 | ||
|
|
5ec8bae983 | ||
|
|
7f06e6567a | ||
|
|
3257cf799a | ||
|
|
70b8ed628d | ||
|
|
aadc0329d2 | ||
|
|
120044af80 | ||
|
|
dfc5263eec | ||
|
|
2646dfae7e | ||
|
|
7d087afbf6 | ||
|
|
59c59a6a70 | ||
|
|
97edfe8ae7 | ||
|
|
daf3ae68c3 | ||
|
|
df5d65dc2d | ||
|
|
4887aa33d9 | ||
|
|
b1af95ad20 | ||
|
|
865a11c628 | ||
|
|
7c2c5319f4 | ||
|
|
f62ed4db8a | ||
|
|
556fc353a8 | ||
|
|
8fd3cb855f | ||
|
|
448a24a975 | ||
|
|
d547198361 | ||
|
|
1143ae1c5a | ||
|
|
69c55a21a4 | ||
|
|
22c631cf88 | ||
|
|
a13868818c | ||
|
|
203160db4f | ||
|
|
61ae37a752 | ||
|
|
e752a7206a | ||
|
|
36b9ed450f | ||
|
|
75215bb143 | ||
|
|
8e625344e6 | ||
|
|
3ecd86dbc2 | ||
|
|
d95e044913 | ||
|
|
0717aae341 | ||
|
|
054d44f737 | ||
|
|
c04c8796f5 | ||
|
|
72e9f2f14e | ||
|
|
9e7c84a430 | ||
|
|
a04fe0a9dd | ||
|
|
9fbe9e7aea | ||
|
|
93bdad4c42 | ||
|
|
21008249ea | ||
|
|
ae5f3e425b | ||
|
|
fccef54cd6 | ||
|
|
d67f4023e1 | ||
|
|
4caafe99d3 | ||
|
|
e33dee192c | ||
|
|
02cd596139 | ||
|
|
d31b89072d | ||
|
|
b11f83c8b3 | ||
|
|
dbdae3c63f | ||
|
|
4a4590f86b | ||
|
|
25e0ae7f0d | ||
|
|
baefa90df9 | ||
|
|
c4f3c42957 | ||
|
|
3a22360a78 | ||
|
|
e4be4944d8 | ||
|
|
b2b4764f36 | ||
|
|
6ac916c785 | ||
|
|
831c8dc64e | ||
|
|
23ec2bbd7e | ||
|
|
945a61c0ed | ||
|
|
883badc1d8 | ||
|
|
135343a2e7 | ||
|
|
ff3b779fa9 | ||
|
|
24c8297df1 | ||
|
|
656b0220c4 | ||
|
|
399a9d43d3 | ||
|
|
ee16a4debc | ||
|
|
454d67d0a2 | ||
|
|
bf44d8124d | ||
|
|
5415a9478d | ||
|
|
62dd661395 | ||
|
|
e6b2144f74 | ||
|
|
9a2454037f | ||
|
|
2fc20adc23 | ||
|
|
ae5528b62f | ||
|
|
92432ad750 | ||
|
|
381db88e33 | ||
|
|
7abe13f23d | ||
|
|
a1f904b84d | ||
|
|
3d147c9e01 | ||
|
|
db23435337 | ||
|
|
7e35721a81 | ||
|
|
8ce4fcdeba | ||
|
|
0080c5b3d4 | ||
|
|
865c3f01ba | ||
|
|
37d0105319 | ||
|
|
c5cd587780 | ||
|
|
e881c47abf | ||
|
|
952020c8a5 | ||
|
|
db1f6fb6d1 | ||
|
|
66fa9d55a1 | ||
|
|
50224326aa | ||
|
|
a59e5c1ed3 | ||
|
|
5df7580a1e | ||
|
|
96223148c0 | ||
|
|
68a8fc97d2 | ||
|
|
ca28c927b2 | ||
|
|
9cf5344fc5 | ||
|
|
e5510620cc | ||
|
|
8e6b440f9a | ||
|
|
f396e1a253 | ||
|
|
39b55fb6e8 | ||
|
|
c91ed5600f | ||
|
|
35e6533986 | ||
|
|
a114fa9d0a | ||
|
|
017c4471ed | ||
|
|
c0e760d73e | ||
|
|
8f5eef94e4 | ||
|
|
c4a7eb7a2e | ||
|
|
9b7c4e279d | ||
|
|
c6fa0b2d95 | ||
|
|
a0cd3dd0e9 | ||
|
|
e578d888e3 | ||
|
|
e0680ccee0 | ||
|
|
6cc8551a6c | ||
|
|
b0225e48b8 | ||
|
|
a5df9e3728 | ||
|
|
ead96654be | ||
|
|
0430ed982d | ||
|
|
52e40b2fdc | ||
|
|
93265ae65c | ||
|
|
699db538b6 | ||
|
|
9ac540f7cf | ||
|
|
8206b5912d | ||
|
|
59d0a58e3a | ||
|
|
945ecdf64d | ||
|
|
e37a360d07 | ||
|
|
9881a061fe | ||
|
|
76729c3377 | ||
|
|
f880007639 | ||
|
|
eee2ce00e2 | ||
|
|
a729282cc1 | ||
|
|
061322d425 | ||
|
|
191e7999c0 | ||
|
|
c0239684ed | ||
|
|
d1095f854a | ||
|
|
902b383de7 | ||
|
|
ab7ab69f20 | ||
|
|
edc53a6bc9 | ||
|
|
1f0766c1d7 | ||
|
|
01f2e926b0 | ||
|
|
0a9e585c1d | ||
|
|
75e8103cdd | ||
|
|
356d06ef58 | ||
|
|
730bc73975 | ||
|
|
b4cb9fbc41 | ||
|
|
2c78428d9d | ||
|
|
88f4c7e104 | ||
|
|
3fb3368272 | ||
|
|
af435fa9bc | ||
|
|
06287aca40 | ||
|
|
6c04cb9a0b | ||
|
|
b5e623e5a1 | ||
|
|
5f7f81bdde | ||
|
|
9ca2f85b08 | ||
|
|
8b42e319e2 | ||
|
|
c8877b49a4 | ||
|
|
54a91f1b7e | ||
|
|
63d7ad788d | ||
|
|
e6619bc6c9 | ||
|
|
ac58bfdd63 | ||
|
|
a705bb3bf2 | ||
|
|
30e49d806a | ||
|
|
5f00329d44 | ||
|
|
ed33a0b00f | ||
|
|
57bbf14e1a | ||
|
|
02006fee2e | ||
|
|
466e2bf927 | ||
|
|
878517dcd2 | ||
|
|
b0ea9513e3 | ||
|
|
817c335f30 | ||
|
|
f8a1e9452e | ||
|
|
1097f35ed8 | ||
|
|
3dac71d0a5 | ||
|
|
993e407df2 | ||
|
|
c94e157b76 | ||
|
|
ca988ffc3a | ||
|
|
93bd6bdf01 | ||
|
|
ec600c8806 | ||
|
|
717c0999a5 | ||
|
|
c4d7ad8d0d | ||
|
|
6711bcf300 | ||
|
|
838b273d9c | ||
|
|
f64570ee84 | ||
|
|
94cb37075a | ||
|
|
6beb8625bf | ||
|
|
998225eb4e | ||
|
|
8de6b447ee | ||
|
|
85683f17c3 | ||
|
|
ffa8e2f25a | ||
|
|
faadebc67a | ||
|
|
748074ba9f | ||
|
|
884accd976 | ||
|
|
7377527f7c | ||
|
|
e44827823a | ||
|
|
1fdef32a4d | ||
|
|
da2dbfb108 | ||
|
|
f8230f9f59 | ||
|
|
71ca05c899 | ||
|
|
509ca60959 | ||
|
|
be91977725 | ||
|
|
22be375f1b | ||
|
|
0142ef1d3f | ||
|
|
6e4c49df61 | ||
|
|
4bf6b1bf0f | ||
|
|
d2ee3bf379 | ||
|
|
f1c182072b | ||
|
|
877ec94fc9 | ||
|
|
a5709d8bfc | ||
|
|
a5f3b0b554 | ||
|
|
3a0fd1c219 | ||
|
|
8e600311d0 | ||
|
|
ea6355a73d | ||
|
|
3cfb3a647e | ||
|
|
22af7fd342 | ||
|
|
c0f70d1a88 | ||
|
|
8b135cf47a | ||
|
|
e5126321ad | ||
|
|
982a1b75ed | ||
|
|
86c87ded89 | ||
|
|
66821b30a7 | ||
|
|
ecd3f124be | ||
|
|
41db5a9bf9 | ||
|
|
dfa966dbfc | ||
|
|
00a2459a86 | ||
|
|
d5b718b380 | ||
|
|
9e08291579 | ||
|
|
075cdfc810 | ||
|
|
b5f10ab7dc | ||
|
|
71f4c11fea | ||
|
|
8a623394da | ||
|
|
349a55fa33 | ||
|
|
83699e2011 | ||
|
|
462de32a5a | ||
|
|
630548644d | ||
|
|
d51b610f97 | ||
|
|
aea2a8a45d | ||
|
|
3b026b2f5f | ||
|
|
ecb23a1651 | ||
|
|
d7f0a718c3 | ||
|
|
f1876321c5 | ||
|
|
8940262618 | ||
|
|
69ab9f7c22 | ||
|
|
caf18dbaab | ||
|
|
7593202492 | ||
|
|
63449a8c26 | ||
|
|
0ef36b4e02 | ||
|
|
8e8d95eba4 | ||
|
|
49cc0f2e80 | ||
|
|
f7179f7a99 | ||
|
|
160c96ad1e | ||
|
|
86ce56b941 | ||
|
|
6952b265b5 | ||
|
|
38cc57e7fb | ||
|
|
7b975aaf93 | ||
|
|
3b634d66ca | ||
|
|
6fe2dcdf8e | ||
|
|
e2e76d3beb | ||
|
|
6cd2af7b72 | ||
|
|
6d289a583a | ||
|
|
667873bdbf | ||
|
|
2d4d11e476 | ||
|
|
a92dff05f8 | ||
|
|
cd07a9d846 | ||
|
|
cd86cc533c | ||
|
|
656f3fb249 | ||
|
|
5870251bdf | ||
|
|
ef0c22eae9 | ||
|
|
94aa3c1d3b | ||
|
|
a172d46c90 | ||
|
|
486ee5f41e | ||
|
|
d98872af4c | ||
|
|
20cc77573b | ||
|
|
7ae725c95d | ||
|
|
b1ba15995f | ||
|
|
fc1ee5bb55 | ||
|
|
6c52e5dddf | ||
|
|
3b4879446f | ||
|
|
b5f0081566 | ||
|
|
dcbfb6314e | ||
|
|
d2833ffded | ||
|
|
8cbade818f | ||
|
|
9ca89250b4 | ||
|
|
a984f5809f | ||
|
|
b6685af3ae | ||
|
|
63d864e1f0 | ||
|
|
6641bf4860 | ||
|
|
05cd788c13 | ||
|
|
c05bfaa9c4 | ||
|
|
f2d4194f37 | ||
|
|
10d12148dd | ||
|
|
ca29cd3b89 | ||
|
|
226eca7a9d | ||
|
|
49db85c09a | ||
|
|
4be925f56c | ||
|
|
7de73dbd33 | ||
|
|
fffdfd2721 | ||
|
|
54cc87132e | ||
|
|
94f63b1324 | ||
|
|
e729e75aaa | ||
|
|
d32fb3bc3c | ||
|
|
bcb8068ebd | ||
|
|
a2583b8114 | ||
|
|
9d477f37e7 | ||
|
|
cce36419ac | ||
|
|
05c6978d80 | ||
|
|
0da2b5db7b | ||
|
|
204d0d022f | ||
|
|
fb39bf38a1 | ||
|
|
fb44159261 | ||
|
|
3413bae7d7 | ||
|
|
d9b986853f | ||
|
|
f262815990 | ||
|
|
05a9c52217 | ||
|
|
fcae886044 | ||
|
|
1fdb4cd6f4 | ||
|
|
15e60dcbe6 | ||
|
|
a03b86e749 | ||
|
|
f678383aad | ||
|
|
1337425504 | ||
|
|
37e11e465f | ||
|
|
07978d2df5 | ||
|
|
65803c7868 | ||
|
|
a2b991adf8 | ||
|
|
f223bf44ce | ||
|
|
60348708a1 | ||
|
|
ede7acfdf6 | ||
|
|
c29d378d4c | ||
|
|
a89b7ac5de | ||
|
|
6f99ebedcc | ||
|
|
77ace64f87 | ||
|
|
a03a9da64a | ||
|
|
9bad2745f7 | ||
|
|
4772c4d6a5 | ||
|
|
425a6c66a8 | ||
|
|
d8300a0211 | ||
|
|
49abbd9519 | ||
|
|
65fa4c06ce | ||
|
|
c3d1490443 | ||
|
|
648422c966 | ||
|
|
eeb6986f16 | ||
|
|
3eef5fe1b5 | ||
|
|
01e8cd7fb0 | ||
|
|
e0ddbed1eb | ||
|
|
95abdc8d94 | ||
|
|
318aa1912b | ||
|
|
6572791e50 | ||
|
|
665c58deab | ||
|
|
c0d8badf55 | ||
|
|
3fa52f2c01 | ||
|
|
73e26c41b5 | ||
|
|
314e317a9a | ||
|
|
8c00a6e98e | ||
|
|
09c7153a04 | ||
|
|
10c29c5ae4 | ||
|
|
2998f37b5a | ||
|
|
f3b435e724 | ||
|
|
09f5e9d51a | ||
|
|
79dda10da3 | ||
|
|
1d21aae32c | ||
|
|
0335cad9ff | ||
|
|
dba335f74a | ||
|
|
3001bc6873 | ||
|
|
5c33917753 | ||
|
|
368249d677 | ||
|
|
f0de841360 | ||
|
|
f205fb7540 | ||
|
|
ba5d0ee605 | ||
|
|
6d4b700181 | ||
|
|
169f29e960 | ||
|
|
752cce3ffd | ||
|
|
25a91d1cec | ||
|
|
b5e7ca98fb | ||
|
|
1f07e57a2c | ||
|
|
5e81bc38e6 | ||
|
|
765e6e8ebc | ||
|
|
863b13b687 | ||
|
|
8af11be0f0 | ||
|
|
198d619358 | ||
|
|
456722c339 | ||
|
|
d81fced051 | ||
|
|
092fcd806d | ||
|
|
64d26f8490 | ||
|
|
2f51cb6287 | ||
|
|
b2c08d8043 | ||
|
|
b83b9e4e9e | ||
|
|
30b22c1efc | ||
|
|
ff446052c7 | ||
|
|
61473f6496 | ||
|
|
a1c8264beb | ||
|
|
81667a9aca | ||
|
|
2b9dae4875 | ||
|
|
8bcf833e2e | ||
|
|
b77ab0f424 | ||
|
|
2fcbd6aefb | ||
|
|
48b0d34938 | ||
|
|
01e643719d | ||
|
|
3c674a7051 | ||
|
|
24d0c2139f | ||
|
|
d73f748ee8 | ||
|
|
362fedfbe6 | ||
|
|
80a9e40d3a | ||
|
|
2664cdd992 | ||
|
|
e95af466ce | ||
|
|
4be6c9662d | ||
|
|
a92fe5ca0a | ||
|
|
61ed07f3b5 | ||
|
|
d38828a2b3 | ||
|
|
28f3ed626b | ||
|
|
c49d7f9d05 | ||
|
|
cfbaad0abd | ||
|
|
71bf43224d | ||
|
|
becb963bd0 | ||
|
|
73d0a6a452 | ||
|
|
d311fe8f38 | ||
|
|
392d4da3ad | ||
|
|
a64674e3ab | ||
|
|
d66dfbbbc2 | ||
|
|
adebedc021 | ||
|
|
2eaaac97f5 | ||
|
|
dcbdf251d7 | ||
|
|
77892b94f2 | ||
|
|
21bf009a3f | ||
|
|
4d626e9632 | ||
|
|
98357b8aa2 | ||
|
|
e50e1a1a8c | ||
|
|
f8daecccb3 | ||
|
|
77e57cff5d | ||
|
|
d0e8d79106 | ||
|
|
cdb12af997 | ||
|
|
69fc3675f0 | ||
|
|
783c34e75b | ||
|
|
c12fbd8eb7 | ||
|
|
053a4f90dc | ||
|
|
645d048df5 | ||
|
|
7c6070ef2f | ||
|
|
f709fc1000 | ||
|
|
095331b06e | ||
|
|
f6f938b5d3 | ||
|
|
8ad04b2660 | ||
|
|
551ee1658c | ||
|
|
0248db80e1 | ||
|
|
4d5c8b7f86 | ||
|
|
2d9dd7d5e9 | ||
|
|
86dc41ba7b | ||
|
|
bc1decb940 | ||
|
|
db95492a4f | ||
|
|
b6c6fc040d | ||
|
|
3de938b7a1 | ||
|
|
b9f49eee1f | ||
|
|
8f210af72c | ||
|
|
7a6321d955 | ||
|
|
a389e30142 | ||
|
|
290c4230ac | ||
|
|
9d4abe5027 | ||
|
|
9ad87dda25 | ||
|
|
c53625946e | ||
|
|
cb565477a6 | ||
|
|
a0df3279f5 | ||
|
|
f74146c6b4 | ||
|
|
297e95ef4b | ||
|
|
fc075bc69e | ||
|
|
92e64bda5f | ||
|
|
1c54689edb | ||
|
|
3faf7d7bd1 | ||
|
|
5549c50d86 | ||
|
|
144762023a | ||
|
|
931f1a074c | ||
|
|
1abce888ae | ||
|
|
c4465a04d8 | ||
|
|
d15b0a99ea | ||
|
|
6cae63fc53 | ||
|
|
fcebd4839b | ||
|
|
d370b67dd7 | ||
|
|
9be3f132ed | ||
|
|
6aa7c650e8 | ||
|
|
20184eeb1f | ||
|
|
39f5fffb2b | ||
|
|
07e754ce4b | ||
|
|
eb29b63aa1 | ||
|
|
f467a77f6e | ||
|
|
d3ea48c87b | ||
|
|
b24aaaccae | ||
|
|
1df68c0e4a | ||
|
|
f6b37f99b3 | ||
|
|
4100de4b4d | ||
|
|
3003a4c7a4 | ||
|
|
2b339247ed | ||
|
|
0f7cac62ef | ||
|
|
7fe463af63 | ||
|
|
8abc2b7fa7 | ||
|
|
d5782788d1 | ||
|
|
7e24a8df05 | ||
|
|
47efeb0143 | ||
|
|
13d0053036 | ||
|
|
a4df975415 | ||
|
|
8fa5239102 | ||
|
|
ceb34ba7f6 | ||
|
|
bdc735b86f | ||
|
|
b3bd6b114f | ||
|
|
04da452a9b | ||
|
|
b30b43b989 | ||
|
|
559adb9a3f | ||
|
|
10a1c383c2 | ||
|
|
234ffbff2e | ||
|
|
143cfde74e | ||
|
|
96561897ae | ||
|
|
a2084e881e | ||
|
|
134e8b8b57 | ||
|
|
587a06fdad | ||
|
|
d3d24a03a4 | ||
|
|
3a6461b6c5 | ||
|
|
e5150aa9c9 | ||
|
|
db7bad7a64 | ||
|
|
2bb36cc40d | ||
|
|
8de8283634 | ||
|
|
cfe411e50d | ||
|
|
9dacc90e66 | ||
|
|
6c73b8e076 | ||
|
|
02311883f7 | ||
|
|
6c7385e5cc | ||
|
|
880cb2f418 | ||
|
|
802fa1f00f | ||
|
|
06a6e4ec57 | ||
|
|
c96a6c465b | ||
|
|
3a7edbde52 | ||
|
|
452c9df178 | ||
|
|
949ceb5a21 | ||
|
|
6329e598ae | ||
|
|
200f952228 | ||
|
|
6f8d2c619f | ||
|
|
20daae0c59 | ||
|
|
68f1631672 | ||
|
|
2ca7c22f99 | ||
|
|
82ea738e4a | ||
|
|
8e9855cf56 | ||
|
|
ad4d0866ae | ||
|
|
074f4b6ff9 | ||
|
|
ed639ac85f | ||
|
|
2e5a60f4fc | ||
|
|
08baab8cbc | ||
|
|
e9208295f1 | ||
|
|
6f8571f77f | ||
|
|
465ef1004b | ||
|
|
f58207d2da | ||
|
|
a3233f04b1 | ||
|
|
7ff2f8e3e8 | ||
|
|
2a7c96909c | ||
|
|
20ce1dcda9 | ||
|
|
76ab8c3584 | ||
|
|
f235fd18b2 | ||
|
|
60cf2d9f09 | ||
|
|
9538feb1ce | ||
|
|
255a212af9 | ||
|
|
3005884032 | ||
|
|
81e666d1ee | ||
|
|
d2fae81a36 | ||
|
|
a85826e82d | ||
|
|
3b3e786a0b | ||
|
|
e72a4536b4 | ||
|
|
6a1d60f9a5 | ||
|
|
2b64f42854 | ||
|
|
56b10a2d6b | ||
|
|
85f4bafca6 | ||
|
|
40bdb90243 | ||
|
|
61e6c0683c | ||
|
|
042da1bcfd | ||
|
|
4ad7ae9b8f | ||
|
|
46da9523ed | ||
|
|
839b241c2c | ||
|
|
724b79f1c0 | ||
|
|
3bcf677768 | ||
|
|
2ba97b674e | ||
|
|
a87d315471 | ||
|
|
ec66cad8f8 | ||
|
|
e1a10e4af4 | ||
|
|
c400fd5062 | ||
|
|
00127ceffa | ||
|
|
2e7ed31f87 | ||
|
|
e330685ec3 | ||
|
|
92c4dee71a | ||
|
|
d6166c7215 | ||
|
|
e9b73d987c | ||
|
|
8928937942 | ||
|
|
8acf6812a6 | ||
|
|
c5ef6f794f | ||
|
|
1ead5f2597 | ||
|
|
a9b1ab302d | ||
|
|
dcd61410ad | ||
|
|
97719f6c47 | ||
|
|
e34496ff05 | ||
|
|
76d358e824 | ||
|
|
365051a440 | ||
|
|
3d3c6d6d45 | ||
|
|
1f2bd8a840 | ||
|
|
05fe1f6fb3 | ||
|
|
c3bf6f9a34 | ||
|
|
25476f993d | ||
|
|
cf1e401df6 | ||
|
|
f7d5195a4d | ||
|
|
d9a5099944 | ||
|
|
2fa5c0ac7b | ||
|
|
f13ab29456 | ||
|
|
10e8c84e51 | ||
|
|
75d7470923 | ||
|
|
550d770fa6 | ||
|
|
af00af0366 | ||
|
|
9ad5ed6d86 | ||
|
|
bdbd955bc6 | ||
|
|
0ebe870658 | ||
|
|
4e8bf411d3 | ||
|
|
2e1eabb186 | ||
|
|
57e8663f7e | ||
|
|
949531a07e | ||
|
|
6e3970b7d1 | ||
|
|
77a3043a73 | ||
|
|
ae66c44728 | ||
|
|
9cc91eea75 | ||
|
|
370940a959 | ||
|
|
edb736f4ce | ||
|
|
ba009b47b2 | ||
|
|
47c82103cd | ||
|
|
0bcf90680c | ||
|
|
a010c8f94b | ||
|
|
74b655df0e | ||
|
|
870b6446b1 | ||
|
|
2e29f91bd4 | ||
|
|
190f596413 | ||
|
|
28180cc337 | ||
|
|
04a3c6e03c | ||
|
|
25487c9325 | ||
|
|
571b0ce53c | ||
|
|
ae55260174 | ||
|
|
de0f533bc3 | ||
|
|
466f90bdd5 | ||
|
|
2ec96d0e47 | ||
|
|
b7e53a185c | ||
|
|
c04b1ca289 | ||
|
|
ee508f707f | ||
|
|
3c36d1feb8 | ||
|
|
9dc78d38bf | ||
|
|
a111a91c83 | ||
|
|
7eff9301b9 | ||
|
|
23a5b53807 | ||
|
|
e159e9d338 | ||
|
|
ef2099c10a | ||
|
|
fb772e29bd | ||
|
|
58666e3377 | ||
|
|
2418ad330e | ||
|
|
bf5b5bef48 | ||
|
|
cd831ec432 | ||
|
|
ef1f5710b3 | ||
|
|
735b0c048a | ||
|
|
d4dc41bb3f | ||
|
|
ab7ea17819 | ||
|
|
f2938f4e2e | ||
|
|
00e11b3df7 | ||
|
|
dcc87973bd | ||
|
|
3b4bb0e62c | ||
|
|
831c59bd12 | ||
|
|
02979cb76f | ||
|
|
b75e131478 | ||
|
|
1810debb58 | ||
|
|
730dab65b8 | ||
|
|
5f7b301542 | ||
|
|
7775ed4184 | ||
|
|
93d4053cf3 | ||
|
|
e7c78f96df | ||
|
|
3a291439b5 | ||
|
|
95fb8340eb | ||
|
|
53254ee1f8 | ||
|
|
d900bdc234 | ||
|
|
af30d64152 | ||
|
|
1c2cde51a4 | ||
|
|
8789191787 | ||
|
|
7265f4e7c2 | ||
|
|
21a25e127f | ||
|
|
28d6246979 | ||
|
|
50d826b11a | ||
|
|
b7a3dae543 | ||
|
|
7daecca8c5 | ||
|
|
f8096bd306 | ||
|
|
274e5d3dbf | ||
|
|
85c06dc62e | ||
|
|
fc32c459bd | ||
|
|
a901ebeb4f | ||
|
|
a0b688e29a | ||
|
|
978d97a90a | ||
|
|
e3a1292d75 | ||
|
|
15dc176e81 | ||
|
|
c8d30df05d | ||
|
|
e4dd08f8b6 | ||
|
|
29960c1589 | ||
|
|
05bb33ed8e | ||
|
|
60e12a94d1 | ||
|
|
4e1a08c23d | ||
|
|
c88b8cccc0 | ||
|
|
50f417593c | ||
|
|
791ebbf576 | ||
|
|
021661b568 | ||
|
|
710d962ffe | ||
|
|
15daadd015 | ||
|
|
c06817b48b | ||
|
|
66d6bf2e2a | ||
|
|
401301438d | ||
|
|
536ff4dd57 | ||
|
|
5e716fb351 | ||
|
|
32be6075c1 | ||
|
|
6bb023e9fe | ||
|
|
9309cef4fb | ||
|
|
6e0cff0830 | ||
|
|
9aaa3232ef | ||
|
|
ed1b456284 | ||
|
|
41f7449008 | ||
|
|
16ceb3e22e | ||
|
|
2cedd97d25 | ||
|
|
342d5cf8dd | ||
|
|
76165b47d6 | ||
|
|
095441dbd1 | ||
|
|
63524215f0 | ||
|
|
5f6c695b80 | ||
|
|
52c5c3afec | ||
|
|
bd9651bddd | ||
|
|
4203988d74 | ||
|
|
2bc299c5b1 | ||
|
|
0803bc3725 | ||
|
|
264dfce3c5 | ||
|
|
c71a27275d | ||
|
|
b43e6e8d72 | ||
|
|
977313dd90 | ||
|
|
656048a0ad | ||
|
|
2a0911ec30 | ||
|
|
1e534b954b | ||
|
|
61c93990d1 | ||
|
|
507863f86a | ||
|
|
4eb3e117e3 | ||
|
|
2fd37afb9e | ||
|
|
94db2dc8c9 | ||
|
|
7f2a6568c3 | ||
|
|
7b80a9b69c | ||
|
|
3afdd894d8 | ||
|
|
f0f6cc92d8 | ||
|
|
2bd4e43eaa | ||
|
|
41133e0cd5 | ||
|
|
bebb0bd5da | ||
|
|
57ed405890 | ||
|
|
bf701b9fed | ||
|
|
1611f61675 | ||
|
|
573b02fbfc | ||
|
|
774bb10c35 | ||
|
|
c837fbceb5 | ||
|
|
b2e1b91265 | ||
|
|
c306339e0a | ||
|
|
703176df7c | ||
|
|
f2cdb6fd19 | ||
|
|
963db29d96 | ||
|
|
20f06b3541 | ||
|
|
b6ac36e65e | ||
|
|
81feb77433 | ||
|
|
ad5d3bad64 | ||
|
|
c5a022bbdf | ||
|
|
89e768a658 | ||
|
|
4744548d27 | ||
|
|
a775a10d95 | ||
|
|
492fe06832 | ||
|
|
762a18425f | ||
|
|
7368416e54 | ||
|
|
65c47d6c38 | ||
|
|
5d9e227823 | ||
|
|
28e7fe3890 | ||
|
|
5f0be7eaad | ||
|
|
d8aed7befe | ||
|
|
f7ff7e20f5 | ||
|
|
873076e973 | ||
|
|
d95b3ffff6 | ||
|
|
949d6ee705 | ||
|
|
101061031c | ||
|
|
3c67ae5aa9 | ||
|
|
b21fab82fc | ||
|
|
5e7f1b921e | ||
|
|
3b6a3da19f | ||
|
|
7f4c78f1b6 | ||
|
|
0d2a6a7bf3 | ||
|
|
b9dda788f6 | ||
|
|
c7ce8d6dbb | ||
|
|
277cb547f5 | ||
|
|
32f4656b87 | ||
|
|
c1809b37b1 | ||
|
|
fdf2efbd82 | ||
|
|
c166140660 | ||
|
|
bd4c3afb22 | ||
|
|
91c3763653 | ||
|
|
be69a8b06f | ||
|
|
8e17b00b51 | ||
|
|
5b92e1da12 | ||
|
|
be12072547 | ||
|
|
f2f4039389 | ||
|
|
4b3be3f0b3 | ||
|
|
0fef311f75 | ||
|
|
2225311d0b | ||
|
|
8aaeb2d6f8 | ||
|
|
51c6176ce1 | ||
|
|
4601ad2b41 | ||
|
|
02b0ec1e9a | ||
|
|
41a61d79d9 | ||
|
|
7cfc108030 | ||
|
|
634da5d769 | ||
|
|
3b01c9b3b2 | ||
|
|
93648e6d50 | ||
|
|
b4f16592d3 | ||
|
|
a6326a989e | ||
|
|
efad0e678e | ||
|
|
c8d2415989 | ||
|
|
6ca27f3663 | ||
|
|
dfe3749af9 | ||
|
|
08ff812ce4 | ||
|
|
8fe4b822ee | ||
|
|
e9e5caccd6 | ||
|
|
61b8b67951 | ||
|
|
21114fdd6f | ||
|
|
5d2290ac2d | ||
|
|
953f2917f7 | ||
|
|
591db3ff72 | ||
|
|
191a875f5a | ||
|
|
06f08edecc | ||
|
|
48efcc7dfb | ||
|
|
9d9baba969 | ||
|
|
9bbbad5546 | ||
|
|
ee41ed94d6 | ||
|
|
340cf2c6ed | ||
|
|
d5c04128b6 | ||
|
|
1349a75350 | ||
|
|
09afc812dd | ||
|
|
8db8d7d926 | ||
|
|
832e01a0ab | ||
|
|
5af58b5ad8 | ||
|
|
bcf9e2e58c | ||
|
|
d50e6236f8 | ||
|
|
427a919077 | ||
|
|
03c3218dc7 | ||
|
|
8576615f2c | ||
|
|
2a3f744c26 | ||
|
|
9f35442a41 | ||
|
|
958b24133d | ||
|
|
e6593d0549 | ||
|
|
b616420722 | ||
|
|
2eafe24607 | ||
|
|
f37b341677 | ||
|
|
64b7ff7c7c | ||
|
|
7af88483fc | ||
|
|
ff5f985f70 | ||
|
|
208f7b3eaa | ||
|
|
e6bae0429f | ||
|
|
b3eab44412 | ||
|
|
1cf54efd89 | ||
|
|
68b0156faa | ||
|
|
f767ec7a59 | ||
|
|
897835b678 | ||
|
|
af13abdaa7 | ||
|
|
17bddbae20 | ||
|
|
f264663367 | ||
|
|
3f9d5448f8 | ||
|
|
b28b7759f5 | ||
|
|
33467911ca | ||
|
|
60609ec326 | ||
|
|
0ba56fdc37 | ||
|
|
0b9ce1e36b | ||
|
|
68770a2b66 | ||
|
|
1d236de956 | ||
|
|
de8e973ce2 | ||
|
|
242f9266d7 | ||
|
|
934a4b5802 | ||
|
|
51adf011b6 | ||
|
|
2a6b9162d4 | ||
|
|
fdb9f50bd8 | ||
|
|
87d89bfa5c | ||
|
|
939ba16e8d | ||
|
|
b552372d76 | ||
|
|
427a270722 | ||
|
|
5025ca9b36 | ||
|
|
d7e2b6f628 | ||
|
|
dc65b7d4a1 | ||
|
|
c24b101ad2 | ||
|
|
07f8b3cb8a | ||
|
|
603af8bd3b | ||
|
|
64466f2916 | ||
|
|
f15ccf5e9e | ||
|
|
212c38eb1b | ||
|
|
eddb33cf83 | ||
|
|
e8b463f82a | ||
|
|
cbbccae7da | ||
|
|
ada6db99d8 | ||
|
|
109e991ffb | ||
|
|
cb234b86ad | ||
|
|
7e29c1ac4e | ||
|
|
af13a8bddb | ||
|
|
a74e315bc3 | ||
|
|
ccea1e05a9 | ||
|
|
0b6f4a3b22 | ||
|
|
05df656616 | ||
|
|
53c052578d | ||
|
|
2bd0789713 | ||
|
|
f7c50606e4 | ||
|
|
4cb2235c91 | ||
|
|
885e493ba2 | ||
|
|
248744f9cd | ||
|
|
b1346eeea3 | ||
|
|
b662f84d41 | ||
|
|
fb261bb4f6 | ||
|
|
61c323189c | ||
|
|
a987250cd3 | ||
|
|
17ac5b04f0 | ||
|
|
2f4addbfb9 | ||
|
|
64318e8045 | ||
|
|
d380d20e84 | ||
|
|
a3edd11528 | ||
|
|
3dd77079f1 | ||
|
|
815213cb5c | ||
|
|
15cf4a1332 | ||
|
|
3db52a63ad | ||
|
|
92d1c0bb10 | ||
|
|
4b84be4bb8 | ||
|
|
a255fe7231 | ||
|
|
f43c8ac65c | ||
|
|
c835c16f1d | ||
|
|
6e77b1cccd | ||
|
|
c8440d2078 | ||
|
|
a4a9b002c6 | ||
|
|
dfd155ab0f | ||
|
|
9129e81fbe | ||
|
|
24159561b6 | ||
|
|
78b4257f25 | ||
|
|
59ebac76e0 | ||
|
|
6d41ed31cf | ||
|
|
b0e587cb8d | ||
|
|
c6e8f6af8f | ||
|
|
67519f5387 | ||
|
|
fa39f921d5 | ||
|
|
776e7ca3e3 | ||
|
|
856de91c51 | ||
|
|
3bc4ab2840 | ||
|
|
2f62092413 | ||
|
|
731237ac8d | ||
|
|
4b97a6a180 | ||
|
|
6797be30f0 | ||
|
|
78a779e48d | ||
|
|
d2a040d77a | ||
|
|
50c5743fa0 | ||
|
|
b0f37b52eb | ||
|
|
6864413456 | ||
|
|
ca9b5c69c0 | ||
|
|
aec75b3a35 | ||
|
|
a6c3ef1a79 | ||
|
|
89b4369f2a | ||
|
|
9b3041ec3a | ||
|
|
86198d85c3 | ||
|
|
99b24ffcec | ||
|
|
ebcc32ddb3 | ||
|
|
c2d624569c | ||
|
|
45ee5d4fee | ||
|
|
2514b6f99e | ||
|
|
12a4f39328 | ||
|
|
18b6d8a620 | ||
|
|
e3df2231cb | ||
|
|
df83d3f8cf | ||
|
|
783cd42638 | ||
|
|
d6f66dec01 | ||
|
|
b7a533f6cb | ||
|
|
7729c83581 | ||
|
|
7eb0304ca2 | ||
|
|
b45d8f1f6e | ||
|
|
17248b5eca | ||
|
|
15f54df0ba | ||
|
|
9a9c28bcc6 | ||
|
|
8587f623f7 | ||
|
|
0f8aede253 | ||
|
|
b6af761da0 | ||
|
|
e210d76a82 | ||
|
|
d68e8d3f95 | ||
|
|
9df7f4eeb7 | ||
|
|
992949e3b1 | ||
|
|
c6afb5004b | ||
|
|
6903eea686 | ||
|
|
3079606054 | ||
|
|
10dc6da903 | ||
|
|
5b7c32ca4d | ||
|
|
54f227a9e1 | ||
|
|
1e2d1d8545 | ||
|
|
dbdccc50c7 | ||
|
|
10d7844fc8 | ||
|
|
aa61285dc3 | ||
|
|
c83fecd057 | ||
|
|
2abc5df9ac | ||
|
|
4dbe029bd3 | ||
|
|
5944ec673a | ||
|
|
de3a5569da | ||
|
|
1af710e998 | ||
|
|
fd4c34b292 | ||
|
|
906e46c8db | ||
|
|
58f770b626 | ||
|
|
8302cbb123 | ||
|
|
b633319705 | ||
|
|
a5fa3d840b | ||
|
|
9341c8049e | ||
|
|
696ef30d10 | ||
|
|
d406df3056 | ||
|
|
b2f6a4b1db | ||
|
|
d17b44596e | ||
|
|
f7c5b56eb0 | ||
|
|
9d030fbbc9 | ||
|
|
1c5c77bd08 | ||
|
|
ba0c9d565a | ||
|
|
2177dbf019 | ||
|
|
e418560a12 | ||
|
|
b453125870 | ||
|
|
c1be0a4ac1 | ||
|
|
7511cd4ed4 | ||
|
|
817f53bf77 | ||
|
|
e15e9b7a47 | ||
|
|
a7683bda8e | ||
|
|
7d9312d8a5 | ||
|
|
b5861e6bf2 | ||
|
|
383b655072 | ||
|
|
297b66838b | ||
|
|
1838865507 | ||
|
|
084f051a4a | ||
|
|
9ed6613a94 | ||
|
|
546c8c3ac1 | ||
|
|
afd683ac06 | ||
|
|
94e028e807 | ||
|
|
d61fcc1249 | ||
|
|
4d5492c998 | ||
|
|
6f33275309 | ||
|
|
bfa728f7e5 | ||
|
|
7bf8f99a10 | ||
|
|
ee85b51e55 | ||
|
|
f6f10c7f9d | ||
|
|
0cc81cfd4f | ||
|
|
157fd8ff92 | ||
|
|
81248b1c7f | ||
|
|
6976243328 | ||
|
|
33abf3f960 | ||
|
|
e41401f31f | ||
|
|
9b91e56fea | ||
|
|
cb177288be | ||
|
|
3a0ec9feb9 | ||
|
|
8db57dd59a | ||
|
|
7f1518382c | ||
|
|
55f203710b | ||
|
|
839b06cbaa | ||
|
|
6756cc3a69 | ||
|
|
c7d6bb84e5 | ||
|
|
40d58a7090 | ||
|
|
f216601aba | ||
|
|
42789b471c | ||
|
|
c1f3ba6b89 | ||
|
|
3c0cf73141 | ||
|
|
fd9613222e | ||
|
|
df6d7ee9c3 | ||
|
|
ccf43f8466 | ||
|
|
17c2f38a8f | ||
|
|
a6cc7e6629 | ||
|
|
c049f9ba6d | ||
|
|
f952c1cfb4 | ||
|
|
e592496c61 | ||
|
|
a0c1408279 | ||
|
|
abbc8e16f0 | ||
|
|
c0fb243760 | ||
|
|
28616777af | ||
|
|
e57e410c4d | ||
|
|
550e0159ed | ||
|
|
3d662bc40e | ||
|
|
f265098eed | ||
|
|
9aa6e7d1ab | ||
|
|
43f273de03 | ||
|
|
9e8d77fc70 | ||
|
|
716eb053d1 | ||
|
|
8f3f5bff2c | ||
|
|
3064c984b5 | ||
|
|
24a5bba852 | ||
|
|
1cee15479d | ||
|
|
30a07b7f36 | ||
|
|
c57d13e61b | ||
|
|
24d6a62a54 | ||
|
|
f2f74a8aab | ||
|
|
c73690b38c | ||
|
|
0dd8d268a6 | ||
|
|
446134020f | ||
|
|
7a97a5fe9d | ||
|
|
4eefc0f87e | ||
|
|
b660e790c6 | ||
|
|
7e8e03d4a1 | ||
|
|
994ee5d918 | ||
|
|
2f3f54b01a | ||
|
|
c119c426d3 | ||
|
|
e0f0f3c8b3 | ||
|
|
f7141b85e0 | ||
|
|
489059019d | ||
|
|
3b4182cd08 | ||
|
|
32e93fe075 | ||
|
|
3a35968338 | ||
|
|
393cde9259 | ||
|
|
52683a5646 | ||
|
|
176e50450b | ||
|
|
5db02079d0 | ||
|
|
7f8f8ae656 | ||
|
|
9717d64458 | ||
|
|
1b7de88a59 | ||
|
|
7f386f749e | ||
|
|
f662ada9f6 | ||
|
|
198708866a | ||
|
|
37c7f5b79c | ||
|
|
ab85388122 | ||
|
|
7463f8ec53 | ||
|
|
eec805287b | ||
|
|
733aa96c87 | ||
|
|
e5c95acd33 | ||
|
|
e87b14fa1e | ||
|
|
2cc0ea9f31 | ||
|
|
5fcdd2a3ed | ||
|
|
dbc0ed2673 | ||
|
|
02f61ca02f | ||
|
|
ff7814e3c6 | ||
|
|
074105c37c | ||
|
|
ffd81de815 | ||
|
|
4ce4ee6532 | ||
|
|
fe11d3691c | ||
|
|
5cca1692c9 | ||
|
|
609c2c4b42 | ||
|
|
32f34f2fab | ||
|
|
5f59b0bf57 | ||
|
|
66b7b97839 | ||
|
|
9a3693d2f0 | ||
|
|
1de5a9aac4 | ||
|
|
1c49ea3451 | ||
|
|
6aeef2d76e | ||
|
|
f4230c7386 | ||
|
|
150e9fc1cd | ||
|
|
4f0513996a | ||
|
|
ee6fbea177 | ||
|
|
a06898ec92 | ||
|
|
aaf8391a70 | ||
|
|
adbf3a1e34 | ||
|
|
f9745dd7a6 | ||
|
|
c3cccd8a9d | ||
|
|
24d977797d | ||
|
|
a7c155e5c3 | ||
|
|
f23a4a29c6 | ||
|
|
556fd42973 | ||
|
|
891fe07c6a | ||
|
|
7b29cd6817 | ||
|
|
17970feb21 | ||
|
|
38bf4b0136 | ||
|
|
0fce14bcfd | ||
|
|
11d06df771 | ||
|
|
cf1cc72255 | ||
|
|
d5b664b7f4 | ||
|
|
68e9f5254a | ||
|
|
64ca7d7d4b | ||
|
|
7965249e23 | ||
|
|
a40627ab9f | ||
|
|
5b705a3319 | ||
|
|
018d01e90e | ||
|
|
a8b012f9e7 | ||
|
|
2eb396d22c | ||
|
|
da760e646d | ||
|
|
a9c5c3a821 | ||
|
|
8c6264d699 | ||
|
|
22967b9a64 | ||
|
|
441f59d23b | ||
|
|
9d142ec964 | ||
|
|
da94b1ec50 | ||
|
|
92da1b4489 | ||
|
|
749b3f3aee | ||
|
|
1ea10e02ce | ||
|
|
8c7e94f6fc | ||
|
|
dd901b1f49 | ||
|
|
cd7e60a8dd | ||
|
|
8f5a829833 | ||
|
|
a618a52d03 | ||
|
|
ec525a3d70 | ||
|
|
4ba1b2fb95 | ||
|
|
8f0b7064fc | ||
|
|
4d5068ba3b | ||
|
|
d725fb81d6 | ||
|
|
0b279ef6d8 | ||
|
|
2d7fc7a161 | ||
|
|
bee9455935 | ||
|
|
5d3209d8e4 | ||
|
|
dab2478120 | ||
|
|
d288fa5901 | ||
|
|
e125ebb374 | ||
|
|
eb95722bd0 | ||
|
|
549cbabbb8 | ||
|
|
3d6c1abf8c | ||
|
|
0407ad024e | ||
|
|
dd70517f96 | ||
|
|
4bf0e2715c | ||
|
|
ecd4a37d94 | ||
|
|
3d4a76cda5 | ||
|
|
e1215c79be | ||
|
|
9910df4e24 | ||
|
|
d704aea774 | ||
|
|
8fab67b5fc | ||
|
|
767870a4fb | ||
|
|
3edd68558f | ||
|
|
956394b4ac | ||
|
|
4fb6a3a688 | ||
|
|
44e6367a9c | ||
|
|
d02516d37d | ||
|
|
af17f8b11d | ||
|
|
afbfc21d57 | ||
|
|
12338332d6 | ||
|
|
78120f5360 | ||
|
|
4486a8133e | ||
|
|
8db9e2ebef | ||
|
|
afd7f04ff6 | ||
|
|
a06d007202 | ||
|
|
229b8e4ee0 | ||
|
|
03e04d5333 | ||
|
|
b46ea65fdd | ||
|
|
b96df5ac21 | ||
|
|
4fc361fba0 | ||
|
|
8202db1eb1 | ||
|
|
d9003e525d | ||
|
|
1041cdb961 | ||
|
|
cbbf7e1535 | ||
|
|
740b017d0c | ||
|
|
5d71659bfa | ||
|
|
11daffcd27 | ||
|
|
1bf212e225 | ||
|
|
61e9a18942 | ||
|
|
e25b31ecd8 | ||
|
|
c2ed71a388 | ||
|
|
bab30952b8 | ||
|
|
5469335de9 | ||
|
|
7c604f37dd | ||
|
|
5de4156147 | ||
|
|
6cc2e9ade7 | ||
|
|
9eceb189ba | ||
|
|
8f1cb1b8c2 | ||
|
|
960f2a305e | ||
|
|
9b07cd21c0 | ||
|
|
c9cb2edc7e | ||
|
|
d92a29d63c | ||
|
|
52fa401725 | ||
|
|
7d71507688 | ||
|
|
9eee15ac3a | ||
|
|
04a4a730fb | ||
|
|
3cc3134386 | ||
|
|
d5b919e0d6 | ||
|
|
4b90fe91bf | ||
|
|
6908bb5947 | ||
|
|
2b36c261b8 | ||
|
|
f394f15ba5 | ||
|
|
9721c81eb0 | ||
|
|
06c5cb7652 | ||
|
|
d8ba6c39f7 | ||
|
|
eea94ea4ba | ||
|
|
e3cb29982f | ||
|
|
c1c8a21f9a | ||
|
|
6fa08f8917 | ||
|
|
6ed8af8764 | ||
|
|
98bbd6f185 | ||
|
|
5934d043ac | ||
|
|
b1ebd77f8a | ||
|
|
40701760eb | ||
|
|
72c68dad84 | ||
|
|
a8ae6ca2f8 | ||
|
|
cb642b0d37 | ||
|
|
0f2ec7c263 | ||
|
|
d7e416982f | ||
|
|
9753b34fb9 | ||
|
|
7fff857b4c | ||
|
|
29468602b4 | ||
|
|
a4e30e292a | ||
|
|
64ca693fce | ||
|
|
2f020ed0bb | ||
|
|
3b2c35c9c4 | ||
|
|
2d032b3360 | ||
|
|
21b6848e21 | ||
|
|
fb172b6049 | ||
|
|
d9a88fbd88 | ||
|
|
c94e3a01e1 | ||
|
|
abb6adb5d2 | ||
|
|
42066f1e00 | ||
|
|
8135ff9006 | ||
|
|
cc98c0eb28 | ||
|
|
d0bdad58c2 | ||
|
|
ea2c7418f1 | ||
|
|
3750a350fd | ||
|
|
057c2eff5f | ||
|
|
093a84fc83 | ||
|
|
b51c32401c | ||
|
|
849af7a4d0 | ||
|
|
8072730e51 | ||
|
|
f30dc705c9 | ||
|
|
88cf6f366f | ||
|
|
8b46b72a17 | ||
|
|
5f8b3d9a57 | ||
|
|
9be37ea123 | ||
|
|
8f987857e8 | ||
|
|
4830c3aba1 | ||
|
|
4beb1f61ea | ||
|
|
dd509d40c6 | ||
|
|
bf6915b43b | ||
|
|
7f62c457ad | ||
|
|
9dcbd4c3e0 | ||
|
|
e6c458021c | ||
|
|
6e133a7229 | ||
|
|
342f194c87 | ||
|
|
28447466ca | ||
|
|
b48483b7f1 | ||
|
|
dba0bdcbdd | ||
|
|
15fb1e5741 | ||
|
|
dadf3ae7a3 | ||
|
|
439bbab634 | ||
|
|
dbb47e0bef | ||
|
|
ff279177c4 | ||
|
|
5886f6ad08 | ||
|
|
501dcc916f | ||
|
|
9a5f44ff65 | ||
|
|
b6a57f75f6 | ||
|
|
f8143bffaf | ||
|
|
c7c1a0a68a | ||
|
|
bf887fca13 | ||
|
|
f858f48c34 | ||
|
|
b7cbf05305 | ||
|
|
d6e36a3268 | ||
|
|
6437580600 | ||
|
|
5af9a5b1b6 | ||
|
|
623f45a254 | ||
|
|
e51944f045 | ||
|
|
a40e45c90a | ||
|
|
2d4ffe0bce | ||
|
|
375d0216d1 | ||
|
|
3ea005822e | ||
|
|
231ab3a4bf | ||
|
|
bdb52b1ec7 | ||
|
|
62d88d919d | ||
|
|
3a0c8e1597 | ||
|
|
03256db913 | ||
|
|
ccddab6425 | ||
|
|
cff858ec23 | ||
|
|
6b48c4d3c5 | ||
|
|
681a37905c | ||
|
|
629159a29f | ||
|
|
c9e48b320d | ||
|
|
7ddf74570a | ||
|
|
cc2c9a2973 | ||
|
|
6e5ed683d6 | ||
|
|
faac237f0d | ||
|
|
50ccf0f21b | ||
|
|
9a00c91632 | ||
|
|
52d8807282 | ||
|
|
32d20eea01 | ||
|
|
17e799a766 | ||
|
|
0031953ed3 | ||
|
|
ca9e1840cf | ||
|
|
a24d026b0e | ||
|
|
8ec398522e | ||
|
|
4d897186c2 | ||
|
|
c4cc93512f | ||
|
|
0a538380a9 | ||
|
|
9afe8a3243 | ||
|
|
72d6ac8719 | ||
|
|
bc31bfac58 | ||
|
|
80a41d3264 | ||
|
|
7141734f07 | ||
|
|
6b49be085d | ||
|
|
b12e3320a9 | ||
|
|
37281b64f2 | ||
|
|
22d974a722 | ||
|
|
2deb93c7ce | ||
|
|
4c7088d757 | ||
|
|
f0efafbba9 | ||
|
|
5783503f74 | ||
|
|
530c14e4b8 | ||
|
|
f7293411cd | ||
|
|
18ae9934e1 | ||
|
|
365ec8b7fa | ||
|
|
607923dfbd | ||
|
|
f5c4943337 | ||
|
|
3a18842ac7 | ||
|
|
469410cbcf | ||
|
|
813ff7e199 | ||
|
|
7851fc0aac | ||
|
|
974dde3b88 | ||
|
|
e232f5468a | ||
|
|
6fa67097aa | ||
|
|
506719796e | ||
|
|
20f975c9b7 | ||
|
|
1f29d1a39d | ||
|
|
e605a5886f | ||
|
|
400cca9252 | ||
|
|
458a87e81a | ||
|
|
e8d8f139d0 | ||
|
|
e8e50bfa6b | ||
|
|
b61504e821 | ||
|
|
fc3a0718a1 | ||
|
|
c618fa694c | ||
|
|
e64a559595 | ||
|
|
44ff1411a3 | ||
|
|
d06bb14e64 | ||
|
|
f186994c83 | ||
|
|
d1bc1d41b9 | ||
|
|
9bc7920ab5 | ||
|
|
69cc4d38f0 | ||
|
|
9b4b24c7ae | ||
|
|
d56d3951d3 | ||
|
|
9e886bc73e | ||
|
|
3a58ab7bb3 | ||
|
|
b977bacb29 | ||
|
|
8560bd88a7 | ||
|
|
7181afab40 | ||
|
|
0d7d3c7bf1 | ||
|
|
fd32805c30 | ||
|
|
bd959ce25c | ||
|
|
cfbafbd67f | ||
|
|
582a2cd31f | ||
|
|
4f622b8e32 | ||
|
|
de3f64f41d | ||
|
|
c9b7e7f462 | ||
|
|
898ff98761 | ||
|
|
07827f56ce | ||
|
|
544257bfdd | ||
|
|
0dd48f6152 | ||
|
|
74ac3b73cd | ||
|
|
ae6eb52f35 | ||
|
|
97484091b6 | ||
|
|
bca464cc73 | ||
|
|
5472ceca48 | ||
|
|
32ac0eee35 | ||
|
|
0764275b44 | ||
|
|
277f94885f | ||
|
|
84819e2bf7 | ||
|
|
31f7db302d | ||
|
|
1c61cce2e7 | ||
|
|
7c7e11d3aa | ||
|
|
7a717f2d25 | ||
|
|
1247cdabc1 | ||
|
|
6812167422 | ||
|
|
30e9e65854 | ||
|
|
ed3e97c22a | ||
|
|
e0721c1dbc | ||
|
|
4302fbbf69 | ||
|
|
f6fbba4c48 | ||
|
|
164941f373 | ||
|
|
d5d7292bf7 | ||
|
|
d2ad131eb2 | ||
|
|
44262c4236 | ||
|
|
2a3208b96e | ||
|
|
aa81aa8c6f | ||
|
|
5187a77dcd | ||
|
|
c83bb9360f | ||
|
|
1a33df4b6f | ||
|
|
7906ca5326 | ||
|
|
dc5ce2ba72 | ||
|
|
787abf4f7d | ||
|
|
2109ed6403 | ||
|
|
3d42c64238 | ||
|
|
ec8a4582b4 | ||
|
|
6442a5cb18 | ||
|
|
e588b0c229 | ||
|
|
c3410c2101 | ||
|
|
2ac84cc611 | ||
|
|
97df1a4086 | ||
|
|
d8051af226 | ||
|
|
f74cf78187 | ||
|
|
1ba99cdf8a | ||
|
|
6899b446c7 | ||
|
|
5b7b8fa37c | ||
|
|
205eb7bacb | ||
|
|
45d2c67689 | ||
|
|
17c0b8d0fe | ||
|
|
7c387d60a8 | ||
|
|
4c189fb0da | ||
|
|
68fda050de | ||
|
|
b94891ed1b | ||
|
|
2acc72ca5e | ||
|
|
6e73c0f700 | ||
|
|
e65c023d4f | ||
|
|
a22edf160f | ||
|
|
39874d92dc | ||
|
|
21c388e0d3 | ||
|
|
b3fe725742 | ||
|
|
40ba8f5d73 | ||
|
|
a63a85d667 | ||
|
|
848ca4a59d | ||
|
|
56bb46634c | ||
|
|
08397f3f3b | ||
|
|
e88ecd1903 | ||
|
|
e9354b38d6 | ||
|
|
2533a3d5d3 | ||
|
|
b981bbd87b | ||
|
|
1100ded218 | ||
|
|
f9043eec7b | ||
|
|
35ac6cb61f | ||
|
|
6ecb5c4f73 | ||
|
|
cba46c6a39 | ||
|
|
36c932a5b3 | ||
|
|
20f72214d7 | ||
|
|
fac3d8baf0 | ||
|
|
8d85a76811 | ||
|
|
ec638af7e6 | ||
|
|
ba4d6e3e84 | ||
|
|
6d25dd3c35 | ||
|
|
cb47d97ccc | ||
|
|
b244f1e1d0 | ||
|
|
b676083e7f | ||
|
|
f927b37b83 | ||
|
|
88e43f7402 | ||
|
|
23f234d095 | ||
|
|
31bd50137d | ||
|
|
f708c1ffbe | ||
|
|
a47f2c4689 | ||
|
|
e37c151f0e | ||
|
|
e2cf6ed85f | ||
|
|
be5bbf3117 | ||
|
|
d2e80871ce | ||
|
|
9db8cdc7f8 | ||
|
|
e10b4ad4f0 | ||
|
|
520e3f8294 | ||
|
|
103ea7bb53 | ||
|
|
211738132c | ||
|
|
f8ece7f55e | ||
|
|
1f9a575d07 | ||
|
|
c4532e1be7 | ||
|
|
b2131b64a1 |
@@ -1,119 +0,0 @@
|
||||
---
|
||||
name: "ticket-reviewer"
|
||||
description: "Use this agent when a ticket implementation is submitted for review in this project (insomnia). The agent reviews the ticket's premises/requirements and the actual implementation, creates `tickets/<ticket>.review.md` with findings, and updates the original `tickets/<ticket>.md` with review status. Do NOT use this agent for general code review unrelated to a ticket. "
|
||||
model: opus
|
||||
color: purple
|
||||
---
|
||||
|
||||
You are a senior reviewer specialized in the `insomnia` project. You are an expert at evaluating ticket-scoped implementations against their stated premises and requirements, and at safeguarding the codebase from unnecessary complexity or architectural drift. You operate strictly within the project's ticket lifecycle conventions defined in `CLAUDE.md`.
|
||||
|
||||
## Your Core Responsibility
|
||||
|
||||
Given a ticket (normally `tickets/<name>.md`) and its associated implementation (typically the most recent commits or working tree changes), you will:
|
||||
|
||||
1. Read the ticket thoroughly to understand its **背景・前提・要件**.
|
||||
2. Inspect the implementation (diff + surrounding code, not only the diff).
|
||||
3. Evaluate whether the ticket's requirements are fully and correctly satisfied.
|
||||
4. Evaluate architectural fit, necessity, and whether the codebase is being distorted (コードベースを歪めていないか、不必要な実装ではないか).
|
||||
5. Produce `tickets/<name>.review.md` with findings and a clear judgment.
|
||||
6. Update the original `tickets/<name>.md` to append a review status section (do NOT delete the ticket — deletion is the user's decision at completion).
|
||||
|
||||
You must NEVER run `git` write operations (commit, add, push, etc.). Git is the user's responsibility (per CLAUDE.md). You only edit/create files in the working tree.
|
||||
|
||||
## Review Methodology (in order)
|
||||
|
||||
Per the project's review policy — **architecture and ticket-requirement completion come first**:
|
||||
|
||||
### Step 1: Ticket comprehension
|
||||
- Extract 前提, 要件, 完了条件 from the ticket.
|
||||
- Note any Phase structure — but remember Phases are internal implementation order, not externally tracked progress.
|
||||
- Confirm the ticket's intended scope boundary.
|
||||
|
||||
### Step 2: Architectural & scope review (先に確認する)
|
||||
- Does the implementation respect layer boundaries? (e.g., `llm-worker` stays low-level; higher-level features live in upper layers.)
|
||||
- Are new crates named without the `insomnia-` prefix, short and consistent?
|
||||
- Were dependencies added via `cargo add` (not manual edits to Cargo.toml)?
|
||||
- Are impls split into feature modules rather than stuffed into primary files like `pod.rs`?
|
||||
- Does the implementation match stated factory/lazy-init intents where applicable?
|
||||
- Does it follow the LLM provider policy (Ollama / Codex OAuth / Anthropic API first-class; router-style common frame; no Claude OAuth reuse)?
|
||||
- Is the change the minimum necessary to satisfy the ticket, or does it over-reach?
|
||||
|
||||
### Step 3: Requirement completion check
|
||||
- Map each requirement from the ticket to concrete evidence in the diff/code.
|
||||
- Flag any requirement that is unmet, partially met, or silently deferred.
|
||||
- Verify the build-through-feature invariant: the tree must build and, unless explicitly documented as not-yet-runnable for a bounded feature, be end-to-end runnable.
|
||||
|
||||
### Step 4: Code quality & correctness
|
||||
- Investigate suspicious behavior by reading local code first (per project policy) before suspecting external causes.
|
||||
- Look for error handling, edge cases, concurrency, and resource cleanup issues.
|
||||
- Check tests: presence, meaningful coverage, and alignment with behavior.
|
||||
- Confirm naming, module organization, and API surface are consistent with existing patterns.
|
||||
|
||||
### Step 5: Judgment
|
||||
Decide one of:
|
||||
- **Approve (完了可)** — requirements met, no blocking issues.
|
||||
- **Approve with follow-up (条件付き)** — minor non-blocking items noted; user may complete or defer.
|
||||
- **Request changes (要修正)** — blocking issues must be addressed.
|
||||
|
||||
## Output Artifacts
|
||||
|
||||
### A. `tickets/<name>.review.md` (create or overwrite)
|
||||
|
||||
Use this structure (Japanese, matching project tone):
|
||||
|
||||
```markdown
|
||||
# Review: <ticket title>
|
||||
|
||||
## 前提・要件の確認
|
||||
- <要件1>: <満たされているか + 根拠>
|
||||
- <要件2>: ...
|
||||
|
||||
## アーキテクチャ・スコープ
|
||||
- <観点と判断>
|
||||
|
||||
## 指摘事項
|
||||
### Blocking
|
||||
- <項目> — <理由と該当箇所 path:line>
|
||||
|
||||
### Non-blocking / Follow-up
|
||||
- <項目> — <理由>
|
||||
|
||||
### Nits
|
||||
- <項目>
|
||||
|
||||
## 判断
|
||||
<Approve / Approve with follow-up / Request changes> — <一文の理由>
|
||||
```
|
||||
|
||||
Omit empty sections. Cite concrete file paths and line ranges. Be concise; avoid restating obvious code.
|
||||
|
||||
### B. Update `tickets/<name>.md`
|
||||
|
||||
Append (or update if present) a trailing section like:
|
||||
|
||||
```markdown
|
||||
## Review
|
||||
- 状態: <Approve / Approve with follow-up / Request changes>
|
||||
- レビュー詳細: [./<name>.review.md](./<name>.review.md)
|
||||
- 日付: 2026-04-21
|
||||
```
|
||||
|
||||
Do not modify the ticket's 背景・要件 sections unless the user explicitly asked for it. Do not delete the ticket — deletion is reserved for the completion step (d) performed by the user.
|
||||
|
||||
## Operating Principles
|
||||
|
||||
- **Do not commit or stage anything.** File edits only. The user will handle git.
|
||||
- **Do not over-engineer the review.** Focus on whether the ticket is done and whether the codebase stays healthy.
|
||||
- **Prefer concrete citations** (path:line) over abstract complaints.
|
||||
- **Ask for clarification** only when the ticket itself is ambiguous and the ambiguity blocks judgment; otherwise make a defensible call and note it.
|
||||
- **Re-review mode**: if `.review.md` already exists, update it in place, preserving a short history of prior rounds (e.g., `## Round 2` section) so the evolution is visible until the ticket is closed.
|
||||
- **TODO.md is not your concern** unless a requirement explicitly demands it; ticket lifecycle edits to TODO.md are the user's.
|
||||
|
||||
## Quality Self-Check (before finishing)
|
||||
|
||||
1. Did I evaluate architectural fit before nitpicks?
|
||||
2. Did I map every ticket requirement to evidence?
|
||||
3. Are all blocking issues genuinely blocking (not stylistic)?
|
||||
4. Did I avoid making git writes?
|
||||
5. Did I update both `<name>.review.md` and `<name>.md`?
|
||||
6. Is my judgment line unambiguous?
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: worktree-workflow
|
||||
description: "Worktreeを用いた開発フローを進める。git上の開発に置けるミクロな指示で、プロジェクトの管理に関する指示は提供されていない。"
|
||||
allowed-tools: "Bash(cd *), Bash(git worktree *), Bash(mkdir *), Bash(cp *), Bash(ln *), Bash(ls *), Bash(find *)"
|
||||
---
|
||||
|
||||
# Worktreeを用いた開発
|
||||
|
||||
Goal: 実装を完了させ、ブランチをマージ待ちの状態にする。
|
||||
|
||||
`./.worktree`にworktreeを作成します。
|
||||
エージェントの1セッション=1ワークツリーとしており、ブランチ/イシュー/チケット単位で切ります。
|
||||
|
||||
このワークフローにおいては、ブランチはローカルで並行開発するためのマージ後削除の運用とし、Worktreeと同名のbranchを同時に作って進めます。メインのディレクトリのブランチから切るものとして扱います。
|
||||
|
||||
```
|
||||
git worktree add .worktree/<task-name> -n <task-name>
|
||||
```
|
||||
|
||||
## flake.nixの無効化
|
||||
|
||||
基本的に、CWDを変更できない場合、.envrcによる自動アクティベートは効かないので無視で構わない。
|
||||
|
||||
## 完了時
|
||||
|
||||
マージウィンドウからこのスキルがinvokeされた際は、ブランチのマージ・worktreeの削除まで行う。対して、実装者がマージしてクローズしてはならない。
|
||||
+4
-2
@@ -1,5 +1,7 @@
|
||||
/target
|
||||
.direnv
|
||||
/result
|
||||
/.direnv
|
||||
/.yoi/dev
|
||||
.worktree
|
||||
*.local*
|
||||
.env
|
||||
.worktree
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/memory/
|
||||
@@ -1,30 +0,0 @@
|
||||
[scope]
|
||||
allow = [
|
||||
{ target = ".", permission = "write", recursive = true },
|
||||
]
|
||||
|
||||
[session]
|
||||
record_event_trace = true
|
||||
|
||||
[worker]
|
||||
reasoning = "high"
|
||||
|
||||
[model]
|
||||
ref = "codex-oauth/gpt-5.5"
|
||||
|
||||
[compaction]
|
||||
compact_threshold = 200000
|
||||
compact_request_threshold = 240000
|
||||
compact_worker_max_input_tokens = 100000
|
||||
|
||||
[memory]
|
||||
extract_threshold = 50000
|
||||
|
||||
consolidation_threshold_files = 5
|
||||
consolidation_threshold_bytes = 50000
|
||||
|
||||
[web]
|
||||
enabled = true
|
||||
[web.search]
|
||||
provider = "brave"
|
||||
api_key_env = "BRAVE_SEARCH_API_KEY"
|
||||
@@ -1,148 +0,0 @@
|
||||
---
|
||||
description: TODO / tickets / docs / git history から次の作業候補を見繕い、課題発見や方針決定を半自動でイテレーションする WIP maintainer workflow
|
||||
model_invokation: false
|
||||
user_invocable: true
|
||||
requires: []
|
||||
---
|
||||
# Auto Maintain Workflow (WIP)
|
||||
|
||||
insomnia を AI maintainer として運用するための半自動 loop。TODO / tickets から「今進められそうな作業」を選ぶだけでなく、課題の発見、設計判断の切り分け、次に人間へ戻すべき問いの整理までを扱う。
|
||||
|
||||
これは unattended 自動開発ではない。実装の並列委譲は `multi-agent-workflow`、worktree の機械的作成は `worktree-workflow` に任せる。本 Workflow はその前段として、何を進めるべきか、何をまだ決めるべきか、下位 orchestrator にどの intent packet を渡すべきかを整理する。
|
||||
|
||||
参照:
|
||||
|
||||
- `docs/plan/ai-maintainer.md`
|
||||
- `tickets/auto-maintain-workflow.md`
|
||||
|
||||
## 位置づけ
|
||||
|
||||
AI maintainer の目的は、コードを書くこと自体ではなく、プロジェクト状態を前に進めることである。
|
||||
|
||||
この Workflow は WIP として、以下を行う。
|
||||
|
||||
- TODO / tickets / docs / git history を読んで現在地を把握する。
|
||||
- 実装可能な ticket と、方針決定が必要な ticket を分ける。
|
||||
- 小さく実装できる候補を提案する。
|
||||
- 複数 ticket からなる作業群は、下位 orchestrator に任せる単位として整理する。
|
||||
- 設計相談が必要な論点を人間に戻す。
|
||||
- 運用上の問題や繰り返し発生する詰まりを report / ticket / workflow 改訂候補として整理する。
|
||||
|
||||
## 非目標
|
||||
|
||||
現時点では以下をしない。
|
||||
|
||||
- 常駐 scheduler として自動実行する。
|
||||
- 人間の合意なしに新規 ticket を作る。
|
||||
- 人間の合意なしに既存 ticket を大幅変更する。
|
||||
- 人間の合意なしに ticket 完了削除を行う。
|
||||
- push する。
|
||||
- Workflow を自律生成・自律改訂する。
|
||||
- scope / permission / history persistence / prompt context 加工原則に関わる判断を勝手に決める。
|
||||
|
||||
## 入力として読むもの
|
||||
|
||||
必要に応じて以下を読む。
|
||||
|
||||
1. `TODO.md`
|
||||
2. `tickets/*.md`
|
||||
3. `docs/plan/`
|
||||
4. `docs/report/`
|
||||
5. `git log --oneline` / ticket file の git history
|
||||
6. 既存 worktree / branch 状態
|
||||
7. 最近の失敗や通知、ユーザーからの観測
|
||||
|
||||
TODO と ticket の不整合を見つけたら、勝手に修正せず、まず報告する。ただしユーザーが明示的に「直して」と言った場合は Mode 1 として整理してよい。
|
||||
|
||||
## 分類
|
||||
|
||||
候補を以下に分ける。
|
||||
|
||||
### A. 実装委譲可能
|
||||
|
||||
- 要件と完了条件が具体的。
|
||||
- 影響範囲が限定的。
|
||||
- test / build で確認できる。
|
||||
- 大きな設計判断が不要。
|
||||
- scope を狭く切れる。
|
||||
|
||||
この場合は、人間に候補として提示する。人間が実行を許可したら `$user/multi-agent-workflow` に進む。複数 ticket や連続した作業群では、最上位 Pod が直接 coder を抱えず、下位 orchestrator に intent packet を渡して coder / reviewer sibling loop を管理させる。
|
||||
|
||||
### B. 方針決定が必要
|
||||
|
||||
- 複数の設計方針が自然に導ける。
|
||||
- protocol / permission / scope / persistence / prompt context に触れる。
|
||||
- UX の仕様が未確定。
|
||||
- 既存 ticket の要件が古い。
|
||||
|
||||
この場合は、実装せず、決めるべき問いを短く提示する。
|
||||
|
||||
### C. ticket 整理が必要
|
||||
|
||||
- TODO にあるが ticket がない。
|
||||
- ticket があるが TODO にない。
|
||||
- 完了済みに見えるが残っている。
|
||||
- ticket の前提が変わっている。
|
||||
|
||||
この場合は、不整合と修正案を提示する。修正は人間の許可後に行う。
|
||||
|
||||
### D. report / workflow 改善候補
|
||||
|
||||
- 同じ tool 問題が繰り返し出る。
|
||||
- Workflow の指示が曖昧で実装 Pod が迷った。
|
||||
- coder / reviewer / orchestrator の責務が混ざり、親 Pod が細かい code review に戻ってしまった。
|
||||
- AI が過剰に Task tool を使うなど、運用上の癖が出た。
|
||||
- 通知や Pod completion tracking など、開発基盤の不足が観測された。
|
||||
|
||||
この場合は、すぐ ticket 化するか、`docs/report/` に観測として残すか、人間に確認する。
|
||||
|
||||
## 半自動 iteration
|
||||
|
||||
1. 状態把握
|
||||
- TODO / tickets / git status を読む。
|
||||
- 最近完了した流れや未完了 branch を確認する。
|
||||
|
||||
2. 候補抽出
|
||||
- 実装可能そうな ticket を 2〜5 件挙げる。
|
||||
- correctness / developer experience / user-visible UX / cleanup で分類する。
|
||||
|
||||
3. 推奨順位
|
||||
- blocking correctness を最優先。
|
||||
- 実害が出ている運用問題を次点。
|
||||
- 小さく完了できる UX / cleanup を次点。
|
||||
- 大きな設計変更は方針相談に回す。
|
||||
|
||||
4. 人間への提示
|
||||
- 「次に進めるなら X」を1つ推奨する。
|
||||
- 理由を短く述べる。
|
||||
- 実装委譲する場合の scope / test 方針を添える。
|
||||
- 複数 ticket の作業群なら、下位 orchestrator に任せる単位として提示する。
|
||||
|
||||
5. 実行への接続
|
||||
- 人間が「進めて」と言ったら `$user/multi-agent-workflow` に接続する。
|
||||
- 単発 ticket か、下位 orchestrator に任せる ticket 群かを明示する。
|
||||
- 下位 orchestrator に渡す intent / requirements / invariants / non-goals / escalation 条件を短くまとめる。
|
||||
- worktree 作成は `$user/worktree-workflow` に従う。
|
||||
|
||||
## エスカレーション基準
|
||||
|
||||
以下では実装に進まず、人間へ戻す。
|
||||
|
||||
- ticket の要件から複数の設計方針が自然に導ける。
|
||||
- 長期構造、crate boundary、protocol、permission、scope、history persistence に触れる。
|
||||
- prompt context 加工原則に関わる。
|
||||
- 新 ticket の作成、既存 ticket の大幅変更、ticket 完了削除について合意がない。
|
||||
- test 不能、再現不能、または作業範囲外の不具合に遭遇した。
|
||||
- WorkItem / Thread / Lease / maintainer state など、まだ設計中の概念が必要になる。
|
||||
- 下位 orchestrator に委譲するには intent / invariant / escalation 条件が曖昧すぎる。
|
||||
|
||||
## まだ固定しないもの
|
||||
|
||||
以下は `docs/plan/ai-maintainer.md` の上位設計に残し、本 Workflow では詳細を固定しない。
|
||||
|
||||
- WorkItemStore / LeaseStore。
|
||||
- operation inbox / trial log。
|
||||
- QA feedback を ticket / review / report のどれに落とすか。
|
||||
- AI 自身の feedback を Knowledge / report / ticket / workflow 改訂のどれにするか。
|
||||
- maintainer doctor。
|
||||
- reviewer Pod の評価基準の機械化。
|
||||
@@ -1,302 +0,0 @@
|
||||
---
|
||||
description: worktree と sibling の coder / reviewer Pod を使い、下位 orchestrator が複数 ticket の実装・外部レビュー・修正・完了準備を管理する orchestration フロー
|
||||
model_invokation: true
|
||||
user_invocable: true
|
||||
requires: []
|
||||
---
|
||||
# Multi-agent Worktree Workflow
|
||||
|
||||
insomnia を insomnia で開発する際の、worktree + coder Pod + 外部 reviewer Pod + orchestrator Pod の標準フロー。これは **最上位 Pod が細かい code review を抱えず、下位 orchestrator が実装と外部レビューの loop を完了状態まで運ぶためのフロー** である。
|
||||
|
||||
worktree の機械的作成手順は `$user/worktree-workflow`、ticket 候補選定や方針探索の半自動 loop は `$user/auto-maintain` に分ける。
|
||||
|
||||
## 目的
|
||||
|
||||
- 実装差分を ticket ごとの child worktree に隔離する。
|
||||
- coder Pod に narrow write scope を渡して実装させる。
|
||||
- reviewer Pod を coder の子ではなく **同じ orchestrator 配下の sibling** として立て、外部レビューを行わせる。
|
||||
- orchestrator は coder / reviewer のやり取り、修正 loop、validation、merge-ready dossier 作成に責任を持つ。
|
||||
- 最上位 orchestrator は、コードを直接理解し切ることではなく、委譲した intent / 要件 / invariant に沿って下位 orchestrator が完了まで運んだかを acceptance する。
|
||||
|
||||
## 階層モデル
|
||||
|
||||
基本形は以下。
|
||||
|
||||
```text
|
||||
最上位 orchestrator Pod
|
||||
- 人間との会話相手
|
||||
- intent / 要件 / invariant / escalation 条件を定義
|
||||
- 複数の作業群を並列管理
|
||||
- final merge / ticket close / main workspace validation を行う
|
||||
- 原則として line-by-line code review を主業務にしない
|
||||
|
||||
下位 orchestrator Pod(area / epic / ticket-group coordinator)
|
||||
- 連続した複数 ticket または大きめの ticket 群を完了状態まで運ぶ
|
||||
- worktree / branch / coder / reviewer / validation / 修正 loop を管理する
|
||||
- coder と reviewer を sibling として扱う
|
||||
- 親には merge-ready dossier と残論点だけを返す
|
||||
|
||||
coder Pod
|
||||
- 指定 worktree / branch に実装する
|
||||
- ticket 外判断や設計衝突は orchestrator に戻す
|
||||
- reviewer に直接反論・修正依頼を完結させず、orchestrator に報告する
|
||||
|
||||
reviewer Pod
|
||||
- 原則 read-only
|
||||
- ticket / intent packet / diff / validation 結果を読む
|
||||
- 実際のコード変更が概念的に何を変えたかを説明する
|
||||
- intent / 要件 / invariant に反する blocker を分類して返す
|
||||
```
|
||||
|
||||
一段だけで足りる小さい ticket では、最上位 orchestrator が直接 coder / reviewer sibling を扱ってよい。複数 ticket や設計境界をまたぐ作業では、最上位の下に下位 orchestrator を挟む。
|
||||
|
||||
## 開始条件
|
||||
|
||||
以下が揃っている時に使う。
|
||||
|
||||
- 対象 ticket または ticket 群が決まっている。
|
||||
- ticket の背景・要件・完了条件から実装方針が概ね導ける。
|
||||
- worktree 作成と git 書き込み操作について、人間の許可がある。
|
||||
- main workspace の unrelated dirty changes を把握している。
|
||||
- 下位 orchestrator に渡す intent / invariant / non-goals / escalation 条件を短く書ける。
|
||||
|
||||
設計方針が複数自然に導ける場合、protocol / scope / permission / history persistence に触れる場合、ticket 自体の再定義が必要な場合は、実装委譲前に人間へ戻す。ただし下位 orchestrator に探索だけを委譲することはできる。
|
||||
|
||||
## Intent packet
|
||||
|
||||
階層化すると人間の意図が劣化しやすい。下位 orchestrator や coder / reviewer へは、自然文の依頼だけでなく intent packet を渡す。
|
||||
|
||||
標準形:
|
||||
|
||||
```text
|
||||
Intent:
|
||||
- 何を実現するか。
|
||||
|
||||
Requirements:
|
||||
- 完了時に満たすべき observable な要件。
|
||||
|
||||
Invariants:
|
||||
- 壊してはいけない設計境界。
|
||||
- 残してはいけない旧概念や互換層。
|
||||
|
||||
Non-goals:
|
||||
- 今回やらないこと。
|
||||
|
||||
Escalate if:
|
||||
- 親へ戻すべき判断条件。
|
||||
|
||||
Validation:
|
||||
- 実行すべき format / build / test / doctor。
|
||||
```
|
||||
|
||||
reviewer には coder の実装方針ではなく、この intent packet と diff を中心に読ませる。
|
||||
|
||||
## orchestrator の責務
|
||||
|
||||
下位 orchestrator を挟まない場合は、以下を最上位 orchestrator が行う。下位 orchestrator を挟む場合は、最上位は intent packet を渡し、以下の実務を下位に委譲する。
|
||||
|
||||
1. 状態確認
|
||||
- `git status --short --branch`
|
||||
- 対象 ticket / ticket 群
|
||||
- 関連 TODO / docs / 既存 worktree
|
||||
|
||||
2. worktree 作成
|
||||
- `$user/worktree-workflow` に従い `./.worktree/<task-name>` を作る。
|
||||
- `.insomnia` を sparse checkout で除外する。
|
||||
|
||||
3. coder Pod spawn
|
||||
- read scope: main workspace 全体。
|
||||
- write scope: child worktree、または必要最小 directory。
|
||||
- task には以下を明示する。
|
||||
- child worktree path / branch
|
||||
- 対象 ticket path
|
||||
- intent packet
|
||||
- Bash は必ず child worktree に `cd` すること
|
||||
- main workspace の `TODO.md` / `tickets/` / `docs/report/` / `.insomnia` は編集しないこと
|
||||
- 範囲外事項
|
||||
- 実行すべき build / test / format
|
||||
- 完了報告項目
|
||||
|
||||
4. coder 完了確認
|
||||
- `ReadPodOutput` で報告を読む。
|
||||
- 通知が来ない場合でも、worktree の `git status` / `git diff` / test で完了状態を確認する。
|
||||
- coder が止まった場合、worktree 状態を見て再 spawn / rollback / 親 escalation を判断する。
|
||||
|
||||
5. reviewer Pod spawn
|
||||
- reviewer は coder の子ではなく orchestrator 配下の sibling として立てる。
|
||||
- 原則 read-only scope にする。
|
||||
- reviewer に渡すもの:
|
||||
- ticket / intent packet
|
||||
- branch / commit / diff の読み方
|
||||
- 実行済み validation
|
||||
- blocker / non-blocker / acceptable の分類基準
|
||||
- reviewer の主目的は「コードの詳細を親の代わりに説明し、intent / invariant 違反を見つけること」。
|
||||
|
||||
6. review → 修正 loop
|
||||
- reviewer finding を orchestrator が読む。
|
||||
- blocker は coder に修正依頼する。
|
||||
- reviewer の指摘を却下する場合は、orchestrator が理由を dossier に残す。
|
||||
- 修正後は focused validation を実行し、必要なら reviewer に再確認させる。
|
||||
- reviewer の blocker が未解決のまま親に提出しない。
|
||||
|
||||
7. merge-ready dossier 作成
|
||||
- 親がコードを直接理解しなくても判断できるよう、変更の概念的説明と evidence をまとめる。
|
||||
|
||||
8. merge / lifecycle
|
||||
- 最上位 orchestrator または人間の許可を持つ orchestrator が main workspace へ merge する。
|
||||
- `TODO.md` から該当行を削除し、ticket を完了処理して commit する。
|
||||
- main workspace で必要な test / `cargo check --workspace` / `cargo fmt --check` を再実行する。
|
||||
|
||||
## coder Pod の責務
|
||||
|
||||
- child worktree 内でのみ実装する。
|
||||
- main workspace の管理ファイルを書かない。
|
||||
- intent / requirements / invariants / non-goals を読んでから実装する。
|
||||
- 指定された build / test / format を実行する。
|
||||
- ticket 要件外の設計変更、依存関係追加、scope / permission / history persistence / prompt context 加工原則に触れる変更が必要なら止めて orchestrator に報告する。
|
||||
- 完了時に以下を報告する。
|
||||
- worktree path / branch
|
||||
- commit hash(commit した場合)
|
||||
- 変更ファイル
|
||||
- 実装概要
|
||||
- 実行した build / test / format
|
||||
- 未解決事項
|
||||
- review に回せるか
|
||||
|
||||
## reviewer Pod の責務
|
||||
|
||||
reviewer は coder の subordinate ではない。orchestrator 配下の sibling として、実装 diff を外部から読む。
|
||||
|
||||
- 原則 read-only で作業する。
|
||||
- ticket / intent packet / diff / validation 結果を読む。
|
||||
- 実際のコード変更が概念的に何を変えたかを説明する。
|
||||
- 親や上位 orchestrator が line-by-line diff を読まずに判断できるよう、以下を整理する。
|
||||
- 変更の概念モデル
|
||||
- intent / requirements との対応
|
||||
- invariant 違反の有無
|
||||
- 旧概念・禁止語彙・不要な互換層の残存
|
||||
- validation の妥当性
|
||||
- blocker / non-blocker / follow-up
|
||||
- reviewer は直接 merge しない。
|
||||
- reviewer は coder に直接作業指示を出さず、orchestrator に finding を返す。
|
||||
|
||||
## commit 方針
|
||||
|
||||
coder Pod には child worktree 内での commit を許可してよい。
|
||||
|
||||
- commit は ticket 内で意味のある粒度にする。
|
||||
- 例: `feat: ...`、`fix: ...`、`test: ...`、`docs: ...`
|
||||
- coder Pod は merge / push / branch deletion / worktree remove をしない。
|
||||
- coder Pod は `TODO.md` / ticket の完了処理 commit をしない。
|
||||
- orchestrator は review 時に commit 粒度も確認する。
|
||||
- 必要な修正は、原則追加 commit として積む。履歴改変や squash は人間の明示指示がある時だけ行う。
|
||||
|
||||
## Review → 修正 → 完了の標準形
|
||||
|
||||
### Approve
|
||||
|
||||
1. coder Pod / reviewer Pod を停止し、scope を回収する。
|
||||
2. orchestrator が merge-ready dossier を確認する。
|
||||
3. 最上位 orchestrator が必要最小限の spot check を行う。
|
||||
4. main workspace で `git merge --no-ff <branch>` する。
|
||||
5. `TODO.md` と ticket を完了処理して commit する。
|
||||
6. main workspace で検証コマンドを再実行する。
|
||||
7. 変更内容・commit・検証結果・残 dirty changes を報告する。
|
||||
|
||||
### Request changes
|
||||
|
||||
1. reviewer finding または orchestrator finding を blocker / non-blocker に分ける。
|
||||
2. blocker はファイル / 行 / 理由 / 修正方針つきで coder に戻す。
|
||||
3. coder が停止済みなら、同じ worktree / branch / scope で再 spawn する。
|
||||
4. 修正後に focused test と必要な broader test を再実行する。
|
||||
5. 必要なら reviewer に再確認させる。
|
||||
6. blocker が解消したら dossier を更新する。
|
||||
|
||||
### Non-blocking comments
|
||||
|
||||
- ticket 要件外の改善はその場で混ぜない。
|
||||
- 必要なら後続 ticket / docs/report にする。
|
||||
- non-blocking を理由に completion を遅らせない。
|
||||
|
||||
## 並列実装時の注意
|
||||
|
||||
- 1 ticket = 1 worktree = 1 branch を基本にする。
|
||||
- 複数 Pod に同じ write scope を渡さない。
|
||||
- parent / orchestrator は coder の write scope 配下を直接編集しない。
|
||||
- reviewer は read-only を基本にする。review artifact を書かせる場合は ticket artifacts など限定 scope にする。
|
||||
- 依存関係がある ticket は、土台 branch を merge してから次 worktree を切る。
|
||||
- parallel に走らせた Pod の完了通知は取りこぼしうるため、`ReadPodOutput` と worktree 状態で確認する。
|
||||
|
||||
## merge-ready dossier の標準形
|
||||
|
||||
```text
|
||||
Status:
|
||||
- completed / blocked / needs parent decision
|
||||
|
||||
Scope:
|
||||
- ticket(s): <path>
|
||||
- branch: <name>
|
||||
- commits:
|
||||
- <hash> <subject>
|
||||
|
||||
Intent check:
|
||||
- invariant 1: ok / violated / not applicable, evidence ...
|
||||
- invariant 2: ok / violated / not applicable, evidence ...
|
||||
|
||||
Implementation summary:
|
||||
- 変更の概念的説明: ...
|
||||
- 主要変更ファイル: ...
|
||||
- compatibility / migration: ...
|
||||
|
||||
Coder loop:
|
||||
- coder pod: <name>
|
||||
- produced commits: ...
|
||||
- unresolved coder notes: ...
|
||||
|
||||
External review:
|
||||
- reviewer pod: <name>
|
||||
- blockers found: ...
|
||||
- blockers fixed: ...
|
||||
- rejected findings and reasons: ...
|
||||
- non-blocking follow-ups: ...
|
||||
|
||||
Validation:
|
||||
- cargo fmt --check
|
||||
- cargo check --workspace
|
||||
- cargo test ...
|
||||
- ./tickets.sh doctor
|
||||
|
||||
Parent decision needed:
|
||||
- none / specific question
|
||||
|
||||
Residual risk:
|
||||
- ...
|
||||
|
||||
Dirty state:
|
||||
- ...
|
||||
```
|
||||
|
||||
## 最上位 orchestrator の acceptance
|
||||
|
||||
最上位 orchestrator は、下位 orchestrator の成果を code review するのではなく acceptance する。
|
||||
|
||||
確認するもの:
|
||||
|
||||
- intent packet が保持されているか。
|
||||
- reviewer が coder と独立した sibling として機能したか。
|
||||
- blocker が未解決のまま握りつぶされていないか。
|
||||
- 変更の概念的説明が要件と対応しているか。
|
||||
- validation が ticket のリスクに対して十分か。
|
||||
- escalation すべき判断を下位が勝手に決めていないか。
|
||||
|
||||
必要なら spot check するが、常態的な line-by-line review に戻らない。
|
||||
|
||||
## この Workflow で扱わないもの
|
||||
|
||||
以下は `$user/auto-maintain` または別の設計相談で扱う。
|
||||
|
||||
- ticket 候補を見繕うこと。
|
||||
- 新規 ticket 作成判断。
|
||||
- QA feedback / AI feedback を ticket / report / workflow に落とす判断。
|
||||
- 長期 maintainer loop / WorkItemStore / LeaseStore の設計。
|
||||
- reviewer Pod の品質評価を機械的に採点する仕組み。
|
||||
@@ -1,119 +0,0 @@
|
||||
---
|
||||
description: insomnia プロジェクトで child git worktree を作成・管理するための機械的手順。coder Pod に作らせず、orchestrator Pod が main workspace で実行する。
|
||||
model_invokation: true
|
||||
user_invocable: true
|
||||
requires: []
|
||||
---
|
||||
# Worktree Workflow
|
||||
|
||||
insomnia プロジェクトで実装差分を main workspace から分離するため、`./.worktree/<task-name>` に child git worktree を作る。これは **worktree の扱い方だけ** を定める Workflow であり、ticket 選定、coder / reviewer sibling の起動、外部レビュー、merge の運用は `$user/multi-agent-workflow` 側で扱う。
|
||||
|
||||
insomnia では Pod の write scope が排他的に委譲されるため、child worktree に `.insomnia` を置かない。main workspace は orchestration / ticket / docs / memory / workflow 管理の場所として残し、child worktree はコード差分専用の作業面として扱う。
|
||||
|
||||
## 適用範囲
|
||||
|
||||
この Workflow は親 Pod / 下位 orchestrator が main workspace で実行する。
|
||||
|
||||
- coder Pod にこの Workflow を渡して worktree を作らせない。
|
||||
- coder Pod は、orchestrator が作成済みの child worktree を受け取り、その中で実装・build・test・報告を行う。
|
||||
- reviewer Pod は、coder Pod の子ではなく orchestrator 配下の sibling として、原則 read-only で main workspace と child worktree を読む。
|
||||
- ticket 作成、TODO 更新、review artifact、docs/report は main workspace 側で扱う。
|
||||
|
||||
## 原則
|
||||
|
||||
- 1 ticket / 1 実装 task につき 1 worktree を作る。
|
||||
- 複数 ticket を下位 orchestrator に任せる場合も、実装差分は ticket / bounded task ごとに worktree を分ける。
|
||||
- worktree path は `./.worktree/<task-name>`。
|
||||
- branch 名は原則 `<task-name>` と同じ kebab-case。
|
||||
- child worktree には `.insomnia` を出さない。
|
||||
- child worktree は実装差分用。`TODO.md` / `tickets/` / `docs/report/` / workflow / memory は原則 main workspace 側で扱う。
|
||||
- push はしない。
|
||||
|
||||
## 事前確認
|
||||
|
||||
作成前に以下を確認する。
|
||||
|
||||
1. 対象 ticket / task が決まっているか。
|
||||
2. `<task-name>` が branch / path 名に使える kebab-case か。
|
||||
3. `git worktree add` を実行してよい許可があるか。
|
||||
4. main workspace に混ぜてはいけない未保存差分がないか。
|
||||
5. 同名 branch / worktree が既に存在しないか。
|
||||
6. coder / reviewer を sibling として扱う orchestrator が誰か明確か。
|
||||
|
||||
同名 branch がある場合は、既存 branch を使うか、人間に確認する。`git worktree add -b` で上書きしない。
|
||||
|
||||
## 作成手順
|
||||
|
||||
main workspace で実行する。
|
||||
|
||||
```bash
|
||||
git worktree add .worktree/<task-name> -b <task-name>
|
||||
|
||||
git -C .worktree/<task-name> sparse-checkout init --no-cone
|
||||
git -C .worktree/<task-name> sparse-checkout set --no-cone \
|
||||
'/*' \
|
||||
'!/.insomnia/' \
|
||||
'!/.insomnia/**'
|
||||
```
|
||||
|
||||
確認する。
|
||||
|
||||
```bash
|
||||
git -C .worktree/<task-name> status --short --branch
|
||||
test ! -e .worktree/<task-name>/.insomnia
|
||||
```
|
||||
|
||||
失敗した場合は、worktree / branch / lock の状態を確認し、勝手に cleanup せず人間へ報告する。
|
||||
|
||||
## Pod へ渡す scope
|
||||
|
||||
Pod を使う場合、Pod の cwd は main workspace のままになる。必ず作業対象が child worktree であることを明示し、Bash 実行時は毎回 `cd <repo>/.worktree/<task-name> && ...` させる。
|
||||
|
||||
coder Pod 推奨 scope:
|
||||
|
||||
```text
|
||||
read: <repo>
|
||||
write: <repo>/.worktree/<task-name>
|
||||
```
|
||||
|
||||
reviewer Pod 推奨 scope:
|
||||
|
||||
```text
|
||||
read: <repo>
|
||||
read: <repo>/.worktree/<task-name> # main workspace の read に含まれるなら別指定不要
|
||||
```
|
||||
|
||||
reviewer は原則 write scope を持たない。review artifact を書かせる必要がある場合だけ、ticket artifacts など限定 directory を write scope として渡す。
|
||||
|
||||
より狭く切れる場合は、coder の write scope を変更対象 crate / directory まで狭めてよい。ただし build / test に必要な生成物を書けることを確認する。
|
||||
|
||||
## child worktree 内の禁止事項
|
||||
|
||||
- `.insomnia` を作らない / コピーしない。
|
||||
- main workspace の `TODO.md` / `tickets/` / `docs/report/` を編集しない。
|
||||
- merge / push / branch deletion / worktree remove をしない。
|
||||
- scope / permission / history persistence / prompt context 加工原則に関わる設計変更を無断で行わない。
|
||||
|
||||
## 完了時の扱い
|
||||
|
||||
worktree 作成 Workflow としては、完了時に merge しない。merge、ticket 完了、TODO 削除は `$user/multi-agent-workflow` または人間の明示指示で行う。
|
||||
|
||||
coder Pod へ渡す完了報告項目の標準形:
|
||||
|
||||
- worktree path
|
||||
- branch 名
|
||||
- commit hash(coder Pod に commit を許可した場合)
|
||||
- 変更ファイル
|
||||
- 実装概要
|
||||
- 実行した build / test / format
|
||||
- 未解決事項
|
||||
- review に回せるか
|
||||
|
||||
reviewer Pod へ渡す完了報告項目の標準形:
|
||||
|
||||
- 読んだ ticket / intent packet / diff
|
||||
- 実際のコード変更の概念的説明
|
||||
- intent / requirements / invariant との対応
|
||||
- blocker / non-blocker / follow-up
|
||||
- validation の妥当性
|
||||
- 親または上位 orchestrator が判断すべき残論点
|
||||
@@ -0,0 +1,4 @@
|
||||
/memory/
|
||||
tickets/.ticket-backend.lock
|
||||
/workspace.db*
|
||||
tickets
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: "E2E テスト戦略"
|
||||
state: "active"
|
||||
created_at: "2026-06-09T07:09:26Z"
|
||||
updated_at: "2026-06-09T07:09:26Z"
|
||||
linked_tickets: ["00001KSKBP9YG"]
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Yoi の実プロセス・実 socket・実 provider 境界をまたぐ振る舞いを、通常の crate 内 unit / integration test だけに頼らず検証できる E2E テスト戦略を確立する。
|
||||
|
||||
最初の到達点は、実 `pod` / product binary を spawn し、protocol 経由で最小シナリオを実行し、graceful shutdown まで確認できる opt-in E2E harness を持つこと。その上で、permission、resume/fork、spawned Pod、provider stream、TUI/Panel などの重要境界を段階的に増やせる状態にする。
|
||||
|
||||
## Motivation / background
|
||||
|
||||
現状のテストは crate 内の in-process coverage が厚い一方で、以下の性質は単体テストだけでは十分に確認しづらい。
|
||||
|
||||
- 実プロセス spawn と runtime dir / socket / env の相互作用。
|
||||
- Pod controller / protocol client / session store / metadata / restore の統合挙動。
|
||||
- provider endpoint、streaming、auth/token、tool call、continuation、retry の実接続に近い振る舞い。
|
||||
- permission deny、scope、manifest/profile 解決、child Pod delegation など、複数 crate と実 runtime state をまたぐ policy。
|
||||
- TUI / Panel が前提にする Pod lifecycle や Ticket orchestration の外形。
|
||||
|
||||
E2E は常時実行の軽いテストではなく、dogfooding 中に「この機能は実 runtime でも壊れていない」と確認するための opt-in 検証基盤として必要。
|
||||
|
||||
## Strategy / design direction
|
||||
|
||||
- E2E は通常の `cargo test --workspace` からは外し、明示 feature / 専用 package / 独立 job で opt-in 実行する。
|
||||
- まずはワークスペース直下の専用 E2E package / harness として設計し、個別 crate の unit test に押し込めない。
|
||||
- protocol を喋る側は TUI の PTY 操作ではなく、typed client / protocol client を使う方向を優先する。
|
||||
- provider 依存は最初から全部を対象にしない。
|
||||
- 最小 harness では canned / fixture / mock HTTP server を使う。
|
||||
- provider 差分は代表 provider から段階的に増やす。
|
||||
- env / runtime dir / socket path は test ごとに隔離し、並列実行方針を明示する。
|
||||
- 必要なら最初は `--test-threads=1` 相当で安全側に倒す。
|
||||
- 将来的には per-test runtime dir と typed launch config で並列性を上げる。
|
||||
- E2E は「全シナリオを大量に持つ」より、重要な runtime seam ごとに少数の高価値 scenario を置く。
|
||||
- 失敗時 diagnostics は、secret を出さずに process phase、socket path、session id、log path、provider fixture id を辿れる形にする。
|
||||
- E2E harness 自体が flaky にならないよう、network / time / external auth への依存は明示 opt-in に分ける。
|
||||
|
||||
## Success criteria / exit conditions
|
||||
|
||||
- `cargo test --workspace` では E2E が走らず、通常開発の feedback loop を重くしない。
|
||||
- 明示コマンドで E2E harness を実行できる。
|
||||
- 例: `cargo test -p e2e --features e2e` または後続で決める同等コマンド。
|
||||
- 最小 scenario が実 `pod` / product binary を spawn し、protocol 経由で 1 turn 実行し、graceful shutdown まで通る。
|
||||
- E2E 実行は専用 runtime/data dir を使い、通常の user/workspace state を汚さない。
|
||||
- fixture / mock provider の設計があり、少なくとも 1 provider 相当の canned response を実 HTTP 経由で返せる。
|
||||
- failure diagnostics から、spawn 失敗・socket 接続失敗・provider fixture 失敗・protocol 失敗・shutdown 失敗を区別できる。
|
||||
- 後続 Ticket が permission / resume / fork / spawned Pod / provider streaming / Panel などを追加できる harness boundary がある。
|
||||
|
||||
## Decision context
|
||||
|
||||
- linked Ticket `00001KSKBP9YG` は、E2E harness の最初の concrete implementation Ticket として扱う。
|
||||
- この Objective は E2E 全体の中長期方針・判断軸を保持する。個別 scenario の実装や harness の細部は concrete Ticket に分ける。
|
||||
- TUI を直接 PTY で叩く方針は初期 harness では避け、protocol/client 経由を優先する。
|
||||
- provider 全対応は初期 scope にしない。fixture / mock HTTP server を基礎にし、代表 provider から段階的に広げる。
|
||||
- E2E は flakiness と実行コストが高いため、既定 CI / 既定 workspace test には入れず、opt-in 検証として始める。
|
||||
- Objective context は判断材料であり、実装 authority は各 Ticket の body/thread/artifacts と明示的な Ticket relation / OrchestrationPlan に置く。
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "ネイティブGUIアプリケーション"
|
||||
state: "active"
|
||||
created_at: "2026-06-10T07:41:18Z"
|
||||
updated_at: "2026-07-15T21:18:00Z"
|
||||
linked_tickets: []
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Yoi の Pod / Ticket / Orchestrator / Skill・prompt resource 操作を、TUI だけでなくネイティブ GUI から扱えるようにする。
|
||||
|
||||
最初の到達点は、既存の runtime / Ticket backend / Pod protocol / Profile / Skill/prompt resource authority を再実装せずに、workspace の状態を視覚的に把握し、選択した Pod・Ticket・role action に対して安全に操作できる desktop GUI client を持つこと。GUI は core authority ではなく client surface とし、既存 CLI/TUI と同じ durable state・同じ protocol・同じ permission/prompt/resource 境界を使う。
|
||||
|
||||
## Motivation / background
|
||||
|
||||
現在の TUI / Panel は dogfooding 可能な状態まで進んでいるが、複数 Pod・複数 Ticket・Orchestrator 状態・session output・role launch・review/merge dossier を同時に扱うには、terminal UI の表示密度・視覚的状態表現・スクロール/選択/比較操作に限界が出ている。
|
||||
|
||||
ネイティブ GUI があると、以下をより自然に扱える。
|
||||
|
||||
- live / stored Pod、Ticket lane、Orchestrator progress、Companion/Intake 状態の同時表示。
|
||||
- Ticket body/thread/artifacts、Pod output、validation evidence、diff/report の並列閲覧。
|
||||
- composer target、role action、queue/routing/attach/restore の明確な affordance。
|
||||
- long-running orchestration の通知、状態変化、失敗診断の視覚化。
|
||||
- 将来的な review / merge-ready dossier / plan board / settings editor の専用 UI。
|
||||
|
||||
一方で、GUI を理由に runtime authority を分散させたり、Ticket/Pod state を独自 DB として二重管理したり、prompt/resource 文字列を GUI code に直書きしたりしてはいけない。
|
||||
|
||||
## Strategy / design direction
|
||||
|
||||
- GUI は Yoi core の上に乗る client として作る。
|
||||
- Pod lifecycle、session/history、Ticket storage、Profile resolution、resource/prompt authority は既存 core を正とする。
|
||||
- GUI 固有 state は selection、layout、local UI preference などに限定する。
|
||||
- 最初に toolkit / architecture の小さな spike を置く。
|
||||
- 評価軸は Rust code reuse、async/runtime 統合、native packaging、Linux dogfooding しやすさ、testability、accessibility、long-running log/output 表示、将来の cross-platform 余地。
|
||||
- toolkit 選定は Objective では固定しない。候補比較と採用理由を Ticket に残す。
|
||||
- 実装は段階的に進める。
|
||||
1. read-only workspace dashboard: Pod / Ticket / Orchestrator 状態を既存 backend から表示する。
|
||||
2. attach / restore / open など、既存 protocol に乗る低リスク操作を追加する。
|
||||
3. composer と role action: Companion / Intake / Orchestrator / coder / reviewer launch を既存 launcher 経由で扱う。
|
||||
4. Ticket body/thread/artifacts、Pod output、validation evidence、review report を閲覧しやすくする。
|
||||
5. 必要に応じて settings/profile/config editor や merge-ready dossier UI を追加する。
|
||||
- TUI は廃止前提にしない。
|
||||
- GUI 導入後も CLI/TUI は fallback / automation / terminal-first operation flow として維持する。
|
||||
- GUI で見つかった state model の改善は、TUI と共有できる pure data model / client API に寄せる。
|
||||
- Prompt / resource / role guidance は GUI code に直書きしない。
|
||||
- LLM-facing prompt は `resources/prompts` または `.yoi/skills` / configured resources を正とする。
|
||||
- GUI は prompt 文言を所有せず、選択・起動・runtime context の入力面を担当する。
|
||||
- Security / privacy / authority boundary を保つ。
|
||||
- secret-like data は UI diagnostics / logs / model context に漏らさない。
|
||||
- permission / scope / profile の authority は既存 resolver/policy に従う。
|
||||
- GUI convenience action は durable Ticket/Pod state transition と対応付け、暗黙の side effect を避ける。
|
||||
|
||||
## Success criteria / exit conditions
|
||||
|
||||
- 明示コマンドまたは binary で native GUI を起動できる。
|
||||
- GUI は既存 workspace config、Profile、Ticket backend、Pod registry/protocol を使い、独自の authority store を持たない。
|
||||
- 最小 dashboard で live/stored Pod、Ticket lane/state、Orchestrator/role session の概況を確認できる。
|
||||
- GUI から少なくとも attach/restore/open 相当の安全な Pod 操作ができる。
|
||||
- GUI から Ticket Intake または既存 role launcher を使った role action を実行でき、既存の prompt/resource 境界を壊さない。
|
||||
- Pod output / Ticket body/thread/artifacts を、TUI より見通しよく閲覧できる最小 UI がある。
|
||||
- GUI 固有 state と core durable state の境界が文書化されている。
|
||||
- toolkit / architecture 選定理由、採用しなかった選択肢、packaging 方針が Ticket artifact または design note として残っている。
|
||||
- GUI で使う state transformation / action eligibility は pure model として test 可能で、主要な selection/action state の unit test がある。
|
||||
- GUI 実装は CLI/TUI の既存 operation flow を破壊せず、必要な targeted validation が定義されている。
|
||||
|
||||
## Decision context
|
||||
|
||||
- この Objective は中長期の方向性・判断軸を保持する。具体的な toolkit 選定、crate 構成、初期 dashboard 実装、role action 実装、packaging は個別 Ticket に分ける。
|
||||
- GUI は TUI の単純な置換ではなく、複数 Pod / Ticket / Orchestrator を扱う workspace cockpit として設計する。
|
||||
- authority は既存 core に残す。GUI は client/view/controller surface であり、Pod/Ticket/resource/prompt の正本を所有しない。
|
||||
- Prompt 直書き禁止方針を守る。GUI 実装中に LLM-facing 文言が必要になった場合は、`resources/prompts` または Skill/resource 側に置く。
|
||||
- 初期 target は dogfooding しやすい desktop GUI とし、public release / cross-platform polish / installer は後続段階で扱う。
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
title: 'MCP local stdio integration roadmap'
|
||||
state: 'active'
|
||||
created_at: '2026-06-10T07:48:45Z'
|
||||
updated_at: '2026-06-20T05:34:00Z'
|
||||
linked_tickets: ['00001KTR81P9X', '00001KV0SP0TY', '00001KVHR3WRF', '00001KVHR3WRY', '00001KVHR3WS6', '00001KVHR3WSD', '00001KVHR3WSN', '00001KVHR3WSW']
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Add MCP local stdio integration to Yoi without weakening Worker history, prompt-context, scoped tool permission, or Plugin/Feature layering invariants.
|
||||
|
||||
MCP is a protocol-backed integration layer on top of `pod::feature`. `pod::feature` supplies contribution/lifecycle/runtime-discovered registration substrate; MCP owns its own enablement, local server trust model, command/env/secret policy, and MCP-specific permission decisions. MCP is not the Plugin model, and Plugin permission policy is not implemented by feature-layer authority grants.
|
||||
|
||||
## Motivation / background
|
||||
|
||||
Yoi needs to integrate with external capability providers without turning them into hidden context sources or bypassing ordinary Tool/Worker safety rules. MCP is useful because it can expose tools, resources, and prompts from local protocol servers, but those server-provided declarations and results are untrusted and must be normalized through Yoi's existing authority boundaries.
|
||||
|
||||
The first MCP slice should focus on local stdio servers because they are concrete enough to implement and debug while keeping remote auth, OAuth, Streamable HTTP, registry distribution, sampling, and elicitation out of the initial trust boundary.
|
||||
|
||||
A configured local MCP server runs as a local executable. Yoi feature authority does not sandbox that executable's OS-level side effects, so command/env/secret handling and explicit local trust policy are MCP-layer responsibilities rather than generic `pod::feature` grants.
|
||||
|
||||
## Strategy / design direction
|
||||
|
||||
- Baseline the initial implementation on MCP specification `2025-11-25`.
|
||||
- Start with local stdio MCP servers only.
|
||||
- Treat MCP server metadata, tools, resources, prompts, and results as untrusted content.
|
||||
- Do not allow MCP resources/prompts to become hidden context injection.
|
||||
- They must be explicit tool operations with history records.
|
||||
- Use the normal Yoi ToolRegistry, PreToolCall permission, history, and bounded result paths.
|
||||
- Do not add private MCP-only bypasses around Worker/tool invariants.
|
||||
- Keep sampling and elicitation fail-closed initially.
|
||||
- Keep Streamable HTTP, remote auth, OAuth, and MCP Registry/distribution out of the first slice.
|
||||
- Treat local stdio server execution as an explicit MCP config/trust decision, not as a `pod::feature` authority grant.
|
||||
- Document clearly that a configured local MCP server runs as a local executable; Yoi feature authority does not sandbox its OS-level side effects.
|
||||
|
||||
### Layering decisions
|
||||
|
||||
- `pod::feature` is an API/contribution substrate.
|
||||
- It owns contribution declarations, provider/service lifecycle hooks, diagnostics, runtime-discovered registration plumbing, and integration with normal Worker/ToolRegistry paths.
|
||||
- It does not own Plugin permission policy or MCP server trust policy.
|
||||
- Plugin is a user-facing package/config/runtime layer over `pod::feature`.
|
||||
- Plugin permissions are Plugin-layer policy.
|
||||
- Plugin package discovery/enablement must not be conflated with MCP local server execution.
|
||||
- MCP is a separate feature-backed integration layer.
|
||||
- MCP enablement, command/env/secret handling, server trust, and MCP-specific permission decisions live in MCP config/implementation.
|
||||
- MCP provider-discovered tools/resources/prompts are exposed through the feature API and ordinary Yoi tool paths.
|
||||
|
||||
### Concrete implementation tickets
|
||||
|
||||
Completed prerequisites:
|
||||
|
||||
- `00001KTR81P9X` — Extend `pod::feature` API for external protocol-backed capability providers.
|
||||
- `00001KV0SP0TY` — Remove feature-layer HostAuthority model.
|
||||
|
||||
Concrete MCP implementation sequence:
|
||||
|
||||
1. `00001KVHR3WRF` — MCP local stdio server config and trust policy.
|
||||
- explicit config, command/env/secret redaction, local executable trust boundary, no auto-start.
|
||||
2. `00001KVHR3WRY` — MCP stdio JSON-RPC lifecycle client.
|
||||
- subprocess lifecycle, initialize/capability negotiation, diagnostics, shutdown.
|
||||
3. `00001KVHR3WS6` — MCP tools/list registration into ToolRegistry.
|
||||
- provider-discovered tools, stable namespacing, schema validation, untrusted metadata normalization, no tools/call yet.
|
||||
4. `00001KVHR3WSD` — MCP tools/call execution through ordinary Tool path.
|
||||
- PreToolCall gate before server call, bounded result serialization, history path.
|
||||
5. `00001KVHR3WSN` — MCP resources/prompts as explicit tool operations.
|
||||
- resources/list/read and prompts/list/get without hidden context injection.
|
||||
6. `00001KVHR3WSW` — MCP list_changed notification handling.
|
||||
- deterministic safe refresh/diagnostic behavior without breaking tool schema or prompt-cache invariants.
|
||||
|
||||
The old broad implementation Ticket `00001KTR82RB7` is superseded by this sequence and should not be used as an implementation work item.
|
||||
|
||||
### Terminology
|
||||
|
||||
Use `runtime-discovered` or `provider-discovered` for MCP tools/resources/prompts discovered from `tools/list`, `resources/list`, or `prompts/list`. Avoid `dynamic tools` / `dynamic registry` in new MCP design prose because those phrases imply that model-visible tool schemas may change during an active LLM run.
|
||||
|
||||
The intended invariant is:
|
||||
|
||||
```text
|
||||
provider-discovered at startup / provider initialization;
|
||||
registered into the ordinary ToolRegistry before model exposure;
|
||||
run-stable for the duration of a model request/run;
|
||||
refreshed only at a safe boundary or reported as a diagnostic.
|
||||
```
|
||||
|
||||
### Later follow-ups
|
||||
|
||||
- Richer MCP task/task-support integration if ordinary tool-call fallback is insufficient.
|
||||
- Streamable HTTP transport.
|
||||
- OAuth / remote auth.
|
||||
- Registry/package distribution.
|
||||
- Explicit MCP/Plugin bridge only if separately approved; do not conflate Plugin packages with MCP local server execution.
|
||||
|
||||
## Success criteria / exit conditions
|
||||
|
||||
- A local mock MCP server can be configured explicitly and initialized.
|
||||
- Discovered MCP tools appear as ordinary Yoi tools with stable namespacing.
|
||||
- Tool calls go through ordinary permission and history paths.
|
||||
- MCP resources/prompts are explicit operations, not hidden context injections.
|
||||
- MCP result forms are bounded and safely serialized.
|
||||
- Secret values, command/env details, and server diagnostics are redacted where required.
|
||||
- Local server trust boundary is documented: Yoi does not sandbox the configured executable through feature authority.
|
||||
- Feature, Plugin, and MCP permission/trust responsibilities are documented as separate layers.
|
||||
|
||||
## Decision context
|
||||
|
||||
- MCP is not the Plugin model; it is a protocol-backed integration layer using `pod::feature` substrate.
|
||||
- `pod::feature` should provide contribution/lifecycle/runtime-discovered registration plumbing, not MCP server trust policy or Plugin package permission policy.
|
||||
- MCP resources and prompts must never be hidden context injection. They are explicit operations recorded through ordinary history/tool paths.
|
||||
- Provider-discovered tools are discovered at startup/provider initialization and registered before model exposure; model-visible schemas remain run-stable during a request/run.
|
||||
- Local stdio server execution is a user/config trust decision. Yoi does not sandbox the local executable merely because it is configured through MCP.
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: "Plugin platform roadmap"
|
||||
state: "active"
|
||||
created_at: "2026-06-19T13:18:58Z"
|
||||
updated_at: "2026-06-24T19:55:00Z"
|
||||
linked_tickets: ["00001KV5R5V2S", "00001KV5W3PHA", "00001KV5W3PHW", "00001KV5W3PJ3", "00001KVFD3YSV", "00001KVFDX9AF", "00001KVFDX9AY", "00001KVG0HR96", "00001KVXHVCR5", "00001KVXK0WD3", "00001KVXK0WDH", "00001KVXK0WDQ", "00001KVXK0WDX", "00001KVXK0WE4", "00001KVXK0WEA"]
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Build Yoi's Plugin platform as a coherent extension system: packages are discovered and inspected safely, enabled explicitly, registered through typed Plugin surfaces, executed in a sandboxed runtime, constrained by Plugin-layer grants, and authored through SDK/templates rather than raw runtime ABI details.
|
||||
|
||||
The long-term platform goal is not merely to run Wasm. It is to make Plugin packages a durable, inspectable, permissioned, and authorable extension layer for Tools first, then host APIs (`https`, `fs`), and later Service / Ingress surfaces when concrete needs justify them.
|
||||
|
||||
## Motivation / background
|
||||
|
||||
The current Plugin foundation is already substantial:
|
||||
|
||||
- package discovery and explicit enablement resolver;
|
||||
- Tool surface registration through the ordinary ToolRegistry/model-visible schema path;
|
||||
- minimal sandboxed WASM Tool execution;
|
||||
- Plugin permission grant enforcement;
|
||||
- follow-up Tickets for read-only inspection CLI, `https`, `fs`, and Component Model migration.
|
||||
|
||||
The remaining work must be kept as one roadmap because the pieces constrain each other:
|
||||
|
||||
- Plugin authoring needs an SDK/PDK and examples, not raw pointer/length Wasm ABI hand-coding.
|
||||
- `https` and `fs` host APIs must be grant-gated and shaped so they can move cleanly to typed Component Model interfaces.
|
||||
- Diagnostics (`yoi plugin list/show`) are needed before the system becomes harder to debug.
|
||||
- Component Model adoption should guide new host API design before a custom raw ABI becomes entrenched.
|
||||
- Service / Ingress are useful for bridge-style integrations, but should come after Tool runtime, diagnostics, and host API policy are stable.
|
||||
|
||||
Research of common Wasm extension systems points to the same pattern: mature systems combine a package manifest, explicit capabilities, a sandbox runtime, host-provided capability APIs, language SDK/PDK bindings, templates/examples, inspection/check tooling, and versioned interfaces.
|
||||
|
||||
## Strategy / design direction
|
||||
|
||||
- Keep Plugin as a user-facing package/config/runtime layer above lower-level `pod::feature` substrate.
|
||||
- `pod::feature` provides contribution/registration substrate.
|
||||
- Plugin owns package discovery, enablement, grant policy, runtime selection, authoring UX, and user-facing diagnostics.
|
||||
- Preserve authority boundaries.
|
||||
- Package discovery is read-only inventory.
|
||||
- Package presence never registers a Tool/Hook, executes Wasm, starts a Service, reads files, opens network, or injects context.
|
||||
- Explicit enablement and Plugin grants are required before registration/execution/host API use.
|
||||
- Tool calls/results continue through ordinary ToolRegistry and Worker history paths.
|
||||
- Treat Component Model as the active Plugin runtime shape before public release.
|
||||
- New typed Plugin host APIs should be designed in WIT-compatible terms.
|
||||
- `runtime.kind = "wasm-component"` is the current Plugin runtime authority for new work.
|
||||
- The earlier `yoi-plugin-wasm-1` raw core-Wasm compatibility bridge is retired from the active roadmap because Plugin has not been publicly released and compatibility would preserve the wrong boundary.
|
||||
- Sequence the platform in usable layers:
|
||||
1. Package discovery / explicit enablement / digest-pinned restore. Completed foundation.
|
||||
2. Tool surface registration. Completed foundation.
|
||||
3. Minimal WASM Tool execution. Completed foundation.
|
||||
4. Permission grants. Completed foundation.
|
||||
5. Read-only Plugin CLI inspection (`yoi plugin list/show`) for debugging discovery/enablement/grants/runtime metadata.
|
||||
6. `https` and `fs` host APIs for Tool Plugins, grant-gated and WIT-compatible.
|
||||
7. Remove the raw core-Wasm compatibility bridge and reject legacy runtime manifests.
|
||||
8. Component Model runtime and authoring model become the only active Plugin runtime path.
|
||||
9. Guest SDK/PDK, examples, `check`/`pack`/`new` authoring tooling target Component Model only.
|
||||
10. Service / Ingress runtime is developed as host-managed lifecycle, event queue, output command, and diagnostics slices.
|
||||
11. WebSocket support for long-lived integrations is host-owned connection driver + ingress event delivery + output command, not Plugin-owned polling with `recv(timeout)`.
|
||||
- Keep Discord-style bridge goals split into two stages.
|
||||
- Outbound Discord/webhook Tool is possible after `https`.
|
||||
- Bidirectional Discord bridge requires Service + Ingress + WebSocket or inbound HTTP and host routing policy.
|
||||
|
||||
## Current implementation split
|
||||
|
||||
The broad Plugin runtime redesign is tracked by `00001KVXHVCR5` as context only; implementation should proceed through concrete Tickets instead of routing that umbrella as a single coding task.
|
||||
|
||||
1. `00001KVXK0WD3` Remove legacy raw WASM Plugin runtime.
|
||||
- Deletes the active `LegacyToolAdapter` / raw-Wasm execution path.
|
||||
2. `00001KVXK0WDH` Reject legacy Plugin runtime in manifest and CLI diagnostics.
|
||||
- Makes `plugin.toml`, `yoi plugin check/list/show`, docs, and fixtures reflect Component Model only runtime authority.
|
||||
3. `00001KVXK0WDQ` Define Plugin Service lifecycle and ingress queue runtime.
|
||||
- Adds host-managed lifecycle, bounded queue, serial dispatch, backpressure, timeout, and diagnostics.
|
||||
4. `00001KVXK0WDX` Add Plugin service output command model.
|
||||
- Lets service handlers request side effects as grant-checked commands rather than ambient authority.
|
||||
5. `00001KVXK0WE4` Add host-owned WebSocket driver for Plugin services.
|
||||
- Converts incoming WS frames into ingress events and sends outbound frames through output commands.
|
||||
6. `00001KVXK0WEA` Update Plugin WIT PDK templates for service event runtime.
|
||||
- Aligns authoring API, WIT, PDK, templates, and docs with the new event/command execution model.
|
||||
|
||||
## Success criteria / exit conditions
|
||||
|
||||
- Users can inspect Plugin discovery/enablement/grant/runtime state through a read-only CLI without executing Plugin code.
|
||||
- Plugin authors can build a Tool Plugin without writing raw memory/pointer ABI plumbing.
|
||||
- Tool Plugins can safely call grant-gated `https` and `fs` host APIs.
|
||||
- Component Model is the only active Plugin runtime path, with WIT-compatible host API types and measured packaging/runtime impact.
|
||||
- Plugin grants remain authoritative over registration, execution, and host API calls.
|
||||
- Plugin diagnostics explain missing package, invalid manifest, digest/version mismatch, missing grant, rejected schema, runtime mismatch, legacy runtime rejection, and unsupported host API cases safely.
|
||||
- Raw core-Wasm Plugin compatibility is removed before public release; tests and docs no longer treat it as a current runtime.
|
||||
- Documentation covers package format, Component Model runtime, host API authority, authoring SDK/templates, Service/Ingress event runtime, and operational debugging.
|
||||
- Service/Ingress work is host-managed: services have lifecycle/status, ingress uses bounded queues, side effects are output commands, and WebSocket integrations use host-owned connection drivers.
|
||||
|
||||
## Decision context
|
||||
|
||||
- This Objective is roadmap context, not Ticket authority. Implementation still requires reading concrete Ticket bodies, threads, artifacts, and relations.
|
||||
- Component Model direction supersedes Yoi's custom raw ABI as both long-term and current active Plugin runtime authority before public release.
|
||||
- `https` / `fs` work should avoid choices that conflict with later WIT typed interfaces.
|
||||
- Guest SDK work targets Component Model directly; raw ABI wrappers are not a supported transitional authoring path.
|
||||
- Plugin and MCP remain separate. Component Model adoption for Plugin does not imply MCP server execution, MCP prompt/resource injection, or MCP trust policy changes.
|
||||
- Plugin surfaces remain Tool / Hook / Service / Ingress; outbound side effects are Tool metadata and host API grants, not a separate surface.
|
||||
@@ -0,0 +1,287 @@
|
||||
---
|
||||
title: "Team workspace control plane and runtime architecture"
|
||||
state: "active"
|
||||
created_at: "2026-06-20T14:26:29Z"
|
||||
updated_at: "2026-07-15T21:18:00Z"
|
||||
linked_tickets: ["00001KVMFFYVX", "00001KWMBAA6V"]
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Yoi を、単一のローカル開発ディレクトリで動くエージェント実行ツールから、チームで作業・判断・実行結果を管理できるワークスペース基盤へ発展させる。
|
||||
|
||||
この Objective の中心は、Web から扱える管理システムを作り、その管理システムにローカル Runtime・リモート Runtime・将来のクラウド Runtime を接続できるようにすることである。管理システムは Ticket、Objective、Memory、Skill catalog、Artifact、Policy、Actor、Repository、Runtime state の正本を持つ。Runtime はその管理システムから Worker launch request / config bundle / repository target / authority を受け取り、作業環境を用意して Worker を実行し、結果・イベント・証跡を返す。
|
||||
|
||||
この Objective は Git ホスティングサービスを作るものではない。Git は重要な Repository provider として扱うが、Yoi の Workspace は Git Repository root と同じものにしない。Yoi が作るべきものは、コード・ドキュメント・データ・成果物などの Repository と Runtime を接続しながら、人間とエージェントの作業、Ticket lifecycle、Memory、Skill catalog、検証証跡、実行環境配置を管理するチームワークスペースである。
|
||||
|
||||
## Glossary
|
||||
|
||||
この Objective では、以下の語をこの意味で使う。
|
||||
|
||||
- Workspace: チームまたはプロジェクトの管理単位。Ticket、Objective、Memory、Skill catalog、Artifact、Policy、Actor、Repository、Runtime state を持つ。Git Repository root ではない。
|
||||
- Control plane: Workspace の正本を持ち、Web UI / API / CLI から操作される管理システム。
|
||||
- Runtime: Worker 群を束ねる実行基盤。Worker lifecycle、sandbox、mount、cache、checkout/worktree/container filesystem などの working directory materialization、event/control plane を管理する。将来的には 1 つの Runtime が複数 Workspace / Repository の Worker を抱えられる。
|
||||
- Worker: Runtime が管理する 1 つの agent/session/process。Runtime が用意した working directory と authority の中で動く。
|
||||
- Repository: Workspace に接続される source/storage。コード、ドキュメント、local directory、object storage、artifact store、dataset などを含む。Git Repository も Repository の一種であり、基本的には filesystem path ではなく URI / URL で識別する。
|
||||
- RepositoryId: Workspace 内でどの Repository を対象にするかを指す安定 identifier。Git hash、branch、path ではない。
|
||||
- Repository provider: Repository の種類ごとの実装。Git、local filesystem、object store、artifact store、将来の non-Git VCS など。
|
||||
- RepositorySelector: Repository provider に渡す未解決の地点指定。branch/tag/PR/revspec/bookmark/revset/path@revision/object version/latest など provider-specific な symbolic / mutable / query-like locator であり、それ自体は再現性の authority ではない。
|
||||
- RepositoryPoint: RepositorySelector をある時点で解決した具体地点。Git commit/tree、Mercurial changeset、SVN revision、object store version/manifest digest、file snapshot など provider ごとの immutable / reproducible point を表し、Artifact/evidence に残す。
|
||||
- working directory: Runtime が Worker のために作る作業環境。1 つ以上の RepositoryPoint から materialize される作業用ディレクトリ、container filesystem、sandbox mount の集合であり、Git worktree、clone、sparse checkout などはこれを作る手段である。Browser-facing UI/API では product `Workspace` と混同しないよう、この呼称に寄せる。`Volume` は storage backing の候補名に留め、作業領域そのものの呼称にはしない。
|
||||
- Ticket: チームで扱う作業単位。目的、要件、判断、議論、完了条件、関係、証跡を持つ。
|
||||
- Objective: 複数の Ticket を束ねる長期目標や設計方針。
|
||||
- Artifact: Ticket や Worker 実行に紐づく成果物や証跡。diff、log、validation result、review result、report など。
|
||||
- Memory: エージェントやユーザーが再利用するための要約された文脈。Ticket や Artifact の正本ではない。
|
||||
- Skill catalog: `.yoi/skills` / builtin skills から Workspace backend が解決する procedural guidance catalog。外部状態 authority は持たず、Ticket / Worker / workdir などの操作は typed feature/tool surface が担う。
|
||||
- Actor: 人間、エージェント、システム、外部サービスなど、Workspace 上で操作や発言を行う主体。
|
||||
|
||||
## Motivation / background
|
||||
|
||||
現在の Yoi は、ローカルの `.yoi` ディレクトリ、ローカルプロセス、Ticket ファイル、ワークツリー運用によって、自分自身の開発に使えるエージェント実行環境になっている。しかし、チーム利用、Web UI、リモート実行、クラウド実行、最終的な SaaS 提供を考えると、次の前提を変える必要がある。
|
||||
|
||||
- Workspace を Git Repository root と同一視しない。
|
||||
- ローカル filesystem 上の `.yoi` を、長期的なチーム用正本 store にしない。
|
||||
- Ticket をローカル作業メモではなく、チームの作業調整 record にする。
|
||||
- 実行証跡は Ticket thread、Artifact、WorkerRef snapshot、Runtime event として扱い、独立した実行単位概念を先に増やさない。
|
||||
- 管理システムと Runtime を分ける。
|
||||
- まず Web から Ticket、Objective、Memory、Skill catalog、Artifact、Runtime / Worker state を見られるようにする。
|
||||
- 最初はローカル Runtime を使い、後でリモート Runtime、クラウド Runtime、runtime pool、resource allocation、quota、billing、sandboxing に拡張する。
|
||||
- Git ホスティング機能を取り込むのではなく、Git Repository / worktree / clone は Repository provider と working directory materialization の手段として扱う。
|
||||
|
||||
OSS として Control plane、Runtime、Web frontend、protocol を公開しつつ、managed service では hosted control plane、runtime fleet、リソース柔軟性、team auth、backup、audit、availability、multi-tenant operations で価値を出す。
|
||||
|
||||
## Strategy / design direction
|
||||
|
||||
### 1. Control plane を先に作る
|
||||
|
||||
Team Workspace の正本は server-side control plane に置く。`.yoi` は local backend、single-user/self-hosted compatibility、offline/export/import、local projection、migration bridge として残せるが、multi-user SaaS の正本とはみなさない。
|
||||
|
||||
Control plane は Ticket、Objective、Memory、Skill catalog、Artifact、Actor、Permission、Audit、Repository、Runtime / Worker state を管理する。Web UI、CLI、TUI、将来の desktop client は、この Control plane を操作する client であり、別の正本 store を持たない。
|
||||
|
||||
### 2. Workspace と Repository を同一視しない
|
||||
|
||||
Workspace はチームまたはプロジェクトの作業管理単位である。Repository は Workspace に接続される source/storage である。Git Repository は Repository の一種にすぎない。
|
||||
|
||||
1 つの Workspace は複数の Repository を持てる。Repository は filesystem path ではなく URI / URL で識別する。例として `git+https://...`、`file://...`、`s3://...`、`artifact://...`、将来の VCS provider URI などを扱えるようにする。
|
||||
|
||||
Ticket と Objective は Repository 配下に置かず、Workspace 配下に平たく持つ。Ticket は必要に応じて対象 RepositoryId、RepositorySelector、path scope、必要 capability を持つ。Objective は複数 Ticket にまたがる target default / scope hint を持てるが、Repository の所有物にはしない。
|
||||
|
||||
RepositorySelector は Git branch/tag の抽象化ではない。Selector は provider-specific な未解決 locator であり、Git provider なら branch/tag/ref/revspec/PR/commit、Mercurial provider なら bookmark/revset/changeset、SVN provider なら path/revision、object store provider なら prefix/version/latest などを解釈する。実行時には Control plane または Repository provider が Selector を RepositoryPoint に解決し、Runtime はその RepositoryPoint を materialize する。
|
||||
|
||||
Worker launch request は Ticket の target selector を concrete RepositoryPoint に解決し、その RepositoryPoint から Runtime が working directory を materialize する。Git worktree 相当の機能は、この working directory を作るための実装戦略として扱う。
|
||||
|
||||
Backend は cwd や `--workspace` を暗黙の Repository として扱わない。`--workspace` は当面 workspace config root / local descriptor root を指すだけであり、Repository registry は明示設定された Workspace config から構築する。短期的には `.yoi/workspace-backend.local.toml` の `[[repositories]]` を local descriptor として使い、`uri = "."` のような local repository も明示 entry として登録する。`./` を暗黙 Repository として自動採用しない。
|
||||
|
||||
`.yoi` は現在の local backend / fs-store / compatibility surface として残るが、long-term Backend store ではない。将来的には `~/.yoi` 側に Backend store と Workspace registry を置き、1 Backend process が複数 Workspace を扱える形へ移行する。`.yoi/workspace-backend.local.toml` はその移行までの workspace-local descriptor / override surface として扱う。
|
||||
|
||||
短期的には Git を主な Repository provider とする。ただし Yoi の authority model を Git object、Git branch、Git Repository root、worktree path に固定しない。Orchestration は Git そのものではなく、`resolve_ref`、`materialize`、`diff`、`patch`、`commit`、`merge` などの Repository capability に依存する。
|
||||
|
||||
### 3. Ticket を team coordination record にする
|
||||
|
||||
Ticket は実行そのものではない。Ticket は「何を、なぜ、どの条件で完了とみなすか」を持つ。Ticket は Workspace に平たく所属し、Repository には所属しない。コードやドキュメントを対象にする Ticket は、対象 Repository / ref selector / path / intent を target として持つ。
|
||||
|
||||
Ticket target は intent/selector であり、実行再現性のための immutable point ではない。Worker launch request が target selector を concrete RepositoryPoint に解決し、Runtime が実際にどの revision/snapshot を materialize したかを Artifact / evidence として記録する。
|
||||
|
||||
```text
|
||||
Ticket
|
||||
-> target selectors: Repository + ref selector + path + intent
|
||||
-> resolved RepositoryPoint
|
||||
-> working directory
|
||||
-> WorkerRef / Artifact / Evidence
|
||||
-> Review / Decision
|
||||
-> Audit / Notification
|
||||
```
|
||||
|
||||
Target 例:
|
||||
|
||||
```text
|
||||
Ticket targets:
|
||||
- repository: main-code
|
||||
role: primary
|
||||
ref: develop
|
||||
paths: ["crates/pod/"]
|
||||
intent: change
|
||||
- repository: docs
|
||||
role: related
|
||||
ref: main
|
||||
paths: ["docs/development/"]
|
||||
intent: read
|
||||
|
||||
Worker launch materialization:
|
||||
- repository: main-code
|
||||
requested_ref: develop
|
||||
resolved_point: git commit abc123
|
||||
mount: /workspace/main-code
|
||||
```
|
||||
|
||||
Ticket には次の概念が必要になる。
|
||||
|
||||
- Actor identity: human / agent / system / service account.
|
||||
- Assignment / owner / reviewer / watcher.
|
||||
- Typed thread events: comment, decision, plan, review, implementation report, state transition.
|
||||
- Linked Objective / Artifact / WorkerRef / Repository / RepositoryPoint / working directory.
|
||||
- Permission / visibility.
|
||||
- Audit trail.
|
||||
- Notification / mention.
|
||||
- Board / queue / planning / review / done / archived views.
|
||||
- Conflict handling and concurrent editing policy.
|
||||
|
||||
### 4. Memory / Skill catalog の本格再設計は後回しにする
|
||||
|
||||
Memory は Ticket / Artifact のコピーではない。再利用可能な文脈、方針、学習された制約を扱うが、Ticket や Artifact の authority を置き換えない。Skill catalog は procedural guidance の catalog であり、外部状態 authority を持たない。Knowledge record kind は削除方針なので、この Objective では separate Knowledge storage を新しい control plane entity として増やさない。
|
||||
|
||||
理由は、Memory と Skill catalog の正しい設計が Workspace control plane の record model、Actor / visibility / permission、Ticket、Artifact / evidence、RepositoryPoint、Runtime に渡す context の監査方法に依存するためである。これらが固まる前に Memory schema や Skill API だけを作ると、local `.yoi` 前提や現行 agent runtime 前提に引っ張られ、後で再設計が必要になる。
|
||||
|
||||
この Objective では、Memory / Skill catalog について以下の platform contract だけを維持する。
|
||||
|
||||
- Memory は Control plane が扱う record だが、Ticket / Artifact の authority を置き換えない。
|
||||
- Skill catalog は Workspace backend が扱う prompt/resource catalog だが、Ticket / Worker / workdir / queue の authority を持たない。
|
||||
- 将来、Memory と Skill catalog の canonical storage / API は Workspace control plane 側に置く。
|
||||
- local `.yoi` memory と `.yoi/skills` は compatibility、offline/export/import、local projection、migration bridge として扱う。
|
||||
- Personal Memory、Workspace Memory、Worker Summary、Skill catalog は分離が必要である。
|
||||
- Generated Memory には provenance、visibility、approval、audit が必要である。
|
||||
- Runtime / Worker に渡した Memory / Skill context は、将来 ContextPack などとして Artifact/evidence に記録できる必要がある。
|
||||
|
||||
本格的な Memory 再設計は、Memory の保存先を Workspace backend / control plane record に移すタイミングで回収する。それまでは低リスクな観察、問題例の収集、既存 local memory の互換維持に留める。
|
||||
|
||||
### 5. Control plane / Runtime を分離する
|
||||
|
||||
Control plane は正本と調整を持つ。Runtime は Worker 群と実行基盤を管理する。初期実装では local backend と runtime process が同じマシン上にあり、役割が近く見えるが、設計上は分ける。
|
||||
|
||||
初期形:
|
||||
|
||||
```text
|
||||
Web UI / Control Plane
|
||||
-> Runtime registry / local backend
|
||||
-> Runtime process
|
||||
-> Workers
|
||||
-> Existing Yoi tools, working copy, build/test commands
|
||||
```
|
||||
|
||||
この段階では、現在ローカル管理画面が行っている Ticket 選択、エージェント起動、レビュー起動、作業用 checkout 作成、検証実行、結果表示を、Web/control plane から local Runtime に対して実行できるようにする。
|
||||
|
||||
長期的には Runtime が作業環境の用意まで請け負う。Runtime は Worker launch request / config bundle / repository target / authority を受け取り、必要な checkout、worktree、container filesystem、sandbox mount、cache、secret boundary を準備して Worker を起動する。Runtime 起動時に特定 Workspace path を必須にする形は暫定であり、Workspace / Repository 情報は Runtime process 起動引数ではなく Worker launch request 側の入力に寄せる。
|
||||
|
||||
Sandbox と authority 分離が成立している前提では、1 つの Runtime が複数 Workspace / Repository の Worker を抱えられる。したがって Runtime identity は Git repository root や single workspace directory と同一視しない。Runtime は execution substrate、Workspace は作業管理 record の正本として扱う。
|
||||
|
||||
Worker の作業環境を用意する経路は次のようにまとめる。
|
||||
|
||||
```text
|
||||
Ticket / user intent
|
||||
-> RepositoryId + RepositorySelector + path scope + required authority
|
||||
-> resolved RepositoryPoint
|
||||
-> WorkerLaunchRequest / ConfigBundle / AuthorityBundle
|
||||
-> Runtime WorkingDirectoryMaterializer
|
||||
-> working directory allocation
|
||||
-> Worker process
|
||||
-> WorkerRef + Artifact/evidence
|
||||
```
|
||||
|
||||
Control plane は Workspace authority、Ticket target、Repository registry、Actor permission、設定 bundle の正本を持つ。Control plane または Repository provider は RepositorySelector を RepositoryPoint に解決し、どの地点を対象にしたかを evidence に残す。Runtime は RepositoryPoint、materialization policy、sandbox policy、mount/cache/secret policy、Worker config を受け取り、Runtime-local な working directory を確保して Worker を起動する。
|
||||
|
||||
この境界では、Worker は host filesystem path や Repository credential を自分で発見しない。Worker は Runtime が materialize した working directory root、mount、環境変数、tool authority、config bundle だけを見る。Runtime は working directory の lifecycle、cleanup、cache reuse、namespace、quota、sandbox boundary、event collection を管理する。Control plane / Browser-facing API は raw host path、secret、socket、internal runtime path を authority-bearing internals として扱い、必要な evidence だけを Artifact として公開する。
|
||||
|
||||
v0 materializer は existing local root を明示的な working directory として返してよい。ただし型と呼び出し順序は、後で Git worktree、clone、sparse checkout、container filesystem、remote object snapshot、multi-repository mount に置き換えられる形にする。Runtime process 起動時の `--workspace` はこの v0 materializer の legacy bootstrap input であり、Runtime identity や long-term workspace binding ではない。
|
||||
|
||||
その後で、remote Runtime、self-hosted Runtime、hosted cloud runtime fleet、runtime pool、resource allocation、quota、billing、sandbox、network policy、secret distribution を追加する。
|
||||
|
||||
```text
|
||||
Phase 1: Web control plane + local Runtime
|
||||
Phase 2: Remote/self-hosted Runtime
|
||||
Phase 3: Hosted cloud runtime fleet
|
||||
Phase 4: Resource allocation / scheduling / quotas / billing / isolation
|
||||
```
|
||||
|
||||
### 6. Web frontend を先に作る
|
||||
|
||||
Desktop app は対応コストが高いので、まず Web frontend を primary UI とする。
|
||||
|
||||
- Web: チームで使う主要 UI。
|
||||
- CLI: automation、scripting、local operations。
|
||||
- TUI/local panel: fallback、dogfooding surface。
|
||||
- Future desktop: Web/control-plane model が安定した後に検討する optional client。
|
||||
|
||||
Web UI は Ticket、Objective、Memory、Skill catalog、Runtime、Worker、Artifact を扱う。UI の都合で正本を二重化しない。
|
||||
|
||||
### 7. 多重起動コストと runtime placement を見直す
|
||||
|
||||
Cloud/remote execution を成立させるには、多数のエージェント実行を安く管理できる必要がある。logical Worker session と Runtime process/resource placement を分ける。Runtime は Git Repository root や Workspace path に固定されず、request ごとに必要な working directory を materialize できる実行基盤として扱う。
|
||||
|
||||
初期 Workspace DB では、Worker を canonical table として永続化しない。Runtime / Worker 一覧は backend-local runtime inspection や将来の Runtime protocol から逐次取得する live view とし、Ticket に関わった Worker は Ticket thread events と WorkerRef snapshot / TicketWorkerLink として記録する。
|
||||
|
||||
Worker の一元管理、データ永続化、アーカイブは将来的には必要になる。これは Runtime protocol、remote/self-hosted/hosted runtime lifecycle、worker identity、retention policy、audit requirements が固まった後に、dedicated Worker registry / archive model として追加する。v0 で Pod metadata の代替として Worker table を作らない。
|
||||
|
||||
検討対象:
|
||||
|
||||
- Worker identity と Runtime process/resource placement の分離。
|
||||
- 1 Runtime が複数 Workspace / Repository の Worker を抱える場合の namespace、quota、cleanup、audit boundary。
|
||||
- Runtime-side working directory materialization、sandbox、mount、checkout/worktree/container filesystem の責務。
|
||||
- Provider client、tool registry、resource cache の共有可能性。
|
||||
- Prompt/resource/profile/config bundle resolution cache。
|
||||
- Model call multiplexing and scheduling。
|
||||
- Tool execution sandbox reuse。
|
||||
- Plugin instance / Service runtime との統合。
|
||||
- Session/event stream と runtime lifecycle の分離。
|
||||
- Runtime-local cache、checkout reuse、build cache、dependency cache。
|
||||
|
||||
## Initial phases / candidate tickets
|
||||
|
||||
1. **Vocabulary / architecture record**
|
||||
- Workspace / RepositoryId / RepositorySelector / RepositoryPoint / working directory / Runtime / Worker / Control Plane / Ticket / Memory の用語と境界を固める。
|
||||
2. **Team-space canonical data model**
|
||||
- Ticket / Objective / Target / Artifact / Actor / Permission / Audit / Memory の entity/event model を設計する。
|
||||
3. **Ticket evidence model**
|
||||
- Ticket lifecycle、WorkerRef、Artifact、validation evidence、review evidence、Ticket thread の責務を明確化する。
|
||||
4. **Memory storage migration boundary**
|
||||
- Memory の本格再設計は後回しにし、まずは Workspace backend に移す時の platform contract、compatibility/cache/export 方針、将来の provenance / visibility / approval 要件だけを固定する。
|
||||
5. **Control plane backend architecture**
|
||||
- local `.yoi` backend と server-side canonical backend の境界、migration/export/import、compatibility mode を設計する。
|
||||
6. **Web control plane MVP design**
|
||||
- read-only Ticket / Objective / Memory / Runtime / Worker state UI/API の範囲を決める。
|
||||
7. **Local Runtime protocol design**
|
||||
- Web/control plane から local Runtime に安全な操作を送り、Runtime が Worker lifecycle と working directory materialization を担う protocol と authority boundary を設計する。
|
||||
8. **Repository and working directory materialization model**
|
||||
- Repository URI、Repository provider capability、RepositorySelector resolution、RepositoryPoint evidence、Git worktree / clone / sparse checkout / future source backend を Runtime-side materialization strategy として抽象化する。
|
||||
9. **Remote/hosted runtime foundation**
|
||||
- runtime registration, heartbeat, capability advertisement, job assignment, logs/events, secrets, sandbox/resource policy を設計する。
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Git hosting service を作ること。
|
||||
- `.yoi` filesystem をそのまま SaaS canonical store にすること。
|
||||
- 最初から full hosted cloud execution を作ること。
|
||||
- local execution / CLI / TUI / local panel を捨てること。
|
||||
- Ticket を単なる issue tracker clone にすること。
|
||||
- Memory を Ticket/Artifact audit log の代替にすること。
|
||||
- Web UI のために core authority を二重化すること。
|
||||
- hidden server state を LLM context に直接注入すること。
|
||||
- multi-tenant auth/billing/secret/security を shortcut して実装すること。
|
||||
|
||||
## Success criteria / exit conditions
|
||||
|
||||
- Workspace / RepositoryId / RepositorySelector / RepositoryPoint / working directory / Runtime / Worker / Control Plane / Ticket / Memory の境界が文書化されている。
|
||||
- Ticket が team coordination record として、target selector / Artifact / Actor / Permission / Audit と分離された model を持つ。
|
||||
- `.yoi` local backend は compatibility/local backend として整理され、server-side canonical backend の設計を阻害しない。
|
||||
- Web UI/API が Ticket / Objective / Runtime / Worker state を中心とした read-only view を提供できる設計または MVP を持つ。Memory は既存 record の表示または将来 placeholder に留め、本格再設計をこの段階の必須条件にしない。
|
||||
- Control plane から local Runtime に対して、現在のローカル管理画面相当の安全な操作を実行できる design/protocol がある。
|
||||
- Runtime は single Workspace / Git repository root 専用 process ではなく、sandbox/authority が成立すれば複数 Workspace / Repository の Worker を抱えられる execution substrate として設計されている。
|
||||
- Git Repository root に依存しない Workspace model があり、Git Repository は Repository provider の一種として扱われている。
|
||||
- Ticket と Objective は Workspace 配下に平たく存在し、Repository への所属ではなく RepositoryId / RepositorySelector / path scope / intent で対象を表現する。
|
||||
- Git worktree 相当は working directory materialization strategy として扱われ、Artifact/evidence が concrete RepositoryPoint を記録する。
|
||||
- Memory は Ticket / Artifact の authority を置き換えない record として platform contract だけを持つ。本格的な意味論・抽出・承認・検索・staleness 処理は、Memory の保存先を Workspace backend / control plane record に移すタイミングで回収する。
|
||||
- Hosted Runtime / resource allocation / SaaS offering に進むための後続 Ticket が切れる状態になっている。
|
||||
- 既存 local dogfooding runtime を壊さず、local use と remote-capable architecture が両立している。
|
||||
|
||||
## Decision context
|
||||
|
||||
- Yoi は hosted Git tool ではなく、team workspace control plane + Runtime execution environment として設計する。
|
||||
- Team-space の長期 canonical authority は server-side control plane に置く。local `.yoi` は互換/local/offline/export/import surface だが、multi-user SaaS の正本ではない。
|
||||
- 実行環境と管理システムは弱結合にする。まず管理システムを独立させ、local Runtime を実行環境として接続する。その後に remote/self-hosted/hosted runtime fleet へ進む。
|
||||
- Runtime は Worker 群を束ねる実行基盤であり、将来的には作業環境の用意、sandbox、mount、checkout/worktree/container filesystem、cache、secret boundary を Worker launch request ごとに準備する。Runtime process は single Workspace / Git repository root 専用に固定しない。RepositorySelector は provider-specific な未解決 locator、RepositoryPoint は解決済み evidence として扱う。
|
||||
- Web frontend を最初の primary team UI とする。Desktop app は web/control-plane model が安定した後に検討する。
|
||||
- Git は重要な Repository provider / materialization backend として使うが、Workspace identity と authority を Git Repository root に固定しない。
|
||||
- Ticket と Objective は Workspace 配下に平たく持つ。対象コードベースや地点指定は RepositoryId / RepositorySelector / path scope / intent として表現し、Worker launch materialization が concrete RepositoryPoint に解決する。
|
||||
- Backend Repository は cwd inspection ではなく、Workspace config の明示 Repository registry から構築する。`--workspace` は Repository ではなく workspace config root / local descriptor root を指す。
|
||||
- `./.yoi` は local descriptor / fs-store / compatibility surface であり、将来の Backend canonical store と Workspace registry は `~/.yoi` 側へ寄せる。
|
||||
- Memory の本格再設計は後回しにする。先に Workspace / Ticket / Repository / Runtime/Worker live view / Control plane の基盤を固め、Memory の保存先を Workspace backend に移すタイミングで、意味論・抽出・承認・検索・staleness 処理をまとめて回収する。
|
||||
- Worker の一元管理・データ永続化・アーカイブも後続設計に回す。初期 DB では Worker を Pod metadata の代替として永続化せず、live view と Ticket-linked WorkerRef 記録に留める。
|
||||
@@ -0,0 +1,289 @@
|
||||
---
|
||||
title: "効果的な Memory システム設計・検証"
|
||||
state: "active"
|
||||
created_at: "2026-06-20T15:16:00Z"
|
||||
updated_at: "2026-07-17T23:10:00Z"
|
||||
linked_tickets: ["00001KSKBPHRG", "00001KT02TCCG", "00001KTGCAFXG", "00001KSKBPTHR", "00001KXMEZNYC", "00001KXMK7YMC", "00001KXNYXNM6", "00001KXRM6G0G", "00001KXS56AS5", "00001KXMK846H"]
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Yoi の Memory / Knowledge / Skills / generated context / resident context / retrieval / usage metrics を、実際の開発・設計・レビュー・オーケストレーションに効く sensemaking substrate として再設計・検証する。Memory は短期・変化前提の context、Knowledge は育てる long-term note、Skill は移植可能な workflow として分け、この Objective ではそれらと authority record / docs / typed tools の境界を再整理する。
|
||||
|
||||
この Objective でいう「効果的な Memory システム」は、単に多く保存する仕組みではなく、作業中の問いに対して relevant material を集め、根拠を検証可能にし、再表現・仮説形成・反証探索・意思決定・成果物への反映を低コストにする仕組みである。
|
||||
|
||||
暫定的な定義:
|
||||
|
||||
- foraging cost を下げる: Ticket / Objective / current question に対して、関連する memory / docs / tickets / session evidence / code references を探しやすい。
|
||||
- evidence を失わない: Memory が authority そのものにならず、Ticket / docs / git history / session logs / user instruction への検証可能な入口になる。
|
||||
- schema 化を支援する: raw summary ではなく、subsystem、invariant、risk、authority boundary、open question、rejected alternative、hypothesis など推論しやすい形へ再表現できる。
|
||||
- hypothesis loop を支援する: 支持証拠だけでなく、代替仮説・棄却理由・反証 evidence・stale assumption を扱える。
|
||||
- product に戻る: Memory に保存して終わりではなく、Ticket、review、docs、implementation、decision、report に影響を戻せる。
|
||||
- stale / contradiction を扱う: 古い前提、矛盾、適用範囲外の memory を検出・降格・更新できる。
|
||||
- usage を成果基準で測る: resident exposure や read count ではなく、判断・レビュー・実装・docs に効いたかを観測できる。
|
||||
|
||||
## Motivation / background
|
||||
|
||||
現在の Memory システムは「墓場化」している。保存された情報はあるが、後続の作業で自然に使われにくく、使われたとしても根拠・適用範囲・鮮度・反証可能性が弱い。結果として Memory は、作業場ではなく古い結論の倉庫になりやすい。
|
||||
|
||||
Pirolli & Card 2005 の sensemaking model では、分析作業は単なる保存ではなく、次の変換として捉えられる。
|
||||
|
||||
```text
|
||||
external data sources
|
||||
-> shoebox
|
||||
-> evidence file
|
||||
-> schema / representation
|
||||
-> hypotheses
|
||||
-> presentation / product
|
||||
```
|
||||
|
||||
Yoi の現行 Memory は、この流れのうち「保存」と「一部の検索」には対応しているが、少なくとも以下が弱い。
|
||||
|
||||
- Ticket / task / question ごとの shoebox がない。
|
||||
- shoebox から evidence snippets を切り出し、source / provenance / applicability / confidence と共に扱う evidence file がない。
|
||||
- `summary`, `decision`, `request` は durable memory storage taxonomy であり、sensemaking 用 schema としては粗い。Knowledge は古い record kind をそのまま残すのではなく、育てる long-term note subsystem として再設計する。再利用可能な手順は Skill、保守された設計資料は Knowledge / docs / Ticket decisions に寄せる。
|
||||
- decision は残るが、hypothesis space、alternative、rejected reason、disconfirming evidence が残りにくい。
|
||||
- reviewer / orchestrator が confirmation bias を避けるための反証探索導線が弱い。関連する手順誘導は旧 Workflow ではなく Skill と role prompt / typed tools へ寄せる。
|
||||
- resident exposure と explicit retrieval は観測できても、Memory が product に効いたかは測りにくい。
|
||||
|
||||
この Objective は、Memory 関連の設計・検証・検討・考察を一元化し、個別 Ticket がばらばらに storage、prompt、retrieval、metrics を改善して再び墓場を増やすことを防ぐための判断背景である。
|
||||
|
||||
## Strategy / design direction
|
||||
|
||||
Memory を「長期保存領域」ではなく、Yoi の multi-agent 開発における sensemaking loop の支援機構として設計する。
|
||||
|
||||
### 1. Pirolli & Card の stage に合わせて責務を分ける
|
||||
|
||||
- external data sources: Tickets、docs、git history、session logs、reports、code、user instructions。
|
||||
- shoebox: 特定 Ticket / Objective / design question に対して関連しそうな材料を集めた task-bound working set。
|
||||
- evidence file: shoebox から抜き出した根拠 snippet。source anchor、支持/反証、適用範囲、confidence、staleness を持つ。
|
||||
- schema / representation: subsystem、invariant、risk、authority boundary、open question、hypothesis、alternative、contradiction など、推論しやすい再表現。
|
||||
- hypotheses: 採用前の設計仮説、代替案、棄却条件、反証 evidence。
|
||||
- product: Ticket、review、docs、implementation、decision、report、orchestration plan などの成果物。
|
||||
|
||||
### 2. 最初の重点は task-bound shoebox と evidence file
|
||||
|
||||
Memory 墓場化の最初の原因は、保存情報が現在の問いに集まらないことである。まずは Orchestrator / Intake / Reviewer が Ticket を扱う時に、関連 memory / docs / tickets / reports / prior decisions を shoebox として束ねる導線を作る。
|
||||
|
||||
この段階では大きな永続 schema 追加に飛びつかず、report / Ticket artifact / bounded generated context として検証してよい。
|
||||
|
||||
### 3. Session Overview を extract の足場にする
|
||||
|
||||
現在の extract は、tool call / tool result summary を含む flat slice から意味を復元しようとして断片化しやすい。改善方針は、main Worker が通常 Assistant Message として Progress message を残し、user messages + Assistant messages を Session Overview として先に読む形にする。
|
||||
|
||||
- Progress message は専用 Tool ではなく通常 Message とし、ユーザーへの進捗報告と extract 用 semantic summary を兼ねる。
|
||||
- extract worker には専用の read-only evidence tools を渡し、Overview で重要そうに見えた箇所だけ session range / tool summaries / source anchors を探索させる。
|
||||
- extract worker は Memory / Knowledge / Skill を直接更新しない。output は必ず staging を挟み、source / provenance を host 側で機械的に保持する。
|
||||
- trigger は初期実装では現行通り Worker run cycle 完了後の threshold 判定にする。LLM call 単位や Run 中の Overview accumulation trigger は含めない。
|
||||
|
||||
### 4. Memory を authority にしない
|
||||
|
||||
Memory は Ticket、docs、git history、session logs、user instruction の代替ではない。Memory は authority record への evidence index / schema / reasoning aid として扱う。
|
||||
|
||||
したがって、改善案は次の性質を持つべきである。
|
||||
|
||||
- source / provenance を辿れる。
|
||||
- stale / superseded / contradicted を扱える。
|
||||
- Memory の断定をそのまま authority として使わない。
|
||||
- Ticket body/thread/artifacts を読まずに Objective や Memory だけで実装判断できる状態を作らない。
|
||||
|
||||
### 5. 反証探索を first-class にする
|
||||
|
||||
より効果的な Memory は、過去方針を思い出すだけでなく、現在案を疑うために使える必要がある。
|
||||
|
||||
Reviewer / Orchestrator / Intake の導線では、次を探せるようにする。
|
||||
|
||||
- supporting evidence
|
||||
- contradicting evidence
|
||||
- stale decisions
|
||||
- rejected alternatives
|
||||
- unresolved questions
|
||||
- authority boundary risks
|
||||
- prior failures / reports
|
||||
|
||||
### 6. Metrics は exposure から product impact へ寄せる
|
||||
|
||||
Memory が prompt に入った、または query されたことは成功ではない。評価は次を区別する。
|
||||
|
||||
- resident exposure
|
||||
- explicit retrieval
|
||||
- cited in response
|
||||
- cited in Ticket / review / report
|
||||
- changed requirement
|
||||
- changed implementation
|
||||
- contradicted / invalidated
|
||||
- led to docs or decision update
|
||||
|
||||
### 7. 後続 Ticket は concrete slice に分割する
|
||||
|
||||
この Objective は中期的な設計・検証の一元化 record であり、umbrella Ticket ではない。実装や調査は、単独で実装・レビュー・close できる concrete Ticket に分割する。
|
||||
|
||||
候補 slice:
|
||||
|
||||
- Memory sensemaking 分析 report を `docs/report/` に作る。
|
||||
- Ticket routing 用 Memory shoebox artifact を試作する。
|
||||
- evidence snippet schema / source resolver を設計する。
|
||||
- hypothesis / rejected alternative / disconfirming evidence の表現を追加する。
|
||||
- Reviewer Skill / review process に反証探索を入れる。
|
||||
- Memory usage metrics を product impact oriented に拡張する。
|
||||
- stale / contradiction / renewal の検出・表示を設計する。
|
||||
- turn 中の Progress message を通常 Assistant Message として残す prompt/guidance を追加する (`00001KXMEZNYC`)。
|
||||
- Session Overview + Evidence index を使う extract input を設計・実装する。
|
||||
- extract worker 専用の read-only evidence search/read/source-anchor tools を設計する。
|
||||
- extract output を staging に限定し、source range と output entry を結びつける schema を設計する。
|
||||
|
||||
## Success criteria / exit conditions
|
||||
|
||||
- Memory システムの目的が「保存」ではなく「sensemaking loop 支援」として project records / docs / prompts / Skills で一貫して説明されている。
|
||||
- Pirolli & Card の `shoebox -> evidence file -> schema -> hypotheses -> product` に対応する Yoi 内の責務と非責務が整理されている。
|
||||
- Ticket / Objective / docs / session logs / Memory / Skills の authority boundary が明確で、Memory が authority を僭称せず、Skill は手順資源として外部状態 authority を持たない。
|
||||
- 少なくとも一つの実作業 routing / review / design analysis で、task-bound shoebox または evidence file が生成・利用され、作業品質にどう効いたかが確認されている。
|
||||
- Memory records または関連 artifacts が source / provenance / applicability / staleness / supports-or-refutes のいずれかを扱えるようになっている。
|
||||
- extract が User / Assistant messages 由来の Session Overview を primary input とし、tool logs を evidence として探索できる設計になっている。
|
||||
- extract worker 専用の read-only evidence tools が設計され、main Worker の tool surface を増やさない方針になっている。
|
||||
- extract output は direct Memory / Knowledge / Skill write ではなく staging を挟む方針になっている。
|
||||
- Reviewer / Orchestrator が supporting evidence だけでなく、contradicting evidence / stale assumptions / rejected alternatives を探す導線を持っている。
|
||||
- Memory usage metrics が resident exposure と product impact を区別している。
|
||||
- 古い Memory が放置されるのではなく、stale / superseded / contradicted / needs-review として扱える方針がある。
|
||||
- 後続の実装 Ticket が concrete slice として分割され、Objective が Ticket dependency や進捗 container として使われていない。
|
||||
|
||||
この Objective は、Memory が少なくとも一つの中規模設計・実装・レビュー作業で「関連情報を見つける」「根拠を確認する」「代替案/反証を検討する」「成果物へ反映する」流れを実証し、その設計方針が docs / Skills / metrics に反映された時点で `done` を検討できる。
|
||||
|
||||
## Decision context
|
||||
|
||||
- ユーザー指摘: 「Memoryシステムが完全に墓場化している」。これは保存量不足ではなく、保存情報が現在の問い・根拠・仮説・成果物に接続されない問題として扱う。
|
||||
- ユーザー指示: Memory システムの設計・検証・検討・考察を Objective にまとめ、より効果的な Memory システムを作成する目標のもとで情報を一元化する。
|
||||
- 「効果的」の定義は未確定だが、当面は Pirolli & Card の sensemaking process に沿って、foraging cost、evidence quality、schema usefulness、hypothesis/disconfirmation support、product impact、staleness handling を評価軸にする。
|
||||
- Memory は durable project authority ではない。Ticket、docs、git history、session logs、明示 user instruction の代替として使わない。
|
||||
- Objective context は判断背景であり、個別実装の authority は各 Ticket body/thread/artifacts と明示的な Ticket relations / OrchestrationPlan records にある。
|
||||
- `history` に残らない context-only injection を改善案にしない。新しい context input は history に commit する原則を守る。
|
||||
- Knowledge は古い unused record kind をそのまま残すのではなく、育てる long-term Markdown note subsystem として再設計する。再利用可能な手順・作法は Agent Skills (`.yoi/skills/<skill>/SKILL.md`) へ、durable policy/rationale は Knowledge / maintained docs / Ticket decisions へ、外部状態 authority は typed feature/tool surface へ分ける。
|
||||
- Generated memory / Ticket / docs / report / Skill の境界を再定義する場合は、authority boundary と migration/staleness を明示する。
|
||||
- 関連する既存 Ticket:
|
||||
- `00001KSKBPHRG` — Prompt / Workflow 評価メトリクスと改善 Offer
|
||||
- `00001KT02TCCG` — Memory prompt: conditional guidance and proactive lookup
|
||||
- `00001KTGCAFXG` — Use .yoi/memory marker for repo-local memory root
|
||||
- `00001KSKBPTHR` — ワークスペースのメモリーをLintするヘッドレスCLI
|
||||
- `00001KXMEZNYC` — ターン中のProgress messageを残す指示を追加する
|
||||
|
||||
## Historical references / prior design sources
|
||||
|
||||
現在の Memory システムの初期設計時には、Codex Memories / Chronicle と HermesAgent を明示的な参考事例として調査していた。関連する調査・設計記録は、現在は主に以下に退避されている。
|
||||
|
||||
- `docs/.local/old-docs/ref/memory-systems.md`
|
||||
- `docs/.local/old-docs/plan/memory.md`
|
||||
- 初期設計 commit: `ca5a3d11` — `2026-04-21 メモリシステムの設計`
|
||||
- 関連 commit:
|
||||
- `0c1276b7` — `Memoryシステムの整理・Promptカタログチケット`
|
||||
- `3d04f793` — `memoryを抽出する仕組みの実装`
|
||||
- `f1b7af62` — `docs: memoryシステムの仕様変更と、動的Tool・VCSの話`
|
||||
- `a2aecbf0` — `update: memoryシステムの"Phase"表記を撤廃`
|
||||
|
||||
### Codex Memories / Chronicle から得た設計要素
|
||||
|
||||
旧設計では、Codex Memories / Chronicle を `extract -> staging -> consolidation -> durable Markdown memory` の非同期パイプラインとして捉えていた。
|
||||
|
||||
主な参照点:
|
||||
|
||||
- extract と consolidation の 2 段構成。
|
||||
- extract は JSON schema / structured output で分類ブレを抑える。
|
||||
- consolidation は reasoning model / agentic rewrite によって、既存 memory と staging entries を統合・整理する。
|
||||
- staging と durable memory を分ける。
|
||||
- `MEMORY.md` は retrieval-oriented handbook として扱う。
|
||||
- `memory_summary.md` は prompt-loaded high-signal context として扱う。
|
||||
- `raw_memories.md` は routing layer / task inventory 的な中間層として扱う。
|
||||
- workspace diff や usage 情報を使い、stale / deleted evidence / noisy entries を整理する。
|
||||
- consolidation は append だけでなく、rewrite / merge / split / trim / drop / cleanup を担う。
|
||||
|
||||
Yoi 初期設計では、これを参考に以下を意図していた。
|
||||
|
||||
- activity token 閾値で extract を発火する。
|
||||
- compact より前に session log range を抽出する。
|
||||
- extract は `decisions`, `discussions`, `attempts`, `requests` などの候補を staging に保存する。
|
||||
- 抽出時点では durable policy / Skill / docs へ早期分類せず、純粋な「起きたこと」に寄せる。
|
||||
- consolidation が summary / decisions / requests と、必要に応じた docs / Skill / Ticket decision 更新候補を整理する。
|
||||
- consolidation 入力に linter warnings / usage metrics / stale cleanup 候補を含める。
|
||||
- stale / superseded / unused / noisy な情報を整理する。
|
||||
|
||||
この Objective での再解釈:
|
||||
|
||||
- Codex の `raw_memories.md` は、Pirolli & Card の sensemaking model では `shoebox` または `evidence file` に近い。
|
||||
- Yoi は extract / consolidation という pipeline だけを継承しても不十分であり、task-bound shoebox / evidence file / hypothesis loop / product feedback がなければ Memory は再び墓場化する。
|
||||
- 特に、staging を consolidation の一時入力としてだけ扱うと、後続 Ticket / Objective / review が使う探索入口にならない。
|
||||
- Yoi では `raw memories` 相当の中間層を、現在の問いに紐づく working set / evidence index として再設計する必要がある。
|
||||
|
||||
### HermesAgent から得た設計要素
|
||||
|
||||
旧設計では、Nous Research HermesAgent を 3 層の memory system として整理していた。
|
||||
|
||||
- Persistent Memory:
|
||||
- `MEMORY.md` / `USER.md`
|
||||
- Markdown + SQLite / FTS5 session search
|
||||
- 起動時 system prompt snapshot
|
||||
- bounded character limits
|
||||
- Skill Library:
|
||||
- procedural memory
|
||||
- `~/.hermes/skills/<name>/SKILL.md`
|
||||
- `skill_manage` tool による agentic CRUD
|
||||
- User Model / Honcho:
|
||||
- dialectic user modeling
|
||||
- 外部 service 連携
|
||||
|
||||
HermesAgent で特に重要だった点:
|
||||
|
||||
- memory / skill review は一定 turn / tool iteration ごとに background agent として起動する。
|
||||
- 保存すべきものがなければ `Nothing to save.` で NOP として終了する。
|
||||
- Yoi extract の「空配列許容」はこの設計からも影響を受けている。
|
||||
- memory は session start 時の frozen snapshot として system prompt に入り、mid-session write で prompt cache を壊さない。
|
||||
- persistent memory は bounded で、limit 超過時は deterministic eviction ではなく、agent に replace / remove を促す。
|
||||
- procedural memory / skills は一般 memory から分離されている。
|
||||
- SQLite FTS5 + LLM summarization による cross-session recall がある。
|
||||
|
||||
この Objective での再解釈:
|
||||
|
||||
- HermesAgent の `MEMORY.md` / `USER.md` / `skills` の分離は、Yoi の Memory / Knowledge / Skills / prompt resources / docs / Ticket decision / generated memory の責務再整理に使える。
|
||||
- Yoi は foreground isolation 自体をすでに持つため、取り入れるべきなのは isolation そのものではなく、Overview を足場にした maintenance / extraction の質改善である。
|
||||
- main Worker が通常 Assistant Message として残す Progress message は、人間向け進捗報告と machine-readable session overview を兼ねられる。
|
||||
- extract worker は専用 read-only evidence tools で必要箇所だけ探索し、direct write ではなく staging に出力する。
|
||||
- reusable procedure, reviewer focus, orchestration tactic, project preference, user preference, design invariant を同じ Memory bucket に入れると墓場化しやすい。
|
||||
- `Nothing to save.` / empty extraction allowed は重要だが、保存抑制だけでは効果的な Memory にはならない。保存されたものが task-bound shoebox / evidence / schema / hypothesis / product に接続される必要がある。
|
||||
- frozen snapshot / prompt cache 配慮は Yoi の history/context 加工原則と整合するが、それだけでは retrieval / resurfacing / disconfirmation は解決しない。
|
||||
|
||||
### Lessons for the next design iteration
|
||||
|
||||
Codex と HermesAgent の調査から、Yoi が継承すべきものと、継承するだけでは足りないものを分ける。
|
||||
|
||||
継承すべきもの:
|
||||
|
||||
- structured extract と agentic consolidation の分離。
|
||||
- staging / raw memories / durable memory の分離。
|
||||
- 保存対象がなければ NOP にする発火設計。
|
||||
- prompt-loaded summary と durable retrieval-oriented memory の分離。
|
||||
- stale / noisy / unused entries の cleanup。
|
||||
- procedural memory と declarative memory の分離。
|
||||
- session search / usage metrics / linter feedback を consolidation に入れる設計。
|
||||
- user / Assistant messages から作る Session Overview を semantic guide にし、tool logs を evidence として探索する設計。
|
||||
|
||||
足りないもの:
|
||||
|
||||
- Pirolli & Card の sensemaking stage における各 record の役割定義。
|
||||
- Ticket / Objective / current question に紐づく task-bound shoebox。
|
||||
- authority record へ戻れる evidence file / provenance / source anchor。
|
||||
- hypothesis, alternative hypothesis, rejected reason, disconfirming evidence の first-class 表現。
|
||||
- reviewer / orchestrator が confirmation bias を避けるための反証探索導線。
|
||||
- resident exposure や read count ではなく product impact を測る metrics。
|
||||
- stale / contradiction / renewal を作業中に resurfacing する導線。
|
||||
|
||||
したがって、次の Memory 設計は Codex / HermesAgent の単純なコピーではなく、以下を満たす必要がある。
|
||||
|
||||
```text
|
||||
external data / sessions / tickets / docs / code
|
||||
-> task-bound shoebox
|
||||
-> evidence file with provenance
|
||||
-> schema / representation
|
||||
-> hypotheses and disconfirmation
|
||||
-> Ticket / review / docs / implementation / decision product
|
||||
-> product impact and stale-feedback metrics
|
||||
```
|
||||
|
||||
この Objective では、以後の Memory 関連 Ticket / report / implementation をこの historical reference と sensemaking model の両方に照らして判断する。
|
||||
@@ -0,0 +1,916 @@
|
||||
---
|
||||
created_at: "2026-07-15T21:33:00Z"
|
||||
updated_at: "2026-07-16T18:20:00Z"
|
||||
objective: "00001KVJSMQXZ"
|
||||
status: "architecture-draft"
|
||||
notes: "Memory / Knowledge / Skills を別々の workspace resource として再設計するための draft architecture。この文書は Objective resource であり、実装 authority ではない。"
|
||||
---
|
||||
|
||||
# Memory / Knowledge / Skills architecture overview
|
||||
|
||||
## 1. Position
|
||||
|
||||
Yoi は **Memory**、**Knowledge**、**Skills** を 1 つの汎用 record store に押し込めず、別々の resource class として扱う。
|
||||
|
||||
最初に固めるべきなのは、成果物が人間に読める形で成長できる workspace resource model である。Pirolli & Card の sensemaking process は有用な背景知識だが、storage taxonomy を shoebox / evidence / hypothesis として先に固定しない。sensemaking は Memory / Knowledge / Skills の上に乗る usage pattern として扱う。
|
||||
|
||||
Target split:
|
||||
|
||||
- **Memory**: 短期 fact、嗜好、現在の focus、進行中の context。変化する前提で書く。
|
||||
- **Knowledge**: 長期的に育てる note。人間と agent が改訂し、相互リンクで mesh を形成し、durable project understanding として読めるもの。
|
||||
- **Skill**: Agent Skills format に従う、移植可能で確立された手順・workflow。
|
||||
|
||||
この文書の中核は次の 2 つである。
|
||||
|
||||
1. resource class の境界を明確にする。
|
||||
2. session から extract / staging / consolidation を経て resource に至る pipeline の責務を明確にする。
|
||||
|
||||
## 2. Design goals
|
||||
|
||||
- 人間が読め、成長できる artifact を作る。
|
||||
- 一時的な model summary だけでは足りない。
|
||||
- 有用な成果は Knowledge note、Skill、Ticket decision、doc、report に育てられるべき。
|
||||
- 揮発的なものと durable なものを分ける。
|
||||
- 短期 context が長期 note を汚染しないようにする。
|
||||
- 長期 note が session extraction のたびに上書きされないようにする。
|
||||
- 手順と note を分ける。
|
||||
- 繰り返し使える作業方法は Knowledge note ではなく Skill にする。
|
||||
- authority boundary を明示する。
|
||||
- Ticket は work authority を定義する。
|
||||
- docs と Objective resources は maintained design context を持つ。
|
||||
- Knowledge notes は育てる project understanding を持つ。
|
||||
- Skills は execution を guide する。
|
||||
- Memory は変化する working context と preferences を追跡する。
|
||||
- typed feature/tool surfaces が external state changes を所有する。
|
||||
- 可能な限り Workspace backend を resource API の共有 authority にする。
|
||||
- `WorkspaceClient::Http` が使えるときに、Worker ごとに local view が分岐してはいけない。
|
||||
|
||||
## 3. Resource model
|
||||
|
||||
### 3.1 Memory
|
||||
|
||||
Memory は、agent が作業を継続する助けになる volatile / short-to-medium-term な情報を扱う。長期的な project truth のふりはしない。
|
||||
|
||||
Memory record に入るもの:
|
||||
|
||||
- 現在の focus。
|
||||
- user preferences。
|
||||
- working assumptions。
|
||||
- authority が別にある recent decisions の要約や pointer。
|
||||
- 進行中の constraints。
|
||||
- あとで Ticket / doc / session を再確認するための reminder。
|
||||
- session から得た observations。
|
||||
- 変化することが前提の personal / workspace context。
|
||||
|
||||
Memory は provisional に書く:
|
||||
|
||||
- いつ / なぜ learned したかを書く。
|
||||
- どの前提で learned したかを必要な範囲で書く。
|
||||
- staleness / supersession を許す。
|
||||
- authoritative records を verbatim にコピーしない。
|
||||
- 可能なら Tickets / docs / Knowledge notes への pointer を優先する。
|
||||
|
||||
Memory は resident context と lightweight lookup には有用だが、permanent note system として最適化しない。
|
||||
|
||||
#### 3.1.1 Memory storage profile: bounded H2 Markdown file
|
||||
|
||||
Memory の初期 storage profile は、bounded な single Markdown file にする。
|
||||
|
||||
Memory は Knowledge のような note bundle ではない。1〜3 行程度の short items を H2 section ごとに並べる resident context surface として扱う。
|
||||
|
||||
初期 filesystem shape:
|
||||
|
||||
```text
|
||||
.yoi/memory/
|
||||
memory.md
|
||||
_staging/
|
||||
_resolutions.jsonl
|
||||
```
|
||||
|
||||
`memory.md` は H2 section を基本単位にする。
|
||||
|
||||
```md
|
||||
# Workspace Memory
|
||||
|
||||
## Current focus
|
||||
|
||||
- Memory extract redesign is focused on Overview-first extraction and staging resolution.
|
||||
Source: Objective 00001KVJSMQXZ. Stale when related tickets close.
|
||||
|
||||
## Preferences
|
||||
|
||||
- User prefers implementation Tickets, not design-only Tickets.
|
||||
Source: 2026-07-16 session.
|
||||
|
||||
## Working assumptions
|
||||
|
||||
- Knowledge should be OKF-compatible, while volatile Memory and staging should not be OKF.
|
||||
Source: architecture objective.
|
||||
|
||||
## Reminders
|
||||
|
||||
- Re-check extract/consolidation prompts after staging resolution is implemented.
|
||||
```
|
||||
|
||||
Recommended H2 sections:
|
||||
|
||||
- `## Current focus`
|
||||
- `## Preferences`
|
||||
- `## Working assumptions`
|
||||
- `## Constraints`
|
||||
- `## Reminders`
|
||||
- `## Stale or superseded`
|
||||
|
||||
Each item should stay short. When useful, include `Source` and `Stale when` inline. Long explanations, evidence-heavy analysis, durable rationale, citations, and cross-linked concepts should be routed to Knowledge rather than expanded inside Memory.
|
||||
|
||||
The single-file layout is an initial storage profile, not an API contract. Workers, Web, Runtime, and CLI should use the Workspace Memory API view rather than depending on the exact file layout, so storage can later split or evolve without changing the model-visible contract.
|
||||
|
||||
#### Memory examples
|
||||
|
||||
```text
|
||||
User preference: prefers direct commits only when explicitly requested.
|
||||
Source: repeated user corrections in sessions around git operations.
|
||||
Staleness: revisit if user changes repo workflow.
|
||||
```
|
||||
|
||||
```text
|
||||
Current focus: web Workspace console and Workspace-backed Ticket/Skill authority.
|
||||
Source: recent Tickets and Objective updates.
|
||||
Expected to change after current milestone.
|
||||
```
|
||||
|
||||
### 3.2 Knowledge
|
||||
|
||||
Knowledge は long-term note system。人間と agent が育て、改訂し、link し、split / merge しながら読むもの。抽出 snippet の山ではなく、project understanding の mesh を形成する。
|
||||
|
||||
Target Knowledge は、古い未使用の Knowledge feature をそのまま残すものではない。legacy implementation は先に削除してよい。置き換えは proper workspace note subsystem として設計する。
|
||||
|
||||
Knowledge notes の要件:
|
||||
|
||||
- Markdown-first で人間が読める。
|
||||
- OKF-compatible な concept document として扱える。
|
||||
- stable path / slug を持ち、OKF concept ID として参照できる。
|
||||
- Yoi 内部で move / rename に強い identity が必要な場合は、extension frontmatter として `yoi_id` を持てる。
|
||||
- bidirectional links / backlinks を support する。
|
||||
- Obsidian-style wiki links (`[[slug]]`, `[[slug|label]]`) を support し、Knowledge mesh の authoring shorthand として使える。
|
||||
- 必要なら tags や typed relations を support する。
|
||||
- 重要な claim には provenance / citations を残す。
|
||||
- Tickets、Objectives、docs、commits、reports、Skills、他 Knowledge notes に link できる。
|
||||
- review / staleness / supersession を support する。
|
||||
- 自動生成だけに頼らず、意図的に maintain される。
|
||||
|
||||
Knowledge は、長期 architecture note、conceptual model、subsystem explanation、decision context、recurring constraints、domain understanding を育てる場所である。
|
||||
|
||||
#### 3.2.1 Knowledge format profile: OKF-compatible bundle
|
||||
|
||||
Yoi Knowledge は、可能な限り Open Knowledge Format (OKF) compatible な bundle として設計する。
|
||||
|
||||
OKF から採用する baseline:
|
||||
|
||||
- Knowledge bundle は Markdown file tree とする。
|
||||
- non-reserved `.md` file は concept document とする。
|
||||
- concept document は YAML frontmatter + Markdown body とする。
|
||||
- path without `.md` を OKF concept ID として扱う。
|
||||
- `type` は required field とする。
|
||||
- `title`, `description`, `resource`, `tags`, `timestamp` は recommended field とする。
|
||||
- normal Markdown links を OKF-compatible graph edges として扱う。
|
||||
- Obsidian-style wiki links (`[[slug]]`, `[[slug|label]]`) も graph edges として扱い、Markdown links へ解決・export できるようにする。
|
||||
- `index.md` は progressive disclosure のための directory listing として使える。
|
||||
- `log.md` は agent-readable update history として使える。
|
||||
- `# Citations` section は external source / authority reference を示す convention として使う。
|
||||
- consumers は unknown frontmatter fields、unknown `type`、broken links を tolerant に扱う。
|
||||
|
||||
Yoi は OKF に extension frontmatter を足してよい。候補:
|
||||
|
||||
```yaml
|
||||
yoi_id: 00001...
|
||||
status: draft | active | stale | superseded
|
||||
source_refs: []
|
||||
authority_refs: []
|
||||
objective_refs: []
|
||||
ticket_refs: []
|
||||
skill_refs: []
|
||||
reviewed_at: 2026-07-16T00:00:00Z
|
||||
staleness: "Revisit when ..."
|
||||
supersedes: []
|
||||
superseded_by: null
|
||||
```
|
||||
|
||||
Filesystem shape の例:
|
||||
|
||||
```text
|
||||
.yoi/knowledge/
|
||||
index.md
|
||||
log.md
|
||||
architecture/
|
||||
index.md
|
||||
memory-architecture.md
|
||||
workspace-authority.md
|
||||
references/
|
||||
pirolli-card-2005-sensemaking.md
|
||||
```
|
||||
|
||||
OKF compatibility は Knowledge の exchange / storage profile であり、Memory / staging / Skill の format ではない。
|
||||
|
||||
- Memory は短期・変化前提の resident context store なので OKF にしない。
|
||||
- staging は extract / consolidation の審査キューなので OKF にしない。
|
||||
- Skill は Agent Skills format を維持する。OKF `type: Playbook` に吸収しない。
|
||||
|
||||
#### Knowledge examples
|
||||
|
||||
- `workspace-authority-model`
|
||||
- Workspace backend が Tickets / Skills / Runtime views の authority である理由を説明する。
|
||||
- Ticket backend API design、Skill support ticket、Workspace control plane Objective に link する。
|
||||
- `memory-knowledge-skill-boundary`
|
||||
- Memory / Knowledge / Skills の boundary を定義する。
|
||||
- この architecture resource と将来の implementation Tickets に link する。
|
||||
- `ticket-lifecycle-authority`
|
||||
- Ticket state authority と transition graph の rationale を説明する。
|
||||
- 関連 decisions と code locations に link する。
|
||||
|
||||
### 3.3 Skill
|
||||
|
||||
Skill は、ある種類の task に対して確立された、portable な workflow / procedure。Agent Skills format に従う。
|
||||
|
||||
```text
|
||||
.yoi/skills/<skill-name>/
|
||||
SKILL.md
|
||||
scripts/
|
||||
references/
|
||||
assets/
|
||||
```
|
||||
|
||||
Skill は state machine ではなく、external authority も所有しない。agent が available tools を使って task をどう実行するかを示す prompt/resource guidance である。
|
||||
|
||||
Skill に含めるもの:
|
||||
|
||||
- いつその Skill を使うか。
|
||||
- step-by-step procedure。
|
||||
- expected inputs。
|
||||
- expected outputs / report shape。
|
||||
- examples。
|
||||
- edge cases。
|
||||
- optional references / scripts / assets。
|
||||
|
||||
Skill は、project-specific assumptions が少なく、別 workspace に移しても使えるとき portable と言える。
|
||||
|
||||
#### Skill examples
|
||||
|
||||
- `coder-review-cycle`
|
||||
- Coder が実装、検証、review request、feedback 対応、dossier 作成をどう行うか。
|
||||
- `ticket-intake`
|
||||
- 曖昧な user request を accepted Ticket requirements に変換する方法。
|
||||
- `architecture-review`
|
||||
- design proposals、alternatives、authority boundaries を評価する方法。
|
||||
|
||||
## 4. Resource boundaries and authority
|
||||
|
||||
### 4.1 Memory vs Knowledge
|
||||
|
||||
Memory は provisional / change-oriented。Knowledge は maintained / growth-oriented。
|
||||
|
||||
Memory を使うべきとき:
|
||||
|
||||
- 情報が短命。
|
||||
- preference や working assumption である。
|
||||
- resident context として有用。
|
||||
- 長期的な置き場所がまだ明確でない。
|
||||
|
||||
Knowledge を使うべきとき:
|
||||
|
||||
- 時間をかけて読み直し、改訂するべき情報。
|
||||
- durable project concept を説明する情報。
|
||||
- 複数の future tasks から link されるべき情報。
|
||||
- backlinks / mesh structure が有用な note。
|
||||
- 人間が project understanding として browse できるべきもの。
|
||||
|
||||
Promotion path:
|
||||
|
||||
```text
|
||||
Memory observation -> candidate note/update -> Knowledge note / docs / Ticket decision
|
||||
```
|
||||
|
||||
Promotion は明示的に行う。すべての Memory item が Knowledge になるわけではない。
|
||||
|
||||
### 4.2 Knowledge vs Docs
|
||||
|
||||
Docs は public / project-facing な maintained exposition。Knowledge は internal で、link され、発展する understanding。
|
||||
|
||||
Knowledge note は後で doc になり得るが、threshold は違う:
|
||||
|
||||
- Knowledge は uncertainty、partial models、evidence links を含められる。
|
||||
- Docs は settled explanations または user/developer guidance を提示するべき。
|
||||
|
||||
### 4.3 Knowledge vs Ticket decisions
|
||||
|
||||
Ticket decisions は work item history と state の authority。Knowledge notes は複数 Ticket をまたいだ synthesis。
|
||||
|
||||
ある decision が Ticket の requirement、state、acceptance criteria を変えるなら、それは Ticket に記録する。Knowledge はそこに link し、より広い pattern を説明できる。
|
||||
|
||||
### 4.4 Skill vs Knowledge
|
||||
|
||||
Knowledge は「何が true か」「project をどう理解するか」を説明する。Skill は「recurring task をどう実行するか」を説明する。
|
||||
|
||||
Skill を使うべきとき:
|
||||
|
||||
- review process。
|
||||
- implementation process。
|
||||
- release checklist。
|
||||
- architecture evaluation method。
|
||||
|
||||
Knowledge を使うべきとき:
|
||||
|
||||
- Workspace authority model。
|
||||
- Memory architecture。
|
||||
- Ticket lifecycle rationale。
|
||||
|
||||
### 4.5 Skill vs Feature/Plugin
|
||||
|
||||
Skill は prompt/resource guidance。Feature/Plugin は executable authority と tool surface。
|
||||
|
||||
Skill は「何をどう進めるか」を書けるが、Ticket、Workspace、Memory、外部状態を変更する権限そのものは持たない。その権限は Feature/Plugin や typed tool/API が持つ。
|
||||
|
||||
例: Skill は「review 前に Ticket shoebox を作る」と指示できる。実際に作成する authority は Workspace / Memory feature の typed tool/API が提供する。
|
||||
|
||||
## 5. Workspace API authority
|
||||
|
||||
Target architecture は Workspace-backed にする。
|
||||
|
||||
### 5.1 Memory API
|
||||
|
||||
Workspace backend が最終的に提供するもの:
|
||||
|
||||
- Memory list / search / read / write / edit / delete。
|
||||
- resident memory summary の生成または取得。
|
||||
- staleness / supersession markers。
|
||||
- sessions や artifacts からの Memory candidate proposal。
|
||||
- provenance と audit events。
|
||||
- preference / current-focus surfaces。
|
||||
|
||||
移行期間中は local `.yoi/memory` を compatibility / offline storage として残してよい。初期 storage profile は H2 section ベースの bounded `.yoi/memory/memory.md` だが、これは API contract ではない。
|
||||
|
||||
### 5.2 Knowledge API
|
||||
|
||||
Workspace backend は、raw filesystem layout を唯一の interface にするのではなく、proper note API を提供する。
|
||||
|
||||
- Knowledge catalog / list / search。
|
||||
- note read / write / edit / delete。
|
||||
- OKF-compatible frontmatter validation / normalization。
|
||||
- link / backlink extraction from Markdown links and wiki links (`[[slug]]`)。
|
||||
- relation / tag metadata。
|
||||
- staleness / supersession markers。
|
||||
- note diagnostics / lint。
|
||||
- source / provenance refs and citations。
|
||||
- Markdown files からの import / export。
|
||||
- OKF bundle import / export profile。
|
||||
|
||||
Filesystem representation は `.yoi/knowledge/` 配下の OKF-compatible bundle を第一候補にする。ただし Worker / Runtime / Web / CLI は、利用可能なら raw filesystem ではなく Workspace API view に収束する。
|
||||
|
||||
### 5.3 Skill API
|
||||
|
||||
Skill support は separate Skill Ticket の方針に従う。
|
||||
|
||||
- Workspace backend が discovery / lint / catalog / activation を所有する。
|
||||
- `.yoi/skills/<skill>/SKILL.md` は workspace storage convention。
|
||||
- `WorkspaceClient::Http` が使えるとき、Workers は Workspace API から Skill metadata / body を使う。
|
||||
- Skill references / assets は backend-resolved authority または Skill resource APIs 経由で access する。
|
||||
|
||||
## 6. Session-to-resource pipeline
|
||||
|
||||
この章が extract 改善の中心である。`extract -> staging -> consolidate` の分割は維持する。3 つは同じ memory maintenance pipeline の一部だが、判断の種類が違う。
|
||||
|
||||
```text
|
||||
Session history
|
||||
-> Overview + Evidence index
|
||||
-> extract
|
||||
-> staging
|
||||
-> consolidation
|
||||
-> Memory / Knowledge candidate / Skill candidate / Ticket-doc candidate / discard
|
||||
```
|
||||
|
||||
役割の要約:
|
||||
|
||||
- **extract**: session から候補を拾う。recall 寄り。Memory 化はしない。
|
||||
- **staging**: provenance 付き候補キュー。まだ Memory ではない。
|
||||
- **consolidation**: staging を審査・剪定・統合する。precision 寄り。Memory 肥大化と陳腐化を防ぐ。
|
||||
|
||||
### 6.1 Overview: transcript as semantic backbone
|
||||
|
||||
Overview は、committed history にある user messages と normal Assistant text outputs から作る。Assistant text outputs には、最終応答だけでなく、長い作業中の Progress message も含める。
|
||||
|
||||
Progress message は専用 Tool ではなく、ordinary user-visible prose response として残す。Tool surface を増やさず、ユーザーへの進捗報告と extract 用 semantic summary を兼ねるためである。
|
||||
|
||||
Overview に含めるもの:
|
||||
|
||||
- user requests / corrections / approvals。
|
||||
- Assistant progress messages。
|
||||
- Assistant final responses。
|
||||
- parent delegation や Ticket context のように、history に commit された task context。
|
||||
- tool evidence への bounded index。
|
||||
|
||||
Overview に含めないもの:
|
||||
|
||||
- raw reasoning / chain-of-thought。
|
||||
- raw tool-result content 全文。
|
||||
- secret-like data。
|
||||
- history に commit されていない hidden context injection。
|
||||
|
||||
Overview の意図は、extract worker に「何のための探索だったか」「どの判断が節目だったか」「どこが未解決か」を伝えることである。Overview は authority ではない。durable output には evidence と source anchors が必要である。
|
||||
|
||||
### 6.2 Extract: candidate generation
|
||||
|
||||
Extract は candidate generation であり、Memory 化ではない。runtime 側の extract worker が Overview と session evidence を読んで、後で記憶化を検討すべき **flat candidate records** を staging に出す。
|
||||
|
||||
Extract の責務:
|
||||
|
||||
- Overview を読んで、記憶化を検討すべき candidate を見つける。
|
||||
- 必要な箇所だけ evidence tools で確認する。
|
||||
- candidate ごとに bounded evidence snippets / source anchors を選ぶ。
|
||||
- candidate ごとに `stage_candidate` を呼び、1 candidate = 1 staging record として保存する。
|
||||
- 最後に `finish_extraction` を呼び、staged count または NOP reason を残す。
|
||||
|
||||
Extract がしてはいけないこと:
|
||||
|
||||
- Memory / Knowledge / Skill / Ticket / docs を直接変更しない。
|
||||
- batch payload として複数 candidate を 1 staging record にまとめない。
|
||||
- tool result 全文や reasoning を無制限に取り込まない。
|
||||
- local slice だけから根拠を推測しない。
|
||||
- tool call chronology、generic progress、current focus update を抽出しない。
|
||||
- 「進捗報告があった」こと自体を Memory 化しない。
|
||||
|
||||
Staging candidate は、Consolidation がそれ単体で discard / merge / promote / defer を判断できる最小単位にする。
|
||||
|
||||
旧 `decisions` / `discussions` / `attempts` / `requests` batch schema は維持しなくてよい。互換性よりも、flat candidate records と clear responsibility を優先する。
|
||||
|
||||
#### 6.2.1 Extract candidate taxonomy
|
||||
|
||||
Extract が抽出する対象は activity log ではない。抽出対象は次の candidate kinds に絞る。
|
||||
|
||||
| kind | extract で見る観点 | consolidation での扱い |
|
||||
| --- | --- | --- |
|
||||
| `preference` | ユーザーまたは workspace の継続的な好み・作法。単発指示ではなく、今後の agent behavior に効くもの。 | Memory に短く merge / replace する候補。既存 preference と重複するなら統合。Ticket/docs の要件そのものなら mirror せず authority link へ。曖昧なら discard / defer。 |
|
||||
| `working_assumption` | 現時点で仮に置いている設計・実装前提。future work に影響し、変更条件や反証条件があり得るもの。 | Memory に入れる場合は short-lived assumption として stale condition 必須。長期設計なら Knowledge candidate。すでに authority record に反映済みなら Memory には pointer だけ、または discard。 |
|
||||
| `constraint` | 今後守るべき境界・禁止・invariant。実装や review でチェック可能なもの。 | active implementation に効くなら Memory。durable policy なら Ticket decision / Knowledge / docs candidate。破られた既存 Memory があれば mark_stale / replace。 |
|
||||
| `decision` | alternatives / chosen / rationale がある判断。単なる事実確認や作業進行ではないもの。 | まず authority routing を判断する。Ticket 要件・state・acceptance に関係するなら Ticket decision/comment candidate。長期設計なら Knowledge/Objective candidate。短期実装判断だけ Memory に短く置く。会話中の一時結論なら discard。 |
|
||||
| `open_question` | 未解決で後続作業に影響する問い。next action が書けるもの。単なる会話中の疑問ではない。 | Memory reminder にするか、Ticket follow-up / planning item に送る。解決済みなら discard。長く残る概念的問いなら Knowledge candidate。TTL / defer reason を持たせる。 |
|
||||
| `lesson` | 検証・失敗・試行から得た再利用価値のある学び。同じ失敗を避ける、作業方法を改善する、Skill 化できる可能性があるもの。 | Memory に短く残すか、recurring / portable なら Skill candidate。単なる tool execution result は discard。validation evidence として Ticket/report に残すべきものは authority link。 |
|
||||
|
||||
抽出しないもの:
|
||||
|
||||
- `current_focus` update。これは resident summary surface であり、extract candidate kind ではない。
|
||||
- tool call chronology。
|
||||
- file read/write history。
|
||||
- generic progress updates。
|
||||
- one-off chit-chat。
|
||||
- resolved local confusion。
|
||||
- assistant self-corrections without durable consequence。
|
||||
- authoritative Ticket/docs/git facts copied verbatim。
|
||||
- validation results unless they imply a reusable lesson, active blocker, or authority evidence。
|
||||
- implementation details that belong only in commit diff。
|
||||
|
||||
### 6.3 Extract worker tools
|
||||
|
||||
extract worker には `session-explore` feature の constrained tools だけを渡す。これは main Worker の tool 数を増やすものではない。
|
||||
|
||||
Tool surface:
|
||||
|
||||
- `search_evidence`: session snapshot / tool summaries / message index から query で候補 range を探す。
|
||||
- `read_evidence`: bounded な session entry range、tool call/result summary、host が許す bounded excerpt を読む。
|
||||
- `stage_candidate`: 1 candidate を staging record として保存する。
|
||||
- `finish_extraction`: extract run を終了し、staged count または NOP reason を記録する。
|
||||
|
||||
`stage_candidate` は direct Memory write ではない。これは staging write だけを行う output tool である。
|
||||
|
||||
`stage_candidate` input の概念形:
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "decision",
|
||||
"claim": "Overview is runtime-only extract projection, not staging data.",
|
||||
"why_useful": "Clarifies extract/consolidate responsibility boundary.",
|
||||
"staleness": "Revisit if Workspace can directly explore runtime sessions.",
|
||||
"evidence_ids": ["E001", "E002"]
|
||||
}
|
||||
```
|
||||
|
||||
Host wrapper は `evidence_ids` を runtime reference environment から解決し、bounded evidence snippets と source anchors を staging record に機械的に付与する。LLM に自由な source anchor を書かせない。
|
||||
|
||||
`finish_extraction` の概念形:
|
||||
|
||||
```json
|
||||
{
|
||||
"staged_count": 0,
|
||||
"reason": "Only local progress and tool chronology; no durable candidates."
|
||||
}
|
||||
```
|
||||
|
||||
候補が無い場合は `stage_candidate` を呼ばず、`finish_extraction` で NOP を明示する。tool call なし終了も host 側では NOP fallback として扱ってよいが、基本は `finish_extraction` を要求する。
|
||||
|
||||
制約:
|
||||
|
||||
- read-only evidence access。file write、Ticket mutation、Memory/Knowledge/Skill direct write は持たせない。
|
||||
- bounded output。large tool result は summary / excerpt / pointer に留める。
|
||||
- provenance first。extract worker が根拠を推測せず、読んだ evidence ids を `stage_candidate` に渡す。
|
||||
|
||||
### 6.4 Staging: flat provenance-backed candidate records
|
||||
|
||||
Staging は Memory ではない。Staging は、extract が切り出した candidate を provenance 付きで保管し、consolidation が後で審査できるようにする queue である。
|
||||
|
||||
Staging は flat records にする。
|
||||
|
||||
```text
|
||||
1 extract run = 0..N staging records
|
||||
1 staging record = 1 candidate = 1 consolidation decision unit
|
||||
```
|
||||
|
||||
1 record の概念形:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": 2,
|
||||
"id": "stg_...",
|
||||
"extract_run_id": "er_...",
|
||||
"source": {
|
||||
"segment_id": "segment-1",
|
||||
"range": [120, 180]
|
||||
},
|
||||
"kind": "constraint",
|
||||
"claim": "Extract worker must not direct-write Memory/Knowledge/Skill; it only writes staging.",
|
||||
"why_useful": "Preserves runtime/embedded responsibility boundary.",
|
||||
"staleness": "Revisit if extract and consolidation move into the same Workspace execution context.",
|
||||
"evidence": [
|
||||
{
|
||||
"id": "E001",
|
||||
"kind": "message",
|
||||
"entry_range": [132, 133],
|
||||
"excerpt": "extract worker は Memory / Knowledge / Skill を直接更新しない",
|
||||
"summary": "User and architecture discussion fixed staging-only extract boundary."
|
||||
}
|
||||
],
|
||||
"source_refs": [
|
||||
{
|
||||
"evidence_id": "E001",
|
||||
"evidence_kind": "message",
|
||||
"entry_range": [132, 133]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Staging の責務:
|
||||
|
||||
- candidate kind / claim / hints を保持する。
|
||||
- extract が選んだ bounded evidence snippets を保持する。
|
||||
- source anchors を保持する。
|
||||
- extract と consolidation を decouple する。
|
||||
- duplicate / defer / discard / consumed の追跡対象になる。
|
||||
- crash / cancel / long-running work の途中でも、後から審査できる候補を残す。
|
||||
|
||||
Staging がしてはいけないこと:
|
||||
|
||||
- Memory として resident context に直接入らない。
|
||||
- Knowledge note の代替にならない。
|
||||
- raw session log や Overview 全体の保存場所にならない。
|
||||
- extract run 単位の batch file として複数 candidate を抱え込まない。
|
||||
- staging entry をすべて Memory 化する前提にしない。
|
||||
|
||||
Pirolli & Card 的には、staging は shoebox / evidence file に近い。ただし Yoi の storage taxonomy そのものを shoebox にするのではなく、審査前の evidence-backed candidate queue として扱う。
|
||||
|
||||
### 6.5 Staging resolution: the important filter
|
||||
|
||||
Staging から Memory 化する段階が、Memory 肥大化と陳腐化を防ぐ中核 filter である。
|
||||
|
||||
Consolidation は staging entry ごとに resolution / disposition を決めるべきである。
|
||||
|
||||
Disposition action 候補:
|
||||
|
||||
- `discard`: 保存価値なし。discard reason を残す。
|
||||
- `merge_memory`: 既存 Memory に統合する。
|
||||
- `replace_memory`: 古い Memory を新しい内容で置換する。
|
||||
- `mark_memory_stale`: 既存 Memory が古くなったことを記録する。
|
||||
- `delete_memory`: 邪魔または誤った Memory を削除する。
|
||||
- `defer`: 価値判断できないため短期保留する。TTL / defer reason を持つ。
|
||||
- `promote_to_knowledge_candidate`: long-term note に育てる候補へ送る。
|
||||
- `promote_to_skill_candidate`: recurring / portable procedure の候補へ送る。
|
||||
- `link_to_authority`: Ticket / doc / commit / Objective への pointer だけ残す。
|
||||
- `create_ticket_or_doc_candidate`: authority / public guidance にすべき候補として送る。
|
||||
|
||||
Resolution に残すべき情報:
|
||||
|
||||
- staging entry id。
|
||||
- action。
|
||||
- reason。
|
||||
- source anchors。
|
||||
- target record / candidate destination。
|
||||
- consolidation run id / consumed_by。
|
||||
- reviewed_at。
|
||||
- discard / defer / stale reason。
|
||||
|
||||
Consumed staging を削除する場合も、resolution は残す。これにより「何を Memory にしなかったか」が改善材料として残る。
|
||||
|
||||
### 6.6 Consolidation: memoryization, routing, and gardening
|
||||
|
||||
Consolidation は precision-oriented pass である。staging を読んで、Memory にするか、別 resource candidate に送るか、捨てるかを決める。
|
||||
|
||||
Consolidation の責務:
|
||||
|
||||
- staging entry を candidate kind ごとの扱いに沿って評価する。
|
||||
- Memory 化するなら usefulness / staleness / source を要求する。
|
||||
- 既存 Memory と merge / replace / mark stale / delete する。
|
||||
- decision / constraint / lesson などを authority / Knowledge / Skill / Ticket / docs candidate に routing する。
|
||||
- discard reason / defer reason を残す。
|
||||
- linter feedback / usage evidence / tidy hints を使う。
|
||||
- Memory bloat と stale records を防ぐ。
|
||||
|
||||
Consolidation がしてはいけないこと:
|
||||
|
||||
- staging entry を無条件に append しない。
|
||||
- Ticket / docs / git / session log の mirror を Memory に作らない。
|
||||
- Knowledge / Skill / docs を background review だけで無制限に rewrite しない。
|
||||
- source / provenance のない claim を durable Memory にしない。
|
||||
|
||||
Memory 化に必要な minimum fields / prose:
|
||||
|
||||
```text
|
||||
content: 何を覚えるか
|
||||
why_useful: 今後なぜ役立つか
|
||||
source: staging / evidence anchor
|
||||
staleness: 何が起きたら古くなるか
|
||||
destination_reason: なぜ Knowledge / Skill / Ticket / docs ではなく Memory なのか
|
||||
```
|
||||
|
||||
Consolidation は append worker ではなく garden worker である。新規作成より、既存 record の統合・置換・陳腐化・削除を優先する。
|
||||
|
||||
### 6.7 Trigger policy
|
||||
|
||||
発火単位は LLM call 単位にしない。LLM call 単位では文脈が薄く、断片的な extraction になりやすい。
|
||||
|
||||
初期方針:
|
||||
|
||||
- 現行通り、Worker run cycle が完了してから threshold を判定し、超えていれば extract を発火する。
|
||||
- extract 開始時点で immutable snapshot / Overview projection / Evidence index を作る。
|
||||
- LLM call ごとには発火しない。
|
||||
- Run 中の Overview accumulation trigger / mid-run extract は初期実装に含めない。
|
||||
- 将来 long-running 中に mid-run 発火を入れる場合も、direct update ではなく staging/checkpoint extraction に限定する。
|
||||
|
||||
この trigger は、まず既存の post-run memory job model を保ち、extract の入力品質と staging record 粒度の改善に集中する。
|
||||
|
||||
## 7. Sensemaking interpretation
|
||||
|
||||
Sensemaking は storage taxonomy ではなく、pipeline の見方として使う。
|
||||
|
||||
Pirolli & Card の flow を Yoi に対応させるとこうなる:
|
||||
|
||||
```text
|
||||
External data sources
|
||||
= session log, tool results, Tickets, docs, code, web refs
|
||||
|
||||
Shoebox
|
||||
= Overview + Evidence index + selected candidate ranges
|
||||
|
||||
Evidence file
|
||||
= source anchors 付き staging entries
|
||||
|
||||
Schemas / hypotheses
|
||||
= Memory 化すべきか、Knowledge/Skill に送るべきか、stale かの判断
|
||||
|
||||
Product
|
||||
= Memory update, Knowledge candidate, Skill candidate,
|
||||
Ticket/doc update, review evidence, discard resolution
|
||||
```
|
||||
|
||||
重要なのは、Memory 化を evidence から product へ進める審査の一形態として扱うこと。Memory record は「将来の作業に効く」という hypothesis なので、why_useful、staleness、source が必要である。
|
||||
|
||||
初期 sensemaking support は lightweight でよい:
|
||||
|
||||
- task-bound collected references as Ticket/Objective artifacts。
|
||||
- provenance 付き evidence summaries。
|
||||
- review Skills における explicit contradictory evidence sections。
|
||||
- recurring patterns を synthesize する Knowledge notes。
|
||||
- staging resolution に discard / defer / promote reason を残す。
|
||||
|
||||
## 8. Human-readable growth paths
|
||||
|
||||
中核要件は、有用な material が人間に読める形へ成長できること。
|
||||
|
||||
Typical paths:
|
||||
|
||||
```text
|
||||
Session observation
|
||||
-> staging candidate
|
||||
-> Memory record
|
||||
-> Knowledge note candidate
|
||||
-> Knowledge note with links/backlinks
|
||||
-> maintained doc or Ticket decision if it becomes authority
|
||||
```
|
||||
|
||||
```text
|
||||
Repeated successful procedure
|
||||
-> staging candidate / Memory observation / Ticket comment
|
||||
-> Skill candidate
|
||||
-> `.yoi/skills/<skill>/SKILL.md`
|
||||
-> builtin or shared Skill if portable
|
||||
```
|
||||
|
||||
```text
|
||||
Design discussion
|
||||
-> Objective resource
|
||||
-> Knowledge note synthesis
|
||||
-> implementation Tickets
|
||||
-> docs after stabilization
|
||||
```
|
||||
|
||||
Architecture は、これらの promotion を explicit and reviewable にする。
|
||||
|
||||
## 9. External reference lessons
|
||||
|
||||
### 9.1 Current Yoi pipeline
|
||||
|
||||
現在の Yoi extraction は activity-log pipeline:
|
||||
|
||||
- `build_extract_input` が conversation slice を flat Markdown として render する。
|
||||
- user / assistant text は保持する。
|
||||
- tool-call names を含める。
|
||||
- raw tool-result content ではなく tool-result summaries のみを含める。
|
||||
- reasoning は落とす。
|
||||
- extract worker の tool は `write_extracted` 1 つだけ。
|
||||
- `write_extracted` は `decisions`、`discussions`、`attempts`、`requests` を持つ structured `ExtractedPayload` を 1 件受け取る。
|
||||
- LLM は provenance を作らない。Worker が `StagingRecord` を書くときに `source` を機械的に付与する。
|
||||
- empty payload は valid で、no-op として扱える。
|
||||
- consolidation は後で staging entries、full current Memory records、usage evidence、tidy hints を consume する。
|
||||
- consolidation は Memory tools 経由で write し、linter feedback に対応し、record を merge / replace し、outdated / superseded / unused / noisy records を clean up する。
|
||||
|
||||
残す価値がある点:
|
||||
|
||||
- foreground 応答生成から memory maintenance が隔離されている。
|
||||
- provenance が host 側で機械付与される。
|
||||
- extract が direct write せず staging を挟む。
|
||||
- empty / no-op が許される。
|
||||
- consolidation が linter feedback と tidy hints を使う。
|
||||
|
||||
変えるべき点:
|
||||
|
||||
- flat slice ではなく Overview-first / Evidence-index-second にする。
|
||||
- extract worker に read-only evidence tools を持たせる。
|
||||
- staging を一時バッファではなく審査キューとして扱う。
|
||||
- consolidation に entry-level disposition / resolution を要求する。
|
||||
- legacy Knowledge as generated memory 前提を外す。
|
||||
|
||||
### 9.2 HermesAgent
|
||||
|
||||
HermesAgent は background review により、conversation snapshot から Memory / Skill update を直接判断する。
|
||||
|
||||
参考になる点:
|
||||
|
||||
- maintenance を foreground interaction から隔離する。
|
||||
- 保存すべきものがなければ NOP にする。
|
||||
- Memory と Skill を分ける。
|
||||
- prompt snapshot / drift / injection guard を重視する。
|
||||
|
||||
そのまま採用しない点:
|
||||
|
||||
- direct write-only review を主経路にしない。
|
||||
- aggressive Skill update bias を避ける。
|
||||
- `MEMORY.md` / `USER.md` two-file model を Yoi 全体の architecture にしない。
|
||||
|
||||
Yoi では、Hermes 的な direct maintenance は bounded Memory update lane では参考になるが、extract worker 自体は direct write しない。
|
||||
|
||||
### 9.3 Codex
|
||||
|
||||
Codex は session / rollout を durable source として扱い、background pipeline が後から claim / extract / consolidate する。
|
||||
|
||||
参考になる点:
|
||||
|
||||
- durable source。
|
||||
- phase separation。
|
||||
- claim / lease / retry / global lock。
|
||||
- workspace diff / baseline guard。
|
||||
- extraction と consolidation の分離。
|
||||
|
||||
Yoi では、Codex 的な durable job / phase pipeline は staging resolution と consolidation scheduling に取り入れる価値がある。ただし、この architecture phase ではまず Overview-first extract と staging resolution を優先する。
|
||||
|
||||
## 10. Implementation posture
|
||||
|
||||
現在の実装は redesign してよい。既存の Memory / Knowledge shape があるからという理由で残さない。
|
||||
|
||||
ただし big-bang rewrite は避ける。この architecture が accepted されてから分割する。
|
||||
|
||||
Recommended implementation sequence:
|
||||
|
||||
1. **Knowledge removal 後の current Memory を clarify する**
|
||||
- short-term / resident Memory を維持する。
|
||||
- Memory が Knowledge を代替しなければならない、という前提を外す。
|
||||
- legacy `knowledge/*` を generated memory として扱う stale consolidation prompt language を削除する。
|
||||
|
||||
2. **Progress message guidance を追加する**
|
||||
- 長い作業や tool loop の節目で、main Worker が ordinary user-visible prose response として短い Progress message を残す。
|
||||
- 専用 Tool は追加しない。
|
||||
- Progress message は public に見せられる作業状態、確認済み事実、判断、未解決点、次の作業に限定する。
|
||||
|
||||
3. **Overview-first extract input を実装する**
|
||||
- user messages + Assistant text outputs を semantic Overview として優先する。
|
||||
- tool calls / tool results は Evidence index として分離する。
|
||||
- committed history だけから Overview を作り、hidden context injection を避ける。
|
||||
|
||||
4. **Extract worker 専用 evidence tools と staging output tools を実装する**
|
||||
- extract worker に read-only Evidence search / Evidence read を渡す。
|
||||
- main Worker の tool surface は増やさない。
|
||||
- output tools は `stage_candidate` / `finish_extraction` にする。
|
||||
- `stage_candidate` は 1 candidate = 1 flat staging record を書く。
|
||||
- source range と candidate record を結びつけられる schema / staging format を実装する。
|
||||
|
||||
5. **Staging-first extract を維持する**
|
||||
- extract worker は direct Memory / Knowledge / Skill write をしない。
|
||||
- overview accumulation / evidence growth / run or task boundary で extract を予約する。
|
||||
- mid-run 発火を入れる場合も staging/checkpoint extraction に限定する。
|
||||
|
||||
6. **Staging resolution / disposition を実装する**
|
||||
- staging entry ごとに discard / merge / replace / stale / delete / defer / promote / link を記録する。
|
||||
- consumed staging を削除する前に resolution log / archive を残す。
|
||||
- discard reason と defer reason を extract prompt 改善に使えるようにする。
|
||||
|
||||
7. **Target Knowledge note model を設計する**
|
||||
- OKF-compatible Markdown concept document / bundle profile。
|
||||
- Yoi extension frontmatter。
|
||||
- link / backlink model。
|
||||
- provenance / citations / staleness metadata。
|
||||
- Workspace API surface。
|
||||
- reviewable Knowledge updates の candidate / staging format。
|
||||
|
||||
8. **Consolidation / tidy lane を更新する**
|
||||
- staging entries、existing Memory、usage evidence、linter feedback、stale/noisy hints を統合する。
|
||||
- legacy Knowledge as generated memory 前提を外す。
|
||||
- Memory update、Knowledge candidate、Skill candidate を分けて扱う。
|
||||
|
||||
9. **Minimal Knowledge catalog/read/write を実装する**
|
||||
- OKF-compatible Markdown files と Workspace API から始める。
|
||||
- frontmatter validation / lint と backlinks を追加する。
|
||||
- `index.md` progressive disclosure と `# Citations` の扱いを実装する。
|
||||
|
||||
10. **Skill support を別に実装する**
|
||||
- Agent Skills standard と Workspace authority に従う。
|
||||
- Skill と Knowledge note schema を混ぜない。
|
||||
- automatic Skill modification は recurrence と portability で gate する。
|
||||
|
||||
11. **Promotion workflows/tools を追加する**
|
||||
- Memory -> Knowledge candidate。
|
||||
- Knowledge -> docs / Ticket decision candidate。
|
||||
- repeated procedure -> Skill candidate。
|
||||
|
||||
12. **Resource classes が安定してから sensemaking helpers を追加する**
|
||||
- collected refs。
|
||||
- evidence extraction。
|
||||
- contradiction / staleness views。
|
||||
- product-impact metrics。
|
||||
|
||||
## 11. Non-goals
|
||||
|
||||
- Memory を唯一の long-term knowledge store として扱うこと。
|
||||
- Knowledge を名前だけ変えた generated memory として扱うこと。
|
||||
- Skill を Workflow tracker や state machine として扱うこと。
|
||||
- Worker history / tool results の外で context injection を隠すこと。
|
||||
- Knowledge notes を Tickets / docs / git history より authoritative にすること。
|
||||
- review なしに Knowledge / Skills / docs を自動 rewrite すること。
|
||||
- extract worker に direct resource mutation authority を持たせること。
|
||||
- extract run 単位の batch staging record に複数 candidate を抱え込ませること。
|
||||
- staging entry をすべて Memory 化すること。
|
||||
- human-readable artifact model が安定する前に vector database を設計すること。
|
||||
- volatile Memory や staging queue を OKF concept document として扱うこと。
|
||||
- H2 Markdown single-file layout を Workspace Memory API の長期 contract として固定すること。
|
||||
- Agent Skills format を OKF Playbook documents で置き換えること。
|
||||
|
||||
## 12. Open decisions
|
||||
|
||||
- Knowledge bundle の exact directory organization: flat files、nested directories、domain directories のどれを初期推奨にするか。
|
||||
- Yoi extension frontmatter の exact fields。
|
||||
- Yoi stable `yoi_id` を必須にするか optional にするか。
|
||||
- OKF `type` values の初期 convention をどうするか。
|
||||
- Link syntax は OKF-compatible Markdown links と Obsidian-style wiki links (`[[slug]]`, `[[slug|label]]`) を support する。canonical internal representation と export normalization をどうするか。
|
||||
- Knowledge note IDs / slugs と titles の関係。
|
||||
- Memory は local-first のままにするか、Knowledge と同じ phase で Workspace API-first にするか。
|
||||
- Memory / Knowledge APIs は同じ crate にするか、別 domain crates にするか。
|
||||
- Memory -> Knowledge の promotion UI/tool をどうするか。
|
||||
- personal Memory と workspace Memory をどう区別するか。
|
||||
- Knowledge drafts の auto-generation をどこまで許すか。
|
||||
- extract worker 専用 evidence tools の exact API: search/read の引数、上限、evidence id format。
|
||||
- `stage_candidate` / `finish_extraction` の exact tool schema。
|
||||
- flat staging record の exact fields: `claim`, `why_useful`, `staleness`, `evidence`, `source_refs`, `extract_run_id` など。
|
||||
- Overview trigger を overview token count、Assistant message count、evidence growth、run/task boundary のどれで制御するか。
|
||||
- mid-run extract をどこまで許すか。初期は staging/checkpoint extraction に限定する方針。
|
||||
- staging resolution log / archive の保持期間と compact policy。
|
||||
- どの Memory sidecar writes を direct に許し、どれを staged proposals にするか。extract worker 自体は direct write しない。
|
||||
- Skill sidecar output は patches/proposals だけから始めるか、low-risk Skill edits を auto-apply してよいか。
|
||||
- Memory / Knowledge / Skill Workspace APIs をまたぐ sidecar audit events をどう表現するか。
|
||||
- prompt-cache-aware sidecar input で full replay と digest-plus-tail をどう選ぶか。
|
||||
|
||||
## 13. Exit criteria for architecture phase
|
||||
|
||||
この architecture は、次が満たされたら Tickets に分割できる。
|
||||
|
||||
- Memory / Knowledge / Skill boundary が accepted される。
|
||||
- OKF-compatible bundle / Markdown concept documents としての target Knowledge が accepted される。
|
||||
- Memory / Knowledge / Skills の Workspace backend authority が accepted される。
|
||||
- Overview-first extract、extract worker 専用 evidence tools、staging-first output の方針が accepted される。
|
||||
- staging resolution / disposition と、staging -> Memory 化 filter の方針が accepted される。
|
||||
- 最初の implementation slice が選ばれる。
|
||||
- non-goals が accepted され、old Workflow tracking や old unused Knowledge をそのまま再作成しないことが確認される。
|
||||
@@ -0,0 +1,137 @@
|
||||
---
|
||||
source_url: "https://andymatuschak.org/files/papers/Pirolli%2C%20Card%20-%202005%20-%20The%20sensemaking%20process%20and%20leverage%20points%20for%20analyst%20technology%20as.pdf"
|
||||
fetched_at: "2026-07-15T21:24:00Z"
|
||||
content_type: "application/pdf"
|
||||
notes: "Untrusted external reference captured as local Objective resource for Memory sensemaking design. This is a summary/extraction for design discussion, not project authority."
|
||||
---
|
||||
|
||||
# Pirolli & Card (2005): The sensemaking process and leverage points for analyst technology
|
||||
|
||||
## Citation / source
|
||||
|
||||
Peter Pirolli and Stuart Card, PARC. "The Sensemaking Process and Leverage Points for Analyst Technology as Identified Through Cognitive Task Analysis" (2005).
|
||||
|
||||
Source PDF: <https://andymatuschak.org/files/papers/Pirolli%2C%20Card%20-%202005%20-%20The%20sensemaking%20process%20and%20leverage%20points%20for%20analyst%20technology%20as.pdf>
|
||||
|
||||
## Core model
|
||||
|
||||
The paper frames intelligence analysis as a sensemaking task:
|
||||
|
||||
```text
|
||||
Information -> Schema -> Insight -> Product
|
||||
```
|
||||
|
||||
The analyst transforms raw data into progressively more structured representations so expertise can apply and so results can be communicated.
|
||||
|
||||
The paper's notional data flow is especially relevant to Yoi Memory design:
|
||||
|
||||
```text
|
||||
External data sources
|
||||
-> shoebox
|
||||
-> evidence file
|
||||
-> schemas
|
||||
-> hypotheses
|
||||
-> presentation / work product
|
||||
```
|
||||
|
||||
- **External data sources**: raw material, mostly text in the studied setting.
|
||||
- **Shoebox**: the smaller subset collected as relevant for the task.
|
||||
- **Evidence file**: extracted snippets / nuggets from the shoebox, plus low-level inferences.
|
||||
- **Schemas**: re-representations that organize information for analysis.
|
||||
- **Hypotheses**: tentative conclusions with supporting or disconfirming evidence.
|
||||
- **Product**: report / presentation / action suited for communication.
|
||||
|
||||
## Two major loops
|
||||
|
||||
The process has two interacting loops rather than a simple linear pipeline.
|
||||
|
||||
### Foraging loop
|
||||
|
||||
Activities aimed at finding and selecting information:
|
||||
|
||||
- search and filter external data sources;
|
||||
- collect potentially relevant material into a shoebox;
|
||||
- read and extract evidence snippets;
|
||||
- follow up on questions generated by extracted evidence.
|
||||
|
||||
The paper highlights the exploration / enrichment / exploitation tradeoff:
|
||||
|
||||
- **Exploration**: monitor or search more of the information space; increases recall.
|
||||
- **Enrichment**: narrow the collected set into smaller, higher-precision subsets.
|
||||
- **Exploitation**: read/extract/analyze the chosen material more thoroughly.
|
||||
|
||||
Analyst tooling can help by changing the cost structure of search, scanning, assessment, selection, attention shifting, and follow-up searches.
|
||||
|
||||
### Sensemaking loop
|
||||
|
||||
Activities aimed at structuring and reasoning:
|
||||
|
||||
- schematize evidence;
|
||||
- build a case;
|
||||
- generate / manage hypotheses;
|
||||
- marshal evidence for and against hypotheses;
|
||||
- tell a story / produce a report;
|
||||
- re-evaluate based on feedback or new evidence.
|
||||
|
||||
The paper emphasizes opportunistic mixing of bottom-up and top-down processing:
|
||||
|
||||
- **Bottom-up**: data triggers schemas, relations, hypotheses, and products.
|
||||
- **Top-down**: hypotheses / client feedback trigger new searches, re-reading, and re-organization.
|
||||
|
||||
## Leverage points
|
||||
|
||||
### Foraging loop leverage
|
||||
|
||||
- Cost structure of exploration / enrichment / exploitation.
|
||||
- Cost structure of scanning, recognizing, and selecting items for attention.
|
||||
- Cost of shifting attentional control to a new domain or task.
|
||||
- Cost of follow-up searches generated by extracted information.
|
||||
- Broad-band low-fidelity assessment plus narrow-band high-fidelity processing is a useful design pattern.
|
||||
|
||||
### Sensemaking loop leverage
|
||||
|
||||
- Span of attention for evidence, hypotheses, and evidentiary relations.
|
||||
- Generation of alternative hypotheses.
|
||||
- Confirmation bias and failure to seek disconfirming evidence.
|
||||
- External representations can expand working memory for evidence/hypothesis structures.
|
||||
- Tools should help distribute attention toward diagnostic evidence and disconfirming relations.
|
||||
|
||||
## Implications for Yoi Memory Objective
|
||||
|
||||
This paper directly supports the Objective's direction that effective Memory is not just durable storage or retrieval count.
|
||||
|
||||
Useful design implications:
|
||||
|
||||
1. **Task-bound shoebox**
|
||||
- For each Ticket / Objective / question, provide a bounded collection of potentially relevant materials.
|
||||
- Include provenance and why each item was collected.
|
||||
|
||||
2. **Evidence file**
|
||||
- Extract snippets from source material with source, applicability, confidence, and context.
|
||||
- Evidence should be usable for support and disconfirmation, not only recall.
|
||||
|
||||
3. **Schema / representation layer**
|
||||
- The system should help create intermediate structures: timelines, entity/relation maps, alternatives, checklists, hypothesis spaces, decision tables.
|
||||
- These are separate from raw Memory records.
|
||||
|
||||
4. **Hypotheses and alternatives**
|
||||
- Record competing explanations, rejected alternatives, open questions, and evidence gaps.
|
||||
- Avoid only storing final decisions.
|
||||
|
||||
5. **Disconfirming evidence**
|
||||
- Reviewer / Orchestrator support should explicitly search for contradiction and diagnostic evidence.
|
||||
- This belongs in Skill guidance and typed review tooling, not in a separate Knowledge store.
|
||||
|
||||
6. **Metrics**
|
||||
- Measure whether Memory changes product quality, review quality, decision quality, or time-to-evidence.
|
||||
- Avoid treating exposure/retrieval counts alone as success.
|
||||
|
||||
7. **Product connection**
|
||||
- The loop should end in a product: Ticket decision, implementation report, review, design doc, Skill update, or validated artifact.
|
||||
- Memory that never reaches a product is likely a graveyard.
|
||||
|
||||
## Boundary with current Yoi direction
|
||||
|
||||
- Knowledge as a separate record kind is being removed; this paper's reusable representations should map to Memory artifacts, Ticket artifacts, maintained docs, or Skills depending on authority.
|
||||
- Workflow tracking is being removed; procedural guidance such as "generate alternatives" or "seek disconfirming evidence" should live in Skills / role prompts and be enforced by typed tools where authority is needed.
|
||||
- Workspace backend should eventually be the authority for task-bound shoebox / evidence artifacts when Memory moves from local compatibility storage to control-plane records.
|
||||
@@ -0,0 +1,343 @@
|
||||
---
|
||||
title: "Runtime working directory materialization and sandboxed agent environments"
|
||||
state: "active"
|
||||
created_at: "2026-07-06T16:28:12Z"
|
||||
updated_at: "2026-07-10T16:50:00Z"
|
||||
linked_tickets: ["00001KWPC13WQ", "00001KWMBAA6V", "00001KX6BPY7M", "00001KX6CRVBE"]
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Runtime が Worker ごとに安全で安価な作業環境を用意できるようにする。Yoi の Runtime は、単に既存ディレクトリで Worker process を起動する launcher ではなく、RepositoryPoint から working directory を materialize し、sandbox / mount / cache / cleanup / evidence を管理する実行基盤になる。
|
||||
|
||||
この Objective の中心は、Worker 用 working directory の materialization、Repository cache、working directory allocation、sandboxed agent environment の境界を設計し、将来的に 1 つの Runtime が複数 Workspace / Repository の Worker を抱えられるようにすることである。
|
||||
|
||||
初期実装では Git/local repository を主対象にしてよい。ただし設計は Git worktree 固定にしない。Git worktree、bare object cache、sparse checkout、copy-on-write snapshot、reflink copy、APFS clonefile、btrfs snapshot、overlay filesystem、container filesystem、remote object snapshot は、すべて working directory materialization strategy の候補として扱う。
|
||||
|
||||
## Motivation / background
|
||||
|
||||
現在の Runtime は `--workspace` で与えられた単一 root を Worker の workspace scope として使う。この形では次の問題がある。
|
||||
|
||||
- 複数 Worker が同じ repository root を scope として要求すると allocation conflict が起きる。
|
||||
- Runtime が single Workspace / Git repository root 専用 process になってしまう。
|
||||
- Worker が source repository root を直接触るため、sandbox / cleanup / quota / evidence の境界が曖昧になる。
|
||||
- Worker ごとに full clone すると容量と時間のコストが大きすぎる。
|
||||
- `.yoi` / Backend fs-store / Workspace descriptor と、Worker 実行用 checkout / scratch / build output の lifecycle が混ざりやすい。
|
||||
|
||||
Yoi は Workspace / Repository / Runtime を分ける方針になっている。Backend は Repository registry、RepositorySelector、RepositoryPoint、Ticket/Artifact evidence の authority を持つ。一方 Runtime は RepositoryPoint を受け取り、Worker が使う working directory を用意する責務を持つべきである。
|
||||
|
||||
したがって、Repository を持ってくる実体ディレクトリは Backend / Workspace store ではなく Runtime 管理領域に置く。`./.yoi` は local descriptor / compatibility / project record surface であり、Worker ごとの checkout、worktree、snapshot、sandbox root、build cache、dependency cache を置く場所ではない。
|
||||
|
||||
参考になる方向性として、Rift のような copy-on-write workspace snapshot / reflink / btrfs snapshot / APFS clonefile による安価な workspace creation がある。ただし Yoi は Rift を Git worktree 代替 CLI として直接前提にするのではなく、Runtime materializer backend の一候補として扱う。
|
||||
|
||||
## Glossary
|
||||
|
||||
- Runtime root: Runtime が自身の store、cache、Worker metadata、working directory allocation を管理する root。長期的には `~/.yoi/runtimes/<runtime-id>/` 配下など、Workspace backend store とは別に置く。
|
||||
- Repository cache: Runtime-local の共有 source/cache。Git なら bare mirror / object cache / packfile cache など。重く、長寿命で、複数 Worker allocation から共有される。
|
||||
- working directory: Worker ごとの作業環境。短寿命で、Worker が読み書きする root / mounts / scratch / overlay を含む。Browser-facing UI では `workspace` と混同しないよう `workdir` と表示してよい。`Volume` は storage backing の候補名であり、この作業領域そのものの呼称にはしない。
|
||||
- Worker record: 作業単位として保存する record。profile、Runtime、RepositoryPoint / workdir evidence、session/transcript refs、status、diagnostics、summary、pinned flag を束ねる。Session 単体ではなく Worker を保存・削除の単位にする。
|
||||
- Materialization strategy: RepositoryPoint から working directory を作る実装戦略。Git detached worktree、sparse checkout、CoW snapshot、reflink copy、overlay、container filesystem など。
|
||||
- WorkingDirectoryAllocation: Runtime が払い出した作業環境の record。allocation id、worker id、workspace id、repository point、materializer kind、root、mounts、cleanup policy、status を持つ。
|
||||
- Sandbox policy: Worker が見られる filesystem、network、process、secret、tool authority の境界を表す policy。
|
||||
- Dirty state policy: local uncommitted changes を Worker environment に含めるかどうかの方針。clean point only、patch artifact apply、snapshot current worktree など。
|
||||
|
||||
## Strategy / design direction
|
||||
|
||||
### 1. Backend store と Runtime execution storage を分ける
|
||||
|
||||
Workspace/backend store は canonical or local descriptor records を扱う。
|
||||
|
||||
```text
|
||||
Backend / Control plane store
|
||||
- Workspace registry
|
||||
- Repository registry
|
||||
- Ticket / Objective
|
||||
- Artifact metadata / evidence
|
||||
- Actor / Permission / Audit
|
||||
- Runtime registry / observed state
|
||||
```
|
||||
|
||||
Runtime execution storage は Worker 実行のための materialized filesystem と cache を扱う。
|
||||
|
||||
```text
|
||||
Runtime root
|
||||
- runtime catalog / runtime-local DB
|
||||
- config bundles
|
||||
- worker metadata / transcript references
|
||||
- repository-cache
|
||||
- working-directories
|
||||
- sandbox state
|
||||
- build/dependency cache
|
||||
- cleanup ledger
|
||||
```
|
||||
|
||||
この 2 つを混ぜない。特に `.yoi` や workspace config root に Worker ごとの checkout/worktree/sandbox root を置かない。
|
||||
|
||||
推奨配置の方向:
|
||||
|
||||
```text
|
||||
~/.yoi/
|
||||
workspace-server/
|
||||
backend.db
|
||||
workspaces/
|
||||
<workspace-id>/
|
||||
workspace.db
|
||||
artifacts/
|
||||
|
||||
runtimes/
|
||||
<runtime-id>/
|
||||
runtime.db
|
||||
config-bundles/
|
||||
workers/
|
||||
<worker-id>/
|
||||
metadata/
|
||||
repository-cache/
|
||||
git/
|
||||
<repository-cache-key>/
|
||||
bare.git
|
||||
working-directories/
|
||||
<allocation-id>/
|
||||
root/
|
||||
mounts/
|
||||
scratch/
|
||||
materialization.json
|
||||
build-cache/
|
||||
tmp/
|
||||
```
|
||||
|
||||
### 2. clone ではなく materialize と呼ぶ
|
||||
|
||||
Runtime は Repository を毎回 clone するのではない。Runtime は RepositoryPoint を Worker 用の working directory として materialize する。
|
||||
|
||||
```text
|
||||
RepositoryId + RepositorySelector
|
||||
-> resolved RepositoryPoint
|
||||
-> Runtime repository-cache
|
||||
-> WorkingDirectoryMaterializer
|
||||
-> WorkingDirectoryAllocation
|
||||
-> Worker process
|
||||
```
|
||||
|
||||
Git の場合でも materialization strategy は複数あり得る。
|
||||
|
||||
- shared bare cache + detached Git worktree
|
||||
- sparse checkout
|
||||
- partial clone / blob filter
|
||||
- reflink copy
|
||||
- btrfs writable snapshot
|
||||
- APFS clonefile
|
||||
- overlay filesystem
|
||||
- external tool backed snapshot, e.g. Rift-like CoW workspace creation
|
||||
|
||||
### 3. Repository cache と Worker working directory を分離する
|
||||
|
||||
full clone を Worker ごとに作らない。重い source/object data は Runtime-local repository cache に集約し、Worker ごとの working directory は cheap allocation にする。
|
||||
|
||||
Git v0 の方向:
|
||||
|
||||
```text
|
||||
repository-cache/git/<repo-key>/bare.git
|
||||
working-directories/<allocation-id>/root/<repository-id>/
|
||||
```
|
||||
|
||||
初回:
|
||||
|
||||
```text
|
||||
git clone --mirror <uri> repository-cache/git/<repo-key>/bare.git
|
||||
```
|
||||
|
||||
次回以降:
|
||||
|
||||
```text
|
||||
git -C repository-cache/git/<repo-key>/bare.git fetch --prune
|
||||
```
|
||||
|
||||
Worker allocation:
|
||||
|
||||
```text
|
||||
git --git-dir=<bare.git> worktree add --detach <execution-root>/<repository-id> <commit>
|
||||
```
|
||||
|
||||
Git branch 名を直接 checkout しない。Git worktree は同一 branch を複数 worktree に checkout しづらいため、RepositorySelector を RepositoryPoint に解決し、resolved commit を detached worktree として materialize する。Worker が branch を必要とする場合は Worker/Task ごとの synthetic branch を別途作る。
|
||||
|
||||
### 4. Dirty state を明示 policy にする
|
||||
|
||||
local dirty changes を暗黙に Worker に見せない。
|
||||
|
||||
可能な policy:
|
||||
|
||||
- `clean_point_only`: dirty workspace は materialization 拒否。再現性が高く v0 の default 候補。
|
||||
- `patch_artifact`: clean RepositoryPoint を materialize し、Backend が保存した dirty diff artifact を apply する。
|
||||
- `current_worktree_snapshot`: CoW/reflink/Rift-like snapshot で現在の working tree を snapshot として materialize する。便利だが evidence と cleanup の扱いを明示する必要がある。
|
||||
- `direct_legacy_mount`: 既存 root をそのまま渡す。debug/legacy only。通常 Worker creation の default にしない。
|
||||
|
||||
Dirty state を含める場合、Artifact/evidence には source RepositoryPoint、patch/snapshot digest、created_at、materializer kind を残す。
|
||||
|
||||
### 5. Sandbox を materialization と同じ境界で扱う
|
||||
|
||||
working directory は単なる directory path ではなく、Worker が見てよい filesystem view である。Runtime は working directory allocation と同時に sandbox/mount/authority を構築する。
|
||||
|
||||
Worker に渡すもの:
|
||||
|
||||
- workspace root
|
||||
- repository mounts
|
||||
- scratch/cache dirs
|
||||
- tool authority
|
||||
- env vars
|
||||
- config bundle
|
||||
- secret handles, not raw secrets
|
||||
|
||||
Worker が自分で発見してはいけないもの:
|
||||
|
||||
- host repository root
|
||||
- Backend store path
|
||||
- `.yoi` authority-bearing internals
|
||||
- raw credentials
|
||||
- Runtime socket/store/cache internals
|
||||
- sibling Worker working directories
|
||||
|
||||
Sandbox v0 は strong isolation でなくてもよい。ただし型と lifecycle は、後で container sandbox、namespace, mount filtering, network policy, secret boundary に拡張できる形にする。
|
||||
|
||||
### 6. working directory registry を持つ
|
||||
|
||||
Runtime は materialized workspace を filesystem だけでなく registry でも管理する。
|
||||
|
||||
必要な record:
|
||||
|
||||
```text
|
||||
WorkingDirectoryAllocation
|
||||
id
|
||||
runtime_id
|
||||
worker_id
|
||||
workspace_id
|
||||
repository_points[]
|
||||
materializer_kind
|
||||
root
|
||||
mounts[]
|
||||
scratch
|
||||
source_cache_refs[]
|
||||
sandbox_policy
|
||||
dirty_state_policy
|
||||
cleanup_policy
|
||||
status: active | stopped | cleanup_pending | removed | failed
|
||||
created_at
|
||||
stopped_at
|
||||
diagnostics[]
|
||||
```
|
||||
|
||||
この registry は Runtime root 側に置く。Backend は必要な evidence と summary だけを受け取る。Browser-facing API は raw host paths を原則漏らさない。
|
||||
|
||||
### 7. Heavy regenerable artifacts は policy で除外または cache 化する
|
||||
|
||||
Worker working directory creation では、`node_modules`, `target`, `.venv`, framework cache, dist, build, coverage などを無条件に full copy しない。
|
||||
|
||||
Materialization policy は以下を持てるようにする。
|
||||
|
||||
- exclude regenerable artifacts
|
||||
- include manifests / lockfiles
|
||||
- share dependency cache
|
||||
- use build cache
|
||||
- path scope / sparse checkout
|
||||
- per-repository materialization options
|
||||
|
||||
Rift の filtered CoW creation のように、重い artifacts を除外しながら source tree を高速に用意できる strategy を将来取り込めるようにする。
|
||||
|
||||
## Initial implementation phases
|
||||
|
||||
### Phase 1: Materialization boundary and legacy allocation record
|
||||
|
||||
- `CreateWorkerRequest` に working directory request / target placeholder を追加する。
|
||||
- `WorkingDirectoryMaterializer` trait を `worker-runtime` に追加する。
|
||||
- `WorkerRuntimeExecutionBackend` が Worker spawn 前に materializer を呼ぶ順序にする。
|
||||
- v0 materializer は existing local root を explicit allocation として返してよいが、`direct_legacy_mount` として明示し、通常設計の final form と混同しない。
|
||||
- Allocation record / cleanup policy / diagnostics の型を先に作る。
|
||||
- Worker が source repository root を直接 scope として要求する経路を deprecated/legacy に閉じ込める。
|
||||
|
||||
### Phase 2: Runtime root and working directory storage
|
||||
|
||||
- `--runtime-root` を導入し、Runtime state / repository-cache / working-directories / worker metadata / worker runtime dirs を Runtime root 配下へ寄せる。
|
||||
- `--workspace` は legacy bootstrap input としてだけ扱い、Runtime identity / long-term workspace binding から外す。
|
||||
- Runtime root default は user data 配下にする。
|
||||
- working directory allocation registry を Runtime root に保存する。
|
||||
|
||||
### Phase 3: Git cached detached worktree materializer
|
||||
|
||||
- Git repository cache を Runtime root に作る。
|
||||
- RepositorySelector を RepositoryPoint に解決する呼び出し境界を作る。
|
||||
- resolved commit/tree を evidence として残す。
|
||||
- Worker ごとに detached worktree を `working-directories/<allocation-id>/root/<repository-id>` に作る。
|
||||
- Worker stop / cleanup 時に `git worktree remove` と registry cleanup を行う。
|
||||
- dirty state は v0 では `clean_point_only` を default にし、dirty local workspace は明示 diagnostic で拒否する。
|
||||
|
||||
### Phase 4: Path scope / sparse checkout / cache policies
|
||||
|
||||
- Ticket target / Worker launch request の path scope を materialization に渡す。
|
||||
- Git sparse checkout を materializer strategy として追加する。
|
||||
- heavy artifact exclude / dependency cache / build cache policy を導入する。
|
||||
|
||||
### Phase 5: CoW / snapshot materializer
|
||||
|
||||
- btrfs snapshot、Linux reflink、macOS APFS clonefile、Rift-like snapshot backend を materializer strategy として検討・実装する。
|
||||
- `current_worktree_snapshot` policy を evidence と cleanup 付きで扱う。
|
||||
- source root と generated workspace の registry / ancestor / cleanup model を設計する。
|
||||
|
||||
### Phase 6: Strong sandbox and remote Runtime support
|
||||
|
||||
- container filesystem / mount namespace / network policy / secret handle / tool authority を Runtime allocation と統合する。
|
||||
- Runtime が複数 Workspace / Repository の Worker を同時に抱える場合の namespace、quota、cleanup、audit boundary を固める。
|
||||
- remote/self-hosted/hosted Runtime fleet で repository cache と working directory storage をどう扱うかを設計する。
|
||||
|
||||
### 7. Worker / Session / workdir retention and cleanup policy
|
||||
|
||||
- Worker を保存・削除単位にする。Session / transcript は Worker に内包または参照される履歴として扱い、Session 単体を長期保存 authority にしない。
|
||||
- Worker lifecycle は `running -> stopped -> delete` を基本にする。`archived` を lifecycle state として導入しない。
|
||||
- Worker には `pinned` flag を持たせる。Pinned Worker は manual delete / cleanup / future automatic prune から守られる。
|
||||
- Worker delete は Worker record と内包する Session / transcript history の削除を意味する。Workdir files は自動削除しない。
|
||||
- Session / transcript は削除可能にする。圧縮 archive storage や高度な summarized retention は必要になった時点で別の storage policy として設計する。
|
||||
- Workdir 実ファイルは durable history ではなく再現可能 cache として扱う。RepositoryPoint / resolved commit から再現でき、dirty/uncommitted state が無いなら、running Worker に紐づかない workdir files は manual cleanup eligible とする。
|
||||
- Dirty Workdir は活動中または要判断状態として扱う。削除は可能だが、changes ごと消す explicit confirmation を要求する。Dirty orphan は recovery Worker 起動または explicit discard の判断対象であり、通常の clean cleanup と区別する。
|
||||
- Workdir record は materialization evidence として扱う。実ファイルが削除済みなら `removed`、外部要因で欠落しているなら stale/missing materialization として診断可能にする。
|
||||
- Worker と Workdir の関係は Backend registry の link table を authority にする。Worker record は最後に活動した RepositoryPoint / resolved commit / workdir binding summary を保持し、Stopped Worker + Removed Workdir でも必要に応じて再 materialize できるようにする。
|
||||
- Cleanup/delete は当面 manual-first にする。削除前に対象、理由、削除される bytes、blocking conditions(running Worker、pinned Worker、dirty state、unreachable commit など)を plan として提示する。
|
||||
- 将来的には容量/期限ベースの automatic prune を設定可能にする予定。ただし automatic prune は user-configured policy と pinned protection を前提にし、初期の manual cleanup/delete とは別段階で扱う。
|
||||
- UI 表示は `workdir` に寄せる。内部型/API の互換名 `working_directory` は移行中に残ってよいが、Browser-facing navigation では Runtime 管理配下の `Workdirs` として扱う。
|
||||
|
||||
## Non-goals
|
||||
|
||||
- v0 で完全な container sandbox を実装すること。
|
||||
- Worker ごとに full clone すること。
|
||||
- `.yoi` や Backend workspace store に Worker working directory を置くこと。
|
||||
- Git worktree を唯一の materialization strategy として固定すること。
|
||||
- Dirty local changes を暗黙に Worker に渡すこと。
|
||||
- Browser-facing API に raw host path、secret、Runtime internal store path を公開すること。
|
||||
- Repository credential / secret distribution の本格設計をこの Objective だけで完了させること。
|
||||
|
||||
## Success criteria / exit conditions
|
||||
|
||||
- Runtime root、Repository cache、working directory、Backend/Workspace store の境界が文書化されている。
|
||||
- Runtime が Worker spawn 前に working directory materializer を呼ぶ型と順序を持つ。
|
||||
- Worker は source repository root ではなく materialized working directory を scope として起動する。
|
||||
- working directory allocation が Runtime registry に記録され、cleanup policy を持つ。
|
||||
- Git/local repository の v0 materializer が full clone 連発ではなく shared cache / detached worktree / cheap allocation の方向に進んでいる。
|
||||
- dirty state policy が明示され、clean point、patch artifact、snapshot のどれを使ったか evidence に残せる。
|
||||
- Runtime process 起動時の `--workspace` は legacy bootstrap input として隔離され、Runtime identity や single workspace binding とみなされない。
|
||||
- Worker ごとの scope allocation conflict が、同一 source root を直接渡す設計ではなく materialized workspace allocation によって解消される。
|
||||
- Sandbox / mount / cache / secret boundary を後続実装で強化できる model になっている。
|
||||
- Worker record が Session / transcript refs と pinned flag を束ね、`pinned` Worker を cleanup/delete/future automatic prune から守れる。
|
||||
- Workdir 実ファイルは再現可能 cache として manual cleanup/delete でき、削除前に plan-first で linked Worker、session retention、commit reachability、dirty/missing 状態を確認できる。
|
||||
- 将来的に容量/期限ベースの automatic prune を user-configured policy として追加できる設計余地がある。
|
||||
|
||||
## References
|
||||
|
||||
- [Rift: Worktree alternative for fast copy-on-write workspaces](https://github.com/anomalyco/rift) — CoW snapshot / reflink / btrfs snapshot / APFS clonefile による高速 workspace creation、heavy regenerable artifacts の除外、workspace registry などを Runtime materializer backend 設計の参考にする。
|
||||
|
||||
## Decision context
|
||||
|
||||
- Runtime は Worker 群を束ねる実行基盤であり、Worker 用の作業環境を用意する責務を持つ。
|
||||
- Repository は clone されるものではなく、RepositoryPoint から Worker 用 working directory として materialize される。
|
||||
- Runtime execution storage は Backend/Workspace store と分離する。`.yoi` は Worker working directory 置き場ではない。
|
||||
- full clone を Worker ごとに作らない。Runtime-local repository cache と Worker-local cheap workspace allocation を分ける。
|
||||
- Git detached worktree は v0 materialization strategy として有力だが、Git worktree 固定の設計にはしない。
|
||||
- CoW snapshot / reflink / btrfs snapshot / APFS clonefile / Rift-like workspace creation は、将来の materializer backend として検討する。
|
||||
- Dirty local state は暗黙に渡さず、policy と evidence を持って扱う。
|
||||
- Sandbox は後付けの別機能ではなく、working directory allocation と同じ境界で扱う。
|
||||
@@ -0,0 +1,3 @@
|
||||
default = "builtin:companion"
|
||||
|
||||
[profile]
|
||||
@@ -0,0 +1,329 @@
|
||||
---
|
||||
description: worktree と sibling の coder / reviewer Pod を使い、下位 orchestrator が concrete Ticket 群の実装・外部レビュー・修正・完了準備を管理する orchestration フロー
|
||||
model_invokation: true
|
||||
user_invocable: true
|
||||
requires: []
|
||||
---
|
||||
# Multi-agent Worktree Workflow
|
||||
|
||||
yoi を yoi で開発する際の、worktree + coder Pod + 外部 reviewer Pod + orchestrator Pod の標準フロー。これは **最上位 Pod が細かい code review を抱えず、下位 orchestrator が実装と外部レビューの loop を完了状態まで運ぶためのフロー** である。
|
||||
|
||||
worktree の機械的作成手順は `$user/worktree-workflow`、ユーザー依頼の Ticket 化は `$user/ticket-intake-workflow`、Ticket の next action 分類は `$user/ticket-orchestrator-routing`、実装前の planning/requirements sync は compatibility canonical id `$user/ticket-preflight-workflow` に分ける。
|
||||
|
||||
この Workflow は、対象 ticket が implementation-ready であることを前提にする。implementation-ready は full implementation plan ではなく、recorded intent / binding decisions / invariants / implementation latitude / acceptance criteria / escalation conditions に基づいて coder が bounded investigation を進め、reviewer が判断できる状態を指す。設計境界・仕様・authority boundary が未同期の場合は、worktree 作成や coder Pod 起動の前に planning/requirements sync 互換入口として `ticket-preflight-workflow` を通す。
|
||||
|
||||
## 目的
|
||||
|
||||
- 実装差分を ticket ごとの child worktree に隔離する。
|
||||
- coder Pod に narrow write scope を渡して実装させる。
|
||||
- reviewer Pod を coder の子ではなく **同じ orchestrator 配下の sibling** として立て、外部レビューを行わせる。
|
||||
- orchestrator は coder / reviewer のやり取り、修正 loop、orchestration branch への integration、validation、Ticket 記録、child worktree cleanup に責任を持つ。
|
||||
- 最上位 orchestrator は、コードを直接理解し切ることではなく、委譲した intent / 要件 / invariant に沿って下位 orchestrator が完了まで運んだかを acceptance する。
|
||||
|
||||
## Pod coordination model
|
||||
|
||||
基本形は以下。
|
||||
|
||||
```text
|
||||
最上位 orchestrator Pod
|
||||
- 人間との会話相手
|
||||
- intent / 要件 / invariant / escalation 条件を定義
|
||||
- 複数の作業群を並列管理
|
||||
- root/original workspace での read/write/validation/cleanup/git 操作を行う
|
||||
- 原則として line-by-line code review を主業務にしない
|
||||
|
||||
下位 orchestrator Pod(area / concrete-ticket-set coordinator)
|
||||
- 連続した複数 concrete Ticket または大きめの concrete Ticket を完了状態まで運ぶ
|
||||
- worktree / branch / coder / reviewer / validation / 修正 loop を管理する
|
||||
- coder と reviewer を sibling として扱う
|
||||
- orchestration branch 上の integration 結果と残論点だけを返す
|
||||
|
||||
coder Pod
|
||||
- 指定 worktree / branch に実装する
|
||||
- ticket 外判断や設計衝突は orchestrator に戻す
|
||||
- reviewer に直接反論・修正依頼を完結させず、orchestrator に報告する
|
||||
|
||||
reviewer Pod
|
||||
- 原則 read-only
|
||||
- ticket / intent packet / diff / validation 結果を読む
|
||||
- 実際のコード変更が概念的に何を変えたかを説明する
|
||||
- intent / 要件 / invariant に反する blocker を分類して返す
|
||||
```
|
||||
|
||||
一段だけで足りる小さい concrete Ticket では、最上位 orchestrator が直接 coder / reviewer sibling を扱ってよい。複数 concrete Ticket や設計境界をまたぐ作業では、最上位の下に下位 orchestrator を挟む。
|
||||
|
||||
この Pod coordination model は runtime delegation の形であり、Ticket hierarchy を作る根拠ではない。複数 Ticket を扱う場合も各 Ticket は concrete work item として独立に実装・レビュー・検証・close できる必要がある。broad effort の進捗コンテナとして umbrella Ticket、parent/child Ticket、sub-ticket、part-of、contains などを作らない。中期的な目的や戦略は Objective context に置き、typed Ticket relations が利用可能な場合も dependency / related / blocking / replacement などの非階層メタデータに限る。
|
||||
|
||||
明示的な queue review や routing kick の中で、独立した queued Ticket が複数あり coder/reviewer capacity が残っているなら、最初の Ticket の完了を待つことを default にしない。Orchestrator は各 Ticket の relation metadata、OrchestrationPlan records、Ticket thread、workspace/worktree dirty state、visible Pods、既存 branch、conflict notes を確認し、blocking relation や `do_not_parallelize` がなく、write surface が disjoint または conflict risk が小さく機械的に処理できるものを、別 worktree・別 branch・別 narrow write scope で並列開始する。これは background scheduler や queue drain loop ではなく、人間が queued にした Ticket だけを、その場の明示的な acceptance 判断で `queued -> inprogress` に進める運用である。
|
||||
|
||||
## 開始条件
|
||||
|
||||
以下が揃っている時に使う。
|
||||
|
||||
- 対象 concrete Ticket または concrete Ticket 群が決まっている。
|
||||
- Ticket lifecycle を使う場合、対象はすでに `inprogress` であるか、worktree 作成・Pod spawn・coder routing の前に Orchestrator が個別に `queued -> inprogress` acceptance を記録できる `queued` Ticket に限る。unqueued Ticket は capacity 埋めの対象にしない。
|
||||
- ticket の背景・意図・制約・受け入れ条件から、実装調査と局所 tactic 選択を coder に委ねても product / API / UX / authority / design-boundary decision を silently 固定しないと判断できる。
|
||||
- worktree 作成と git 書き込み操作について、人間の許可がある。
|
||||
- 下位 orchestrator に渡す binding decisions / invariants、implementation latitude、escalation conditions を短く書ける。
|
||||
- 設計境界・仕様・authority boundary に不確定要素があり、bounded project-context checks 後も concrete missing decision / information が残る場合、planning/requirements sync 互換入口 `ticket-preflight-workflow` の結果が ticket thread に記録されている。
|
||||
|
||||
product / API / UX / authority / design-boundary 方針が複数自然に導ける場合、ticket 自体の再定義が必要な場合、または protocol / scope / permission / history persistence に触れる作業で bounded project-context checks 後も concrete missing decision / information が残る場合は、実装委譲前に planning/requirements sync 互換入口 `ticket-preflight-workflow` を通し、必要なら人間へ戻す。scope / Pod / permission / persistence のような authority-adjacent domain は reviewer focus と context lookup の signal であり、intent / binding decisions / invariants / implementation latitude / acceptance criteria / escalation conditions が明確なら coder に委ねてよい。実装ファイルの探索、既存コード読解、局所的な構成選択のような bounded implementation uncertainty も同様に coder に委ねてよい。
|
||||
|
||||
## Intent packet
|
||||
|
||||
階層化すると人間の意図が劣化しやすい。下位 orchestrator や coder / reviewer へは、自然文の依頼だけでなく intent packet を渡す。
|
||||
|
||||
標準形:
|
||||
|
||||
```text
|
||||
Intent:
|
||||
- 何を実現するか。
|
||||
|
||||
Binding decisions / invariants:
|
||||
- 人間/Orchestrator/Ticket に記録済みで coder / reviewer が従うべき decision。
|
||||
- 壊してはいけない設計境界、authority boundary、明示制約。
|
||||
- 残してはいけない旧概念や互換層。
|
||||
|
||||
Requirements / acceptance criteria:
|
||||
- 完了時に満たすべき observable な要件と reviewer が判断できる基準。
|
||||
|
||||
Implementation latitude:
|
||||
- Coder が調査しながら選んでよい local tactic / file-local organization / bounded uncertainty。
|
||||
|
||||
Escalate if:
|
||||
- 親へ戻すべき判断条件。特に product / API / UX / authority boundary / explicit design constraint を変える必要が出た場合。
|
||||
|
||||
Validation:
|
||||
- 実行すべき format / build / test / doctor。
|
||||
|
||||
Reviewer focus:
|
||||
- reviewer が確認すべき critical risks。
|
||||
```
|
||||
|
||||
reviewer には coder の実装方針ではなく、この intent packet と diff を中心に読ませる。reviewer は recorded intent / binding decisions / invariants / implementation latitude / acceptance criteria / explicit escalation conditions に照らして判断し、不記録の preferred tactic を基準にしない。
|
||||
|
||||
## orchestrator の責務
|
||||
|
||||
下位 orchestrator を挟まない場合は、以下を最上位 orchestrator が行う。下位 orchestrator を挟む場合は、最上位は intent packet を渡し、以下の実務を下位に委譲する。
|
||||
|
||||
1. 状態確認
|
||||
- `git status --short --branch`
|
||||
- 対象 ticket / ticket 群
|
||||
- relation metadata / OrchestrationPlan records / do_not_parallelize / conflict or dependency notes
|
||||
- visible Pods、既存 worktree/branch、coder/reviewer follow-up capacity
|
||||
- 関連 TODO / docs / 既存 worktree
|
||||
- planning sync が必要な ticket では、互換入口 `ticket-preflight-workflow` の分類・要件同期・critical risks
|
||||
|
||||
2. worktree 作成
|
||||
- 対象 Ticket が `queued` なら、この step の前に typed Ticket backend/tool path で `queued -> inprogress` を記録する。これより前に branch 作成、worktree 作成、Pod spawn、実装調査依頼などの implementation side effect を行わない。
|
||||
- Orchestrator が dedicated orchestration worktree で動く場合、作業対象は Orchestrator workspace/orchestration branch と child implementation worktree に限定する。root/original workspace は read/write/validation/cleanup/git 操作の対象ではない。
|
||||
- implementation worktree は記録済み implementation worktree root の `.worktree` 配下に置く。root/original workspace は配置基準としてだけ扱い、作業対象にしない。
|
||||
- implementation branch は Orchestrator workspace の current HEAD/orchestration branch HEAD から作り、reviewer approve 後は orchestration branch へ自動 integration する。
|
||||
- `.yoi` 自体は除外しない。tracked project records は child worktree に存在してよく、`.yoi/memory` と local/runtime/log/lock/secret-like paths だけを sparse checkout で除外する。
|
||||
|
||||
3. coder Pod spawn
|
||||
- read scope: original workspace root 全体、または実装に必要な bounded read scope。
|
||||
- write scope: child worktree、または必要最小 directory。
|
||||
- task には以下を明示する。
|
||||
- child worktree path / branch
|
||||
- 対象 ticket path
|
||||
- intent packet
|
||||
- SpawnPod の `cwd` は child worktree に設定すること(`cwd` は process/tool default cwd であり、scope/authority ではない)
|
||||
- Orchestrator workspace / recorded Ticket backend の `TODO.md` / Ticket records / `docs/report/` / `.yoi` は編集しないこと
|
||||
- child worktree 内の tracked `.yoi` project records は実装対象に必要な branch-local artifacts/dossiers として編集してよいが、`.yoi/memory` や local/runtime/secret-like files は作らないこと
|
||||
- active orchestration progress、review、integration、Ticket lifecycle update は Orchestrator workspace または recorded Ticket backend の責任として残すこと
|
||||
- 遵守すべき binding decisions / invariants と escalation conditions
|
||||
- 実行すべき build / test / format
|
||||
- 完了報告項目
|
||||
|
||||
4. coder 完了確認
|
||||
- `ReadPodOutput` で報告を読む。
|
||||
- 通知が来ない場合でも、worktree の `git status` / `git diff` / test で完了状態を確認する。
|
||||
- coder が止まった場合、worktree 状態を見て再 spawn / rollback / 親 escalation を判断する。
|
||||
|
||||
5. reviewer Pod spawn
|
||||
- reviewer は coder の子ではなく orchestrator 配下の sibling として立てる。
|
||||
- 原則 read-only scope にする。
|
||||
- reviewer に渡すもの:
|
||||
- ticket / intent packet
|
||||
- branch / commit / diff の読み方
|
||||
- 実行済み validation
|
||||
- blocker / non-blocker / acceptable の分類基準
|
||||
- reviewer の主目的は「コードの詳細を親の代わりに説明し、intent / invariant 違反を見つけること」。
|
||||
|
||||
6. review → 修正 loop
|
||||
- reviewer finding を orchestrator が読む。
|
||||
- blocker は coder に修正依頼する。
|
||||
- reviewer の指摘を却下する場合は、orchestrator が理由を dossier に残す。
|
||||
- 修正後は focused validation を実行し、必要なら reviewer に再確認させる。
|
||||
- reviewer の blocker が未解決のまま親に提出しない。
|
||||
|
||||
7. orchestration branch integration
|
||||
- 親がコードを直接理解しなくても判断できるよう、変更の概念的説明と evidence をまとめる。
|
||||
|
||||
8. integration / lifecycle
|
||||
- reviewer approve と blocker 解消を確認し、implementation branch を Orchestrator workspace の orchestration branch へ integration する。
|
||||
- Orchestrator workspace で必要な validation を実行し、Ticket thread に integration と validation の結果を記録する。
|
||||
- Ticket を完了処理して commit する。TODO cleanup が対象 Ticket の明示要件なら child implementation worktree/branch に限定して行う。root/original workspace は触らない。
|
||||
|
||||
## coder Pod の責務
|
||||
|
||||
- child worktree 内でのみ実装する。
|
||||
- root/original workspace の管理ファイルを読まない・書かない。
|
||||
- child worktree 内の tracked `.yoi` project records は ticket 要件に必要な branch-local artifact/dossier として扱ってよい。
|
||||
- `.yoi/memory`、local/runtime state、logs、locks、secret-like files を child worktree に作らない。
|
||||
- intent / requirements / acceptance criteria / binding decisions / invariants / implementation latitude / escalation conditions を読んでから実装する。
|
||||
- 実装ファイルの探索、既存コード読解、局所的な tactic 選択は、intent packet の implementation latitude 内で行ってよい。
|
||||
- 指定された build / test / format を実行する。
|
||||
- ticket 要件外または binding decisions/invariants 外の設計変更、依存関係追加、scope / permission / history persistence / prompt context 加工原則に触れる変更が必要なら止めて orchestrator に報告する。
|
||||
- 完了時に以下を報告する。
|
||||
- worktree path / branch
|
||||
- commit hash(commit した場合)
|
||||
- 変更ファイル
|
||||
- 実装概要
|
||||
- 実行した build / test / format
|
||||
- 未解決事項
|
||||
- review に回せるか
|
||||
|
||||
## reviewer Pod の責務
|
||||
|
||||
reviewer は coder の subordinate ではない。orchestrator 配下の sibling として、実装 diff を外部から読む。
|
||||
|
||||
- 原則 read-only で作業する。
|
||||
- ticket / intent packet / diff / validation 結果を読む。
|
||||
- 実際のコード変更が概念的に何を変えたかを説明する。
|
||||
- 親や上位 orchestrator が line-by-line diff を読まずに判断できるよう、以下を整理する。
|
||||
- 変更の概念モデル
|
||||
- recorded intent / binding decisions / invariants / implementation latitude / acceptance criteria / explicit escalation conditions との対応
|
||||
- binding decisions / invariants 違反の有無
|
||||
- 旧概念・禁止語彙・不要な互換層の残存
|
||||
- validation の妥当性
|
||||
- blocker / non-blocker / follow-up
|
||||
- reviewer は直接 merge しない。
|
||||
- reviewer は coder に直接作業指示を出さず、orchestrator に finding を返す。
|
||||
|
||||
## commit 方針
|
||||
|
||||
coder Pod には child worktree 内での commit を許可してよい。
|
||||
|
||||
- commit は ticket 内で意味のある粒度にする。
|
||||
- 例: `feat: ...`、`fix: ...`、`test: ...`、`docs: ...`
|
||||
- coder Pod は merge / push / branch deletion / worktree remove をしない。
|
||||
- coder Pod は recorded Ticket backend の Ticket 完了処理 commit、最終 review/approval/close をしない。child worktree 側には branch-local dossier や実装証跡を残してよい。
|
||||
- orchestrator は review 時に commit 粒度も確認する。
|
||||
- 必要な修正は、原則追加 commit として積む。履歴改変や squash は人間の明示指示がある時だけ行う。
|
||||
|
||||
## Review → 修正 → 完了の標準形
|
||||
|
||||
### Approve
|
||||
|
||||
reviewer が approve し blocker が残っていない場合、Orchestrator workspace の orchestration branch への integration、validation、Ticket 記録、child worktree cleanup まで自動的に進める。root/original workspace への read/write/validation/cleanup/git 操作は行わない。
|
||||
|
||||
1. coder Pod / reviewer Pod を停止し、scope を回収する。
|
||||
2. orchestrator が Ticket、child worktree/branch、commits、reviewer verdict、validation evidence を確認する。
|
||||
3. 最上位 orchestrator が必要最小限の spot check を行う。
|
||||
4. Orchestrator workspace の orchestration branch で implementation branch を merge または project-agreed method で integration する。
|
||||
5. Ticket を完了処理して commit する。TODO cleanup が対象 Ticket の明示要件なら child implementation worktree/branch に限定して行う。
|
||||
6. Orchestrator workspace/orchestration branch で検証コマンドを再実行する。root/original workspace では実行しない。
|
||||
7. 変更内容・commit・検証結果・残 dirty changes を報告する。
|
||||
|
||||
### Request changes
|
||||
|
||||
1. reviewer finding または orchestrator finding を blocker / non-blocker に分ける。
|
||||
2. blocker はファイル / 行 / 理由 / 修正方針つきで coder に戻す。
|
||||
3. coder が停止済みなら、同じ worktree / branch / scope で再 spawn する。
|
||||
4. 修正後に focused test と必要な broader test を再実行する。
|
||||
5. 必要なら reviewer に再確認させる。
|
||||
6. blocker が解消したら dossier を更新する。
|
||||
|
||||
### Non-blocking comments
|
||||
|
||||
- ticket 要件外の改善はその場で混ぜない。
|
||||
- 必要なら後続 ticket / docs/report にする。
|
||||
- non-blocking を理由に completion を遅らせない。
|
||||
|
||||
## 並列実装時の注意
|
||||
|
||||
- 1 ticket = 1 worktree = 1 branch を基本にする。
|
||||
- 複数 Pod に同じ write scope を渡さない。parallel Coder Pod は別 worktree と narrow write scope を持つ。
|
||||
- parent / orchestrator は coder の write scope 配下を直接編集しない。
|
||||
- reviewer は read-only を基本にする。明示的に別 scope を与える判断がない限り、review artifact を書かせる場合も ticket artifacts など限定 scope にする。
|
||||
- independent queued Ticket は、blocking relation/dependency がなく、`do_not_parallelize` や relevant `conflicts_with` がなく、source/write surface が disjoint または conflict risk が小さく機械的で、coder/reviewer follow-up capacity があり、workspace records を side effect 前に commit でき、別 worktree/branch/scope を切れる場合に並列開始を優先する。
|
||||
- 依存関係がある ticket、同一 migration boundary や同一 schema/tool/panel surface を大きく触る ticket、または reviewer/coder bottleneck がある ticket は、土台 branch を merge してから次 worktree を切るか、bounded defer reason を記録して idle にする。
|
||||
- capacity が見えるのに queued Ticket を開始しない場合、dependency / conflict / capacity / missing planning decision / dirty workspace / reviewer-coder bottleneck / migration boundary / human gate のいずれかを bounded reason として Ticket thread または `TicketOrchestrationPlanRecord` に残す。
|
||||
- parallel に走らせた Pod の完了通知は取りこぼしうるため、`ReadPodOutput` と worktree 状態で確認する。
|
||||
- この節は自動 scheduler、background runner、resource graph solver、automatic queue drain loop を導入しない。parallel start は明示的な routing/acceptance の結果であり、unqueued Ticket を開始する根拠ではない。
|
||||
|
||||
## orchestration integration dossier の標準形
|
||||
|
||||
```text
|
||||
Status:
|
||||
- completed / blocked / needs parent decision
|
||||
|
||||
Scope:
|
||||
- ticket(s): <path>
|
||||
- branch: <name>
|
||||
- commits:
|
||||
- <hash> <subject>
|
||||
|
||||
Intent check:
|
||||
- invariant 1: ok / violated / not applicable, evidence ...
|
||||
- invariant 2: ok / violated / not applicable, evidence ...
|
||||
|
||||
Implementation summary:
|
||||
- 変更の概念的説明: ...
|
||||
- 主要変更ファイル: ...
|
||||
- compatibility / migration: ...
|
||||
|
||||
Coder loop:
|
||||
- coder pod: <name>
|
||||
- produced commits: ...
|
||||
- unresolved coder notes: ...
|
||||
|
||||
External review:
|
||||
- reviewer pod: <name>
|
||||
- blockers found: ...
|
||||
- blockers fixed: ...
|
||||
- rejected findings and reasons: ...
|
||||
- non-blocking follow-ups: ...
|
||||
|
||||
Validation:
|
||||
- cargo fmt --check
|
||||
- cargo check --workspace
|
||||
- cargo test ...
|
||||
- yoi ticket doctor
|
||||
|
||||
Parent decision needed:
|
||||
- none / specific question
|
||||
|
||||
Residual risk:
|
||||
- ...
|
||||
|
||||
Dirty state:
|
||||
- ...
|
||||
```
|
||||
|
||||
## 最上位 orchestrator の acceptance
|
||||
|
||||
最上位 orchestrator は、下位 orchestrator の成果を code review するのではなく acceptance する。
|
||||
|
||||
確認するもの:
|
||||
|
||||
- intent packet が保持されているか。
|
||||
- reviewer が coder と独立した sibling として機能したか。
|
||||
- blocker が未解決のまま握りつぶされていないか。
|
||||
- 変更の概念的説明が要件と対応しているか。
|
||||
- validation が ticket のリスクに対して十分か。
|
||||
- escalation すべき判断を下位が勝手に決めていないか。
|
||||
|
||||
必要なら spot check するが、常態的な line-by-line review に戻らない。
|
||||
|
||||
## この Workflow で扱わないもの
|
||||
|
||||
以下は `$user/ticket-intake-workflow`、`$user/ticket-orchestrator-routing`、または別の設計相談で扱う。
|
||||
|
||||
- ユーザー依頼を Ticket 化すること。
|
||||
- Ticket の next action を分類すること。
|
||||
- QA feedback / AI feedback を Ticket / report / workflow に落とす判断。
|
||||
- 長期 maintainer loop / scheduler / LeaseStore の設計。
|
||||
- reviewer Pod の品質評価を機械的に採点する仕組み。
|
||||
@@ -0,0 +1,350 @@
|
||||
---
|
||||
description: ユーザーの曖昧な依頼を要件同期し、合意済みの Ticket として作成・更新する Intake workflow
|
||||
model_invokation: true
|
||||
user_invocable: true
|
||||
requires: []
|
||||
---
|
||||
# Ticket Intake Workflow
|
||||
|
||||
Yoi の multi-agent 運用で、ユーザーの依頼をいきなり実装委譲せず、まず **合意済み Ticket** または「まだ Ticket 化しない」判断に変換するための Workflow。
|
||||
|
||||
この workspace workflow は bundled `resources/workflows/ticket-intake-workflow.md` を dogfooding 用に詳述した override である。Objective / split policy / local Ticket 運用の説明を追加するが、bundled workflow の調査ゲート、Ticket 作成前の user agreement、Intake の非 scheduler 境界を弱めてはならない。
|
||||
|
||||
Intake の目的は、ユーザーの意図・要件・制約・受け入れ条件・未決定点を明確にし、Orchestrator が次の routing を判断できる Ticket を作ることである。Intake は scheduler ではなく、coder / reviewer / read-only investigation helper Pod を起動しない。
|
||||
|
||||
## 位置づけ
|
||||
|
||||
```text
|
||||
User request / conversation
|
||||
-> Ticket Intake Workflow
|
||||
-> TicketCreate / TicketComment
|
||||
-> Orchestrator routing
|
||||
-> planning sync / spike / implementation / review / blocked / close
|
||||
```
|
||||
|
||||
- `Ticket` は durable orchestration record。
|
||||
- `Objective` は medium-term goal / motivation / strategy / success criteria / decision context の project record。Objective context は判断背景であり、Ticket body/thread/artifacts を読む代替ではない。
|
||||
- `Task` は session-local progress tracking。
|
||||
- `Assignment` は Orchestrator から coder / reviewer Pod、または task-specific helper Pod への具体的委譲。
|
||||
- `IntentPacket` は Ticket から抽出して Assignment に渡す短い実装・レビュー契約。
|
||||
|
||||
Intake は、要件同期と Ticket 化を担当する。実装の起動・worktree 作成・review 委譲・merge 判断は Orchestrator 側の責務である。`ready` は Orchestrator が routing できる状態を意味し、実装戦術がすべて事前固定されていることを意味しない。
|
||||
|
||||
Ticket に残す内容は、binding decisions / invariants、implementation latitude、escalation conditions を区別する。Coder が調査しながら局所的な実装手段を選べる余地は残してよいが、product / API / UX / authority boundary / explicit design constraint を silently 決める余地を残してはならない。
|
||||
|
||||
## Intake の責務
|
||||
|
||||
Intake は以下を行う。
|
||||
|
||||
- ユーザー依頼の主語と目的を確認する。
|
||||
- 既存 Ticket を確認し、duplicate / related work を探す。
|
||||
- 曖昧な依頼、現在挙動への claim、authority boundary、workflow/source-of-truth 変更では、Ticket 化前の最小調査ゲートとして関連 docs / code / workflow / history を読む。
|
||||
- 不足している要件を質問する。
|
||||
- 作成または refinement する Ticket が、実装・レビュー・検証・完了判断を単独で行える concrete work item であるか確認する。
|
||||
- 広い依頼を分割する場合は、進捗コンテナとしての umbrella Ticket ではなく、concrete Ticket / Objective context / split decision record に責務を分ける。
|
||||
- Objective-to-Ticket links を提案する場合は canonical opaque Ticket ID だけを使い、dependency / blocking / ordering relation として扱わない。
|
||||
- Ticket の title / body/request snapshot / acceptance criteria / priority / readiness / risk flags を、現在の要件として意味がある範囲で提案する。
|
||||
- canonical ID は Ticket 作成/storage が opaque な path-derived value として割り当てるため、Intake はユーザー向け metadata として提案しない。
|
||||
- ユーザー主張、Intake が確認した事実、未確認仮説、未決定点を分けて整理する。
|
||||
- background / requirements / acceptance criteria / escalation conditions を整理する。
|
||||
- binding decisions / invariants と implementation latitude を分けて書く。
|
||||
- 具体的な除外や触れてはいけない境界が binding decision である場合は、generic な除外リストではなく invariant / escalation condition として明記する。
|
||||
- readiness / open questions / risk flags を明示する。
|
||||
- ユーザー合意後にだけ official Ticket を作成する。
|
||||
- 既存 Ticket の refinement を求められた場合は、TicketComment で経緯を残す。
|
||||
|
||||
## Intake がしないこと
|
||||
|
||||
- coder / reviewer Pod や read-only investigation helper Pod を起動しない。
|
||||
- worktree を作らない。
|
||||
- merge / close / branch cleanup をしない。
|
||||
- implementation-ready でない Ticket を実装に投げない。
|
||||
- unattended scheduler として自動実行しない。
|
||||
- ユーザー合意なしに official Ticket を作らない。
|
||||
- broad effort の進捗を保持するためだけの long-lived umbrella / progress-container Ticket を作らない。
|
||||
- parent/child、sub-ticket、umbrella、part-of、contains などの hierarchy/container relation を split の代替として提案しない。
|
||||
- secrets / private context を Ticket body / thread / artifacts / diagnostics に保存しない。
|
||||
- arbitrary filesystem write で `work-items/` を編集しない。Ticket 操作は Ticket tools を使う。
|
||||
|
||||
## 使用する Ticket tools
|
||||
|
||||
利用可能なら、以下の typed Ticket tools を使う。
|
||||
|
||||
- `QueryTicket`: 既存 Ticket の一覧・重複確認。
|
||||
- `ShowTicket`: 関連 Ticket の詳細確認。
|
||||
- `TicketCreate`: 合意済み Ticket の作成。
|
||||
- `TicketComment`: 既存 Ticket refinement / decision / plan の記録。
|
||||
|
||||
Intake は `MergeRequest*`, `TicketWorkflowState`, `TicketClose` を通常使わない。review authority は assigned Coder が起動した read-only direct-child Reviewer の immutable Merge Request attempt に属し、completion / merge / close は各guarded workflowの責務である。
|
||||
|
||||
Ticket tools が利用できない環境では、勝手に file write で代替しない。ユーザーまたは Orchestrator に「Ticket tools がないため materialize できない」と報告し、必要なら `yoi ticket` を使える人間/親 workflow に戻す。
|
||||
|
||||
## 手順
|
||||
|
||||
### 1. 依頼を受け取る
|
||||
|
||||
ユーザー依頼を短く言い換え、以下を分ける。
|
||||
|
||||
- 何を変えたいか。
|
||||
- なぜ必要か。
|
||||
- 影響を受けるユーザー / Pod / workflow / crate / docs。
|
||||
- 既に決まっていること。
|
||||
- まだ未決定のこと。
|
||||
|
||||
この段階では Ticket を作らない。ユーザー発話は request snapshot / claim として扱い、確認済み requirements と混同しない。
|
||||
|
||||
### 2. 既存 Ticket を確認する
|
||||
|
||||
`QueryTicket` / `ShowTicket` で duplicate / related work を探す。
|
||||
|
||||
確認観点:
|
||||
|
||||
- 同じ目的の未完了 Ticket がないか。
|
||||
- closed Ticket の判断・resolution と矛盾しないか。
|
||||
- 既存の umbrella/progress-container Ticket が、superseded/decomposed として退役できる状態か。
|
||||
- 既存 concrete follow-up Ticket や Objective context で足りるか、新規 concrete Ticket が必要か。
|
||||
|
||||
既存 Ticket の更新で足りる場合、新規 Ticket を作らず、ユーザーに更新案を提示する。
|
||||
|
||||
### 2.1. Ticket 化前の最小調査ゲート
|
||||
|
||||
`TicketCreate` または material な `TicketComment` の前に、以下の gate を通す。
|
||||
|
||||
必ず行うこと:
|
||||
|
||||
- duplicate / related / blocking-looking Ticket を確認する。
|
||||
- 既存 Ticket を更新するなら、その Ticket の item/thread/artifacts を読む。
|
||||
- ユーザー claim と、Intake が読んで確認した fact を分ける。
|
||||
|
||||
次のいずれかに当たる場合は、Ticket 作成前に関連 docs / code / workflow / prompt / config / history を読む。
|
||||
|
||||
- 依頼が曖昧、または複数の concrete work item を含む。
|
||||
- 「現在の挙動」「既存仕様」「壊れている」「既にある」など、事実確認を要する claim がある。
|
||||
- scope / permission / history / prompt context / persistence / public API など authority boundary に触れる。
|
||||
- prompt / workflow resource、Ticket schema、source-of-truth 境界の変更に触れる。
|
||||
- 既存実装の map がないと requirements / acceptance criteria を誤って固定しそうである。
|
||||
|
||||
Gate output は draft に以下を分けて残す。
|
||||
|
||||
- User claims / request snapshot: ユーザーが述べたこと。
|
||||
- Confirmed facts / sources: Intake が読んで確認したことと source。
|
||||
- Unverified hypotheses: ありそうだが未確認の推測。
|
||||
- Undecided points / open questions: ユーザーまたは Orchestrator の判断が必要なこと。
|
||||
|
||||
調査が大きい、current-code map がない、または仕様同期が足りない場合は、official Ticket を作らず draft で止める。readiness は `spike_needed` / `requirements_sync_needed` / `blocked` のいずれかを付け、次に必要な調査や質問を報告する。確認できない claim を requirements / acceptance criteria として保存しない。
|
||||
|
||||
### 2.5. Broad request の split policy
|
||||
|
||||
1つの依頼が複数の implementable work item を含む場合、Intake は以下を提案する。
|
||||
|
||||
- broad effort を表す umbrella/progress-container Ticket は新規作成しない。
|
||||
- concrete slice ごとに、単独で実装・レビュー・検証・close できる Ticket を作る。
|
||||
- split した理由と残した範囲は、関連 Ticket の thread、必要なら Objective context に記録する。
|
||||
- Objective は中期的な目的・方針・success criteria を保持するために使う。Objective record の実装や schema 追加はこの workflow の責務ではない。
|
||||
- typed Ticket relation が利用可能になった場合も、dependency / related / blocking / replacement / supersedes などの非階層メタデータに限る。
|
||||
- parent/child、sub-ticket、part-of、contains のような hierarchy/container relation を作らない。
|
||||
|
||||
ユーザーが「まず調査 Ticket」「設計 Ticket」「計画 Ticket」を concrete work item として求める場合は作成してよい。禁止されるのは、他の concrete Ticket の進捗を持つためだけに長期 open となる container Ticket である。
|
||||
|
||||
### 3. 要件を同期する
|
||||
|
||||
最低限、以下を確認する。
|
||||
|
||||
- ユーザー claim のうち、どれが確認済み fact で、どれが未確認仮説か。
|
||||
- observable な完了条件は何か。
|
||||
- 作業の種類・影響範囲は prose として body に書けばよいが、current Ticket core metadata として扱わない。
|
||||
- 受け入れ条件は何か。
|
||||
- binding decision として残す具体的な除外・authority boundary はあるか。
|
||||
- 後方互換が必要か。
|
||||
- authority boundary / scope / permission / history / prompt context に触れるか。
|
||||
- validation は何で確認できるか。
|
||||
- 人間判断が必要な論点は何か。
|
||||
|
||||
不足がある場合は、Ticket 作成前に質問する。質問は多すぎず、Ticket 作成に必要な最小限に絞る。調査が先に必要な場合は `spike_needed`、仕様同期が先に必要な場合は `requirements_sync_needed` として draft に留める。
|
||||
|
||||
### 4. readiness を分類する
|
||||
|
||||
Ticket 作成または更新前に、readiness を明示する。
|
||||
|
||||
```text
|
||||
implementation_ready:
|
||||
- 意図・受け入れ条件・binding decisions / invariants / implementation latitude が明確。
|
||||
- Reviewer が判断できる基準と escalation conditions が明確。
|
||||
- 実装調査や局所的な tactic 選択は残っていてよいが、product / API / UX / authority boundary / explicit design constraint を coder が silently 決める余地はない。
|
||||
- validation が書ける。
|
||||
|
||||
requirements_sync_needed:
|
||||
- 目的は見えているが、仕様・用語・UX・責務境界・受け入れ条件が未同期。
|
||||
- ユーザー claim を requirements として固定するには合意や確認が足りない。
|
||||
|
||||
spike_needed:
|
||||
- 技術調査、依存関係、性能、license、diagnostics、現在コード map が先に必要。
|
||||
- どの files/workflows/Tickets を読むべきかは見えているが、Intake の最小調査では実装可能な要件まで確定できない。
|
||||
|
||||
blocked:
|
||||
- 人間判断、外部イベント、別 Ticket の完了が必要。
|
||||
|
||||
unspecified:
|
||||
- どうしても分類不能な時だけ使う。理由を Ticket に書く。
|
||||
```
|
||||
|
||||
### 5. open questions / risk flags を付ける
|
||||
|
||||
以下に触れる Ticket は risk flags と reviewer/orchestrator focus を Ticket body に短く明記する。これは stop gate ではない。具体的な未決定 decision / information がある場合だけ、blocking open question として記録する。
|
||||
|
||||
- profile / manifest / scope / permission。
|
||||
- session / history / Pod metadata / persistence。
|
||||
- prompt context 加工原則。
|
||||
- public API / plugin / feature boundary。
|
||||
- security / secret / credential handling。
|
||||
- storage migration / backward compatibility。
|
||||
- 複数の自然な設計方針があるもの。
|
||||
- reviewer が diff だけでは見落としやすい設計リスク。
|
||||
|
||||
risk flags は短い語でよい。missing boundary がすでに人間/Orchestrator の Ticket-recorded decision で補われている場合は、その decision を根拠に Orchestrator が routing できる。単に risk があるだけなら Orchestrator は Ticket を戻さず、IntentPacket に escalation / reviewer focus を明記して進める。
|
||||
|
||||
例:
|
||||
|
||||
```text
|
||||
risk_flags: [authority-boundary, persistence, prompt-context, public-api]
|
||||
```
|
||||
|
||||
### 6. Ticket draft を提示する
|
||||
|
||||
ユーザーに作成前 draft を提示する。
|
||||
|
||||
標準 draft:
|
||||
|
||||
```text
|
||||
Title:
|
||||
|
||||
Priority:
|
||||
Readiness:
|
||||
Next Ticket operation: draft_only | create_after_user_agreement | update_existing_after_user_agreement | no_ticket
|
||||
Risk flags:
|
||||
|
||||
User claims / request snapshot:
|
||||
|
||||
Confirmed facts / sources:
|
||||
|
||||
Unverified hypotheses:
|
||||
|
||||
Undecided points / open questions:
|
||||
|
||||
Background:
|
||||
|
||||
Requirements:
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
Binding decisions / invariants:
|
||||
|
||||
Implementation latitude:
|
||||
|
||||
Escalation conditions:
|
||||
|
||||
Validation:
|
||||
|
||||
Related tickets/docs/files:
|
||||
```
|
||||
|
||||
canonical ID は作成時に storage が opaque/path-derived value として割り当てるため、draft では提案しない。
|
||||
|
||||
この時点ではまだ Ticket を作らない。`Next Ticket operation` が `draft_only` / `no_ticket` の場合は、ユーザー合意があっても `TicketCreate` ではなく追加同期または調査へ戻す。
|
||||
|
||||
### 7. ユーザー合意を取る
|
||||
|
||||
以下のどちらかがあるまで official Ticket を作らない。
|
||||
|
||||
- ユーザーが draft を明示的に承認する。
|
||||
- ユーザーが「作って」「切って」「記録して」など、作成を明示する。
|
||||
|
||||
未決定のまま記録する場合は、`requirements_sync_needed` / `spike_needed` / `blocked` として未決定点を明示する。ユーザー合意は「この未決定状態で記録する」ことへの合意であり、未確認仮説を requirements 化する許可ではない。
|
||||
|
||||
### 8. Ticket を作成または更新する
|
||||
|
||||
新規 Ticket の場合:
|
||||
|
||||
- `TicketCreate` を使う。
|
||||
- title / priority / body と、必要な readiness / risk flags を指定する。canonical ID は storage が割り当てる。
|
||||
- body に readiness / open questions / risk flags と、binding decisions / invariants、implementation latitude、escalation conditions を Markdown で明記する。
|
||||
- user claims、confirmed facts、unverified hypotheses、undecided points / open questions を分けて書き、未確認 claim を requirements / acceptance criteria として保存しない。
|
||||
|
||||
既存 Ticket refinement の場合:
|
||||
|
||||
- `TicketComment` を使う。
|
||||
- role は内容に応じて `comment`, `plan`, `decision` を選ぶ。
|
||||
- 既存 `item.md` の大幅変更が必要なら、Orchestrator / maintainer に戻す。
|
||||
|
||||
### 9. 作成後の報告
|
||||
|
||||
ユーザーへ以下を返す。
|
||||
|
||||
- 作成/更新した Ticket の system-assigned id / title。
|
||||
- readiness。
|
||||
- open questions / risk flags。
|
||||
- 次に Orchestrator が取るべき routing 候補。
|
||||
- 未決定点があれば、そのまま明示する。
|
||||
|
||||
Intake はここで止まる。implementation / worktree / coder / reviewer 起動は Orchestrator routing の責務である。
|
||||
|
||||
## Ticket body の推奨形
|
||||
|
||||
```markdown
|
||||
## User claims / request snapshot
|
||||
|
||||
## Confirmed facts / sources
|
||||
|
||||
## Unverified hypotheses
|
||||
|
||||
## Undecided points / open questions
|
||||
|
||||
## Background
|
||||
|
||||
## Requirements
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
## Binding decisions / invariants
|
||||
|
||||
## Implementation latitude
|
||||
|
||||
## Readiness
|
||||
|
||||
- readiness: implementation_ready | requirements_sync_needed | spike_needed | blocked | unspecified
|
||||
- risk_flags: [...]
|
||||
|
||||
## Escalation conditions
|
||||
|
||||
## Validation
|
||||
|
||||
## Related work
|
||||
```
|
||||
|
||||
Ticket の body は Markdown/freeform を維持する。すべてを strict schema に押し込まない。
|
||||
|
||||
## secret / private context policy
|
||||
|
||||
以下は Ticket に保存しない。
|
||||
|
||||
- API keys / tokens / credentials。
|
||||
- secret file contents。
|
||||
- private prompts / model responses that should not become project records。
|
||||
- user private context not needed for project history。
|
||||
- raw logs containing secrets。
|
||||
|
||||
必要なら、保存せずに「secret/config が必要」とだけ書く。
|
||||
|
||||
## 完了条件
|
||||
|
||||
この Workflow の完了条件は次のいずれかである。
|
||||
|
||||
- ユーザー合意済みの新規 Ticket が作成され、Orchestrator が routing できる情報が揃っている。
|
||||
- 既存 Ticket に refinement / decision / plan が記録され、次の routing が明確である。
|
||||
- Ticket 化しない判断がユーザーに説明され、未決定の問いまたは関連 Ticket が明確になっている。
|
||||
|
||||
## 他 Workflow への接続
|
||||
|
||||
- `ticket-preflight-workflow`: legacy compatibility planning/requirements sync 入口。新規 routing は standalone preflight ではなく planning return/requirements sync として扱う。
|
||||
- `multi-agent-workflow`: Orchestrator が implementation_ready と判断した後に接続する。
|
||||
- `ticket-orchestrator-routing`: この Workflow が作った Ticket を routing する後続 Workflow。
|
||||
@@ -0,0 +1,419 @@
|
||||
---
|
||||
description: Ticket を読み、Orchestrator が planning return / spike / implementation / review / blocked / close へ明示的に routing する workflow
|
||||
model_invokation: true
|
||||
user_invocable: true
|
||||
requires: []
|
||||
---
|
||||
# Ticket Orchestrator Routing Workflow
|
||||
|
||||
Yoi の multi-agent 運用で、Intake や人間が作成した Ticket を Orchestrator が読み、次に取るべき action を明示的に分類・記録するための Workflow。
|
||||
|
||||
これは scheduler ではない。目的は、Ticket の fields / body / thread / artifacts / 現在の repository/Pod 状態を明示的に確認し、隠れた会話状態ではなく Ticket に基づいて routing 判断を残すことである。
|
||||
|
||||
Panel Queue / queued notification は、人間が Orchestrator に routing を開始してよいと許可した signal であり、unattended scheduler ではない。implementation side effect に進む場合は、Orchestrator が Ticket と workspace state を再確認し、unblocked と判断してから `queued -> inprogress` を記録する必要がある。明示的な routing / queue review / panel kick の中で、独立した queued Ticket と coder/reviewer capacity が複数あるなら、1件ずつ完了を待つことを default にせず、安全確認を満たす範囲で追加 Ticket の並列開始を優先する。
|
||||
|
||||
`ready` は Orchestrator routing に十分な状態であり、実装戦術が事前にすべて固定されている状態ではない。Orchestrator は、recorded intent / binding decisions / invariants / implementation latitude / acceptance criteria / escalation conditions が揃っていれば、bounded implementation uncertainty を残したまま implementation-ready と判断してよい。
|
||||
|
||||
`ready` / `queued` を `planning` に戻す判断は、Ticket text や risk flags だけで決めない。Orchestrator は、Ticket thread/artifacts、関連 Ticket / orchestration plan、関連 workflow/docs/code、durable project context、現在の repository/Pod/worktree 状態のうち relevant なものを bounded に確認し、実装前に必要な concrete missing decision / information が残っている場合だけ planning return とする。risk flags と risky domain は context lookup と reviewer focus の signal であり、automatic stop gate ではない。
|
||||
|
||||
## 位置づけ
|
||||
|
||||
```text
|
||||
TicketCreate / TicketComment
|
||||
-> Ticket Orchestrator Routing Workflow
|
||||
-> planning return / requirements sync / spike / implementation / review / blocked / close
|
||||
-> 必要に応じて他 Workflow へ接続
|
||||
```
|
||||
|
||||
- Intake は Ticket の materialization と planning/clarification を担当する role であり、state 名ではない。
|
||||
- state は `planning -> ready -> queued -> inprogress -> done` を基本遷移とする。
|
||||
- `ticket-preflight-workflow` は legacy canonical id 互換の planning/requirements sync entry であり、`preflight` を独立 state / lane / long-lived operation として扱わない。
|
||||
- `ready -> queued` は人間が Orchestrator routing を許可した状態であり、worktree 作成や Pod 起動の許可そのものではない。
|
||||
- `multi-agent-workflow` は coder / reviewer Pod と worktree を使う実装・レビュー loop。
|
||||
- この Workflow は自動 scheduler / lease / unattended maintainer ではない。
|
||||
|
||||
## Orchestrator の責務
|
||||
|
||||
Orchestrator は以下を行う。
|
||||
|
||||
- Ticket を `ShowTicket` で読む。
|
||||
- 必要に応じて関連 Ticket を `QueryTicket` / `ShowTicket` で確認する。
|
||||
- Ticket body / thread / artifacts / resolution / review / implementation report を読む。
|
||||
- Ticket が Objective context と結びついている場合は、Objective を medium-term goal / motivation / strategy / success criteria / decision context として読む。ただし Objective context は判断背景であり、Ticket body/thread/artifacts や explicit Ticket relations / OrchestrationPlan records を読む代替ではない。
|
||||
- repository 状態、関連 docs/code、既存 worktree、visible Pods を必要に応じて明示的に確認する。
|
||||
- queued notification を受けた場合も、Ticket と workspace state を再確認してから routing する。
|
||||
- next action を routing classification として決める。
|
||||
- routing decision を `TicketComment` で Ticket thread に記録する。
|
||||
- broad request や split/refinement では、long-lived umbrella/progress-container Ticket ではなく concrete implementable Ticket、Objective context、split decision record を使う。
|
||||
- Objective-to-Ticket links は canonical opaque Ticket ID による non-blocking context link として扱い、dependency / blocking / ordering / ownership / scheduling relation と解釈しない。
|
||||
- 既存 umbrella/progress-container Ticket が concrete follow-up Ticket / Objective context で置き換え済みなら、superseded/decomposed として退役・close する routing を検討する。
|
||||
- implementation-ready の場合は `multi-agent-workflow` に渡す `IntentPacket` を作る。
|
||||
- implementation-ready かつ Ticket が `queued` の場合は、worktree 作成 / implementation Pod `SpawnPod` / coder routing などの side effect の前に、既存の typed Ticket backend/tool path で `queued -> inprogress` を記録する。
|
||||
- 人間による `ready -> queued` は、記録済み Ticket scopeについて、実装、current MRのguarded merge、completion記録、Ticket closeまでをWorkspace Orchestratorへ委任するdurable gateである。Orchestratorは`queued -> inprogress`を受理した後、current-ref approval、repository evidence、blocking relations、merge CASを確認して完了まで進め、別のmerge確認を待たない。Ticketがseparate approval gateを明記する場合、またはqueued scope外の新しい判断が必要な場合だけ停止する。
|
||||
- 明示的な queue review 中に、他にも queued Ticket が見え、capacity が空いている場合は、各 Ticket について relation / orchestration-plan / dirty state / visible Pods / worktree / conflict risk を確認し、独立して受理できるものを同じ routing pass で追加の `queued -> inprogress` 候補にする。
|
||||
- queued Ticket を capacity が見える状態で idle のまま残す場合は、dependency / conflict / capacity / missing planning decision / dirty workspace / reviewer-coder bottleneck / migration boundary / human gate のいずれかに絞った bounded reason を Ticket thread または `TicketOrchestrationPlanRecord` に残す。
|
||||
- `ready` または `queued` に concrete missing decision / information がある場合だけ、typed state-change/routing event 付きで `planning` に戻す。その event/comment には missing item、checked context、implementation latitude では足りない理由、次の planning question/action を含める。
|
||||
|
||||
## Orchestrator がしないこと
|
||||
|
||||
- 自動 scheduler として unattended に実行しない。
|
||||
- Panel Queue / queued notification だけを unattended scheduler trigger として扱わない。
|
||||
- `queued -> inprogress` acceptance なしに worktree 作成、implementation Pod `SpawnPod`、coder/reviewer routing を行わない。
|
||||
- 人間/上位 Orchestrator の許可または明示的な routing acceptance なしに coder / reviewer Pod や read-only investigation helper Pod を起動しない。
|
||||
- unqueued Ticket を capacity 埋めのために開始しない。parallel start の候補は、個別に `queued` であり、人間が routing を許可済みの Ticket に限る。
|
||||
- 設計境界の未決定を勝手に implementation-ready として固定しない。
|
||||
- Ticketが`ready -> queued`されておらず完了権限を委任されていない場合、またはTicketがseparate approval gateを明記する場合に、勝手にmerge / close / cleanupしない。queued delegationとguarded completion evidenceが揃っている場合は、追加のhuman gateを作らず完了まで進める。
|
||||
- Ticket tools があるからといって arbitrary filesystem write を行わない。
|
||||
- broad multi-Ticket effort のために新しい umbrella/progress-container Ticket を作らない。
|
||||
- parent/child、sub-ticket、umbrella、part-of、contains などの hierarchy/container relation を split/refinement の代替として扱わない。
|
||||
- Notification だけを完了証拠にしない。Pod output / diff / validation / Ticket evidence を確認する。
|
||||
- 具体的な不足項目を言語化できない場合に、単に risky という理由だけで `planning` に戻さない。その場合は IntentPacket に escalation / reviewer focus を明記して進める。
|
||||
- risk flags / risky domain / authority-adjacent domain を automatic stop gate として扱わない。これらは bounded context lookup、IntentPacket invariant、reviewer focus、escalation condition に反映する signal である。
|
||||
|
||||
## 使用する Ticket tools
|
||||
|
||||
利用可能なら、以下を使う。
|
||||
|
||||
- `QueryTicket`: routing 候補、関連 Ticket、project-level forward relation (`depends_on` / `blocks` / `related` / `supersedes` / `duplicate_of`) と derived blocker summary を bounded filter/projection で確認する。`depends_on` と incoming unresolved `blocks` は queue/acceptance blocker であり、`related` は blocker ではない。`supersedes` / `duplicate_of` は visible diagnostic として扱い、自動的な lifecycle 変更や scheduler 判断にはしない。
|
||||
- `ShowTicket`: 対象 Ticket の body / thread / artifacts / resolution / typed relation metadata、linked Objective、assignment、implementation/review evidence を確認する。
|
||||
- `TicketComment`: routing decision / intent packet / blocked reason / next question の記録。
|
||||
- `TicketWorkflowState`: `queued -> inprogress` acceptance、`inprogress -> done`、または concrete missing decision/information reason を伴う `ready|queued -> planning` に使う。
|
||||
- `TicketDependencyCheck`: queue/acceptance 直前の typed dependency readiness guard に使う。
|
||||
- `TicketRelationRecord` / `TicketRelationRemove`: ユーザー合意済みの durable project relation を明示的に更新する場合だけ使う。
|
||||
- `TicketOrchestrationPlanQuery`: 対象 Ticket や関連 Ticket の ordering / blocker / conflict / waiting-capacity / accepted-plan 記録を読む。queued acceptance 前に必ず確認する。
|
||||
- `TicketOrchestrationPlanRecord`: Orchestrator が routing 中に project-relevant な ordering / dependency / conflict / capacity/waiting / accepted-plan decision を残す。これは queue reorder、自動起動、state 変更ではない。
|
||||
- `TicketClose`: 完了権限と resolution が揃っている場合だけ使う。
|
||||
|
||||
`TicketCreate` は通常 Intake の責務だが、routing 中に follow-up Ticket が必要だと判断した場合は、ユーザー/上位 Orchestrator の合意後にだけ使う。
|
||||
|
||||
## Queued acceptance contract
|
||||
|
||||
- `queued -> inprogress` acceptance の直前に `ShowTicket` / `QueryTicket` の relation blocker projection を再確認する。unresolved `depends_on` や incoming unresolved `blocks` が残る場合は implementation side effect を始めず、理由を thread に残して `planning` へ戻すか blocked diagnostic として停止する。
|
||||
- Relation metadata は project-level constraint であり、OrchestrationPlan は runtime ordering/capacity decision である。relation を OrchestrationPlan で代替しないし、OrchestrationPlan を durable dependency authority として扱わない。
|
||||
|
||||
`state = queued` は、Ticket が routing 対象として人間により Orchestrator へ渡された状態である。Orchestrator は queued notification を受けたら、Ticket、workspace state、対象 Ticket の `TicketOrchestrationPlanQuery` 記録、risk domain に応じた bounded project context を読んで、次のどちらかを行う。
|
||||
|
||||
- unblocked と判断する場合: `queued -> inprogress` を記録してから worktree 作成、implementation/review Pod spawn、その他の implementation side effect に進む。
|
||||
- `before` / `after` / `blocked_by` / `blocks` / `conflicts_with` / `do_not_parallelize` / waiting-capacity 記録がある場合、それを acceptance 判断の入力にする。記録は自動 scheduler ではないため、実際に進めるかどうかは Orchestrator が読んだうえで明示的に決める。
|
||||
- risk flags / risky domain がある場合は、IntentPacket に invariants / reviewer focus / escalation conditions を入れる。risk flag だけを `queued -> planning` の理由にしない。
|
||||
- concrete missing decision / information がある場合: `TicketWorkflowState` で `queued -> planning` を記録し、reason/body と `TicketComment` に不足項目、checked context、なぜ coder の implementation latitude では解決できないか、次の planning question/action を残す。既存の claimed live/restorable Intake/Planning Pod があり、既存通知経路が使える場合は同じ理由を通知する。
|
||||
- external action 待ちなど planning では解決しない blocker の場合: concise な理由を Ticket thread に記録し、必要に応じて attention / action-required frontmatter や `TicketOrchestrationPlanRecord` の blocker/waiting-capacity 記録で明示する。lifecycle 外の storage bucket を routing target にしない。
|
||||
|
||||
Parallel acceptance pass:
|
||||
|
||||
- 明示的な queue review 中に複数の queued Ticket が見える場合、Orchestrator は最初の1件の完了待ちを default にしない。各 Ticket について Ticket body/thread/artifacts、QueryTicket の relation/blocker projection、TicketOrchestrationPlanQuery、workspace/worktree dirty state、visible Pods、既存 branches、conflict/dependency notes を確認する。
|
||||
- 追加で開始してよいのは、blocking relation/dependency がなく、`do_not_parallelize` または applicable conflict record がなく、source/write surfaces が disjoint または conflict risk が小さく機械的で、coder/reviewer follow-up capacity があり、acceptance basis となる Ticket thread/plan/workspace records を side effect 前に記録・commit でき、別 worktree/branch/scope を切れる Ticket だけである。
|
||||
- capacity が見えるのに queued Ticket を idle にする場合は、dependency / conflict / capacity / missing planning decision / dirty workspace / reviewer-coder bottleneck / migration boundary / human gate のいずれかの bounded reason を記録する。
|
||||
- この pass は scheduler、background runner、resource graph solver、automatic queue drain loop ではない。unqueued Ticket を開始せず、各 Ticket の `queued -> inprogress` acceptance を個別に記録する。
|
||||
|
||||
Invariant:
|
||||
|
||||
- `queued -> inprogress` は Orchestrator acceptance marker であり、coder Pod が既に動いていることの後追い記録ではない。
|
||||
- `queued -> inprogress` に失敗した場合、implementation/review Pod を spawn しない。
|
||||
- `inprogress` acceptance 後に worktree/spawn/first-run が失敗した場合は、queued に黙って戻さず、in-progress Ticket に failure/block/recovery note を記録する。
|
||||
- Queue notification だけを根拠に blind spawn しない。必ず Ticket と workspace state を再確認する。
|
||||
|
||||
## Routing classification
|
||||
|
||||
Orchestrator は対象 Ticket を以下のいずれかに分類する。複数に見える場合は、次に必要な action が最も早いものを選ぶ。
|
||||
|
||||
### `requirements_sync_needed`
|
||||
|
||||
まだ `planning` に留めるべき、または `planning -> ready` に進める前に clarification が必要な状態。
|
||||
|
||||
条件:
|
||||
|
||||
- Ticket の目的は見えるが、observable な完了条件が書けない。
|
||||
- ユーザー判断がないと product/API/UX を固定してしまう。
|
||||
- acceptance criteria、binding decisions / invariants、または escalation conditions が不足している。
|
||||
- existing decisions / docs / closed Tickets と矛盾する可能性がある。
|
||||
|
||||
Action:
|
||||
|
||||
- Intake / human / Planning sync に戻す。
|
||||
- `TicketComment` で不足情報と質問を記録する。
|
||||
- coder Pod は起動しない。
|
||||
|
||||
### `return_to_planning`
|
||||
|
||||
`ready` または `queued` とされているが、bounded project-context checks の後でも、実装 side effect 前に解決すべき concrete missing decision / information が残っている状態。
|
||||
|
||||
条件:
|
||||
|
||||
- product / API / UX / authority boundary / storage migration / security / secrets などについて、実装前に決めなければならない具体項目がある。
|
||||
- 複数の自然な方針があり、human / Orchestrator decision なしでは固定できない。
|
||||
- acceptance criteria、binding decisions、または escalation conditions に、実装可否を左右する具体的欠落がある。
|
||||
- Ticket text / risk flags だけではなく、関連 Ticket/thread/artifacts、orchestration plan、関連 workflow/docs/code、durable project context、現在の workspace evidence の relevant subset を確認しても、既存 recorded decision が見つからない。
|
||||
- その不足は coder の bounded implementation latitude / local tactic selection では安全に解消できない。
|
||||
|
||||
Action:
|
||||
|
||||
- `TicketWorkflowState` で `ready -> planning` または `queued -> planning` を記録する。
|
||||
- reason/body と `TicketComment` に以下を明記する。
|
||||
- concrete missing decision / information。
|
||||
- context checked(Ticket thread/artifacts、関連 Ticket/plan、docs/workflow/code/durable context/workspace state のうち実際に確認したもの)。
|
||||
- why implementation latitude is insufficient(なぜ coder/reviewer の local tactic や escalation condition では足りないか)。
|
||||
- next planning question/action。
|
||||
- risk flag / risky domain だけを return reason にしない。
|
||||
- 既存の claimed live/restorable Intake/Planning Pod があり、既存通知経路が使える場合は同じ理由を通知する。実用的な経路がない場合は follow-up として report する。
|
||||
- planning が再度 `ready` にするまで coder Pod は起動しない。
|
||||
|
||||
### `spike_needed`
|
||||
|
||||
実装前に read-only 調査が必要。
|
||||
|
||||
条件:
|
||||
|
||||
- 技術的実現性が不明。
|
||||
- dependency / license / packaging / portability / performance / diagnostics の確認が必要。
|
||||
- current code map が不足している。
|
||||
- bug の再現条件や観測 evidence が不足している。
|
||||
|
||||
Action:
|
||||
|
||||
- read-only investigation を提案する。
|
||||
- 許可があれば task-specific read-only helper Pod を普通の scoped Pod として起動できる。
|
||||
- `TicketComment` に調査問い・scope・完了条件を記録する。
|
||||
- 実装 worktree はまだ作らない。
|
||||
|
||||
### `implementation_ready`
|
||||
|
||||
既存 multi-agent workflow に渡せる状態。
|
||||
|
||||
条件:
|
||||
|
||||
- intent / binding decisions / invariants / implementation latitude / acceptance criteria が明確。
|
||||
- binding decisions / invariants と implementation latitude が区別されている。
|
||||
- reviewer が判断する basis と escalation conditions が明確。
|
||||
- validation が書ける。
|
||||
- design / authority boundary の未決定がない、または planning return / human decision で補われている。
|
||||
- risk flags / risky domain は context lookup と reviewer focus に反映済みで、bounded context check 後に concrete missing decision / information が残っていない。
|
||||
- 残る不確実性が bounded implementation investigation / local tactic selection に閉じている。
|
||||
- IntentPacket を短く書ける。
|
||||
|
||||
Action:
|
||||
|
||||
- `IntentPacket` を作る。risky-but-specified Ticket では、risk を stop reason にせず、binding invariants / implementation latitude / escalation conditions / Critical risks / reviewer focus として記録する。
|
||||
- project-relevant な ordering / blocker / conflict / capacity decision や accepted work plan がある場合は、`TicketOrchestrationPlanRecord` に bounded typed record として残す。local session/socket/raw model output は入れない。
|
||||
- `TicketComment` に routing decision と IntentPacket を記録する。
|
||||
- 許可があれば `multi-agent-workflow` に接続し、worktree + coder/reviewer sibling loop に進む。
|
||||
|
||||
### `review_needed`
|
||||
|
||||
実装はあるが、review / acceptance が未完了。
|
||||
|
||||
条件:
|
||||
|
||||
- implementation report / branch / commit / diff がある。
|
||||
- external review がない、または reviewer blocker が未解決。
|
||||
- validation evidence が足りない。
|
||||
|
||||
Action:
|
||||
|
||||
- reviewer Pod 起動または追加 validation を提案する。
|
||||
- reviewer は recorded intent / binding decisions / invariants / implementation latitude / acceptance criteria / explicit escalation conditions に照らして判断する。不記録の好みや Orchestrator の未共有 preferred tactic を基準にしない。
|
||||
- `TicketComment` に review target と確認観点を記録する。
|
||||
- blocker 未解決のまま merge-ready としない。
|
||||
|
||||
### `blocked_by_dependency_or_missing_authority`
|
||||
|
||||
人間判断または外部イベント待ち。
|
||||
|
||||
条件:
|
||||
|
||||
- design/product/security 判断が必要だが、planning で同期すれば進められる種類ではない。
|
||||
- credential / secret / environment / external service が必要。
|
||||
- 別 Ticket / branch / upstream change の完了待ち。
|
||||
- scope/permission が不足している。
|
||||
|
||||
Action:
|
||||
|
||||
- 必要な判断・外部 action を短く書く。
|
||||
- `TicketComment` に blocked reason と next question を記録する。
|
||||
- 必要に応じて typed relation metadata や orchestration plan の blocker/waiting-capacity 記録で、待ち理由を current state とは別に表す。lifecycle 外の storage bucket へ移す route は使わない。
|
||||
|
||||
### `close_ready`
|
||||
|
||||
完了処理に進める状態。
|
||||
|
||||
条件:
|
||||
|
||||
- requirements / acceptance criteria が満たされている。
|
||||
- review / validation / merge / cleanup evidence が揃っている。
|
||||
- resolution を書ける。
|
||||
- close 権限がある。
|
||||
- 既存 umbrella/progress-container Ticket については、concrete follow-up Ticket と必要な Objective context が存在し、container role を superseded/decomposed として退役できる。
|
||||
|
||||
Action:
|
||||
|
||||
- `TicketClose` または既存 close workflow で resolution を記録する。
|
||||
- umbrella/progress-container Ticket を退役する close resolution では、関連作業がすべて完了したという意味ではなく container role を retired したことを明記し、完了済み concrete Ticket と残る follow-up Ticket / Objective を列挙する。
|
||||
- close 権限がない場合は merge-ready / close-ready dossier を親/人間に提出する。
|
||||
|
||||
### `closed_or_noop`
|
||||
|
||||
routing 不要。
|
||||
|
||||
条件:
|
||||
|
||||
- closed Ticket。
|
||||
- duplicate として既存 Ticket に統合済み。
|
||||
- Ticket 自体が不要と判断済み。
|
||||
|
||||
Action:
|
||||
|
||||
- 追加 action なし。
|
||||
- 必要なら related Ticket へ comment する。
|
||||
|
||||
## Routing 手順
|
||||
|
||||
### 1. 状態確認
|
||||
|
||||
- `git state --short --branch`
|
||||
- `ShowTicket <target>`
|
||||
- 関連 Ticket の `QueryTicket` / `ShowTicket`
|
||||
- 必要に応じて docs/code/workflow/history
|
||||
- 必要に応じて visible Pods / worktrees / branches
|
||||
|
||||
この段階で unrelated dirty changes がある場合、実装/merge には進まず、routing decision の記録だけに留めるかユーザーに確認する。
|
||||
|
||||
### 2. Ticket evidence を読む
|
||||
|
||||
最低限、以下を確認する。
|
||||
|
||||
- Background
|
||||
- Requirements
|
||||
- Acceptance criteria
|
||||
- Binding decisions / invariants
|
||||
- Implementation latitude
|
||||
- Readiness / open questions / risk flags
|
||||
- Escalation conditions
|
||||
- Validation
|
||||
- Thread の plan / decision / implementation_report / review
|
||||
- Artifacts / branch / commit references
|
||||
- risk flags が示す domain(authority-boundary / scope / permission / Pod / persistence / prompt-context / public-api など)
|
||||
|
||||
### 3. Bounded project-context checks を行う
|
||||
|
||||
Ticket evidence だけで planning return を決めない。risk flags や risky domain がある場合は、必要な最小限の project context を確認する。
|
||||
|
||||
代表的な確認先:
|
||||
|
||||
- Ticket thread/artifacts/resolution と関連 Ticket。
|
||||
- `TicketOrchestrationPlanQuery` の accepted-plan / ordering / blocker / conflict 記録。
|
||||
- 関連 workflow/docs/design/development docs。
|
||||
- 現在の code map や既存 implementation pattern。
|
||||
- durable memory/Knowledge/project decisions(利用可能で、判断に関係する場合)。
|
||||
- repository / branch / worktree / visible Pod state。
|
||||
|
||||
目的は broad repository archaeology ではなく、以下を区別できるだけの bounded check である。
|
||||
|
||||
- genuinely missing binding decision / requirement / invariant / acceptance criterion。
|
||||
- Ticket thread、関連 Ticket、docs/workflows/code、durable context にすでに記録された decision。
|
||||
- coder が implementation latitude の中で選べる local tactic / bounded investigation。
|
||||
|
||||
### 4. Classification を決める
|
||||
|
||||
例:
|
||||
|
||||
- implementation-ready に見えるが authority boundary の explicit decision がなく、bounded context check でも recorded decision がない → concrete missing decision として `return_to_planning`; routing record に missing item / checked context / implementation latitude では足りない理由 / next question を書く。
|
||||
- implementation-ready に見えるが単に risk が高い → `implementation_ready` とし、IntentPacket に escalation / reviewer focus を明記する。
|
||||
- `allow-spawnpod-child-workspace-cwd` のように scope / Pod / authority-adjacent domain に触れるが、intent、authority invariants、implementation latitude、escalation conditions が指定済み → `implementation_ready`。context check で既存 SpawnPod scope/cwd policy と workflow invariants を確認し、reviewer focus に authority leakage / workspace-cwd separation / delegation-scope preservation を入れる。risk domain だけを理由に `planning` へ戻さない。
|
||||
- 実装済みだが review がない → `review_needed`
|
||||
- 要件が曖昧で spike も必要そう → `requirements_sync_needed` を優先し、調査問いを明確化する
|
||||
- 完了しているが close 権限がない → `close_ready` として dossier を返す
|
||||
|
||||
### 5. Routing decision を Ticket に記録する
|
||||
|
||||
`TicketComment` の role は通常 `decision` または `plan` を使う。
|
||||
|
||||
標準形:
|
||||
|
||||
```markdown
|
||||
Routing decision: <classification>
|
||||
|
||||
Reason:
|
||||
- ...
|
||||
|
||||
Evidence checked:
|
||||
- Ticket body / thread / artifacts
|
||||
- related Ticket(s) / orchestration plan records
|
||||
- code/docs/workflow paths
|
||||
- branch/worktree/Pod state if relevant
|
||||
|
||||
If returning to planning:
|
||||
- Missing decision/information: ...
|
||||
- Context checked: summarize the relevant subset from Evidence checked above
|
||||
- Why implementation latitude is insufficient: ...
|
||||
- Next planning question/action: ...
|
||||
|
||||
Next action:
|
||||
- ...
|
||||
|
||||
Escalate if:
|
||||
- ...
|
||||
```
|
||||
|
||||
### 6. IntentPacket を作る(implementation_ready の場合)
|
||||
|
||||
`multi-agent-workflow` に渡す前に、以下を短くまとめる。
|
||||
|
||||
```text
|
||||
Intent:
|
||||
- 何を実現するか。
|
||||
|
||||
Binding decisions / invariants:
|
||||
- 人間/Orchestrator/Ticket に記録済みで coder / reviewer が従うべき decision と、壊してはいけない design / authority boundary。
|
||||
- 具体的な除外・触れてはいけない場所が binding decision である場合はここに書く。
|
||||
|
||||
Requirements / acceptance criteria:
|
||||
- 完了時に満たす observable な要件と reviewer が判断できる基準。
|
||||
|
||||
Implementation latitude:
|
||||
- Coder が調査しながら選んでよい local tactic / file-local organization / bounded uncertainty。
|
||||
|
||||
Escalate if:
|
||||
- 親/人間に戻す判断条件。特に product / API / UX / authority boundary / explicit design constraint を変える必要が出た場合。
|
||||
|
||||
Validation:
|
||||
- 実行すべき format / build / test / doctor。
|
||||
|
||||
Current code map:
|
||||
- 実装対象と触ってはいけない場所。
|
||||
|
||||
Critical risks / reviewer focus:
|
||||
- reviewer にも見てほしい失敗パターン。reviewer は recorded intent / binding decisions / invariants / implementation latitude / acceptance criteria / explicit escalation conditions に照らして判断し、不記録の preferred tactic を基準にしない。
|
||||
```
|
||||
|
||||
IntentPacket が短く書けない場合、`implementation_ready` ではなく `return_to_planning` または `requirements_sync_needed` に戻す。
|
||||
|
||||
### 7. 後続 Workflow へ接続する
|
||||
|
||||
- `requirements_sync_needed` → `ticket-intake-workflow` / human / planning sync
|
||||
- `return_to_planning` → `ticket-preflight-workflow`(legacy compatibility canonical id の planning sync entry)
|
||||
- `spike_needed` → read-only investigation plan / Pod(許可後)
|
||||
- `implementation_ready` → `multi-agent-workflow`
|
||||
- `review_needed` → reviewer Pod / review workflow
|
||||
- concrete blocker / missing decision → human / parent Orchestrator
|
||||
- `close_ready` → close workflow / maintainer decision
|
||||
|
||||
## 完了条件
|
||||
|
||||
この Workflow の完了条件は次のいずれかである。
|
||||
|
||||
- routing decision が Ticket に記録され、次に接続する Workflow / human action が明確である。
|
||||
- `ready` / `queued` を `planning` に戻した場合、typed state-change/routing event に concrete missing decision / information、checked context、implementation latitude では足りない理由、次の planning question/action が残っている。
|
||||
- implementation-ready Ticket について IntentPacket が Ticket に記録され、`multi-agent-workflow` に渡せる。
|
||||
- requirements-sync / planning return / spike / blocked / review / close-ready の理由と次 action が Ticket に記録されている。
|
||||
- routing 不要と判断され、その理由が明確である。
|
||||
|
||||
## この Workflow で固定しないもの
|
||||
|
||||
- unattended scheduler。
|
||||
- LeaseStore / queue persistence。
|
||||
- queue/dashboard UI。
|
||||
- automatic Pod spawning policy。
|
||||
- TicketUpdate tool の導入。
|
||||
- external tracker integration。
|
||||
|
||||
これらは routing decision を人間/Orchestrator が明示的に扱えるようになった後の follow-up とする。
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
description: 互換 canonical id を残した Ticket planning / requirements sync workflow。preflight を独立 lane や state として扱わない。
|
||||
model_invokation: true
|
||||
user_invocable: true
|
||||
requires: []
|
||||
---
|
||||
|
||||
# Ticket Planning / Requirements Sync Workflow
|
||||
|
||||
このファイル名は既存の workflow discovery / durable references 互換のために残す。新しい概念としての `preflight` state / lane / long-lived operation は作らない。ここで扱う作業は、Ticket を `planning` に戻して不足している決定・情報・受け入れ条件を同期するための planning activity である。
|
||||
|
||||
## 目的
|
||||
|
||||
実装に入る前または Orchestrator routing 中に、bounded project-context checks 後も concrete missing decision / information / acceptance condition が残る Ticket を planning に戻し、Ticket thread に監査可能な形で同期内容を残す。
|
||||
|
||||
この workflow は次をしてはいけない。
|
||||
|
||||
- `preflight` を state として扱う。
|
||||
- `preflight` vocabulary を current Ticket metadata として新規に書く。
|
||||
- 「リスクがある」だけで Ticket を戻す。
|
||||
- broad effort の進捗を保持するためだけの umbrella/progress-container Ticket を作る。
|
||||
- parent/child、sub-ticket、part-of、contains などの hierarchy/container relation を split/refinement の代替として設計する。
|
||||
- Coder / Reviewer / worktree mechanics を再設計する。
|
||||
|
||||
## 適用条件
|
||||
|
||||
次のいずれかを満たす場合に使う。
|
||||
|
||||
- `planning` Ticket の要件・受け入れ条件・制約を明確化する。
|
||||
- ユーザーが concrete work item として求めた initial planning / design / investigation Ticket の scope と acceptance criteria を明確化する。
|
||||
- `ready` または `queued` Ticket について、Orchestrator が Ticket/thread/artifacts、関連 Ticket/plan、関連 workflow/docs/code、durable project context、workspace evidence の relevant subset を bounded に確認したうえで、実装開始前に concrete missing decision / information を特定した。
|
||||
- 既存 Ticket に obsolete state vocabulary が残っており、planning terminology へ整理する必要がある。
|
||||
|
||||
適用しない条件:
|
||||
|
||||
- Orchestrator が具体的な不足項目、checked context、implementation latitude では足りない理由を言語化できない。
|
||||
- 単に変更範囲が広い、リスクが高い、またはレビュー観点が多いだけである。
|
||||
- すでに `queued -> inprogress` が記録され、実装 side effect が始まっている。
|
||||
|
||||
この場合は Ticket を戻さず、IntentPacket に escalation / reviewer focus を明記して進める。
|
||||
|
||||
## 手順
|
||||
|
||||
1. Ticket の current frontmatter と recent thread を読む。
|
||||
2. `ready` / `queued` から planning に戻す場合は、関連 Ticket/plan、docs/workflow/code、durable project context、workspace state の relevant subset を bounded に確認する。
|
||||
3. 不足している decision / information / acceptance condition を箇条書きで特定し、なぜ coder/reviewer の implementation latitude や escalation condition では足りないかを書く。
|
||||
4. `ready` または `queued` から戻す場合は、typed state change で `to = planning` を記録する。reason/body には concrete missing item、checked context、implementation latitude では足りない理由、次の planning question/action を含める。
|
||||
5. 既存の claimed live/restorable Intake/Planning Pod があり、利用可能な通知経路がある場合は、その Pod に同じ不足理由を通知する。実用的な経路が無い場合は follow-up として report する。
|
||||
6. Ticket body または thread に requirements sync 結果を残す。広い依頼を分割する場合は、concrete follow-up Ticket / Objective context / split decision record へ責務を分け、umbrella container や hierarchy relation を作らないことを記録する。
|
||||
7. Ticket が queue 可能になったら `planning -> ready` を typed state change / `TicketIntakeReady` で記録する。
|
||||
|
||||
## 記録テンプレート
|
||||
|
||||
```markdown
|
||||
## Planning sync
|
||||
|
||||
Missing decisions / information:
|
||||
- ...
|
||||
|
||||
Context checked:
|
||||
- Ticket/thread/artifacts: ...
|
||||
- related Ticket/plan: ...
|
||||
- docs/workflow/code/durable/workspace context: ...
|
||||
|
||||
Why implementation latitude is insufficient:
|
||||
- ...
|
||||
|
||||
Next planning question/action:
|
||||
- ...
|
||||
|
||||
Decisions made:
|
||||
- ...
|
||||
|
||||
Acceptance criteria changes:
|
||||
- ...
|
||||
|
||||
Risk / reviewer focus:
|
||||
- ...
|
||||
|
||||
Readiness:
|
||||
- Keep in planning because ...
|
||||
- or mark ready because ...
|
||||
```
|
||||
|
||||
## 完了条件
|
||||
|
||||
- Ticket に具体的な不足項目または解決済み decision が記録されている。
|
||||
- `planning` に戻した場合、state_changed event または thread に concrete missing decision / information、checked context、implementation latitude では足りない理由、次の planning question/action が残っている。
|
||||
- `ready` に進める場合、未解決の blocking attention/action が残っていない。
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
description: yoi プロジェクトで child git worktree を作成・管理するための機械的手順。coder Pod に作らせず、orchestrator Pod が Orchestrator workspace/orchestration branch から実行する。
|
||||
model_invokation: true
|
||||
user_invocable: true
|
||||
requires: []
|
||||
---
|
||||
# Worktree Workflow
|
||||
|
||||
yoi プロジェクトで実装差分を root/original workspace から分離するため、記録済み implementation worktree root の `.worktree/<task-name>` に child git worktree を作る。これは **worktree の扱い方だけ** を定める Workflow であり、ticket 選定、coder / reviewer sibling の起動、外部レビュー、orchestration branch への integration は `/multi-agent-workflow` 側で扱う。
|
||||
|
||||
yoi では Pod の write scope が排他的に委譲されるため、child Pod の write scope は child worktree に限定する。child worktree は Yoi project records marker として tracked `.yoi` records を含んでよいが、generated/personal memory root `.yoi/memory`、local override、runtime state、logs、locks、secret-like files は出さない。Orchestrator workspace or recorded Ticket backend remains the place for active orchestration progress, review evidence, integration records, and Ticket lifecycle updates.
|
||||
|
||||
## 適用範囲
|
||||
|
||||
この Workflow は親 Pod / 下位 orchestrator が Orchestrator workspace/orchestration branch から実行する。root/original workspace は read/write/validation/cleanup/git 操作の対象にしない。implementation worktree は記録済み implementation worktree root 配下に作る。
|
||||
|
||||
- coder Pod にこの Workflow を渡して worktree を作らせない。
|
||||
- coder Pod は、orchestrator が作成済みの child worktree を受け取り、その中で実装・build・test・報告を行う。
|
||||
- reviewer Pod は、coder Pod の子ではなく orchestrator 配下の sibling として、原則 read-only で child worktree と Orchestrator workspace の Ticket/context を読む。
|
||||
- ticket 作成、active orchestration progress、review evidence、integration、Ticket lifecycle update は Orchestrator workspace または記録済み Ticket backend 側で扱う。
|
||||
- branch-local artifacts / dossiers / docs/report / tracked `.yoi` project records は、実装対象に必要なら child worktree 内で扱ってよい。
|
||||
|
||||
## 原則
|
||||
|
||||
- 1 ticket / 1 実装 task につき 1 worktree を作る。
|
||||
- 複数 ticket を下位 orchestrator に任せる場合も、実装差分は ticket / bounded task ごとに worktree を分ける。
|
||||
- worktree path は `<implementation-worktree-root>/.worktree/<task-name>`。
|
||||
- branch 名は原則 `<task-name>` と同じ kebab-case。
|
||||
- child worktree には `.yoi` project records を出してよい。
|
||||
- child worktree では `.yoi/memory`、local/runtime/log/lock/secret-like paths を sparse checkout で除外する。
|
||||
- active orchestration progress、review evidence、integration、Ticket lifecycle update は Orchestrator workspace または記録済み Ticket backend 側で扱う。branch-local artifacts/dossiers は child worktree 内に置いてよい。
|
||||
- push はしない。
|
||||
|
||||
## 事前確認
|
||||
|
||||
作成前に以下を確認する。
|
||||
|
||||
1. 対象 ticket / task が決まっているか。
|
||||
2. `<task-name>` が branch / path 名に使える kebab-case か。
|
||||
3. `git worktree add` を実行してよい許可があるか。
|
||||
4. root/original workspace で実行しようとしていないか。
|
||||
5. 同名 branch / worktree が既に存在しないか。
|
||||
6. coder / reviewer を sibling として扱う orchestrator が誰か明確か。
|
||||
|
||||
同名 branch がある場合は、既存 branch を使うか、人間に確認する。`git worktree add -b` で上書きしない。
|
||||
|
||||
## 作成手順
|
||||
|
||||
Orchestrator workspace で実行する。root/original workspace では実行しない。
|
||||
|
||||
```bash
|
||||
git -C <orchestrator-workspace-root> worktree add <implementation-worktree-root>/.worktree/<task-name> -b <task-name> HEAD
|
||||
|
||||
git -C <implementation-worktree-root>/.worktree/<task-name> sparse-checkout init --no-cone
|
||||
git -C <implementation-worktree-root>/.worktree/<task-name> sparse-checkout set --no-cone \
|
||||
'/*' \
|
||||
'!/.yoi/memory/' \
|
||||
'!/.yoi/memory/**' \
|
||||
'!/.yoi/logs/' \
|
||||
'!/.yoi/logs/**' \
|
||||
'!/.yoi/_logs/' \
|
||||
'!/.yoi/_logs/**' \
|
||||
'!/.yoi/**/_logs/' \
|
||||
'!/.yoi/**/_logs/**' \
|
||||
'!/.yoi/locks/' \
|
||||
'!/.yoi/locks/**' \
|
||||
'!/.yoi/**/*.log' \
|
||||
'!/.yoi/**/*.lock' \
|
||||
'!/.yoi/**/.lock' \
|
||||
'!/.yoi/override.local.toml' \
|
||||
'!/.yoi/**/*.local' \
|
||||
'!/.yoi/**/*.local.*' \
|
||||
'!/.yoi/local/' \
|
||||
'!/.yoi/local/**' \
|
||||
'!/.yoi/runtime/' \
|
||||
'!/.yoi/runtime/**' \
|
||||
'!/.yoi/pods/' \
|
||||
'!/.yoi/pods/**' \
|
||||
'!/.yoi/sessions/' \
|
||||
'!/.yoi/sessions/**' \
|
||||
'!/.yoi/sockets/' \
|
||||
'!/.yoi/sockets/**' \
|
||||
'!/.yoi/tmp/' \
|
||||
'!/.yoi/tmp/**' \
|
||||
'!/.yoi/cache/' \
|
||||
'!/.yoi/cache/**' \
|
||||
'!/.yoi/secrets/' \
|
||||
'!/.yoi/secrets/**' \
|
||||
'!/.yoi/**/*.secret' \
|
||||
'!/.yoi/**/*.secret.*'
|
||||
```
|
||||
|
||||
この sparse-checkout は `.yoi` 自体を除外しない。`.yoi/memory` は generated/personal memory marker として child worktree から外し、generated log trees は root `_logs` だけでなく recursive `.yoi/**/_logs/**` も外す。memory root detection の実装変更はこの Workflow では扱わない。
|
||||
|
||||
確認する。
|
||||
|
||||
```bash
|
||||
git -C <implementation-worktree-root>/.worktree/<task-name> status --short --branch
|
||||
test ! -e <implementation-worktree-root>/.worktree/<task-name>/.yoi/memory
|
||||
if test -d <implementation-worktree-root>/.worktree/<task-name>/.yoi; then
|
||||
test ! -e <implementation-worktree-root>/.worktree/<task-name>/.yoi/override.local.toml
|
||||
test -z "$(find <implementation-worktree-root>/.worktree/<task-name>/.yoi \
|
||||
\( -path '*/_logs' -o -path '*/logs' -o -path '*/locks' \
|
||||
-o -path '*/local' -o -path '*/runtime' -o -path '*/pods' \
|
||||
-o -path '*/sessions' -o -path '*/sockets' -o -path '*/tmp' \
|
||||
-o -path '*/cache' -o -path '*/secrets' -o -name '*.log' \
|
||||
-o -name '*.lock' -o -name '.lock' -o -name '*.local' \
|
||||
-o -name '*.local.*' -o -name '*.secret' -o -name '*.secret.*' \) \
|
||||
-print -quit)"
|
||||
fi
|
||||
```
|
||||
|
||||
この確認は `.yoi` project records の存在を失敗扱いしない。`.yoi/memory` と local/runtime/log/lock/secret-like paths が出ていないことを確認する。
|
||||
|
||||
失敗した場合は、worktree / branch / lock の状態を確認し、勝手に cleanup せず人間へ報告する。
|
||||
|
||||
## Pod へ渡す scope
|
||||
|
||||
Pod を使う場合、coder Pod の SpawnPod `cwd` は child worktree に設定する。`cwd` は child process/tool default cwd だけを変え、runtime workspace root・project record root・scope/authority は変えないため、下の明示 scope は別途渡す。
|
||||
|
||||
coder Pod 推奨 scope:
|
||||
|
||||
```text
|
||||
read: <implementation-worktree-root>/.worktree/<task-name>
|
||||
write: <implementation-worktree-root>/.worktree/<task-name>
|
||||
```
|
||||
|
||||
reviewer Pod 推奨 scope:
|
||||
|
||||
```text
|
||||
read: <implementation-worktree-root>/.worktree/<task-name>
|
||||
read: <orchestrator-workspace-root> # Ticket/intent/review context が必要な範囲に限定する
|
||||
```
|
||||
|
||||
reviewer は原則 write scope を持たない。review artifact を書かせる必要がある場合だけ、ticket artifacts など限定 directory を write scope として渡す。
|
||||
|
||||
より狭く切れる場合は、coder の write scope を変更対象 crate / directory まで狭めてよい。ただし build / test に必要な生成物を書けることを確認する。
|
||||
|
||||
## child worktree 内の禁止事項
|
||||
|
||||
- `.yoi/memory` を作らない / コピーしない / 復元しない。
|
||||
- local overrides、runtime sockets/state、Pod session mirrors、cache/tmp、logs、locks、secret-like files を作らない / コピーしない / commit しない。
|
||||
- root/original workspace の `TODO.md` / `tickets/` / `docs/report/` / `.yoi` を読まない・編集しない。
|
||||
- active orchestration progress、review evidence、integration、Ticket lifecycle update を child worktree 内だけで完結させない。
|
||||
- 実装対象に必要な tracked `.yoi` project records、branch-local artifacts / dossiers / docs/report は child worktree 内で扱ってよい。
|
||||
- merge / push / branch deletion / worktree remove をしない。
|
||||
- scope / permission / history persistence / prompt context 加工原則に関わる設計変更を無断で行わない。
|
||||
|
||||
## 完了時の扱い
|
||||
|
||||
worktree 作成 Workflow としては、完了時に integration しない。orchestration branch への integration、Ticket 完了、child worktree cleanup は `/multi-agent-workflow` 側で Orchestrator workspace に対して行う。root/original workspace は対象にしない。
|
||||
|
||||
coder Pod へ渡す完了報告項目の標準形:
|
||||
|
||||
- worktree path
|
||||
- branch 名
|
||||
- commit hash(coder Pod に commit を許可した場合)
|
||||
- 変更ファイル
|
||||
- 実装概要
|
||||
- 実行した build / test / format
|
||||
- 未解決事項
|
||||
- review に回せるか
|
||||
|
||||
reviewer Pod へ渡す完了報告項目の標準形:
|
||||
|
||||
- 読んだ ticket / intent packet / diff
|
||||
- 実際のコード変更の概念的説明
|
||||
- intent / requirements / invariant との対応
|
||||
- blocker / non-blocker / follow-up
|
||||
- validation の妥当性
|
||||
- 親または上位 orchestrator が判断すべき残論点
|
||||
@@ -0,0 +1,22 @@
|
||||
workspace_id = "0197a949-4b6b-7f2a-9d9a-1f87e3a4c5b6"
|
||||
created_at = "2026-06-23T00:00:00Z"
|
||||
display_name = "yoi"
|
||||
|
||||
[ticket]
|
||||
language = "Japanese"
|
||||
|
||||
[ticket.backend]
|
||||
provider = "builtin:yoi_local"
|
||||
root = ".yoi/tickets"
|
||||
|
||||
[ticket.roles.intake]
|
||||
profile = "builtin:intake"
|
||||
|
||||
[ticket.roles.orchestrator]
|
||||
profile = "builtin:orchestrator"
|
||||
|
||||
[ticket.roles.coder]
|
||||
profile = "builtin:coder"
|
||||
|
||||
[ticket.roles.reviewer]
|
||||
profile = "builtin:reviewer"
|
||||
@@ -1,77 +1,68 @@
|
||||
すでにシステムのドッグフーディングに成功しており、改善・機能追加のフェーズになっている。
|
||||
随所の細かい仕様を詰めながら実装を進めている。
|
||||
すでにシステムのドッグフーディングに成功しているが、一旦安定した旧バージョンで、ブラウザ/TUI Client/backend/runtimeの分離とチームスペースとしてのworkspaceを作るObjectiveを進めている。
|
||||
|
||||
## このシステムに置ける設計要旨
|
||||
|
||||
- プロンプトはすべて resources/promptsに集約している。管理効率の向上と同時に、ユーザーがオーバーライドする形式でもある。
|
||||
- E2E(実プロセスをスポーンさせてのテスト)は未設計。
|
||||
- 変更量を最小にするために設計を歪めたり、設計問題に対して不必要な後方互換性を作らない。長期的なメンテナンスと型安全性を追求すること。
|
||||
|
||||
### LLM コンテキストの加工原則
|
||||
|
||||
LLM に投げる context への割り込みは、大きく2種類に分かれる。**前者は許されるが、後者は禁止**。
|
||||
|
||||
Podの状態から純粋に再現可能で、且つ揮発性の無い操作であることが望ましい。(pruning、tool result の content 切り詰め、prompt cache anchor の付与等)。
|
||||
Workerの状態から純粋に再現可能で、且つ揮発性の無い操作であることが望ましい。(pruning、tool result の content 切り詰め、prompt cache anchor の付与等)。
|
||||
原則として、コンテキストは積み重ねるものであり、一時的にメッセージを差し込むことや、過去のメッセージを改ざんすることはKVキャッシュのヒット率を下げる。
|
||||
|
||||
**禁止**: ターンを跨ぐことができない情報に基づいて、history に記録せずに context だけにコンテンツを差し込むこと。これをやると LLM はそれに反応して生成を行う一方、次以降のターンでhistoryに残らないため、「自分がなぜその発言/tool call をしたか」の根拠が消えるうえ、prompt cache のヒット率も低下させることになる。
|
||||
|
||||
新しい input を context に乗せたいなら、必ず先に `worker.history` に append して commit すること。`history.json` への永続化はそこから自動的についてくる。Notify / PodEvent / `<system-reminder>` 系はこの原則で扱う。
|
||||
新しい input を context に乗せたいなら、必ず先に `worker.history` に append して commit すること。`history.json` への永続化はそこから自動的についてくる。Notify / WorkerEvent / typed `SystemItem` reminder はこの原則で扱う。
|
||||
また、キャッシュを破壊するタイミングは正確にコントロールされる必要があり、キャッシュ破壊とトークン消費のトレードオフに基づいて慎重に設計されるべきである。
|
||||
|
||||
---
|
||||
|
||||
## 実際のセッションを読んでデバッグする
|
||||
## 検証
|
||||
|
||||
`~/.insomnia/sessions`にすべてのセッションがある。jsonlなので、いい感じにBashで読むこと。
|
||||
開発中は、変更した契約を証明する最小の target / filter から実行する。
|
||||
|
||||
```sh
|
||||
cargo test -p <crate> --lib <test-or-module-filter>
|
||||
cargo test -p <crate> --test <test-target> <test-filter>
|
||||
```
|
||||
|
||||
完了前には、workspace rootで必ず`cargo check`を実行する。rootの`cargo check`は
|
||||
`default-members`に含まれるTUIやServerを含む通常のcompile closureを確認するため、公開型の
|
||||
変更ごとにLLMがreverse dependencyを推測して`-p`を列挙する運用にはしない。
|
||||
|
||||
```sh
|
||||
cargo check
|
||||
cargo test -p <changed-crate>
|
||||
cargo fmt --all -- --check
|
||||
git diff --check HEAD
|
||||
```
|
||||
|
||||
変更したcrate全体のtestに加え、影響するfeature構成やtest-only targetがある場合は、その検証を
|
||||
追加する。`cargo check`はtestを実行せず、通常有効でないfeatureまでは確認しないため、semanticな
|
||||
証明とfeature境界の検証はtargeted test/checkで補う。
|
||||
|
||||
workspace全体のtest、`--all-targets`、E2E、Nix/Docker buildなどの重い検証は、変更した境界を
|
||||
通常のroot checkと狭い検証では証明できない場合や、明示的に要求された場合に選ぶ。実行した検証が
|
||||
何を証明するのかを意識し、広い検証を形式的に回すだけにしない。
|
||||
|
||||
---
|
||||
|
||||
## Git操作
|
||||
## ドッグフーディング時の Ticket 境界
|
||||
|
||||
明示的に指示されない限り、読み取り以外の操作は控えること。
|
||||
基本はworktree上の一時的なブランチでコミットを重ね、メインブランチに取り込む運用をしている。
|
||||
コミットメッセージは適当に`<prefix>: *簡潔な1行*`で書いている。
|
||||
Yoi Worker で作業する場合、Ticket の authority・ライフサイクル・操作方法は Yoi
|
||||
system instructions と、その Worker に提供された typed Ticket tools に従うこと。
|
||||
|
||||
外部の参考プロジェクトは必要に応じてローカルの外部 checkout からReadすること。
|
||||
Codex など typed Ticket tools が提供されていないクライアントでは、`yoi ticket`
|
||||
CLI や保存先の直接操作で Ticket tools を代替しないこと。Ticket の作成・更新は
|
||||
Yoi Worker に委ねる。
|
||||
|
||||
---
|
||||
|
||||
## Work item / Ticket の運用について
|
||||
|
||||
作業管理は `work-items/` と `tickets.sh` を正とする。時系列・状態遷移の最終的な根拠は git history なので、work item の作成・更新・レビュー・完了はファイル操作と commit で表現する。
|
||||
|
||||
### 基本コマンド
|
||||
|
||||
- 新規作成: `./tickets.sh create --title "..." [--slug slug] [--kind task] [--priority P2] [--label a,b]`
|
||||
- 一覧: `./tickets.sh list [--status open|pending|closed|all]`
|
||||
- 詳細: `./tickets.sh show <id-or-slug>`
|
||||
- コメント / 計画 / 判断 / 実装報告: `./tickets.sh comment <id-or-slug> [--role comment|plan|decision|implementation_report] [--file path]`
|
||||
- レビュー記録: `./tickets.sh review <id-or-slug> --approve|--request-changes [--file path]`
|
||||
- 状態変更: `./tickets.sh status <id-or-slug> open|pending|closed`
|
||||
- 完了: `./tickets.sh close <id-or-slug> [--resolution text|--file path]`
|
||||
- 整合性確認: `./tickets.sh doctor`
|
||||
|
||||
`tickets.sh` は `work-items/{open,pending,closed}/<id>/` 配下の `item.md`、`thread.md`、`artifacts/` を扱う。完了時は `resolution.md` も作られる。手でファイルを作るより、原則としてスクリプトを使うこと。
|
||||
|
||||
### Work item の粒度
|
||||
|
||||
- 1 work item = 完了時点で、実装が仕様または機能として説明できる粒度。
|
||||
- 作成時は背景・要件・受け入れ条件を明確にする。実装手順やコード詳細は、必要になるまで増やしすぎない。
|
||||
- チケット内の Phase / Step は実装順序であり、外部の依存関係管理として扱わない。
|
||||
- ビルドが通り、その機能に限り「まだ動作できない」と明示できている場合を除き、全体として動作可能な状態を保つ。
|
||||
|
||||
### ライフサイクル
|
||||
|
||||
- 作成: `./tickets.sh create ...` で `work-items/open/...` を作成し、必要な前提を書いて commit する。
|
||||
- 詳細化・前提変更: `item.md` を更新し、必要に応じて `./tickets.sh comment` で `thread.md` に経緯を残して commit する。
|
||||
- レビュー: `./tickets.sh review <id-or-slug> --approve|--request-changes` で `thread.md` にレビュー結果を追記して commit する。
|
||||
- 完了: `./tickets.sh close <id-or-slug>` で `work-items/closed/...` に移動し、`resolution.md` と完了状態を commit する。
|
||||
|
||||
worktree と併用して作業を進める場合、必ずブランチを切る前に対象 work item を作成・詳細化して commit してから切ること。
|
||||
|
||||
レビューは diff の確認だけでなく、work item の前提・要件・受け入れ条件が提出された実装で満たされているかを確認する。常に、その実装で良いのか、コードベースを歪めていないか、不必要な実装ではないかを確認すること。
|
||||
YoiでYoiを開発している際、AI自身のフィードバックを元に改善を回すために `docs/report/`ディレクトリに感じた障壁や改善案等を書き残す形にした。 明確に力不足な点/ツールの問題があった場合や、ユーザーからの指示があった際に作ること。
|
||||
|
||||
---
|
||||
|
||||
insomniaでinsomniaを開発している際、AI自身のフィードバックを元に改善を回すために `docs/report/`ディレクトリに感じた障壁や改善案等を書き残す形にした。 明確に力不足な点/ツールの問題があった場合や、ユーザーからの指示があった際に作ること。
|
||||
絶対に自身が動作しているプロセスを止めないこと。
|
||||
マージ後のドッグフーディング環境の更新は必ずユーザーの操作で行う。
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
全体設計が概ね固まり、随所の細かい仕様を詰めながら実装を進めている。
|
||||
|
||||
## このシステムに置ける設計要旨
|
||||
|
||||
- プロンプトはすべて resources/promptsに集約している。管理効率の工場と同時に、ユーザーがオーバーライドする形式でもある。
|
||||
- E2E(実プロセスをスポーンさせてのテスト)は未設計。
|
||||
- 変更量を最小にするために設計を歪めたり、設計問題に対して不必要な後方互換性を作らない。長期的なメンテナンスと型安全性を追求すること。
|
||||
|
||||
### LLM コンテキストの加工原則
|
||||
|
||||
LLM に投げる context への割り込みは、大きく2種類に分かれる。**前者は許されるが、後者は禁止**。
|
||||
|
||||
Podの状態から純粋に再現可能で、且つ揮発性の無い操作であることが望ましい。(pruning、tool result の content 切り詰め、prompt cache anchor の付与等)。
|
||||
原則として、コンテキストは積み重ねるものであり、一時的にメッセージを差し込むことや、過去のメッセージを改ざんすることはKVキャッシュのヒット率を下げる。
|
||||
|
||||
**禁止**: ターンを跨ぐことができない情報に基づいて、history に記録せずに context だけにコンテンツを差し込むこと。これをやると LLM はそれに反応して生成を行う一方、次以降のターンでhistoryに残らないため、「自分がなぜその発言/tool call をしたか」の根拠が消えるうえ、prompt cache のヒット率も低下させることになる。
|
||||
|
||||
新しい input を context に乗せたいなら、必ず先に `worker.history` に append して commit すること。`history.json` への永続化はそこから自動的についてくる。Notify / PodEvent / `<system-reminder>` 系はこの原則で扱う(→ `tickets/notify-history-persist.md`)。
|
||||
また、キャッシュを破壊するタイミングは正確にコントロールされる必要があり、キャッシュ破壊とトークン消費のトレードオフに基づいて慎重に設計されるべきである。
|
||||
|
||||
---
|
||||
|
||||
## 実際のセッションを読んでデバッグする
|
||||
|
||||
`~/.insomnia/sessions`にすべてのセッションがある。jsonlなので、いい感じにBashで読むこと。
|
||||
|
||||
---
|
||||
|
||||
## Git操作
|
||||
|
||||
workflowで明示されない限り、読み取り以外の操作は控えること。
|
||||
基本はworktree上の一時的なブランチでコミットを重ね、メインブランチに取り込む運用をしている。
|
||||
コミットメッセージは適当に`<prefix>: *簡潔な1行*`で書いている。
|
||||
|
||||
外部の参考プロジェクトは必要に応じてローカルの外部 checkout からReadすること。
|
||||
|
||||
---
|
||||
|
||||
## Ticketの運用について
|
||||
|
||||
`TODO.md`、`tickets/`はgitで管理されていて、時系列の管理はgitを参照して把握すること。
|
||||
|
||||
### TODO.md
|
||||
|
||||
- 1チケット = 1行。未完了のみ記載し、完了したら行ごと削除する(履歴はgitで追える)
|
||||
- ネストは同一領域のグルーピング(表示用)にのみ使う。実装上の依存関係はネストで表現しない
|
||||
- 完了した子は削除し、親は未完了の子がある限り残す。最後の子が完了したら親ごと削除
|
||||
- Ticketを追加する際は、合わせてTODOも書くこと
|
||||
|
||||
### Ticket の粒度
|
||||
|
||||
- 1チケット = 完了時点で、実装が仕様又は機能として説明できる粒度。
|
||||
- 作成時、背景や要件を前提として書き、実装の方針やコードの詳細は不必要に増やさない。
|
||||
- チケット内のステップ(Phase 1, 2, ...)は実装順序であり、TODO等、外に出さない
|
||||
- ビルドが通り、その機能に限り,まだ動作できないと明示出来ている場合を除いて全体を通して動作させられる状態である必要がある。
|
||||
|
||||
### Ticket のライフサイクル
|
||||
|
||||
gitがタイムラインの単一の情報源。ファイル操作とcommitで状態遷移を表現する。
|
||||
|
||||
a. 作成: `tickets/foo.md` を作成してcommit
|
||||
b. 詳細化や前提の変化: `tickets/foo.md` を更新してcommit
|
||||
c. レビュー: `tickets/foo.md` にレビュー状態を追記 + `tickets/foo.review.md` を作成してcommit
|
||||
d. 完了: `tickets/foo.md` と `tickets/foo.review.md` を両方削除してcommit
|
||||
|
||||
worktreeと併用して作業を進める場合、必ずブランチを切る前に対象のチケットをコミットしてから切ること。
|
||||
|
||||
TODO.mdのリンクは完了後に切れるが、そのリンクを元にgitで消されたファイルを読み、内容を把握できる。
|
||||
`.review.md` にはレビューの指摘事項と判断結果を記載する。
|
||||
レビューはdiffの確認だけでなく、チケットはどのような前提・要件であり、それが達成されたかの確認まで含めて行う。
|
||||
常に、提出された実装で良いのか、コードベースを歪めていないか、不必要な実装ではないかを確認すること。
|
||||
|
||||
---
|
||||
|
||||
insomniaでinsomniaを開発している際、AI自身のフィードバックを元に改善を回すために `docs/report/`ディレクトリに感じた障壁や改善案等を書き残す形にした。 明確に力不足な点/ツールの問題があった場合や、ユーザーからの指示があった際に作ること。
|
||||
Generated
+2060
-218
File diff suppressed because it is too large
Load Diff
+81
-11
@@ -2,21 +2,64 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/client",
|
||||
"crates/daemon",
|
||||
"crates/llm-worker",
|
||||
"crates/llm-worker-macros",
|
||||
"crates/agen",
|
||||
"crates/agen-macros",
|
||||
"crates/session-store",
|
||||
"crates/secrets",
|
||||
"crates/manifest",
|
||||
"crates/pod",
|
||||
"crates/mcp",
|
||||
"crates/worker",
|
||||
"crates/worker-runtime",
|
||||
"crates/plugin-pdk",
|
||||
"crates/yoi",
|
||||
"crates/protocol",
|
||||
"crates/provider",
|
||||
"crates/pod-registry",
|
||||
"crates/session-metrics",
|
||||
"crates/session-analytics",
|
||||
"crates/lint-common",
|
||||
"crates/tools",
|
||||
"crates/fs-operation",
|
||||
"crates/flow",
|
||||
"crates/config-source",
|
||||
"crates/config-source-wasm",
|
||||
"crates/workdir",
|
||||
"crates/tui",
|
||||
"crates/memory",
|
||||
"crates/workflow",
|
||||
"crates/ticket",
|
||||
"crates/merge-request",
|
||||
"crates/project-record",
|
||||
"crates/workspace-api",
|
||||
"crates/workspace-server",
|
||||
"tests/e2e",
|
||||
]
|
||||
default-members = [
|
||||
"crates/client",
|
||||
"crates/agen",
|
||||
"crates/agen-macros",
|
||||
"crates/session-store",
|
||||
"crates/secrets",
|
||||
"crates/manifest",
|
||||
"crates/mcp",
|
||||
"crates/worker",
|
||||
"crates/worker-runtime",
|
||||
"crates/plugin-pdk",
|
||||
"crates/yoi",
|
||||
"crates/protocol",
|
||||
"crates/session-metrics",
|
||||
"crates/session-analytics",
|
||||
"crates/lint-common",
|
||||
"crates/tools",
|
||||
"crates/fs-operation",
|
||||
"crates/flow",
|
||||
"crates/config-source",
|
||||
"crates/config-source-wasm",
|
||||
"crates/workdir",
|
||||
"crates/tui",
|
||||
"crates/memory",
|
||||
"crates/ticket",
|
||||
"crates/merge-request",
|
||||
"crates/project-record",
|
||||
"crates/workspace-api",
|
||||
"crates/workspace-server",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
@@ -26,32 +69,59 @@ license = "MIT"
|
||||
[workspace.dependencies]
|
||||
# Internal crates
|
||||
client = { path = "crates/client" }
|
||||
llm-worker = { path = "crates/llm-worker", version = "0.2" }
|
||||
llm-worker-macros = { path = "crates/llm-worker-macros", version = "0.2" }
|
||||
agen = { path = "crates/agen", version = "0.2" }
|
||||
agen-macros = { path = "crates/agen-macros", version = "0.2" }
|
||||
manifest = { path = "crates/manifest" }
|
||||
mcp = { path = "crates/mcp" }
|
||||
lint-common = { path = "crates/lint-common" }
|
||||
memory = { path = "crates/memory" }
|
||||
pod-registry = { path = "crates/pod-registry" }
|
||||
merge-request = { path = "crates/merge-request" }
|
||||
ticket = { path = "crates/ticket" }
|
||||
project-record = { path = "crates/project-record" }
|
||||
worker = { path = "crates/worker" }
|
||||
worker-runtime = { path = "crates/worker-runtime" }
|
||||
workspace-api = { path = "crates/workspace-api" }
|
||||
yoi-plugin-pdk = { path = "crates/plugin-pdk" }
|
||||
yoi = { path = "crates/yoi" }
|
||||
protocol = { path = "crates/protocol" }
|
||||
provider = { path = "crates/provider" }
|
||||
session-metrics = { path = "crates/session-metrics" }
|
||||
session-analytics = { path = "crates/session-analytics" }
|
||||
session-store = { path = "crates/session-store" }
|
||||
secrets = { path = "crates/secrets" }
|
||||
tools = { path = "crates/tools" }
|
||||
config-source = { path = "crates/config-source" }
|
||||
fs-operation = { path = "crates/fs-operation" }
|
||||
workdir = { path = "crates/workdir" }
|
||||
tui = { path = "crates/tui" }
|
||||
yoi-workspace-server = { path = "crates/workspace-server" }
|
||||
|
||||
# External
|
||||
# Note: `reqwest` and `chrono` are not aggregated here because some crates
|
||||
# need `default-features = false`, which workspace inheritance cannot override.
|
||||
async-trait = "0.1"
|
||||
axum = "0.8"
|
||||
base64 = "0.22.1"
|
||||
decodal = "0.4.0"
|
||||
decodal-language-service = "0.4.0"
|
||||
decodal-language-tools = "0.4.0"
|
||||
fs4 = "0.13"
|
||||
futures = "0.3"
|
||||
libc = "0.2"
|
||||
schemars = "1.2"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9.34"
|
||||
tar = "0.4"
|
||||
rusqlite = { version = "0.37", features = ["backup", "bundled"] }
|
||||
ring = "0.17.14"
|
||||
sha2 = "0.11"
|
||||
tempfile = "3.27"
|
||||
thiserror = "2.0"
|
||||
tokio = "1.52"
|
||||
tokio-tungstenite = "0.29"
|
||||
tower = "0.5"
|
||||
toml = "1.1"
|
||||
tracing = "0.1"
|
||||
url = "2.5"
|
||||
uuid = "1.23"
|
||||
webauthn-rs = { version = "0.5.2", features = ["danger-allow-state-serialisation", "danger-credential-internals"] }
|
||||
|
||||
+4
-14
@@ -2,17 +2,7 @@
|
||||
|
||||
Ticket を切るほどではないが、次に近所を触るときに合わせて拾いたい小粒な所見の置き場。
|
||||
|
||||
## 運用
|
||||
|
||||
- 1 項目 = 出典 (file:line) + 症状 (一文) + トリガー (いつ拾うか、一文)
|
||||
- 関連 ticket があれば `→ [tickets/foo.md]` でリンク
|
||||
- 修正したら同じコミットで該当エントリを削除する (履歴は git)
|
||||
- ここに溜める基準: 「ticket は重い」「だが忘れたら次の触り手が踏む」もの。明確に作業すべきものは ticket 化する
|
||||
|
||||
## エントリ
|
||||
|
||||
- `crates/tui/src/app.rs:478-485` — bad workflow slug を含む `Method::Run` 送信時、`Event::UserMessage` の早期 broadcast で `turn_index += 1` されターンヘッダだけ残る ("ghost turn header")。次に TUI のターンヘッダ / エラー表示周りを触るときに整理。→ [tickets/pod-input-validate-internalize.md] の review 由来。
|
||||
- `crates/pod/src/controller.rs:944` — `worker_error_code` で `PodError::WorkflowResolve(_) => InvalidRequest` が post-commit な resolve エラー (`KnowledgeNotFound` 等) にも適用される。意味論的には妥当方向だが、resolve 系のエラー粒度を分けたくなったタイミングで再評価。
|
||||
- `crates/pod/tests/controller_test.rs` — `double_run_returns_error` がたまに失敗する flakiness を観測。`pod-interrupt-prep-internalize` 以前から存在する別件。次に controller_test の Run 連投系のタイミングを触るときに併せて原因を切り分け。
|
||||
- `crates/session-store/src/fs_store.rs:117-122` — `FsStore::read_entry_count` が `fs::read_to_string` で全文ロードしてから行数カウントするため O(n)。`ensure_head_or_fork` は run-start でしか呼ばれず現状は許容範囲だが、長期セッションが普通になった時点で `\n` バイト数の cheap count か末尾 seek に置き換える。
|
||||
- `crates/session-store/src/segment.rs:121` `ensure_head_or_fork` (free fn, test 専用・本番 caller ゼロ) と `crates/pod/src/pod.rs` `Pod::ensure_segment_head` (本番 inline) に live auto-fork の検知 + forked_from 記録が二重実装されている。entry-hash-abolish 以前からの重複で、両方独立にテスト済みだが drift 必至。session-store 側を本番から呼ぶ形に寄せるか free fn を畳むかは要設計判断。Pod state / fork 周辺を次に触るときに統合を検討。
|
||||
- `crates/worker/src/controller.rs:1453-1461` — `worker_error_code` で `WorkerError::WorkflowResolve(_) => InvalidRequest` が post-commit な resolve エラー (`KnowledgeNotFound` 等) にも適用される。意味論的には妥当方向だが、resolve 系のエラー粒度を分けたくなったタイミングで再評価。
|
||||
- `crates/session-store/src/fs_store.rs:200-210` — `FsStore::read_entry_count` が `fs::read_to_string` で全文ロードしてから行数カウントするため O(n)。`ensure_head_or_fork` は run-start でしか呼ばれず現状は許容範囲だが、長期セッションが普通になった時点で `\n` バイト数の cheap count か末尾 seek に置き換える。
|
||||
- `crates/session-store/src/segment.rs:143-172` `ensure_head_or_fork` (free fn, test 専用・本番 caller ゼロ) と `crates/worker/src/worker.rs:2032` `Worker::ensure_segment_head` (本番 inline) に live auto-fork の検知 + forked_from 記録が二重実装されている。entry-hash-abolish 以前からの重複で、両方独立にテスト済みだが drift 必至。session-store 側を本番から呼ぶ形に寄せるか free fn を畳むかは要設計判断。Worker state / fork 周辺を次に触るときに統合を検討。
|
||||
- `crates/worker/src/worker.rs` / `crates/worker/src/spawn/registry.rs:84-174` — restore 時の spawned child prune/reclaim が Worker restore path と spawned registry load path の両方に残っている。現状は安全側の重複チェックだが、Worker state / spawned registry 周辺を次に触るときに責務境界を再整理。
|
||||
|
||||
@@ -1,5 +1,85 @@
|
||||
# INSOMNIA
|
||||
# 夜居 / Yoi agent
|
||||
|
||||
insomnia(i6a)は不休のエージェントループを回すためのエージェントプラットフォーム。
|
||||
Yoi is an agent runtime for building, running, and orchestrating LLM Workers while preserving explicit history, scoped capabilities, and developer-controlled workflows.
|
||||
|
||||
ワークフローを統括し、四六時中電力を消費し、イテレーションします。
|
||||
## 1. Yoi agent
|
||||
|
||||
Yoi focuses on long-running agent operation rather than one-off prompt execution. A named Worker can keep durable session history, run with explicit tool and filesystem authority, delegate bounded work to child Workers, and be inspected or restored through CLI/TUI surfaces.
|
||||
|
||||
Main highlights:
|
||||
|
||||
- Named long-running **Workers** with durable session and metadata records.
|
||||
- Explicit tool permissions and filesystem scopes.
|
||||
- Multi-agent orchestration with scoped coder/reviewer Workers.
|
||||
- Profile, Manifest, and prompt-based runtime configuration.
|
||||
- Local Tickets and workflow files for auditable project coordination.
|
||||
- TUI and CLI entry points, including the `yoi panel` workspace Dashboard and single-Worker Console.
|
||||
|
||||
Yoi is actively dogfooded in this repository. Public APIs, configuration formats, and workflows may still change.
|
||||
|
||||
## 2. Quick Install
|
||||
|
||||
From source:
|
||||
|
||||
```sh
|
||||
cargo build --release -p yoi
|
||||
./target/release/yoi --help
|
||||
```
|
||||
|
||||
With Nix:
|
||||
|
||||
```sh
|
||||
nix build .#yoi
|
||||
./result/bin/yoi --help
|
||||
```
|
||||
|
||||
## 3. Getting Started
|
||||
|
||||
```sh
|
||||
yoi --help
|
||||
yoi
|
||||
yoi panel
|
||||
yoi --worker <name>
|
||||
yoi worker --help
|
||||
```
|
||||
|
||||
Typical flow:
|
||||
|
||||
1. Configure providers, models, profiles, prompts, and scopes.
|
||||
2. Start or attach to a named Worker in the Console, or inspect workspace activity in the Dashboard.
|
||||
3. Use explicit tools and scoped delegation for multi-agent work.
|
||||
4. Record project work through Tickets, workflow files, and git history.
|
||||
|
||||
Runtime surfaces use `yoi`, `.yoi`, `~/.yoi`, and `YOI_*`.
|
||||
|
||||
## 4. Documentation
|
||||
|
||||
Start with [`docs/README.md`](docs/README.md).
|
||||
|
||||
Key docs:
|
||||
|
||||
- [`docs/design/overview.md`](docs/design/overview.md) — architecture and crate ownership map.
|
||||
- [`docs/design/context-history.md`](docs/design/context-history.md) — history/context invariants.
|
||||
- [`docs/design/worker-session-state.md`](docs/design/worker-session-state.md) — Worker identity, metadata, and session logs.
|
||||
- [`docs/design/profiles-manifests-prompts.md`](docs/design/profiles-manifests-prompts.md) — Profiles, Manifests, and prompt resources.
|
||||
- [`docs/design/tool-permissions-scope.md`](docs/design/tool-permissions-scope.md) — tool policy and filesystem scope.
|
||||
- [`docs/development/work-items.md`](docs/development/work-items.md) — Ticket workflow and project records.
|
||||
- [`docs/development/validation.md`](docs/development/validation.md) — validation expectations.
|
||||
|
||||
## 5. Development
|
||||
|
||||
This repository dogfoods Yoi to develop Yoi. Work is tracked through `.yoi/tickets/` and `yoi ticket ...`; git history plus Ticket files are the authoritative project record.
|
||||
|
||||
Common checks:
|
||||
|
||||
```sh
|
||||
yoi ticket doctor
|
||||
git diff --check
|
||||
cargo fmt --check
|
||||
cargo check --workspace --all-targets
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
Real-process E2E testing is opt-in. Do not design or implement E2E coverage unless the work explicitly requires E2E; otherwise protect the narrower parser, API, protocol, authority, or runtime boundary.
|
||||
|
||||
License: MIT. See [`LICENSE`](LICENSE).
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# TODO legacy notice
|
||||
|
||||
Active repository work items have been migrated to `work-items/`.
|
||||
|
||||
Use `./tickets.sh list --status all` for the generated/current view and `./tickets.sh doctor` to validate the migration state.
|
||||
@@ -0,0 +1,38 @@
|
||||
name: yoi
|
||||
|
||||
services:
|
||||
runtime:
|
||||
image: yoi-runtime:latest
|
||||
pull_policy: never
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "38800"
|
||||
volumes:
|
||||
- runtime-data:/runtime-data
|
||||
- runtime-workdirs:/workdirs
|
||||
|
||||
server:
|
||||
image: yoi-server:latest
|
||||
pull_policy: never
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- runtime
|
||||
expose:
|
||||
- "8787"
|
||||
volumes:
|
||||
- server-data:/server-data
|
||||
- ./docker/workspace:/workspace:ro
|
||||
|
||||
webui:
|
||||
image: yoi-webui:latest
|
||||
pull_policy: never
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- server
|
||||
ports:
|
||||
- "${YOI_WEBUI_PORT:-8080}:80"
|
||||
|
||||
volumes:
|
||||
runtime-data:
|
||||
runtime-workdirs:
|
||||
server-data:
|
||||
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "agen-macros"
|
||||
description = "Procedural macros for declaring agen tools"
|
||||
version = "0.2.0"
|
||||
edition.workspace = true
|
||||
rust-version = "1.85"
|
||||
license.workspace = true
|
||||
readme = "README.md"
|
||||
repository = "https://gitea.hareworks.net/Hare/yoi"
|
||||
homepage = "https://gitea.hareworks.net/Hare/yoi"
|
||||
documentation = "https://docs.rs/agen-macros"
|
||||
keywords = ["llm", "agent", "tools", "macros"]
|
||||
categories = ["development-tools::procedural-macro-helpers"]
|
||||
include = ["src/**", "README.md", "LICENSE"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "2", features = ["full"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -0,0 +1,7 @@
|
||||
Copyright 2026 Hare
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,32 @@
|
||||
# agen-macros
|
||||
|
||||
Procedural macros used by [`agen`](https://crates.io/crates/agen) to declare LLM tools from Rust methods.
|
||||
|
||||
Applications should normally depend only on `agen` and import its re-exports:
|
||||
|
||||
```rust
|
||||
use agen::tool_registry;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Tools;
|
||||
|
||||
#[tool_registry]
|
||||
impl Tools {
|
||||
/// Returns the supplied text.
|
||||
#[tool]
|
||||
async fn echo(
|
||||
&self,
|
||||
#[description = "Text to return"] text: String,
|
||||
) -> Result<String, std::io::Error> {
|
||||
Ok(text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`#[tool_registry]` generates the argument schema, a `Tool` implementation, and an `<method>_definition` constructor. It rejects arguments of its own, duplicate `#[tool]` markers, malformed or duplicate `#[description = "..."]` attributes, and non-identifier argument patterns.
|
||||
|
||||
Generated code targets the canonical `::agen` path and uses implementation dependencies re-exported by `agen`; consumers do not need direct `serde`, `schemars`, `serde_json`, or `async-trait` dependencies. Renaming the `agen` dependency in `Cargo.toml` is not currently supported.
|
||||
|
||||
This companion package is published before the matching `agen` release. Its public contract is the generated API consumed by `agen`, and its minor version compatibility follows the `agen` 0.2 series.
|
||||
|
||||
Licensed under the [MIT License](https://gitea.hareworks.net/Hare/yoi/src/branch/develop/LICENSE).
|
||||
@@ -0,0 +1,482 @@
|
||||
//! Procedural macros for declaring [`agen`](https://docs.rs/agen) tools.
|
||||
//!
|
||||
//! [`tool_registry`] expands methods marked with `#[tool]` into `agen::tool::Tool`
|
||||
//! implementations and tool definitions. Applications normally use the re-exports from
|
||||
//! `agen`; this companion crate exists so those macros can be published and versioned
|
||||
//! independently.
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
Attribute, FnArg, ImplItem, ItemImpl, Lit, Meta, Pat, ReturnType, Type, parse_macro_input,
|
||||
spanned::Spanned,
|
||||
};
|
||||
|
||||
/// Generates tools for methods marked with `#[tool]` in an `impl` block.
|
||||
///
|
||||
/// Method doc comments become the tool description. An argument can use
|
||||
/// `#[description = "..."]` to supply its JSON Schema description.
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[derive(Clone)]
|
||||
/// struct MyApp;
|
||||
///
|
||||
/// #[agen::tool_registry]
|
||||
/// impl MyApp {
|
||||
/// /// Retrieves a user by ID.
|
||||
/// #[tool]
|
||||
/// async fn get_user(
|
||||
/// &self,
|
||||
/// #[description = "The user ID"] user_id: String,
|
||||
/// ) -> Result<String, std::io::Error> {
|
||||
/// todo!()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This generates a `ToolGetUser` wrapper, a `GetUserArgs` schema type, and
|
||||
/// `MyApp::get_user_definition()`.
|
||||
#[proc_macro_attribute]
|
||||
pub fn tool_registry(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let attr = proc_macro2::TokenStream::from(attr);
|
||||
let impl_block = parse_macro_input!(item as ItemImpl);
|
||||
|
||||
expand_tool_registry(attr, impl_block)
|
||||
.unwrap_or_else(syn::Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn expand_tool_registry(
|
||||
attr: proc_macro2::TokenStream,
|
||||
mut impl_block: ItemImpl,
|
||||
) -> syn::Result<proc_macro2::TokenStream> {
|
||||
if !attr.is_empty() {
|
||||
return Err(syn::Error::new(
|
||||
attr.span(),
|
||||
"tool_registry does not accept arguments",
|
||||
));
|
||||
}
|
||||
|
||||
let self_ty = impl_block.self_ty.as_ref().clone();
|
||||
let mut generated_items = Vec::new();
|
||||
|
||||
for item in &mut impl_block.items {
|
||||
let ImplItem::Fn(method) = item else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let tool_attrs: Vec<_> = method
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path().is_ident("tool"))
|
||||
.collect();
|
||||
if tool_attrs.len() > 1 {
|
||||
return Err(syn::Error::new_spanned(
|
||||
tool_attrs[1],
|
||||
"duplicate #[tool] attribute",
|
||||
));
|
||||
}
|
||||
let Some(tool_attr) = tool_attrs.first() else {
|
||||
continue;
|
||||
};
|
||||
if !matches!(tool_attr.meta, Meta::Path(_)) {
|
||||
return Err(syn::Error::new_spanned(
|
||||
tool_attr,
|
||||
"#[tool] does not accept arguments",
|
||||
));
|
||||
}
|
||||
|
||||
method.attrs.retain(|attr| !attr.path().is_ident("tool"));
|
||||
generated_items.push(generate_tool_impl(&self_ty, method)?);
|
||||
|
||||
for input in &mut method.sig.inputs {
|
||||
if let FnArg::Typed(pat_type) = input {
|
||||
pat_type
|
||||
.attrs
|
||||
.retain(|attr| !attr.path().is_ident("description"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(quote! {
|
||||
#impl_block
|
||||
|
||||
#(#generated_items)*
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_doc_comment(attrs: &[Attribute]) -> String {
|
||||
let mut lines = Vec::new();
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path().is_ident("doc")
|
||||
&& let Meta::NameValue(meta) = &attr.meta
|
||||
&& let syn::Expr::Lit(expr_lit) = &meta.value
|
||||
&& let Lit::Str(lit_str) = &expr_lit.lit
|
||||
{
|
||||
let line = lit_str.value();
|
||||
let trimmed = line.strip_prefix(' ').unwrap_or(&line);
|
||||
lines.push(trimmed.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
fn extract_description_attr(attrs: &[Attribute]) -> syn::Result<Option<String>> {
|
||||
let mut description = None;
|
||||
|
||||
for attr in attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path().is_ident("description"))
|
||||
{
|
||||
let value = match &attr.meta {
|
||||
Meta::NameValue(meta) => match &meta.value {
|
||||
syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
|
||||
Lit::Str(value) => value.value(),
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attr,
|
||||
"description must be a string literal",
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attr,
|
||||
"description must be a string literal",
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attr,
|
||||
"expected #[description = \"...\"]",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
if description.replace(value).is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attr,
|
||||
"duplicate #[description] attribute",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(description)
|
||||
}
|
||||
|
||||
fn argument_ident(pat: &Pat) -> syn::Result<&syn::Ident> {
|
||||
match pat {
|
||||
Pat::Ident(pat_ident) => Ok(&pat_ident.ident),
|
||||
_ => Err(syn::Error::new_spanned(
|
||||
pat,
|
||||
"tool arguments must use simple identifier patterns",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_tool_execution_context_type(ty: &Type) -> bool {
|
||||
let Type::Path(path) = ty else {
|
||||
return false;
|
||||
};
|
||||
path.path
|
||||
.segments
|
||||
.last()
|
||||
.is_some_and(|segment| segment.ident == "ToolExecutionContext")
|
||||
}
|
||||
|
||||
fn generate_tool_impl(
|
||||
self_ty: &Type,
|
||||
method: &syn::ImplItemFn,
|
||||
) -> syn::Result<proc_macro2::TokenStream> {
|
||||
let sig = &method.sig;
|
||||
let method_name = &sig.ident;
|
||||
let tool_name = method_name.to_string();
|
||||
|
||||
let pascal_name = to_pascal_case(&method_name.to_string());
|
||||
let tool_struct_name = format_ident!("Tool{}", pascal_name);
|
||||
let args_struct_name = format_ident!("{}Args", pascal_name);
|
||||
let definition_name = format_ident!("{}_definition", method_name);
|
||||
|
||||
let description = extract_doc_comment(&method.attrs);
|
||||
let description = if description.is_empty() {
|
||||
format!("Tool: {}", tool_name)
|
||||
} else {
|
||||
description
|
||||
};
|
||||
|
||||
let method_args: Vec<_> = sig
|
||||
.inputs
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
FnArg::Typed(pat_type) => Some(pat_type),
|
||||
FnArg::Receiver(_) => None,
|
||||
})
|
||||
.collect();
|
||||
let json_args: Vec<_> = method_args
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|pat_type| !is_tool_execution_context_type(pat_type.ty.as_ref()))
|
||||
.collect();
|
||||
|
||||
let arg_fields: Vec<_> = json_args
|
||||
.iter()
|
||||
.map(|pat_type| {
|
||||
let field_name = argument_ident(pat_type.pat.as_ref())?;
|
||||
let ty = &pat_type.ty;
|
||||
let description = extract_description_attr(&pat_type.attrs)?;
|
||||
|
||||
Ok(if let Some(description) = description {
|
||||
quote! {
|
||||
#[schemars(description = #description)]
|
||||
pub #field_name: #ty
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
pub #field_name: #ty
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<syn::Result<_>>()?;
|
||||
|
||||
let call_args: Vec<_> = method_args
|
||||
.iter()
|
||||
.map(|pat_type| {
|
||||
if is_tool_execution_context_type(pat_type.ty.as_ref()) {
|
||||
Ok(quote! { ctx.clone() })
|
||||
} else {
|
||||
let ident = argument_ident(pat_type.pat.as_ref())?;
|
||||
Ok(quote! { args.#ident })
|
||||
}
|
||||
})
|
||||
.collect::<syn::Result<_>>()?;
|
||||
let method_call = if call_args.is_empty() {
|
||||
quote! { self.ctx.#method_name() }
|
||||
} else {
|
||||
quote! { self.ctx.#method_name(#(#call_args),*) }
|
||||
};
|
||||
|
||||
let awaiter = if sig.asyncness.is_some() {
|
||||
quote! { .await }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let result_handling = if is_result_type(&sig.output) {
|
||||
quote! {
|
||||
match result {
|
||||
Ok(val) => Ok(format!("{:?}", val).into()),
|
||||
Err(error) => Err(::agen::tool::ToolError::ExecutionFailed(format!("{}", error))),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
Ok(format!("{:?}", result).into())
|
||||
}
|
||||
};
|
||||
|
||||
let args_struct_def = quote! {
|
||||
#[derive(
|
||||
::agen::__private::serde::Deserialize,
|
||||
::agen::__private::schemars::JsonSchema,
|
||||
)]
|
||||
#[serde(crate = "::agen::__private::serde")]
|
||||
#[schemars(crate = "::agen::__private::schemars")]
|
||||
struct #args_struct_name {
|
||||
#(#arg_fields),*
|
||||
}
|
||||
};
|
||||
|
||||
let execute_body = if json_args.is_empty() {
|
||||
quote! {
|
||||
let _: #args_struct_name = ::agen::__private::serde_json::from_str(input_json)
|
||||
.unwrap_or(#args_struct_name {});
|
||||
|
||||
let result = #method_call #awaiter;
|
||||
#result_handling
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
let args: #args_struct_name = ::agen::__private::serde_json::from_str(input_json)
|
||||
.map_err(|error| ::agen::tool::ToolError::InvalidArgument(error.to_string()))?;
|
||||
|
||||
let result = #method_call #awaiter;
|
||||
#result_handling
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#args_struct_def
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct #tool_struct_name {
|
||||
ctx: #self_ty,
|
||||
}
|
||||
|
||||
#[::agen::__private::async_trait::async_trait]
|
||||
impl ::agen::tool::Tool for #tool_struct_name {
|
||||
async fn execute(
|
||||
&self,
|
||||
input_json: &str,
|
||||
ctx: ::agen::tool::ToolExecutionContext,
|
||||
) -> Result<::agen::tool::ToolOutput, ::agen::tool::ToolError> {
|
||||
let _ = &ctx;
|
||||
#execute_body
|
||||
}
|
||||
}
|
||||
|
||||
impl #self_ty {
|
||||
/// Returns a tool definition for registration with an `agen::Engine`.
|
||||
pub fn #definition_name(&self) -> ::agen::tool::ToolDefinition {
|
||||
let ctx = self.clone();
|
||||
::std::sync::Arc::new(move || {
|
||||
let schema = ::agen::__private::schemars::schema_for!(#args_struct_name);
|
||||
let meta = ::agen::tool::ToolMeta::new(#tool_name)
|
||||
.description(#description)
|
||||
.input_schema(
|
||||
::agen::__private::serde_json::to_value(schema)
|
||||
.unwrap_or_else(|_| ::agen::__private::serde_json::json!({})),
|
||||
);
|
||||
let tool: ::std::sync::Arc<dyn ::agen::tool::Tool> =
|
||||
::std::sync::Arc::new(#tool_struct_name { ctx: ctx.clone() });
|
||||
(meta, tool)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn is_result_type(return_type: &ReturnType) -> bool {
|
||||
match return_type {
|
||||
ReturnType::Default => false,
|
||||
ReturnType::Type(_, ty) => {
|
||||
if let Type::Path(type_path) = ty.as_ref()
|
||||
&& let Some(segment) = type_path.path.segments.last()
|
||||
{
|
||||
return segment.ident == "Result";
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_pascal_case(s: &str) -> String {
|
||||
s.split('_')
|
||||
.map(|part| {
|
||||
let mut chars = part.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(first) => first.to_uppercase().chain(chars).collect(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Marker attribute interpreted by [`tool_registry`].
|
||||
#[proc_macro_attribute]
|
||||
pub fn tool(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
marker_attribute("tool", attr, item)
|
||||
}
|
||||
|
||||
/// Argument description marker interpreted by [`tool_registry`].
|
||||
///
|
||||
/// Use it as `#[description = "The argument description"]` on a tool method argument.
|
||||
#[proc_macro_attribute]
|
||||
pub fn description(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
marker_attribute("description", attr, item)
|
||||
}
|
||||
|
||||
fn marker_attribute(name: &str, attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
if attr.is_empty() {
|
||||
item
|
||||
} else {
|
||||
syn::Error::new(
|
||||
proc_macro2::Span::call_site(),
|
||||
format!("{name} is a marker interpreted by #[tool_registry]"),
|
||||
)
|
||||
.into_compile_error()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use quote::quote;
|
||||
use syn::parse_quote;
|
||||
|
||||
#[test]
|
||||
fn rejects_tool_registry_arguments() {
|
||||
let implementation: ItemImpl = parse_quote!(impl Registry {});
|
||||
let error = expand_tool_registry(quote!(unexpected), implementation).unwrap_err();
|
||||
|
||||
assert!(error.to_string().contains("does not accept arguments"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_duplicate_tool_markers() {
|
||||
let implementation: ItemImpl = parse_quote! {
|
||||
impl Registry {
|
||||
#[tool]
|
||||
#[tool]
|
||||
fn inspect(&self) {}
|
||||
}
|
||||
};
|
||||
let error = expand_tool_registry(quote!(), implementation).unwrap_err();
|
||||
|
||||
assert!(error.to_string().contains("duplicate #[tool]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_description_attributes() {
|
||||
let implementation: ItemImpl = parse_quote! {
|
||||
impl Registry {
|
||||
#[tool]
|
||||
fn inspect(&self, #[description] input: String) {}
|
||||
}
|
||||
};
|
||||
let error = expand_tool_registry(quote!(), implementation).unwrap_err();
|
||||
|
||||
assert!(error.to_string().contains("expected #[description"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_duplicate_description_attributes() {
|
||||
let implementation: ItemImpl = parse_quote! {
|
||||
impl Registry {
|
||||
#[tool]
|
||||
fn inspect(
|
||||
&self,
|
||||
#[description = "first"]
|
||||
#[description = "second"]
|
||||
input: String,
|
||||
) {}
|
||||
}
|
||||
};
|
||||
let error = expand_tool_registry(quote!(), implementation).unwrap_err();
|
||||
|
||||
assert!(error.to_string().contains("duplicate #[description]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_code_uses_only_agen_runtime_paths() {
|
||||
let implementation: ItemImpl = parse_quote! {
|
||||
impl Registry {
|
||||
#[tool]
|
||||
fn inspect(&self, input: String) -> Result<String, Error> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
};
|
||||
let expanded = expand_tool_registry(quote!(), implementation)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
assert!(expanded.contains(":: agen :: tool :: Tool"));
|
||||
assert!(expanded.contains(":: agen :: __private :: serde_json"));
|
||||
assert!(expanded.contains(":: agen :: __private :: serde"));
|
||||
assert!(expanded.contains(":: agen :: __private :: schemars"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
[package]
|
||||
name = "agen"
|
||||
description = "Provider-neutral orchestration for tool-using LLM applications"
|
||||
version = "0.2.1"
|
||||
edition.workspace = true
|
||||
rust-version = "1.86"
|
||||
license.workspace = true
|
||||
readme = "README.md"
|
||||
repository = "https://gitea.hareworks.net/Hare/yoi"
|
||||
homepage = "https://gitea.hareworks.net/Hare/yoi"
|
||||
documentation = "https://docs.rs/agen"
|
||||
keywords = ["llm", "agent", "tools", "streaming", "orchestration"]
|
||||
categories = ["api-bindings", "asynchronous"]
|
||||
include = ["src/**", "tests/**", "examples/*.rs", "docs/**", "README.md", "LICENSE"]
|
||||
autoexamples = false
|
||||
|
||||
[features]
|
||||
default = []
|
||||
codex = ["dep:chrono"]
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "macros", "rt-multi-thread", "sync", "time"] }
|
||||
tokio-util = "0.7"
|
||||
reqwest = { version = "0.13", default-features = false, features = ["stream", "json", "native-tls", "http2"] }
|
||||
eventsource-stream = "0.2"
|
||||
zstd = "0.13"
|
||||
base64 = "0.22.1"
|
||||
chrono = { version = "0.4", default-features = false, features = ["serde", "clock"], optional = true }
|
||||
agen-macros = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
clap = { version = "4.5", features = ["derive", "env"] }
|
||||
tempfile = { workspace = true }
|
||||
dotenv = "0.15"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
trybuild = "1.0.116"
|
||||
wiremock = "0.6.5"
|
||||
|
||||
[[example]]
|
||||
name = "engine_cancel_demo"
|
||||
path = "examples/engine_cancel_demo.rs"
|
||||
|
||||
[[example]]
|
||||
name = "engine_cli"
|
||||
path = "examples/engine_cli.rs"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -0,0 +1,7 @@
|
||||
Copyright 2026 Hare
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,90 @@
|
||||
# agen
|
||||
|
||||
`agen` is a provider-neutral Rust engine for streaming LLM applications that use tools. It owns the turn loop, typed conversation history, provider wire-format adapters, tool execution, interceptors, usage accounting, and cache-aware state transitions.
|
||||
|
||||
> `agen` is pre-1.0. Public APIs may change between minor releases.
|
||||
|
||||
## Installation
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
agen = "0.2.1"
|
||||
```
|
||||
|
||||
The default feature set is intentionally empty. Enable the experimental Codex/ChatGPT authentication adapter when needed:
|
||||
|
||||
```toml
|
||||
agen = { version = "0.2.1", features = ["codex"] }
|
||||
```
|
||||
|
||||
`agen` requires Rust 1.86 or newer. The companion `agen-macros` package requires Rust 1.85 or newer.
|
||||
|
||||
## Quick start
|
||||
|
||||
Supply an implementation of [`LlmClient`](https://docs.rs/agen/latest/agen/llm_client/trait.LlmClient.html), then run a turn. The first call consumes the mutable engine and returns a cache-locked engine for later turns.
|
||||
|
||||
```no_run
|
||||
use agen::{Engine, EngineError};
|
||||
use agen::llm_client::LlmClient;
|
||||
|
||||
async fn conversation<C: LlmClient>(client: C) -> Result<(), EngineError> {
|
||||
let output = Engine::new(client)
|
||||
.system_prompt("You are a concise assistant.")
|
||||
.run("Explain typed state in one sentence.")
|
||||
.await?;
|
||||
|
||||
let mut engine = output.engine;
|
||||
let _result = engine.run("Give a Rust example.").await?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Declaring tools
|
||||
|
||||
The tool macros are re-exported by `agen`; applications do not need direct dependencies on `serde`, `schemars`, `serde_json`, or `async-trait` for generated code.
|
||||
|
||||
```rust
|
||||
use agen::tool_registry;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Tools;
|
||||
|
||||
#[tool_registry]
|
||||
impl Tools {
|
||||
/// Returns the supplied text.
|
||||
#[tool]
|
||||
async fn echo(
|
||||
&self,
|
||||
#[description = "Text to return"] text: String,
|
||||
) -> Result<String, std::io::Error> {
|
||||
Ok(text)
|
||||
}
|
||||
}
|
||||
|
||||
let definition = Tools.echo_definition();
|
||||
assert_eq!(definition().0.name, "echo");
|
||||
```
|
||||
|
||||
The generated API uses the canonical crate name `agen`. Renaming the `agen` dependency in `Cargo.toml` is not currently supported by these macros.
|
||||
|
||||
## Features
|
||||
|
||||
| Feature | Default | Adds |
|
||||
|---|---:|---|
|
||||
| `codex` | No | Experimental Codex/ChatGPT auth-file loading and token refresh support |
|
||||
|
||||
The base crate includes provider-neutral transport and Anthropic, OpenAI-compatible, Gemini, and Ollama wire-format schemes. See [`llm_client`](https://docs.rs/agen/latest/agen/llm_client/) for the client boundary.
|
||||
|
||||
## Architecture and API scope
|
||||
|
||||
The current public modules cover the engine, typed history, client transport/schemes, timeline events, tools, interceptors, pruning, token estimation, and usage records. Their relationships are described in [Architecture](https://gitea.hareworks.net/Hare/yoi/src/branch/develop/crates/agen/docs/architecture.md); behavioral requirements are summarized in [Requirements](https://gitea.hareworks.net/Hare/yoi/src/branch/develop/crates/agen/docs/requirements.md).
|
||||
|
||||
Low-level modules remain public in the 0.2 series because downstream Yoi components implement custom clients, event handlers, pruning policies, and tool registries against them. This surface is versioned as pre-1.0 API rather than declared stable.
|
||||
|
||||
## Packaging and security
|
||||
|
||||
The published package contains source, public documentation, curated examples, and deterministic tests/fixtures. Credentialed fixture-recording utilities are intentionally excluded. Examples that contact a provider read credentials from environment variables and never embed production credentials.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the [MIT License](https://gitea.hareworks.net/Hare/yoi/src/branch/develop/LICENSE).
|
||||
@@ -0,0 +1,62 @@
|
||||
# agen architecture
|
||||
|
||||
`agen` separates orchestration, event projection, and provider transport so applications can replace an LLM client without changing the turn loop or tool model.
|
||||
|
||||
```text
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Engine │
|
||||
│ turn loop · interceptors · tool execution │
|
||||
│ typed state: Mutable → Locked → Mutable │
|
||||
└─────────────────────┬──────────────────────┘
|
||||
│
|
||||
┌─────────────────────▼──────────────────────┐
|
||||
│ Timeline │
|
||||
│ event dispatch · block collectors │
|
||||
└─────────────────────┬──────────────────────┘
|
||||
│
|
||||
┌─────────────────────▼──────────────────────┐
|
||||
│ LlmClient │
|
||||
│ transport · provider wire-format schemes │
|
||||
└────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Main modules
|
||||
|
||||
| Module | Responsibility |
|
||||
|---|---|
|
||||
| `engine` | Turn execution, pause/resume, retries, tool integration, and callbacks |
|
||||
| `state` | Sealed `Mutable` and `Locked` type-state markers |
|
||||
| `interceptor` | Application-owned control decisions at orchestration boundaries |
|
||||
| `tool` / `tool_server` | Tool metadata, registration, execution, and bounded output |
|
||||
| `timeline` | Streaming event dispatch, handlers, and block assembly |
|
||||
| `llm_client` | Provider-neutral request, response, auth, transport, and scheme contracts |
|
||||
| `providers` | Optional higher-level provider adapters such as the `codex` feature |
|
||||
| `prune` / `token_counter` | Cache-aware history reduction and token estimation |
|
||||
| `usage_record` | Request and token usage accounting |
|
||||
|
||||
## Request flow
|
||||
|
||||
```text
|
||||
Engine history
|
||||
→ provider-neutral Request
|
||||
→ Scheme::build_request
|
||||
→ Provider transport
|
||||
```
|
||||
|
||||
## Response flow
|
||||
|
||||
```text
|
||||
streaming response bytes
|
||||
→ Scheme event parsing
|
||||
→ unified Event values
|
||||
→ Timeline handlers and collectors
|
||||
→ Engine history/tool decisions
|
||||
```
|
||||
|
||||
## Type state and cache protection
|
||||
|
||||
`Engine<C, Mutable>` permits configuration and history editing. `Engine::run` or `Engine::lock` commits the current prefix and produces `Engine<C, Locked>`. The locked engine may append turns without mutating the committed prefix. `Engine::unlock` explicitly returns to mutable state when an application accepts losing that cache guarantee.
|
||||
|
||||
## Public surface
|
||||
|
||||
The 0.2 series exposes the low-level client, timeline, tool, pruning, and usage modules because custom clients and orchestration hosts build directly on them. These APIs are intentionally provider-neutral but remain pre-1.0 and may change in later minor releases.
|
||||
@@ -0,0 +1,39 @@
|
||||
# agen requirements
|
||||
|
||||
## R1: Turn execution and continuation
|
||||
|
||||
- `Engine::run` starts a turn and loops through provider output and tool calls.
|
||||
- An `Interceptor` may continue, cancel, or pause work at defined orchestration boundaries.
|
||||
- `Engine::resume` continues paused generation without fabricating another user message.
|
||||
- Cancellation and provider errors are represented as typed `EngineError` values.
|
||||
|
||||
## R2: Explicit cache-preserving state
|
||||
|
||||
- `Engine<C, Mutable>` permits configuration and history edits.
|
||||
- `Engine::run` or `Engine::lock` transitions to `Engine<C, Locked>` and records the committed prefix.
|
||||
- A locked engine appends turns but cannot mutate that prefix through mutable-only APIs.
|
||||
- `Engine::unlock` explicitly abandons the lock before configuration or history changes.
|
||||
|
||||
## R3: Tool declarations and execution
|
||||
|
||||
- `#[tool_registry]` generates a schema and `Tool` implementation for methods marked `#[tool]`.
|
||||
- `#[description = "..."]` supplies argument descriptions in generated JSON Schema.
|
||||
- Generated code resolves its runtime and helper dependencies through `::agen`.
|
||||
- Invalid and duplicate marker attributes produce compile errors rather than panics.
|
||||
- Tools execute through `ToolServer` with typed context, errors, and output limits.
|
||||
|
||||
## R4: Provider-neutral orchestration
|
||||
|
||||
- `LlmClient` is the boundary between the engine and provider-specific transport.
|
||||
- Request/response schemes translate provider wire formats into shared request and event types.
|
||||
- Interceptors, tool execution, timeline collection, and pruning stay above the provider transport.
|
||||
- Provider-specific capabilities are optional features when they require additional policy or dependencies.
|
||||
|
||||
## R5: Publication quality
|
||||
|
||||
- crates.io metadata includes license, repository, documentation, README, categories, keywords, and MSRV.
|
||||
- The default feature set and each optional feature compile and test independently.
|
||||
- Macro expansion compiles in a downstream-style integration test without direct helper dependencies.
|
||||
- rustdoc builds without dependency documentation.
|
||||
- Package contents are explicitly bounded and exclude credentialed fixture-recording utilities.
|
||||
- `cargo package` and `cargo publish --dry-run` are run for `agen-macros` before `agen` because the main package depends on its companion package.
|
||||
+13
-13
@@ -1,10 +1,10 @@
|
||||
//! Worker cancellation demo
|
||||
//! Engine cancellation demo
|
||||
//!
|
||||
//! Example of cancelling from another thread during streaming
|
||||
|
||||
use llm_worker::llm_client::scheme::{Scheme, anthropic::AnthropicScheme};
|
||||
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
|
||||
use llm_worker::{Worker, WorkerResult};
|
||||
use agen::llm_client::scheme::{Scheme, anthropic::AnthropicScheme};
|
||||
use agen::llm_client::transport::{HttpTransport, ResolvedAuth};
|
||||
use agen::{Engine, EngineResult};
|
||||
use std::time::Duration;
|
||||
|
||||
#[tokio::main]
|
||||
@@ -28,29 +28,29 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cap = scheme.default_capability();
|
||||
let base_url = scheme.default_base_url().to_string();
|
||||
let client = HttpTransport::new(scheme, model, base_url, ResolvedAuth::ApiKey(api_key), cap);
|
||||
let worker = Worker::new(client);
|
||||
let engine = Engine::new(client);
|
||||
|
||||
println!("🚀 Starting Worker...");
|
||||
println!("🚀 Starting Engine...");
|
||||
println!("💡 Will cancel after 2 seconds\n");
|
||||
|
||||
// Get cancel sender before run (Mutable state)
|
||||
let cancel_tx = worker.cancel_sender();
|
||||
let cancel_tx = engine.cancel_sender();
|
||||
|
||||
// Task: Cancel after 2 seconds
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
println!("\n🛑 Cancelling worker...");
|
||||
println!("\n🛑 Cancelling engine...");
|
||||
let _ = cancel_tx.send(()).await;
|
||||
});
|
||||
|
||||
println!("📡 Sending request to LLM...");
|
||||
|
||||
match worker.run("Tell me a very long story about a brave knight. Make it as detailed as possible with many paragraphs.").await {
|
||||
match engine.run("Tell me a very long story about a brave knight. Make it as detailed as possible with many paragraphs.").await {
|
||||
Ok(out) => match out.result {
|
||||
WorkerResult::Finished => println!("✅ Task completed normally"),
|
||||
WorkerResult::Paused => println!("⏸️ Task paused"),
|
||||
WorkerResult::LimitReached => println!("🔒 Turn limit reached"),
|
||||
WorkerResult::Yielded => println!("↩️ Task yielded"),
|
||||
EngineResult::Finished => println!("✅ Task completed normally"),
|
||||
EngineResult::Paused => println!("⏸️ Task paused"),
|
||||
EngineResult::LimitReached => println!("🔒 Turn limit reached"),
|
||||
EngineResult::Yielded => println!("↩️ Task yielded"),
|
||||
},
|
||||
Err(e) => {
|
||||
println!("❌ Task error: {}", e);
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Interactive CLI client using Worker
|
||||
//! Interactive CLI client using Engine
|
||||
//!
|
||||
//! A CLI application for interacting with multiple LLM providers (Anthropic, Gemini, OpenAI, Ollama).
|
||||
//! Demonstrates tool registration and execution, and streaming response display.
|
||||
@@ -12,22 +12,22 @@
|
||||
//! echo "OPENAI_API_KEY=your-api-key" >> .env
|
||||
//!
|
||||
//! # Anthropic (default)
|
||||
//! cargo run --example worker_cli
|
||||
//! cargo run --example engine_cli
|
||||
//!
|
||||
//! # Gemini
|
||||
//! cargo run --example worker_cli -- --provider gemini
|
||||
//! cargo run --example engine_cli -- --provider gemini
|
||||
//!
|
||||
//! # OpenAI
|
||||
//! cargo run --example worker_cli -- --provider openai --model gpt-4o
|
||||
//! cargo run --example engine_cli -- --provider openai --model gpt-4o
|
||||
//!
|
||||
//! # Ollama (local)
|
||||
//! cargo run --example worker_cli -- --provider ollama --model llama3.2
|
||||
//! cargo run --example engine_cli -- --provider ollama --model llama3.2
|
||||
//!
|
||||
//! # With options
|
||||
//! cargo run --example worker_cli -- --provider anthropic --model claude-3-haiku-20240307 --system "You are a helpful assistant."
|
||||
//! cargo run --example engine_cli -- --provider anthropic --model claude-3-haiku-20240307 --system "You are a helpful assistant."
|
||||
//!
|
||||
//! # Show help
|
||||
//! cargo run --example worker_cli -- --help
|
||||
//! cargo run --example engine_cli -- --help
|
||||
//! ```
|
||||
|
||||
use std::collections::HashMap;
|
||||
@@ -38,9 +38,8 @@ use async_trait::async_trait;
|
||||
use tracing::info;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use llm_worker::{
|
||||
Worker,
|
||||
use agen::{
|
||||
Engine,
|
||||
interceptor::{Interceptor, PostToolAction, ToolResultInfo},
|
||||
llm_client::{
|
||||
LlmClient,
|
||||
@@ -51,12 +50,9 @@ use llm_worker::{
|
||||
transport::{HttpTransport, ResolvedAuth},
|
||||
},
|
||||
timeline::{Handler, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, ToolUseBlockKind},
|
||||
tool_registry,
|
||||
};
|
||||
use llm_worker_macros::tool_registry;
|
||||
|
||||
// Required imports for macro expansion
|
||||
use schemars;
|
||||
use serde;
|
||||
use clap::{Parser, ValueEnum};
|
||||
|
||||
// =============================================================================
|
||||
// Provider Definition
|
||||
@@ -114,8 +110,8 @@ impl Provider {
|
||||
|
||||
/// Interactive CLI client supporting multiple LLM providers
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "worker-cli")]
|
||||
#[command(about = "Interactive CLI client for multiple LLM providers using Worker")]
|
||||
#[command(name = "engine-cli")]
|
||||
#[command(about = "Interactive CLI client for multiple LLM providers using Engine")]
|
||||
#[command(version)]
|
||||
struct Args {
|
||||
/// Provider to use
|
||||
@@ -393,7 +389,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
// Initialize logging
|
||||
// Use RUST_LOG=debug cargo run --example worker_cli ... for detailed logs
|
||||
// Use RUST_LOG=debug cargo run --example engine_cli ... for detailed logs
|
||||
// Default is warn level, can be overridden with RUST_LOG environment variable
|
||||
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"));
|
||||
|
||||
@@ -408,7 +404,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!(
|
||||
provider = ?args.provider,
|
||||
model = ?args.model,
|
||||
"Starting worker CLI"
|
||||
"Starting engine CLI"
|
||||
);
|
||||
|
||||
// Interactive mode or one-shot mode
|
||||
@@ -421,7 +417,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.unwrap_or_else(|| args.provider.default_model().to_string());
|
||||
|
||||
if is_interactive {
|
||||
let title = format!("Worker CLI - {}", args.provider.display_name());
|
||||
let title = format!("Engine CLI - {}", args.provider.display_name());
|
||||
let border_len = title.len() + 6;
|
||||
println!("╔{}╗", "═".repeat(border_len));
|
||||
println!("║ {} ║", title);
|
||||
@@ -453,34 +449,34 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
};
|
||||
|
||||
// Create Worker
|
||||
let mut worker = Worker::new(client);
|
||||
// Create Engine
|
||||
let mut engine = Engine::new(client);
|
||||
|
||||
let tool_call_names = Arc::new(Mutex::new(HashMap::new()));
|
||||
|
||||
// Set system prompt
|
||||
if let Some(ref system_prompt) = args.system {
|
||||
worker.set_system_prompt(system_prompt);
|
||||
engine.set_system_prompt(system_prompt);
|
||||
}
|
||||
|
||||
// Register tools (unless --no-tools)
|
||||
if !args.no_tools {
|
||||
let app = AppContext;
|
||||
worker.register_tool(app.get_current_time_definition());
|
||||
worker.register_tool(app.calculate_definition());
|
||||
engine.register_tool(app.get_current_time_definition());
|
||||
engine.register_tool(app.calculate_definition());
|
||||
}
|
||||
|
||||
// Register streaming display handlers
|
||||
worker
|
||||
engine
|
||||
.timeline_mut()
|
||||
.on_text_block(StreamingPrinter::new())
|
||||
.on_tool_use_block(ToolCallPrinter::new(tool_call_names.clone()));
|
||||
|
||||
worker.set_interceptor(ToolResultPrinterPolicy::new(tool_call_names));
|
||||
engine.set_interceptor(ToolResultPrinterPolicy::new(tool_call_names));
|
||||
|
||||
// One-shot mode
|
||||
if let Some(prompt) = args.prompt {
|
||||
match worker.run(&prompt).await {
|
||||
match engine.run(&prompt).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
eprintln!("\n❌ Error: {}", e);
|
||||
@@ -504,8 +500,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut locked = match worker.run(first_input).await {
|
||||
Ok(out) => out.worker,
|
||||
let mut locked = match engine.run(first_input).await {
|
||||
Ok(out) => out.engine,
|
||||
Err(e) => {
|
||||
eprintln!("\n❌ Error: {}", e);
|
||||
return Ok(());
|
||||
+4
-4
@@ -19,11 +19,11 @@
|
||||
mod recorder;
|
||||
mod scenarios;
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use llm_worker::llm_client::scheme::{
|
||||
use agen::llm_client::scheme::{
|
||||
Scheme, anthropic::AnthropicScheme, gemini::GeminiScheme, openai_chat::OpenAIScheme,
|
||||
};
|
||||
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
|
||||
use agen::llm_client::transport::{HttpTransport, ResolvedAuth};
|
||||
use clap::{Parser, ValueEnum};
|
||||
|
||||
fn make_transport<S: Scheme>(scheme: S, model: &str, auth: ResolvedAuth) -> HttpTransport<S> {
|
||||
let cap = scheme.default_capability();
|
||||
@@ -225,7 +225,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
println!("\n✅ Done!");
|
||||
println!("Run tests with: cargo test -p worker");
|
||||
println!("Run tests with: cargo test -p engine");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+2
-2
@@ -7,8 +7,8 @@ use std::io::{BufWriter, Write};
|
||||
use std::path::Path;
|
||||
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use agen::llm_client::{LlmClient, Request};
|
||||
use futures::StreamExt;
|
||||
use llm_worker::llm_client::{LlmClient, Request};
|
||||
|
||||
/// Recorded event
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
@@ -79,7 +79,7 @@ pub async fn record_request<C: LlmClient>(
|
||||
}
|
||||
|
||||
// Save
|
||||
let fixtures_dir = Path::new("worker/tests/fixtures").join(subdir);
|
||||
let fixtures_dir = Path::new("engine/tests/fixtures").join(subdir);
|
||||
fs::create_dir_all(&fixtures_dir)?;
|
||||
|
||||
let filepath = fixtures_dir.join(format!("{}.jsonl", output_name));
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Defines requests and output file names for each scenario
|
||||
|
||||
use llm_worker::llm_client::{Request, ToolDefinition};
|
||||
use agen::llm_client::{Request, ToolDefinition};
|
||||
|
||||
/// Test scenario
|
||||
pub struct TestScenario {
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Closure-based event callback API
|
||||
//!
|
||||
//! Provides a closure-based alternative to implementing `Handler<K>` directly.
|
||||
//! Register callbacks on `Worker` via `on_text_block()`, `on_tool_use_block()`,
|
||||
//! Register callbacks on `Engine` via `on_text_block()`, `on_tool_use_block()`,
|
||||
//! `on_usage()`, etc.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
@@ -18,13 +18,13 @@ use crate::tool::ToolCall;
|
||||
|
||||
/// Callback scope for a text block.
|
||||
///
|
||||
/// Passed to the setup closure registered with `Worker::on_text_block()`.
|
||||
/// Passed to the setup closure registered with `Engine::on_text_block()`.
|
||||
/// Register per-block callbacks via `on_delta()` and `on_stop()`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// worker.on_text_block(|block| {
|
||||
/// engine.on_text_block(|block| {
|
||||
/// block.on_delta(|text| print!("{}", text));
|
||||
/// block.on_stop(|full_text| println!("\n--- {} chars ---", full_text.len()));
|
||||
/// });
|
||||
@@ -176,13 +176,13 @@ impl Handler<ThinkingBlockKind> for ClosureThinkingBlockHandler {
|
||||
|
||||
/// Callback scope for a tool use block.
|
||||
///
|
||||
/// Passed to the setup closure registered with `Worker::on_tool_use_block()`.
|
||||
/// Passed to the setup closure registered with `Engine::on_tool_use_block()`.
|
||||
/// The setup closure also receives `&ToolUseBlockStart` with `id` and `name`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// worker.on_tool_use_block(|start, block| {
|
||||
/// engine.on_tool_use_block(|start, block| {
|
||||
/// println!("Tool: {} ({})", start.name, start.id);
|
||||
/// block.on_delta(|json| { /* streaming JSON fragment */ });
|
||||
/// block.on_stop(|call| println!("Done: {}", call.name));
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
//! Public event types for Worker layer
|
||||
//! Public event types for Engine layer
|
||||
//!
|
||||
//! Re-exports from the canonical event definitions in llm_client.
|
||||
|
||||
@@ -32,7 +32,7 @@ pub trait Kind {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use llm_worker::timeline::{Handler, TextBlockEvent, TextBlockKind};
|
||||
/// use agen::timeline::{Handler, TextBlockEvent, TextBlockKind};
|
||||
///
|
||||
/// struct TextCollector {
|
||||
/// texts: Vec<String>,
|
||||
@@ -91,16 +91,6 @@ impl Kind for ErrorKind {
|
||||
type Event = ErrorEvent;
|
||||
}
|
||||
|
||||
/// Reasoning item Kind - 完成済み reasoning item の永続化用
|
||||
///
|
||||
/// 1 reasoning item につき 1 度だけ発火する。Worker は
|
||||
/// `ReasoningItemCollector` 経由で受け取り、ターン終了時に
|
||||
/// `Item::Reasoning` として history に append する。
|
||||
pub struct ReasoningItemKind;
|
||||
impl Kind for ReasoningItemKind {
|
||||
type Event = ReasoningItemEvent;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Block Kind Definitions
|
||||
// =============================================================================
|
||||
@@ -152,6 +142,7 @@ pub struct ThinkingBlockStart {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ThinkingBlockStop {
|
||||
pub index: usize,
|
||||
pub reasoning: Option<ReasoningBlockData>,
|
||||
}
|
||||
|
||||
/// ToolUseBlock Kind - for tool use blocks
|
||||
@@ -1,16 +1,15 @@
|
||||
//! Interceptor - control flow delegation for the Worker execution loop
|
||||
//! Interceptor - control flow delegation for the Engine execution loop
|
||||
//!
|
||||
//! Defines the [`Interceptor`] trait that upper layers (e.g. Pod) implement
|
||||
//! to inject orchestration decisions (approval, skip, pause, abort)
|
||||
//! into the Worker's turn loop without the Worker knowing about
|
||||
//! higher-level concepts.
|
||||
//! Defines the [`Interceptor`] trait that callers implement to inject
|
||||
//! orchestration decisions (approval, skip, pause, abort) into the Engine's
|
||||
//! turn loop without the Engine knowing about host-application concepts.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::Item;
|
||||
use crate::tool::{Tool, ToolCall, ToolMeta, ToolResult};
|
||||
use crate::tool::{Tool, ToolCall, ToolExecutionContext, ToolMeta, ToolResult};
|
||||
|
||||
// =============================================================================
|
||||
// Action Enums
|
||||
@@ -36,17 +35,23 @@ pub enum PromptAction {
|
||||
pub enum PreRequestAction {
|
||||
/// Proceed normally.
|
||||
Continue,
|
||||
/// Proceed after appending these items to durable worker history.
|
||||
/// Proceed after appending these items to durable engine history.
|
||||
///
|
||||
/// This is for upper-layer budget/status nudges that the model may react
|
||||
/// to: the items are committed before the request so later turns can see
|
||||
/// why the worker changed course.
|
||||
/// why the engine changed course.
|
||||
ContinueWith(Vec<Item>),
|
||||
/// Yield after appending these items to durable engine history.
|
||||
///
|
||||
/// This is for host-mediated pre-request appends that must be visible to
|
||||
/// usage accounting and compaction checks before the current LLM request is
|
||||
/// allowed to proceed.
|
||||
YieldWith(Vec<Item>),
|
||||
/// Cancel with a reason (treated as an error).
|
||||
Cancel(String),
|
||||
/// Yield control to the caller for external processing.
|
||||
///
|
||||
/// The Worker exits the turn loop cleanly with `WorkerResult::Yielded`.
|
||||
/// The Engine exits the turn loop cleanly with `EngineResult::Yielded`.
|
||||
/// The caller is expected to resume execution later.
|
||||
Yield,
|
||||
}
|
||||
@@ -101,6 +106,8 @@ pub struct ToolCallInfo {
|
||||
pub meta: ToolMeta,
|
||||
/// Tool instance (for state access).
|
||||
pub tool: Arc<dyn Tool>,
|
||||
/// Response-local execution context for this call.
|
||||
pub context: ToolExecutionContext,
|
||||
}
|
||||
|
||||
/// Context for post-tool-call decisions.
|
||||
@@ -113,17 +120,19 @@ pub struct ToolResultInfo {
|
||||
pub meta: ToolMeta,
|
||||
/// Tool instance (for state access).
|
||||
pub tool: Arc<dyn Tool>,
|
||||
/// Response-local execution context for this call.
|
||||
pub context: ToolExecutionContext,
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Interceptor Trait
|
||||
// =============================================================================
|
||||
|
||||
/// Intercepts the Worker execution loop at key decision points.
|
||||
/// Intercepts the Engine execution loop at key decision points.
|
||||
///
|
||||
/// All methods have default implementations that let the Worker
|
||||
/// proceed without intervention. Upper layers (e.g. Pod) provide
|
||||
/// richer implementations for approval flows, permission checks, etc.
|
||||
/// All methods have default implementations that let the Engine
|
||||
/// proceed without intervention. Callers provide richer implementations for
|
||||
/// approval flows, permission checks, etc.
|
||||
#[async_trait]
|
||||
pub trait Interceptor: Send + Sync {
|
||||
/// Called after receiving user input, before adding to history.
|
||||
@@ -131,7 +140,7 @@ pub trait Interceptor: Send + Sync {
|
||||
PromptAction::Continue
|
||||
}
|
||||
|
||||
/// Items that should be **committed to `worker.history`** just
|
||||
/// Items that should be **committed to `engine.history`** just
|
||||
/// before the next LLM request. Returned items are `extend`ed into
|
||||
/// the persistent history (and therefore picked up by the per-turn
|
||||
/// clone that backs the LLM request, plus the usual
|
||||
@@ -139,7 +148,7 @@ pub trait Interceptor: Send + Sync {
|
||||
///
|
||||
/// Use this for inputs that arrive from outside the LLM and need
|
||||
/// to be reflected in the on-disk history — notifications,
|
||||
/// cross-Pod events, system reminders. Do **not** use
|
||||
/// external events, system reminders. Do **not** use
|
||||
/// [`Self::pre_llm_request`] for that purpose: it mutates a
|
||||
/// per-request clone, so any committed assistant response that
|
||||
/// reacts to the injection would have no visible trigger on the
|
||||
@@ -149,17 +158,17 @@ pub trait Interceptor: Send + Sync {
|
||||
/// reproducible per-request transformations (pruning, content
|
||||
/// trimming, cache anchors) that depend only on the existing
|
||||
/// history.
|
||||
async fn pending_history_appends(&self) -> Vec<Item> {
|
||||
Vec::new()
|
||||
async fn pending_history_appends(&self) -> Result<Vec<Item>, String> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Called before each LLM request. The context starts as a clone
|
||||
/// of `worker.history` (after `pending_history_appends` and the
|
||||
/// Worker's own prune projection have been applied).
|
||||
/// of `engine.history` (after `pending_history_appends` and the
|
||||
/// Engine's own prune projection have been applied).
|
||||
///
|
||||
/// Direct mutations to `context` remain request-local and are not persisted.
|
||||
/// If an interceptor derives a human/model-visible nudge from the current
|
||||
/// request context, return [`PreRequestAction::ContinueWith`] so the Worker
|
||||
/// request context, return [`PreRequestAction::ContinueWith`] so the Engine
|
||||
/// commits it to history before the request is sent.
|
||||
async fn pre_llm_request(&self, _context: &mut Vec<Item>) -> PreRequestAction {
|
||||
PreRequestAction::Continue
|
||||
@@ -184,7 +193,7 @@ pub trait Interceptor: Send + Sync {
|
||||
async fn on_abort(&self, _reason: &str) {}
|
||||
}
|
||||
|
||||
/// Default interceptor: no intervention. Worker proceeds through the loop
|
||||
/// Default interceptor: no intervention. Engine proceeds through the loop
|
||||
/// without any external control flow decisions.
|
||||
pub(crate) struct DefaultInterceptor;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
mod engine;
|
||||
mod handler;
|
||||
mod message;
|
||||
|
||||
pub(crate) mod callback;
|
||||
pub mod event;
|
||||
pub mod interceptor;
|
||||
pub mod llm_client;
|
||||
pub mod providers;
|
||||
pub mod prune;
|
||||
pub mod state;
|
||||
pub mod timeline;
|
||||
pub mod token_counter;
|
||||
pub mod tool;
|
||||
pub mod tool_server;
|
||||
pub mod usage_record;
|
||||
|
||||
pub use agen_macros::{description, tool, tool_registry};
|
||||
pub use callback::{TextBlockScope, ThinkingBlockScope, ToolUseBlockScope};
|
||||
pub use engine::{
|
||||
Engine, EngineConfig, EngineError, EngineResult, EngineRunOutput, LlmRetryNotice,
|
||||
ToolRegistryError,
|
||||
};
|
||||
pub use handler::ToolUseBlockStart;
|
||||
pub use interceptor::Interceptor;
|
||||
pub use message::{ContentPart, Item, Message, Role};
|
||||
pub use tool::{ToolCall, ToolExecutionContext, ToolOutputLimits, ToolResult};
|
||||
pub use usage_record::UsageRecord;
|
||||
|
||||
/// Implementation dependencies used by code generated from `agen` macros.
|
||||
///
|
||||
/// This module is not a stable user-facing API. It is public only because macro expansion
|
||||
/// happens in the downstream crate.
|
||||
#[doc(hidden)]
|
||||
pub mod __private {
|
||||
pub use async_trait;
|
||||
pub use schemars;
|
||||
pub use serde;
|
||||
pub use serde_json;
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
//! `Scheme` 実装と通信層が要求する認証要件、および動的認証プロバイダ。
|
||||
//!
|
||||
//! マニフェスト側の型(`ModelConfig` / `SchemeKind` / `AuthRef`)は
|
||||
//! `crates/manifest` に置き、llm-worker はそれを知らずに済む。
|
||||
//! `AuthRequirement` は scheme が宣言する「この scheme はどんな認証を
|
||||
//! 期待するか」のランタイム記述で、manifest 側の `AuthRef` との
|
||||
//! 照合(`AuthRef → ResolvedAuth` 変換の適否)は `crates/provider`
|
||||
//! で行う。
|
||||
//! 期待するか」のランタイム記述で、設定ファイルや環境変数などから
|
||||
//! [`super::transport::ResolvedAuth`] を組み立てる責務は呼び出し側にある。
|
||||
//!
|
||||
//! Codex OAuth のようにリクエスト毎にトークンが変わり得る認証は
|
||||
//! [`AuthProvider`] trait を `crates/provider` 側で実装し、
|
||||
//! [`super::transport::ResolvedAuth::Custom`] 経由で transport に渡す。
|
||||
//! リクエスト毎にトークンが変わり得る認証は [`AuthProvider`] trait を
|
||||
//! 実装し、[`super::transport::ResolvedAuth::Custom`] 経由で transport に渡す。
|
||||
|
||||
use async_trait::async_trait;
|
||||
use reqwest::header::{HeaderName, HeaderValue};
|
||||
@@ -27,16 +23,15 @@ pub enum AuthRequirement {
|
||||
XApiKey,
|
||||
/// クエリパラメータ `?<name>=<token>`(Gemini 形式)
|
||||
QueryParam { name: &'static str },
|
||||
/// 複合ヘッダ(Codex OAuth 等、`crates/provider` 側で解決)
|
||||
/// 複合ヘッダ(呼び出し側が [`AuthProvider`] で解決)
|
||||
Custom,
|
||||
}
|
||||
|
||||
/// リクエスト毎に認証ヘッダを動的に組み立てるプロバイダ。
|
||||
///
|
||||
/// Codex OAuth のように access_token が refresh で更新されたり、
|
||||
/// `ChatGPT-Account-Id` / `X-OpenAI-Fedramp` のような複数ヘッダを
|
||||
/// 同時に注入する必要があるケースで使う。実体は `crates/provider`
|
||||
/// 側に置き、llm-worker は trait を知るだけ。
|
||||
/// access token が refresh で更新されたり、複数ヘッダを同時に注入する
|
||||
/// 必要があるケースで使う。実体は呼び出し側に置き、agen は
|
||||
/// trait を知るだけ。
|
||||
///
|
||||
/// 返したヘッダはそのまま `HeaderMap` に挿入される。`Authorization`
|
||||
/// 含む scheme 既定の認証ヘッダは送出されないので、必要なら
|
||||
@@ -45,13 +40,4 @@ pub enum AuthRequirement {
|
||||
pub trait AuthProvider: Send + Sync + std::fmt::Debug {
|
||||
/// 1 リクエスト分の認証ヘッダを返す。refresh が必要なら内部で行う。
|
||||
async fn headers(&self) -> Result<Vec<(HeaderName, HeaderValue)>, ClientError>;
|
||||
|
||||
/// ChatGPT Codex backend 向けの複合認証かどうか。
|
||||
///
|
||||
/// transport は provider crate の具象型を知らないため、この hook だけで
|
||||
/// Codex CLI 互換の wire behavior(conversation header / request compression 等)
|
||||
/// を切り替える。
|
||||
fn is_codex_backend(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ impl Clone for Box<dyn LlmClient> {
|
||||
|
||||
/// `Box<dyn LlmClient>` に対する `LlmClient` の実装
|
||||
///
|
||||
/// これにより、動的ディスパッチを使用するクライアントも `Worker` で利用可能になる。
|
||||
/// これにより、動的ディスパッチを使用するクライアントも `Engine` で利用可能になる。
|
||||
#[async_trait]
|
||||
impl LlmClient for Box<dyn LlmClient> {
|
||||
async fn stream(&self, request: Request) -> Result<ResponseStream, ClientError> {
|
||||
@@ -17,14 +17,12 @@ use serde::{Deserialize, Serialize};
|
||||
///
|
||||
/// - **メタイベント**: `Ping`, `Usage`, `Status`, `Error`, `UnhandledSse`
|
||||
/// - **ブロックイベント**: `BlockStart`, `BlockDelta`, `BlockStop`, `BlockAbort`
|
||||
/// - **永続化イベント**: `ReasoningItem` (history に commit すべき完成済み
|
||||
/// reasoning item。streaming 表示用の Thinking BlockStart/Delta/Stop と
|
||||
/// は別経路で発火する)
|
||||
///
|
||||
/// # ブロックのライフサイクル
|
||||
///
|
||||
/// テキストやツール呼び出しは、`BlockStart` → `BlockDelta`(複数) → `BlockStop`
|
||||
/// の順序でイベントが発生します。
|
||||
/// テキスト、thinking、ツール呼び出しは、`BlockStart` → `BlockDelta`(複数) → `BlockStop`
|
||||
/// の順序でイベントが発生します。thinking の round-trip metadata は
|
||||
/// `BlockStop.reasoning` に載ります。
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Event {
|
||||
/// ハートビート
|
||||
@@ -48,18 +46,6 @@ pub enum Event {
|
||||
BlockStop(BlockStop),
|
||||
/// ブロック中断
|
||||
BlockAbort(BlockAbort),
|
||||
|
||||
/// Reasoning item の完成。scheme が「次の request に送り返すための
|
||||
/// reasoning material が揃った」点で 1 度だけ発火する。
|
||||
///
|
||||
/// - Anthropic: 1 つの `thinking` content_block 完了ごと
|
||||
/// - OpenAI Responses: 1 つの reasoning output_item 完了ごと
|
||||
///
|
||||
/// 上位層(Worker / ReasoningItemCollector)はこれを `Item::Reasoning`
|
||||
/// として `worker.history` に append する。streaming 表示用の
|
||||
/// `BlockStart(Thinking)` / `BlockDelta(Thinking)` / `BlockStop(Thinking)`
|
||||
/// は依然として並行発火する(live display と round-trip persist の責務分離)。
|
||||
ReasoningItem(ReasoningItemEvent),
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -218,6 +204,12 @@ pub struct BlockStop {
|
||||
pub block_type: BlockType,
|
||||
/// 停止理由
|
||||
pub stop_reason: Option<StopReason>,
|
||||
/// Thinking block の停止時に確定した reasoning round-trip metadata。
|
||||
///
|
||||
/// `None` の Thinking block は live streaming / trace 用で、history に
|
||||
/// `Item::Reasoning` として永続化しない。`Some` の場合は block lifecycle
|
||||
/// が永続化の authoritative source になる。
|
||||
pub reasoning: Option<ReasoningBlockData>,
|
||||
}
|
||||
|
||||
impl BlockStop {
|
||||
@@ -243,22 +235,17 @@ impl BlockAbort {
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Reasoning Item Event
|
||||
// =============================================================================
|
||||
|
||||
/// 完成済み reasoning item。scheme が round-trip に必要なすべての
|
||||
/// material(text, summary, encrypted_content, signature, id)を揃えて
|
||||
/// 1 度だけ発火する。
|
||||
/// Thinking block stop で確定した reasoning material。
|
||||
///
|
||||
/// `Item::Reasoning` のフィールドを 1:1 に持つ。
|
||||
/// `Item::Reasoning` の round-trip に必要な provider material を保持する。
|
||||
/// `text` は deltas から収集した本文を上書きするために使う(metadata-only
|
||||
/// reasoning block や provider completion event で全文が届くケース)。
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct ReasoningItemEvent {
|
||||
pub struct ReasoningBlockData {
|
||||
/// scheme 側で観測した item id(OpenAI Responses の `id`)。
|
||||
pub id: Option<String>,
|
||||
/// reasoning 本体テキスト。Anthropic は `thinking` 累積、OpenAI は
|
||||
/// `reasoning_text` 累積。redacted_thinking では空。
|
||||
pub text: String,
|
||||
/// reasoning 本体テキスト。`None` の場合は block delta 収集結果を使う。
|
||||
pub text: Option<String>,
|
||||
/// summary (OpenAI Responses の `summary_text[]`)。他 scheme は空。
|
||||
pub summary: Vec<String>,
|
||||
/// 暗号化された opaque blob(Anthropic `redacted_thinking.data` /
|
||||
@@ -309,6 +296,7 @@ impl Event {
|
||||
index,
|
||||
block_type: BlockType::Text,
|
||||
stop_reason,
|
||||
reasoning: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -338,6 +326,7 @@ impl Event {
|
||||
index,
|
||||
block_type: BlockType::ToolUse,
|
||||
stop_reason: Some(StopReason::ToolUse),
|
||||
reasoning: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
//! LLM response stream を開く前の transient error 向けリトライポリシー。
|
||||
//!
|
||||
//! Worker が `LlmClient::stream` の open error に対して `is_retryable` を見て
|
||||
//! retry / backoff / TUI event / cancellation をまとめて管理する。
|
||||
//! `LlmClient::stream` の open error に対して `is_retryable` を見て
|
||||
//! retry / backoff / cancellation をまとめて管理する。
|
||||
//! SSE 読み出し開始後の失敗は対象外。
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
/// 指数バックオフ + ジッター + 累積タイムアウトを表すポリシー。
|
||||
///
|
||||
/// `Default` は llm-worker 全体の固定値を返す。manifest 経由の上書きが
|
||||
/// 必要になったら拡張する(現状は不要 → `tickets/llm-worker-transient-retry.md`)。
|
||||
/// `Default` は agen 全体の固定値を返す。呼び出し側からの上書きが
|
||||
/// 必要になったら拡張する。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RetryPolicy {
|
||||
/// 指数の基準値。`base * 2^attempt` を `cap` で頭打ちにした上限から
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
//! Anthropic scheme の wire-level 既定 capability。
|
||||
//!
|
||||
//! モデル ID 固有のテーブル(`claude-*` など)は高レベル構築層
|
||||
//! (`provider::capability`)の責務。ここでは未知モデルでも「この wire で
|
||||
//! モデル ID 固有のテーブル(`claude-*` など)は client construction layer
|
||||
//! の責務。ここでは未知モデルでも「この wire で
|
||||
//! 安全に送れる最小共通項」を返すだけに留める。
|
||||
|
||||
use crate::llm_client::capability::{
|
||||
+32
-21
@@ -216,6 +216,7 @@ impl AnthropicScheme {
|
||||
index: event.index,
|
||||
block_type: BlockType::Text, // Timeline層で上書きされる
|
||||
stop_reason: None,
|
||||
reasoning: None,
|
||||
})))
|
||||
}
|
||||
AnthropicEventType::MessageDelta => {
|
||||
@@ -286,9 +287,9 @@ impl AnthropicScheme {
|
||||
/// `parse_event` の単発 Event に加えて、以下を行う:
|
||||
/// - `content_block_stop` の `block_type` を直前の Start 値で書き戻す
|
||||
/// - `thinking` / `redacted_thinking` ブロックの本体・signature・data を
|
||||
/// `state.pending_thinking` に蓄積し、`content_block_stop` で
|
||||
/// `Event::ReasoningItem` を追加発火する
|
||||
/// - `signature_delta` を蓄積(Stream channel には流さず、reasoning event
|
||||
/// `state.pending_thinking` に蓄積し、`content_block_stop` の Thinking
|
||||
/// BlockStop metadata に載せる
|
||||
/// - `signature_delta` を蓄積(Stream channel には流さず、reasoning metadata
|
||||
/// にだけ反映する)
|
||||
pub(crate) fn parse_with_state(
|
||||
&self,
|
||||
@@ -374,16 +375,21 @@ impl AnthropicScheme {
|
||||
AnthropicEventType::ContentBlockStop => {
|
||||
let raw: ContentBlockStopEvent = serde_json::from_str(data)?;
|
||||
let block_type = state.current_block_type.take().unwrap_or(BlockType::Text);
|
||||
let reasoning = if matches!(block_type, BlockType::Thinking) {
|
||||
state
|
||||
.pending_thinking
|
||||
.take()
|
||||
.map(PendingThinking::into_reasoning)
|
||||
} else {
|
||||
state.pending_thinking.take();
|
||||
None
|
||||
};
|
||||
emitted.push(Event::BlockStop(BlockStop {
|
||||
index: raw.index,
|
||||
block_type,
|
||||
stop_reason: None,
|
||||
reasoning,
|
||||
}));
|
||||
if matches!(block_type, BlockType::Thinking) {
|
||||
if let Some(pending) = state.pending_thinking.take() {
|
||||
emitted.push(Event::ReasoningItem(pending.into_event()));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 残りは state を必要としない。既存 parse_event に委譲。
|
||||
_ => {
|
||||
@@ -524,8 +530,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thinking_block_emits_reasoning_item_with_signature() {
|
||||
// thinking ブロックが完了したら ReasoningItem に text+signature が乗ること
|
||||
fn thinking_block_stop_carries_reasoning_with_signature() {
|
||||
// thinking ブロックが完了したら reasoning metadata に text+signature が乗ること
|
||||
let scheme = AnthropicScheme::new();
|
||||
let mut state = AnthropicState::default();
|
||||
|
||||
@@ -567,18 +573,18 @@ mod tests {
|
||||
&mut state,
|
||||
)
|
||||
.unwrap();
|
||||
// BlockStop と ReasoningItem の 2 件が並ぶ
|
||||
assert!(matches!(stop_evs[0], Event::BlockStop(_)));
|
||||
let Event::ReasoningItem(reasoning) = &stop_evs[1] else {
|
||||
panic!("expected ReasoningItem, got {:?}", stop_evs[1]);
|
||||
assert_eq!(stop_evs.len(), 1);
|
||||
let Event::BlockStop(stop) = &stop_evs[0] else {
|
||||
panic!("expected BlockStop, got {:?}", stop_evs[0]);
|
||||
};
|
||||
assert_eq!(reasoning.text, "hello world");
|
||||
let reasoning = stop.reasoning.as_ref().expect("reasoning metadata");
|
||||
assert_eq!(reasoning.text.as_deref(), Some("hello world"));
|
||||
assert_eq!(reasoning.signature.as_deref(), Some("SIG-XYZ"));
|
||||
assert!(reasoning.encrypted_content.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redacted_thinking_emits_reasoning_item_with_data() {
|
||||
fn redacted_thinking_stop_carries_reasoning_with_data() {
|
||||
let scheme = AnthropicScheme::new();
|
||||
let mut state = AnthropicState::default();
|
||||
|
||||
@@ -596,16 +602,18 @@ mod tests {
|
||||
&mut state,
|
||||
)
|
||||
.unwrap();
|
||||
let Event::ReasoningItem(reasoning) = &stop_evs[1] else {
|
||||
panic!("expected ReasoningItem");
|
||||
assert_eq!(stop_evs.len(), 1);
|
||||
let Event::BlockStop(stop) = &stop_evs[0] else {
|
||||
panic!("expected BlockStop");
|
||||
};
|
||||
assert!(reasoning.text.is_empty());
|
||||
let reasoning = stop.reasoning.as_ref().expect("reasoning metadata");
|
||||
assert_eq!(reasoning.text.as_deref(), Some(""));
|
||||
assert!(reasoning.signature.is_none());
|
||||
assert_eq!(reasoning.encrypted_content.as_deref(), Some("opaque-blob"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_block_does_not_emit_reasoning_item() {
|
||||
fn text_block_stop_has_no_reasoning_metadata() {
|
||||
let scheme = AnthropicScheme::new();
|
||||
let mut state = AnthropicState::default();
|
||||
|
||||
@@ -631,7 +639,10 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(stop_evs.len(), 1);
|
||||
assert!(matches!(stop_evs[0], Event::BlockStop(_)));
|
||||
let Event::BlockStop(stop) = &stop_evs[0] else {
|
||||
panic!("expected BlockStop");
|
||||
};
|
||||
assert!(stop.reasoning.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
+5
-5
@@ -9,7 +9,7 @@ use crate::llm_client::{
|
||||
ClientError,
|
||||
auth::AuthRequirement,
|
||||
capability::ModelCapability,
|
||||
event::{BlockType, Event, ReasoningItemEvent},
|
||||
event::{BlockType, Event, ReasoningBlockData},
|
||||
scheme::Scheme,
|
||||
types::Request,
|
||||
};
|
||||
@@ -23,7 +23,7 @@ use super::AnthropicScheme;
|
||||
/// `BlockStop` に書き戻す。
|
||||
/// 2. `thinking` ブロック中の `thinking_delta` テキストと `signature_delta`
|
||||
/// 署名、および `redacted_thinking` ブロックの `data` を蓄積し、
|
||||
/// `content_block_stop` で `Event::ReasoningItem` を発火する
|
||||
/// `content_block_stop` の Thinking block metadata として返す
|
||||
/// (round-trip 永続化のため)。
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AnthropicState {
|
||||
@@ -40,10 +40,10 @@ pub(crate) struct PendingThinking {
|
||||
}
|
||||
|
||||
impl PendingThinking {
|
||||
pub(crate) fn into_event(self) -> ReasoningItemEvent {
|
||||
ReasoningItemEvent {
|
||||
pub(crate) fn into_reasoning(self) -> ReasoningBlockData {
|
||||
ReasoningBlockData {
|
||||
id: None,
|
||||
text: self.text,
|
||||
text: Some(self.text),
|
||||
summary: Vec::new(),
|
||||
encrypted_content: self.redacted_data,
|
||||
signature: self.signature,
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
//! Gemini scheme の wire-level 既定 capability。
|
||||
//!
|
||||
//! モデル ID 固有のテーブル(`gemini-*` バージョン別の reasoning 有無)は
|
||||
//! 高レベル構築層(`provider::capability`)の責務。ここでは wire の
|
||||
//! client construction layer の責務。ここでは wire の
|
||||
//! 保守的 default のみ。
|
||||
|
||||
use crate::llm_client::capability::{
|
||||
+1
@@ -205,6 +205,7 @@ impl GeminiScheme {
|
||||
index: candidate_index,
|
||||
block_type: BlockType::Text,
|
||||
stop_reason,
|
||||
reasoning: None,
|
||||
}));
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -45,8 +45,8 @@ pub trait Scheme: Clone + Send + Sync + 'static {
|
||||
/// プロバイダもあるため、モデル ID を受け取る。
|
||||
fn path(&self, model_id: &str) -> String;
|
||||
|
||||
/// この scheme が要求する認証形式。`build_client` 時に
|
||||
/// `manifest::AuthRef` と照合する。
|
||||
/// この scheme が要求する認証形式。呼び出し側は client 構築時に
|
||||
/// 設定された認証情報と照合する。
|
||||
fn required_auth(&self) -> AuthRequirement;
|
||||
|
||||
/// `Content-Type` 以外の追加ヘッダ。`anthropic-version` / `anthropic-beta` 等。
|
||||
@@ -78,8 +78,7 @@ pub trait Scheme: Clone + Send + Sync + 'static {
|
||||
|
||||
/// scheme 既定の capability。モデル ID に関係なく、この wire で
|
||||
/// 安全に送れる最小共通項を返す。既知モデル ID の能力テーブルは
|
||||
/// `provider::capability::lookup` 側(高レベル構築層)の責務で、
|
||||
/// scheme はここには関与しない。
|
||||
/// 高レベルの client 構築層の責務で、scheme はここには関与しない。
|
||||
fn default_capability(&self) -> ModelCapability;
|
||||
|
||||
/// scheme 側でサポートしていない `RequestConfig` フィールドを
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
//! OpenAI Chat Completions scheme の wire-level 既定 capability。
|
||||
//!
|
||||
//! モデル ID 固有のテーブル(`gpt-5` 系など)は高レベル構築層
|
||||
//! (`provider::capability`)の責務。ここでは wire の保守的 default のみ。
|
||||
//! モデル ID 固有のテーブル(`gpt-5` 系など)は client construction layer
|
||||
//! の責務。ここでは wire の保守的 default のみ。
|
||||
|
||||
use crate::llm_client::capability::{
|
||||
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
||||
+159
-15
@@ -5,10 +5,13 @@
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::llm_client::{
|
||||
Request,
|
||||
capability::{ModelCapability, ReasoningControl, ReasoningSupport},
|
||||
types::{Item, Role, ToolDefinition, parse_tool_arguments},
|
||||
use crate::{
|
||||
llm_client::{
|
||||
Request,
|
||||
capability::{ModelCapability, ReasoningControl, ReasoningSupport},
|
||||
types::{ContentPart, Item, Role, ToolDefinition, image_data_url, parse_tool_arguments},
|
||||
},
|
||||
tool::Attachment,
|
||||
};
|
||||
|
||||
use super::OpenAIScheme;
|
||||
@@ -134,7 +137,7 @@ impl OpenAIScheme {
|
||||
}
|
||||
|
||||
// Convert items to messages
|
||||
messages.extend(self.convert_items_to_messages(&request.items));
|
||||
messages.extend(self.convert_items_to_messages(&request.items, capability.vision));
|
||||
|
||||
let tools = request.tools.iter().map(|t| self.convert_tool(t)).collect();
|
||||
|
||||
@@ -185,12 +188,38 @@ impl OpenAIScheme {
|
||||
/// - Assistant messages have role "assistant"
|
||||
/// - Tool calls are within assistant messages as tool_calls array
|
||||
/// - Tool results have role "tool" with tool_call_id
|
||||
fn convert_items_to_messages(&self, items: &[Item]) -> Vec<OpenAIMessage> {
|
||||
fn flush_pending_tool_result_images(
|
||||
messages: &mut Vec<OpenAIMessage>,
|
||||
pending_images: &mut Vec<OpenAIContentPart>,
|
||||
) {
|
||||
if !pending_images.is_empty() {
|
||||
messages.push(OpenAIMessage {
|
||||
role: "user".to_string(),
|
||||
content: Some(OpenAIContent::Parts(std::mem::take(pending_images))),
|
||||
tool_calls: vec![],
|
||||
tool_call_id: None,
|
||||
name: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_items_to_messages(
|
||||
&self,
|
||||
items: &[Item],
|
||||
supports_images: bool,
|
||||
) -> Vec<OpenAIMessage> {
|
||||
let mut messages = Vec::new();
|
||||
let mut pending_tool_calls: Vec<OpenAIToolCall> = Vec::new();
|
||||
let mut pending_assistant_text: Option<String> = None;
|
||||
let mut pending_tool_result_images: Vec<OpenAIContentPart> = Vec::new();
|
||||
|
||||
for item in items {
|
||||
if !matches!(item, Item::ToolResult { .. }) {
|
||||
Self::flush_pending_tool_result_images(
|
||||
&mut messages,
|
||||
&mut pending_tool_result_images,
|
||||
);
|
||||
}
|
||||
match item {
|
||||
Item::Message { role, content, .. } => {
|
||||
// Flush pending tool calls
|
||||
@@ -205,16 +234,17 @@ impl OpenAIScheme {
|
||||
Role::Assistant => "assistant",
|
||||
Role::System => "system",
|
||||
};
|
||||
|
||||
let text_content: String = content
|
||||
.iter()
|
||||
.map(|p| p.as_text())
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
let message_content = OpenAIContent::Text(
|
||||
content
|
||||
.iter()
|
||||
.map(ContentPart::as_text)
|
||||
.collect::<Vec<_>>()
|
||||
.join(""),
|
||||
);
|
||||
|
||||
messages.push(OpenAIMessage {
|
||||
role: openai_role.to_string(),
|
||||
content: Some(OpenAIContent::Text(text_content)),
|
||||
content: Some(message_content),
|
||||
tool_calls: vec![],
|
||||
tool_call_id: None,
|
||||
name: None,
|
||||
@@ -244,19 +274,35 @@ impl OpenAIScheme {
|
||||
call_id,
|
||||
summary,
|
||||
content,
|
||||
attachments,
|
||||
..
|
||||
} => {
|
||||
// Flush pending tool calls before tool result
|
||||
// OpenAI requires every parallel tool result before a new user message.
|
||||
self.flush_pending_assistant(
|
||||
&mut messages,
|
||||
&mut pending_tool_calls,
|
||||
&mut pending_assistant_text,
|
||||
);
|
||||
|
||||
let text = match content {
|
||||
let mut text = match content {
|
||||
Some(c) => format!("{summary}\n{c}"),
|
||||
None => summary.clone(),
|
||||
};
|
||||
if supports_images {
|
||||
pending_tool_result_images.extend(attachments.iter().map(|attachment| {
|
||||
let Attachment::Image(image) = attachment;
|
||||
OpenAIContentPart::ImageUrl {
|
||||
image_url: ImageUrl {
|
||||
url: image_data_url(image.mime_type(), image.data()),
|
||||
},
|
||||
}
|
||||
}));
|
||||
} else if !attachments.is_empty() {
|
||||
text.push_str(&format!(
|
||||
"\n[{} image attachment(s) omitted: model does not support images]",
|
||||
attachments.len()
|
||||
));
|
||||
}
|
||||
messages.push(OpenAIMessage {
|
||||
role: "tool".to_string(),
|
||||
content: Some(OpenAIContent::Text(text)),
|
||||
@@ -284,6 +330,7 @@ impl OpenAIScheme {
|
||||
&mut pending_tool_calls,
|
||||
&mut pending_assistant_text,
|
||||
);
|
||||
Self::flush_pending_tool_result_images(&mut messages, &mut pending_tool_result_images);
|
||||
|
||||
messages
|
||||
}
|
||||
@@ -334,6 +381,13 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn vision_cap() -> ModelCapability {
|
||||
ModelCapability {
|
||||
vision: true,
|
||||
..cap()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_simple_request() {
|
||||
let scheme = OpenAIScheme::new();
|
||||
@@ -439,4 +493,94 @@ mod tests {
|
||||
assert_eq!(body.messages[1].tool_calls.len(), 1);
|
||||
assert_eq!(body.messages[2].role, "tool");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parallel_tool_results_precede_durable_image_projection() {
|
||||
let scheme = OpenAIScheme::new();
|
||||
let image = std::sync::Arc::<[u8]>::from(&b"\x89PNG\r\n\x1a\nbody"[..]);
|
||||
let request = Request::new()
|
||||
.item(Item::tool_call("call_image", "ViewImage", "{}"))
|
||||
.item(Item::tool_call("call_text", "Read", "{}"))
|
||||
.item(Item::tool_result_item_with_attachments(
|
||||
"call_image",
|
||||
"Attached image",
|
||||
None,
|
||||
false,
|
||||
vec![crate::tool::Attachment::Image(
|
||||
crate::tool::ImageAttachment::new("image/png", image),
|
||||
)],
|
||||
))
|
||||
.item(Item::tool_result_item(
|
||||
"call_text",
|
||||
"Read text",
|
||||
None,
|
||||
false,
|
||||
));
|
||||
let json = serde_json::to_value(
|
||||
&scheme
|
||||
.build_request("gpt-4o", &request, &vision_cap())
|
||||
.messages,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(json[0]["role"], "assistant");
|
||||
assert_eq!(json[1]["role"], "tool");
|
||||
assert_eq!(json[2]["role"], "tool");
|
||||
assert_eq!(json[3]["role"], "user");
|
||||
assert_eq!(json[3]["content"][0]["type"], "image_url");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn durable_tool_image_is_deterministically_lowered_to_following_user_content() {
|
||||
let scheme = OpenAIScheme::new();
|
||||
let image = std::sync::Arc::<[u8]>::from(&b"\x89PNG\r\n\x1a\nbody"[..]);
|
||||
let attachment = crate::tool::Attachment::Image(crate::tool::ImageAttachment::new(
|
||||
"image/png",
|
||||
image.clone(),
|
||||
));
|
||||
let item = Item::tool_result_item_with_attachments(
|
||||
"call_image",
|
||||
"Attached image",
|
||||
None,
|
||||
false,
|
||||
vec![attachment],
|
||||
);
|
||||
let persisted = serde_json::to_string(&item).unwrap();
|
||||
assert!(persisted.contains("attachments"));
|
||||
let restored: Item = serde_json::from_str(&persisted).unwrap();
|
||||
|
||||
let request = Request::new()
|
||||
.item(Item::tool_call(
|
||||
"call_image",
|
||||
"ViewImage",
|
||||
r#"{"path":"a.png"}"#,
|
||||
))
|
||||
.item(restored);
|
||||
let body = scheme.build_request("gpt-4o", &request, &vision_cap());
|
||||
let json = serde_json::to_value(&body.messages).unwrap();
|
||||
let rebuilt = serde_json::to_value(
|
||||
&scheme
|
||||
.build_request("gpt-4o", &request, &vision_cap())
|
||||
.messages,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(rebuilt, json);
|
||||
|
||||
assert_eq!(json[0]["role"], "assistant");
|
||||
assert_eq!(json[1]["role"], "tool");
|
||||
assert_eq!(json[2]["role"], "user");
|
||||
assert_eq!(json[2]["content"][0]["type"], "image_url");
|
||||
assert!(
|
||||
json[2]["content"][0]["image_url"]["url"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.starts_with("data:image/png;base64,")
|
||||
);
|
||||
|
||||
let mut no_vision = cap();
|
||||
no_vision.vision = false;
|
||||
let disabled =
|
||||
serde_json::to_string(&scheme.build_request("gpt-4o", &request, &no_vision)).unwrap();
|
||||
assert!(!disabled.contains("data:image"));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
//! OpenAI Responses scheme の wire-level 既定 capability。
|
||||
//!
|
||||
//! モデル ID 固有のテーブル(`gpt-5` / `codex-` 系など)は高レベル構築層
|
||||
//! (`provider::capability`)の責務。ここでは wire の保守的 default のみ。
|
||||
//! モデル ID 固有の能力テーブルは高レベルの client 構築層の責務。
|
||||
//! ここでは wire の保守的 default のみ。
|
||||
|
||||
use crate::llm_client::capability::{
|
||||
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
|
||||
+208
-34
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! `response.*` 名前空間の SSE を共通の [`Event`](crate::llm_client::event::Event)
|
||||
//! に変換する。Responses の (output_index, content_index) 2 次元座標と
|
||||
//! insomnia 側 1 次元 `BlockStart/Delta/Stop::index` のマッピングは
|
||||
//! この crate の 1 次元 `BlockStart/Delta/Stop::index` のマッピングは
|
||||
//! [`OpenAIResponsesState`] が保持する。
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
@@ -14,7 +14,7 @@ use crate::llm_client::{
|
||||
ClientError,
|
||||
event::{
|
||||
BlockDelta, BlockMetadata, BlockStart, BlockStop, BlockType, DeltaContent, ErrorEvent,
|
||||
Event, ReasoningItemEvent, ResponseStatus, StatusEvent, UnhandledSseEvent, UsageEvent,
|
||||
Event, ReasoningBlockData, ResponseStatus, StatusEvent, UnhandledSseEvent, UsageEvent,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,8 +25,8 @@ pub struct OpenAIResponsesState {
|
||||
next_index: usize,
|
||||
/// 蓄積中の reasoning output_item。`output_item.added`(Reasoning) で
|
||||
/// 確保し、`reasoning_text.delta` / `reasoning_summary_text.delta` で
|
||||
/// 蓄積、`output_item.done`(Reasoning) で `Event::ReasoningItem` を
|
||||
/// 発火してエントリを除去する。
|
||||
/// 蓄積、`output_item.done`(Reasoning) で既存 reasoning_text block または
|
||||
/// metadata-only Thinking block に reasoning persistence material を載せる。
|
||||
pending_reasoning: HashMap<usize, PendingReasoning>,
|
||||
}
|
||||
|
||||
@@ -38,6 +38,24 @@ struct PendingReasoning {
|
||||
text: String,
|
||||
/// `reasoning_summary_text.delta` を summary_index 順に蓄積。
|
||||
summary: Vec<String>,
|
||||
/// `response.content_part.done` が先に到着した reasoning/thinking block。
|
||||
///
|
||||
/// `response.output_item.done` まで待たないと encrypted_content や最終
|
||||
/// summary が揃わないため、live-visible な余分な synthetic Thinking block
|
||||
/// を作らず、既存 block の stop に persistence metadata を載せる。
|
||||
deferred_thinking_stops: Vec<SlotInfo>,
|
||||
}
|
||||
|
||||
impl PendingReasoning {
|
||||
fn into_reasoning_data(self, encrypted_content: Option<String>) -> ReasoningBlockData {
|
||||
ReasoningBlockData {
|
||||
id: self.id,
|
||||
text: Some(self.text),
|
||||
summary: self.summary.into_iter().filter(|s| !s.is_empty()).collect(),
|
||||
encrypted_content,
|
||||
signature: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenAIResponsesState {
|
||||
@@ -73,6 +91,31 @@ impl OpenAIResponsesState {
|
||||
}
|
||||
entry.summary[summary_index].push_str(text);
|
||||
}
|
||||
|
||||
fn defer_reasoning_stop(&mut self, output_index: usize, info: SlotInfo) {
|
||||
self.ensure_reasoning(output_index)
|
||||
.deferred_thinking_stops
|
||||
.push(info);
|
||||
}
|
||||
|
||||
fn take_active_reasoning_slots(&mut self, output_index: usize) -> Vec<SlotInfo> {
|
||||
let mut keys: Vec<_> = self
|
||||
.slots
|
||||
.iter()
|
||||
.filter_map(|(key, info)| match key {
|
||||
SlotKey::ContentPart { output, content }
|
||||
if *output == output_index && info.block_type == BlockType::Thinking =>
|
||||
{
|
||||
Some((*content, *key))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
keys.sort_by_key(|(content, _)| *content);
|
||||
keys.into_iter()
|
||||
.filter_map(|(_, key)| self.slots.remove(&key))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@@ -380,9 +423,10 @@ pub(crate) fn parse_sse(
|
||||
|
||||
"response.output_item.done" => {
|
||||
let ev: OutputItemDone = from_json(data)?;
|
||||
// Reasoning wrapper の done で蓄積分を ReasoningItem として発火。
|
||||
// これは `slots` の OutputItem slot とは独立している
|
||||
// (FunctionCall は slots、Reasoning は pending_reasoning)。
|
||||
// Reasoning wrapper の done で蓄積分を既存 reasoning_text block の
|
||||
// stop に載せる。content_part.done が先に来た場合は stop を defer
|
||||
// しておき、ここで encrypted_content / summary と一緒に完了させる。
|
||||
// reasoning_text が無い metadata-only item だけ synthetic block を作る。
|
||||
if let OutputItem::Reasoning {
|
||||
id,
|
||||
encrypted_content,
|
||||
@@ -396,23 +440,50 @@ pub(crate) fn parse_sse(
|
||||
if pending.id.is_none() {
|
||||
pending.id = id;
|
||||
}
|
||||
return Ok(vec![Event::ReasoningItem(ReasoningItemEvent {
|
||||
id: pending.id,
|
||||
text: pending.text,
|
||||
summary: pending
|
||||
.summary
|
||||
.into_iter()
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect(),
|
||||
encrypted_content,
|
||||
signature: None,
|
||||
})]);
|
||||
|
||||
let mut stop_blocks = std::mem::take(&mut pending.deferred_thinking_stops);
|
||||
stop_blocks.extend(state.take_active_reasoning_slots(ev.output_index));
|
||||
let reasoning = pending.into_reasoning_data(encrypted_content);
|
||||
|
||||
if stop_blocks.is_empty() {
|
||||
let info =
|
||||
state.allocate(SlotKey::OutputItem(ev.output_index), BlockType::Thinking);
|
||||
state.slots.remove(&SlotKey::OutputItem(ev.output_index));
|
||||
return Ok(vec![
|
||||
Event::BlockStart(BlockStart {
|
||||
index: info.flat_index,
|
||||
block_type: BlockType::Thinking,
|
||||
metadata: BlockMetadata::Thinking,
|
||||
}),
|
||||
Event::BlockStop(BlockStop {
|
||||
index: info.flat_index,
|
||||
block_type: BlockType::Thinking,
|
||||
stop_reason: None,
|
||||
reasoning: Some(reasoning),
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
let last = stop_blocks.len() - 1;
|
||||
return Ok(stop_blocks
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, info)| {
|
||||
Event::BlockStop(BlockStop {
|
||||
index: info.flat_index,
|
||||
block_type: info.block_type,
|
||||
stop_reason: None,
|
||||
reasoning: (idx == last).then(|| reasoning.clone()),
|
||||
})
|
||||
})
|
||||
.collect());
|
||||
}
|
||||
if let Some(info) = state.slots.remove(&SlotKey::OutputItem(ev.output_index)) {
|
||||
Ok(vec![Event::BlockStop(BlockStop {
|
||||
index: info.flat_index,
|
||||
block_type: info.block_type,
|
||||
stop_reason: None,
|
||||
reasoning: None,
|
||||
})])
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
@@ -446,11 +517,19 @@ pub(crate) fn parse_sse(
|
||||
output: ev.output_index,
|
||||
content: ev.content_index,
|
||||
}) {
|
||||
Ok(vec![Event::BlockStop(BlockStop {
|
||||
index: info.flat_index,
|
||||
block_type: info.block_type,
|
||||
stop_reason: None,
|
||||
})])
|
||||
if matches!(ev.part, ContentPart::ReasoningText { .. })
|
||||
|| info.block_type == BlockType::Thinking
|
||||
{
|
||||
state.defer_reasoning_stop(ev.output_index, info);
|
||||
Ok(Vec::new())
|
||||
} else {
|
||||
Ok(vec![Event::BlockStop(BlockStop {
|
||||
index: info.flat_index,
|
||||
block_type: info.block_type,
|
||||
stop_reason: None,
|
||||
reasoning: None,
|
||||
})])
|
||||
}
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
@@ -531,6 +610,7 @@ pub(crate) fn parse_sse(
|
||||
index: info.flat_index,
|
||||
block_type: info.block_type,
|
||||
stop_reason: None,
|
||||
reasoning: None,
|
||||
})])
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
@@ -1116,9 +1196,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_output_item_emits_reasoning_item_with_text_summary_encrypted() {
|
||||
fn reasoning_output_item_completes_metadata_thinking_block_with_text_summary_encrypted() {
|
||||
// 完成済み reasoning wrapper が text + summary[] + encrypted_content を持って
|
||||
// ReasoningItem として届くこと。
|
||||
// Thinking BlockStop metadata として届くこと。
|
||||
let mut state = OpenAIResponsesState::default();
|
||||
|
||||
// wrapper added (id だけ持つ)
|
||||
@@ -1128,11 +1208,15 @@ mod tests {
|
||||
r#"{"output_index":0,"item":{"type":"reasoning","id":"r1"}}"#,
|
||||
);
|
||||
// 内側の reasoning_text 用 content_part
|
||||
with(
|
||||
let start = with(
|
||||
&mut state,
|
||||
"response.content_part.added",
|
||||
r#"{"output_index":0,"content_index":0,"item_id":"r1","part":{"type":"reasoning_text","text":""}}"#,
|
||||
);
|
||||
let start_index = match start.as_slice() {
|
||||
[Event::BlockStart(start)] => start.index,
|
||||
other => panic!("expected one BlockStart, got {other:?}"),
|
||||
};
|
||||
with(
|
||||
&mut state,
|
||||
"response.reasoning_text.delta",
|
||||
@@ -1143,11 +1227,12 @@ mod tests {
|
||||
"response.reasoning_text.delta",
|
||||
r#"{"output_index":0,"content_index":0,"item_id":"r1","delta":"world"}"#,
|
||||
);
|
||||
with(
|
||||
let part_done = with(
|
||||
&mut state,
|
||||
"response.content_part.done",
|
||||
r#"{"output_index":0,"content_index":0,"item_id":"r1","part":{"type":"reasoning_text","text":"hello world"}}"#,
|
||||
);
|
||||
assert!(part_done.is_empty());
|
||||
// summary 1 件
|
||||
with(
|
||||
&mut state,
|
||||
@@ -1172,11 +1257,13 @@ mod tests {
|
||||
r#"{"output_index":0,"item":{"type":"reasoning","id":"r1","encrypted_content":"ENC-XYZ"}}"#,
|
||||
);
|
||||
assert_eq!(evs.len(), 1);
|
||||
let Event::ReasoningItem(reasoning) = &evs[0] else {
|
||||
panic!("expected ReasoningItem, got {:?}", evs[0]);
|
||||
let Event::BlockStop(stop) = &evs[0] else {
|
||||
panic!("expected BlockStop, got {:?}", evs[0]);
|
||||
};
|
||||
assert_eq!(stop.index, start_index);
|
||||
let reasoning = stop.reasoning.as_ref().expect("reasoning metadata");
|
||||
assert_eq!(reasoning.id.as_deref(), Some("r1"));
|
||||
assert_eq!(reasoning.text, "hello world");
|
||||
assert_eq!(reasoning.text.as_deref(), Some("hello world"));
|
||||
assert_eq!(reasoning.summary, vec!["sum-A".to_string()]);
|
||||
assert_eq!(reasoning.encrypted_content.as_deref(), Some("ENC-XYZ"));
|
||||
assert!(reasoning.signature.is_none());
|
||||
@@ -1184,10 +1271,94 @@ mod tests {
|
||||
assert!(state.pending_reasoning.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_text_done_then_output_done_emits_single_existing_block_stop() {
|
||||
let mut state = OpenAIResponsesState::default();
|
||||
with(
|
||||
&mut state,
|
||||
"response.output_item.added",
|
||||
r#"{"output_index":0,"item":{"type":"reasoning","id":"r1"}}"#,
|
||||
);
|
||||
|
||||
let mut lifecycle = Vec::new();
|
||||
lifecycle.extend(with(
|
||||
&mut state,
|
||||
"response.content_part.added",
|
||||
r#"{"output_index":0,"content_index":0,"item_id":"r1","part":{"type":"reasoning_text","text":""}}"#,
|
||||
));
|
||||
lifecycle.extend(with(
|
||||
&mut state,
|
||||
"response.reasoning_text.delta",
|
||||
r#"{"output_index":0,"content_index":0,"item_id":"r1","delta":"think"}"#,
|
||||
));
|
||||
lifecycle.extend(with(
|
||||
&mut state,
|
||||
"response.content_part.done",
|
||||
r#"{"output_index":0,"content_index":0,"item_id":"r1","part":{"type":"reasoning_text","text":"think"}}"#,
|
||||
));
|
||||
lifecycle.extend(with(
|
||||
&mut state,
|
||||
"response.output_item.done",
|
||||
r#"{"output_index":0,"item":{"type":"reasoning","id":"r1","encrypted_content":"ENC"}}"#,
|
||||
));
|
||||
|
||||
let starts: Vec<_> = lifecycle
|
||||
.iter()
|
||||
.filter_map(|event| match event {
|
||||
Event::BlockStart(start) => Some(start.index),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let stops: Vec<_> = lifecycle
|
||||
.iter()
|
||||
.filter_map(|event| match event {
|
||||
Event::BlockStop(stop) => Some(stop),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert_eq!(starts.len(), 1, "no synthetic second Thinking start");
|
||||
assert_eq!(stops.len(), 1, "no duplicate empty Thinking stop");
|
||||
assert_eq!(stops[0].index, starts[0]);
|
||||
let reasoning = stops[0].reasoning.as_ref().expect("reasoning metadata");
|
||||
assert_eq!(reasoning.text.as_deref(), Some("think"));
|
||||
assert_eq!(reasoning.encrypted_content.as_deref(), Some("ENC"));
|
||||
|
||||
struct StopRecorder(std::sync::Arc<std::sync::Mutex<Vec<(usize, String, bool)>>>);
|
||||
impl crate::handler::Handler<crate::handler::ThinkingBlockKind> for StopRecorder {
|
||||
type Scope = String;
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
scope: &mut Self::Scope,
|
||||
event: &crate::handler::ThinkingBlockEvent,
|
||||
) {
|
||||
match event {
|
||||
crate::handler::ThinkingBlockEvent::Start(_) => scope.clear(),
|
||||
crate::handler::ThinkingBlockEvent::Delta(delta) => scope.push_str(delta),
|
||||
crate::handler::ThinkingBlockEvent::Stop(stop) => self
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((stop.index, scope.clone(), stop.reasoning.is_some())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stops = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
|
||||
let mut timeline = crate::timeline::Timeline::new();
|
||||
timeline.on_thinking_block(StopRecorder(stops.clone()));
|
||||
for event in &lifecycle {
|
||||
timeline.dispatch(event);
|
||||
}
|
||||
let stops = stops.lock().unwrap().clone();
|
||||
assert_eq!(stops, vec![(starts[0], "think".to_string(), true)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_wrapper_without_inner_content_emits_empty_text() {
|
||||
// encrypted_content だけ届く(reasoning_text 無し)ケースでも
|
||||
// ReasoningItem は発火する。
|
||||
// reasoning metadata は届く。
|
||||
let mut state = OpenAIResponsesState::default();
|
||||
with(
|
||||
&mut state,
|
||||
@@ -1199,10 +1370,13 @@ mod tests {
|
||||
"response.output_item.done",
|
||||
r#"{"output_index":2,"item":{"type":"reasoning","id":"r9","encrypted_content":"BLOB"}}"#,
|
||||
);
|
||||
let Event::ReasoningItem(r) = &evs[0] else {
|
||||
panic!()
|
||||
assert_eq!(evs.len(), 2);
|
||||
assert!(matches!(evs[0], Event::BlockStart(_)));
|
||||
let Event::BlockStop(stop) = &evs[1] else {
|
||||
panic!("expected BlockStop")
|
||||
};
|
||||
assert!(r.text.is_empty());
|
||||
let r = stop.reasoning.as_ref().expect("reasoning metadata");
|
||||
assert_eq!(r.text.as_deref(), Some(""));
|
||||
assert!(r.summary.is_empty());
|
||||
assert_eq!(r.encrypted_content.as_deref(), Some("BLOB"));
|
||||
}
|
||||
+11
-11
@@ -2,10 +2,10 @@
|
||||
//!
|
||||
//! Chat Completions とは別物の item-based wire format。reasoning item と
|
||||
//! function_call item が first-class で、SSE イベントも `response.*` 名前空間で
|
||||
//! 流れる。ChatGPT OAuth 経路 (codex) は本 scheme 必須。
|
||||
//! 流れる。
|
||||
//!
|
||||
//! - リクエスト JSON 生成: [`request`]
|
||||
//! - SSE イベントパース → [`Event`](crate::llm_client::event::Event) 変換: [`events`]
|
||||
//! - リクエスト JSON 生成: `request`
|
||||
//! - SSE イベントパース → [`Event`](crate::llm_client::event::Event) 変換: `events`
|
||||
|
||||
mod capability;
|
||||
mod events;
|
||||
@@ -19,10 +19,10 @@ pub use scheme_impl::OpenAIResponsesState;
|
||||
/// `store` / `include_encrypted_content` / `send_max_output_tokens` /
|
||||
/// `send_sampling_params` は scheme 固定の wire 設定で、デフォルトは
|
||||
/// 公式 OpenAI Responses API 向け (stateless + ZDR + `max_output_tokens`
|
||||
/// / `temperature` / `top_p` 送出可)。ChatGPT backend (codex-oauth) の
|
||||
/// ように受理パラメータが subset の経路では provider 層で
|
||||
/// `send_max_output_tokens=false` / `send_sampling_params=false` に
|
||||
/// 上書きする。`ModelCapability` には入れない(モデル能力ではなく wire policy)。
|
||||
/// / `temperature` / `top_p` 送出可)。受理パラメータが subset の
|
||||
/// 互換 backend では client 構築層で `send_max_output_tokens=false` /
|
||||
/// `send_sampling_params=false` に上書きする。`ModelCapability` には
|
||||
/// 入れない(モデル能力ではなく wire policy)。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OpenAIResponsesScheme {
|
||||
/// サーバ側に response を保存するか。ZDR/stateless 運用では `false`。
|
||||
@@ -31,12 +31,12 @@ pub struct OpenAIResponsesScheme {
|
||||
/// `store=false` で reasoning を使うなら必須。
|
||||
pub include_encrypted_content: bool,
|
||||
/// `max_output_tokens` を body に載せるか。公式 OpenAI Responses API は
|
||||
/// 受理するが、ChatGPT backend (codex-oauth) は `Unsupported parameter`
|
||||
/// で 400 を返すため、その経路では `false` にする。
|
||||
/// 受理するが、互換 backend によっては `Unsupported parameter` で
|
||||
/// 400 を返すため、その経路では `false` にする。
|
||||
pub send_max_output_tokens: bool,
|
||||
/// `temperature` / `top_p` を body に載せるか。公式 OpenAI Responses API
|
||||
/// は受理するが、ChatGPT backend (codex-oauth) は `Unsupported parameter`
|
||||
/// で 400 を返すため、その経路では `false` にする。
|
||||
/// は受理するが、互換 backend によっては `Unsupported parameter` で
|
||||
/// 400 を返すため、その経路では `false` にする。
|
||||
pub send_sampling_params: bool,
|
||||
}
|
||||
|
||||
+109
-32
@@ -7,14 +7,31 @@
|
||||
use serde::{Serialize, Serializer};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::llm_client::{
|
||||
Request,
|
||||
capability::{ModelCapability, ReasoningControl, ReasoningSupport},
|
||||
types::{ContentPart, Item, Role, ToolDefinition, parse_tool_arguments},
|
||||
use crate::{
|
||||
llm_client::{
|
||||
Request,
|
||||
capability::{ModelCapability, ReasoningControl, ReasoningSupport},
|
||||
types::{ContentPart, Item, Role, ToolDefinition, image_data_url, parse_tool_arguments},
|
||||
},
|
||||
tool::Attachment,
|
||||
};
|
||||
|
||||
use super::OpenAIResponsesScheme;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum FunctionCallOutputBody {
|
||||
Text(String),
|
||||
ContentItems(Vec<FunctionCallOutputContentItem>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub(crate) enum FunctionCallOutputContentItem {
|
||||
InputText { text: String },
|
||||
InputImage { image_url: String },
|
||||
}
|
||||
|
||||
/// `/v1/responses` のリクエスト body。
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct ResponsesRequest {
|
||||
@@ -38,21 +55,21 @@ pub(crate) struct ResponsesRequest {
|
||||
/// `["reasoning.encrypted_content"]` 等。
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub include: Vec<&'static str>,
|
||||
/// 公式 OpenAI Responses API では受理されるが、ChatGPT backend
|
||||
/// (codex-oauth) は 400 で弾く。scheme の `send_max_output_tokens`
|
||||
/// が `false` のときは `None` のまま送る (skip_serializing_if で除外)。
|
||||
/// 公式 OpenAI Responses API では受理されるが、互換 backend によっては
|
||||
/// 400 で弾く。scheme の `send_max_output_tokens` が `false` のときは
|
||||
/// `None` のまま送る (skip_serializing_if で除外)。
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_output_tokens: Option<u32>,
|
||||
/// 公式 OpenAI Responses API では受理されるが、ChatGPT backend
|
||||
/// (codex-oauth) は `temperature` / `top_p` を 400 で弾く。scheme の
|
||||
/// 公式 OpenAI Responses API では受理されるが、互換 backend によっては
|
||||
/// `temperature` / `top_p` を 400 で弾く。scheme の
|
||||
/// `send_sampling_params` が `false` のときは `None` のまま送る。
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub temperature: Option<f32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub top_p: Option<f32>,
|
||||
/// 会話単位の安定キー。ChatGPT backend (codex-oauth) は明示キーが
|
||||
/// 無いとプロンプトキャッシュがほぼ効かない。pod 側は `SegmentId`
|
||||
/// を渡す。`Request::cache_key` が `None` のときはキー自体を送らない。
|
||||
/// 会話単位の安定キー。明示キーを必要とする backend では、
|
||||
/// 呼び出し側が安定した conversation identifier を渡す。
|
||||
/// `Request::cache_key` が `None` のときはキー自体を送らない。
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prompt_cache_key: Option<String>,
|
||||
}
|
||||
@@ -74,10 +91,9 @@ pub(crate) struct ReasoningConfig {
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub(crate) enum InputItem {
|
||||
/// 会話メッセージ。user / assistant / developer のいずれか。
|
||||
/// `Role::System` items は `developer` として投影する(ChatGPT
|
||||
/// backend が `role: "system"` を拒否するため。Codex CLI も
|
||||
/// system 相当の挿入には DeveloperInstructions = `role: "developer"`
|
||||
/// を使う)。
|
||||
/// `Role::System` items は `developer` として投影する。OpenAI
|
||||
/// Responses 互換 backend の一部は `role: "system"` を拒否するため、
|
||||
/// system 相当の挿入には `role: "developer"` を使う。
|
||||
Message {
|
||||
role: &'static str,
|
||||
content: Vec<InputContent>,
|
||||
@@ -92,9 +108,7 @@ pub(crate) enum InputItem {
|
||||
/// function tool の結果(user 側)。
|
||||
FunctionCallOutput {
|
||||
call_id: String,
|
||||
/// Responses は文字列 or 構造化 output を許すが、ここでは
|
||||
/// `summary` + `content` を改行連結した文字列で送る。
|
||||
output: String,
|
||||
output: FunctionCallOutputBody,
|
||||
},
|
||||
/// reasoning item。`encrypted_content` があれば必ず添える。
|
||||
Reasoning {
|
||||
@@ -119,6 +133,7 @@ pub(crate) enum InputItem {
|
||||
pub(crate) enum InputContent {
|
||||
/// user / developer 側のテキスト
|
||||
InputText { text: String },
|
||||
/// user 側の画像
|
||||
/// assistant 側のテキスト
|
||||
OutputText { text: String },
|
||||
}
|
||||
@@ -174,7 +189,7 @@ impl OpenAIResponsesScheme {
|
||||
request: &Request,
|
||||
capability: &ModelCapability,
|
||||
) -> ResponsesRequest {
|
||||
let input = convert_items_to_input(&request.items);
|
||||
let input = convert_items_to_input(&request.items, capability.vision);
|
||||
let tools = request.tools.iter().map(convert_tool).collect();
|
||||
|
||||
// Reasoning 投影: capability が Effort / Both をサポートし、かつ
|
||||
@@ -235,7 +250,7 @@ impl OpenAIResponsesScheme {
|
||||
}
|
||||
|
||||
/// `Item` 列を `input[]` に変換する。
|
||||
fn convert_items_to_input(items: &[Item]) -> Vec<InputItem> {
|
||||
fn convert_items_to_input(items: &[Item], supports_images: bool) -> Vec<InputItem> {
|
||||
let mut out = Vec::with_capacity(items.len());
|
||||
for item in items {
|
||||
match item {
|
||||
@@ -248,7 +263,7 @@ fn convert_items_to_input(items: &[Item]) -> Vec<InputItem> {
|
||||
};
|
||||
let parts: Vec<InputContent> = content
|
||||
.iter()
|
||||
.map(|p| match p {
|
||||
.map(|part| match part {
|
||||
ContentPart::Text { text } => text_variant(text.clone()),
|
||||
ContentPart::Refusal { refusal } => text_variant(refusal.clone()),
|
||||
})
|
||||
@@ -276,15 +291,33 @@ fn convert_items_to_input(items: &[Item]) -> Vec<InputItem> {
|
||||
call_id,
|
||||
summary,
|
||||
content,
|
||||
attachments,
|
||||
..
|
||||
} => {
|
||||
let text = match content {
|
||||
Some(c) => format!("{summary}\n{c}"),
|
||||
None => summary.clone(),
|
||||
};
|
||||
let output = if attachments.is_empty() {
|
||||
FunctionCallOutputBody::Text(text)
|
||||
} else if supports_images {
|
||||
let mut parts = vec![FunctionCallOutputContentItem::InputText { text }];
|
||||
parts.extend(attachments.iter().map(|attachment| {
|
||||
let Attachment::Image(image) = attachment;
|
||||
FunctionCallOutputContentItem::InputImage {
|
||||
image_url: image_data_url(image.mime_type(), image.data()),
|
||||
}
|
||||
}));
|
||||
FunctionCallOutputBody::ContentItems(parts)
|
||||
} else {
|
||||
FunctionCallOutputBody::Text(format!(
|
||||
"{text}\n[{} image attachment(s) omitted: model does not support images]",
|
||||
attachments.len()
|
||||
))
|
||||
};
|
||||
out.push(InputItem::FunctionCallOutput {
|
||||
call_id: call_id.clone(),
|
||||
output: text,
|
||||
output,
|
||||
});
|
||||
}
|
||||
Item::Reasoning {
|
||||
@@ -403,10 +436,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn system_role_item_is_projected_as_developer() {
|
||||
// ChatGPT backend (codex-oauth) は input[] の `role: "system"` を
|
||||
// "System messages are not allowed" で 400 拒否する。in-conversation
|
||||
// な system note (notify / fs_view auto-read / compaction summary) は
|
||||
// `role: "developer"` として投影し、両 backend で受理されるようにする。
|
||||
// Some compatible backends reject `role: "system"` in input[].
|
||||
// Project in-conversation system notes as `role: "developer"` so
|
||||
// both official and compatible backends can accept them.
|
||||
let scheme = OpenAIResponsesScheme::new();
|
||||
let req = Request::new()
|
||||
.user("hi")
|
||||
@@ -523,11 +555,9 @@ mod tests {
|
||||
fn reasoning_summary_field_is_always_serialized() {
|
||||
// Responses API は reasoning item に `summary` を必須で要求する。
|
||||
// summary が空でも wire 上に `summary: []` として残らないと、
|
||||
// ChatGPT backend (codex-oauth) が
|
||||
// 400 invalid_request_error: Missing required parameter:
|
||||
// 'input[N].summary'.
|
||||
// で弾く。GPT-5 + reasoning effort 未指定のターンでは summary text
|
||||
// が付かないことがあるため、空のままでも skip しないこと。
|
||||
// backend によっては missing required parameter として拒否される。
|
||||
// reasoning effort 未指定のターンでは summary text が付かないことが
|
||||
// あるため、空のままでも skip しないこと。
|
||||
let scheme = OpenAIResponsesScheme::new();
|
||||
let item = Item::reasoning("").with_encrypted_content("ENC");
|
||||
let req = Request::new().user("hi").item(item);
|
||||
@@ -694,4 +724,51 @@ mod tests {
|
||||
assert_eq!(json["tools"][0]["type"], "function");
|
||||
assert_eq!(json["tools"][0]["name"], "t");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn durable_tool_image_uses_function_call_output_content_items() {
|
||||
let scheme = OpenAIResponsesScheme::new();
|
||||
let image = std::sync::Arc::<[u8]>::from(&b"\x89PNG\r\n\x1a\nbody"[..]);
|
||||
let item = Item::tool_result_item_with_attachments(
|
||||
"call_image",
|
||||
"Attached image",
|
||||
None,
|
||||
false,
|
||||
vec![crate::tool::Attachment::Image(
|
||||
crate::tool::ImageAttachment::new("image/png", image),
|
||||
)],
|
||||
);
|
||||
let persisted = serde_json::to_string(&item).unwrap();
|
||||
let restored: Item = serde_json::from_str(&persisted).unwrap();
|
||||
let req = Request::new()
|
||||
.item(Item::tool_call(
|
||||
"call_image",
|
||||
"ViewImage",
|
||||
r#"{"path":"a.png"}"#,
|
||||
))
|
||||
.item(restored);
|
||||
let body = scheme.build_request("gpt-5", &req, &cap_with_reasoning());
|
||||
let json = serde_json::to_value(&body).unwrap();
|
||||
|
||||
assert_eq!(json["input"][1]["type"], "function_call_output");
|
||||
assert_eq!(json["input"].as_array().unwrap().len(), 2);
|
||||
assert_eq!(json["input"][1]["output"][0]["type"], "input_text");
|
||||
assert_eq!(json["input"][1]["output"][1]["type"], "input_image");
|
||||
assert!(
|
||||
json["input"][1]["output"][1]["image_url"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.starts_with("data:image/png;base64,")
|
||||
);
|
||||
let rebuilt =
|
||||
serde_json::to_value(scheme.build_request("gpt-5", &req, &cap_with_reasoning()))
|
||||
.unwrap();
|
||||
assert_eq!(rebuilt["input"], json["input"]);
|
||||
|
||||
let mut no_vision = cap_with_reasoning();
|
||||
no_vision.vision = false;
|
||||
let disabled =
|
||||
serde_json::to_string(&scheme.build_request("gpt-5", &req, &no_vision)).unwrap();
|
||||
assert!(!disabled.contains("data:image"));
|
||||
}
|
||||
}
|
||||
+11
-10
@@ -20,9 +20,8 @@ impl Scheme for OpenAIResponsesScheme {
|
||||
type State = OpenAIResponsesState;
|
||||
|
||||
fn default_base_url(&self) -> &'static str {
|
||||
// `/v1` は base_url 側に寄せる。ChatGPT OAuth 経由のときは
|
||||
// `https://chatgpt.com/backend-api/codex` を base にすれば同じ
|
||||
// `/responses` path で両系統を吸収できる(Codex CLI 準拠)。
|
||||
// `/v1` は base_url 側に寄せる。互換 backend を使う場合も、
|
||||
// base URL を差し替えるだけで同じ `/responses` path を使える。
|
||||
"https://api.openai.com/v1"
|
||||
}
|
||||
|
||||
@@ -59,27 +58,29 @@ impl Scheme for OpenAIResponsesScheme {
|
||||
|
||||
fn validate_config(&self, config: &RequestConfig) -> Vec<ConfigWarning> {
|
||||
let mut warnings = Vec::new();
|
||||
// ChatGPT backend (codex-oauth) は `max_output_tokens` を 400 で弾く。
|
||||
// scheme 構築時に `send_max_output_tokens=false` で組まれていれば
|
||||
// body 投影は止まっているので、ユーザの意図が落ちることだけを通知する。
|
||||
// Some compatible backends reject `max_output_tokens` with HTTP 400.
|
||||
// If the scheme was built with `send_max_output_tokens=false`, body
|
||||
// projection is already disabled; only notify that the user's intent
|
||||
// was dropped.
|
||||
if !self.send_max_output_tokens && config.max_tokens.is_some() {
|
||||
warnings.push(ConfigWarning::unsupported(
|
||||
"max_tokens",
|
||||
"OpenAI Responses (ChatGPT backend)",
|
||||
"OpenAI Responses compatible backend",
|
||||
));
|
||||
}
|
||||
// 同上、`temperature` / `top_p` も ChatGPT backend では 400 で弾かれる。
|
||||
// Same for `temperature` / `top_p` on compatible backends that
|
||||
// reject unsupported sampling parameters.
|
||||
if !self.send_sampling_params {
|
||||
if config.temperature.is_some() {
|
||||
warnings.push(ConfigWarning::unsupported(
|
||||
"temperature",
|
||||
"OpenAI Responses (ChatGPT backend)",
|
||||
"OpenAI Responses compatible backend",
|
||||
));
|
||||
}
|
||||
if config.top_p.is_some() {
|
||||
warnings.push(ConfigWarning::unsupported(
|
||||
"top_p",
|
||||
"OpenAI Responses (ChatGPT backend)",
|
||||
"OpenAI Responses compatible backend",
|
||||
));
|
||||
}
|
||||
}
|
||||
+249
-45
@@ -1,8 +1,8 @@
|
||||
//! `HttpTransport<S: Scheme>`: すべての LLM wire scheme を共通の 1 本の
|
||||
//! HTTP クライアントで扱う。
|
||||
//!
|
||||
//! 旧 `providers/{anthropic,openai,gemini,ollama}.rs` を置き換える。
|
||||
//! scheme 固有の差分は [`Scheme`] trait 実装に委譲する。
|
||||
//! scheme 固有の差分は [`Scheme`] trait 実装に委譲し、backend 固有の
|
||||
//! HTTP policy は [`TransportPolicy`] で明示的に差し込む。
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -12,7 +12,8 @@ use async_trait::async_trait;
|
||||
use eventsource_stream::Eventsource;
|
||||
use futures::{Stream, StreamExt, TryStreamExt};
|
||||
use reqwest::header::{
|
||||
ACCEPT, CONTENT_ENCODING, CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue, RETRY_AFTER,
|
||||
ACCEPT, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue,
|
||||
RETRY_AFTER, TRANSFER_ENCODING,
|
||||
};
|
||||
use serde_json::{Map, Value, json};
|
||||
|
||||
@@ -27,11 +28,11 @@ use super::types::{Request, RequestConfig};
|
||||
pub const DEFAULT_STREAM_OPEN_TIMEOUT: Duration = Duration::from_secs(20);
|
||||
pub const DEFAULT_FIRST_STREAM_EVENT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
/// `AuthRef` を解決したランタイム表現。`crates/provider` が構築する。
|
||||
/// 認証設定をリクエスト時に使える形へ解決したランタイム表現。
|
||||
///
|
||||
/// - `None`: 認証ヘッダを送らない(Ollama 等の opt-out)
|
||||
/// - `ApiKey`: 静的な API key 文字列
|
||||
/// - `Custom`: リクエスト毎に動的にヘッダを組み立てる(Codex OAuth 等)
|
||||
/// - `Custom`: リクエスト毎に動的にヘッダを組み立てる
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ResolvedAuth {
|
||||
None,
|
||||
@@ -62,6 +63,120 @@ impl ResolvedAuth {
|
||||
}
|
||||
}
|
||||
|
||||
/// Request body encoding policy used by [`HttpTransport`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RequestBodyEncoding {
|
||||
/// Send the request body as plain JSON.
|
||||
Json,
|
||||
/// Send the request body as zstd-compressed JSON with `Content-Encoding: zstd`.
|
||||
ZstdJson,
|
||||
}
|
||||
|
||||
impl Default for RequestBodyEncoding {
|
||||
fn default() -> Self {
|
||||
Self::Json
|
||||
}
|
||||
}
|
||||
|
||||
/// Conversation header policy used by [`HttpTransport`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ConversationHeaderPolicy {
|
||||
/// Do not derive any transport headers from [`Request::cache_key`].
|
||||
None,
|
||||
/// Send OpenAI-compatible conversation headers from [`Request::cache_key`].
|
||||
OpenAiCompatible {
|
||||
/// Send the legacy `session_id` header in addition to `session-id`.
|
||||
include_legacy_session_id: bool,
|
||||
/// Send `thread-id` with the same value as `session-id`.
|
||||
include_thread_id: bool,
|
||||
/// Send `x-client-request-id` with the same value as `session-id`.
|
||||
include_client_request_id: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for ConversationHeaderPolicy {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Backend-specific HTTP transport policy.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct TransportPolicy {
|
||||
/// How to serialize and encode the HTTP request body.
|
||||
pub request_body_encoding: RequestBodyEncoding,
|
||||
/// Optional backend-specific conversation headers derived from request metadata.
|
||||
pub conversation_headers: ConversationHeaderPolicy,
|
||||
}
|
||||
|
||||
impl TransportPolicy {
|
||||
/// Plain JSON requests with no derived conversation headers.
|
||||
pub fn standard() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// OpenAI-compatible backend profile that uses zstd JSON bodies and
|
||||
/// conversation headers derived from [`Request::cache_key`].
|
||||
pub fn openai_compatible_zstd() -> Self {
|
||||
Self {
|
||||
request_body_encoding: RequestBodyEncoding::ZstdJson,
|
||||
conversation_headers: ConversationHeaderPolicy::OpenAiCompatible {
|
||||
include_legacy_session_id: true,
|
||||
include_thread_id: true,
|
||||
include_client_request_id: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
fn header_value_for_diagnostics(headers: &HeaderMap, name: &str) -> Option<String> {
|
||||
headers
|
||||
.get(name)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
fn response_header_diagnostics(headers: &HeaderMap) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"content_type": header_value_for_diagnostics(headers, CONTENT_TYPE.as_str()),
|
||||
"content_encoding": header_value_for_diagnostics(headers, CONTENT_ENCODING.as_str()),
|
||||
"transfer_encoding": header_value_for_diagnostics(headers, TRANSFER_ENCODING.as_str()),
|
||||
"content_length": header_value_for_diagnostics(headers, CONTENT_LENGTH.as_str()),
|
||||
})
|
||||
}
|
||||
|
||||
fn request_header_diagnostics(headers: &HeaderMap) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"content_type": header_value_for_diagnostics(headers, CONTENT_TYPE.as_str()),
|
||||
"content_encoding": header_value_for_diagnostics(headers, CONTENT_ENCODING.as_str()),
|
||||
"accept": header_value_for_diagnostics(headers, ACCEPT.as_str()),
|
||||
"openai_beta": header_value_for_diagnostics(headers, "openai-beta"),
|
||||
"session_id_present": headers.contains_key("session-id"),
|
||||
"thread_id_present": headers.contains_key("thread-id"),
|
||||
"legacy_session_id_present": headers.contains_key("session_id"),
|
||||
"legacy_thread_id_present": headers.contains_key("thread_id"),
|
||||
"x_client_request_id_present": headers.contains_key("x-client-request-id"),
|
||||
"chatgpt_account_id_present": headers.contains_key("chatgpt-account-id"),
|
||||
})
|
||||
}
|
||||
|
||||
fn sse_error_context(status: u16, headers: &serde_json::Value, source: &str) -> String {
|
||||
let field = |name: &str| {
|
||||
headers
|
||||
.get(name)
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or("<none>")
|
||||
};
|
||||
format!(
|
||||
"SSE stream parse failed after HTTP {status}: {source}; content-type={}, content-encoding={}, transfer-encoding={}, content-length={}",
|
||||
field("content_type"),
|
||||
field("content_encoding"),
|
||||
field("transfer_encoding"),
|
||||
field("content_length")
|
||||
)
|
||||
}
|
||||
|
||||
/// scheme 共通の HTTP 通信層。
|
||||
pub struct HttpTransport<S: Scheme> {
|
||||
http_client: reqwest::Client,
|
||||
@@ -70,6 +185,7 @@ pub struct HttpTransport<S: Scheme> {
|
||||
base_url: String,
|
||||
auth: ResolvedAuth,
|
||||
capability: ModelCapability,
|
||||
policy: TransportPolicy,
|
||||
}
|
||||
|
||||
impl<S: Scheme> HttpTransport<S> {
|
||||
@@ -91,9 +207,16 @@ impl<S: Scheme> HttpTransport<S> {
|
||||
base_url,
|
||||
auth,
|
||||
capability,
|
||||
policy: TransportPolicy::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a backend-specific HTTP transport policy.
|
||||
pub fn with_transport_policy(mut self, policy: TransportPolicy) -> Self {
|
||||
self.policy = policy;
|
||||
self
|
||||
}
|
||||
|
||||
/// カスタム HTTP クライアントを差し込む(テスト等)。
|
||||
pub fn with_http_client(mut self, client: reqwest::Client) -> Self {
|
||||
self.http_client = client;
|
||||
@@ -156,13 +279,6 @@ impl<S: Scheme> HttpTransport<S> {
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
fn is_codex_backend(&self) -> bool {
|
||||
match &self.auth {
|
||||
ResolvedAuth::Custom(provider) => provider.is_codex_backend(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_stream_headers(
|
||||
&self,
|
||||
headers: &mut HeaderMap,
|
||||
@@ -170,14 +286,26 @@ impl<S: Scheme> HttpTransport<S> {
|
||||
) -> Result<(), ClientError> {
|
||||
headers.insert(ACCEPT, HeaderValue::from_static("text/event-stream"));
|
||||
|
||||
if self.is_codex_backend()
|
||||
if let ConversationHeaderPolicy::OpenAiCompatible {
|
||||
include_legacy_session_id,
|
||||
include_thread_id,
|
||||
include_client_request_id,
|
||||
} = self.policy.conversation_headers
|
||||
&& let Some(cache_key) = request.cache_key.as_deref()
|
||||
{
|
||||
let value = HeaderValue::from_str(cache_key).map_err(|e| {
|
||||
ClientError::Config(format!("invalid Codex conversation header: {e}"))
|
||||
ClientError::Config(format!("invalid conversation header value: {e}"))
|
||||
})?;
|
||||
headers.insert(HeaderName::from_static("session_id"), value.clone());
|
||||
headers.insert(HeaderName::from_static("x-client-request-id"), value);
|
||||
headers.insert(HeaderName::from_static("session-id"), value.clone());
|
||||
if include_thread_id {
|
||||
headers.insert(HeaderName::from_static("thread-id"), value.clone());
|
||||
}
|
||||
if include_legacy_session_id {
|
||||
headers.insert(HeaderName::from_static("session_id"), value.clone());
|
||||
}
|
||||
if include_client_request_id {
|
||||
headers.insert(HeaderName::from_static("x-client-request-id"), value);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -188,19 +316,22 @@ impl<S: Scheme> HttpTransport<S> {
|
||||
body: &serde_json::Value,
|
||||
headers: &mut HeaderMap,
|
||||
) -> Result<RequestBody, ClientError> {
|
||||
if !self.is_codex_backend() {
|
||||
return Ok(RequestBody::Json(body.clone()));
|
||||
match self.policy.request_body_encoding {
|
||||
RequestBodyEncoding::Json => Ok(RequestBody::Json(body.clone())),
|
||||
RequestBodyEncoding::ZstdJson => {
|
||||
let raw = serde_json::to_vec(body)?;
|
||||
let raw_json_bytes = raw.len();
|
||||
let compressed =
|
||||
zstd::stream::encode_all(std::io::Cursor::new(raw), 3).map_err(|e| {
|
||||
ClientError::Config(format!("failed to zstd-compress request: {e}"))
|
||||
})?;
|
||||
headers.insert(CONTENT_ENCODING, HeaderValue::from_static("zstd"));
|
||||
Ok(RequestBody::CompressedJson {
|
||||
bytes: compressed,
|
||||
raw_json_bytes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let raw = serde_json::to_vec(body)?;
|
||||
let raw_json_bytes = raw.len();
|
||||
let compressed = zstd::stream::encode_all(std::io::Cursor::new(raw), 3)
|
||||
.map_err(|e| ClientError::Config(format!("failed to zstd-compress request: {e}")))?;
|
||||
headers.insert(CONTENT_ENCODING, HeaderValue::from_static("zstd"));
|
||||
Ok(RequestBody::CompressedJson {
|
||||
bytes: compressed,
|
||||
raw_json_bytes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,6 +460,7 @@ impl<S: Scheme + Clone> Clone for HttpTransport<S> {
|
||||
base_url: self.base_url.clone(),
|
||||
auth: self.auth.clone(),
|
||||
capability: self.capability.clone(),
|
||||
policy: self.policy.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -392,7 +524,8 @@ impl<S: Scheme + Clone + 'static> LlmClient for HttpTransport<S> {
|
||||
"path": path,
|
||||
"auth_kind": auth_kind(&self.auth),
|
||||
"required_auth": format!("{:?}", self.scheme.required_auth()),
|
||||
"codex_backend": self.is_codex_backend(),
|
||||
"request_body_encoding": format!("{:?}", self.policy.request_body_encoding),
|
||||
"conversation_headers": format!("{:?}", self.policy.conversation_headers),
|
||||
"cache_key_present": request.cache_key.is_some(),
|
||||
"stream_open_timeout_ms": DEFAULT_STREAM_OPEN_TIMEOUT.as_millis() as u64,
|
||||
}),
|
||||
@@ -416,6 +549,7 @@ impl<S: Scheme + Clone + 'static> LlmClient for HttpTransport<S> {
|
||||
json!({
|
||||
"elapsed_ms": headers_started.elapsed().as_millis() as u64,
|
||||
"headers_len": headers.len(),
|
||||
"headers": request_header_diagnostics(&headers),
|
||||
}),
|
||||
);
|
||||
headers
|
||||
@@ -451,6 +585,7 @@ impl<S: Scheme + Clone + 'static> LlmClient for HttpTransport<S> {
|
||||
json!({
|
||||
"elapsed_ms": stream_headers_started.elapsed().as_millis() as u64,
|
||||
"headers_len": headers.len(),
|
||||
"headers": request_header_diagnostics(&headers),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -485,15 +620,19 @@ impl<S: Scheme + Clone + 'static> LlmClient for HttpTransport<S> {
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
let final_request_headers = request_header_diagnostics(&headers);
|
||||
let body_compression = request_body.encoding().to_string();
|
||||
emit_transport_trace(
|
||||
&request,
|
||||
"transport_body_encode_done",
|
||||
json!({
|
||||
"elapsed_ms": encode_started.elapsed().as_millis() as u64,
|
||||
"encoding": request_body.encoding(),
|
||||
"encoding": body_compression.as_str(),
|
||||
"body_compression": body_compression.as_str(),
|
||||
"raw_json_bytes": request_body.raw_json_bytes(),
|
||||
"wire_bytes": request_body.wire_bytes(),
|
||||
"request_shape": body_shape.clone(),
|
||||
"headers": final_request_headers.clone(),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -510,6 +649,7 @@ impl<S: Scheme + Clone + 'static> LlmClient for HttpTransport<S> {
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
let response_headers = response_header_diagnostics(response.headers());
|
||||
emit_transport_trace(
|
||||
&request,
|
||||
"transport_http_headers_received",
|
||||
@@ -517,6 +657,7 @@ impl<S: Scheme + Clone + 'static> LlmClient for HttpTransport<S> {
|
||||
"elapsed_ms": send_started.elapsed().as_millis() as u64,
|
||||
"status": response.status().as_u16(),
|
||||
"success": response.status().is_success(),
|
||||
"headers": response_headers,
|
||||
}),
|
||||
);
|
||||
response
|
||||
@@ -549,6 +690,8 @@ impl<S: Scheme + Clone + 'static> LlmClient for HttpTransport<S> {
|
||||
"context_length_exceeded": context_length_exceeded,
|
||||
"provider_usage_absent": context_length_exceeded,
|
||||
"request_shape": body_shape.clone(),
|
||||
"request_headers": final_request_headers.clone(),
|
||||
"body_compression": body_compression.as_str(),
|
||||
}),
|
||||
);
|
||||
return Err(error);
|
||||
@@ -563,6 +706,9 @@ impl<S: Scheme + Clone + 'static> LlmClient for HttpTransport<S> {
|
||||
);
|
||||
|
||||
let scheme = self.scheme.clone();
|
||||
let status = response.status().as_u16();
|
||||
let response_headers = response_header_diagnostics(response.headers());
|
||||
let transport_trace = request.transport_trace.clone();
|
||||
let byte_stream = response.bytes_stream().map_err(std::io::Error::other);
|
||||
let event_stream = byte_stream.eventsource();
|
||||
|
||||
@@ -575,7 +721,21 @@ impl<S: Scheme + Clone + 'static> LlmClient for HttpTransport<S> {
|
||||
Ok(events) => Ok(events),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Err(e) => Err(ClientError::Sse(e.to_string())),
|
||||
Err(e) => {
|
||||
let source = e.to_string();
|
||||
let message = sse_error_context(status, &response_headers, &source);
|
||||
if let Some(trace) = &transport_trace {
|
||||
trace.emit(
|
||||
"transport_sse_parse_error",
|
||||
json!({
|
||||
"status": status,
|
||||
"headers": response_headers.clone(),
|
||||
"error": source,
|
||||
}),
|
||||
);
|
||||
}
|
||||
Err(ClientError::Sse(message))
|
||||
}
|
||||
})
|
||||
.map(|res| {
|
||||
let s: Pin<Box<dyn Stream<Item = Result<Event, ClientError>> + Send>> = match res {
|
||||
@@ -596,9 +756,7 @@ mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestAuthProvider {
|
||||
codex: bool,
|
||||
}
|
||||
struct TestAuthProvider;
|
||||
|
||||
#[async_trait]
|
||||
impl AuthProvider for TestAuthProvider {
|
||||
@@ -614,10 +772,6 @@ mod tests {
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
fn is_codex_backend(&self) -> bool {
|
||||
self.codex
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -675,6 +829,39 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sse_error_context_includes_response_headers() {
|
||||
let headers = json!({
|
||||
"content_type": "application/octet-stream",
|
||||
"content_encoding": "gzip",
|
||||
"transfer_encoding": "chunked",
|
||||
"content_length": "123",
|
||||
});
|
||||
|
||||
let message = sse_error_context(200, &headers, "stream did not contain valid UTF-8");
|
||||
|
||||
assert!(message.contains("HTTP 200"));
|
||||
assert!(message.contains("stream did not contain valid UTF-8"));
|
||||
assert!(message.contains("content-type=application/octet-stream"));
|
||||
assert!(message.contains("content-encoding=gzip"));
|
||||
assert!(message.contains("transfer-encoding=chunked"));
|
||||
assert!(message.contains("content-length=123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_header_diagnostics_redacts_to_safe_header_subset() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(CONTENT_TYPE, HeaderValue::from_static("text/event-stream"));
|
||||
headers.insert(CONTENT_ENCODING, HeaderValue::from_static("identity"));
|
||||
headers.insert("authorization", HeaderValue::from_static("Bearer secret"));
|
||||
|
||||
let diagnostics = response_header_diagnostics(&headers);
|
||||
|
||||
assert_eq!(diagnostics["content_type"], "text/event-stream");
|
||||
assert_eq!(diagnostics["content_encoding"], "identity");
|
||||
assert!(diagnostics.get("authorization").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_body_shape_counts_reasoning_encrypted_content() {
|
||||
let payload = request_body_shape_payload(&json!({
|
||||
@@ -713,10 +900,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn codex_backend_adds_conversation_headers_and_zstd_body() {
|
||||
let transport = transport(ResolvedAuth::Custom(Arc::new(TestAuthProvider {
|
||||
codex: true,
|
||||
})));
|
||||
async fn transport_policy_adds_conversation_headers_and_zstd_body() {
|
||||
let transport = transport(ResolvedAuth::Custom(Arc::new(TestAuthProvider)))
|
||||
.with_transport_policy(TransportPolicy::openai_compatible_zstd());
|
||||
let request = Request::new().user("hello").cache_key("segment-123");
|
||||
let mut headers = transport.build_headers().await.unwrap();
|
||||
transport
|
||||
@@ -730,16 +916,32 @@ mod tests {
|
||||
let encoded = transport.encode_request_body(&body, &mut headers).unwrap();
|
||||
|
||||
assert_eq!(headers.get(ACCEPT).unwrap(), "text/event-stream");
|
||||
assert_eq!(headers.get("session-id").unwrap(), "segment-123");
|
||||
assert_eq!(headers.get("thread-id").unwrap(), "segment-123");
|
||||
assert_eq!(headers.get("session_id").unwrap(), "segment-123");
|
||||
assert_eq!(headers.get("x-client-request-id").unwrap(), "segment-123");
|
||||
assert_eq!(headers.get(CONTENT_ENCODING).unwrap(), "zstd");
|
||||
|
||||
let diagnostics = request_header_diagnostics(&headers);
|
||||
assert_eq!(diagnostics["content_type"], "application/json");
|
||||
assert_eq!(diagnostics["content_encoding"], "zstd");
|
||||
assert_eq!(diagnostics["accept"], "text/event-stream");
|
||||
assert!(diagnostics["session_id_present"].as_bool().unwrap());
|
||||
assert!(diagnostics["thread_id_present"].as_bool().unwrap());
|
||||
assert!(diagnostics["legacy_session_id_present"].as_bool().unwrap());
|
||||
assert!(
|
||||
diagnostics["x_client_request_id_present"]
|
||||
.as_bool()
|
||||
.unwrap()
|
||||
);
|
||||
assert!(diagnostics["chatgpt_account_id_present"].as_bool().unwrap());
|
||||
|
||||
let RequestBody::CompressedJson {
|
||||
bytes: compressed,
|
||||
raw_json_bytes,
|
||||
} = encoded
|
||||
else {
|
||||
panic!("Codex backend request body must be zstd-compressed");
|
||||
panic!("transport policy should zstd-compress request body");
|
||||
};
|
||||
assert!(raw_json_bytes > 0);
|
||||
let decoded = zstd::stream::decode_all(std::io::Cursor::new(compressed)).unwrap();
|
||||
@@ -748,7 +950,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn non_codex_request_does_not_get_codex_only_headers_or_compression() {
|
||||
async fn standard_policy_does_not_get_conversation_headers_or_compression() {
|
||||
let transport = transport(ResolvedAuth::ApiKey("api-key".to_string()));
|
||||
let request = Request::new().user("hello").cache_key("segment-123");
|
||||
let mut headers = transport.build_headers().await.unwrap();
|
||||
@@ -763,12 +965,14 @@ mod tests {
|
||||
let encoded = transport.encode_request_body(&body, &mut headers).unwrap();
|
||||
|
||||
assert_eq!(headers.get(ACCEPT).unwrap(), "text/event-stream");
|
||||
assert!(headers.get("session-id").is_none());
|
||||
assert!(headers.get("thread-id").is_none());
|
||||
assert!(headers.get("session_id").is_none());
|
||||
assert!(headers.get("x-client-request-id").is_none());
|
||||
assert!(headers.get(CONTENT_ENCODING).is_none());
|
||||
|
||||
let RequestBody::Json(decoded) = encoded else {
|
||||
panic!("non-Codex request body must remain normal JSON");
|
||||
panic!("standard transport policy should keep request body as JSON");
|
||||
};
|
||||
assert_eq!(decoded["prompt_cache_key"], "segment-123");
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
//! LLM Client Common Types
|
||||
//!
|
||||
//! Core conversation types for insomnia's LLM interaction model.
|
||||
//! Core conversation types for LLM interaction.
|
||||
//! The core abstraction is `Item` which represents different types of conversation elements:
|
||||
//! - Message items (user/assistant messages with content parts)
|
||||
//! - ToolCall items (tool invocations)
|
||||
@@ -9,12 +9,19 @@
|
||||
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
use crate::tool::Attachment;
|
||||
use base64::Engine as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn is_false(value: &bool) -> bool {
|
||||
!*value
|
||||
}
|
||||
|
||||
pub(crate) fn image_data_url(media_type: &str, data: &[u8]) -> String {
|
||||
let encoded = base64::engine::general_purpose::STANDARD.encode(data);
|
||||
format!("data:{media_type};base64,{encoded}")
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Item - The core unit of conversation
|
||||
// ============================================================================
|
||||
@@ -62,7 +69,7 @@ impl fmt::Debug for RequestTrace {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use llm_worker::Item;
|
||||
/// use agen::Item;
|
||||
///
|
||||
/// let user = Item::user_message("Hello!");
|
||||
/// let assistant = Item::assistant_message("Hi there!");
|
||||
@@ -117,6 +124,9 @@ pub enum Item {
|
||||
/// Whether the tool result represents an execution error.
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
is_error: bool,
|
||||
/// Durable binary details (removed with `content` by normal pruning).
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
attachments: Vec<Attachment>,
|
||||
},
|
||||
|
||||
/// Reasoning/thinking item
|
||||
@@ -250,6 +260,17 @@ impl Item {
|
||||
summary: impl Into<String>,
|
||||
content: Option<String>,
|
||||
is_error: bool,
|
||||
) -> Self {
|
||||
Self::tool_result_item_with_attachments(call_id, summary, content, is_error, Vec::new())
|
||||
}
|
||||
|
||||
/// Create a tool result item with durable, prunable structured attachments.
|
||||
pub fn tool_result_item_with_attachments(
|
||||
call_id: impl Into<String>,
|
||||
summary: impl Into<String>,
|
||||
content: Option<String>,
|
||||
is_error: bool,
|
||||
attachments: Vec<Attachment>,
|
||||
) -> Self {
|
||||
Self::ToolResult {
|
||||
id: None,
|
||||
@@ -257,6 +278,7 @@ impl Item {
|
||||
summary: summary.into(),
|
||||
content,
|
||||
is_error,
|
||||
attachments,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,7 +483,7 @@ impl ContentPart {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the text content regardless of type
|
||||
/// Get a textual projection of the content part.
|
||||
pub fn as_text(&self) -> &str {
|
||||
match self {
|
||||
Self::Text { text } => text,
|
||||
@@ -521,9 +543,8 @@ pub struct Request {
|
||||
/// Providers without prompt caching ignore the field.
|
||||
pub cache_anchor: Option<usize>,
|
||||
/// 会話単位の安定キー。`prompt_cache_key` として送られる
|
||||
/// (OpenAI Responses)。ChatGPT backend (codex-oauth) は明示キーが
|
||||
/// 無いと org/project ハッシュ衝突でプロンプトキャッシュが
|
||||
/// ほぼヒットしないため、pod 側で `SegmentId` を渡す運用を想定。
|
||||
/// (OpenAI Responses)。明示キーを必要とする backend では、
|
||||
/// 呼び出し側が安定した conversation identifier を渡す。
|
||||
/// `cache_anchor` と違い名前空間キーであり、`prefix anchor` とは
|
||||
/// 別の概念。`cache_anchor` を読まない provider と同じく、
|
||||
/// `prompt_cache_key` を持たない provider は無視する。
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
//! `~/.codex/auth.json` の読み書き。
|
||||
//!
|
||||
//! Codex CLI と schema を共有するが、insomnia は知らないフィールドを
|
||||
//! 失わないようファイル全体を `serde_json::Value` で保持し、必要箇所
|
||||
//! Codex CLI と schema を共有するが、知らないフィールドを失わないよう
|
||||
//! ファイル全体を `serde_json::Value` で保持し、必要箇所
|
||||
//! のみアクセスする。書込は `mode 0o600` を再設定(Codex CLI 同様)、
|
||||
//! ファイルロックは取らない(manager 側で guarded reload)。
|
||||
|
||||
@@ -106,7 +106,7 @@ pub async fn load(path: &Path) -> Result<AuthSnapshot, CodexAuthError> {
|
||||
/// 既存ファイルを再読込し、`tokens.{id_token,access_token,refresh_token}` と
|
||||
/// `last_refresh` を更新して書き戻す。Codex CLI の `persist_tokens` 相当。
|
||||
///
|
||||
/// 並行する Codex CLI / 別 insomnia プロセスが先に refresh していた場合の
|
||||
/// 並行する Codex CLI / 別プロセスが先に refresh していた場合の
|
||||
/// fields を保護するため、書込前に再 load して merge する。
|
||||
pub async fn persist_refreshed(
|
||||
path: &Path,
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use llm_worker::llm_client::ClientError;
|
||||
use crate::llm_client::ClientError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -4,11 +4,11 @@
|
||||
//!
|
||||
//! 設計:
|
||||
//!
|
||||
//! - llm-worker は [`AuthProvider`] trait しか知らず、実体である
|
||||
//! [`CodexAuthProvider`] はこのクレートに置く(feedback_llm_worker_scope)
|
||||
//! - HTTP transport は [`AuthProvider`] trait だけを見て、実体である
|
||||
//! [`CodexAuthProvider`] はこの optional module に置く
|
||||
//! - access_token JWT の `exp` を読み、`now` 以下で proactive refresh
|
||||
//! (Codex CLI と同じバッファなし)
|
||||
//! - 並行する Codex CLI / 別 insomnia の refresh と取り違えないよう、
|
||||
//! - 並行する Codex CLI / 別プロセスの refresh と取り違えないよう、
|
||||
//! refresh 直前に再 load して account_id 一致を確認(guarded reload)
|
||||
//! - ファイルロックは取らず、書込前に再 load + diff merge で吸収
|
||||
//! - Codex の Keyring storage は対象外。auth.json 不在ならエラーで案内
|
||||
@@ -21,9 +21,9 @@ mod refresh;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::llm_client::{ClientError, auth::AuthProvider};
|
||||
use async_trait::async_trait;
|
||||
use chrono::{Duration, Utc};
|
||||
use llm_worker::llm_client::{ClientError, auth::AuthProvider};
|
||||
use reqwest::header::{HeaderName, HeaderValue};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
@@ -152,7 +152,7 @@ impl CodexAuthProvider {
|
||||
.map_err(|e| CodexAuthError::InvalidHeader(format!("ChatGPT-Account-Id: {e}")))?;
|
||||
out.push((HeaderName::from_static("chatgpt-account-id"), acc_val));
|
||||
|
||||
// Cloudflare WAF は ChatGPT backend アクセス元を `originator` /
|
||||
// Cloudflare WAF は互換 backend アクセス元を `originator` /
|
||||
// `User-Agent` で識別する。Codex CLI が送る固定値を流用しないと
|
||||
// HTML challenge (403) を返されて SSE に到達できない。
|
||||
out.push((
|
||||
@@ -188,10 +188,6 @@ impl AuthProvider for CodexAuthProvider {
|
||||
.map_err(CodexAuthError::to_client_error)?;
|
||||
Self::build_headers(&snap).map_err(CodexAuthError::to_client_error)
|
||||
}
|
||||
|
||||
fn is_codex_backend(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// `access_token` の JWT `exp` を見て、期限切れなら true。
|
||||
+1
-1
@@ -80,7 +80,7 @@ async fn response_with_timeout(
|
||||
.await
|
||||
.map_err(|_| {
|
||||
CodexAuthError::RefreshTransient(format!(
|
||||
"codex_oauth_refresh timed out after {}s",
|
||||
"oauth_token_refresh timed out after {}s",
|
||||
timeout.as_secs()
|
||||
))
|
||||
})?
|
||||
@@ -0,0 +1,4 @@
|
||||
//! Optional built-in provider/backend helpers.
|
||||
|
||||
#[cfg(feature = "codex")]
|
||||
pub mod codex;
|
||||
@@ -8,8 +8,8 @@
|
||||
//!
|
||||
//! Prune は **コンテキスト射影** であり、history の変換ではない。
|
||||
//! この crate が提供するのは pure な候補抽出 [`prunable_indices`] のみで、
|
||||
//! 射影の適用は上位層(`pod::prune_hook` 等)が LLM に送る一時コンテキスト
|
||||
//! に対してだけ行う。Worker の永続履歴は決して変更されない。
|
||||
//! 射影の適用は LLM に送る一時コンテキストに対してだけ行う。
|
||||
//! Engine の永続履歴は決して変更されない。
|
||||
//!
|
||||
//! 保護境界は末尾 token budget で決めるが、この crate は usage 履歴を
|
||||
//! 所有しない。prefix ごとの token 推定値と savings 推定は上位層から
|
||||
@@ -32,8 +32,8 @@ pub type TokenEstimator = Box<dyn Fn(&[Item]) -> Vec<TokenEstimate> + Send + Syn
|
||||
/// Callback that estimates the token savings for projecting the
|
||||
/// `ToolResult.content` out of `history[i]` for each `i` in `indices`.
|
||||
///
|
||||
/// Injected into [`crate::Worker`] via `set_savings_estimator` so the
|
||||
/// Worker can make `min_savings` decisions without knowing about usage
|
||||
/// Injected into [`crate::Engine`] via `set_savings_estimator` so the
|
||||
/// Engine can make `min_savings` decisions without knowing about usage
|
||||
/// measurement sources. Return `0` to signal "no data / refuse to prune".
|
||||
///
|
||||
/// 推定対象は「drop する範囲全体」ではなく「content を None にする差分」
|
||||
@@ -44,7 +44,7 @@ pub type SavingsEstimator = Box<dyn Fn(&[Item], &[usize]) -> u64 + Send + Sync>;
|
||||
/// Result of one prune evaluation pass, surfaced to the optional
|
||||
/// [`PruneObserver`] for instrumentation.
|
||||
///
|
||||
/// Worker は LLM リクエストごとに 1 回 prune の評価をし、その結果を
|
||||
/// Engine は LLM リクエストごとに 1 回 prune の評価をし、その結果を
|
||||
/// (observer が登録されていれば)この値で通知する。fire/skip の判定
|
||||
/// 結果と、判定材料になった候補数 / 推定 savings / 保護領域の先頭 index を持つ。
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -61,7 +61,7 @@ pub struct PruneEvaluation {
|
||||
}
|
||||
|
||||
/// Outcome of one prune evaluation. Each variant is one branch of the
|
||||
/// "fire vs skip" decision tree the Worker walks before each LLM request.
|
||||
/// "fire vs skip" decision tree the Engine walks before each LLM request.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PruneDecision {
|
||||
/// `prunable_indices` が空 → 何もしない。
|
||||
@@ -75,7 +75,7 @@ pub enum PruneDecision {
|
||||
}
|
||||
|
||||
/// Optional observer invoked after each prune evaluation, regardless of
|
||||
/// branch. Pod 等の上位層が install して metrics を発行する。
|
||||
/// branch. Callers can install this to publish metrics or traces.
|
||||
pub type PruneObserver = Box<dyn Fn(&PruneEvaluation) + Send + Sync>;
|
||||
|
||||
/// Configuration for the Prune algorithm.
|
||||
@@ -110,25 +110,30 @@ impl Default for PruneConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set `content = None` on each `Item::ToolResult` at the given indices.
|
||||
/// Remove detailed text and attachments from each `Item::ToolResult` at the given indices.
|
||||
///
|
||||
/// Returns the number of items that were actually modified — items that
|
||||
/// are already content-less are counted as 0. Intended for use on a
|
||||
/// request-context clone (never on a persistent history).
|
||||
/// The mandatory summary remains. Returns the number of items that were actually
|
||||
/// modified — results that already contain no detail are counted as 0. Intended
|
||||
/// for use on a request-context clone (never on a persistent history).
|
||||
pub fn project(items: &mut [Item], indices: &[usize]) -> usize {
|
||||
let mut count = 0;
|
||||
for &i in indices {
|
||||
if let Item::ToolResult { content, .. } = &mut items[i]
|
||||
&& content.is_some()
|
||||
if let Item::ToolResult {
|
||||
content,
|
||||
attachments,
|
||||
..
|
||||
} = &mut items[i]
|
||||
&& (content.is_some() || !attachments.is_empty())
|
||||
{
|
||||
*content = None;
|
||||
attachments.clear();
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
/// Indices of `Item::ToolResult { content: Some(_), .. }` that lie before
|
||||
/// Indices of detailed `Item::ToolResult` values that lie before
|
||||
/// the suffix protected by `protected_tokens`. Pure: does not mutate `items`.
|
||||
///
|
||||
/// Returns an empty vector when token estimates are unavailable (`NoData`) or
|
||||
@@ -159,8 +164,10 @@ pub fn evaluate_candidates(
|
||||
.enumerate()
|
||||
.filter_map(|(i, item)| match item {
|
||||
Item::ToolResult {
|
||||
content: Some(_), ..
|
||||
} => Some(i),
|
||||
content,
|
||||
attachments,
|
||||
..
|
||||
} if content.is_some() || !attachments.is_empty() => Some(i),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
@@ -373,6 +380,38 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_drops_image_detail_but_keeps_summary_and_persistent_source() {
|
||||
let original = vec![Item::tool_result_item_with_attachments(
|
||||
"call_image",
|
||||
"Attached image/png image (12 bytes)",
|
||||
None,
|
||||
false,
|
||||
vec![crate::tool::Attachment::Image(
|
||||
crate::tool::ImageAttachment::new("image/png", b"image-body".to_vec()),
|
||||
)],
|
||||
)];
|
||||
let mut request_context = original.clone();
|
||||
|
||||
let estimates = uniform_estimates(&original, 100);
|
||||
assert_eq!(prunable_indices(&original, 0, &estimates), vec![0]);
|
||||
|
||||
assert_eq!(project(&mut request_context, &[0]), 1);
|
||||
assert!(matches!(
|
||||
&request_context[0],
|
||||
Item::ToolResult {
|
||||
summary,
|
||||
content: None,
|
||||
attachments,
|
||||
..
|
||||
} if summary == "Attached image/png image (12 bytes)" && attachments.is_empty()
|
||||
));
|
||||
assert!(matches!(
|
||||
&original[0],
|
||||
Item::ToolResult { attachments, .. } if attachments.len() == 1
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_skips_already_pruned_items() {
|
||||
// indices points at an item whose content is already None.
|
||||
@@ -1,12 +1,12 @@
|
||||
//! Worker State
|
||||
//! Engine State
|
||||
//!
|
||||
//! State marker types for cache protection using the Type-state pattern.
|
||||
//! Worker has state transitions from `Mutable` → `Locked`.
|
||||
//! Engine has state transitions from `Mutable` → `Locked`.
|
||||
|
||||
/// Marker trait representing Worker state
|
||||
/// Marker trait representing Engine state
|
||||
///
|
||||
/// This trait is sealed and cannot be implemented externally.
|
||||
pub trait WorkerState: private::Sealed + Send + Sync + 'static {}
|
||||
pub trait EngineState: private::Sealed + Send + Sync + 'static {}
|
||||
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
@@ -19,28 +19,28 @@ mod private {
|
||||
/// - Editing message history (add, delete, clear)
|
||||
/// - Registering tools and hooks
|
||||
///
|
||||
/// Can transition to [`Locked`] state via `Worker::lock()`.
|
||||
/// Can transition to [`Locked`] state via `Engine::lock()`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use llm_worker::Worker;
|
||||
/// use agen::Engine;
|
||||
///
|
||||
/// let mut worker = Worker::new(client)
|
||||
/// let mut engine = Engine::new(client)
|
||||
/// .system_prompt("You are helpful.");
|
||||
///
|
||||
/// // History can be edited
|
||||
/// worker.push_message(Message::user("Hello"));
|
||||
/// worker.clear_history();
|
||||
/// engine.push_message(Message::user("Hello"));
|
||||
/// engine.clear_history();
|
||||
///
|
||||
/// // Lock to protected state
|
||||
/// let locked = worker.lock();
|
||||
/// let locked = engine.lock();
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Mutable;
|
||||
|
||||
impl private::Sealed for Mutable {}
|
||||
impl WorkerState for Mutable {}
|
||||
impl EngineState for Mutable {}
|
||||
|
||||
/// Cache locked state (cache protected)
|
||||
///
|
||||
@@ -51,10 +51,10 @@ impl WorkerState for Mutable {}
|
||||
/// To ensure LLM API KV cache hits,
|
||||
/// using this state during execution is recommended.
|
||||
///
|
||||
/// Can return to [`Mutable`] state via `Worker::unlock()`,
|
||||
/// Can return to [`Mutable`] state via `Engine::unlock()`,
|
||||
/// but note that cache protection will be released.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Locked;
|
||||
|
||||
impl private::Sealed for Locked {}
|
||||
impl WorkerState for Locked {}
|
||||
impl EngineState for Locked {}
|
||||
@@ -7,18 +7,19 @@
|
||||
//! - [`Timeline`] - イベントストリームの管理とディスパッチ
|
||||
//! - [`Handler`] - イベントを処理するトレイト
|
||||
//! - [`TextBlockCollector`] - テキストブロックを収集するHandler
|
||||
//! - [`ThinkingBlockCollector`] - reasoning material を Thinking block から収集するHandler
|
||||
//! - [`ToolCallCollector`] - ツール呼び出しを収集するHandler
|
||||
|
||||
pub mod event;
|
||||
mod reasoning_item_collector;
|
||||
mod text_block_collector;
|
||||
mod thinking_block_collector;
|
||||
mod timeline;
|
||||
mod tool_call_collector;
|
||||
|
||||
// 公開API
|
||||
pub use event::*;
|
||||
pub use reasoning_item_collector::ReasoningItemCollector;
|
||||
pub use text_block_collector::TextBlockCollector;
|
||||
pub use thinking_block_collector::ThinkingBlockCollector;
|
||||
pub use timeline::Timeline;
|
||||
pub use tool_call_collector::ToolCallCollector;
|
||||
|
||||
@@ -30,7 +31,6 @@ pub use crate::handler::{
|
||||
Handler,
|
||||
Kind,
|
||||
PingKind,
|
||||
ReasoningItemKind,
|
||||
StatusKind,
|
||||
// Block Events
|
||||
TextBlockEvent,
|
||||
@@ -0,0 +1,138 @@
|
||||
//! `ThinkingBlockCollector` - reasoning material from Thinking block lifecycle.
|
||||
//!
|
||||
//! Scheme implementations emit Thinking BlockStart/Delta/Stop events for live
|
||||
//! streaming. A Thinking block stop with `ReasoningBlockData` is also the single
|
||||
//! authoritative persistence signal for `Item::Reasoning` round-trip material.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::handler::{Handler, ThinkingBlockEvent, ThinkingBlockKind};
|
||||
use crate::llm_client::event::ReasoningBlockData;
|
||||
|
||||
/// Reasoning material collected from completed Thinking blocks.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ThinkingBlockCollector {
|
||||
collected: Arc<Mutex<Vec<ReasoningBlockData>>>,
|
||||
}
|
||||
|
||||
impl ThinkingBlockCollector {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// 収集済み item を取り出してクリア
|
||||
pub fn take_collected(&self) -> Vec<ReasoningBlockData> {
|
||||
let mut guard = self.collected.lock().unwrap();
|
||||
std::mem::take(&mut *guard)
|
||||
}
|
||||
|
||||
/// 収集をクリア
|
||||
pub fn clear(&self) {
|
||||
self.collected.lock().unwrap().clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<ThinkingBlockKind> for ThinkingBlockCollector {
|
||||
type Scope = String;
|
||||
|
||||
fn on_event(&mut self, scope: &mut Self::Scope, event: &ThinkingBlockEvent) {
|
||||
match event {
|
||||
ThinkingBlockEvent::Start(_) => scope.clear(),
|
||||
ThinkingBlockEvent::Delta(text) => scope.push_str(text),
|
||||
ThinkingBlockEvent::Stop(stop) => {
|
||||
if let Some(mut reasoning) = stop.reasoning.clone() {
|
||||
if reasoning.text.is_none() {
|
||||
reasoning.text = Some(scope.clone());
|
||||
}
|
||||
self.collected.lock().unwrap().push(reasoning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::llm_client::event::{
|
||||
BlockMetadata, BlockStart, BlockStop, BlockType, DeltaContent, Event, ReasoningBlockData,
|
||||
};
|
||||
use crate::timeline::Timeline;
|
||||
|
||||
#[test]
|
||||
fn collects_in_order_from_thinking_block_stops() {
|
||||
let collector = ThinkingBlockCollector::new();
|
||||
let mut timeline = Timeline::new();
|
||||
timeline.on_thinking_block(collector.clone());
|
||||
|
||||
timeline.dispatch(&Event::BlockStart(BlockStart {
|
||||
index: 0,
|
||||
block_type: BlockType::Thinking,
|
||||
metadata: BlockMetadata::Thinking,
|
||||
}));
|
||||
timeline.dispatch(&Event::BlockDelta(crate::llm_client::event::BlockDelta {
|
||||
index: 0,
|
||||
delta: DeltaContent::Thinking("first".into()),
|
||||
}));
|
||||
timeline.dispatch(&Event::BlockStop(BlockStop {
|
||||
index: 0,
|
||||
block_type: BlockType::Thinking,
|
||||
stop_reason: None,
|
||||
reasoning: Some(ReasoningBlockData {
|
||||
id: Some("r1".into()),
|
||||
signature: Some("sig1".into()),
|
||||
..Default::default()
|
||||
}),
|
||||
}));
|
||||
|
||||
timeline.dispatch(&Event::BlockStart(BlockStart {
|
||||
index: 1,
|
||||
block_type: BlockType::Thinking,
|
||||
metadata: BlockMetadata::Thinking,
|
||||
}));
|
||||
timeline.dispatch(&Event::BlockStop(BlockStop {
|
||||
index: 1,
|
||||
block_type: BlockType::Thinking,
|
||||
stop_reason: None,
|
||||
reasoning: Some(ReasoningBlockData {
|
||||
id: Some("r2".into()),
|
||||
text: Some("second".into()),
|
||||
..Default::default()
|
||||
}),
|
||||
}));
|
||||
|
||||
let items = collector.take_collected();
|
||||
assert_eq!(items.len(), 2);
|
||||
assert_eq!(items[0].text.as_deref(), Some("first"));
|
||||
assert_eq!(items[0].signature.as_deref(), Some("sig1"));
|
||||
assert_eq!(items[1].text.as_deref(), Some("second"));
|
||||
|
||||
// take は drain なので 2 度目は空
|
||||
assert!(collector.take_collected().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_streaming_only_thinking_blocks() {
|
||||
let collector = ThinkingBlockCollector::new();
|
||||
let mut timeline = Timeline::new();
|
||||
timeline.on_thinking_block(collector.clone());
|
||||
|
||||
timeline.dispatch(&Event::BlockStart(BlockStart {
|
||||
index: 0,
|
||||
block_type: BlockType::Thinking,
|
||||
metadata: BlockMetadata::Thinking,
|
||||
}));
|
||||
timeline.dispatch(&Event::BlockDelta(crate::llm_client::event::BlockDelta {
|
||||
index: 0,
|
||||
delta: DeltaContent::Thinking("live only".into()),
|
||||
}));
|
||||
timeline.dispatch(&Event::BlockStop(BlockStop {
|
||||
index: 0,
|
||||
block_type: BlockType::Thinking,
|
||||
stop_reason: None,
|
||||
reasoning: None,
|
||||
}));
|
||||
|
||||
assert!(collector.take_collected().is_empty());
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
//! Timeline層
|
||||
//!
|
||||
//! LLMからのイベントストリームを受信し、登録されたHandlerにディスパッチします。
|
||||
//! 通常はWorker経由で使用しますが、直接使用することも可能です。
|
||||
//! 通常はEngine経由で使用しますが、直接使用することも可能です。
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::{collections::HashMap, marker::PhantomData};
|
||||
|
||||
use super::event::*;
|
||||
use crate::handler::*;
|
||||
@@ -47,10 +47,8 @@ fn merge_usage(acc: &mut UsageEvent, new: &UsageEvent) {
|
||||
pub trait ErasedHandler<K: Kind>: Send + Sync {
|
||||
/// イベントをディスパッチ
|
||||
fn dispatch(&mut self, event: &K::Event);
|
||||
/// スコープを開始(Block開始時)
|
||||
/// スコープを開始
|
||||
fn start_scope(&mut self);
|
||||
/// スコープを終了(Block終了時)
|
||||
fn end_scope(&mut self);
|
||||
}
|
||||
|
||||
/// `Handler<K>`を`ErasedHandler<K>`として扱うためのラッパー
|
||||
@@ -94,10 +92,6 @@ where
|
||||
fn start_scope(&mut self) {
|
||||
self.scope = Some(H::Scope::default());
|
||||
}
|
||||
|
||||
fn end_scope(&mut self) {
|
||||
self.scope = None;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -195,7 +189,7 @@ where
|
||||
H: Handler<ThinkingBlockKind>,
|
||||
{
|
||||
handler: H,
|
||||
scope: Option<H::Scope>,
|
||||
scopes: HashMap<usize, H::Scope>,
|
||||
}
|
||||
|
||||
impl<H> ThinkingBlockHandlerWrapper<H>
|
||||
@@ -205,7 +199,7 @@ where
|
||||
fn new(handler: H) -> Self {
|
||||
Self {
|
||||
handler,
|
||||
scope: None,
|
||||
scopes: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,44 +210,43 @@ where
|
||||
H::Scope: Send + Sync,
|
||||
{
|
||||
fn dispatch_start(&mut self, start: &BlockStart) {
|
||||
if let Some(scope) = &mut self.scope {
|
||||
self.handler.on_event(
|
||||
scope,
|
||||
&ThinkingBlockEvent::Start(ThinkingBlockStart { index: start.index }),
|
||||
);
|
||||
}
|
||||
let scope = self.scopes.entry(start.index).or_default();
|
||||
self.handler.on_event(
|
||||
scope,
|
||||
&ThinkingBlockEvent::Start(ThinkingBlockStart { index: start.index }),
|
||||
);
|
||||
}
|
||||
|
||||
fn dispatch_delta(&mut self, delta: &BlockDelta) {
|
||||
if let Some(scope) = &mut self.scope {
|
||||
if let DeltaContent::Thinking(text) = &delta.delta {
|
||||
self.handler
|
||||
.on_event(scope, &ThinkingBlockEvent::Delta(text.clone()));
|
||||
}
|
||||
if let DeltaContent::Thinking(text) = &delta.delta {
|
||||
let scope = self.scopes.entry(delta.index).or_default();
|
||||
self.handler
|
||||
.on_event(scope, &ThinkingBlockEvent::Delta(text.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_stop(&mut self, stop: &BlockStop) {
|
||||
if let Some(scope) = &mut self.scope {
|
||||
if let Some(mut scope) = self.scopes.remove(&stop.index) {
|
||||
self.handler.on_event(
|
||||
scope,
|
||||
&ThinkingBlockEvent::Stop(ThinkingBlockStop { index: stop.index }),
|
||||
&mut scope,
|
||||
&ThinkingBlockEvent::Stop(ThinkingBlockStop {
|
||||
index: stop.index,
|
||||
reasoning: stop.reasoning.clone(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_abort(&mut self, _abort: &BlockAbort) {}
|
||||
|
||||
fn start_scope(&mut self) {
|
||||
self.scope = Some(H::Scope::default());
|
||||
fn dispatch_abort(&mut self, _abort: &BlockAbort) {
|
||||
self.scopes.clear();
|
||||
}
|
||||
|
||||
fn end_scope(&mut self) {
|
||||
self.scope = None;
|
||||
}
|
||||
fn start_scope(&mut self) {}
|
||||
|
||||
fn end_scope(&mut self) {}
|
||||
|
||||
fn has_scope(&self) -> bool {
|
||||
self.scope.is_some()
|
||||
!self.scopes.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +348,7 @@ where
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use llm_worker::{Timeline, Handler, TextBlockKind, TextBlockEvent};
|
||||
/// use agen::{Timeline, Handler, TextBlockKind, TextBlockEvent};
|
||||
///
|
||||
/// struct MyHandler;
|
||||
/// impl Handler<TextBlockKind> for MyHandler {
|
||||
@@ -381,8 +374,6 @@ pub struct Timeline {
|
||||
ping_handlers: Vec<Box<dyn ErasedHandler<PingKind>>>,
|
||||
status_handlers: Vec<Box<dyn ErasedHandler<StatusKind>>>,
|
||||
error_handlers: Vec<Box<dyn ErasedHandler<ErrorKind>>>,
|
||||
reasoning_item_handlers: Vec<Box<dyn ErasedHandler<ReasoningItemKind>>>,
|
||||
|
||||
// Block系ハンドラー(BlockTypeごとにグループ化)
|
||||
text_block_handlers: Vec<Box<dyn ErasedBlockHandler>>,
|
||||
thinking_block_handlers: Vec<Box<dyn ErasedBlockHandler>>,
|
||||
@@ -411,7 +402,6 @@ impl Timeline {
|
||||
ping_handlers: Vec::new(),
|
||||
status_handlers: Vec::new(),
|
||||
error_handlers: Vec::new(),
|
||||
reasoning_item_handlers: Vec::new(),
|
||||
text_block_handlers: Vec::new(),
|
||||
thinking_block_handlers: Vec::new(),
|
||||
tool_use_block_handlers: Vec::new(),
|
||||
@@ -473,18 +463,6 @@ impl Timeline {
|
||||
self
|
||||
}
|
||||
|
||||
/// `ReasoningItemKind` 用 Handler を登録
|
||||
pub fn on_reasoning_item<H>(&mut self, handler: H) -> &mut Self
|
||||
where
|
||||
H: Handler<ReasoningItemKind> + Send + Sync + 'static,
|
||||
H::Scope: Send + Sync,
|
||||
{
|
||||
let mut wrapper = HandlerWrapper::new(handler);
|
||||
wrapper.start_scope();
|
||||
self.reasoning_item_handlers.push(Box::new(wrapper));
|
||||
self
|
||||
}
|
||||
|
||||
/// TextBlockKind用のHandlerを登録
|
||||
pub fn on_text_block<H>(&mut self, handler: H) -> &mut Self
|
||||
where
|
||||
@@ -538,9 +516,6 @@ impl Timeline {
|
||||
Event::BlockDelta(d) => self.handle_block_delta(d),
|
||||
Event::BlockStop(s) => self.handle_block_stop(s),
|
||||
Event::BlockAbort(a) => self.handle_block_abort(a),
|
||||
|
||||
// 完成済み reasoning item: 即時ディスパッチ
|
||||
Event::ReasoningItem(r) => self.dispatch_reasoning_item(r),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,12 +558,6 @@ impl Timeline {
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_reasoning_item(&mut self, event: &ReasoningItemEvent) {
|
||||
for handler in &mut self.reasoning_item_handlers {
|
||||
handler.dispatch(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_block_start(&mut self, start: &BlockStart) {
|
||||
self.current_block = Some(start.block_type);
|
||||
|
||||
+2
-2
@@ -130,13 +130,13 @@ mod tests {
|
||||
let mut timeline = Timeline::new();
|
||||
timeline.on_tool_use_block(collector.clone());
|
||||
|
||||
timeline.dispatch(&Event::tool_use_start(0, "tool_empty", "ListPods"));
|
||||
timeline.dispatch(&Event::tool_use_start(0, "tool_empty", "ListItems"));
|
||||
timeline.dispatch(&Event::tool_use_stop(0));
|
||||
|
||||
let calls = collector.take_collected();
|
||||
assert_eq!(calls.len(), 1);
|
||||
assert_eq!(calls[0].id, "tool_empty");
|
||||
assert_eq!(calls[0].name, "ListPods");
|
||||
assert_eq!(calls[0].name, "ListItems");
|
||||
assert!(calls[0].input.is_object());
|
||||
assert_eq!(
|
||||
calls[0].input,
|
||||
@@ -6,7 +6,8 @@
|
||||
//! # 方針
|
||||
//!
|
||||
//! - ローカルトークナイザは持たない。実測値があればそれを採用し、
|
||||
//! measurement 間はバイト数で按分、最新 measurement より先は最終 rate で外挿する
|
||||
//! measurement 間はバイト数で按分、最新 measurement より先は byte/4
|
||||
//! fallback で外挿する
|
||||
//! - 推定の出どころは [`EstimateSource`] で呼び出し側に明示する。
|
||||
//! 課金判断には使えないが、compact / prune / memory extract trigger 等の
|
||||
//! 閾値判定には十分な精度
|
||||
@@ -21,7 +22,7 @@ pub enum EstimateSource {
|
||||
Measured,
|
||||
/// 連続する 2 つの measurement の間をバイト按分で計算
|
||||
Interpolated,
|
||||
/// 最後の measurement より新しい区間を最終 rate で外挿
|
||||
/// 最後の measurement より新しい区間を byte/4 fallback で外挿
|
||||
Extrapolated,
|
||||
/// measurement が 1 件も無く、バイト数のみのフォールバック
|
||||
NoData,
|
||||
@@ -119,17 +120,10 @@ pub fn tokens_at(
|
||||
(Some(lo), None) => {
|
||||
let lo_bytes = prefix[lo.history_len.min(cap)];
|
||||
let at_bytes = prefix[index];
|
||||
if lo_bytes == 0 || lo.input_total_tokens == 0 {
|
||||
return TokenEstimate {
|
||||
tokens: lo.input_total_tokens,
|
||||
source: EstimateSource::Extrapolated,
|
||||
};
|
||||
}
|
||||
let delta_bytes = at_bytes.saturating_sub(lo_bytes);
|
||||
let delta_tokens =
|
||||
(delta_bytes as u128 * lo.input_total_tokens as u128 / lo_bytes as u128) as u64;
|
||||
|
||||
TokenEstimate {
|
||||
tokens: lo.input_total_tokens + delta_tokens,
|
||||
tokens: lo.input_total_tokens.saturating_add(delta_bytes / 4),
|
||||
source: EstimateSource::Extrapolated,
|
||||
}
|
||||
}
|
||||
@@ -214,6 +208,63 @@ mod tests {
|
||||
assert!(est.tokens > 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extrapolation_after_single_measurement_uses_byte_fallback_not_total_prompt_rate() {
|
||||
let history = vec![msg("first"), msg(&"tool output ".repeat(400))];
|
||||
let records = vec![record(1, 11_124)];
|
||||
let prefix = prefix_bytes(&history);
|
||||
let delta_bytes = prefix[2].saturating_sub(prefix[1]);
|
||||
|
||||
let est = total_tokens(&history, &records);
|
||||
|
||||
assert_eq!(est.source, EstimateSource::Extrapolated);
|
||||
assert_eq!(est.tokens, 11_124 + delta_bytes / 4);
|
||||
|
||||
let old_projection =
|
||||
11_124 + (delta_bytes as u128 * 11_124_u128 / prefix[1] as u128) as u64;
|
||||
assert!(
|
||||
old_projection > est.tokens.saturating_mul(10),
|
||||
"old_projection={old_projection}, corrected={}",
|
||||
est.tokens
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extrapolation_after_multiple_measurements_uses_byte_fallback_for_unmeasured_delta() {
|
||||
let history = vec![
|
||||
msg("first"),
|
||||
msg(&"measured increment ".repeat(20)),
|
||||
msg(&"unmeasured increment ".repeat(30)),
|
||||
];
|
||||
let records = vec![record(1, 10_000), record(2, 10_200)];
|
||||
let prefix = prefix_bytes(&history);
|
||||
let delta_bytes = prefix[3].saturating_sub(prefix[2]);
|
||||
|
||||
let est = total_tokens(&history, &records);
|
||||
|
||||
assert_eq!(est.source, EstimateSource::Extrapolated);
|
||||
assert_eq!(est.tokens, 10_200 + delta_bytes / 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extrapolation_does_not_reuse_measured_rate_after_context_projection() {
|
||||
let compacted_span = msg("x");
|
||||
let projected = vec![
|
||||
msg("first"),
|
||||
msg("summary only"),
|
||||
compacted_span,
|
||||
msg("new user input"),
|
||||
];
|
||||
let records = vec![record(1, 10_000), record(3, 30_000)];
|
||||
let prefix = prefix_bytes(&projected);
|
||||
let delta_bytes = prefix[4].saturating_sub(prefix[3]);
|
||||
|
||||
let est = total_tokens(&projected, &records);
|
||||
|
||||
assert_eq!(est.source, EstimateSource::Extrapolated);
|
||||
assert_eq!(est.tokens, 30_000 + delta_bytes / 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn total_zero_history_is_zero() {
|
||||
let est = total_tokens(&[], &[]);
|
||||
@@ -3,11 +3,11 @@
|
||||
//! Traits for defining tools callable by LLM.
|
||||
//! Usually auto-implemented using the `#[tool]` macro.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, fmt, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
|
||||
use serde_json::Value;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -33,7 +33,7 @@ pub enum ToolError {
|
||||
/// Outputs this small don't benefit from pruning.
|
||||
pub const SUMMARY_THRESHOLD: usize = 200;
|
||||
|
||||
/// Byte-size caps applied to tool execution `content` at the Worker's
|
||||
/// Byte-size caps applied to tool execution `content` at the Engine's
|
||||
/// tool-execution boundary, before results enter conversation history.
|
||||
///
|
||||
/// Exists so a single oversized tool result (e.g. a wide `Glob` scan)
|
||||
@@ -89,19 +89,90 @@ pub(crate) fn truncate_content(content: &mut String, limit: usize) {
|
||||
content.push_str(&suffix_template.replace("%BYTES%", &dropped.to_string()));
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct ImageAttachment {
|
||||
mime_type: String,
|
||||
data: Arc<[u8]>,
|
||||
}
|
||||
|
||||
impl ImageAttachment {
|
||||
pub fn new(mime_type: impl Into<String>, data: impl Into<Arc<[u8]>>) -> Self {
|
||||
Self {
|
||||
mime_type: mime_type.into(),
|
||||
data: data.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mime_type(&self) -> &str {
|
||||
&self.mime_type
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ImageAttachment {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ImageAttachment")
|
||||
.field("mime_type", &self.mime_type)
|
||||
.field("bytes", &self.data.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ImageAttachmentWire {
|
||||
mime_type: String,
|
||||
data: String,
|
||||
}
|
||||
|
||||
impl Serialize for ImageAttachment {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
ImageAttachmentWire {
|
||||
mime_type: self.mime_type.clone(),
|
||||
data: STANDARD.encode(self.data.as_ref()),
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ImageAttachment {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let wire = ImageAttachmentWire::deserialize(deserializer)?;
|
||||
let data = STANDARD.decode(wire.data).map_err(D::Error::custom)?;
|
||||
Ok(Self::new(wire.mime_type, data))
|
||||
}
|
||||
}
|
||||
|
||||
/// Durable binary detail emitted by a tool and handled by normal ToolResult pruning.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
|
||||
pub enum Attachment {
|
||||
Image(ImageAttachment),
|
||||
}
|
||||
|
||||
/// Tool execution result.
|
||||
///
|
||||
/// Every output has a mandatory `summary` (1-2 lines) that persists in
|
||||
/// conversation history even after pruning. The optional `content` carries
|
||||
/// full details and is removed by the Prune mechanism when the context
|
||||
/// grows too large.
|
||||
/// conversation history even after pruning. Optional text and binary details are
|
||||
/// committed to history and may later be omitted only by normal ToolResult pruning.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolOutput {
|
||||
/// Short summary (1-2 lines). Always remains in history.
|
||||
pub summary: String,
|
||||
/// Detailed output. Removed by Prune when old enough.
|
||||
/// Detailed text output. Removed by Prune when old enough.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub content: Option<String>,
|
||||
/// Durable binary details handled by the same pruning lifecycle as `content`.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub attachments: Vec<Attachment>,
|
||||
}
|
||||
|
||||
impl From<String> for ToolOutput {
|
||||
@@ -110,6 +181,7 @@ impl From<String> for ToolOutput {
|
||||
ToolOutput {
|
||||
summary: s,
|
||||
content: None,
|
||||
attachments: Vec::new(),
|
||||
}
|
||||
} else {
|
||||
let lines = s.lines().count();
|
||||
@@ -118,6 +190,7 @@ impl From<String> for ToolOutput {
|
||||
ToolOutput {
|
||||
summary,
|
||||
content: Some(s),
|
||||
attachments: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,9 +200,34 @@ impl From<String> for ToolOutput {
|
||||
// ToolMeta - Immutable Meta Information
|
||||
// =============================================================================
|
||||
|
||||
/// Origin metadata for a registered tool.
|
||||
///
|
||||
/// This metadata is intentionally not part of the provider-facing tool schema.
|
||||
/// It lets host layers audit where a model-visible tool definition came from
|
||||
/// while keeping execution and permission semantics in the normal Engine path.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ToolOrigin {
|
||||
/// Origin kind, for example `plugin` or `builtin`.
|
||||
pub kind: String,
|
||||
/// Package-local plugin id.
|
||||
pub plugin_id: String,
|
||||
/// Source-qualified plugin/package reference when `kind == "plugin"`.
|
||||
pub plugin_ref: String,
|
||||
/// Plugin source such as `user`, `project`, or `builtin`.
|
||||
pub source: String,
|
||||
/// Resolved package digest.
|
||||
pub digest: String,
|
||||
/// Resolved package version.
|
||||
pub package_version: String,
|
||||
/// Plugin API/schema version declared by the package.
|
||||
pub package_api_version: u32,
|
||||
/// Surface that contributed this tool. Plugin tools use `tool`.
|
||||
pub surface: String,
|
||||
}
|
||||
|
||||
/// Tool meta information (fixed at registration, immutable)
|
||||
///
|
||||
/// Generated from `ToolDefinition` factory and does not change after registration with Worker.
|
||||
/// Generated from `ToolDefinition` factory and does not change after registration with Engine.
|
||||
/// Used for sending tool definitions to LLM.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ToolMeta {
|
||||
@@ -139,6 +237,8 @@ pub struct ToolMeta {
|
||||
pub description: String,
|
||||
/// JSON Schema for arguments
|
||||
pub input_schema: Value,
|
||||
/// Optional host-side origin metadata. This is not exposed to the LLM.
|
||||
pub origin: Option<ToolOrigin>,
|
||||
}
|
||||
|
||||
impl ToolMeta {
|
||||
@@ -148,6 +248,7 @@ impl ToolMeta {
|
||||
name: name.into(),
|
||||
description: String::new(),
|
||||
input_schema: Value::Object(Default::default()),
|
||||
origin: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +263,12 @@ impl ToolMeta {
|
||||
self.input_schema = schema;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set host-side origin metadata.
|
||||
pub fn origin(mut self, origin: ToolOrigin) -> Self {
|
||||
self.origin = Some(origin);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -171,7 +278,7 @@ impl ToolMeta {
|
||||
/// Tool definition factory
|
||||
///
|
||||
/// When called, returns `(ToolMeta, Arc<dyn Tool>)`.
|
||||
/// Called once during Worker registration, and the meta information and instance
|
||||
/// Called once during Engine registration, and the meta information and instance
|
||||
/// are cached at session scope.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -185,10 +292,48 @@ impl ToolMeta {
|
||||
/// Arc::new(MyToolImpl { state: 0 }) as Arc<dyn Tool>,
|
||||
/// )
|
||||
/// });
|
||||
/// worker.register_tool(def)?;
|
||||
/// engine.register_tool(def)?;
|
||||
/// ```
|
||||
pub type ToolDefinition = Arc<dyn Fn() -> (ToolMeta, Arc<dyn Tool>) + Send + Sync>;
|
||||
|
||||
/// Per-call context supplied by the engine when executing a tool call.
|
||||
///
|
||||
/// The context identifies a tool call within one assistant response's tool-call
|
||||
/// batch without imposing any scheduling policy on the engine. Tool
|
||||
/// implementations may use it for response-local ordering, diagnostics, or
|
||||
/// correlation, but it is intentionally not a handle to engine state, history,
|
||||
/// or session mutation.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ToolExecutionContext {
|
||||
/// Provider/tool-call id for the call being executed.
|
||||
pub call_id: String,
|
||||
/// Engine-local identity shared by all tool calls from one execution batch.
|
||||
pub batch_id: String,
|
||||
/// Zero-based order of this call in the model-returned tool-call list.
|
||||
pub call_index: usize,
|
||||
}
|
||||
|
||||
impl ToolExecutionContext {
|
||||
pub fn new(call_id: impl Into<String>, batch_id: impl Into<String>, call_index: usize) -> Self {
|
||||
Self {
|
||||
call_id: call_id.into(),
|
||||
batch_id: batch_id.into(),
|
||||
call_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Context for direct, non-engine calls in unit tests and low-level callers.
|
||||
pub fn direct() -> Self {
|
||||
Self::new("direct", "direct", 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ToolExecutionContext {
|
||||
fn default() -> Self {
|
||||
Self::direct()
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Tool trait
|
||||
// =============================================================================
|
||||
@@ -213,22 +358,22 @@ pub type ToolDefinition = Arc<dyn Fn() -> (ToolMeta, Arc<dyn Tool>) + Send + Syn
|
||||
/// }
|
||||
///
|
||||
/// // Register
|
||||
/// worker.register_tool(app.search_definition())?;
|
||||
/// engine.register_tool(app.search_definition())?;
|
||||
/// ```
|
||||
///
|
||||
/// # Manual Implementation
|
||||
///
|
||||
/// ```ignore
|
||||
/// use llm_worker::tool::{Tool, ToolError, ToolMeta, ToolDefinition};
|
||||
/// use agen::tool::{Tool, ToolError, ToolExecutionContext, ToolMeta, ToolDefinition, ToolOutput};
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// struct MyTool { counter: std::sync::atomic::AtomicUsize }
|
||||
///
|
||||
/// #[async_trait::async_trait]
|
||||
/// impl Tool for MyTool {
|
||||
/// async fn execute(&self, input: &str) -> Result<String, ToolError> {
|
||||
/// async fn execute(&self, input: &str, ctx: ToolExecutionContext) -> Result<ToolOutput, ToolError> {
|
||||
/// self.counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
/// Ok("result".to_string())
|
||||
/// Ok(format!("call {}: {}", ctx.call_index, input).into())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@@ -247,11 +392,16 @@ pub trait Tool: Send + Sync {
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `input_json` - JSON-formatted arguments generated by LLM
|
||||
/// * `ctx` - response-local call identity and ordering context
|
||||
///
|
||||
/// # Returns
|
||||
/// A [`ToolOutput`] with summary and optional detailed content.
|
||||
/// For simple cases, use `From<String>`: `Ok("done".to_string().into())`
|
||||
async fn execute(&self, input_json: &str) -> Result<ToolOutput, ToolError>;
|
||||
async fn execute(
|
||||
&self,
|
||||
input_json: &str,
|
||||
ctx: ToolExecutionContext,
|
||||
) -> Result<ToolOutput, ToolError>;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -287,6 +437,9 @@ pub struct ToolResult {
|
||||
/// Whether this is an error
|
||||
#[serde(default)]
|
||||
pub is_error: bool,
|
||||
/// Durable binary details (prunable with `content`).
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub attachments: Vec<Attachment>,
|
||||
}
|
||||
|
||||
impl ToolResult {
|
||||
@@ -297,6 +450,7 @@ impl ToolResult {
|
||||
summary: output.summary,
|
||||
content: output.content,
|
||||
is_error: false,
|
||||
attachments: output.attachments,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,6 +461,7 @@ impl ToolResult {
|
||||
summary: message.into(),
|
||||
content: None,
|
||||
is_error: true,
|
||||
attachments: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user