mistri 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +596 -3
  3. data/CONTRIBUTING.md +52 -0
  4. data/README.md +291 -306
  5. data/SECURITY.md +40 -0
  6. data/UPGRADING.md +640 -0
  7. data/assets/logo-animated.svg +30 -0
  8. data/assets/logo-dark.svg +14 -0
  9. data/assets/logo-light.svg +14 -0
  10. data/assets/logo.svg +14 -0
  11. data/assets/social-preview.png +0 -0
  12. data/docs/README.md +87 -0
  13. data/docs/context-and-workspaces.md +378 -0
  14. data/docs/mcp.md +366 -0
  15. data/docs/reliability.md +450 -0
  16. data/docs/sessions.md +295 -0
  17. data/docs/sub-agents.md +401 -0
  18. data/docs/tool-contracts.md +324 -0
  19. data/examples/approval.rb +36 -0
  20. data/examples/browser.rb +27 -0
  21. data/examples/page_editor.rb +31 -0
  22. data/examples/quickstart.rb +21 -0
  23. data/lib/generators/mistri/install/install_generator.rb +7 -3
  24. data/lib/generators/mistri/install/templates/migration.rb.tt +2 -2
  25. data/lib/generators/mistri/mcp/templates/migration.rb.tt +1 -1
  26. data/lib/generators/mistri/mcp/templates/model.rb.tt +15 -8
  27. data/lib/mistri/abort_signal.rb +10 -0
  28. data/lib/mistri/agent.rb +635 -108
  29. data/lib/mistri/budget.rb +26 -1
  30. data/lib/mistri/child.rb +186 -0
  31. data/lib/mistri/compaction.rb +26 -10
  32. data/lib/mistri/compactor.rb +35 -12
  33. data/lib/mistri/console.rb +209 -0
  34. data/lib/mistri/content.rb +9 -3
  35. data/lib/mistri/dispatchers.rb +49 -0
  36. data/lib/mistri/errors.rb +83 -4
  37. data/lib/mistri/event.rb +30 -8
  38. data/lib/mistri/event_delivery.rb +60 -0
  39. data/lib/mistri/locks/rails_cache.rb +48 -0
  40. data/lib/mistri/locks.rb +141 -0
  41. data/lib/mistri/mcp/client.rb +74 -19
  42. data/lib/mistri/mcp/egress.rb +216 -0
  43. data/lib/mistri/mcp/oauth.rb +476 -127
  44. data/lib/mistri/mcp/wires.rb +115 -23
  45. data/lib/mistri/mcp.rb +43 -9
  46. data/lib/mistri/message.rb +21 -11
  47. data/lib/mistri/models.rb +160 -22
  48. data/lib/mistri/providers/anthropic/assembler.rb +282 -44
  49. data/lib/mistri/providers/anthropic/serializer.rb +14 -9
  50. data/lib/mistri/providers/anthropic.rb +29 -6
  51. data/lib/mistri/providers/fake.rb +36 -6
  52. data/lib/mistri/providers/gemini/assembler.rb +148 -21
  53. data/lib/mistri/providers/gemini/serializer.rb +78 -9
  54. data/lib/mistri/providers/gemini.rb +31 -5
  55. data/lib/mistri/providers/openai/assembler.rb +337 -60
  56. data/lib/mistri/providers/openai/serializer.rb +13 -12
  57. data/lib/mistri/providers/openai.rb +29 -5
  58. data/lib/mistri/providers/schema_capabilities.rb +214 -0
  59. data/lib/mistri/result.rb +8 -3
  60. data/lib/mistri/retry_policy.rb +2 -2
  61. data/lib/mistri/schema.rb +893 -75
  62. data/lib/mistri/session.rb +649 -47
  63. data/lib/mistri/sinks/coalesced.rb +17 -10
  64. data/lib/mistri/skill.rb +1 -1
  65. data/lib/mistri/skills.rb +1 -1
  66. data/lib/mistri/spawner.rb +316 -0
  67. data/lib/mistri/sse.rb +57 -14
  68. data/lib/mistri/stores/active_record.rb +22 -7
  69. data/lib/mistri/stores/jsonl.rb +3 -1
  70. data/lib/mistri/stores/memory.rb +21 -2
  71. data/lib/mistri/sub_agent/execution.rb +81 -0
  72. data/lib/mistri/sub_agent/runtime.rb +297 -0
  73. data/lib/mistri/sub_agent.rb +238 -103
  74. data/lib/mistri/task_output.rb +58 -0
  75. data/lib/mistri/tool.rb +102 -13
  76. data/lib/mistri/tool_arguments.rb +377 -0
  77. data/lib/mistri/tool_call.rb +43 -9
  78. data/lib/mistri/tool_context.rb +7 -5
  79. data/lib/mistri/tool_executor.rb +117 -26
  80. data/lib/mistri/tool_result.rb +15 -10
  81. data/lib/mistri/tools/edit_file.rb +62 -8
  82. data/lib/mistri/tools.rb +41 -4
  83. data/lib/mistri/transport.rb +149 -44
  84. data/lib/mistri/usage.rb +65 -13
  85. data/lib/mistri/version.rb +1 -1
  86. data/lib/mistri/workspace/active_record.rb +183 -3
  87. data/lib/mistri/workspace/directory.rb +28 -8
  88. data/lib/mistri/workspace/memory.rb +34 -9
  89. data/lib/mistri/workspace/single.rb +62 -5
  90. data/lib/mistri/workspace.rb +39 -0
  91. data/lib/mistri.rb +17 -1
  92. data/mistri.gemspec +34 -0
  93. metadata +38 -3
@@ -0,0 +1,30 @@
1
+ <!-- mistri wordmark (مستری), cut from Noto Nastaliq Urdu Bold to pure
2
+ paths. Each stroke is addressable: #stroke-1 through #stroke-6,
3
+ class .glyph, fill inherits currentColor. Edit, recolor, or animate
4
+ per stroke. -->
5
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 -827 2450 1325" role="img" aria-label="mistri">
6
+ <style>
7
+ .glyph {
8
+ opacity: 0;
9
+ transform: translateY(2%);
10
+ animation: mistri-reveal 0.9s cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
11
+ }
12
+ #stroke-1 { animation-delay: 3.30s; }
13
+ #stroke-2 { animation-delay: 2.75s; }
14
+ #stroke-3 { animation-delay: 2.20s; }
15
+ #stroke-4 { animation-delay: 1.65s; }
16
+ #stroke-5 { animation-delay: 1.10s; }
17
+ #stroke-6 { animation-delay: 0.55s; }
18
+ @keyframes mistri-reveal {
19
+ to { opacity: 1; transform: translateY(0); }
20
+ }
21
+ </style>
22
+ <g id="wordmark" fill="currentColor">
23
+ <path id="stroke-1" class="glyph" d="M250 341Q184 341 134.0 309.0Q84 277 56.0 219.5Q28 162 28 88Q28 34 42.5 -37.5Q57 -109 92.5 -194.0Q128 -279 191 -370L217 -355Q170 -273 145.5 -215.0Q121 -157 112.5 -114.0Q104 -71 104 -33Q104 21 126.5 65.5Q149 110 195.5 136.0Q242 162 312 162Q381 162 436.5 139.0Q492 116 531.0 81.5Q570 47 591.0 13.0Q612 -21 612 -42Q612 -62 593.5 -77.0Q575 -92 525 -112Q470 -134 470 -193Q470 -215 479.5 -249.0Q489 -283 505.5 -323.0Q522 -363 543.0 -402.5Q564 -442 586 -476Q630 -544 678.5 -588.0Q727 -632 773 -632Q818 -632 818 -604Q818 -597 813 -584L765 -474L756 -472Q748 -480 725 -480Q701 -480 672.0 -463.5Q643 -447 616.0 -421.0Q589 -395 572.0 -366.5Q555 -338 555 -315Q555 -272 625 -245Q655 -234 668.5 -217.5Q682 -201 682 -169Q682 -129 667.5 -70.0Q653 -11 623.0 54.0Q593 119 548 177Q487 256 409.5 298.5Q332 341 250 341Z"/>
24
+ <path id="stroke-2" class="glyph" d="M686 438 662 411Q760 325 842.0 211.0Q924 97 1007 -39Q1027 -71 1048.0 -101.0Q1069 -131 1089 -157Q1137 -219 1177.5 -251.0Q1218 -283 1254.5 -294.0Q1291 -305 1327 -305Q1341 -305 1353.5 -298.5Q1366 -292 1366 -275Q1366 -243 1348.5 -213.5Q1331 -184 1303.0 -165.5Q1275 -147 1242 -147Q1193 -147 1156.5 -121.5Q1120 -96 1084.0 -47.5Q1048 1 1000 70Q926 177 852.0 271.0Q778 365 686 438Z"/>
25
+ <path id="stroke-3" class="glyph" d="M1172 -570Q1150 -592 1129.5 -610.0Q1109 -628 1089 -644V-653Q1099 -667 1116.5 -687.5Q1134 -708 1151.0 -726.0Q1168 -744 1176 -749H1185Q1209 -734 1226.5 -720.5Q1244 -707 1253 -693Q1264 -707 1278.0 -722.5Q1292 -738 1305.0 -750.5Q1318 -763 1324 -767H1332Q1372 -742 1391.5 -722.5Q1411 -703 1411 -683Q1411 -645 1331 -588H1322Q1305 -605 1289.0 -619.5Q1273 -634 1259 -646Q1243 -614 1181 -570Z"/>
26
+ <path id="stroke-4" class="glyph" d="M1332 -141Q1322 -141 1302.0 -142.5Q1282 -144 1264.0 -145.0Q1246 -146 1242 -146Q1209 -146 1209 -178Q1209 -211 1225.5 -239.5Q1242 -268 1268.5 -286.0Q1295 -304 1326 -304Q1356 -304 1373.5 -301.5Q1391 -299 1416 -299Q1430 -299 1439.5 -292.0Q1449 -285 1449 -267Q1449 -234 1432.5 -205.5Q1416 -177 1389.5 -159.0Q1363 -141 1332 -141Z"/>
27
+ <path id="stroke-5" class="glyph" d="M1333 -140Q1298 -140 1298 -172Q1298 -204 1314.0 -232.5Q1330 -261 1357.0 -279.5Q1384 -298 1417 -298Q1450 -298 1476.0 -309.5Q1502 -321 1533 -364Q1552 -389 1577 -389Q1593 -389 1613 -379Q1635 -369 1654 -369Q1717 -369 1742 -435L1759 -436Q1779 -410 1812 -410Q1857 -410 1870 -468L1891 -469Q1894 -465 1894 -458Q1907 -447 1927 -447Q1941 -447 1948.5 -435.0Q1956 -423 1956 -408Q1956 -377 1936.5 -358.5Q1917 -340 1894 -340Q1884 -340 1875.0 -343.5Q1866 -347 1858 -353Q1845 -333 1825.0 -319.0Q1805 -305 1779 -305Q1748 -305 1725 -327Q1678 -262 1623 -262Q1595 -262 1574 -274Q1552 -286 1544 -286Q1534 -286 1522.5 -271.5Q1511 -257 1497 -237Q1486 -221 1474.5 -207.0Q1463 -193 1450 -182Q1423 -160 1398.0 -150.0Q1373 -140 1333 -140Z"/>
28
+ <path id="stroke-6" class="glyph" d="M1893 -340Q1883 -340 1873.5 -350.5Q1864 -361 1864 -379Q1864 -410 1883.5 -428.0Q1903 -446 1926 -446Q1964 -446 2014.5 -468.0Q2065 -490 2108 -549Q2138 -589 2156.0 -611.0Q2174 -633 2188.5 -643.0Q2203 -653 2220 -657Q2227 -671 2239.0 -687.5Q2251 -704 2263.0 -718.5Q2275 -733 2282 -739H2295Q2305 -715 2314.5 -696.0Q2324 -677 2334 -664Q2348 -647 2353.0 -636.5Q2358 -626 2358 -615Q2358 -593 2342.5 -565.0Q2327 -537 2304.5 -516.5Q2282 -496 2260 -496Q2253 -496 2246.5 -496.5Q2240 -497 2234 -500Q2212 -511 2193.0 -516.5Q2174 -522 2159 -522Q2142 -522 2114.0 -496.0Q2086 -470 2054 -437Q2009 -391 1974.5 -365.5Q1940 -340 1893 -340Z"/>
29
+ </g>
30
+ </svg>
@@ -0,0 +1,14 @@
1
+ <!-- mistri wordmark (مستری), cut from Noto Nastaliq Urdu Bold to pure
2
+ paths. Each stroke is addressable: #stroke-1 through #stroke-6,
3
+ class .glyph, fill inherits currentColor. Edit, recolor, or animate
4
+ per stroke. -->
5
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 -827 2450 1325" role="img" aria-label="mistri">
6
+ <g id="wordmark" fill="#f0f6fc">
7
+ <path id="stroke-1" class="glyph" d="M250 341Q184 341 134.0 309.0Q84 277 56.0 219.5Q28 162 28 88Q28 34 42.5 -37.5Q57 -109 92.5 -194.0Q128 -279 191 -370L217 -355Q170 -273 145.5 -215.0Q121 -157 112.5 -114.0Q104 -71 104 -33Q104 21 126.5 65.5Q149 110 195.5 136.0Q242 162 312 162Q381 162 436.5 139.0Q492 116 531.0 81.5Q570 47 591.0 13.0Q612 -21 612 -42Q612 -62 593.5 -77.0Q575 -92 525 -112Q470 -134 470 -193Q470 -215 479.5 -249.0Q489 -283 505.5 -323.0Q522 -363 543.0 -402.5Q564 -442 586 -476Q630 -544 678.5 -588.0Q727 -632 773 -632Q818 -632 818 -604Q818 -597 813 -584L765 -474L756 -472Q748 -480 725 -480Q701 -480 672.0 -463.5Q643 -447 616.0 -421.0Q589 -395 572.0 -366.5Q555 -338 555 -315Q555 -272 625 -245Q655 -234 668.5 -217.5Q682 -201 682 -169Q682 -129 667.5 -70.0Q653 -11 623.0 54.0Q593 119 548 177Q487 256 409.5 298.5Q332 341 250 341Z"/>
8
+ <path id="stroke-2" class="glyph" d="M686 438 662 411Q760 325 842.0 211.0Q924 97 1007 -39Q1027 -71 1048.0 -101.0Q1069 -131 1089 -157Q1137 -219 1177.5 -251.0Q1218 -283 1254.5 -294.0Q1291 -305 1327 -305Q1341 -305 1353.5 -298.5Q1366 -292 1366 -275Q1366 -243 1348.5 -213.5Q1331 -184 1303.0 -165.5Q1275 -147 1242 -147Q1193 -147 1156.5 -121.5Q1120 -96 1084.0 -47.5Q1048 1 1000 70Q926 177 852.0 271.0Q778 365 686 438Z"/>
9
+ <path id="stroke-3" class="glyph" d="M1172 -570Q1150 -592 1129.5 -610.0Q1109 -628 1089 -644V-653Q1099 -667 1116.5 -687.5Q1134 -708 1151.0 -726.0Q1168 -744 1176 -749H1185Q1209 -734 1226.5 -720.5Q1244 -707 1253 -693Q1264 -707 1278.0 -722.5Q1292 -738 1305.0 -750.5Q1318 -763 1324 -767H1332Q1372 -742 1391.5 -722.5Q1411 -703 1411 -683Q1411 -645 1331 -588H1322Q1305 -605 1289.0 -619.5Q1273 -634 1259 -646Q1243 -614 1181 -570Z"/>
10
+ <path id="stroke-4" class="glyph" d="M1332 -141Q1322 -141 1302.0 -142.5Q1282 -144 1264.0 -145.0Q1246 -146 1242 -146Q1209 -146 1209 -178Q1209 -211 1225.5 -239.5Q1242 -268 1268.5 -286.0Q1295 -304 1326 -304Q1356 -304 1373.5 -301.5Q1391 -299 1416 -299Q1430 -299 1439.5 -292.0Q1449 -285 1449 -267Q1449 -234 1432.5 -205.5Q1416 -177 1389.5 -159.0Q1363 -141 1332 -141Z"/>
11
+ <path id="stroke-5" class="glyph" d="M1333 -140Q1298 -140 1298 -172Q1298 -204 1314.0 -232.5Q1330 -261 1357.0 -279.5Q1384 -298 1417 -298Q1450 -298 1476.0 -309.5Q1502 -321 1533 -364Q1552 -389 1577 -389Q1593 -389 1613 -379Q1635 -369 1654 -369Q1717 -369 1742 -435L1759 -436Q1779 -410 1812 -410Q1857 -410 1870 -468L1891 -469Q1894 -465 1894 -458Q1907 -447 1927 -447Q1941 -447 1948.5 -435.0Q1956 -423 1956 -408Q1956 -377 1936.5 -358.5Q1917 -340 1894 -340Q1884 -340 1875.0 -343.5Q1866 -347 1858 -353Q1845 -333 1825.0 -319.0Q1805 -305 1779 -305Q1748 -305 1725 -327Q1678 -262 1623 -262Q1595 -262 1574 -274Q1552 -286 1544 -286Q1534 -286 1522.5 -271.5Q1511 -257 1497 -237Q1486 -221 1474.5 -207.0Q1463 -193 1450 -182Q1423 -160 1398.0 -150.0Q1373 -140 1333 -140Z"/>
12
+ <path id="stroke-6" class="glyph" d="M1893 -340Q1883 -340 1873.5 -350.5Q1864 -361 1864 -379Q1864 -410 1883.5 -428.0Q1903 -446 1926 -446Q1964 -446 2014.5 -468.0Q2065 -490 2108 -549Q2138 -589 2156.0 -611.0Q2174 -633 2188.5 -643.0Q2203 -653 2220 -657Q2227 -671 2239.0 -687.5Q2251 -704 2263.0 -718.5Q2275 -733 2282 -739H2295Q2305 -715 2314.5 -696.0Q2324 -677 2334 -664Q2348 -647 2353.0 -636.5Q2358 -626 2358 -615Q2358 -593 2342.5 -565.0Q2327 -537 2304.5 -516.5Q2282 -496 2260 -496Q2253 -496 2246.5 -496.5Q2240 -497 2234 -500Q2212 -511 2193.0 -516.5Q2174 -522 2159 -522Q2142 -522 2114.0 -496.0Q2086 -470 2054 -437Q2009 -391 1974.5 -365.5Q1940 -340 1893 -340Z"/>
13
+ </g>
14
+ </svg>
@@ -0,0 +1,14 @@
1
+ <!-- mistri wordmark (مستری), cut from Noto Nastaliq Urdu Bold to pure
2
+ paths. Each stroke is addressable: #stroke-1 through #stroke-6,
3
+ class .glyph, fill inherits currentColor. Edit, recolor, or animate
4
+ per stroke. -->
5
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 -827 2450 1325" role="img" aria-label="mistri">
6
+ <g id="wordmark" fill="#24292f">
7
+ <path id="stroke-1" class="glyph" d="M250 341Q184 341 134.0 309.0Q84 277 56.0 219.5Q28 162 28 88Q28 34 42.5 -37.5Q57 -109 92.5 -194.0Q128 -279 191 -370L217 -355Q170 -273 145.5 -215.0Q121 -157 112.5 -114.0Q104 -71 104 -33Q104 21 126.5 65.5Q149 110 195.5 136.0Q242 162 312 162Q381 162 436.5 139.0Q492 116 531.0 81.5Q570 47 591.0 13.0Q612 -21 612 -42Q612 -62 593.5 -77.0Q575 -92 525 -112Q470 -134 470 -193Q470 -215 479.5 -249.0Q489 -283 505.5 -323.0Q522 -363 543.0 -402.5Q564 -442 586 -476Q630 -544 678.5 -588.0Q727 -632 773 -632Q818 -632 818 -604Q818 -597 813 -584L765 -474L756 -472Q748 -480 725 -480Q701 -480 672.0 -463.5Q643 -447 616.0 -421.0Q589 -395 572.0 -366.5Q555 -338 555 -315Q555 -272 625 -245Q655 -234 668.5 -217.5Q682 -201 682 -169Q682 -129 667.5 -70.0Q653 -11 623.0 54.0Q593 119 548 177Q487 256 409.5 298.5Q332 341 250 341Z"/>
8
+ <path id="stroke-2" class="glyph" d="M686 438 662 411Q760 325 842.0 211.0Q924 97 1007 -39Q1027 -71 1048.0 -101.0Q1069 -131 1089 -157Q1137 -219 1177.5 -251.0Q1218 -283 1254.5 -294.0Q1291 -305 1327 -305Q1341 -305 1353.5 -298.5Q1366 -292 1366 -275Q1366 -243 1348.5 -213.5Q1331 -184 1303.0 -165.5Q1275 -147 1242 -147Q1193 -147 1156.5 -121.5Q1120 -96 1084.0 -47.5Q1048 1 1000 70Q926 177 852.0 271.0Q778 365 686 438Z"/>
9
+ <path id="stroke-3" class="glyph" d="M1172 -570Q1150 -592 1129.5 -610.0Q1109 -628 1089 -644V-653Q1099 -667 1116.5 -687.5Q1134 -708 1151.0 -726.0Q1168 -744 1176 -749H1185Q1209 -734 1226.5 -720.5Q1244 -707 1253 -693Q1264 -707 1278.0 -722.5Q1292 -738 1305.0 -750.5Q1318 -763 1324 -767H1332Q1372 -742 1391.5 -722.5Q1411 -703 1411 -683Q1411 -645 1331 -588H1322Q1305 -605 1289.0 -619.5Q1273 -634 1259 -646Q1243 -614 1181 -570Z"/>
10
+ <path id="stroke-4" class="glyph" d="M1332 -141Q1322 -141 1302.0 -142.5Q1282 -144 1264.0 -145.0Q1246 -146 1242 -146Q1209 -146 1209 -178Q1209 -211 1225.5 -239.5Q1242 -268 1268.5 -286.0Q1295 -304 1326 -304Q1356 -304 1373.5 -301.5Q1391 -299 1416 -299Q1430 -299 1439.5 -292.0Q1449 -285 1449 -267Q1449 -234 1432.5 -205.5Q1416 -177 1389.5 -159.0Q1363 -141 1332 -141Z"/>
11
+ <path id="stroke-5" class="glyph" d="M1333 -140Q1298 -140 1298 -172Q1298 -204 1314.0 -232.5Q1330 -261 1357.0 -279.5Q1384 -298 1417 -298Q1450 -298 1476.0 -309.5Q1502 -321 1533 -364Q1552 -389 1577 -389Q1593 -389 1613 -379Q1635 -369 1654 -369Q1717 -369 1742 -435L1759 -436Q1779 -410 1812 -410Q1857 -410 1870 -468L1891 -469Q1894 -465 1894 -458Q1907 -447 1927 -447Q1941 -447 1948.5 -435.0Q1956 -423 1956 -408Q1956 -377 1936.5 -358.5Q1917 -340 1894 -340Q1884 -340 1875.0 -343.5Q1866 -347 1858 -353Q1845 -333 1825.0 -319.0Q1805 -305 1779 -305Q1748 -305 1725 -327Q1678 -262 1623 -262Q1595 -262 1574 -274Q1552 -286 1544 -286Q1534 -286 1522.5 -271.5Q1511 -257 1497 -237Q1486 -221 1474.5 -207.0Q1463 -193 1450 -182Q1423 -160 1398.0 -150.0Q1373 -140 1333 -140Z"/>
12
+ <path id="stroke-6" class="glyph" d="M1893 -340Q1883 -340 1873.5 -350.5Q1864 -361 1864 -379Q1864 -410 1883.5 -428.0Q1903 -446 1926 -446Q1964 -446 2014.5 -468.0Q2065 -490 2108 -549Q2138 -589 2156.0 -611.0Q2174 -633 2188.5 -643.0Q2203 -653 2220 -657Q2227 -671 2239.0 -687.5Q2251 -704 2263.0 -718.5Q2275 -733 2282 -739H2295Q2305 -715 2314.5 -696.0Q2324 -677 2334 -664Q2348 -647 2353.0 -636.5Q2358 -626 2358 -615Q2358 -593 2342.5 -565.0Q2327 -537 2304.5 -516.5Q2282 -496 2260 -496Q2253 -496 2246.5 -496.5Q2240 -497 2234 -500Q2212 -511 2193.0 -516.5Q2174 -522 2159 -522Q2142 -522 2114.0 -496.0Q2086 -470 2054 -437Q2009 -391 1974.5 -365.5Q1940 -340 1893 -340Z"/>
13
+ </g>
14
+ </svg>
data/assets/logo.svg ADDED
@@ -0,0 +1,14 @@
1
+ <!-- mistri wordmark (مستری), cut from Noto Nastaliq Urdu Bold to pure
2
+ paths. Each stroke is addressable: #stroke-1 through #stroke-6,
3
+ class .glyph, fill inherits currentColor. Edit, recolor, or animate
4
+ per stroke. -->
5
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 -827 2450 1325" role="img" aria-label="mistri">
6
+ <g id="wordmark" fill="currentColor">
7
+ <path id="stroke-1" class="glyph" d="M250 341Q184 341 134.0 309.0Q84 277 56.0 219.5Q28 162 28 88Q28 34 42.5 -37.5Q57 -109 92.5 -194.0Q128 -279 191 -370L217 -355Q170 -273 145.5 -215.0Q121 -157 112.5 -114.0Q104 -71 104 -33Q104 21 126.5 65.5Q149 110 195.5 136.0Q242 162 312 162Q381 162 436.5 139.0Q492 116 531.0 81.5Q570 47 591.0 13.0Q612 -21 612 -42Q612 -62 593.5 -77.0Q575 -92 525 -112Q470 -134 470 -193Q470 -215 479.5 -249.0Q489 -283 505.5 -323.0Q522 -363 543.0 -402.5Q564 -442 586 -476Q630 -544 678.5 -588.0Q727 -632 773 -632Q818 -632 818 -604Q818 -597 813 -584L765 -474L756 -472Q748 -480 725 -480Q701 -480 672.0 -463.5Q643 -447 616.0 -421.0Q589 -395 572.0 -366.5Q555 -338 555 -315Q555 -272 625 -245Q655 -234 668.5 -217.5Q682 -201 682 -169Q682 -129 667.5 -70.0Q653 -11 623.0 54.0Q593 119 548 177Q487 256 409.5 298.5Q332 341 250 341Z"/>
8
+ <path id="stroke-2" class="glyph" d="M686 438 662 411Q760 325 842.0 211.0Q924 97 1007 -39Q1027 -71 1048.0 -101.0Q1069 -131 1089 -157Q1137 -219 1177.5 -251.0Q1218 -283 1254.5 -294.0Q1291 -305 1327 -305Q1341 -305 1353.5 -298.5Q1366 -292 1366 -275Q1366 -243 1348.5 -213.5Q1331 -184 1303.0 -165.5Q1275 -147 1242 -147Q1193 -147 1156.5 -121.5Q1120 -96 1084.0 -47.5Q1048 1 1000 70Q926 177 852.0 271.0Q778 365 686 438Z"/>
9
+ <path id="stroke-3" class="glyph" d="M1172 -570Q1150 -592 1129.5 -610.0Q1109 -628 1089 -644V-653Q1099 -667 1116.5 -687.5Q1134 -708 1151.0 -726.0Q1168 -744 1176 -749H1185Q1209 -734 1226.5 -720.5Q1244 -707 1253 -693Q1264 -707 1278.0 -722.5Q1292 -738 1305.0 -750.5Q1318 -763 1324 -767H1332Q1372 -742 1391.5 -722.5Q1411 -703 1411 -683Q1411 -645 1331 -588H1322Q1305 -605 1289.0 -619.5Q1273 -634 1259 -646Q1243 -614 1181 -570Z"/>
10
+ <path id="stroke-4" class="glyph" d="M1332 -141Q1322 -141 1302.0 -142.5Q1282 -144 1264.0 -145.0Q1246 -146 1242 -146Q1209 -146 1209 -178Q1209 -211 1225.5 -239.5Q1242 -268 1268.5 -286.0Q1295 -304 1326 -304Q1356 -304 1373.5 -301.5Q1391 -299 1416 -299Q1430 -299 1439.5 -292.0Q1449 -285 1449 -267Q1449 -234 1432.5 -205.5Q1416 -177 1389.5 -159.0Q1363 -141 1332 -141Z"/>
11
+ <path id="stroke-5" class="glyph" d="M1333 -140Q1298 -140 1298 -172Q1298 -204 1314.0 -232.5Q1330 -261 1357.0 -279.5Q1384 -298 1417 -298Q1450 -298 1476.0 -309.5Q1502 -321 1533 -364Q1552 -389 1577 -389Q1593 -389 1613 -379Q1635 -369 1654 -369Q1717 -369 1742 -435L1759 -436Q1779 -410 1812 -410Q1857 -410 1870 -468L1891 -469Q1894 -465 1894 -458Q1907 -447 1927 -447Q1941 -447 1948.5 -435.0Q1956 -423 1956 -408Q1956 -377 1936.5 -358.5Q1917 -340 1894 -340Q1884 -340 1875.0 -343.5Q1866 -347 1858 -353Q1845 -333 1825.0 -319.0Q1805 -305 1779 -305Q1748 -305 1725 -327Q1678 -262 1623 -262Q1595 -262 1574 -274Q1552 -286 1544 -286Q1534 -286 1522.5 -271.5Q1511 -257 1497 -237Q1486 -221 1474.5 -207.0Q1463 -193 1450 -182Q1423 -160 1398.0 -150.0Q1373 -140 1333 -140Z"/>
12
+ <path id="stroke-6" class="glyph" d="M1893 -340Q1883 -340 1873.5 -350.5Q1864 -361 1864 -379Q1864 -410 1883.5 -428.0Q1903 -446 1926 -446Q1964 -446 2014.5 -468.0Q2065 -490 2108 -549Q2138 -589 2156.0 -611.0Q2174 -633 2188.5 -643.0Q2203 -653 2220 -657Q2227 -671 2239.0 -687.5Q2251 -704 2263.0 -718.5Q2275 -733 2282 -739H2295Q2305 -715 2314.5 -696.0Q2324 -677 2334 -664Q2348 -647 2353.0 -636.5Q2358 -626 2358 -615Q2358 -593 2342.5 -565.0Q2327 -537 2304.5 -516.5Q2282 -496 2260 -496Q2253 -496 2246.5 -496.5Q2240 -497 2234 -500Q2212 -511 2193.0 -516.5Q2174 -522 2159 -522Q2142 -522 2114.0 -496.0Q2086 -470 2054 -437Q2009 -391 1974.5 -365.5Q1940 -340 1893 -340Z"/>
13
+ </g>
14
+ </svg>
Binary file
data/docs/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # Mistri documentation
2
+
3
+ Mistri is an agent harness for Ruby applications. The
4
+ [project README](../README.md) is the shortest path from installation to a
5
+ working tool-using agent. This directory carries the contracts needed when the
6
+ agent becomes part of a real application.
7
+
8
+ ## Start here
9
+
10
+ 1. Run the [sixty-second example](../README.md#start-in-sixty-seconds).
11
+ 2. Read [Tool contracts](tool-contracts.md) before connecting model output to
12
+ application behavior.
13
+ 3. Read [Sessions and control](sessions.md) before making a run durable or
14
+ accepting approvals from another process.
15
+ 4. Read [Reliability](reliability.md) before a tool performs external writes.
16
+ 5. Check the [upgrade guide](../UPGRADING.md) before changing Mistri versions.
17
+
18
+ ## Guides
19
+
20
+ | Guide | Use it when |
21
+ | --- | --- |
22
+ | [Tool contracts](tool-contracts.md) | Defining schemas, validating arguments, gating tools, returning UI data, or handing off a turn |
23
+ | [Sessions and control](sessions.md) | Choosing a store, resuming, steering, approving, compacting, or rendering transcripts |
24
+ | [Context and workspaces](context-and-workspaces.md) | Loading Skills and Definitions, transforming context, storing host-owned memory, or exposing editable documents |
25
+ | [Sub-agents](sub-agents.md) | Delegating into child sessions, dispatching background workers, or exposing worker controls |
26
+ | [MCP](mcp.md) | Connecting Streamable HTTP or stdio servers, OAuth, private networks, or large results |
27
+ | [Reliability](reliability.md) | Handling retries, events, budgets, response limits, cancellation, concurrency, and side effects |
28
+ | [Upgrade guide](../UPGRADING.md) | Moving an existing host between releases |
29
+ | [Changelog](../CHANGELOG.md) | Reading the exact history of every public behavior change |
30
+
31
+ ## Common paths
32
+
33
+ ### Add a durable conversation
34
+
35
+ Use [`Mistri::Session`](sessions.md#sessions) with a built-in store or a host
36
+ adapter. The store contract is two methods: `append(id, entry)` and `load(id)`.
37
+
38
+ ### Put a human before a write
39
+
40
+ Declare `needs_approval:` on the tool, route the returned call ID to the human,
41
+ append the decision through a bare Session, and rebuild the current tools and
42
+ policy before calling `resume`. The complete flow is in
43
+ [Human approval](sessions.md#human-approval).
44
+
45
+ ### Stream into a web response
46
+
47
+ Pass a block to `run`, or use `Mistri::Sinks::SSE` with any IO-like stream.
48
+ Action Cable is an optional sink, not a framework requirement. See
49
+ [Events and sinks](reliability.md#events-and-sinks).
50
+
51
+ ### Connect an MCP server
52
+
53
+ Build `Mistri::MCP::Client` with exactly one of `url:` or `command:`, then turn
54
+ the remote tools into local tools with `Mistri::MCP.tools`. Start with
55
+ [MCP transports](mcp.md#transports).
56
+
57
+ ### Run work in another agent
58
+
59
+ Use `Mistri::SubAgent` for a fixed specialist or `Mistri::SubAgent.pack` for a
60
+ host-bounded worker pool. Read the [execution modes](sub-agents.md#execution-modes)
61
+ before adding a queue dispatcher.
62
+
63
+ ### Give the model editable context
64
+
65
+ Use Skills for on-demand playbooks, Definitions for host-assembled Agent
66
+ configuration, a Workspace for document tools, or `Mistri::Memory` for one
67
+ host-owned value shared across Sessions. The boundaries are in
68
+ [Context and workspaces](context-and-workspaces.md).
69
+
70
+ ## Framework policy
71
+
72
+ The gem owns mechanism. The host application owns users, authorization,
73
+ business policy, queues, persistence schema, presentation, and the semantics of
74
+ external side effects. Optional adapters are provided for common Rails
75
+ components, but every core contract works without Rails.
76
+
77
+ ## Source landmarks
78
+
79
+ - [`lib/mistri/agent.rb`](../lib/mistri/agent.rb) owns the run loop.
80
+ - [`lib/mistri/session.rb`](../lib/mistri/session.rb) owns the append-only replay
81
+ and approval audit.
82
+ - [`lib/mistri/tool.rb`](../lib/mistri/tool.rb) and
83
+ [`lib/mistri/schema.rb`](../lib/mistri/schema.rb) own tool contracts.
84
+ - [`lib/mistri/mcp/`](../lib/mistri/mcp) contains the MCP client and transports.
85
+
86
+ For contribution setup and quality gates, see
87
+ [`CONTRIBUTING.md`](../CONTRIBUTING.md).
@@ -0,0 +1,378 @@
1
+ # Context and workspaces
2
+
3
+ Mistri keeps several kinds of context separate so the host can give each one
4
+ the right lifetime and authority:
5
+
6
+ | Mechanism | Purpose | Source lifetime |
7
+ | --- | --- | --- |
8
+ | `system:` | Stable instructions for the Agent | Agent instance |
9
+ | `context:` | Host objects available to tool policy and handlers | Agent instance |
10
+ | `skills:` | Host-curated playbooks loaded by the model on demand | Host files or objects |
11
+ | `transform_context:` | Per-turn view of replay history | Agent instance |
12
+ | Session messages | Durable conversation and control facts | Session store |
13
+ | Workspace | Text the built-in document tools can edit | Workspace backend |
14
+ | `Mistri::Memory` | Host-owned knowledge shared across sessions | Host backend |
15
+
16
+ Any model-visible Skill, workspace, or memory tool call and result is still an
17
+ ordinary Session message. Reading external context can therefore copy that
18
+ content into the Session log.
19
+
20
+ Do not put credentials into prompts, Skills, or persisted messages. Pass
21
+ request-scoped identity and services through `context:` and authorize every tool
22
+ at its execution boundary.
23
+
24
+ ## Skills
25
+
26
+ A Skill is a named playbook with a short description and a full Markdown body.
27
+ Only the descriptions join the system prompt. Mistri adds `read_skill`, and the
28
+ model requests a body when the current task needs it.
29
+
30
+ Load either `<root>/<name>/SKILL.md` directories or flat `<root>/<name>.md`
31
+ files:
32
+
33
+ ```ruby
34
+ skills = Mistri::Skills.load("app/agent_skills")
35
+
36
+ agent = Mistri.agent(
37
+ "claude-opus-4-8",
38
+ skills: skills,
39
+ tools: application_tools,
40
+ )
41
+ ```
42
+
43
+ `skills:` also accepts the directory path directly. A Skill file may begin with
44
+ the deliberately small frontmatter contract:
45
+
46
+ ```markdown
47
+ ---
48
+ name: incident-review
49
+ description: Review an incident timeline for missing evidence and unsafe claims.
50
+ ---
51
+
52
+ # Incident review
53
+
54
+ Read the timeline first. Separate observed facts from inference...
55
+ ```
56
+
57
+ Only flat `name` and `description` string fields are interpreted. This is not a
58
+ general YAML configuration surface. Without frontmatter, the file or directory
59
+ name becomes the Skill name.
60
+
61
+ Skill bodies are instructions, not a security boundary. Load only host-reviewed
62
+ files, keep descriptions specific enough for correct selection, and let tool
63
+ authorization enforce what the model may actually do.
64
+
65
+ ## Definitions
66
+
67
+ A Definition keeps an Agent's editable prompt and host vocabulary in one
68
+ Markdown file:
69
+
70
+ ```markdown
71
+ ---
72
+ role: Trip planner
73
+ model: claude-opus-4-8
74
+ tools:
75
+ - search_flights
76
+ - book_hotel
77
+ ---
78
+
79
+ Plan the trip for {first_name}. Confirm constraints before booking.
80
+ ```
81
+
82
+ Load and assemble it through a host registry:
83
+
84
+ ```ruby
85
+ definition = Mistri::Definition.load("app/agents/trip_planner.md")
86
+ tools = definition.tool_names.map { |name| tool_registry.fetch(name) }
87
+
88
+ agent = Mistri.agent(
89
+ definition.model,
90
+ system: definition.render(first_name: traveler.first_name),
91
+ tools: tools,
92
+ )
93
+ ```
94
+
95
+ `render` raises when a placeholder has no value instead of leaking a literal
96
+ `{first_name}` into the prompt. A nil value renders as empty. `role`, `model`,
97
+ and `tools` have convenience readers; other frontmatter remains available
98
+ through `definition.config` for the host to interpret.
99
+
100
+ Mistri never resolves a tool name or arbitrary Definition key by itself. The
101
+ host registry is the authority that turns configuration into application code.
102
+
103
+ ## Application context
104
+
105
+ Pass acting identity and request-scoped services through `context:`:
106
+
107
+ ```ruby
108
+ agent = Mistri.agent(
109
+ "claude-opus-4-8",
110
+ tools: tools,
111
+ context: { actor: current_user, tenant: current_tenant },
112
+ before_tool: lambda { |call, tool_context|
113
+ allowed = policy.allowed?(
114
+ tool_context.app.fetch(:actor),
115
+ call.name,
116
+ call.arguments,
117
+ )
118
+ next if allowed
119
+
120
+ "not authorized"
121
+ },
122
+ )
123
+ ```
124
+
125
+ Handlers that accept a second argument receive the same
126
+ `Mistri::ToolContext`; `context.app` is exactly the host value. This context is
127
+ not persisted. Rebuild it from current identity and policy before resuming a
128
+ persisted Session. Return a sanitized block reason from `before_tool`; an
129
+ unexpected hook exception is also reported to the model and Session, including
130
+ its class and message.
131
+
132
+ ## Per-turn context transforms
133
+
134
+ `transform_context:` accepts one callable or an Array. Before every provider
135
+ request, each callable receives the replay Messages and returns the Messages
136
+ that request should see. The transformed view is not appended to the Session.
137
+
138
+ This seam supports host redaction, retrieval, and deliberate windowing, but it
139
+ is low-level. Preserve message order and every tool-call/result pair. A
140
+ provider will reject a history whose correlation facts were removed.
141
+
142
+ For periodic instruction reinforcement, use the built-in Reminder:
143
+
144
+ ```ruby
145
+ reminder = Mistri::Reminder.every(
146
+ 3,
147
+ "Stay within the approved catalog. Verify availability before claiming it.",
148
+ )
149
+
150
+ agent = Mistri.agent(
151
+ "claude-opus-4-8",
152
+ tools: tools,
153
+ transform_context: reminder,
154
+ )
155
+ ```
156
+
157
+ The reminder counts completed assistant turns, appears only in the provider
158
+ request when due, and never becomes a Session message. Pass `after:` when the
159
+ first reminder should use a different turn boundary.
160
+
161
+ ## Workspaces and document tools
162
+
163
+ A workspace is a four-method port:
164
+
165
+ ```ruby
166
+ read(path) # String or nil
167
+ write(path, content)
168
+ delete(path)
169
+ list(prefix = nil) # sorted path Strings
170
+ ```
171
+
172
+ A backend may additionally claim atomic conditional writes:
173
+
174
+ ```ruby
175
+ atomic_writes? # true
176
+ snapshot(path) # Mistri::Workspace::Snapshot or nil
177
+ compare_and_write(path, content, expected_revision:) # Snapshot
178
+ ```
179
+
180
+ `Snapshot#revision` is an opaque, nonempty String of at most 256 bytes for those
181
+ exact content bytes; Snapshot owns frozen base-String copies of both values.
182
+ `expected_revision: nil` means create only and must conflict if the document
183
+ exists. A non-nil revision must match the latest Snapshot for that path. On
184
+ success, `compare_and_write` returns the content and revision actually committed
185
+ by storage. A conditional mismatch raises `Mistri::WorkspaceConflictError`
186
+ without writing.
187
+
188
+ `edit_file` retries only that conflict, at most three total attempts, and
189
+ reapplies the anchored replacement to each fresh snapshot. Concurrent changes
190
+ outside the target therefore survive; a target that no longer matches becomes
191
+ the ordinary actionable edit failure. Save, callback, validation, connection,
192
+ and other errors are never retried. If storage transforms the committed bytes,
193
+ the write is not retried or falsely reported as the exact requested
194
+ replacement: the model receives an error-marked result saying the write
195
+ committed and must read the document again. Custom four-method workspaces keep
196
+ their existing behavior unless they explicitly return true from
197
+ `atomic_writes?` and implement both methods.
198
+
199
+ The conditional protocol is used by `edit_file`. `write_file` remains an
200
+ intentional blind whole-document replacement, and deletion has no conditional
201
+ form. When concurrent writers must preserve unrelated changes, use an anchored
202
+ edit or provide a host tool with a stronger domain contract.
203
+
204
+ Bind one to the built-in read, write, edit, find, and list tools:
205
+
206
+ ```ruby
207
+ workspace = Mistri::Workspace::Memory.new
208
+ workspace.write("brief.md", "# Launch brief\n")
209
+
210
+ agent = Mistri.agent(
211
+ "claude-opus-4-8",
212
+ tools: Mistri::Tools.files(workspace),
213
+ system: "Read before editing. Keep the brief concise.",
214
+ )
215
+ ```
216
+
217
+ The tool names deliberately use the familiar file vocabulary even when the
218
+ backend is a database row or host callback.
219
+
220
+ ### Memory workspace
221
+
222
+ `Mistri::Workspace::Memory` is a mutex-protected document map for tests and
223
+ ephemeral work. Its conditional writes are atomic between threads sharing the
224
+ instance. It is not durable across processes.
225
+
226
+ ### Directory workspace
227
+
228
+ ```ruby
229
+ workspace = Mistri::Workspace::Directory.new("tmp/agent-workspace")
230
+ ```
231
+
232
+ Directory expands model paths under one canonical root, rejects lexical escape,
233
+ refuses traversal through existing file or directory symlinks, and omits
234
+ symlinked entries from listings. The host must keep the root and its tree stable
235
+ for the workspace instance's lifetime. This is not an operating-system sandbox
236
+ against a concurrent process changing filesystem entries, and Directory does
237
+ not claim atomic conditional writes. A correct cross-process implementation
238
+ needs a stable lock namespace and atomic replacement, not a Ruby mutex.
239
+
240
+ Use a dedicated root with host-owned permissions. Do not point model-controlled
241
+ tools at an application checkout, credential directory, or shared mutable tree.
242
+
243
+ ### Single-value workspace
244
+
245
+ Wrap one host-owned value, such as a page draft or database column:
246
+
247
+ ```ruby
248
+ workspace = Mistri::Workspace::Single.new(
249
+ path: "page.html",
250
+ read: -> { page.reload.draft_html },
251
+ write: ->(html) { page.update!(draft_html: html) },
252
+ synchronize: lambda do |&operation|
253
+ if page.class.connection.transaction_open?
254
+ raise Mistri::ConfigurationError, "page editor must run outside a transaction"
255
+ end
256
+
257
+ page.with_lock(&operation)
258
+ end,
259
+ )
260
+ ```
261
+
262
+ The value can be read and rewritten but not deleted. `synchronize:` is
263
+ optional; when supplied, Mistri performs the revision check and write inside
264
+ that host boundary, so `page.with_lock` makes anchored edits atomic across
265
+ application processes. The raw callbacks still own validation, authorization,
266
+ and any database normalization. Without `synchronize:`, Single retains the
267
+ legacy four-method behavior and makes no atomicity claim.
268
+
269
+ `synchronize:` is an executable host assertion, not merely a mutex callback:
270
+ it must cover the current read, conditional check, write, and committed read
271
+ across every writer that claims the same guarantee. For an Active Record value,
272
+ it must also refuse an already-open host transaction as above. Under MySQL's
273
+ repeatable-read default, joining an earlier transaction can make an ordinary
274
+ reload observe stale bytes even after a locking read.
275
+
276
+ ### Active Record workspace
277
+
278
+ The optional adapter is not loaded by `require "mistri"`:
279
+
280
+ ```ruby
281
+ require "mistri/workspace/active_record"
282
+
283
+ workspace = Mistri::Workspace::ActiveRecord.new(
284
+ AgentDocument,
285
+ scope: { tenant_id: current_tenant.id },
286
+ )
287
+ ```
288
+
289
+ The host model needs a non-null primary key, non-null `path` and `content`
290
+ columns, non-null columns for every scope key, and a non-partial unique index
291
+ whose columns are exactly the scope plus `path`. Scope values must be singular
292
+ String, Symbol, Numeric, Boolean, Time, or Date equality values, not collections,
293
+ Ranges, subqueries, custom mutable objects, or nil. The adapter verifies the
294
+ model primary key against the database catalog and proves the remaining schema
295
+ facts before claiming atomic writes. It uses the host's Active Record
296
+ connection; Mistri does not require Rails or Active Record as a runtime
297
+ dependency. On PostgreSQL, and on MySQL2 or Trilogy when the table is InnoDB,
298
+ anchored edits use an exact-byte content revision, then a short transaction and
299
+ locking read before `save!`; validations and callbacks still run, and no
300
+ `lock_version` column is required. MyISAM and unrecognized storage engines, as
301
+ well as other adapters, retain the legacy four-method behavior rather than
302
+ advertise an unproved lock. Create-only conditional writes rely on the verified
303
+ unique scope/path index. The adapter copies and freezes the scope Hash plus
304
+ nested Hash, Array, Range, and String values at construction; atomic mode's
305
+ narrower scalar contract keeps its tenant identity stable.
306
+
307
+ Storage and schema capability are inspected on the first capability check and
308
+ cached thereafter, off the edit path. Bind the workspace before concurrent use,
309
+ keep its model on the same database role and shard, and do not change its table
310
+ or indexes during the workspace's lifetime.
311
+
312
+ Unsupported storage deliberately keeps the adapter's legacy read/write behavior.
313
+ If lost-update protection is mandatory for your host, assert the capability at
314
+ boot instead of accepting that fallback:
315
+
316
+ ```ruby
317
+ raise "workspace is not atomic" unless workspace.atomic_writes?
318
+ ```
319
+
320
+ Build and run the Agent outside any host Active Record transaction. The adapter
321
+ rejects atomic snapshots and writes when its connection already has an open
322
+ transaction: joining it would let MySQL repeatable-read snapshots stay stale
323
+ across a retry and would extend Mistri's row lock to a boundary it does not own.
324
+
325
+ ## Durable memory across sessions
326
+
327
+ `Mistri::Memory` is one replaceable text value stored wherever the host chooses:
328
+
329
+ ```ruby
330
+ memory = Mistri::Memory.new(
331
+ read: -> { organization.agent_memory.to_s },
332
+ write: ->(text) { organization.update!(agent_memory: text) },
333
+ )
334
+
335
+ agent = Mistri.agent(
336
+ "claude-opus-4-8",
337
+ tools: Mistri::Tools.memory(memory),
338
+ )
339
+ ```
340
+
341
+ The generated `read_memory` and `update_memory` tools make the model read the
342
+ current value and replace it as one coherent document. The host decides scope,
343
+ authorization, size, retention, and conflict handling. Expose only
344
+ `read_memory` when writes are not allowed; when a memory update requires human
345
+ approval, build an equivalent gated Tool around `Memory#replace`. The helper's
346
+ `update_memory` tool is ungated. Memory is not a hidden vector store. Its
347
+ backing value lives outside the Session, but `read_memory` copies the value into
348
+ a persisted tool result and `update_memory` persists the replacement text in
349
+ the tool call.
350
+
351
+ ## Concurrency and delegation
352
+
353
+ Workspace thread safety is backend-defined. Memory supplies in-process atomic
354
+ writes; supported Active Record storage supplies scoped row-locking semantics;
355
+ Single does so only when `synchronize:` satisfies the contract above. Directory
356
+ and legacy custom workspaces do not turn host resources into transactions.
357
+ Blind writers that ignore the same database locking discipline can still
358
+ overwrite a later committed value; the host owns those external write paths and
359
+ authorization.
360
+
361
+ Sub-agent tools are ordinary Ruby objects and may close over a workspace. A
362
+ dispatcher therefore requires a host-owned runtime factory; the model has no
363
+ workspace selector. The factory receives the immutable dispatch spec and
364
+ constructs the provider, tools, and workspace inside the worker. Mistri
365
+ enforces the spec's exact tool-name grant, while the host remains responsible
366
+ for whether the reconstructed backend is fresh, shared, transactional, or
367
+ actually isolated. Derive durable workspace scope from trusted identifiers such
368
+ as the child Session ID, reopen that same scope on queue retry, and release
369
+ per-child resources through Runtime's `cleanup:` hook. See
370
+ [Sub-agents](sub-agents.md#execution-modes).
371
+
372
+ ## Related guides
373
+
374
+ - [Tool contracts](tool-contracts.md)
375
+ - [Sessions and control](sessions.md)
376
+ - [Sub-agents](sub-agents.md)
377
+ - [Reliability](reliability.md)
378
+ - [Upgrading](../UPGRADING.md)