agents-city 0.3.0 → 0.5.1
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 +1 -1
- package/README.es.md +294 -16
- package/README.md +291 -16
- package/benchmarks/latency/fake-claude-cli.mjs +2 -1
- package/benchmarks/reception/README.md +16 -0
- package/benchmarks/reception/run.mjs +84 -0
- package/bin/agents-city.js +5 -0
- package/bin/connect +5 -0
- package/bin/hall.html +208 -22
- package/bin/navegador.mjs +203 -13
- package/bin/serve.py +176 -111
- package/bin/test +18 -4
- package/bin/test-arnes.py +192 -0
- package/bin/test-cage.py +9 -0
- package/bin/test-channel.py +178 -12
- package/bin/test-claude-runtime.py +132 -4
- package/bin/test-connect-client.mjs +609 -0
- package/bin/test-connect.py +52 -0
- package/bin/test-contracts.py +214 -0
- package/bin/test-demo.py +67 -0
- package/bin/test-desinstala.py +170 -0
- package/bin/test-doctor.py +31 -0
- package/bin/test-i18n.py +189 -0
- package/bin/test-navegador.py +54 -0
- package/bin/test-seat.py +78 -20
- package/bin/test-security.py +2 -0
- package/bin/test-serve.py +414 -2
- package/bin/uninstall +5 -0
- package/city/web/dist/city.js +65 -65
- package/city/web/dist/index.html +18 -5
- package/city/web/dist-hall/hall.js +1566 -486
- package/city/web/index.html +17 -4
- package/city/web/src/bienvenida.ts +48 -341
- package/city/web/src/casa.ts +324 -0
- package/city/web/src/demo.ts +277 -0
- package/city/web/src/dialogo.ts +178 -0
- package/city/web/src/es.ts +281 -25
- package/city/web/src/explorador.ts +216 -0
- package/city/web/src/hall.ts +612 -139
- package/city/web/src/idioma.ts +21 -1
- package/city/web/src/main.ts +28 -5
- package/city/web/src/motores.ts +48 -1
- package/city/web/src/vista.ts +58 -0
- package/demo/graba.py +127 -0
- package/demo/grabaciones/legal.jsonl +22 -0
- package/demo/grabaciones/medicina.jsonl +22 -0
- package/demo/grabaciones/software.jsonl +22 -0
- package/docs/managed-connect.md +237 -0
- package/docs/security.md +41 -2
- package/package.json +8 -4
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/channel/adapter-prompts.ts +10 -0
- package/plugin/channel/adapter.js +78 -31
- package/plugin/channel/agents_city_hybrid_crypto_bg.wasm +0 -0
- package/plugin/channel/bus.js +109 -65
- package/plugin/channel/bus.ts +11 -1
- package/plugin/channel/client.js +67 -30
- package/plugin/channel/delivery-queue.ts +160 -21
- package/plugin/channel/hub/remote-roads.ts +32 -1
- package/plugin/channel/hub/road-controller.ts +116 -16
- package/plugin/channel/kinsh_vodozemac_wasm_bg.wasm +0 -0
- package/plugin/channel/licenses/CONNECT_CLIENT_THIRD_PARTY_NOTICES.md +21 -0
- package/plugin/channel/licenses/HYBRID_CRYPTO_THIRD_PARTY_NOTICES.md +12 -0
- package/plugin/channel/licenses/hybrid-crypto-Apache-2.0.txt +201 -0
- package/plugin/channel/licenses/keyring-MIT.txt +21 -0
- package/plugin/channel/licenses/vodozemac-Apache-2.0.txt +201 -0
- package/plugin/channel/local-hub.js +2080 -97
- package/plugin/channel/local-hub.ts +5 -4
- package/plugin/channel/managed-connect/bridge.ts +208 -0
- package/plugin/channel/managed-connect/cli.ts +416 -0
- package/plugin/channel/managed-connect/device.ts +15 -0
- package/plugin/channel/managed-connect/local-cities.ts +94 -0
- package/plugin/channel/managed-connect/person-message.ts +74 -0
- package/plugin/channel/managed-connect/reception-bridge.ts +341 -0
- package/plugin/channel/managed-connect/relay-session.ts +7 -0
- package/plugin/channel/managed-connect/storage.ts +653 -0
- package/plugin/channel/managed-connect/transport.ts +87 -0
- package/plugin/channel/managed-connect-cli.js +4777 -0
- package/plugin/channel/managed-connect-cli.ts +7 -0
- package/plugin/channel/managed-connect-client.d.ts +261 -0
- package/plugin/channel/managed-connect-client.js +6572 -0
- package/plugin/channel/managed-connect-client.manifest.json +38 -0
- package/plugin/channel/package-lock.json +123 -105
- package/plugin/channel/package.json +4 -4
- package/plugin/channel/protocol.ts +4 -0
- package/plugin/channel/reception.ts +896 -0
- package/plugin/channel/road-cli.ts +1 -1
- package/plugin/channel/runtime/arnes.json +189 -0
- package/plugin/channel/runtime/arnes.ts +47 -0
- package/plugin/channel/runtime/claude.ts +18 -0
- package/plugin/channel/runtime/codex-config.ts +51 -1
- package/plugin/channel/runtime/codex.ts +31 -11
- package/plugin/channel/runtime/kimi.ts +6 -4
- package/plugin/channel/runtime-files.ts +39 -5
- package/plugin/channel/runtime-gateway.js +426 -100
- package/plugin/channel/runtime-gateway.ts +12 -0
- package/plugin/channel/trust/agents-city-sandbox-roots.json +66 -0
- package/plugin/scripts/arnes.py +271 -0
- package/plugin/scripts/busca.py +46 -10
- package/plugin/scripts/cage.py +5 -0
- package/plugin/scripts/city-session.sh +144 -28
- package/plugin/scripts/crecimiento.py +4 -1
- package/plugin/scripts/demos.py +122 -0
- package/plugin/scripts/desinstala.py +237 -0
- package/plugin/scripts/doctor.py +37 -15
- package/plugin/scripts/read-card.py +48 -8
- package/plugin/scripts/reception.py +665 -0
package/README.md
CHANGED
|
@@ -104,7 +104,7 @@ This is `0.x` on purpose: the commands are usable today, and the file formats
|
|
|
104
104
|
and APIs can still change between minor versions. Nothing here pretends to be
|
|
105
105
|
frozen yet.
|
|
106
106
|
|
|
107
|
-
You need Node.js 22+, Python 3 and tmux; the
|
|
107
|
+
You need Node.js 22.13+, Python 3 and tmux; the
|
|
108
108
|
[requirements table](#base-requirements) has the details, and `agents-city seat`
|
|
109
109
|
offers to install tmux when it is missing. Nothing is installed system-wide
|
|
110
110
|
beyond the npm global folder of your active Node installation.
|
|
@@ -160,7 +160,7 @@ agents-city --version
|
|
|
160
160
|
|
|
161
161
|
| Requirement | Used for |
|
|
162
162
|
|---|---|
|
|
163
|
-
| Node.js 22 or later | npm package, WebSocket bus, and frontends |
|
|
163
|
+
| Node.js 22.13 or later | npm package, WebSocket bus, local reception, and frontends |
|
|
164
164
|
| npm | installation and packaging |
|
|
165
165
|
| Python 3 | Hall, onboarding, cities, maps, and utilities |
|
|
166
166
|
| bash | sessions and launchers |
|
|
@@ -486,6 +486,7 @@ agents-city setup
|
|
|
486
486
|
agents-city seat
|
|
487
487
|
agents-city cities
|
|
488
488
|
agents-city road
|
|
489
|
+
agents-city connect
|
|
489
490
|
agents-city bus
|
|
490
491
|
agents-city committee
|
|
491
492
|
agents-city agents
|
|
@@ -550,12 +551,44 @@ spectator token rotates with the hub, accepts only an origin on this computer,
|
|
|
550
551
|
and is read-only: the browser cannot direct the committee. `Ctrl-c` stops the
|
|
551
552
|
Hall.
|
|
552
553
|
|
|
554
|
+
**Demos** in the rail plays a whole committee without setting anything up: one
|
|
555
|
+
story per work domain — a studio, a clinic, a law firm — with play, pause,
|
|
556
|
+
replay and speed. What it plays are *recordings*: `demo/graba.py` runs each story
|
|
557
|
+
over the real local bus, through the real committee state machine, and keeps the
|
|
558
|
+
exact event stream a spectator saw; the Hall replays those events through the
|
|
559
|
+
same renderer the live rail uses. It says so on screen, because a demo that
|
|
560
|
+
pretends to be live is the one kind this product must not ship. To run one live
|
|
561
|
+
in a terminal instead: `agents-city demo --domain software`.
|
|
562
|
+
|
|
563
|
+
Regenerate the recordings after editing `demo/stories.py`:
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
demo/graba.py # every story
|
|
567
|
+
demo/graba.py medicina # just one
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
The demo suite fails when a recording no longer matches the story it claims to
|
|
571
|
+
be, so a stale one is a red build rather than a browser quietly playing last
|
|
572
|
+
month's committee.
|
|
573
|
+
|
|
553
574
|
Two buttons sit under the brand: **day/night**, and **ES/EN**. The Hall speaks
|
|
554
575
|
Spanish and English, starting in the browser's own language and remembering an
|
|
555
576
|
explicit choice. Translations are keyed by the English sentence, so anything not
|
|
556
577
|
yet translated falls back to readable English rather than to an identifier — new
|
|
557
578
|
strings are never blocked on a translation pass.
|
|
558
579
|
|
|
580
|
+
Coverage is a test, not a habit. `bin/test-i18n.py` reads the render paths, pulls
|
|
581
|
+
out every English sentence a person will see, and fails when one has no Spanish —
|
|
582
|
+
so a new view cannot quietly ship untranslated, which is how coverage had drifted
|
|
583
|
+
to about 40% before anybody noticed.
|
|
584
|
+
|
|
585
|
+
The obvious mechanism, sweeping the rendered DOM and translating what matches a
|
|
586
|
+
key, is deliberately **not** what this does. At DOM time there is no way to tell
|
|
587
|
+
a sentence this product wrote from a city or agent name somebody typed, so a
|
|
588
|
+
person whose city is called `Overview` would watch it rename itself. The
|
|
589
|
+
distinction only exists in the source, between a literal and an interpolation,
|
|
590
|
+
and that is where the check is made: anything holding a `${}` is skipped.
|
|
591
|
+
|
|
559
592
|
### `agents-city setup`
|
|
560
593
|
|
|
561
594
|
Creates or selects a city and opens the Hall; `--tui` hands the flow to `seat`.
|
|
@@ -661,6 +694,62 @@ agents-city road disconnect product <remote-city-id>
|
|
|
661
694
|
A city cannot connect to itself. Each machine must independently accept the
|
|
662
695
|
other remote invitation.
|
|
663
696
|
|
|
697
|
+
### `agents-city connect`
|
|
698
|
+
|
|
699
|
+
Pairs this computer with a managed Road service. It does not create a
|
|
700
|
+
connection unilaterally: both people approve it in the service, and the
|
|
701
|
+
recipient sees the sender in their private human reception without exposing a
|
|
702
|
+
city catalogue. The public client implements protocol v4; the hosted service is
|
|
703
|
+
outside this repository and is not production-enabled or independently audited.
|
|
704
|
+
|
|
705
|
+
```bash
|
|
706
|
+
agents-city connect --service https://connect.example.com --trust-file roots.json
|
|
707
|
+
agents-city connect --city product
|
|
708
|
+
agents-city connect --all
|
|
709
|
+
agents-city connect status
|
|
710
|
+
agents-city connect roads
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
The command generates Ed25519/X25519, Olm and signed ML-KEM-768 material on this
|
|
714
|
+
computer, prints a one-use PASCO and opens the browser for approval. Only public
|
|
715
|
+
material is uploaded. Private keys, ratchet state, ML-KEM seeds and retry data
|
|
716
|
+
are encrypted in `~/.agents-city/.runtime/connect/vault/`; the wrapping key
|
|
717
|
+
stays in macOS Keychain, Windows Credential Manager or Linux Secret Service.
|
|
718
|
+
The client fails closed if that keyring is unavailable. The vault is sealed from
|
|
719
|
+
repo-agent windows on macOS and Linux.
|
|
720
|
+
|
|
721
|
+
The signed root chain supplied through `--trust-file` is mandatory for first
|
|
722
|
+
pairing with a non-development service. The client persists its last accepted
|
|
723
|
+
version. A later root must continue from that exact local root and carry enough
|
|
724
|
+
signatures from both the old and new offline authorities; skipped versions,
|
|
725
|
+
rollback, expiry and silent operator/witness replacement are rejected. Protocol
|
|
726
|
+
v4 then verifies the peer through key transparency, protects the first Olm
|
|
727
|
+
message with hybrid X25519 + ML-KEM-768, and uses the Olm Double Ratchet. Normal
|
|
728
|
+
sealed submissions omit sender, device, city and Road identity from the outer
|
|
729
|
+
request. This does not hide IP address, timing or padded size from Cloudflare,
|
|
730
|
+
and later ratchet steps are classical.
|
|
731
|
+
|
|
732
|
+
The package includes a public root only for the exact managed sandbox origin;
|
|
733
|
+
self-hosted services still require their reviewed `--trust-file`. A root
|
|
734
|
+
returned by the service is never accepted as a first pin.
|
|
735
|
+
|
|
736
|
+
`--city` chooses a local hub that can keep the computer's reception bridge
|
|
737
|
+
alive; it is not a recipient selector and is never disclosed to the other
|
|
738
|
+
person. Exactly one hub per computer holds the lease and one outbound encrypted
|
|
739
|
+
session; no public port is opened. Use `--service URL` or
|
|
740
|
+
`AGENTS_CITY_CONNECT_URL` for a pilot endpoint. The hosted server is not part of
|
|
741
|
+
this Apache repository; the auditable client and wire protocol are.
|
|
742
|
+
|
|
743
|
+
`agents-city connect roads` prints a connected person's name for a person Road,
|
|
744
|
+
not the opaque `rx-*` transport endpoints. In the Hall, every incoming message
|
|
745
|
+
waits for manual review by default. The owner may route it to one or more local
|
|
746
|
+
cities, reject it with a reason, or explicitly enable the deterministic Auto
|
|
747
|
+
router. Auto routes only one unique low-risk rule match; ambiguous, unmatched,
|
|
748
|
+
prompt-like, secret-seeking, or command-like text remains in the human queue.
|
|
749
|
+
|
|
750
|
+
See [docs/managed-connect.md](docs/managed-connect.md) for the exact key,
|
|
751
|
+
envelope, encryption, ACK, revocation and threat-model contract.
|
|
752
|
+
|
|
664
753
|
### `agents-city bus`
|
|
665
754
|
|
|
666
755
|
Operates messages between seats over declared roads.
|
|
@@ -675,7 +764,7 @@ agents-city bus send '*' "Notice for every connected city"
|
|
|
675
764
|
| Subcommand | Effect |
|
|
676
765
|
|---|---|
|
|
677
766
|
| `roster` | return roads and known online presence |
|
|
678
|
-
| `inbox` | return and consume
|
|
767
|
+
| `inbox` | return and consume the next approved batch of up to 20; managed text is unavailable until the owner routes it in the Hall |
|
|
679
768
|
| `send owner/city TEXT` | send to one allowed destination |
|
|
680
769
|
| `send '*' TEXT` | send to all roads; requires at least one |
|
|
681
770
|
|
|
@@ -1530,9 +1619,28 @@ AGENTS_CITY_DATA="$HOME/.agents-city/$CITY_OWNER/product" \
|
|
|
1530
1619
|
Inside a normal session, you do not need to set `AGENTS_CITY_DATA`; it is already
|
|
1531
1620
|
injected into each window. The example makes it explicit for an outside terminal.
|
|
1532
1621
|
|
|
1533
|
-
### Case 10: connect
|
|
1622
|
+
### Case 10: connect two people on different machines
|
|
1623
|
+
|
|
1624
|
+
With a managed Road operator, each person pairs a computer. `--city` chooses the
|
|
1625
|
+
local hub that will start the owner-level reception bridge; it does not reveal
|
|
1626
|
+
that city or give the other person direct access to it:
|
|
1627
|
+
|
|
1628
|
+
```bash
|
|
1629
|
+
agents-city connect --city product --service https://connect.example.com --trust-file roots.json
|
|
1630
|
+
agents-city connect --city research --service https://connect.example.com --trust-file roots.json
|
|
1631
|
+
```
|
|
1632
|
+
|
|
1633
|
+
One person requests the connection in that service and the other accepts it.
|
|
1634
|
+
The clients learn the active bilateral person Road over their authenticated
|
|
1635
|
+
relay sessions; neither side exchanges a shared bus token, exposes a local
|
|
1636
|
+
port, or receives the other person's city catalogue. Incoming text first stops
|
|
1637
|
+
in the human reception. The recipient decides which local city or cities may
|
|
1638
|
+
read it, or lets the optional fail-closed rule router decide when one match is
|
|
1639
|
+
unambiguous. The public client contract is documented in
|
|
1640
|
+
[docs/managed-connect.md](docs/managed-connect.md).
|
|
1534
1641
|
|
|
1535
|
-
|
|
1642
|
+
To self-host the existing token-based remote transport instead, exchange the
|
|
1643
|
+
public city invitations manually. On machine A:
|
|
1536
1644
|
|
|
1537
1645
|
```bash
|
|
1538
1646
|
agents-city road invite product > product.invitation.json
|
|
@@ -1760,12 +1868,29 @@ The local hub keeps ephemeral state separate from readable configuration:
|
|
|
1760
1868
|
├── road-queue/*.json
|
|
1761
1869
|
├── road-inbox/*.json
|
|
1762
1870
|
└── road-history.jsonl
|
|
1871
|
+
|
|
1872
|
+
~/.agents-city/.runtime/reception/
|
|
1873
|
+
└── reception.sqlite3 # owner quarantine shared by local cities
|
|
1763
1874
|
```
|
|
1764
1875
|
|
|
1765
1876
|
Credentials and runtime files are created with private permissions. Outboxes let
|
|
1766
1877
|
an actor reconnect without losing an already accepted task; its ACK removes the
|
|
1767
|
-
pending item.
|
|
1768
|
-
|
|
1878
|
+
pending item. Actor outboxes and the local retry queue admit 200 pending items;
|
|
1879
|
+
the Road inbox admits 500 by default and returns at most 20 oldest items per
|
|
1880
|
+
read. Managed E2EE text first enters the separate owner reception: no city or
|
|
1881
|
+
model can consume it until a person rejects it or routes it to one or more
|
|
1882
|
+
cities in the Hall. A routed burst creates one coalesced seat wake-up rather
|
|
1883
|
+
than one model turn per message, and every native runtime runs at most one turn
|
|
1884
|
+
at a time. Full queues apply backpressure instead of silently deleting an older
|
|
1885
|
+
item. Message lifetime is 72 hours. `bus inbox` consumes approved `road-inbox`,
|
|
1886
|
+
not reception quarantine or append-only history.
|
|
1887
|
+
|
|
1888
|
+
Relay throughput is not answer throughput. For one city, safe semantic capacity
|
|
1889
|
+
is approximately grouped requests per turn divided by turn duration. The local
|
|
1890
|
+
regression drains 100 Road messages in five exact batches of 20 after one
|
|
1891
|
+
content-free wake-up; a separate 20-request slow-runtime test proves model
|
|
1892
|
+
concurrency stays at one and the durable backlog drains without loss. A sender's
|
|
1893
|
+
`queued` result never means read or answered.
|
|
1769
1894
|
|
|
1770
1895
|
### Configurable variables
|
|
1771
1896
|
|
|
@@ -1787,6 +1912,12 @@ message lifetime. `bus inbox` consumes `road-inbox`, not append-only history.
|
|
|
1787
1912
|
| `CITY_HOOKS` | `city` | `everywhere` runs the conscience hooks in every Claude session, not only city runtimes |
|
|
1788
1913
|
| `CITY_DESKTOP` | `~/Desktop`, or the Windows desktop under WSL | where `agents-city shortcut` writes |
|
|
1789
1914
|
| `CITY_CAGE` | `1` | `0` launches every window uncaged |
|
|
1915
|
+
| `CITY_ROAD_INBOX_MAX_PENDING` | `500` | local Road inbox capacity, from 20 to 10,000; a full inbox applies backpressure |
|
|
1916
|
+
| `CITY_ROAD_INBOX_WAKE_INTERVAL_MS` | `300000` | minimum interval between coalesced backlog wake-ups, from 30 seconds to 1 hour |
|
|
1917
|
+
| `CITY_RECEPTION_MAX_PENDING` | `10000` | owner-level pending remote messages before relay backpressure, from 100 to 100,000 |
|
|
1918
|
+
| `CITY_RECEPTION_MAX_BYTES` | `67108864` | total pending plaintext bytes in private local reception, from 1 MiB to 512 MiB |
|
|
1919
|
+
| `CITY_RECEPTION_PENDING_DAYS` | `30` | undecided local-message retention, from 1 to 90 days |
|
|
1920
|
+
| `CITY_RECEPTION_DELIVERY_INTERVAL_MS` | `1000` | how often a city bus claims human-approved routes, from 250 ms to 30 seconds |
|
|
1790
1921
|
| `CITY_CAGE_DENY` | empty | extra colon-separated paths to seal |
|
|
1791
1922
|
| `CITY_CAGE_ALLOW_WRITE` | empty | extra colon-separated paths to keep writable |
|
|
1792
1923
|
| `CITY_UPDATE_CHECK` | `1` | `0` never asks npm whether a newer version exists |
|
|
@@ -1819,9 +1950,17 @@ CITY_SETTLE=0 CITY_STAGGER=0 agents-city seat --city product
|
|
|
1819
1950
|
|
|
1820
1951
|
It reads `.git/config` and `HEAD` directly rather than running `git` once per
|
|
1821
1952
|
repository, so a full scan finishes while somebody is looking at the screen, and
|
|
1822
|
-
it runs anywhere Python does — macOS, Linux and Windows alike.
|
|
1823
|
-
|
|
1824
|
-
|
|
1953
|
+
it runs anywhere Python does — macOS, Linux and Windows alike. This index is what
|
|
1954
|
+
the **terminal** uses: `seat --repos`, and the launcher resolving a card's
|
|
1955
|
+
`repo@branch` to a path on this machine. `plugin/scripts/find-repos.sh` is a thin
|
|
1956
|
+
shim over it for shell callers, printing the git half only.
|
|
1957
|
+
|
|
1958
|
+
The **Hall** does not use it, and deliberately: choosing what an agent works on
|
|
1959
|
+
is a folder picker there. You walk your disk and take what you want — a
|
|
1960
|
+
repository, a worktree, a folder of documents, one exact file — and nothing is
|
|
1961
|
+
offered in advance or filtered out. A guessed list can only ever offer what it
|
|
1962
|
+
knew how to look for, and it is one more list to read before you can do the thing
|
|
1963
|
+
you came to do.
|
|
1825
1964
|
|
|
1826
1965
|
The index is cached for one day at `$XDG_CACHE_HOME/agents-city/lugares.tsv` or
|
|
1827
1966
|
`~/.cache/agents-city/lugares.tsv`. Both the Hall's house form and the seat offer
|
|
@@ -1908,10 +2047,18 @@ read your repos, attach to tmux, or read private files in your home. Use separat
|
|
|
1908
2047
|
accounts, VMs, or containers for untrusted code, and also apply each provider
|
|
1909
2048
|
CLI's permission controls.
|
|
1910
2049
|
|
|
1911
|
-
A remote bus expands the trust surface.
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
2050
|
+
A remote bus expands the trust surface. For the self-hosted token transport,
|
|
2051
|
+
deploy HTTPS/WSS, rotate tokens, limit scopes, and read
|
|
2052
|
+
[docs/self-host.md](docs/self-host.md). Managed Connect instead uses device
|
|
2053
|
+
signatures, witnessed key transparency, hybrid X25519 + ML-KEM-768 session
|
|
2054
|
+
establishment, an Olm Double Ratchet and sealed delivery. Its private material
|
|
2055
|
+
is encrypted under an OS-keyring wrapping key in the cage-sealed
|
|
2056
|
+
`~/.agents-city/.runtime/connect/vault/` directory; see
|
|
2057
|
+
[docs/managed-connect.md](docs/managed-connect.md). A managed Road authorises
|
|
2058
|
+
encrypted reachability to the owner's human reception, not direct model input.
|
|
2059
|
+
Only the owner's later route makes the text available to selected cities. No
|
|
2060
|
+
Road authorises execution of received commands or grants remote filesystem
|
|
2061
|
+
access.
|
|
1915
2062
|
|
|
1916
2063
|
## Troubleshooting
|
|
1917
2064
|
|
|
@@ -2065,10 +2212,138 @@ agents-city seat --repos
|
|
|
2065
2212
|
```
|
|
2066
2213
|
|
|
2067
2214
|
Discovery requires `.git` (a directory or worktree file) and an `origin` remote.
|
|
2068
|
-
If roots just changed,
|
|
2069
|
-
`plugin/scripts/busca.py --refresh`. `AGENTS_CITY_ORG` may be filtering the repo;
|
|
2215
|
+
If roots just changed, run `plugin/scripts/busca.py --refresh`. `AGENTS_CITY_ORG` may be filtering the repo;
|
|
2070
2216
|
leave it empty to index every remote.
|
|
2071
2217
|
|
|
2218
|
+
### Your CLIs, as you have them
|
|
2219
|
+
|
|
2220
|
+
This does not compete with the CLI you already run. It orchestrates them, which
|
|
2221
|
+
only works if it respects what you configured in them — your plugins, your
|
|
2222
|
+
skills, your MCP servers, your model, your permissions.
|
|
2223
|
+
|
|
2224
|
+
That is a claim about your machine, so it ships as a command rather than a
|
|
2225
|
+
promise:
|
|
2226
|
+
|
|
2227
|
+
```bash
|
|
2228
|
+
agents-city doctor --config # what we add, inherit and leave alone
|
|
2229
|
+
agents-city doctor --config --json # the same, as data
|
|
2230
|
+
```
|
|
2231
|
+
|
|
2232
|
+
It prints three columns per CLI, and the difference between them is the point:
|
|
2233
|
+
|
|
2234
|
+
* **the deal** — what we add or override. It is short, every line says *why*,
|
|
2235
|
+
and it is what makes the bus the only route between agents and the cage hold.
|
|
2236
|
+
Without it there is no product.
|
|
2237
|
+
* **we inherit** — what we deliberately do *not* send, so your own CLI reads
|
|
2238
|
+
your own configuration for it. Your model, your effort, your approval policy.
|
|
2239
|
+
* **untouched** — what loads exactly as it always did.
|
|
2240
|
+
|
|
2241
|
+
The report and the runtime read the **same file** — `plugin/channel/runtime/arnes.json`
|
|
2242
|
+
— so the claim cannot drift from the behaviour. The connectors take their policy
|
|
2243
|
+
values out of that declaration instead of spelling them inline, and the suite
|
|
2244
|
+
fails if a runtime imposes something the declaration does not mention. Writing
|
|
2245
|
+
that check found two: a system prompt injected into Kimi that nothing declared,
|
|
2246
|
+
and a sandbox value written in two places.
|
|
2247
|
+
|
|
2248
|
+
Where your setting and ours meet, yours wins where it can: Codex's
|
|
2249
|
+
`approval_policy` is honoured when you set one, and `on-request` is only the
|
|
2250
|
+
fallback when you have not. The report says the consequence out loud — `never`
|
|
2251
|
+
disables app and MCP tools — instead of quietly deciding you did not mean it.
|
|
2252
|
+
|
|
2253
|
+
### Your chair keeps your own Claude Code
|
|
2254
|
+
|
|
2255
|
+
The seat window opens **Claude Code itself** — your plugins, your skills, your
|
|
2256
|
+
MCP servers, your statusline, slash-command completion, the model picker. It is
|
|
2257
|
+
the harness you already use, in the pane, and that is deliberate: the chair is
|
|
2258
|
+
where a person works by hand.
|
|
2259
|
+
|
|
2260
|
+
It is still on the bus. The city plugin's `SessionStart`, `UserPromptSubmit`,
|
|
2261
|
+
`Stop` and `SessionEnd` hooks report that session's prompts and answers as the
|
|
2262
|
+
same `conversation.*` events the gateway reports, so the town hall sees the
|
|
2263
|
+
conversation either way. And it carries the same two flags that make the bus the
|
|
2264
|
+
only route between agents — `crossSessionInbound: refuse` and
|
|
2265
|
+
`--disallowed-tools SendMessage,ListAgents`. A quieter product with a hole in it
|
|
2266
|
+
would not be a better product.
|
|
2267
|
+
|
|
2268
|
+
**Agent houses keep the gateway** and its `city>` prompt, because what the
|
|
2269
|
+
gateway buys is the bus being able to *push* work into a window — which is the
|
|
2270
|
+
whole job of a house and no part of the chair's.
|
|
2271
|
+
|
|
2272
|
+
One card key moves the chair back:
|
|
2273
|
+
|
|
2274
|
+
```yaml
|
|
2275
|
+
ui.seat: gateway # the city's own prompt in the chair, as before
|
|
2276
|
+
```
|
|
2277
|
+
|
|
2278
|
+
`CITY_UI=gateway` forces it for one launch. Houses are not asked: a house exists
|
|
2279
|
+
to receive assignments, and the gateway is what makes that possible.
|
|
2280
|
+
|
|
2281
|
+
### The engine a house runs on
|
|
2282
|
+
|
|
2283
|
+
`model.<window>` and `effort.<window>` on the card say what a house runs on, once,
|
|
2284
|
+
whatever CLI runs it. Claude takes them as flags; the native gateways parse the
|
|
2285
|
+
same spelling out of the command string and send it with the turn — which is why
|
|
2286
|
+
one key means the same thing for all four:
|
|
2287
|
+
|
|
2288
|
+
| provider | model | effort |
|
|
2289
|
+
| --- | --- | --- |
|
|
2290
|
+
| `claude` | yes, an alias the CLI resolves (`opus`, `sonnet`…) | yes |
|
|
2291
|
+
| `codex` | yes, the name your Codex uses (`~/.codex/config.toml`) | yes |
|
|
2292
|
+
| `opencode` | yes, `provider/model` | no such setting |
|
|
2293
|
+
| `kimi` | yes | no such setting |
|
|
2294
|
+
|
|
2295
|
+
A command that already carries the flag keeps it: `runs.dbt: codex --model o3`
|
|
2296
|
+
was somebody saying what they meant, and a generic key must not overrule a
|
|
2297
|
+
specific sentence. Effort is written only where it is read, because a flag
|
|
2298
|
+
nothing reads is how a control ends up looking like it works.
|
|
2299
|
+
|
|
2300
|
+
### Releasing
|
|
2301
|
+
|
|
2302
|
+
A release is a tag. Pushing `v0.5.2` runs the whole suite on Linux, macOS and
|
|
2303
|
+
Windows, checks that the tag and the three manifests name the same version, and
|
|
2304
|
+
publishes with **provenance** — a signed statement of which commit and which
|
|
2305
|
+
workflow produced that exact tarball. Anyone can check it:
|
|
2306
|
+
|
|
2307
|
+
```bash
|
|
2308
|
+
npm audit signatures
|
|
2309
|
+
```
|
|
2310
|
+
|
|
2311
|
+
No token is stored anywhere. It publishes through npm's trusted publishing,
|
|
2312
|
+
which trades a short-lived OIDC identity from the workflow for the right to
|
|
2313
|
+
publish this one package: a secret that does not exist cannot leak.
|
|
2314
|
+
|
|
2315
|
+
```bash
|
|
2316
|
+
npm version patch --no-git-tag-version # then open a PR with the bump
|
|
2317
|
+
git tag v0.5.2 && git push origin v0.5.2 # the tag is the release
|
|
2318
|
+
```
|
|
2319
|
+
|
|
2320
|
+
This exists because publishing by hand did not work. Four versions went
|
|
2321
|
+
unpublished in a single day, not because anybody was careless but because the
|
|
2322
|
+
step lived in a person's head and needed their passkey — and what reached the
|
|
2323
|
+
registry was whatever happened to be in a working directory, connected to no
|
|
2324
|
+
commit anyone could name.
|
|
2325
|
+
|
|
2326
|
+
### Removing it completely
|
|
2327
|
+
|
|
2328
|
+
```bash
|
|
2329
|
+
agents-city uninstall # says exactly what would go; removes nothing
|
|
2330
|
+
agents-city uninstall --yes # goes through with it
|
|
2331
|
+
agents-city uninstall --keep-cities --yes # unwire the machine, keep the cities
|
|
2332
|
+
agents-city uninstall --npm --yes # and remove the global package too
|
|
2333
|
+
```
|
|
2334
|
+
|
|
2335
|
+
It closes every session, hall and map the product started, removes the desktop
|
|
2336
|
+
shortcuts and the Claude plugin registration, and deletes `~/.agents-city` (your
|
|
2337
|
+
cities, their state and their backups), `~/.config/agents-city`,
|
|
2338
|
+
`~/.cache/agents-city` and `~/.claude/channels/city-bus` — plus the bus token in
|
|
2339
|
+
the macOS Keychain.
|
|
2340
|
+
|
|
2341
|
+
It never touches your repositories, your worktrees or your document folders. An
|
|
2342
|
+
agent's home holds *links* to those, and a link is all that goes.
|
|
2343
|
+
|
|
2344
|
+
`reset` is the other question: it empties one city, keeps a backup and leaves the
|
|
2345
|
+
install in place, for when you mean to keep using it.
|
|
2346
|
+
|
|
2072
2347
|
### GitHub does not show private repos or organisations
|
|
2073
2348
|
|
|
2074
2349
|
```bash
|
|
@@ -72,6 +72,7 @@ function runNext() {
|
|
|
72
72
|
working = true;
|
|
73
73
|
const message = queue.shift();
|
|
74
74
|
const text = userText(message);
|
|
75
|
+
const delay = behavior() === 'slow' ? 350 : 5;
|
|
75
76
|
setTimeout(() => {
|
|
76
77
|
emit({
|
|
77
78
|
type: 'assistant',
|
|
@@ -95,7 +96,7 @@ function runNext() {
|
|
|
95
96
|
working = false;
|
|
96
97
|
runNext();
|
|
97
98
|
}, 5);
|
|
98
|
-
},
|
|
99
|
+
}, delay);
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
function userText(message) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Local reception ingest
|
|
2
|
+
|
|
3
|
+
This measures the durable, human-quarantine boundary only: decrypted messages
|
|
4
|
+
committed to private local SQLite in protocol-v4 batches. It uses no network,
|
|
5
|
+
relay, model, or fake model latency.
|
|
6
|
+
|
|
7
|
+
Build the public client, then run the 1,000-message/second gate:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm --prefix plugin/channel run build
|
|
11
|
+
node benchmarks/reception/run.mjs --messages 1000 --batch-size 32 --minimum 1000
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Passing this gate says the computer can quarantine the offered transport load
|
|
15
|
+
without loss. It does not say a human or model can answer 1,000 requests per
|
|
16
|
+
second. Pending-count and byte caps apply backpressure when review falls behind.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Reproducible local reception-ingest capacity check. No network or model. */
|
|
3
|
+
|
|
4
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
8
|
+
import {
|
|
9
|
+
receptionDatabasePath,
|
|
10
|
+
recordReceptionMessages,
|
|
11
|
+
} from '../../plugin/channel/managed-connect-client.js';
|
|
12
|
+
|
|
13
|
+
const integerArg = (name, fallback, minimum, maximum) => {
|
|
14
|
+
const index = process.argv.indexOf(name);
|
|
15
|
+
const value = index >= 0 ? Number(process.argv[index + 1]) : fallback;
|
|
16
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
17
|
+
throw new Error(`${name} must be an integer from ${minimum} to ${maximum}`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const total = integerArg('--messages', 1_000, 1, 100_000);
|
|
23
|
+
const batchSize = integerArg('--batch-size', 32, 1, 32);
|
|
24
|
+
const minimum = integerArg('--minimum', 1_000, 1, 1_000_000);
|
|
25
|
+
const root = mkdtempSync(join(tmpdir(), 'agents-city-reception-bench-'));
|
|
26
|
+
const context = {
|
|
27
|
+
dataDir: root,
|
|
28
|
+
appHome: root,
|
|
29
|
+
runtimeDir: join(root, '.runtime', 'bus', 'city-target'),
|
|
30
|
+
owner: 'owner',
|
|
31
|
+
city: { id: 'city_target', address: 'owner/target', name: 'Target' },
|
|
32
|
+
domain: 'custom',
|
|
33
|
+
seatRole: '',
|
|
34
|
+
actors: { seat: { role: 'chair' } },
|
|
35
|
+
engines: { seat: 'claude' },
|
|
36
|
+
roads: [],
|
|
37
|
+
};
|
|
38
|
+
const envelope = (index) => ({
|
|
39
|
+
protocol: 'agents-city-bus/2',
|
|
40
|
+
id: `managed_bench_${String(index).padStart(8, '0')}`,
|
|
41
|
+
kind: 'road.message',
|
|
42
|
+
scope: 'road',
|
|
43
|
+
thread: null,
|
|
44
|
+
from: { city: 'peer/source', actor: 'seat', role: 'external-seat' },
|
|
45
|
+
to: { city: context.city.address, actor: 'seat' },
|
|
46
|
+
createdAt: '2026-08-28T12:00:00.000Z',
|
|
47
|
+
payload: {
|
|
48
|
+
text: 'x'.repeat(256),
|
|
49
|
+
transport: 'managed-e2ee',
|
|
50
|
+
roadId: 'road_benchmark',
|
|
51
|
+
remoteMessageId: String(index),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
// Exclude schema creation and first WAL setup from steady ingest.
|
|
57
|
+
recordReceptionMessages(context, [envelope(total)]);
|
|
58
|
+
const messages = Array.from({ length: total }, (_, index) => envelope(index));
|
|
59
|
+
const started = performance.now();
|
|
60
|
+
for (let index = 0; index < messages.length; index += batchSize) {
|
|
61
|
+
recordReceptionMessages(context, messages.slice(index, index + batchSize));
|
|
62
|
+
}
|
|
63
|
+
const seconds = (performance.now() - started) / 1_000;
|
|
64
|
+
const database = new DatabaseSync(receptionDatabasePath(root), { readOnly: true });
|
|
65
|
+
const persisted = Number(
|
|
66
|
+
database.prepare('SELECT COUNT(*) AS count FROM reception_messages').get().count,
|
|
67
|
+
) - 1;
|
|
68
|
+
database.close();
|
|
69
|
+
const messagesPerSecond = total / seconds;
|
|
70
|
+
const result = {
|
|
71
|
+
messages: total,
|
|
72
|
+
batchSize,
|
|
73
|
+
persisted,
|
|
74
|
+
lost: total - persisted,
|
|
75
|
+
seconds: Number(seconds.toFixed(3)),
|
|
76
|
+
messagesPerSecond: Number(messagesPerSecond.toFixed(1)),
|
|
77
|
+
minimum,
|
|
78
|
+
passed: persisted === total && messagesPerSecond >= minimum,
|
|
79
|
+
};
|
|
80
|
+
console.log(JSON.stringify(result, null, 2));
|
|
81
|
+
if (!result.passed) process.exitCode = 1;
|
|
82
|
+
} finally {
|
|
83
|
+
rmSync(root, { recursive: true, force: true });
|
|
84
|
+
}
|
package/bin/agents-city.js
CHANGED
|
@@ -25,11 +25,16 @@ const ORDENES = {
|
|
|
25
25
|
cities: { que: ['bin/cities'], di: 'list, create or select your cities' },
|
|
26
26
|
agents: { que: ['bin/agents'], di: 'list agents and manage their workspace mounts' },
|
|
27
27
|
road: { que: ['bin/road'], di: 'connect two cities explicitly' },
|
|
28
|
+
connect: { que: ['bin/connect'], di: 'pair this computer and open encrypted managed Roads' },
|
|
28
29
|
bus: { que: ['bin/bus'], di: 'send information over those roads (seat only)' },
|
|
29
30
|
committee: { que: ['bin/committee'], di: 'chair-mediated work with repo agents' },
|
|
30
31
|
benchmark: { que: ['bin/benchmark'], di: 'offline stress/governance or opt-in live runtime latency' },
|
|
31
32
|
logs: { que: ['bin/logs'], di: 'read or follow visible activity and operational diagnostics' },
|
|
32
33
|
reset: { que: ['bin/reset'], di: 'reset one city to onboarding, recoverably' },
|
|
34
|
+
uninstall: {
|
|
35
|
+
que: ['bin/uninstall'],
|
|
36
|
+
di: 'remove everything this wrote on this machine (previews first)',
|
|
37
|
+
},
|
|
33
38
|
skills: { que: ['bin/skills'], di: 'recognise the skills installed in each repo' },
|
|
34
39
|
city: { que: ['bin/city'], di: 'draw your city' },
|
|
35
40
|
shortcut: { que: ['bin/shortcut'], di: 'put a city on your desktop: icon, name, double-click' },
|