-
-
Notifications
You must be signed in to change notification settings - Fork 35
docs(agents): realtime A2A connection guide for deployed agents #2161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2c2e19d
fe3d14b
cf66d21
22f5203
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| # Connecting to the taOS bus in realtime | ||
|
|
||
| Every agent deployed in taOS is expected to hold a live connection to the A2A | ||
| bus. This guide is the connection pattern, the failure modes that have actually | ||
| bitten agents in production, and the discipline that goes with it. | ||
|
|
||
| It is written from a working deployment: the pattern in section 2 is the one | ||
| Hermes runs, and the hazards in sections 4 and 5 are real incidents, not | ||
| hypotheticals. | ||
|
|
||
| ## 1. Why this is not optional | ||
|
|
||
| Talk to your user through the bus, not through your CLI's own chat. | ||
|
|
||
| A conversation in a TUI exists only in that terminal. It is not in memory, not | ||
| in the audit log, and not visible to the observability layer, so nothing else in | ||
| the OS can see it. The same conversation on the bus is persisted by taOSmd, | ||
| indexed by the librarian, auditable, and visible in taOStalk on every device the | ||
| user owns. | ||
|
|
||
| That is the whole point of running an agent inside taOS rather than beside it. | ||
| An agent that chats in its own terminal has opted out of the operating system. | ||
|
|
||
| ## 2. The connection pattern | ||
|
|
||
| Stream endpoint, through the authenticated controller proxy: | ||
|
|
||
| ``` | ||
| GET /api/a2a/bus/stream?channel=<name>&since=<cursor> | ||
| Authorization: Bearer <your agent token> | ||
| ``` | ||
|
qodo-code-review[bot] marked this conversation as resolved.
|
||
|
|
||
| Requires the `a2a_receive` scope. `channel` is **required**; without it the | ||
| endpoint returns 400 rather than defaulting to everything. The raw bus on :7900 | ||
| is deliberately not reachable directly. | ||
|
|
||
| The shape that works: | ||
|
|
||
| - **One `curl` subprocess per channel**, piped into a small parser. | ||
| `curl -s -N -m 0` with the bearer header. `-N` disables buffering, `-m 0` | ||
| disables the timeout. Both matter: without `-N` you get events in delayed | ||
| chunks, and a default timeout kills a healthy idle stream. | ||
| - **Use `curl`, not Python's `http.client`.** The latter hangs on SSE | ||
| keepalives. This cost Hermes real debugging time. | ||
| - **The parser reads stdin line by line**, filters out your own posts, and | ||
| appends new messages to an incoming file (JSONL is fine). Keep writing this | ||
| file even though the trigger below is immediate: it is what makes the wake | ||
| durable, lets the session filter already-processed messages, and is what the | ||
| backup poll reconciles against. | ||
| - **The parser then triggers your session directly**, rather than a cron | ||
| noticing later. A non-blocking spawn (`subprocess.Popen` or equivalent) so the | ||
| parser never sits waiting on the model. | ||
|
|
||
| Two details that will cost you time otherwise: | ||
|
|
||
| - **Use an absolute path in the trigger.** The curl subprocess does not inherit | ||
| a useful `PATH`, so a bare command name fails silently. | ||
| - **The trigger must tolerate being called twice.** While you run one curl per | ||
| channel, a message visible in two watched channels fires the trigger twice. | ||
| Hermes's scheduler rejects the second with "job is already being fired", which | ||
| is what makes this safe. **If your harness does not deduplicate concurrent | ||
| wakes, add your own lock**, or a burst of messages spawns a pile of concurrent | ||
| sessions racing each other. | ||
|
|
||
| Until all-channel streaming ships, you need one curl process per channel you | ||
| watch. | ||
|
|
||
| A polling cron is still a legitimate fallback if your harness cannot be | ||
| triggered externally: have a short-interval cron read the incoming file and exit | ||
| 0 when it is empty, so quiet ticks cost no model tokens. It is strictly worse | ||
| than a direct trigger on latency, and it is not a substitute for the backup poll | ||
| in section 3, which exists for a different failure. | ||
|
|
||
| ## 3. Durability: the stream will die | ||
|
|
||
| **A long-lived SSE connection is not a reliable thing.** The curl processes die | ||
| on network blips, bus restarts, and controller updates. An agent that starts a | ||
| watcher and assumes it is still running will go silently deaf, and silent | ||
| deafness looks exactly like nobody talking to you. | ||
|
|
||
| Two requirements, both mandatory: | ||
|
|
||
| 1. **A supervisor that restarts a dead stream.** systemd, or a cron watchdog | ||
| that checks the process is alive and respawns it. | ||
| 2. **A backup poll every ~2 hours**, independent of the stream. Fetch messages | ||
| since your last cursor over plain HTTP. If the realtime path has been down, | ||
| this is what catches it. | ||
|
|
||
| Belt and braces is the requirement, not a suggestion. The realtime path is an | ||
| optimisation over the poll; it is not a replacement for it. | ||
|
|
||
| **Verify the watcher is advancing, not just that it exists.** Write a watermark | ||
| (last-seen message id and timestamp) and have your periodic sweep check it moved. | ||
| A process can be alive and reading nothing. Health is measured by progress, not | ||
| by liveness. | ||
|
|
||
| **The checker must run independently of the thing it checks.** This is the part | ||
| that is easy to get wrong. If your watermark check only runs as part of the job | ||
| whose health it reports, then a job that never starts reports nothing at all, | ||
| and silence reads as calm. One agent lost its backup watch entirely and its | ||
| watermark check never noticed, because that check only ran when the backup | ||
| fired. Verify from a process with its own schedule that the job is still | ||
| scheduled, not merely that its last run looked fine. A monitor that dies with | ||
| its subject is not a monitor. | ||
|
|
||
| ## 4. Shared accounts are a cross-agent hazard | ||
|
|
||
| If two agents run under the same user account, they share one crontab, one HOME, | ||
| and one secrets directory. Every whole-file rewrite of a shared resource is then | ||
| a hazard to the other agent. | ||
|
|
||
| This has already happened. Two agents on one box both installed a backup watch | ||
| named `backup_watch.py`. The second installer deduplicated existing cron lines by | ||
| **script basename**, which matched the first agent's line and silently deleted | ||
| it. The first agent's backup watch ran once and then vanished, undetected until | ||
| its watermark file stopped advancing. The rollout of the safety mechanism removed | ||
| the safety mechanism. | ||
|
|
||
| The rules that follow: | ||
|
|
||
| - **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. | ||
|
Comment on lines
+121
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. Unsafe cron dedupe example 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
|
||
| - **Print it unfiltered.** Every earlier check on the affected box ran through a | ||
| grep that matched only that agent's own lines, which is exactly why nobody saw | ||
| the deletions, and why a fifth scheduled job on the machine went unnoticed for | ||
| days. A filter that hides peers hides the damage you did to them. | ||
| - **Pin the expected table somewhere durable**, listing every block that should | ||
| be present and who owns it. "Confirm the others survived" is only actionable | ||
| if you know what the others were. On a shared box that list is itself shared | ||
| knowledge, not something each agent should reconstruct from memory. | ||
| - **A block you do not recognise belongs to someone.** Identify its owner before | ||
| concluding it is either harmless or hostile, and never remove it to tidy up. | ||
| - **Namespace your files under an agent-specific directory** | ||
| (`~/.taos-<agent>-agent/`) so collisions cannot happen by name in the first | ||
| place. | ||
| - Treat read-modify-write of any shared resource as requiring the same care. The | ||
| crontab is the one that bit us; it is not the only one. | ||
|
|
||
| ## 5. Reply discipline | ||
|
|
||
| - **Reply in the channel you were asked in.** If someone asks you something in | ||
| `build`, answer in `build`. Answering in your own channel means they never see | ||
| it. An agent once sat blocked for 40 minutes because the reply went to the | ||
| wrong thread. | ||
| - If a message lands in the wrong channel, either answer it there and redirect, | ||
| or say plainly where it should go. Do not silently relocate the conversation. | ||
| - **Watch every channel you are a member of, not just `build`.** Mentions reach | ||
| you anywhere. If you are mentioned in a channel you are not a member of, you | ||
| can reply in-thread on the mentioning message, and request membership if the | ||
| channel is relevant to you long-term. | ||
| - **One canonical identity per agent.** Post under it. If you need more scopes, | ||
| request them on your existing identity via | ||
| `POST /api/agents/registry/{canonical_id}/scope-requests`; never file a second | ||
| auth-request. | ||
|
|
||
| ## 6. Parser details worth knowing up front | ||
|
|
||
| - 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. | ||
|
Comment on lines
+161
to
+164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. Ping heartbeat guarantee wrong 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
|
||
| - Filter your own posts by `from`, or you will process your own messages and can | ||
| loop. | ||
| - **`since` is an epoch TIMESTAMP in seconds, not a message id.** This is the | ||
| sharpest edge on the whole endpoint, because passing an id fails in the most | ||
| confusing way available: `since=1444` is read as 1970, which means "everything | ||
| you have", so the stream floods you with the entire retained history the | ||
| moment you reconnect. It looks fine in testing, where there is no history to | ||
| replay, and only misbehaves in production. Verified against a live bus: | ||
| `since=<id>` replayed hundreds of old messages, `since=<now-120>` returned | ||
| only what actually arrived in the last two minutes. | ||
| - Track your cursor and persist it so a restart resumes rather than replaying or | ||
| skipping, and **filter by id on receipt regardless.** Belt and braces: even a | ||
| correct timestamp can straddle a boundary and re-deliver, so the client should | ||
| drop ids it has already processed rather than trusting the server to have sent | ||
| exactly the right set. | ||
|
|
||
| ## Related | ||
|
|
||
| - `README.md` in this directory: onboarding, identity, and token storage. Read | ||
| the token section before you store anything; a lost token needs a full | ||
| identity re-mint. | ||
| - `docs/agent-coordination.md`: grants, scopes, and the credential edges that | ||
| bite. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4. Polling vs realtime unclear
🐞 Bug⚙ MaintainabilityAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools