agents-city 0.3.0-beta.21

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 (440) hide show
  1. package/.claude-plugin/marketplace.json +16 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +5 -0
  4. package/README.es.md +2021 -0
  5. package/README.md +1999 -0
  6. package/benchmarks/committee/README.md +39 -0
  7. package/benchmarks/committee/metrics.py +51 -0
  8. package/benchmarks/committee/run.py +64 -0
  9. package/benchmarks/committee/traces/01-single.json +8 -0
  10. package/benchmarks/committee/traces/02-mesh.json +16 -0
  11. package/benchmarks/committee/traces/03-chair.json +15 -0
  12. package/benchmarks/latency/README.md +35 -0
  13. package/benchmarks/latency/fake-claude-cli.mjs +119 -0
  14. package/benchmarks/latency/fake-native-server.mjs +340 -0
  15. package/benchmarks/latency/fake-tui.py +91 -0
  16. package/benchmarks/latency/live.py +380 -0
  17. package/benchmarks/stress/README.md +20 -0
  18. package/benchmarks/stress/run.py +487 -0
  19. package/bin/agents +4 -0
  20. package/bin/agents-city.js +116 -0
  21. package/bin/benchmark +17 -0
  22. package/bin/bus +6 -0
  23. package/bin/cities +3 -0
  24. package/bin/city +86 -0
  25. package/bin/committee +6 -0
  26. package/bin/demo +200 -0
  27. package/bin/exit +5 -0
  28. package/bin/hall +73 -0
  29. package/bin/hall.html +426 -0
  30. package/bin/logs +3 -0
  31. package/bin/report.py +14 -0
  32. package/bin/reset +3 -0
  33. package/bin/road +3 -0
  34. package/bin/seat +10 -0
  35. package/bin/serve.py +1428 -0
  36. package/bin/setup.py +753 -0
  37. package/bin/skills +3 -0
  38. package/bin/test +131 -0
  39. package/bin/test-adapter.py +183 -0
  40. package/bin/test-admision.py +99 -0
  41. package/bin/test-avatar.py +58 -0
  42. package/bin/test-benchmark.py +40 -0
  43. package/bin/test-broker.py +334 -0
  44. package/bin/test-cage.py +206 -0
  45. package/bin/test-card.py +402 -0
  46. package/bin/test-channel.py +379 -0
  47. package/bin/test-cities.py +453 -0
  48. package/bin/test-claude-runtime.py +252 -0
  49. package/bin/test-committee.py +340 -0
  50. package/bin/test-contracts.py +544 -0
  51. package/bin/test-crecimiento.py +164 -0
  52. package/bin/test-demo.py +308 -0
  53. package/bin/test-doctor.py +106 -0
  54. package/bin/test-domains.py +151 -0
  55. package/bin/test-evidencia.py +50 -0
  56. package/bin/test-exit.py +128 -0
  57. package/bin/test-hall-protocol.py +93 -0
  58. package/bin/test-launch.py +169 -0
  59. package/bin/test-live-feed.py +508 -0
  60. package/bin/test-pairing.py +99 -0
  61. package/bin/test-parcels.py +283 -0
  62. package/bin/test-runtime-failures.py +252 -0
  63. package/bin/test-runtime-ui.py +488 -0
  64. package/bin/test-runtime.py +268 -0
  65. package/bin/test-rutas.py +80 -0
  66. package/bin/test-seat.py +1386 -0
  67. package/bin/test-security.py +150 -0
  68. package/bin/test-serve.py +1057 -0
  69. package/bin/test-stress.py +64 -0
  70. package/bin/test-widgets.py +143 -0
  71. package/bin/test-workspace.py +188 -0
  72. package/bin/testlib.py +95 -0
  73. package/bin/tokens.py +14 -0
  74. package/bus/scripts/deploy.sh +60 -0
  75. package/bus/scripts/mint-token.sh +68 -0
  76. package/bus/scripts/setup-dev.sh +79 -0
  77. package/bus/scripts/test-channel.ts +8 -0
  78. package/bus/scripts/test-hub.ts +266 -0
  79. package/bus/scripts/test-local.sh +40 -0
  80. package/bus/scripts/test-queue.ts +132 -0
  81. package/bus/worker/package-lock.json +1971 -0
  82. package/bus/worker/package.json +17 -0
  83. package/bus/worker/src/index.ts +485 -0
  84. package/bus/worker/tsconfig.json +13 -0
  85. package/bus/worker/wrangler.toml +32 -0
  86. package/city/oven/README.md +59 -0
  87. package/city/oven/collect.py +43 -0
  88. package/city/oven/oven.html +4 -0
  89. package/city/scripts/history.py +120 -0
  90. package/city/scripts/seed.py +442 -0
  91. package/city/web/assets/modelos/License.txt +28 -0
  92. package/city/web/assets/modelos/PROCEDENCIA.txt +3 -0
  93. package/city/web/assets/modelos/building-a.glb +0 -0
  94. package/city/web/assets/modelos/building-b.glb +0 -0
  95. package/city/web/assets/modelos/building-c.glb +0 -0
  96. package/city/web/assets/modelos/building-d.glb +0 -0
  97. package/city/web/assets/modelos/building-e.glb +0 -0
  98. package/city/web/assets/modelos/building-f.glb +0 -0
  99. package/city/web/assets/modelos/building-g.glb +0 -0
  100. package/city/web/assets/modelos/building-h.glb +0 -0
  101. package/city/web/assets/sprites/building-a.png +0 -0
  102. package/city/web/assets/sprites/building-b.png +0 -0
  103. package/city/web/assets/sprites/building-c.png +0 -0
  104. package/city/web/assets/sprites/building-d.png +0 -0
  105. package/city/web/assets/sprites/building-e.png +0 -0
  106. package/city/web/assets/sprites/building-f.png +0 -0
  107. package/city/web/assets/sprites/building-g.png +0 -0
  108. package/city/web/assets/sprites/building-h.png +0 -0
  109. package/city/web/assets/sprites/building-i.png +0 -0
  110. package/city/web/assets/sprites/building-j.png +0 -0
  111. package/city/web/assets/sprites/building-k.png +0 -0
  112. package/city/web/assets/sprites/building-l.png +0 -0
  113. package/city/web/assets/sprites/building-m.png +0 -0
  114. package/city/web/assets/sprites/building-n.png +0 -0
  115. package/city/web/assets/sprites/building-skyscraper-a.png +0 -0
  116. package/city/web/assets/sprites/building-skyscraper-b.png +0 -0
  117. package/city/web/assets/sprites/building-skyscraper-c.png +0 -0
  118. package/city/web/assets/sprites/building-skyscraper-d.png +0 -0
  119. package/city/web/assets/sprites/building-skyscraper-e.png +0 -0
  120. package/city/web/assets/sprites/catalogo.json +47 -0
  121. package/city/web/assets/sprites/detail-awning-wide.png +0 -0
  122. package/city/web/assets/sprites/detail-awning.png +0 -0
  123. package/city/web/assets/sprites/detail-overhang-wide.png +0 -0
  124. package/city/web/assets/sprites/detail-overhang.png +0 -0
  125. package/city/web/assets/sprites/detail-parasol-a.png +0 -0
  126. package/city/web/assets/sprites/detail-parasol-b.png +0 -0
  127. package/city/web/assets/sprites/low-detail-building-a.png +0 -0
  128. package/city/web/assets/sprites/low-detail-building-b.png +0 -0
  129. package/city/web/assets/sprites/low-detail-building-c.png +0 -0
  130. package/city/web/assets/sprites/low-detail-building-d.png +0 -0
  131. package/city/web/assets/sprites/low-detail-building-e.png +0 -0
  132. package/city/web/assets/sprites/low-detail-building-f.png +0 -0
  133. package/city/web/assets/sprites/low-detail-building-g.png +0 -0
  134. package/city/web/assets/sprites/low-detail-building-h.png +0 -0
  135. package/city/web/assets/sprites/low-detail-building-i.png +0 -0
  136. package/city/web/assets/sprites/low-detail-building-j.png +0 -0
  137. package/city/web/assets/sprites/low-detail-building-k.png +0 -0
  138. package/city/web/assets/sprites/low-detail-building-l.png +0 -0
  139. package/city/web/assets/sprites/low-detail-building-m.png +0 -0
  140. package/city/web/assets/sprites/low-detail-building-n.png +0 -0
  141. package/city/web/assets/sprites/low-detail-building-wide-a.png +0 -0
  142. package/city/web/assets/sprites/low-detail-building-wide-b.png +0 -0
  143. package/city/web/assets/sprites/medidas.json +248 -0
  144. package/city/web/dist/city.js +939 -0
  145. package/city/web/dist/index.html +294 -0
  146. package/city/web/dist/modelos/License.txt +28 -0
  147. package/city/web/dist/modelos/PROCEDENCIA.txt +3 -0
  148. package/city/web/dist/modelos/building-a.glb +0 -0
  149. package/city/web/dist/modelos/building-b.glb +0 -0
  150. package/city/web/dist/modelos/building-c.glb +0 -0
  151. package/city/web/dist/modelos/building-d.glb +0 -0
  152. package/city/web/dist/modelos/building-e.glb +0 -0
  153. package/city/web/dist/modelos/building-f.glb +0 -0
  154. package/city/web/dist/modelos/building-g.glb +0 -0
  155. package/city/web/dist/modelos/building-h.glb +0 -0
  156. package/city/web/dist/sprites/building-a.png +0 -0
  157. package/city/web/dist/sprites/building-b.png +0 -0
  158. package/city/web/dist/sprites/building-c.png +0 -0
  159. package/city/web/dist/sprites/building-d.png +0 -0
  160. package/city/web/dist/sprites/building-e.png +0 -0
  161. package/city/web/dist/sprites/building-f.png +0 -0
  162. package/city/web/dist/sprites/building-g.png +0 -0
  163. package/city/web/dist/sprites/building-h.png +0 -0
  164. package/city/web/dist/sprites/building-i.png +0 -0
  165. package/city/web/dist/sprites/building-j.png +0 -0
  166. package/city/web/dist/sprites/building-k.png +0 -0
  167. package/city/web/dist/sprites/building-l.png +0 -0
  168. package/city/web/dist/sprites/building-m.png +0 -0
  169. package/city/web/dist/sprites/building-n.png +0 -0
  170. package/city/web/dist/sprites/building-skyscraper-a.png +0 -0
  171. package/city/web/dist/sprites/building-skyscraper-b.png +0 -0
  172. package/city/web/dist/sprites/building-skyscraper-c.png +0 -0
  173. package/city/web/dist/sprites/building-skyscraper-d.png +0 -0
  174. package/city/web/dist/sprites/building-skyscraper-e.png +0 -0
  175. package/city/web/dist/sprites/catalogo.json +47 -0
  176. package/city/web/dist/sprites/detail-awning-wide.png +0 -0
  177. package/city/web/dist/sprites/detail-awning.png +0 -0
  178. package/city/web/dist/sprites/detail-overhang-wide.png +0 -0
  179. package/city/web/dist/sprites/detail-overhang.png +0 -0
  180. package/city/web/dist/sprites/detail-parasol-a.png +0 -0
  181. package/city/web/dist/sprites/detail-parasol-b.png +0 -0
  182. package/city/web/dist/sprites/low-detail-building-a.png +0 -0
  183. package/city/web/dist/sprites/low-detail-building-b.png +0 -0
  184. package/city/web/dist/sprites/low-detail-building-c.png +0 -0
  185. package/city/web/dist/sprites/low-detail-building-d.png +0 -0
  186. package/city/web/dist/sprites/low-detail-building-e.png +0 -0
  187. package/city/web/dist/sprites/low-detail-building-f.png +0 -0
  188. package/city/web/dist/sprites/low-detail-building-g.png +0 -0
  189. package/city/web/dist/sprites/low-detail-building-h.png +0 -0
  190. package/city/web/dist/sprites/low-detail-building-i.png +0 -0
  191. package/city/web/dist/sprites/low-detail-building-j.png +0 -0
  192. package/city/web/dist/sprites/low-detail-building-k.png +0 -0
  193. package/city/web/dist/sprites/low-detail-building-l.png +0 -0
  194. package/city/web/dist/sprites/low-detail-building-m.png +0 -0
  195. package/city/web/dist/sprites/low-detail-building-n.png +0 -0
  196. package/city/web/dist/sprites/low-detail-building-wide-a.png +0 -0
  197. package/city/web/dist/sprites/low-detail-building-wide-b.png +0 -0
  198. package/city/web/dist/sprites/medidas.json +248 -0
  199. package/city/web/dist-hall/hall.js +264 -0
  200. package/city/web/index.html +294 -0
  201. package/city/web/package.json +18 -0
  202. package/city/web/sella.py +21 -0
  203. package/city/web/src/activity-actors.ts +47 -0
  204. package/city/web/src/activity.ts +172 -0
  205. package/city/web/src/ayuntamiento.ts +580 -0
  206. package/city/web/src/draw.ts +640 -0
  207. package/city/web/src/game-speech.ts +79 -0
  208. package/city/web/src/hall.ts +1867 -0
  209. package/city/web/src/main.ts +2225 -0
  210. package/city/web/src/oven.ts +124 -0
  211. package/city/web/src/people.ts +244 -0
  212. package/city/web/src/presencia.ts +193 -0
  213. package/city/web/src/puertas.ts +180 -0
  214. package/city/web/src/repo-roles.ts +59 -0
  215. package/city/worker/package.json +16 -0
  216. package/city/worker/schema.sql +117 -0
  217. package/city/worker/src/index.ts +702 -0
  218. package/city/worker/src/square.ts +218 -0
  219. package/city/worker/wrangler.toml +58 -0
  220. package/demo/ada.md +50 -0
  221. package/demo/bruno.md +62 -0
  222. package/demo/camila.md +56 -0
  223. package/demo/city/ada.md +40 -0
  224. package/demo/city/city.yml +8 -0
  225. package/demo/city/parcels.yml +15 -0
  226. package/demo/city/roads.json +4 -0
  227. package/demo/city/units.yml +10 -0
  228. package/demo/clinica/city.yml +8 -0
  229. package/demo/clinica/parcels.yml +15 -0
  230. package/demo/clinica/roads.json +4 -0
  231. package/demo/clinica/units.yml +9 -0
  232. package/demo/clinica/vera.md +46 -0
  233. package/demo/dante.md +54 -0
  234. package/demo/despacho/city.yml +8 -0
  235. package/demo/despacho/marta.md +46 -0
  236. package/demo/despacho/parcels.yml +15 -0
  237. package/demo/despacho/roads.json +4 -0
  238. package/demo/despacho/units.yml +9 -0
  239. package/demo/elsa.md +50 -0
  240. package/demo/farid.md +52 -0
  241. package/demo/greta.md +50 -0
  242. package/demo/hugo.md +49 -0
  243. package/demo/iris.md +49 -0
  244. package/demo/jonas.md +51 -0
  245. package/demo/kira.md +51 -0
  246. package/demo/luca.md +49 -0
  247. package/demo/parcels.yml +92 -0
  248. package/demo/seed.py +20 -0
  249. package/demo/show.py +161 -0
  250. package/demo/stories.py +824 -0
  251. package/demo/units.yml +33 -0
  252. package/docs/agents-first.md +99 -0
  253. package/docs/glossary.md +55 -0
  254. package/docs/map-live-layers.md +73 -0
  255. package/docs/security.md +231 -0
  256. package/docs/self-host.md +151 -0
  257. package/docs/testing.md +205 -0
  258. package/package.json +73 -0
  259. package/plugin/.claude-plugin/plugin.json +62 -0
  260. package/plugin/.mcp.json +15 -0
  261. package/plugin/channel/activity-cli.ts +16 -0
  262. package/plugin/channel/adapter-prompts.ts +113 -0
  263. package/plugin/channel/adapter.js +4292 -0
  264. package/plugin/channel/adapter.ts +133 -0
  265. package/plugin/channel/bus.js +20188 -0
  266. package/plugin/channel/bus.ts +131 -0
  267. package/plugin/channel/city-config.ts +125 -0
  268. package/plugin/channel/claude-channel.ts +58 -0
  269. package/plugin/channel/cli-args.ts +75 -0
  270. package/plugin/channel/client.js +4200 -0
  271. package/plugin/channel/client.ts +38 -0
  272. package/plugin/channel/committee/activity.ts +256 -0
  273. package/plugin/channel/committee/collection.ts +154 -0
  274. package/plugin/channel/committee/decision.ts +175 -0
  275. package/plugin/channel/committee/floor.ts +135 -0
  276. package/plugin/channel/committee/guards.ts +30 -0
  277. package/plugin/channel/committee/history.ts +38 -0
  278. package/plugin/channel/committee/render.ts +102 -0
  279. package/plugin/channel/committee/service.ts +72 -0
  280. package/plugin/channel/committee/storage.ts +92 -0
  281. package/plugin/channel/committee/types.ts +163 -0
  282. package/plugin/channel/committee/view.ts +63 -0
  283. package/plugin/channel/committee-cli.ts +109 -0
  284. package/plugin/channel/delivery-metrics.ts +61 -0
  285. package/plugin/channel/delivery-queue.ts +118 -0
  286. package/plugin/channel/hub/activity-controller.ts +72 -0
  287. package/plugin/channel/hub/activity-feed.ts +169 -0
  288. package/plugin/channel/hub/committee-controller.ts +60 -0
  289. package/plugin/channel/hub/connections.ts +48 -0
  290. package/plugin/channel/hub/diagnostics.ts +67 -0
  291. package/plugin/channel/hub/envelope-validity.ts +109 -0
  292. package/plugin/channel/hub/envelopes.ts +66 -0
  293. package/plugin/channel/hub/lifecycle.ts +91 -0
  294. package/plugin/channel/hub/local-roads.ts +89 -0
  295. package/plugin/channel/hub/remote-roads.ts +165 -0
  296. package/plugin/channel/hub/road-controller.ts +99 -0
  297. package/plugin/channel/hub-client.ts +149 -0
  298. package/plugin/channel/local-hub.js +5995 -0
  299. package/plugin/channel/local-hub.ts +349 -0
  300. package/plugin/channel/map-reporter.ts +48 -0
  301. package/plugin/channel/package-lock.json +1731 -0
  302. package/plugin/channel/package.json +19 -0
  303. package/plugin/channel/protocol.ts +98 -0
  304. package/plugin/channel/road-cli.ts +18 -0
  305. package/plugin/channel/run.sh +15 -0
  306. package/plugin/channel/runtime/claude.ts +458 -0
  307. package/plugin/channel/runtime/codex-config.ts +77 -0
  308. package/plugin/channel/runtime/codex.ts +669 -0
  309. package/plugin/channel/runtime/command.ts +62 -0
  310. package/plugin/channel/runtime/factory.ts +15 -0
  311. package/plugin/channel/runtime/json-rpc.ts +154 -0
  312. package/plugin/channel/runtime/kimi.ts +277 -0
  313. package/plugin/channel/runtime/opencode.ts +248 -0
  314. package/plugin/channel/runtime/process.ts +84 -0
  315. package/plugin/channel/runtime/types.ts +45 -0
  316. package/plugin/channel/runtime-files.ts +73 -0
  317. package/plugin/channel/runtime-gateway.js +6396 -0
  318. package/plugin/channel/runtime-gateway.ts +358 -0
  319. package/plugin/channel/runtime-metrics.ts +52 -0
  320. package/plugin/channel/runtime-subscription.ts +180 -0
  321. package/plugin/channel/terminal-delivery.ts +131 -0
  322. package/plugin/channel/untrusted.ts +60 -0
  323. package/plugin/commands/committee.md +31 -0
  324. package/plugin/commands/exit.md +32 -0
  325. package/plugin/commands/goals.md +20 -0
  326. package/plugin/commands/join.md +18 -0
  327. package/plugin/commands/notice.md +20 -0
  328. package/plugin/commands/propose.md +19 -0
  329. package/plugin/commands/round.md +17 -0
  330. package/plugin/commands/session.md +22 -0
  331. package/plugin/commands/settings.md +25 -0
  332. package/plugin/commands/setup.md +21 -0
  333. package/plugin/commands/team.md +23 -0
  334. package/plugin/domains/custom.md +27 -0
  335. package/plugin/domains/finance.md +31 -0
  336. package/plugin/domains/healthcare.md +34 -0
  337. package/plugin/domains/legal.md +31 -0
  338. package/plugin/domains/marketing.md +33 -0
  339. package/plugin/domains/operations.md +31 -0
  340. package/plugin/domains/research.md +32 -0
  341. package/plugin/domains/sales.md +30 -0
  342. package/plugin/domains/software.md +37 -0
  343. package/plugin/hooks/activity.sh +6 -0
  344. package/plugin/hooks/digging.sh +68 -0
  345. package/plugin/hooks/growth.sh +42 -0
  346. package/plugin/hooks/hooks.json +106 -0
  347. package/plugin/hooks/notice-on-pr.sh +35 -0
  348. package/plugin/hooks/notice-on-stop.sh +80 -0
  349. package/plugin/hooks/notice-pending.sh +58 -0
  350. package/plugin/hooks/solo-en-ciudad.sh +23 -0
  351. package/plugin/hooks/tokens.sh +47 -0
  352. package/plugin/roles/examples/account-executive.md +25 -0
  353. package/plugin/roles/examples/ai-manager.md +34 -0
  354. package/plugin/roles/examples/associate.md +24 -0
  355. package/plugin/roles/examples/brand-lead.md +20 -0
  356. package/plugin/roles/examples/cfo.md +20 -0
  357. package/plugin/roles/examples/city-lead.md +25 -0
  358. package/plugin/roles/examples/clinical-director.md +26 -0
  359. package/plugin/roles/examples/clinical-ops.md +25 -0
  360. package/plugin/roles/examples/clinician.md +25 -0
  361. package/plugin/roles/examples/compliance.md +27 -0
  362. package/plugin/roles/examples/content.md +24 -0
  363. package/plugin/roles/examples/controller.md +27 -0
  364. package/plugin/roles/examples/cpto.md +41 -0
  365. package/plugin/roles/examples/customer-success.md +25 -0
  366. package/plugin/roles/examples/data-engineer.md +37 -0
  367. package/plugin/roles/examples/data.md +60 -0
  368. package/plugin/roles/examples/dev.md +37 -0
  369. package/plugin/roles/examples/devops.md +39 -0
  370. package/plugin/roles/examples/enablement.md +24 -0
  371. package/plugin/roles/examples/ethics.md +24 -0
  372. package/plugin/roles/examples/fin-analytics.md +27 -0
  373. package/plugin/roles/examples/health-compliance.md +25 -0
  374. package/plugin/roles/examples/health-data.md +26 -0
  375. package/plugin/roles/examples/knowledge.md +25 -0
  376. package/plugin/roles/examples/lifecycle.md +26 -0
  377. package/plugin/roles/examples/llm-engineer.md +34 -0
  378. package/plugin/roles/examples/managing-partner.md +20 -0
  379. package/plugin/roles/examples/methods.md +23 -0
  380. package/plugin/roles/examples/operations-lead.md +23 -0
  381. package/plugin/roles/examples/ops.md +24 -0
  382. package/plugin/roles/examples/patient-safety.md +26 -0
  383. package/plugin/roles/examples/performance.md +27 -0
  384. package/plugin/roles/examples/po.md +36 -0
  385. package/plugin/roles/examples/process-owner.md +23 -0
  386. package/plugin/roles/examples/product-design.md +39 -0
  387. package/plugin/roles/examples/program-manager.md +24 -0
  388. package/plugin/roles/examples/quality.md +25 -0
  389. package/plugin/roles/examples/research-director.md +24 -0
  390. package/plugin/roles/examples/research-ops.md +25 -0
  391. package/plugin/roles/examples/researcher.md +25 -0
  392. package/plugin/roles/examples/revenue-lead.md +25 -0
  393. package/plugin/roles/examples/revops.md +26 -0
  394. package/plugin/roles/examples/seo.md +27 -0
  395. package/plugin/roles/examples/specialist.md +24 -0
  396. package/plugin/scripts/admision.py +165 -0
  397. package/plugin/scripts/apaga.py +232 -0
  398. package/plugin/scripts/avatar.py +211 -0
  399. package/plugin/scripts/broker.py +531 -0
  400. package/plugin/scripts/cage.py +252 -0
  401. package/plugin/scripts/capabilities.py +197 -0
  402. package/plugin/scripts/card.py +312 -0
  403. package/plugin/scripts/cities.py +520 -0
  404. package/plugin/scripts/city-env.sh +90 -0
  405. package/plugin/scripts/city-runtime.sh +87 -0
  406. package/plugin/scripts/city-session.sh +526 -0
  407. package/plugin/scripts/city_env.py +67 -0
  408. package/plugin/scripts/crecimiento.py +140 -0
  409. package/plugin/scripts/deliberations.py +48 -0
  410. package/plugin/scripts/doctor.py +172 -0
  411. package/plugin/scripts/domains.py +201 -0
  412. package/plugin/scripts/evidencia.py +64 -0
  413. package/plugin/scripts/find-repos.sh +111 -0
  414. package/plugin/scripts/gh.py +159 -0
  415. package/plugin/scripts/hall_protocol.py +147 -0
  416. package/plugin/scripts/hook_activity.py +139 -0
  417. package/plugin/scripts/launch.py +94 -0
  418. package/plugin/scripts/logs.py +98 -0
  419. package/plugin/scripts/pairing.py +185 -0
  420. package/plugin/scripts/parcels.py +132 -0
  421. package/plugin/scripts/read-card.py +51 -0
  422. package/plugin/scripts/report.py +201 -0
  423. package/plugin/scripts/reset.py +180 -0
  424. package/plugin/scripts/roads.py +230 -0
  425. package/plugin/scripts/roles.py +274 -0
  426. package/plugin/scripts/runtime_log.py +69 -0
  427. package/plugin/scripts/runtime_processes.py +132 -0
  428. package/plugin/scripts/rutas.py +83 -0
  429. package/plugin/scripts/seat.py +1349 -0
  430. package/plugin/scripts/tokens.py +194 -0
  431. package/plugin/scripts/trust-repos.py +59 -0
  432. package/plugin/scripts/ui.py +280 -0
  433. package/plugin/scripts/units.py +80 -0
  434. package/plugin/scripts/workspace.py +364 -0
  435. package/plugin/skills/city/SKILL.md +191 -0
  436. package/templates/blank.md +39 -0
  437. package/templates/finance.md +44 -0
  438. package/templates/legal.md +45 -0
  439. package/templates/marketing.md +48 -0
  440. package/templates/product.md +45 -0
package/demo/units.yml ADDED
@@ -0,0 +1,33 @@
1
+ # Business units — the districts of your city.
2
+ #
3
+ # This is yours to define at install time. The map reads it, so the districts,
4
+ # their names, their colours and their order come from here and not from the
5
+ # code. The demo ships a game studio; replace it with your own units and the map
6
+ # redraws itself.
7
+ #
8
+ # id what you write in `unidad` inside parcels.yml
9
+ # name what shows on the banner
10
+ # color hex, without the #
11
+ # order left to right; the shared district usually goes last
12
+ units:
13
+ - id: mobile ; name: Mobile Games ; color: 3fb8a0 ; order: 1
14
+ - id: console ; name: Console Games ; color: 8f7ae6 ; order: 2
15
+ - id: arcade ; name: Arcade Cabinets ; color: e08a3c ; order: 3
16
+ - id: school ; name: School Editions ; color: 4a9ede ; order: 4
17
+ - id: platform ; name: Platform ; color: 6f8fbf ; order: 5
18
+ note: "store, launcher, live ops — every unit ships through it"
19
+ - id: data ; name: Player Data ; color: e2604f ; order: 6
20
+ note: "telemetry, experiments, the numbers everyone argues with"
21
+
22
+ # Two districts every city has, whatever the business.
23
+ #
24
+ # The lab does not ship yet, and it is needed. It gets its own district instead
25
+ # of falling into the shared bucket on purpose: a lab repo among the orphans
26
+ # reads as waste, and it is the opposite. Its houses are drawn with permanent
27
+ # scaffolding, and its growth is not compared against the shipping units — a lab
28
+ # house with few floors is not behind, it is early.
29
+ special:
30
+ - id: lab ; name: Lab ; color: 8fb8c9 ; order: 90
31
+ note: "doesn't ship yet, and it's needed"
32
+ - id: none ; name: No unit ; color: c8b48a ; order: 99
33
+ note: "serves several units, or none"
@@ -0,0 +1,99 @@
1
+ # Agents come first
2
+
3
+ The original model was "a repo is an agent": every window was a git checkout,
4
+ and anyone whose work was a folder of documents — no git at all — had nowhere
5
+ to live. The model is now inverted. The primary unit is the **agent**; a repo
6
+ is just one thing an agent can mount.
7
+
8
+ The inversion is total but loses nothing, because the old model is a special
9
+ case of the new one — so **existing cities keep working untouched**.
10
+
11
+ ## The model
12
+
13
+ An agent has a **workspace folder** (`<city>/agents/<slug>/`) as its working
14
+ directory, and inside it a `mounts/` folder of **symlinks** to wherever the
15
+ real work lives: a git repo, a linked worktree, or a plain folder of documents.
16
+
17
+ ```
18
+ <city>/agents/writer/
19
+ ├── mounts/
20
+ │ ├── handbook -> ~/Documents/handbook # a document folder, no git
21
+ │ └── spec -> ~/code/product/spec # a subtree of a repo
22
+ └── (the agent's own notes live directly here)
23
+ ```
24
+
25
+ An agent also has a **kind** — `code`, `knowledge`, or `coordinator` — which is
26
+ what makes the map polymorphic instead of assuming everyone ships pull requests.
27
+
28
+ ## Two card shapes, one internal model
29
+
30
+ `plugin/scripts/workspace.py` normalises **both** card shapes into one list of
31
+ `Agente`, so the launcher, the cage and the map read one model regardless of how
32
+ the card was written.
33
+
34
+ Legacy (unchanged — every repo is an agent whose single mount is that repo):
35
+
36
+ ```yaml
37
+ repos: [nova, store-service]
38
+ role.nova: po
39
+ ```
40
+
41
+ Agent-first (adds `kind.<agent>` and `mounts.<agent>`):
42
+
43
+ ```yaml
44
+ agents: [writer, chair]
45
+ kind.writer: knowledge
46
+ mounts.writer: [~/Documents/handbook, ~/code/product/spec]
47
+ kind.chair: coordinator
48
+ runs.chair: claude
49
+ ```
50
+
51
+ ## How the cage follows a mount
52
+
53
+ The cage seals by path, so an agent's workspace is exactly what it makes
54
+ writable — plus the **resolved targets** of its mounts. Because seatbelt
55
+ resolves symlinks at the kernel, `mount_targets()` follows each symlink to its
56
+ real destination and the launcher passes those to `cage.py --mounts`. A mount
57
+ that resolves inside a sealed root (`~/.ssh`, the broker store, …) is refused,
58
+ never honoured: the security invariant holds under the new model, and
59
+ `bin/test-cage.py` proves the writable-mount / sealed-secret split against the
60
+ live kernel.
61
+
62
+ ## Managing mounts
63
+
64
+ ```bash
65
+ agents-city agents list --data <city> --card <card> # list normalised agents
66
+ agents-city agents mount --data <city> --agent writer --src ~/Documents/handbook
67
+ agents-city agents mounts --data <city> --agent writer # what is mounted
68
+ agents-city agents unmount --data <city> --agent writer --name handbook
69
+ ```
70
+
71
+ At session start the launcher runs `workspace.py sync` for each agent, which
72
+ creates the workspace and materialises every declared mount (skipping a missing
73
+ source with a warning rather than aborting the city).
74
+
75
+ ## Growth without git — `crecimiento.py`
76
+
77
+ A house grows by what its agent actually does, not by pull requests alone:
78
+
79
+ - **code** — floors = merged PRs, bricks = commits (an injected git counter).
80
+ - **knowledge** — floors = documents in the workspace, bricks = recent edits.
81
+ - **coordinator** — floors = recorded decisions, bricks = notices.
82
+
83
+ Every counter returns the same `{floors, bricks, activity30, signal}` shape, so
84
+ a person with no git at all now has a house that grows as their knowledge does.
85
+
86
+ ## Avatars — `avatar.py`
87
+
88
+ Each agent gets a deterministic, self-contained SVG identicon from its name
89
+ (same name → same face), with the border tinted by kind. No network, no
90
+ library, no external asset, so it drops into the Hall under its strict CSP —
91
+ `avatar.data_uri(name, kind)` is ready for an `<img src>`.
92
+
93
+ ## Presence and "thinking"
94
+
95
+ The activity pipeline already emits on `UserPromptSubmit` (a turn begins) and
96
+ `Stop` (it ends), so "active since the last prompt without a stop" is derivable
97
+ from the existing feed — the Hall renders it. This document's generators
98
+ (avatars, growth) provide the data; wiring them into the Hall view is the
99
+ rendering layer's job.
@@ -0,0 +1,55 @@
1
+ # Glossary
2
+
3
+ The code keeps a small Spanish domain vocabulary. These are the canonical v2
4
+ meanings.
5
+
6
+ | Term | Meaning |
7
+ |---|---|
8
+ | city / `ciudad` | One autonomous work domain with one owner seat |
9
+ | owner / `usuario` | The local person who may own several cities |
10
+ | seat / `asiento` | The city's only road-facing agent; owns its role and goal |
11
+ | chair / `presidente` | The seat while selecting agents, controlling turns and integrating a decision |
12
+ | committee / `comité` | A bounded evidence process among the chair and selected repo agents |
13
+ | floor / `palabra` | One evidence-backed reply requested by a member and granted or denied by the chair |
14
+ | live feed | Read-only browser view of submitted committee artifacts and protocol actions over the local WebSocket bus; never model chain-of-thought |
15
+ | act / `acta` | Durable human-readable decision, verification and closure record |
16
+ | local bus | Per-city loopback WebSocket hub carrying typed internal and road envelopes |
17
+ | native gateway | Provider-specific last mile that accepts a bus envelope through an official runtime protocol, never terminal paste |
18
+ | Claude stream gateway | Default Agents City transport: one persistent official Claude Code process receiving acknowledged JSONL turns over stdin/stdout |
19
+ | Claude Channel | Optional upstream preview path for injecting an event into an opted-in interactive session; not required by Agents City |
20
+ | terminal fallback | Explicit `terminal:<command>` compatibility mode for an unknown CLI; the only mode allowed to inject into tmux |
21
+ | address / `direccion` | Stable `owner/city` identity used on roads |
22
+ | road / `carretera` | Explicit connection from one city seat to another |
23
+ | support agent | A local agent window working inside one repo, with its own operating role; never road-facing |
24
+ | operating role | The professional perspective assigned to one repo agent; separate from chair/member authority |
25
+ | skill | A capability installed and owned by a repo/runtime, only recognised by Agents City |
26
+ | card / `ficha` | The one owner file containing chair role, repos, per-repo roles, goal and window engines |
27
+ | blank role | An explicit role with no built-in role knowledge or inferred responsibility |
28
+ | round / `ronda` | Evidence exchange by a seat across its explicit roads |
29
+ | notice / `carta` | A concrete change one city tells another city may affect its domain |
30
+ | house / `casa` | One parcel drawn on the visual map |
31
+ | parcel / `parcela` | A slice of a repo serving one unit; not necessarily a whole repo |
32
+ | district / `barrio` | The visual area belonging to one business unit |
33
+ | unit / `unidad` | A business unit represented by a district |
34
+ | worker / `obrero` | A currently active agent window drawn on a parcel |
35
+ | foreman / `perito` | Visual representation of the city seat near its support work |
36
+ | architect / `arquitecto` | Legacy map name for the goal-owning seat |
37
+ | trade / `oficio` | A role's name in the city metaphor |
38
+ | square / `plaza` | The map's live-presence area and its Durable Object |
39
+ | scaffold / `andamio` | An open pull request; visually stale after two weeks |
40
+ | floors / `pisos` | Landed work; a house grows when work lands |
41
+ | bricks / `ladrillos` | Commits not yet represented by a pull request |
42
+ | crack / `grieta` | Failing CI |
43
+ | milestone / `hito` | One day's landed work for one parcel |
44
+ | oven / `horno` | Pipeline that bakes 3D models into isometric sprites |
45
+ | step / `paso` | Distance between map plots, in tiles |
46
+
47
+ Two special visual districts remain:
48
+
49
+ - `lab`: research work that has not shipped;
50
+ - `none`: work shared by several units or assigned to none.
51
+
52
+ Some storage and API fields still use names such as `personas` for compatibility
53
+ with existing map data. They do not change the v2 ownership rule: a personal city
54
+ has exactly one owner seat; repo support agents and connected cities are separate
55
+ entities.
@@ -0,0 +1,73 @@
1
+ # The map's live layers
2
+
3
+ The map used to describe last month: floors from merged PRs, windows lit by
4
+ thirty days of activity. Three layers now describe **right now**, and all three
5
+ are derived from data the product already emits — nothing on the map is
6
+ invented at render time.
7
+
8
+ ## Presence: lights are turns
9
+
10
+ A house whose agent is mid-turn glows and breathes, with three thinking dots
11
+ over its worker (or over its roof when no worker figure is on site). A house
12
+ whose agent just stopped cools down over about two minutes. A house with no
13
+ session shows only its historical windows.
14
+
15
+ Derived entirely from the activity feed the hooks already publish:
16
+
17
+ | Event | Effect |
18
+ | --- | --- |
19
+ | `conversation.user` | the actor's house enters "in turn": pulsing glow + dots |
20
+ | `conversation.agent` | the turn ended: the glow cools instead of cutting out |
21
+ | `runtime.session.ended` | lights out, no afterglow |
22
+
23
+ Code: `city/web/src/presencia.ts`. The Hall relays these lifecycle events to
24
+ the embedded map (`isPresenceEvent` in `city/web/src/activity.ts`); they never
25
+ become speech bubbles.
26
+
27
+ ## The town hall: the committee, staged
28
+
29
+ A civic building stands past the square. When a deliberation opens it lights
30
+ up and the badge over its door tracks the state machine — the same kinds
31
+ `isSpeechEvent` already lists:
32
+
33
+ - `committee.position.submitted` — the member's position flies in from their
34
+ house **face down**: isolated positions are the committee's one hard rule,
35
+ and the map shows the rule, not just the traffic.
36
+ - `committee.position(s).revealed` — cards turn over.
37
+ - `committee.floor.requested` — a "✋ palabra?" chip hangs over the house of
38
+ whoever asked; `granted`/`denied` answer it in colour.
39
+ - `committee.verification.passed/failed` — the verifier's stamp over the door.
40
+ - `committee.closed` — the act rises from the hall, signed; the cards leave
41
+ with it. `committee.cancelled` clears the stage with no act.
42
+
43
+ One session on stage at a time; the Hall's right rail remains the transcript.
44
+ Code: `city/web/src/ayuntamiento.ts`.
45
+
46
+ ## Gates: roads made visible
47
+
48
+ One arch per road, at the city's entrance, with the road's name and its
49
+ `owner/city` address on the plate. A notice whose recipient is not in this
50
+ city flies out through the right gate and fades at the edge; one arriving from
51
+ another city comes in the same way. The map never draws the far city — a road
52
+ grants reachability, and the gate is exactly that much.
53
+
54
+ The gates come from the Hall, which is the only thing that knows the roads: on
55
+ loading the map iframe it sends a `map.config` message with them. The
56
+ standalone team map never receives one and draws no gates rather than invent
57
+ connections. Code: `city/web/src/puertas.ts`.
58
+
59
+ ## Rehearsing without a city
60
+
61
+ Every layer can be driven from the browser console on any running map:
62
+
63
+ ```js
64
+ __city.presence('conversation.user', 'nova'); // the house breathes
65
+ __city.committee('committee.opened', 'seat', 'why'); // the hall lights up
66
+ __city.committee('committee.position.submitted', 'nova');
67
+ __city.roads([{ name: 'home', address: 'you/home' }]); // gates appear
68
+ __city.letter('ada', 'you/home', 'security'); // out through the arch
69
+ ```
70
+
71
+ `prefers-reduced-motion` is honoured the way the rest of the map honours it:
72
+ every state is still shown — steady instead of breathing, placed instead of
73
+ flown.
@@ -0,0 +1,231 @@
1
+ # The cage and the broker
2
+
3
+ Yolo is non-negotiable here: a committee dies the moment every bus command
4
+ needs a human. So the security model never touches the approval axis. It
5
+ narrows the other one — what a window can *reach* — at the kernel and at the
6
+ credential store, where a language model cannot talk its way past it.
7
+
8
+ The threat this is built for is the one nobody has solved: prompt injection.
9
+ A repo window reads text that ultimately came from outside — an issue, a log,
10
+ a committee assignment quoting a road message — and no instruction to "treat
11
+ it as untrusted" is a security control. The goal is therefore not to prevent
12
+ the injection but to make a successful one worthless: nothing to steal, no
13
+ credential to hold, and a signed trail of everything it tried.
14
+
15
+ ## Layer 1 — the cage (`plugin/scripts/cage.py`)
16
+
17
+ Claude, OpenCode and Kimi repo windows launch under a generated macOS seatbelt
18
+ profile:
19
+
20
+ ```
21
+ sandbox-exec -f ~/.agents-city/.runtime/cage/<window>.sb <runtime …>
22
+ ```
23
+
24
+ The profile reads top to bottom: allow everything, deny all writes, re-allow
25
+ the working set, then seal the secrets — reads and writes both. The semantics
26
+ (last matching rule wins; children and grandchildren inherit) were verified on
27
+ a real machine before a line of it was written, and `bin/test-cage.py` re-proves
28
+ them live on every run where seatbelt exists.
29
+
30
+ Inside the cage a window **can**: work freely in its own repo, run builds,
31
+ reach the network, keep its runtime state (`~/.claude`, `~/.codex`,
32
+ `~/.agents-city`, build caches). It is never asked a question — yolo intact.
33
+
34
+ Inside the cage a window **cannot**: read or write `~/.ssh`, `~/.aws`,
35
+ `~/.kube`, `~/.gnupg`, `~/.docker`, `~/.config/gcloud`, `~/.config/gh`,
36
+ `~/.git-credentials`, `~/.netrc`, `~/.pgpass`, cargo credentials, any remote
37
+ road `.env` under `~/.claude/channels/`, or the broker's state; nor write
38
+ anywhere outside its repo and the allowed runtime/cache set — other repos on
39
+ the machine included.
40
+
41
+ Dials, all environment variables read at launch:
42
+
43
+ | Variable | Effect |
44
+ | --- | --- |
45
+ | `CITY_CAGE=0` | launch every window uncaged — exactly the old behaviour |
46
+ | `CITY_CAGE_DENY=a:b` | extra paths to seal (e.g. `~/.npmrc`, see below) |
47
+ | `CITY_CAGE_ALLOW_WRITE=a:b` | extra writable roots for unusual toolchains |
48
+
49
+ On Linux or any machine without `sandbox-exec`, the prefix is empty and
50
+ nothing changes. The seat is deliberately never caged: it is the permissioned,
51
+ non-yolo side that holds the road token and asks the owner.
52
+
53
+ Codex uses exactly one confinement layer: its native `workspace-write` sandbox
54
+ (network enabled only in auto mode). It is deliberately not wrapped in the
55
+ outer seatbelt profile because Codex MCPs such as `node_repl` create their own
56
+ sandboxed worker; macOS rejects that second `sandbox_apply` when the entire
57
+ gateway already runs under seatbelt. `CITY_CAGE=0` deliberately changes Codex
58
+ to `dangerFullAccess` and launches the other providers without their outer
59
+ cage.
60
+
61
+ At startup, the Codex gateway also reads the effective MCP registry through the
62
+ Codex CLI. An enabled stdio MCP whose executable is provably absent is disabled
63
+ only through a process-local `-c mcp_servers.<name>.enabled=false` override for
64
+ app-server and its TUI. The owner's `~/.codex/config.toml` is never rewritten;
65
+ healthy, URL-based and uncertain integrations are left alone. Each scoped
66
+ disable is recorded as `codex.mcp.unavailable.disabled` without MCP environment
67
+ values.
68
+
69
+ ## Layer 2 — the credential broker (`plugin/scripts/broker.py`)
70
+
71
+ A caged window cannot read the `gh` token — that is the cage working. But
72
+ opening a pull request is legitimate work, so the window asks a small process
73
+ the owner runs outside every cage, and that process acts with the real
74
+ credentials after validating the request:
75
+
76
+ ```bash
77
+ CITY_BROKER=1 agents-city seat # start the session with a broker
78
+ python3 plugin/scripts/broker.py call pr --title "Add X" --body "why"
79
+ python3 plugin/scripts/broker.py call push
80
+ ```
81
+
82
+ - One token per window, minted at session start, **bound to one repo**: a
83
+ stolen token cannot choose a different target.
84
+ - Only the SHA-256 of a token is stored; the token itself travels as a file
85
+ path whose read is the single exception the window's cage re-allows.
86
+ - The broker refuses any action on the default branch, always. Pushes are
87
+ `git push origin HEAD` — no force, no refspec.
88
+ - The broker is opt-in (`CITY_BROKER=1`) while it covers only `pr` and `push`;
89
+ stop it with `broker.py stop --data <city-dir>`.
90
+
91
+ ## Layer 3 — the audit chain, with an evidence vocabulary
92
+
93
+ Every broker request — served or refused — appends one line to
94
+ `~/.agents-city/.runtime/broker/<id>/audit.log`, and each line carries the
95
+ SHA-256 of the previous one. The cage denies repo windows the whole directory,
96
+ so an agent cannot rewrite its own history, and `broker.py verify` catches a
97
+ single flipped byte. `bin/test-broker.py` proves both directions.
98
+
99
+ A hash chain proves a line was not altered; it says nothing about what the line
100
+ proves. So every line also carries an evidence state (`plugin/scripts/evidencia.py`):
101
+ `enforced` (a control decided the outcome), `attribution-only`, `unattributed`,
102
+ `unknown`, `ambiguous`, `unsupported`. Two rules hold wherever it is read —
103
+ `unknown` never means allowed, and `ambiguous` is never resolved by picking a
104
+ candidate. A missing binding is `unknown`, which authorises nothing; only
105
+ `enforced` is proof a control fired.
106
+
107
+ ## Layer 4 — path containment, resolved twice
108
+
109
+ The cage's write-allow set and the broker's repo binding both trust paths, and
110
+ `plugin/scripts/rutas.py` answers the same question for both: it resolves a path
111
+ through the deepest ancestor that exists — so a missing leaf under a symlinked
112
+ parent still lands on the real target — and refuses a path both when it sits
113
+ inside a sealed root and when it *covers* one. The cage's own guarantee is the
114
+ ordering (the secret-deny block is emitted last and wins), so a covering
115
+ writable root is safe; what containment adds is refusing a repo rooted on the
116
+ credential store and dropping an owner `CITY_CAGE_ALLOW_WRITE` that lands inside
117
+ a seal. `bin/test-rutas.py` drives the primitive directly.
118
+
119
+ ## Layer 5 — road admission as a gate graph
120
+
121
+ An inbound road message is admitted by an ordered gate graph
122
+ (`plugin/scripts/admision.py`), not a boolean: address → road → sender →
123
+ payload, first blocker decisive. It returns a stable reason code
124
+ (`road_missing`, `sender_not_paired`, …) and, per gate, a redacted diagnostic —
125
+ counts and opaque ids, never a raw allowlist entry — so a refusal is
126
+ explainable without leaking who is allowed. A road that matches but whose sender
127
+ does not is an explicit block; an unknown sender under pairing yields
128
+ `pairing-required` rather than a silent drop.
129
+
130
+ ## Layer 6 — road pairing
131
+
132
+ An unknown city earns the right to write to a seat through a short code
133
+ (`plugin/scripts/pairing.py`): eight characters from an unambiguous alphabet, a
134
+ one-hour TTL, at most three pending per city, and an opaque approval id so
135
+ tooling never echoes the human code. The code is revealed once per sender per
136
+ window, so re-messaging cannot re-spam it. Approving grants exactly one thing —
137
+ permission for that address to write to this seat — never road membership, never
138
+ chair authority. The store is consulted only under the pairing policy; it can
139
+ never widen an existing allowlist.
140
+
141
+ ## Layer 7 — untrusted road text
142
+
143
+ Text arriving over a road is wrapped once, on arrival, in an unforgeable
144
+ boundary (`plugin/channel/untrusted.ts`): the opening marker carries a fresh
145
+ random id per wrap, so a message that pastes its own closing marker cannot guess
146
+ the id and cannot smuggle the rest as trusted text. Chat-template role tokens
147
+ (`<|im_start|>`, `[INST]`, `<start_of_turn>`, …) are defanged first, so text
148
+ cannot forge a synthetic system or assistant turn on a self-hosted backend. It
149
+ is defence in depth for the seat, not a promise the model obeys the boundary.
150
+
151
+ ## Host-bound secrets — the broker without handing over the key
152
+
153
+ A caged window cannot read an API token, and the broker does not hand it one.
154
+ The owner binds a secret to an exact host set (`broker.py secret-set OPENAI
155
+ --value … --allow-host api.openai.com`; no wildcards, no ports), and a window
156
+ asks the broker to make the call carrying the secret by *name*
157
+ (`broker.py call fetch --secret OPENAI --host api.openai.com --path /v1/…`). The
158
+ broker injects the value only when the target host matches a bound one, and
159
+ never returns it. A leaked transcript or `ps` line holds the request, never the
160
+ credential.
161
+
162
+ ## The Hall protocol — writing to the seat from a page
163
+
164
+ When the local web Hall is built, it talks to the bus that serves it over one
165
+ same-origin socket, on the contract in `plugin/scripts/hall_protocol.py`: three
166
+ frames (`req`/`res`/`event`), a per-connection monotonic sequence, and the rule
167
+ that events are never replayed — on a gap the client re-fetches a snapshot. The
168
+ method set is closed (`os.system` is not a method); writing to the seat is one
169
+ call with a queue mode (`start`/`steer`/`queue`/`note`) so an idle and a busy
170
+ seat are the same call. Loopback is a pairing convenience, never a substitute
171
+ for the seat's own admission.
172
+
173
+ ## Config migrations — `doctor`
174
+
175
+ The runtime reads one config schema. Every change that can invalidate an
176
+ existing config ships a migration in `plugin/scripts/doctor.py` that detects the
177
+ old shape, explains it in one line, writes a timestamped backup, and rewrites to
178
+ canonical form. Migrations are idempotent and ordered, so a file needing several
179
+ converges in one pass and a canonical file is left untouched. `--fix` writes;
180
+ the default is a dry-run report.
181
+
182
+ ## The ratchet
183
+
184
+ `bin/test-security.py` is not a unit suite but a firewall of cross-cutting
185
+ invariants: the secret set always contains `~/.ssh`/`~/.git-credentials`/gh, the
186
+ seal block is always emitted after the write-allow block, `unknown` evidence
187
+ never authorises, an admission decision never carries a raw allowlist entry, the
188
+ Hall method set stays closed, and the launcher never wraps the seat window in
189
+ the cage. Each past sharp edge becomes a line here, and the line stays.
190
+
191
+ ## Scope — the conscience stays inside the city
192
+
193
+ Installing the plugin does not enrol every Claude session on the machine.
194
+ Every hook sources `plugin/hooks/solo-en-ciudad.sh` first: outside a city
195
+ runtime — no `CITY_BUS_ACTOR`, the identity only `city-session.sh` sets — the
196
+ hook answers `{}` and leaves. No review passes, no digging notes, no notice
197
+ judgements, no token counters in plain conversations. The MCP server already
198
+ declared itself inactive without an actor identity; this extends the same rule
199
+ to the hooks, which were the remaining machine-wide surface.
200
+
201
+ The machine-wide conscience still exists as an explicit choice:
202
+ `CITY_HOOKS=everywhere`, in the environment or in
203
+ `~/.claude/channels/city-bus/.env`. Second-order effects are opted into, never
204
+ discovered. `bin/test-contracts.py` holds both sides: every hook is mute
205
+ outside a city, and both opt-in paths open the gate.
206
+
207
+ ## What this does NOT do, in writing
208
+
209
+ - **Prompt injection still exists.** The cage bounds what it is worth.
210
+ - **Keychain credentials travel over IPC, not file reads.** A jailed `git
211
+ push` still works when GitHub credentials live in the macOS keychain; the
212
+ broker and forge-side protected branches are the guard there. If you keep
213
+ `~/.git-credentials` in plain text, move it: `git config --global
214
+ credential.helper osxkeychain`.
215
+ - **Outbound network stays open** — that is what keeps yolo useful. A window
216
+ can still exfiltrate what it can already see: its own repo.
217
+ - **Everything runs as your OS user.** The cage narrows file reach; it is not
218
+ hostile-process isolation. Untrusted code still belongs in a separate user,
219
+ VM or container.
220
+ - `~/.npmrc` stays readable by default because denying it breaks `npm install`
221
+ for owners with a registry config. If yours holds tokens, add it to
222
+ `CITY_CAGE_DENY`.
223
+
224
+ ## Where this lands against an exposed-gateway agent
225
+
226
+ The criticisms that stuck to OpenClaw-class setups were: reachable from the
227
+ network, broad host access, credentials in reach of the model. Here: nothing
228
+ listens beyond `127.0.0.1`, writes are kernel-bounded per repo, and the
229
+ credentials a hijacked window would want are either unreadable (cage) or never
230
+ held (broker). The fourth problem — the model reads untrusted text — remains
231
+ everyone's, and the three layers above exist to make it survivable.
@@ -0,0 +1,151 @@
1
+ # Self-hosting remote roads
2
+
3
+ Local cities and local roads need no external server: each city automatically
4
+ starts its loopback WebSocket hub. Self-host this transport only when two city
5
+ seats on different machines must exchange messages.
6
+
7
+ The Cloudflare Worker is a narrow relay:
8
+
9
+ - a token authenticates one owner prefix;
10
+ - each running seat registers as its stable `owner/city` address;
11
+ - the relay carries the same `agents-city-bus/2` envelope as the local hub;
12
+ - only `scope: road` envelopes from `seat` to `seat` are accepted;
13
+ - the channel still filters senders and recipients through that city's
14
+ `roads.json`;
15
+ - offline direct messages wait for at most 72 hours, up to 200 per city;
16
+ - message bodies are not logged unless `LOG_CONTENT = "1"` is deliberately set.
17
+
18
+ The relay does not distribute configuration, skills, files or authority. It
19
+ cannot carry an internal committee envelope.
20
+
21
+ ## 1. Deploy the relay
22
+
23
+ Set the Cloudflare account id in `bus/worker/wrangler.toml`, then:
24
+
25
+ ```bash
26
+ cd bus
27
+ npx --yes wrangler@4 login
28
+ ./scripts/deploy.sh
29
+ ```
30
+
31
+ The deploy script creates the `TOKENS` KV namespace when necessary and deploys
32
+ the Worker plus its SQLite-backed Durable Object. Keep the URL it prints.
33
+
34
+ ## 2. Mint one token per owner
35
+
36
+ An owner can run several cities with the same token because every address shares
37
+ that owner's prefix:
38
+
39
+ ```bash
40
+ cd bus
41
+ ./scripts/mint-token.sh joseluiscases
42
+ ```
43
+
44
+ Only the SHA-256 hash is stored in KV and the token is printed once. Store it
45
+ privately. It permits addresses such as `joseluiscases/home` and
46
+ `joseluiscases/product`, but cannot register as another owner.
47
+
48
+ An optional `--to` restriction can narrow the relay-level destinations:
49
+
50
+ ```bash
51
+ ./scripts/mint-token.sh joseluiscases --to alice,bob/research
52
+ ```
53
+
54
+ This is defence in depth. `roads.json` remains the city-level allowlist.
55
+
56
+ ## 3. Configure each machine, not each city
57
+
58
+ The installed plugin accepts the remote roads URL and token. For a manual
59
+ development setup, use:
60
+
61
+ ```text
62
+ ~/.claude/channels/city-bus/.env
63
+ ```
64
+
65
+ with:
66
+
67
+ ```dotenv
68
+ CITY_BUS_URL=https://city-bus.example.workers.dev
69
+ CITY_BUS_TOKEN=rb_...
70
+ ```
71
+
72
+ Do not put `CITY_BUS_AGENT` in new configuration. The seat derives its own
73
+ `owner/city` address from the selected city at runtime, which is what lets
74
+ several local cities use one plugin installation safely.
75
+
76
+ The token can instead live in the macOS keychain under the service
77
+ `city@agents-city`; that is where the plugin configuration stores sensitive
78
+ values.
79
+
80
+ ## 4. Exchange city invitations
81
+
82
+ Authentication makes a seat reachable; a road makes it allowed. Each side sends
83
+ the other its public invitation and accepts it independently:
84
+
85
+ ```bash
86
+ # machine A
87
+ agents-city road invite product > product.invitation.json
88
+ agents-city road connect product research.invitation.json
89
+
90
+ # machine B
91
+ agents-city road invite research > research.invitation.json
92
+ agents-city road connect research product.invitation.json
93
+ ```
94
+
95
+ Transfer invitation files by any ordinary channel. They contain no token:
96
+
97
+ ```json
98
+ {
99
+ "version": 1,
100
+ "id": "city_…",
101
+ "name": "product",
102
+ "owner": "joseluiscases",
103
+ "address": "joseluiscases/product"
104
+ }
105
+ ```
106
+
107
+ Open both seats. `bus_roster` — or `/city:team`, kept as a compatibility command
108
+ name — reports only explicit roads and whether their destination seats are
109
+ online. Messages to an offline remote city are queued by the relay.
110
+
111
+ Validate the relay and its durable typed queue locally before deploying:
112
+
113
+ ```bash
114
+ cd bus/worker
115
+ npm ci
116
+ npm run typecheck
117
+ npm run test:local
118
+ ```
119
+
120
+ ## Trust boundary
121
+
122
+ A valid road grants permission to deliver text, not permission to act. The seat
123
+ must treat incoming text as untrusted context, verify claims locally and ask for
124
+ the same user confirmation that would be required without the road.
125
+
126
+ Repo support windows authenticate only as members of their local committee bus.
127
+ They receive no road tools or remote URL/token; attempts to invoke road commands
128
+ are rejected by the hub ACL. Never copy an inbound request into an autonomous
129
+ repo agent as an instruction; open a bounded evidence request and bring
130
+ consequential actions back to the seat.
131
+
132
+ These local credentials enforce routing, not hostile-process isolation: every
133
+ runtime normally executes as the same OS user. Use separate OS/container users
134
+ for untrusted code. Remote owner tokens remain genuine network credentials and
135
+ must never be exposed to repo runtimes.
136
+
137
+ ## Optional map deployment
138
+
139
+ The visual map is independent of road delivery. A city can run entirely without
140
+ it, and a map outage must not stop the relay.
141
+
142
+ To deploy a map, build `city/web`, create the D1 database from
143
+ `city/worker/schema.sql`, seed one chosen city with `city/scripts/seed.py`, set a
144
+ long `CITY_SECRET`, then deploy `city/worker`. Put an identity proxy such as
145
+ Cloudflare Access in front of it and set `REQUIRE_ACCESS = "1"`; the map exposes
146
+ the shape and activity of the selected codebase and should not be public by
147
+ default.
148
+
149
+ If the relay should report delivery metadata to that map, configure `CITY_URL`
150
+ in `bus/worker/wrangler.toml` and the same `CITY_SECRET` in both Workers. The
151
+ relay works normally when either value is absent.