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.
- package/.claude-plugin/marketplace.json +16 -0
- package/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.es.md +2021 -0
- package/README.md +1999 -0
- package/benchmarks/committee/README.md +39 -0
- package/benchmarks/committee/metrics.py +51 -0
- package/benchmarks/committee/run.py +64 -0
- package/benchmarks/committee/traces/01-single.json +8 -0
- package/benchmarks/committee/traces/02-mesh.json +16 -0
- package/benchmarks/committee/traces/03-chair.json +15 -0
- package/benchmarks/latency/README.md +35 -0
- package/benchmarks/latency/fake-claude-cli.mjs +119 -0
- package/benchmarks/latency/fake-native-server.mjs +340 -0
- package/benchmarks/latency/fake-tui.py +91 -0
- package/benchmarks/latency/live.py +380 -0
- package/benchmarks/stress/README.md +20 -0
- package/benchmarks/stress/run.py +487 -0
- package/bin/agents +4 -0
- package/bin/agents-city.js +116 -0
- package/bin/benchmark +17 -0
- package/bin/bus +6 -0
- package/bin/cities +3 -0
- package/bin/city +86 -0
- package/bin/committee +6 -0
- package/bin/demo +200 -0
- package/bin/exit +5 -0
- package/bin/hall +73 -0
- package/bin/hall.html +426 -0
- package/bin/logs +3 -0
- package/bin/report.py +14 -0
- package/bin/reset +3 -0
- package/bin/road +3 -0
- package/bin/seat +10 -0
- package/bin/serve.py +1428 -0
- package/bin/setup.py +753 -0
- package/bin/skills +3 -0
- package/bin/test +131 -0
- package/bin/test-adapter.py +183 -0
- package/bin/test-admision.py +99 -0
- package/bin/test-avatar.py +58 -0
- package/bin/test-benchmark.py +40 -0
- package/bin/test-broker.py +334 -0
- package/bin/test-cage.py +206 -0
- package/bin/test-card.py +402 -0
- package/bin/test-channel.py +379 -0
- package/bin/test-cities.py +453 -0
- package/bin/test-claude-runtime.py +252 -0
- package/bin/test-committee.py +340 -0
- package/bin/test-contracts.py +544 -0
- package/bin/test-crecimiento.py +164 -0
- package/bin/test-demo.py +308 -0
- package/bin/test-doctor.py +106 -0
- package/bin/test-domains.py +151 -0
- package/bin/test-evidencia.py +50 -0
- package/bin/test-exit.py +128 -0
- package/bin/test-hall-protocol.py +93 -0
- package/bin/test-launch.py +169 -0
- package/bin/test-live-feed.py +508 -0
- package/bin/test-pairing.py +99 -0
- package/bin/test-parcels.py +283 -0
- package/bin/test-runtime-failures.py +252 -0
- package/bin/test-runtime-ui.py +488 -0
- package/bin/test-runtime.py +268 -0
- package/bin/test-rutas.py +80 -0
- package/bin/test-seat.py +1386 -0
- package/bin/test-security.py +150 -0
- package/bin/test-serve.py +1057 -0
- package/bin/test-stress.py +64 -0
- package/bin/test-widgets.py +143 -0
- package/bin/test-workspace.py +188 -0
- package/bin/testlib.py +95 -0
- package/bin/tokens.py +14 -0
- package/bus/scripts/deploy.sh +60 -0
- package/bus/scripts/mint-token.sh +68 -0
- package/bus/scripts/setup-dev.sh +79 -0
- package/bus/scripts/test-channel.ts +8 -0
- package/bus/scripts/test-hub.ts +266 -0
- package/bus/scripts/test-local.sh +40 -0
- package/bus/scripts/test-queue.ts +132 -0
- package/bus/worker/package-lock.json +1971 -0
- package/bus/worker/package.json +17 -0
- package/bus/worker/src/index.ts +485 -0
- package/bus/worker/tsconfig.json +13 -0
- package/bus/worker/wrangler.toml +32 -0
- package/city/oven/README.md +59 -0
- package/city/oven/collect.py +43 -0
- package/city/oven/oven.html +4 -0
- package/city/scripts/history.py +120 -0
- package/city/scripts/seed.py +442 -0
- package/city/web/assets/modelos/License.txt +28 -0
- package/city/web/assets/modelos/PROCEDENCIA.txt +3 -0
- package/city/web/assets/modelos/building-a.glb +0 -0
- package/city/web/assets/modelos/building-b.glb +0 -0
- package/city/web/assets/modelos/building-c.glb +0 -0
- package/city/web/assets/modelos/building-d.glb +0 -0
- package/city/web/assets/modelos/building-e.glb +0 -0
- package/city/web/assets/modelos/building-f.glb +0 -0
- package/city/web/assets/modelos/building-g.glb +0 -0
- package/city/web/assets/modelos/building-h.glb +0 -0
- package/city/web/assets/sprites/building-a.png +0 -0
- package/city/web/assets/sprites/building-b.png +0 -0
- package/city/web/assets/sprites/building-c.png +0 -0
- package/city/web/assets/sprites/building-d.png +0 -0
- package/city/web/assets/sprites/building-e.png +0 -0
- package/city/web/assets/sprites/building-f.png +0 -0
- package/city/web/assets/sprites/building-g.png +0 -0
- package/city/web/assets/sprites/building-h.png +0 -0
- package/city/web/assets/sprites/building-i.png +0 -0
- package/city/web/assets/sprites/building-j.png +0 -0
- package/city/web/assets/sprites/building-k.png +0 -0
- package/city/web/assets/sprites/building-l.png +0 -0
- package/city/web/assets/sprites/building-m.png +0 -0
- package/city/web/assets/sprites/building-n.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-a.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-b.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-c.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-d.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-e.png +0 -0
- package/city/web/assets/sprites/catalogo.json +47 -0
- package/city/web/assets/sprites/detail-awning-wide.png +0 -0
- package/city/web/assets/sprites/detail-awning.png +0 -0
- package/city/web/assets/sprites/detail-overhang-wide.png +0 -0
- package/city/web/assets/sprites/detail-overhang.png +0 -0
- package/city/web/assets/sprites/detail-parasol-a.png +0 -0
- package/city/web/assets/sprites/detail-parasol-b.png +0 -0
- package/city/web/assets/sprites/low-detail-building-a.png +0 -0
- package/city/web/assets/sprites/low-detail-building-b.png +0 -0
- package/city/web/assets/sprites/low-detail-building-c.png +0 -0
- package/city/web/assets/sprites/low-detail-building-d.png +0 -0
- package/city/web/assets/sprites/low-detail-building-e.png +0 -0
- package/city/web/assets/sprites/low-detail-building-f.png +0 -0
- package/city/web/assets/sprites/low-detail-building-g.png +0 -0
- package/city/web/assets/sprites/low-detail-building-h.png +0 -0
- package/city/web/assets/sprites/low-detail-building-i.png +0 -0
- package/city/web/assets/sprites/low-detail-building-j.png +0 -0
- package/city/web/assets/sprites/low-detail-building-k.png +0 -0
- package/city/web/assets/sprites/low-detail-building-l.png +0 -0
- package/city/web/assets/sprites/low-detail-building-m.png +0 -0
- package/city/web/assets/sprites/low-detail-building-n.png +0 -0
- package/city/web/assets/sprites/low-detail-building-wide-a.png +0 -0
- package/city/web/assets/sprites/low-detail-building-wide-b.png +0 -0
- package/city/web/assets/sprites/medidas.json +248 -0
- package/city/web/dist/city.js +939 -0
- package/city/web/dist/index.html +294 -0
- package/city/web/dist/modelos/License.txt +28 -0
- package/city/web/dist/modelos/PROCEDENCIA.txt +3 -0
- package/city/web/dist/modelos/building-a.glb +0 -0
- package/city/web/dist/modelos/building-b.glb +0 -0
- package/city/web/dist/modelos/building-c.glb +0 -0
- package/city/web/dist/modelos/building-d.glb +0 -0
- package/city/web/dist/modelos/building-e.glb +0 -0
- package/city/web/dist/modelos/building-f.glb +0 -0
- package/city/web/dist/modelos/building-g.glb +0 -0
- package/city/web/dist/modelos/building-h.glb +0 -0
- package/city/web/dist/sprites/building-a.png +0 -0
- package/city/web/dist/sprites/building-b.png +0 -0
- package/city/web/dist/sprites/building-c.png +0 -0
- package/city/web/dist/sprites/building-d.png +0 -0
- package/city/web/dist/sprites/building-e.png +0 -0
- package/city/web/dist/sprites/building-f.png +0 -0
- package/city/web/dist/sprites/building-g.png +0 -0
- package/city/web/dist/sprites/building-h.png +0 -0
- package/city/web/dist/sprites/building-i.png +0 -0
- package/city/web/dist/sprites/building-j.png +0 -0
- package/city/web/dist/sprites/building-k.png +0 -0
- package/city/web/dist/sprites/building-l.png +0 -0
- package/city/web/dist/sprites/building-m.png +0 -0
- package/city/web/dist/sprites/building-n.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-a.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-b.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-c.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-d.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-e.png +0 -0
- package/city/web/dist/sprites/catalogo.json +47 -0
- package/city/web/dist/sprites/detail-awning-wide.png +0 -0
- package/city/web/dist/sprites/detail-awning.png +0 -0
- package/city/web/dist/sprites/detail-overhang-wide.png +0 -0
- package/city/web/dist/sprites/detail-overhang.png +0 -0
- package/city/web/dist/sprites/detail-parasol-a.png +0 -0
- package/city/web/dist/sprites/detail-parasol-b.png +0 -0
- package/city/web/dist/sprites/low-detail-building-a.png +0 -0
- package/city/web/dist/sprites/low-detail-building-b.png +0 -0
- package/city/web/dist/sprites/low-detail-building-c.png +0 -0
- package/city/web/dist/sprites/low-detail-building-d.png +0 -0
- package/city/web/dist/sprites/low-detail-building-e.png +0 -0
- package/city/web/dist/sprites/low-detail-building-f.png +0 -0
- package/city/web/dist/sprites/low-detail-building-g.png +0 -0
- package/city/web/dist/sprites/low-detail-building-h.png +0 -0
- package/city/web/dist/sprites/low-detail-building-i.png +0 -0
- package/city/web/dist/sprites/low-detail-building-j.png +0 -0
- package/city/web/dist/sprites/low-detail-building-k.png +0 -0
- package/city/web/dist/sprites/low-detail-building-l.png +0 -0
- package/city/web/dist/sprites/low-detail-building-m.png +0 -0
- package/city/web/dist/sprites/low-detail-building-n.png +0 -0
- package/city/web/dist/sprites/low-detail-building-wide-a.png +0 -0
- package/city/web/dist/sprites/low-detail-building-wide-b.png +0 -0
- package/city/web/dist/sprites/medidas.json +248 -0
- package/city/web/dist-hall/hall.js +264 -0
- package/city/web/index.html +294 -0
- package/city/web/package.json +18 -0
- package/city/web/sella.py +21 -0
- package/city/web/src/activity-actors.ts +47 -0
- package/city/web/src/activity.ts +172 -0
- package/city/web/src/ayuntamiento.ts +580 -0
- package/city/web/src/draw.ts +640 -0
- package/city/web/src/game-speech.ts +79 -0
- package/city/web/src/hall.ts +1867 -0
- package/city/web/src/main.ts +2225 -0
- package/city/web/src/oven.ts +124 -0
- package/city/web/src/people.ts +244 -0
- package/city/web/src/presencia.ts +193 -0
- package/city/web/src/puertas.ts +180 -0
- package/city/web/src/repo-roles.ts +59 -0
- package/city/worker/package.json +16 -0
- package/city/worker/schema.sql +117 -0
- package/city/worker/src/index.ts +702 -0
- package/city/worker/src/square.ts +218 -0
- package/city/worker/wrangler.toml +58 -0
- package/demo/ada.md +50 -0
- package/demo/bruno.md +62 -0
- package/demo/camila.md +56 -0
- package/demo/city/ada.md +40 -0
- package/demo/city/city.yml +8 -0
- package/demo/city/parcels.yml +15 -0
- package/demo/city/roads.json +4 -0
- package/demo/city/units.yml +10 -0
- package/demo/clinica/city.yml +8 -0
- package/demo/clinica/parcels.yml +15 -0
- package/demo/clinica/roads.json +4 -0
- package/demo/clinica/units.yml +9 -0
- package/demo/clinica/vera.md +46 -0
- package/demo/dante.md +54 -0
- package/demo/despacho/city.yml +8 -0
- package/demo/despacho/marta.md +46 -0
- package/demo/despacho/parcels.yml +15 -0
- package/demo/despacho/roads.json +4 -0
- package/demo/despacho/units.yml +9 -0
- package/demo/elsa.md +50 -0
- package/demo/farid.md +52 -0
- package/demo/greta.md +50 -0
- package/demo/hugo.md +49 -0
- package/demo/iris.md +49 -0
- package/demo/jonas.md +51 -0
- package/demo/kira.md +51 -0
- package/demo/luca.md +49 -0
- package/demo/parcels.yml +92 -0
- package/demo/seed.py +20 -0
- package/demo/show.py +161 -0
- package/demo/stories.py +824 -0
- package/demo/units.yml +33 -0
- package/docs/agents-first.md +99 -0
- package/docs/glossary.md +55 -0
- package/docs/map-live-layers.md +73 -0
- package/docs/security.md +231 -0
- package/docs/self-host.md +151 -0
- package/docs/testing.md +205 -0
- package/package.json +73 -0
- package/plugin/.claude-plugin/plugin.json +62 -0
- package/plugin/.mcp.json +15 -0
- package/plugin/channel/activity-cli.ts +16 -0
- package/plugin/channel/adapter-prompts.ts +113 -0
- package/plugin/channel/adapter.js +4292 -0
- package/plugin/channel/adapter.ts +133 -0
- package/plugin/channel/bus.js +20188 -0
- package/plugin/channel/bus.ts +131 -0
- package/plugin/channel/city-config.ts +125 -0
- package/plugin/channel/claude-channel.ts +58 -0
- package/plugin/channel/cli-args.ts +75 -0
- package/plugin/channel/client.js +4200 -0
- package/plugin/channel/client.ts +38 -0
- package/plugin/channel/committee/activity.ts +256 -0
- package/plugin/channel/committee/collection.ts +154 -0
- package/plugin/channel/committee/decision.ts +175 -0
- package/plugin/channel/committee/floor.ts +135 -0
- package/plugin/channel/committee/guards.ts +30 -0
- package/plugin/channel/committee/history.ts +38 -0
- package/plugin/channel/committee/render.ts +102 -0
- package/plugin/channel/committee/service.ts +72 -0
- package/plugin/channel/committee/storage.ts +92 -0
- package/plugin/channel/committee/types.ts +163 -0
- package/plugin/channel/committee/view.ts +63 -0
- package/plugin/channel/committee-cli.ts +109 -0
- package/plugin/channel/delivery-metrics.ts +61 -0
- package/plugin/channel/delivery-queue.ts +118 -0
- package/plugin/channel/hub/activity-controller.ts +72 -0
- package/plugin/channel/hub/activity-feed.ts +169 -0
- package/plugin/channel/hub/committee-controller.ts +60 -0
- package/plugin/channel/hub/connections.ts +48 -0
- package/plugin/channel/hub/diagnostics.ts +67 -0
- package/plugin/channel/hub/envelope-validity.ts +109 -0
- package/plugin/channel/hub/envelopes.ts +66 -0
- package/plugin/channel/hub/lifecycle.ts +91 -0
- package/plugin/channel/hub/local-roads.ts +89 -0
- package/plugin/channel/hub/remote-roads.ts +165 -0
- package/plugin/channel/hub/road-controller.ts +99 -0
- package/plugin/channel/hub-client.ts +149 -0
- package/plugin/channel/local-hub.js +5995 -0
- package/plugin/channel/local-hub.ts +349 -0
- package/plugin/channel/map-reporter.ts +48 -0
- package/plugin/channel/package-lock.json +1731 -0
- package/plugin/channel/package.json +19 -0
- package/plugin/channel/protocol.ts +98 -0
- package/plugin/channel/road-cli.ts +18 -0
- package/plugin/channel/run.sh +15 -0
- package/plugin/channel/runtime/claude.ts +458 -0
- package/plugin/channel/runtime/codex-config.ts +77 -0
- package/plugin/channel/runtime/codex.ts +669 -0
- package/plugin/channel/runtime/command.ts +62 -0
- package/plugin/channel/runtime/factory.ts +15 -0
- package/plugin/channel/runtime/json-rpc.ts +154 -0
- package/plugin/channel/runtime/kimi.ts +277 -0
- package/plugin/channel/runtime/opencode.ts +248 -0
- package/plugin/channel/runtime/process.ts +84 -0
- package/plugin/channel/runtime/types.ts +45 -0
- package/plugin/channel/runtime-files.ts +73 -0
- package/plugin/channel/runtime-gateway.js +6396 -0
- package/plugin/channel/runtime-gateway.ts +358 -0
- package/plugin/channel/runtime-metrics.ts +52 -0
- package/plugin/channel/runtime-subscription.ts +180 -0
- package/plugin/channel/terminal-delivery.ts +131 -0
- package/plugin/channel/untrusted.ts +60 -0
- package/plugin/commands/committee.md +31 -0
- package/plugin/commands/exit.md +32 -0
- package/plugin/commands/goals.md +20 -0
- package/plugin/commands/join.md +18 -0
- package/plugin/commands/notice.md +20 -0
- package/plugin/commands/propose.md +19 -0
- package/plugin/commands/round.md +17 -0
- package/plugin/commands/session.md +22 -0
- package/plugin/commands/settings.md +25 -0
- package/plugin/commands/setup.md +21 -0
- package/plugin/commands/team.md +23 -0
- package/plugin/domains/custom.md +27 -0
- package/plugin/domains/finance.md +31 -0
- package/plugin/domains/healthcare.md +34 -0
- package/plugin/domains/legal.md +31 -0
- package/plugin/domains/marketing.md +33 -0
- package/plugin/domains/operations.md +31 -0
- package/plugin/domains/research.md +32 -0
- package/plugin/domains/sales.md +30 -0
- package/plugin/domains/software.md +37 -0
- package/plugin/hooks/activity.sh +6 -0
- package/plugin/hooks/digging.sh +68 -0
- package/plugin/hooks/growth.sh +42 -0
- package/plugin/hooks/hooks.json +106 -0
- package/plugin/hooks/notice-on-pr.sh +35 -0
- package/plugin/hooks/notice-on-stop.sh +80 -0
- package/plugin/hooks/notice-pending.sh +58 -0
- package/plugin/hooks/solo-en-ciudad.sh +23 -0
- package/plugin/hooks/tokens.sh +47 -0
- package/plugin/roles/examples/account-executive.md +25 -0
- package/plugin/roles/examples/ai-manager.md +34 -0
- package/plugin/roles/examples/associate.md +24 -0
- package/plugin/roles/examples/brand-lead.md +20 -0
- package/plugin/roles/examples/cfo.md +20 -0
- package/plugin/roles/examples/city-lead.md +25 -0
- package/plugin/roles/examples/clinical-director.md +26 -0
- package/plugin/roles/examples/clinical-ops.md +25 -0
- package/plugin/roles/examples/clinician.md +25 -0
- package/plugin/roles/examples/compliance.md +27 -0
- package/plugin/roles/examples/content.md +24 -0
- package/plugin/roles/examples/controller.md +27 -0
- package/plugin/roles/examples/cpto.md +41 -0
- package/plugin/roles/examples/customer-success.md +25 -0
- package/plugin/roles/examples/data-engineer.md +37 -0
- package/plugin/roles/examples/data.md +60 -0
- package/plugin/roles/examples/dev.md +37 -0
- package/plugin/roles/examples/devops.md +39 -0
- package/plugin/roles/examples/enablement.md +24 -0
- package/plugin/roles/examples/ethics.md +24 -0
- package/plugin/roles/examples/fin-analytics.md +27 -0
- package/plugin/roles/examples/health-compliance.md +25 -0
- package/plugin/roles/examples/health-data.md +26 -0
- package/plugin/roles/examples/knowledge.md +25 -0
- package/plugin/roles/examples/lifecycle.md +26 -0
- package/plugin/roles/examples/llm-engineer.md +34 -0
- package/plugin/roles/examples/managing-partner.md +20 -0
- package/plugin/roles/examples/methods.md +23 -0
- package/plugin/roles/examples/operations-lead.md +23 -0
- package/plugin/roles/examples/ops.md +24 -0
- package/plugin/roles/examples/patient-safety.md +26 -0
- package/plugin/roles/examples/performance.md +27 -0
- package/plugin/roles/examples/po.md +36 -0
- package/plugin/roles/examples/process-owner.md +23 -0
- package/plugin/roles/examples/product-design.md +39 -0
- package/plugin/roles/examples/program-manager.md +24 -0
- package/plugin/roles/examples/quality.md +25 -0
- package/plugin/roles/examples/research-director.md +24 -0
- package/plugin/roles/examples/research-ops.md +25 -0
- package/plugin/roles/examples/researcher.md +25 -0
- package/plugin/roles/examples/revenue-lead.md +25 -0
- package/plugin/roles/examples/revops.md +26 -0
- package/plugin/roles/examples/seo.md +27 -0
- package/plugin/roles/examples/specialist.md +24 -0
- package/plugin/scripts/admision.py +165 -0
- package/plugin/scripts/apaga.py +232 -0
- package/plugin/scripts/avatar.py +211 -0
- package/plugin/scripts/broker.py +531 -0
- package/plugin/scripts/cage.py +252 -0
- package/plugin/scripts/capabilities.py +197 -0
- package/plugin/scripts/card.py +312 -0
- package/plugin/scripts/cities.py +520 -0
- package/plugin/scripts/city-env.sh +90 -0
- package/plugin/scripts/city-runtime.sh +87 -0
- package/plugin/scripts/city-session.sh +526 -0
- package/plugin/scripts/city_env.py +67 -0
- package/plugin/scripts/crecimiento.py +140 -0
- package/plugin/scripts/deliberations.py +48 -0
- package/plugin/scripts/doctor.py +172 -0
- package/plugin/scripts/domains.py +201 -0
- package/plugin/scripts/evidencia.py +64 -0
- package/plugin/scripts/find-repos.sh +111 -0
- package/plugin/scripts/gh.py +159 -0
- package/plugin/scripts/hall_protocol.py +147 -0
- package/plugin/scripts/hook_activity.py +139 -0
- package/plugin/scripts/launch.py +94 -0
- package/plugin/scripts/logs.py +98 -0
- package/plugin/scripts/pairing.py +185 -0
- package/plugin/scripts/parcels.py +132 -0
- package/plugin/scripts/read-card.py +51 -0
- package/plugin/scripts/report.py +201 -0
- package/plugin/scripts/reset.py +180 -0
- package/plugin/scripts/roads.py +230 -0
- package/plugin/scripts/roles.py +274 -0
- package/plugin/scripts/runtime_log.py +69 -0
- package/plugin/scripts/runtime_processes.py +132 -0
- package/plugin/scripts/rutas.py +83 -0
- package/plugin/scripts/seat.py +1349 -0
- package/plugin/scripts/tokens.py +194 -0
- package/plugin/scripts/trust-repos.py +59 -0
- package/plugin/scripts/ui.py +280 -0
- package/plugin/scripts/units.py +80 -0
- package/plugin/scripts/workspace.py +364 -0
- package/plugin/skills/city/SKILL.md +191 -0
- package/templates/blank.md +39 -0
- package/templates/finance.md +44 -0
- package/templates/legal.md +45 -0
- package/templates/marketing.md +48 -0
- package/templates/product.md +45 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Opt-in live bus -> native runtime -> committee-position latency benchmark."""
|
|
3
|
+
import argparse
|
|
4
|
+
import datetime
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import signal
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import tempfile
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
16
|
+
ROOT = os.path.dirname(os.path.dirname(HERE))
|
|
17
|
+
GATEWAY = os.path.join(ROOT, 'plugin', 'channel', 'runtime-gateway.js')
|
|
18
|
+
CLIENT = os.path.join(ROOT, 'plugin', 'channel', 'client.js')
|
|
19
|
+
|
|
20
|
+
DEFAULTS = {
|
|
21
|
+
'claude': 'claude --model haiku',
|
|
22
|
+
'codex': 'codex',
|
|
23
|
+
'opencode': 'opencode --model opencode/mimo-v2.5-free --auto',
|
|
24
|
+
'kimi': 'kimi --auto',
|
|
25
|
+
}
|
|
26
|
+
FAILURE = re.compile(
|
|
27
|
+
r'(?:authentication (?:error|failed|required)|auth(?:entication)?_error|'
|
|
28
|
+
r'credentials? (?:are )?(?:missing|invalid|required|not configured)|'
|
|
29
|
+
r'has no credential configured|login required|not logged in|token (?:is )?rejected|'
|
|
30
|
+
r'turn ended as (?:failed|blocked)|session\.error)',
|
|
31
|
+
re.IGNORECASE,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def parse_args():
|
|
36
|
+
parser = argparse.ArgumentParser(
|
|
37
|
+
description='Run a real, paid/cache-using task through the city bus and native runtimes.')
|
|
38
|
+
parser.add_argument('--runtime', action='append', choices=sorted(DEFAULTS),
|
|
39
|
+
help='runtime to include; repeat it (default: every installed runtime)')
|
|
40
|
+
parser.add_argument('--command', action='append', default=[], metavar='RUNTIME=COMMAND',
|
|
41
|
+
help='override one runtime command/model')
|
|
42
|
+
parser.add_argument('--timeout', type=int, default=180,
|
|
43
|
+
help='seconds to wait for model positions (default: 180)')
|
|
44
|
+
parser.add_argument('--json', action='store_true', help='print only the JSON report')
|
|
45
|
+
parser.add_argument('--no-save', action='store_true', help='do not append the local baseline ledger')
|
|
46
|
+
parser.add_argument('--keep', action='store_true', help='keep the temporary city and logs')
|
|
47
|
+
return parser.parse_args()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main():
|
|
51
|
+
args = parse_args()
|
|
52
|
+
commands = dict(DEFAULTS)
|
|
53
|
+
for item in args.command:
|
|
54
|
+
if '=' not in item or item.split('=', 1)[0] not in commands:
|
|
55
|
+
raise SystemExit(f'invalid --command {item!r}; use runtime=full command')
|
|
56
|
+
runtime, command = item.split('=', 1)
|
|
57
|
+
commands[runtime] = command.strip()
|
|
58
|
+
requested = args.runtime or list(DEFAULTS)
|
|
59
|
+
selected, missing = [], []
|
|
60
|
+
for runtime in requested:
|
|
61
|
+
executable = shell_words(commands[runtime])[0]
|
|
62
|
+
if shutil.which(executable):
|
|
63
|
+
selected.append(runtime)
|
|
64
|
+
else:
|
|
65
|
+
missing.append({'runtime': runtime, 'status': 'unavailable',
|
|
66
|
+
'error': f'{executable} is not on PATH'})
|
|
67
|
+
if not selected:
|
|
68
|
+
report = report_for([], missing, '', 0)
|
|
69
|
+
print_report(report, args.json)
|
|
70
|
+
return 1
|
|
71
|
+
|
|
72
|
+
base = tempfile.mkdtemp(prefix='agents-city-live-latency-')
|
|
73
|
+
app = os.path.join(base, 'app')
|
|
74
|
+
city = os.path.join(app, 'benchmark', 'native-e2e')
|
|
75
|
+
repos = os.path.join(base, 'repos')
|
|
76
|
+
tools = os.path.join(base, 'bin')
|
|
77
|
+
logs = os.path.join(base, 'logs')
|
|
78
|
+
os.makedirs(city)
|
|
79
|
+
os.makedirs(tools)
|
|
80
|
+
os.makedirs(logs)
|
|
81
|
+
os.symlink(os.path.join(ROOT, 'bin', 'agents-city.js'), os.path.join(tools, 'agents-city'))
|
|
82
|
+
marker = f'CITY_NATIVE_{int(time.time())}_{os.getpid()}'
|
|
83
|
+
actors = {runtime: f'{runtime}-agent' for runtime in selected}
|
|
84
|
+
for runtime, actor in actors.items():
|
|
85
|
+
repo = os.path.join(repos, actor)
|
|
86
|
+
os.makedirs(repo)
|
|
87
|
+
with open(os.path.join(repo, 'README.md'), 'w', encoding='utf-8') as stream:
|
|
88
|
+
stream.write('# Native delivery benchmark\n\n')
|
|
89
|
+
stream.write(f'Exact evidence marker: `{marker}`\n')
|
|
90
|
+
stream.write('Read-only task. Do not edit this repository.\n')
|
|
91
|
+
write_city(city, actors, commands)
|
|
92
|
+
env = dict(os.environ, AGENTS_CITY_HOME=app, AGENTS_CITY_DATA=city,
|
|
93
|
+
AGENTS_CITY_USER='benchmark', CITY_RUNTIME_AUTO='1',
|
|
94
|
+
CITY_BUS_DEBUG='1',
|
|
95
|
+
PATH=tools + os.pathsep + os.environ.get('PATH', ''))
|
|
96
|
+
for key in ('CITY_BUS_URL', 'CITY_BUS_TOKEN', 'CITY_DIR'):
|
|
97
|
+
env.pop(key, None)
|
|
98
|
+
processes, streams = {}, {}
|
|
99
|
+
endpoint_path = os.path.join(app, '.runtime', 'bus', 'city-native-e2e', 'endpoint.json')
|
|
100
|
+
started = time.monotonic()
|
|
101
|
+
thread = ''
|
|
102
|
+
try:
|
|
103
|
+
for runtime in selected:
|
|
104
|
+
actor = actors[runtime]
|
|
105
|
+
stream = open(os.path.join(logs, f'{runtime}.log'), 'w+', encoding='utf-8')
|
|
106
|
+
streams[runtime] = stream
|
|
107
|
+
process = subprocess.Popen(
|
|
108
|
+
['node', GATEWAY, '--data', city, '--actor', actor,
|
|
109
|
+
'--cwd', os.path.join(repos, actor), '--command', commands[runtime],
|
|
110
|
+
'--auto', '0' if runtime == 'claude' else '1', '--interactive', '0'],
|
|
111
|
+
cwd=os.path.join(repos, actor), env=env,
|
|
112
|
+
stdin=subprocess.DEVNULL, stdout=stream, stderr=stream, text=True)
|
|
113
|
+
processes[runtime] = process
|
|
114
|
+
|
|
115
|
+
wait_ready(selected, actors, processes, streams, app, min(30, args.timeout))
|
|
116
|
+
opened = subprocess.run(
|
|
117
|
+
['node', CLIENT, 'committee', 'open', '--input', '-'],
|
|
118
|
+
input=json.dumps({
|
|
119
|
+
'question': f'Read README.md and report the exact marker {marker}.',
|
|
120
|
+
'desiredOutcome': 'One minimal evidence-backed position from every native runtime.',
|
|
121
|
+
'context': 'Latency benchmark: read only, do not edit files.',
|
|
122
|
+
'definitionOfDone': [
|
|
123
|
+
f'the recommendation contains {marker}',
|
|
124
|
+
'evidence cites README.md',
|
|
125
|
+
'one committee position is submitted with the provided CLI',
|
|
126
|
+
],
|
|
127
|
+
'participants': list(actors.values()),
|
|
128
|
+
}), capture_output=True, text=True,
|
|
129
|
+
env=dict(env, CITY_BUS_ACTOR='seat'), timeout=15)
|
|
130
|
+
if opened.returncode != 0:
|
|
131
|
+
raise RuntimeError(f'could not open benchmark committee: {opened.stderr.strip()}')
|
|
132
|
+
thread = json.loads(opened.stdout)['id']
|
|
133
|
+
state_path = os.path.join(city, 'deliberations', thread, 'state.json')
|
|
134
|
+
terminal = {}
|
|
135
|
+
deadline = time.monotonic() + args.timeout
|
|
136
|
+
while time.monotonic() < deadline:
|
|
137
|
+
state = read_json(state_path)
|
|
138
|
+
positions = state.get('positions', {}) if state else {}
|
|
139
|
+
for runtime, actor in actors.items():
|
|
140
|
+
if actor in positions:
|
|
141
|
+
terminal[runtime] = 'success'
|
|
142
|
+
continue
|
|
143
|
+
process = processes[runtime]
|
|
144
|
+
if process.poll() is not None:
|
|
145
|
+
terminal[runtime] = 'failed'
|
|
146
|
+
continue
|
|
147
|
+
text = read_stream(streams[runtime])
|
|
148
|
+
if FAILURE.search(text):
|
|
149
|
+
terminal[runtime] = 'failed'
|
|
150
|
+
if len(terminal) == len(selected):
|
|
151
|
+
break
|
|
152
|
+
time.sleep(.2)
|
|
153
|
+
|
|
154
|
+
state = read_json(state_path)
|
|
155
|
+
positions = state.get('positions', {}) if state else {}
|
|
156
|
+
metrics = read_jsonl(os.path.join(app, '.runtime', 'bus', 'city-native-e2e',
|
|
157
|
+
'runtime-latency.jsonl'))
|
|
158
|
+
by_actor = {row.get('actor'): row for row in metrics}
|
|
159
|
+
results = []
|
|
160
|
+
for runtime in selected:
|
|
161
|
+
actor = actors[runtime]
|
|
162
|
+
position = positions.get(actor)
|
|
163
|
+
metric = by_actor.get(actor, {})
|
|
164
|
+
log = read_stream(streams[runtime])
|
|
165
|
+
status = 'success' if position else terminal.get(runtime, 'timeout')
|
|
166
|
+
error = ''
|
|
167
|
+
if status != 'success':
|
|
168
|
+
match = FAILURE.search(log)
|
|
169
|
+
error = compact(match.group(0) if match else tail(log, 8))
|
|
170
|
+
if not error:
|
|
171
|
+
error = 'no committee position before timeout'
|
|
172
|
+
submitted_at = position.get('submittedAt') if position else ''
|
|
173
|
+
created_at = metric.get('createdAt', '')
|
|
174
|
+
end_to_end = elapsed(created_at, submitted_at) if submitted_at and created_at else None
|
|
175
|
+
results.append({
|
|
176
|
+
'runtime': runtime,
|
|
177
|
+
'command': commands[runtime],
|
|
178
|
+
'status': status,
|
|
179
|
+
'busToNativeAcceptMs': metric.get('totalToNativeAcceptMs'),
|
|
180
|
+
'endToEndPositionMs': end_to_end,
|
|
181
|
+
'markerVerified': bool(position and marker in json.dumps(position)),
|
|
182
|
+
'transport': metric.get('transport'),
|
|
183
|
+
'error': error,
|
|
184
|
+
})
|
|
185
|
+
report = report_for(results, missing, thread,
|
|
186
|
+
round((time.monotonic() - started) * 1000))
|
|
187
|
+
if not args.no_save:
|
|
188
|
+
add_baselines(report)
|
|
189
|
+
print_report(report, args.json)
|
|
190
|
+
return 0 if all(row['status'] == 'success' and row['markerVerified']
|
|
191
|
+
for row in results) and not missing else 1
|
|
192
|
+
except Exception as error:
|
|
193
|
+
report = report_for([], missing + [{'runtime': 'benchmark', 'status': 'failed',
|
|
194
|
+
'error': str(error)}], thread,
|
|
195
|
+
round((time.monotonic() - started) * 1000))
|
|
196
|
+
print_report(report, args.json)
|
|
197
|
+
return 1
|
|
198
|
+
finally:
|
|
199
|
+
for process in processes.values():
|
|
200
|
+
if process.stdin:
|
|
201
|
+
try:
|
|
202
|
+
process.stdin.close()
|
|
203
|
+
except OSError:
|
|
204
|
+
pass
|
|
205
|
+
if process.poll() is None:
|
|
206
|
+
process.terminate()
|
|
207
|
+
for process in processes.values():
|
|
208
|
+
try:
|
|
209
|
+
process.wait(timeout=5)
|
|
210
|
+
except subprocess.TimeoutExpired:
|
|
211
|
+
process.kill()
|
|
212
|
+
for stream in streams.values():
|
|
213
|
+
stream.close()
|
|
214
|
+
stop_hub(endpoint_path)
|
|
215
|
+
if args.keep:
|
|
216
|
+
if not args.json:
|
|
217
|
+
print(f'\nArtifacts kept at {base}')
|
|
218
|
+
else:
|
|
219
|
+
shutil.rmtree(base, ignore_errors=True)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def wait_ready(runtimes, actors, processes, streams, app, timeout):
|
|
223
|
+
deadline = time.monotonic() + timeout
|
|
224
|
+
status_dir = os.path.join(app, '.runtime', 'bus', 'city-native-e2e', 'gateways')
|
|
225
|
+
while time.monotonic() < deadline:
|
|
226
|
+
ready = True
|
|
227
|
+
for runtime in runtimes:
|
|
228
|
+
process = processes[runtime]
|
|
229
|
+
if process.poll() is not None:
|
|
230
|
+
raise RuntimeError(f'{runtime} exited during startup: {tail(read_stream(streams[runtime]), 12)}')
|
|
231
|
+
ready = ready and os.path.exists(
|
|
232
|
+
os.path.join(status_dir, f'{actors[runtime]}.json'))
|
|
233
|
+
if ready:
|
|
234
|
+
return
|
|
235
|
+
time.sleep(.1)
|
|
236
|
+
pending = [runtime for runtime in runtimes
|
|
237
|
+
if not os.path.exists(os.path.join(status_dir, f'{actors[runtime]}.json'))]
|
|
238
|
+
raise RuntimeError(f'native runtime startup timed out: {", ".join(pending)}')
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def write_city(city, actors, commands):
|
|
242
|
+
with open(os.path.join(city, 'city.yml'), 'w', encoding='utf-8') as stream:
|
|
243
|
+
stream.write('id: city_native_e2e\nname: Native E2E\nslug: native-e2e\nowner: benchmark\n')
|
|
244
|
+
with open(os.path.join(city, 'roads.json'), 'w', encoding='utf-8') as stream:
|
|
245
|
+
stream.write('{"version": 1, "roads": []}\n')
|
|
246
|
+
with open(os.path.join(city, 'benchmark.md'), 'w', encoding='utf-8') as stream:
|
|
247
|
+
stream.write('---\nuser: benchmark\nagent: benchmark/ceo\n')
|
|
248
|
+
stream.write(f'repos: [{", ".join(actors.values())}]\n')
|
|
249
|
+
for runtime, actor in actors.items():
|
|
250
|
+
stream.write(f'role.{actor}: blank\n')
|
|
251
|
+
stream.write(f'runs.{actor}: {commands[runtime]}\n')
|
|
252
|
+
stream.write('---\n')
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def report_for(results, unavailable, thread, duration):
|
|
256
|
+
return {
|
|
257
|
+
'schema': 'agents-city/native-e2e-latency@1',
|
|
258
|
+
'runAt': datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
259
|
+
'scope': 'live task completion through bus, native provider protocol and committee position',
|
|
260
|
+
'thread': thread,
|
|
261
|
+
'durationMs': duration,
|
|
262
|
+
'results': results + unavailable,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def baseline_path():
|
|
267
|
+
home = os.environ.get('AGENTS_CITY_HOME', os.path.expanduser('~/.agents-city'))
|
|
268
|
+
return os.path.join(home, '.benchmarks', 'native-e2e.jsonl')
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def add_baselines(report):
|
|
272
|
+
path = baseline_path()
|
|
273
|
+
os.makedirs(os.path.dirname(path), mode=0o700, exist_ok=True)
|
|
274
|
+
previous = read_jsonl(path)
|
|
275
|
+
for result in report['results']:
|
|
276
|
+
candidates = [run_result
|
|
277
|
+
for run in reversed(previous)
|
|
278
|
+
for run_result in run.get('results', [])
|
|
279
|
+
if run_result.get('runtime') == result.get('runtime')
|
|
280
|
+
and run_result.get('command') == result.get('command')
|
|
281
|
+
and run_result.get('status') == 'success']
|
|
282
|
+
old = candidates[0] if candidates else None
|
|
283
|
+
current = result.get('endToEndPositionMs')
|
|
284
|
+
prior = old.get('endToEndPositionMs') if old else None
|
|
285
|
+
result['previousEndToEndPositionMs'] = prior
|
|
286
|
+
result['changeMs'] = current - prior if current is not None and prior is not None else None
|
|
287
|
+
stored = dict(report)
|
|
288
|
+
stored['results'] = [
|
|
289
|
+
{key: value for key, value in result.items()
|
|
290
|
+
if key not in ('error', 'recommendation')}
|
|
291
|
+
for result in report['results']
|
|
292
|
+
]
|
|
293
|
+
with open(path, 'a', encoding='utf-8') as stream:
|
|
294
|
+
stream.write(json.dumps(stored) + '\n')
|
|
295
|
+
os.chmod(path, 0o600)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def print_report(report, only_json):
|
|
299
|
+
if only_json:
|
|
300
|
+
print(json.dumps(report, indent=2))
|
|
301
|
+
return
|
|
302
|
+
print('\nNative E2E latency (real model calls)')
|
|
303
|
+
print('runtime status accept full position transport')
|
|
304
|
+
print('---------- ----------- ----------- -------------- ---------------------')
|
|
305
|
+
for row in report['results']:
|
|
306
|
+
accept = duration(row.get('busToNativeAcceptMs'))
|
|
307
|
+
total = duration(row.get('endToEndPositionMs'))
|
|
308
|
+
print(f"{row.get('runtime', '')[:10]:10} {row.get('status', '')[:11]:11} "
|
|
309
|
+
f"{accept:11} {total:14} {row.get('transport') or '-'}")
|
|
310
|
+
if row.get('error'):
|
|
311
|
+
print(f" {row['error']}")
|
|
312
|
+
print(f"\nRun: {report['durationMs']} ms · thread {report.get('thread') or '-'}")
|
|
313
|
+
print('Acceptance is not task completion; full position ends only when the agent')
|
|
314
|
+
print('submits evidence back through the committee protocol.')
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def read_stream(stream):
|
|
318
|
+
try:
|
|
319
|
+
size = os.fstat(stream.fileno()).st_size
|
|
320
|
+
return os.pread(stream.fileno(), size, 0).decode('utf-8', errors='replace')
|
|
321
|
+
except OSError:
|
|
322
|
+
return ''
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def read_json(path):
|
|
326
|
+
try:
|
|
327
|
+
with open(path, encoding='utf-8') as stream:
|
|
328
|
+
return json.load(stream)
|
|
329
|
+
except (OSError, json.JSONDecodeError):
|
|
330
|
+
return {}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def read_jsonl(path):
|
|
334
|
+
try:
|
|
335
|
+
with open(path, encoding='utf-8') as stream:
|
|
336
|
+
return [json.loads(line) for line in stream if line.strip()]
|
|
337
|
+
except (OSError, json.JSONDecodeError):
|
|
338
|
+
return []
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def elapsed(start, end):
|
|
342
|
+
try:
|
|
343
|
+
left = datetime.datetime.fromisoformat(start.replace('Z', '+00:00'))
|
|
344
|
+
right = datetime.datetime.fromisoformat(end.replace('Z', '+00:00'))
|
|
345
|
+
return max(0, round((right - left).total_seconds() * 1000))
|
|
346
|
+
except (AttributeError, ValueError):
|
|
347
|
+
return None
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def shell_words(command):
|
|
351
|
+
import shlex
|
|
352
|
+
words = shlex.split(command)
|
|
353
|
+
if not words:
|
|
354
|
+
raise ValueError('empty runtime command')
|
|
355
|
+
return words
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def duration(value):
|
|
359
|
+
return '-' if value is None else f'{value / 1000:.2f}s'
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def compact(value):
|
|
363
|
+
return re.sub(r'\s+', ' ', value or '').strip()[:500]
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def tail(value, lines):
|
|
367
|
+
return '\n'.join((value or '').splitlines()[-lines:])[-1000:]
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def stop_hub(endpoint_path):
|
|
371
|
+
try:
|
|
372
|
+
endpoint = json.load(open(endpoint_path, encoding='utf-8'))
|
|
373
|
+
os.kill(int(endpoint['pid']), signal.SIGTERM)
|
|
374
|
+
time.sleep(.15)
|
|
375
|
+
except (OSError, ValueError, KeyError, json.JSONDecodeError):
|
|
376
|
+
pass
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
if __name__ == '__main__':
|
|
380
|
+
sys.exit(main())
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Mixed-runtime city stress
|
|
2
|
+
|
|
3
|
+
This offline E2E benchmark opens one city with 40 repo actors: 20 use the
|
|
4
|
+
production Claude persistent stream-json gateway and 20 use the production
|
|
5
|
+
Codex app-server WebSocket gateway against deterministic local doubles.
|
|
6
|
+
|
|
7
|
+
It fans one committee assignment out to all 40 actors, forces the shared local
|
|
8
|
+
WebSocket hub down, then repeats the fan-out after all actors reconnect. It
|
|
9
|
+
fails on duplicate native delivery, a retained outbox, more than one hub,
|
|
10
|
+
terminal injection, excessive local acceptance latency, an orphan process, or
|
|
11
|
+
a browser spectator feed that loses/duplicates the committee or fails to name
|
|
12
|
+
all 40 selected actors. The read-only browser WebSocket is re-authenticated
|
|
13
|
+
after the forced hub replacement.
|
|
14
|
+
It never calls a model account and never uses tmux or the clipboard.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
agents-city benchmark stress
|
|
18
|
+
agents-city benchmark stress --json
|
|
19
|
+
agents-city benchmark stress --agents 20 --rounds 2
|
|
20
|
+
```
|