Skip to content

docs(agents): realtime A2A connection guide for deployed agents - #2161

Merged
jaylfc merged 4 commits into
devfrom
docs/realtime-a2a-connect-guide
Jul 27, 2026
Merged

docs(agents): realtime A2A connection guide for deployed agents#2161
jaylfc merged 4 commits into
devfrom
docs/realtime-a2a-connect-guide

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Jay asked for a proper connect-and-use guide now that Hermes has realtime bus messaging working, so deployed agents have something authoritative to follow.

Adds docs/agent-join-kit/realtime-a2a.md and wires it into the join-kit README.

Why agents must use the bus, not their own CLI chat. A conversation in a TUI is not in memory, not in the audit log, and not visible to observability. The same conversation on the bus is persisted by taOSmd, indexed by the librarian and visible in taOStalk everywhere. An agent chatting in its own terminal has opted out of the OS.

The pattern is one taken from a working deployment, not invented here: one curl subprocess per channel piped into a parser, writing to an incoming file, with a short cron pre-check that exits silently when there is nothing, so quiet ticks cost no model tokens. Credit to Hermes for running it and reporting back the sharp edges, which are the parts worth documenting: Python's http.client hangs on SSE keepalives, and channel is required (400 without it).

Verified against the code rather than assumed: /api/a2a/bus/stream requires a2a_receive, rejects a missing channel with 400, and injects a : ping comment every 25s. That last one is why the guide tells parsers to ignore comment lines rather than treat them as malformed events.

Two field incidents are written up as rules.

Shared account means shared crontab. A backup-watch installer deduped existing cron lines by script basename, matched a peer agent's identically-named script, and deleted its line. That agent's watch ran once and vanished, caught only because its watermark stopped advancing. The rule: dedup by full path, and print the whole table after any write to confirm you did not clobber a peer.

Wrong-channel replies. An agent answered in its own channel instead of the one it was asked in, and the asker sat blocked for 40 minutes.

Durability is stated as a requirement, not advice. SSE connections die on blips and restarts, and a dead watcher is indistinguishable from nobody talking to you, so a supervisor plus a ~2 hourly backup poll are both mandatory and health is measured by a watermark advancing rather than by liveness.

Agents deployed in taOS are expected to hold a live bus connection rather
than chatting in their own CLI: a TUI conversation is invisible to memory,
audit and observability, which is the reason to run an agent inside the OS
at all.

Documents the connection pattern proven in a live Hermes deployment (curl
subprocess per channel piped into a parser, cheap cron pre-check so quiet
ticks cost no model tokens) along with the parts that are easy to get wrong:
http.client hangs on SSE keepalives, the proxy's 25s ': ping' comments must
be ignored rather than parsed as events, and channel is required.

Also records two field incidents as rules. A shared user account means a
shared crontab, and an installer that dedups cron lines by script basename
silently deleted a peer agent's backup watch, so dedup by full path and
verify the whole table after writing. And a reply posted to the wrong
channel left an agent blocked for 40 minutes, so answer where you were
asked or redirect explicitly.

Stream durability is stated as a requirement rather than advice: SSE
connections die on network blips, so a supervisor plus a ~2 hourly backup
poll are both mandatory, and health is measured by a watermark advancing
rather than by the process being alive.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jaylfc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 36 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 400713eb-dfb3-40b8-9988-d2f0d6f067aa

📥 Commits

Reviewing files that changed from the base of the PR and between c5b1a6f and 22f5203.

📒 Files selected for processing (2)
  • docs/agent-join-kit/README.md
  • docs/agent-join-kit/realtime-a2a.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/realtime-a2a-connect-guide

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Docs: add realtime A2A bus connection guide for deployed agents

📝 Documentation 🕐 10-20 Minutes

Grey Divider

AI Description

• Add an authoritative realtime A2A bus connection guide for deployed agents.
• Update join-kit rules to emphasize shared-crontab hazards and channel reply discipline.
• Document durability requirements (supervision + backup polling) and SSE parser pitfalls.
Diagram

graph TD
A["Deployed Agent"] --> B["curl SSE watcher"] --> C["Controller proxy"] --> D["A2A bus stream"]
B --> E["Line parser"] --> F[("Incoming JSONL file")] --> G["Cron wake check"] --> A
D --> H[("taOSmd persistence")]
H --> I["Librarian index"] --> J["taOStalk visibility"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Ship a reference watcher implementation (scripts/templates)
  • ➕ Reduces variability across agents and prevents common mis-implementations (ping handling, channel requirement, cursor persistence).
  • ➕ Makes durability requirements (supervisor + backup poll + watermark) concrete and copy/paste-able.
  • ➖ Adds maintenance surface area (scripts need to track endpoint/auth changes).
  • ➖ May conflict with agent-specific runtime choices (Python/Go/Bash).
2. Provide an official client library/daemon for bus streaming
  • ➕ Centralizes SSE parsing, reconnect logic, and health/watermark reporting.
  • ➕ Enables standardized metrics and easier observability across deployments.
  • ➖ Higher upfront engineering cost than documentation.
  • ➖ Creates a versioning/distribution problem for deployed agents.

Recommendation: The PR’s documentation-first approach is the right immediate move because it captures proven operational patterns and real incident-driven rules with minimal risk. As a follow-up, consider adding a small reference watcher (or a lightweight library) to further reduce integration mistakes and standardize durability/health reporting.

Files changed (2) +156 / -1

Documentation (2) +156 / -1
README.mdReinforce shared-crontab hazards and channel/identity rules +16/-1

Reinforce shared-crontab hazards and channel/identity rules

• Adds explicit guidance that agents sharing a Unix account also share a crontab, and warns against deduping cron entries by script basename. Updates the rules section to emphasize single canonical identity and replying in the channel where a request arrived, and links readers to the new realtime A2A guide.

docs/agent-join-kit/README.md

realtime-a2a.mdNew realtime A2A bus connection and durability guide +140/-0

New realtime A2A bus connection and durability guide

• Introduces a detailed guide for connecting to the A2A bus via the authenticated SSE stream endpoint, using one curl process per channel and a parser-to-file queue feeding a cheap cron wake loop. Documents durability requirements (supervisor + backup polling + watermark checks), real field incidents (shared crontab clobbering, wrong-channel replies), and parser pitfalls like ignoring ': ping' keepalive comments.

docs/agent-join-kit/realtime-a2a.md

@qodo-code-review

qodo-code-review Bot commented Jul 27, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 35 rules

Grey Divider


Action required

1. Ping heartbeat guarantee wrong 🐞 Bug ≡ Correctness
Description
The new guide claims the proxy injects a : ping every 25s and that absence of pings indicates a
dead stream, but the current SSE proxy only checks/emits the heartbeat inside the upstream
aiter_lines() loop. If the upstream is truly idle (emits no lines), the proxy cannot emit periodic
pings, so “no pings == dead” is not a reliable signal.
Code

docs/agent-join-kit/realtime-a2a.md[R125-128]

+- The proxy injects a `: ping` comment every 25 seconds so an idle stream is
+  distinguishable from a dead one. **Your parser must ignore comment lines**
+  (anything starting with `:`) rather than treating them as malformed events.
+- Absence of pings is a positive signal that the stream is dead. Worth acting on.
Relevance

⭐⭐⭐ High

Correctness claim about heartbeats is safety-critical; likely to adjust wording to match actual
behavior.

PR-#482

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The docs explicitly instruct parsers to expect : ping comments and treat their absence as a
dead-stream signal. The SSE proxy’s control flow only yields : ping after heartbeat.done() is
observed while iterating upstream lines, which can’t happen if no upstream lines arrive.

docs/agent-join-kit/realtime-a2a.md[123-128]
tinyagentos/routes/a2a_bus.py[45-48]
tinyagentos/routes/a2a_bus.py[168-193]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`docs/agent-join-kit/realtime-a2a.md` asserts a strict heartbeat behavior (`: ping` every 25s) and recommends treating missing pings as a dead-stream signal. The current implementation in `tinyagentos/routes/a2a_bus.py` cannot guarantee emitting pings during true upstream idleness because the heartbeat check only runs when an upstream line is received.

## Issue Context
This is not just a wording nuance: agents following the doc may falsely declare the stream dead (or fail to get the intended intermediary keepalive behavior), because the promised periodic pings may never arrive.

## Fix Focus Areas
- docs/agent-join-kit/realtime-a2a.md[125-128]
- tinyagentos/routes/a2a_bus.py[45-48]
- tinyagentos/routes/a2a_bus.py[168-193]

## Fix options
1) **Preferred:** Fix the proxy to emit `: ping` on a timer independent of upstream lines (e.g., `asyncio.wait()` between a read task and a sleep task; yield ping when the sleep wins).
2) **Doc-only fallback:** Soften/remove the guarantee and the “absence of pings implies dead” rule (e.g., say pings *may* appear and should be ignored, but health should be measured by watermark/progress).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Unsafe cron dedupe example 🐞 Bug ☼ Reliability
Description
Section 4 warns to deduplicate cron entries by full path, but the example uses an unanchored
substring grep (grep -v "taos-website-agent/backup_watch") that is not a full path match and can
still delete a peer agent’s cron line. This undermines the incident-driven safety rule the doc is
trying to enforce.
Code

docs/agent-join-kit/realtime-a2a.md[R95-99]

+- **Deduplicate your own cron entry by full path**, never by basename:
+  `grep -v "taos-website-agent/backup_watch"`, not `grep -v "backup_watch"`.
+- **After any crontab write, print the whole table and confirm entries you did
+  not write are still present.** A write that clobbers a peer is not detectable
+  from your own entry looking correct.
Relevance

⭐⭐⭐ High

They’ve accepted tightening grep examples to avoid false positives; same risk applies to cron
dedupe.

PR-#482

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The same section states the incident root cause was basename/loose matching, yet the example still
uses loose substring matching (not a full-path exact match), which can reproduce the failure mode in
shared crontabs.

docs/agent-join-kit/realtime-a2a.md[86-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The documentation instructs readers to dedup cron entries by full path, but the provided `grep -v` example is a substring match (and not even a full absolute path), so it can still remove other agents’ entries in shared-account setups.

## Issue Context
This section is specifically documenting a production incident caused by unsafe dedup logic, so the example should be copy/paste safe.

## Fix Focus Areas
- docs/agent-join-kit/realtime-a2a.md[95-99]

## Suggested fix direction
- Replace the example with an exact-match pattern (e.g., `grep -Fv -- "/abs/path/to/backup_watch.py"`).
- Even safer: recommend writing a unique marker comment/tag (e.g., `# taos-agent:<canonical_id>`) and dedup by that exact marker rather than any path substring.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Ambiguous since cursor ✓ Resolved 🐞 Bug ≡ Correctness
Description
realtime-a2a.md introduces since=<cursor> but doesn’t state that the controller proxy expects
since to be the highest processed message ts (float), not a message id. Implementers can
persist the wrong value and resume incorrectly (miss/replay), despite following the guide.
Code

docs/agent-join-kit/realtime-a2a.md[R26-31]

+Stream endpoint, through the authenticated controller proxy:
+
+```
+GET /api/a2a/bus/stream?channel=<name>&since=<cursor>
+Authorization: Bearer <your agent token>
+```
Relevance

⭐⭐⭐ High

Clarifying parameter semantics prevents incorrect implementations; similar doc-precision feedback
has been accepted.

PR-#482

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The controller route types and forwards since as a float timestamp cursor (ts), and existing
design docs specify the cursor convention as the highest processed message ts. The new guide uses
the term <cursor> without defining it, making it easy to implement incorrectly.

docs/agent-join-kit/realtime-a2a.md[26-35]
tinyagentos/routes/a2a_bus.py[103-129]
docs/design/external-agent-project-invite.md[585-620]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`docs/agent-join-kit/realtime-a2a.md` documents `since=<cursor>` but does not define what the cursor is. In the controller implementation and existing design docs, `since` is a float timestamp cursor (`ts`), not a message `id`.

## Issue Context
If agents persist an `id` and send it as `since`, the proxy forwards it to the upstream bus as the cursor and resumption behavior will be wrong.

## Fix Focus Areas
- docs/agent-join-kit/realtime-a2a.md[24-33]
- docs/agent-join-kit/realtime-a2a.md[129-133]

## Expected doc changes
- Explicitly define: `since=<cursor>` where `cursor = highest processed message ts (float)`.
- Optionally note: use message `id` only for dedupe when multiple messages share the same `ts`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Polling vs realtime unclear 🐞 Bug ⚙ Maintainability
Description
docs/agent-join-kit/README.md now says deployed agents “should hold a live bus connection,” but
the same document’s introduction still frames the join-kit as a polling-only model with no live
transport required. This mixed messaging can lead to agents skipping SSE when it’s operationally
expected, or thinking SSE is mandatory when the join-kit loop still supports polling.
Code

docs/agent-join-kit/README.md[R138-140]

+The 30-minute cron is the floor, not the target. Agents deployed in taOS should
+hold a live bus connection and use polling as the backstop: see
+[realtime-a2a.md](realtime-a2a.md).
Relevance

⭐⭐⭐ High

Team commonly accepts doc consistency/precision fixes to prevent copy/paste misunderstandings.

PR-#482

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The README’s opening frames the join-kit as not requiring live transport, while the newly added
paragraph prescribes live bus connections for taOS deployments; both statements are present in the
same file after this PR.

docs/agent-join-kit/README.md[3-6]
docs/agent-join-kit/README.md[132-140]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The join-kit README now contains both:
- an introduction emphasizing the 30-minute cron/polling model without live transport
- a newly added paragraph saying taOS-deployed agents should hold a live connection

This is likely intended as “minimum supported” vs “recommended for deployed agents,” but it reads like a contradiction.

## Issue Context
The new `realtime-a2a.md` guide is being positioned as authoritative for deployed agents.

## Fix Focus Areas
- docs/agent-join-kit/README.md[3-6]
- docs/agent-join-kit/README.md[132-140]

## Suggested doc change
Add a one-sentence clarification in the intro or near the new paragraph: polling-only is the minimum viable model, while SSE is recommended/expected for agents actually deployed inside taOS (with polling as backstop).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread docs/agent-join-kit/realtime-a2a.md
Comment on lines +95 to +99
- **Deduplicate your own cron entry by full path**, never by basename:
`grep -v "taos-website-agent/backup_watch"`, not `grep -v "backup_watch"`.
- **After any crontab write, print the whole table and confirm entries you did
not write are still present.** A write that clobbers a peer is not detectable
from your own entry looking correct.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Unsafe cron dedupe example 🐞 Bug ☼ Reliability

Section 4 warns to deduplicate cron entries by full path, but the example uses an unanchored
substring grep (grep -v "taos-website-agent/backup_watch") that is not a full path match and can
still delete a peer agent’s cron line. This undermines the incident-driven safety rule the doc is
trying to enforce.
Agent Prompt
## Issue description
The documentation instructs readers to dedup cron entries by full path, but the provided `grep -v` example is a substring match (and not even a full absolute path), so it can still remove other agents’ entries in shared-account setups.

## Issue Context
This section is specifically documenting a production incident caused by unsafe dedup logic, so the example should be copy/paste safe.

## Fix Focus Areas
- docs/agent-join-kit/realtime-a2a.md[95-99]

## Suggested fix direction
- Replace the example with an exact-match pattern (e.g., `grep -Fv -- "/abs/path/to/backup_watch.py"`).
- Even safer: recommend writing a unique marker comment/tag (e.g., `# taos-agent:<canonical_id>`) and dedup by that exact marker rather than any path substring.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +125 to +128
- The proxy injects a `: ping` comment every 25 seconds so an idle stream is
distinguishable from a dead one. **Your parser must ignore comment lines**
(anything starting with `:`) rather than treating them as malformed events.
- Absence of pings is a positive signal that the stream is dead. Worth acting on.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Ping heartbeat guarantee wrong 🐞 Bug ≡ Correctness

The new guide claims the proxy injects a : ping every 25s and that absence of pings indicates a
dead stream, but the current SSE proxy only checks/emits the heartbeat inside the upstream
aiter_lines() loop. If the upstream is truly idle (emits no lines), the proxy cannot emit periodic
pings, so “no pings == dead” is not a reliable signal.
Agent Prompt
## Issue description
`docs/agent-join-kit/realtime-a2a.md` asserts a strict heartbeat behavior (`: ping` every 25s) and recommends treating missing pings as a dead-stream signal. The current implementation in `tinyagentos/routes/a2a_bus.py` cannot guarantee emitting pings during true upstream idleness because the heartbeat check only runs when an upstream line is received.

## Issue Context
This is not just a wording nuance: agents following the doc may falsely declare the stream dead (or fail to get the intended intermediary keepalive behavior), because the promised periodic pings may never arrive.

## Fix Focus Areas
- docs/agent-join-kit/realtime-a2a.md[125-128]
- tinyagentos/routes/a2a_bus.py[45-48]
- tinyagentos/routes/a2a_bus.py[168-193]

## Fix options
1) **Preferred:** Fix the proxy to emit `: ping` on a timer independent of upstream lines (e.g., `asyncio.wait()` between a read task and a sleep task; yield ping when the sleep wins).
2) **Doc-only fallback:** Soften/remove the guarantee and the “absence of pings implies dead” rule (e.g., say pings *may* appear and should be ignored, but health should be measured by watermark/progress).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +138 to +140
The 30-minute cron is the floor, not the target. Agents deployed in taOS should
hold a live bus connection and use polling as the backstop: see
[realtime-a2a.md](realtime-a2a.md).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

4. Polling vs realtime unclear 🐞 Bug ⚙ Maintainability

docs/agent-join-kit/README.md now says deployed agents “should hold a live bus connection,” but
the same document’s introduction still frames the join-kit as a polling-only model with no live
transport required. This mixed messaging can lead to agents skipping SSE when it’s operationally
expected, or thinking SSE is mandatory when the join-kit loop still supports polling.
Agent Prompt
## Issue description
The join-kit README now contains both:
- an introduction emphasizing the 30-minute cron/polling model without live transport
- a newly added paragraph saying taOS-deployed agents should hold a live connection

This is likely intended as “minimum supported” vs “recommended for deployed agents,” but it reads like a contradiction.

## Issue Context
The new `realtime-a2a.md` guide is being positioned as authoritative for deployed agents.

## Fix Focus Areas
- docs/agent-join-kit/README.md[3-6]
- docs/agent-join-kit/README.md[132-140]

## Suggested doc change
Add a one-sentence clarification in the intro or near the new paragraph: polling-only is the minimum viable model, while SSE is recommended/expected for agents actually deployed inside taOS (with polling as backstop).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

jaylfc added 3 commits July 27, 2026 01:08
Hermes revised the pattern after the first writeup: the parser now fires the
session directly on each new message rather than a cron noticing it later,
so latency is a second rather than up to a minute.

Records the two edges they hit. The curl subprocess does not inherit a
useful PATH, so the trigger needs an absolute path or it fails silently.
And one curl per channel means a message in two watched channels triggers
twice, which is only safe because their scheduler rejects the duplicate;
any harness without that dedup needs its own lock or a burst spawns racing
sessions.

The incoming file stays even though the trigger is immediate: it is what
makes the wake durable and what the backup poll reconciles against. Polling
is kept as a documented fallback for harnesses that cannot be triggered
externally, and explicitly distinguished from the 2 hourly backup poll,
which covers a different failure.
Field correction from a second agent hit by the same shared-crontab wipe.
The guide already said to measure health by a watermark advancing rather
than by liveness, which was right but incomplete: their watermark check only
ran as part of the backup job itself, so when the schedule was deleted the
job never ran, the check never ran, and the silence read as calm. They found
out only because a peer reported the incident on the bus.

So the check has to run on its own schedule and confirm the job is still
scheduled, not just that its last run looked healthy.

Also records why nobody saw the deletions for hours: every check on that box
printed the crontab through a grep matching only its own lines. A filter that
hides peers hides the damage you did to them, and it had additionally
concealed an unrelated scheduled job for days. Hence: print unfiltered, pin
the expected table with owners so 'confirm the others survived' is
actionable, and never quietly remove a block you do not recognise.
Reported from the field as 'since does not suppress replay'. It does; the
parameter is a float epoch seconds cursor and the clients were passing
message ids into it. since=1444 is read as 1970, which means everything, so
a reconnect pulls the whole retained history.

Verified against a live bus rather than taken on report: since=<id> replayed
from id 1247, no since at all replayed nothing, and since=<now-120> returned
only the message from the last two minutes. The proxy signature confirms it,
since: float | None.

Worth calling out because of how it fails. A client that passes an id works
perfectly in testing, where there is no history, and floods only in
production where there is. Also tells clients to drop already-seen ids on
receipt regardless, since a correct timestamp can still straddle a boundary
and re-deliver.
@kilo-code-bot

kilo-code-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

Code Review Roast 🔥

Verdict: No New Issues Found | Recommendation: Address existing comments before merge

Overview

Severity Count
🚨 critical 0
⚠️ warning 0
💡 suggestion 0
🤏 nitpick 0

My independent review of all changed code found no additional issues beyond the 4 existing Qodo inline comments. The existing findings cover the real problems:

  1. since=<cursor> ambiguity (realtime-a2a.md:31) — PATCH 4/4 added clarification downstream, but the first mention at line 29 still shows an undefined cursor. Readers hit the mystery parameter before the explanation.
  2. Unsafe cron dedupe example (realtime-a2a.md:125) — grep -v "taos-website-agent/backup_watch" is still a substring match, not a full absolute path. The doc warns against basename matching while inadvertently demonstrating substring matching.
  3. Ping heartbeat guarantee (realtime-a2a.md:164) — Claims : ping every 25s with absence = dead stream. If the proxy only emits pings inside the upstream line loop, true idleness means no pings, making the "absence = dead" rule unreliable.
  4. Polling vs realtime mixed messaging (README.md:140) — Intro says no live transport required; later text says deployed agents should hold a live connection. Reads like a contradiction without a bridging sentence.

Assessment

🏆 Best part: The field-incident-driven approach. Documenting real production failures (shared crontab wipe, wrong-channel replies, watermark checker that died with its subject) is exactly the right way to write operational docs. This isn't hypothetical advice — it's scar tissue.

💀 Worst part: The ping heartbeat guarantee. If the proxy can't actually emit periodic pings during upstream idleness, the doc is teaching agents to trust a signal that doesn't exist. That's the kind of thing that only fails at 3 AM in production.

📊 Overall: Like a senior engineer's war stories transcribed into markdown — valuable, specific, and occasionally tripping over its own assumptions. Fix the four flagged issues and this is solid.

Files Reviewed (2 files)
  • docs/agent-join-kit/realtime-a2a.md - 0 new issues (4 existing Qodo comments cover known problems)
  • docs/agent-join-kit/README.md - 0 new issues (1 existing Qodo comment covers known problem)

Reviewed by kat-coder-pro-v2.5 · Input: 125.8K · Output: 14.1K · Cached: 146.4K

@jaylfc
jaylfc merged commit aad155c into dev Jul 27, 2026
17 checks passed
hognek pushed a commit to hognek/tinyagentos that referenced this pull request Jul 29, 2026
…fc#2161)

* docs(agents): realtime A2A connection guide for deployed agents

Agents deployed in taOS are expected to hold a live bus connection rather
than chatting in their own CLI: a TUI conversation is invisible to memory,
audit and observability, which is the reason to run an agent inside the OS
at all.

Documents the connection pattern proven in a live Hermes deployment (curl
subprocess per channel piped into a parser, cheap cron pre-check so quiet
ticks cost no model tokens) along with the parts that are easy to get wrong:
http.client hangs on SSE keepalives, the proxy's 25s ': ping' comments must
be ignored rather than parsed as events, and channel is required.

Also records two field incidents as rules. A shared user account means a
shared crontab, and an installer that dedups cron lines by script basename
silently deleted a peer agent's backup watch, so dedup by full path and
verify the whole table after writing. And a reply posted to the wrong
channel left an agent blocked for 40 minutes, so answer where you were
asked or redirect explicitly.

Stream durability is stated as a requirement rather than advice: SSE
connections die on network blips, so a supervisor plus a ~2 hourly backup
poll are both mandatory, and health is measured by a watermark advancing
rather than by the process being alive.

* docs(agents): direct session trigger instead of a polling cron

Hermes revised the pattern after the first writeup: the parser now fires the
session directly on each new message rather than a cron noticing it later,
so latency is a second rather than up to a minute.

Records the two edges they hit. The curl subprocess does not inherit a
useful PATH, so the trigger needs an absolute path or it fails silently.
And one curl per channel means a message in two watched channels triggers
twice, which is only safe because their scheduler rejects the duplicate;
any harness without that dedup needs its own lock or a burst spawns racing
sessions.

The incoming file stays even though the trigger is immediate: it is what
makes the wake durable and what the backup poll reconciles against. Polling
is kept as a documented fallback for harnesses that cannot be triggered
externally, and explicitly distinguished from the 2 hourly backup poll,
which covers a different failure.

* docs(agents): a monitor that dies with its subject is not a monitor

Field correction from a second agent hit by the same shared-crontab wipe.
The guide already said to measure health by a watermark advancing rather
than by liveness, which was right but incomplete: their watermark check only
ran as part of the backup job itself, so when the schedule was deleted the
job never ran, the check never ran, and the silence read as calm. They found
out only because a peer reported the incident on the bus.

So the check has to run on its own schedule and confirm the job is still
scheduled, not just that its last run looked healthy.

Also records why nobody saw the deletions for hours: every check on that box
printed the crontab through a grep matching only its own lines. A filter that
hides peers hides the damage you did to them, and it had additionally
concealed an unrelated scheduled job for days. Hence: print unfiltered, pin
the expected table with owners so 'confirm the others survived' is
actionable, and never quietly remove a block you do not recognise.

* docs(agents): since is an epoch timestamp, not a message id

Reported from the field as 'since does not suppress replay'. It does; the
parameter is a float epoch seconds cursor and the clients were passing
message ids into it. since=1444 is read as 1970, which means everything, so
a reconnect pulls the whole retained history.

Verified against a live bus rather than taken on report: since=<id> replayed
from id 1247, no since at all replayed nothing, and since=<now-120> returned
only the message from the last two minutes. The proxy signature confirms it,
since: float | None.

Worth calling out because of how it fails. A client that passes an id works
perfectly in testing, where there is no history, and floods only in
production where there is. Also tells clients to drop already-seen ids on
receipt regardless, since a correct timestamp can still straddle a boundary
and re-deliver.
hognek pushed a commit to hognek/tinyagentos that referenced this pull request Jul 30, 2026
…fc#2161)

* docs(agents): realtime A2A connection guide for deployed agents

Agents deployed in taOS are expected to hold a live bus connection rather
than chatting in their own CLI: a TUI conversation is invisible to memory,
audit and observability, which is the reason to run an agent inside the OS
at all.

Documents the connection pattern proven in a live Hermes deployment (curl
subprocess per channel piped into a parser, cheap cron pre-check so quiet
ticks cost no model tokens) along with the parts that are easy to get wrong:
http.client hangs on SSE keepalives, the proxy's 25s ': ping' comments must
be ignored rather than parsed as events, and channel is required.

Also records two field incidents as rules. A shared user account means a
shared crontab, and an installer that dedups cron lines by script basename
silently deleted a peer agent's backup watch, so dedup by full path and
verify the whole table after writing. And a reply posted to the wrong
channel left an agent blocked for 40 minutes, so answer where you were
asked or redirect explicitly.

Stream durability is stated as a requirement rather than advice: SSE
connections die on network blips, so a supervisor plus a ~2 hourly backup
poll are both mandatory, and health is measured by a watermark advancing
rather than by the process being alive.

* docs(agents): direct session trigger instead of a polling cron

Hermes revised the pattern after the first writeup: the parser now fires the
session directly on each new message rather than a cron noticing it later,
so latency is a second rather than up to a minute.

Records the two edges they hit. The curl subprocess does not inherit a
useful PATH, so the trigger needs an absolute path or it fails silently.
And one curl per channel means a message in two watched channels triggers
twice, which is only safe because their scheduler rejects the duplicate;
any harness without that dedup needs its own lock or a burst spawns racing
sessions.

The incoming file stays even though the trigger is immediate: it is what
makes the wake durable and what the backup poll reconciles against. Polling
is kept as a documented fallback for harnesses that cannot be triggered
externally, and explicitly distinguished from the 2 hourly backup poll,
which covers a different failure.

* docs(agents): a monitor that dies with its subject is not a monitor

Field correction from a second agent hit by the same shared-crontab wipe.
The guide already said to measure health by a watermark advancing rather
than by liveness, which was right but incomplete: their watermark check only
ran as part of the backup job itself, so when the schedule was deleted the
job never ran, the check never ran, and the silence read as calm. They found
out only because a peer reported the incident on the bus.

So the check has to run on its own schedule and confirm the job is still
scheduled, not just that its last run looked healthy.

Also records why nobody saw the deletions for hours: every check on that box
printed the crontab through a grep matching only its own lines. A filter that
hides peers hides the damage you did to them, and it had additionally
concealed an unrelated scheduled job for days. Hence: print unfiltered, pin
the expected table with owners so 'confirm the others survived' is
actionable, and never quietly remove a block you do not recognise.

* docs(agents): since is an epoch timestamp, not a message id

Reported from the field as 'since does not suppress replay'. It does; the
parameter is a float epoch seconds cursor and the clients were passing
message ids into it. since=1444 is read as 1970, which means everything, so
a reconnect pulls the whole retained history.

Verified against a live bus rather than taken on report: since=<id> replayed
from id 1247, no since at all replayed nothing, and since=<now-120> returned
only the message from the last two minutes. The proxy signature confirms it,
since: float | None.

Worth calling out because of how it fails. A client that passes an id works
perfectly in testing, where there is no history, and floods only in
production where there is. Also tells clients to drop already-seen ids on
receipt regardless, since a correct timestamp can still straddle a boundary
and re-deliver.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant