- Drop unused `import time` from inbound.py and `import call` from
test_inbound.py (caught by ruff in CI; would have caught locally if I'd
run it before pushing).
- Rewrite the misleading comment in PollDelivery.run_once: the cursor DOES
advance past handler exceptions (poison-pill resilience). The previous
comment claimed otherwise, which would have confused future readers.
- Drop `_parse_activity_row` from inbound.py's `__all__`. The leading
underscore signals "private helper"; exposing it via `__all__`
contradicted the convention. Tests still import it directly via the
module path.
- Add `test_fetch_inbound_429_retries_via_get_with_retry` — the PR
description claimed branch-coverage of the 429 path but no test
exercised it. Closes the gap.
External agents that can't expose a public HTTP endpoint (laptops behind
NAT, ephemeral CI runners, hermes self-hosted, codex et al) had to reverse-
engineer the activity-poll loop from molecule-mcp-claude-channel/server.ts
because the SDK only shipped the push-mode `A2AServer` (Phase 30.8b).
This adds the complementary path:
- `RemoteAgentClient.fetch_inbound(since_id=…)` — one-shot GET against
`/workspaces/:id/activity?type=a2a_receive&since_id=…`. Cursor-loss (410)
surfaces as `CursorLostError`; caller resets and re-polls.
- `RemoteAgentClient.reply(msg, text)` — smart-routes to `/notify` for
canvas users, `/a2a` (JSON-RPC envelope + X-Source-Workspace-Id) for peer
agents. Hides the reply-path bifurcation from connector authors.
- `PollDelivery` / `PushDelivery` / `InboundDelivery` protocol — same
`MessageHandler` callback works for both transports.
- `RemoteAgentClient.run_agent_loop(handler, delivery=None)` — combined
heartbeat + state-poll + inbound dispatch. Defaults to `PollDelivery`.
Async handlers detected and `asyncio.run`'d (matches A2AServer pattern).
Sleep cadence = min(heartbeat_interval, delivery.interval).
- `python -m molecule_agent connect` CLI — one-line bootstrap. Loads a
user's `module:function` via importlib, registers, runs the loop until
pause/delete or SIGTERM. All flags also read from environment variables.
Tests: 50 new (test_inbound.py, test_cli_connect.py) covering every prod
branch — source normalization, cursor advancement, 410 reset, async/sync
handler dispatch, handler exception → log+continue+advance, smart-reply
routing for canvas vs peer vs unknown sources, run_agent_loop terminal
states, sleep-interval selection, CLI handler resolution failures.
Resolves#17.