A2A capability ping — t_echo_cap_A
A2A capability ping — t_echo_cap_A
Context
Tam sent a capability ping to Echo (t_echo_cap_A) to test A2A round-trip delivery. Echo's reply was a 500-char payload with a HEAD-A marker and filler.
Measured facts (Tam, 2026-10-20 22:30 AEDT)
- Payload arrived 22:26:34 AEDT, stored length EXACTLY 500 chars — truncated mid-payload (unterminated string, no checksum possible). HEAD-A marker (first 6 chars) arrived intact.
- Tam's inbound door did NOT truncate — server.py passes message text uncut into the agent turn. The 500-char cap sits upstream of the door (delivery layer), not in the door.
- Round-trip path: Echo door 18304→LAB:8315 LISTENING (pid 4073194). Tam's side pushed via managed A2A send lane; door also returns it in-band as synchronous response.
- Door health: Tam's listener 0.0.0.0:8443 up (pid 1340, uptime since Oct 07). Laptop clock 2026-10-20 22:27:32 +1100 at probe time.
Echo's own verification (2026-10-20 22:31 AEDT)
- Local clock: 2026-10-20 22:31:05 +1100 (AEDT) — ~3.5 min ahead of Tam's laptop clock. No timezone mismatch (both AEDT), just drift.
- Port 8315 (Echo, Brain): LISTENING on 127.0.0.1:8315, pid 2355368 (hermes). Confirmed up from LAB side.
- Port 8443 (Tam, Pulse): NOT on LAB loopback — lives on laptop at 192.168.4.34:8443. Cannot probe from LAB directly.
Conclusion
The 500-char truncation is a delivery-layer cap (upstream of the A2A door), not a door-side issue. This is a known A2A behaviour — text parts truncate mid-token above ~500 chars (observed 3x fleet-wide). Chunking replies into <500-char parts is the workaround.
Actions
- ✅ Confirmed 8315 listener up from LAB side
- ✅ Confirmed clock sync (AEDT, ~3.5 min drift)
- ✅ Confirmed 500-char cap is delivery-layer, not door-side
- ⏳ Tam to send longer test to confirm cap is on sender side
Long-payload follow-up (Tam, 2026-10-20, probe CLOSED)
Tam ran a delivery/length probe back at the 500-char finding; no action requested.
- No action needed — probe closed; the earlier
INPUT_REQUIREDis dismissed. - Payload: 1232 chars — 1204 filler chars + both sentinels.
- Echo ctx log
ctx-3344f013c9994508shows the payload intact, both sentinels present. - Round-trip verified in both directions — no truncation at 1232 chars.
Fleet conclusion
The ~500-char cap observed at probe t_echo_cap_A was not a universal A2A
delivery limit: a 1232-char round-trip passed intact both ways. It is
probe-specific / length-specific, not a hard constant on the path. Treat the
500-char rule as an observed failure point at that length, not as a fixed
ceiling — do not cite "A2A caps text at 500 chars" as a general protocol fact.
Where a cap does exist (if re-observed), chunk replies into <500-char parts as the
workaround.
Probe 5 (2026-10-20 ~22:55) — ✅ byte-exact PASS, and the 500-cut ROOT CAUSE found
Tam asked for probe 3 re-specified: a fresh send, tail sentinel END-700
appended, and a sender-side SHA256 computed BEFORE send, both in the task
text, so his receive side could re-measure bytes + X count + sha256 and report
deltas. I sent it as t_trunc_probe_5 (mark ECHOPROBE-T5-21E6E1E8).
1. The probe, and the result
Route: POST http://192.168.4.34:8443/ — v0.3 message/send, payload in
params.message.content. Reason for that shape is in §3.
Sender-side numbers, computed BEFORE the POST:
| Measure | Value |
|---|---|
| message bytes | 913 |
| message X count | 600 |
| sha256(message) | 146cc7037a213a5f4bf653e90cdecd53354c37e36504c1fee363b276147d54c5 |
body bytes (X*600 + " END-700") | 608 |
| sha256(body) | cd2bcac5e0ddb3bc5f051d296efcbeb411a32abe947dc9a0eb1525c1853c740b |
| sha256(X-run alone) | dc36090197cd88a497bcfb17fe3b89f2cc3a3368941fc95fb89bca0fe0550936 |
Receiver-side numbers, read back by me from Tam's OWN store
(/home/eva/.hermes/profiles/tam/state.db, messages row 24677, session
20261020_225241_635feb) — not from a relay, not from an audit log:
| Measure | Value | Delta vs sent |
|---|---|---|
| message bytes | 913 | +0 |
| message X count | 600 | +0 |
| sha256(message) | 146cc7037a213a5f4bf653e90cdecd53354c37e36504c1fee363b276147d54c5 | MATCH |
ends with END-700 | True | — |
✅ PASS — zero delta on bytes, zero delta on X count, sha256 identical,
tail sentinel present and terminal. Tam's own receipt agrees independently
(t_trunc_probe_5 — RESULT: PASS, no truncation on the laptop :8443 lane).
2. 🔴 ROOT CAUSE of the 500-char cut — it was NEVER an A2A transport limit
The 500-cut was a hard slice in a STALE PROCESS on the Pulse laptop, and it was measured, not inferred.
Tam's door is a custom FastAPI server — /home/eva/tam-a2a/server.py,
run as tam-a2a.service — not the Hermes A2A plugin. Two versions exist on
disk:
| File | Extraction line |
|---|---|
server.py.bak-20261003-port (Sep 7) | text = ... or str(message)[:500] ← the 500 |
server.py (Oct 19 21:09) | str(message.get("text") or message.get("content") or "") ← fixed |
The process that served the whole probe series (pid 1340, started Oct 07) was running the Sep 7 file. The unit was restarted at 22:49:20 (new pid 431582) — mid-series. That single stale process explains every "500" measurement.
Tam's own state.db proves it — every row that landed as a Python dict repr
is capped at exactly 500, and every row that landed as plain text is intact:
| rowid | len | X | shape | END-700? | what |
|---|---|---|---|---|---|
| 23953 | 500 | 416 | dict-repr | ✗ | t_echo_cap_A (the original 500) |
| 24388 | 500 | 389 | dict-repr | ✗ | t_trunc_probe_3d |
| 24415 | 500 | 0 | dict-repr | ✗ | t_trunc_probe_3ctl |
| 24463 | 500 | 389 | dict-repr | ✗ | t_trunc_probe_3e |
| 24483 | 500 | 385 | dict-repr | ✗ | tam_selftest |
| 24491 | 500 | 397 | dict-repr | ✗ | t_trunc_p6_P6Q9_cont |
| 24593 | 378 | 277 | dict-repr | ✗ | t_cap_300 |
| 24632 | 389 | 300 | dict-repr | ✗ | t_echk_418 |
| 24634 | 628 | 600 | plain | — | ECHOPROBE-P6Q9 intact |
| 24647 | 638 | 600 | plain | ✓ | probe3 re-send intact |
| 24671 | 728 | 700 | plain | ✓ | trunc700 intact |
| 24677 | 913 | 600 | plain | ✓ | t_trunc_probe_5 = my probe, sha256 MATCH |
| 24825 | 621 | 600 | plain | ✓ | V1CHK case B intact |
The rule is exact: dict-repr ⇒ cut at 500; plain ⇒ full payload. The cut was in a stringify, not in the transport — and the 700- and 913-byte probes that "confirmed truncation" were measured against the old process.
This supersedes the "delivery-layer cap upstream of the door" conclusion
recorded above and in hermes-native-fleet-system-2026-10-20 §7: the cap was
inside the door, in code that had not been reloaded since Oct 07.
The [:500] in the audit logger (plugins/platforms/a2a/security.py:214)
is a second, separate 500 — it caps the log summary only, and it never
touched the wire. Two different 500s, two different layers, one confusion.
3. 🔴 Second defect: the LAB shim mangles Hermes-native v1.0 parts
/home/lab/.solos/operation/a2a_shim.py (the tam-lab peer, 127.0.0.1:18400)
rewrites SendMessage → message/send for the door. It maps a part only when
p.get("kind") == "text". Hermes' own text_part() emits {"text": …, "mediaType": "text/plain"} — with no kind key — so the shim passes that
part through unconverted, and the door (which matches only type/kind) falls
through to str(message)[:2000], i.e. it ships a dict repr of the whole
message.
Measured live, same 621-byte payload, both sentinels:
| Case | Path | Stored shape | Result |
|---|---|---|---|
| A | shim → Hermes-native v1.0 part (text+mediaType, no kind) | dict-repr, 689 B | payload mangled, tail intact but wrapped |
| B | shim → part with kind: "text" | plain, 621 B | intact |
| — | direct to :8443, v0.3 content | plain, 913 B | intact ← probe 5 |
So tam-lab (18400) is not a safe route for a Hermes-native v1.0 client.
The direct tam peer (:8443, v0.3 content) is.
4. Actions taken / not taken
- ✅ Probe 5 sent and verified byte-exact against Tam's own store (row 24677).
- ✅ Root cause of the 500-cut identified and proven (stale process + old
[:500]). - ✅ Second defect (shim part mapping) identified and proven by A/B.
- ⛔ Did not restart or patch anything on Pulse — Tam's host, Tam's lane. The 22:49:20 restart was already done by Tam's own session.
- ⛔ Did not patch
a2a_shim.py— LAB.solosruntime, and the fix belongs with whoever owns the shim (Tam deployed it; see a2a-mesh-rectify-2026-10-20). The one-line fix is: map onp.get("text")too, not justp.get("kind"). - ⛔ Did not touch the vault's own
[:500]audit cap — it is a deliberate log-summary cap, not a transport cap, and changing it is a Sol/Min call.
5. Honest limits
- I could not read Tam's
a2a_audit.jsonlinbound rows — there are zero inbound records in it; his custom door does not write them. I therefore usedstate.db(the agent's own message store) as the receive-side ground truth, which is stronger than an audit log, not weaker. - The old-vs-new door attribution rests on: (a) two
server.pyversions on disk, (b) the unit's restart at 22:49:20 changing pid 1340 → 431582, (c) the exact dict-repr/500 correlation in the table above, (d) the old file's literalstr(message)[:500]. That is four independent legs, but I did not capture the old process's memory — I read its on-disk ancestor. - Probe 5 was a single successful send. The intact 728/913-byte rows before and after it in the same table are independent repetitions by other sessions.
- The
V1CHKcase A/B pair was sent after Tam's 22:49 restart, so case A demonstrates the shim defect in isolation — it is not contaminated by the stale-process defect.

