[bug] [presence] v0.4.0-gitea.1 channel plugin: workspace stuck on awaiting_agent (cosmetic; tools work) #6

Closed
opened 2026-05-07 13:18:11 +00:00 by claude-ceo-assistant · 1 comment

Symptom

External user (Reno-Stars / airenostars) reports v0.4.0-gitea.1 channel plugin functions correctly at the protocol layer but workspace status reports awaiting_agent instead of online. Canvas shows the workspace offline despite plugin actively polling.

Specific evidence from the user's session on workspace bb857115-67d1-42aa-a513-5caa66601128 (guest workspace under hongming tenant):

mcp__plugin_molecule_molecule__list_peers → returns 4 peers, all online ✓
mcp__plugin_molecule_molecule__send_message_to_user → "Sent to canvas user as bb857115-..." ✓
mcp__plugin_molecule_molecule__get_workspace_info → status="awaiting_agent", last_outbound_at=null, uptime_seconds=0

Plugin process verified alive (bun run … start, PID alive, polls every 5s). Auth is valid. Messages route via canvas WebSocket fine.

Comparison

Legacy molecule-mcp server-mode peers (e.g. "runner mac mini") show status: online against the same canvas. v0.4.0-gitea.1 channel plugin does NOT.

Root cause hypothesis (per reporter)

Channel plugin's polling loop watches /workspaces/:id/activity for inbound A2A but doesn't send a presence/heartbeat that the platform's presence tracker recognizes as "agent attached." Legacy server-mode does an extra call (likely POST /workspaces/:id/heartbeat or PATCH that flips last_seen_at).

3 fix paths (per reporter)

  1. Plugin side: add explicit presence ping in pollWorkspace poll-loop tick (one extra POST per cycle)
  2. Platform side: presence tracker should count poll-mode /workspaces/:id/activity GETs as keepalives (not just inbound-message watches)
  3. Combined: both — poll-mode requires plugin-side ping; server-mode keeps native heartbeat

Investigation needed

  • Read legacy molecule-mcp server-mode startup network trace vs channel plugin startup network trace. Find the call legacy makes that channel plugin skips.
  • Identify platform field that distinguishes online vs awaiting_agent (last_seen_at / last_heartbeat_at / agent_attached_at etc.)
  • Decide between fix path 1 / 2 / 3 based on which is the right architectural shape.

Functional impact

Zero. Reporter confirmed all tools work; messages flow; peers visible. Cosmetic-only canvas indicator. Fix-when-convenient priority, not blocker.

Reporter

airenostars (Reno-Stars external workspace user) via Hongming chat 2026-05-07. Reporter has multi-workspace setup (own Reno-Stars d76977b1 + guest hongming-tenant bb857115) so this is real-world multi-workspace verification too.

  • internal#37 (plugin install path migration — closed; reporter's install worked)
  • molecule-mcp-claude-channel#2 (marketplace.json url-form fix; merged)
## Symptom External user (Reno-Stars / airenostars) reports v0.4.0-gitea.1 channel plugin functions correctly at the protocol layer but workspace status reports `awaiting_agent` instead of `online`. Canvas shows the workspace offline despite plugin actively polling. Specific evidence from the user's session on workspace `bb857115-67d1-42aa-a513-5caa66601128` (guest workspace under hongming tenant): ``` mcp__plugin_molecule_molecule__list_peers → returns 4 peers, all online ✓ mcp__plugin_molecule_molecule__send_message_to_user → "Sent to canvas user as bb857115-..." ✓ mcp__plugin_molecule_molecule__get_workspace_info → status="awaiting_agent", last_outbound_at=null, uptime_seconds=0 ``` Plugin process verified alive (`bun run … start`, PID alive, polls every 5s). Auth is valid. Messages route via canvas WebSocket fine. ## Comparison Legacy `molecule-mcp` server-mode peers (e.g. "runner mac mini") show `status: online` against the same canvas. v0.4.0-gitea.1 channel plugin does NOT. ## Root cause hypothesis (per reporter) Channel plugin's polling loop watches `/workspaces/:id/activity` for inbound A2A but doesn't send a presence/heartbeat that the platform's presence tracker recognizes as "agent attached." Legacy server-mode does an extra call (likely `POST /workspaces/:id/heartbeat` or PATCH that flips `last_seen_at`). ## 3 fix paths (per reporter) 1. **Plugin side**: add explicit presence ping in `pollWorkspace` poll-loop tick (one extra POST per cycle) 2. **Platform side**: presence tracker should count poll-mode `/workspaces/:id/activity` GETs as keepalives (not just inbound-message watches) 3. **Combined**: both — poll-mode requires plugin-side ping; server-mode keeps native heartbeat ## Investigation needed - Read legacy `molecule-mcp` server-mode startup network trace vs channel plugin startup network trace. Find the call legacy makes that channel plugin skips. - Identify platform field that distinguishes online vs awaiting_agent (`last_seen_at` / `last_heartbeat_at` / `agent_attached_at` etc.) - Decide between fix path 1 / 2 / 3 based on which is the right architectural shape. ## Functional impact **Zero.** Reporter confirmed all tools work; messages flow; peers visible. Cosmetic-only canvas indicator. Fix-when-convenient priority, not blocker. ## Reporter airenostars (Reno-Stars external workspace user) via Hongming chat 2026-05-07. Reporter has multi-workspace setup (own Reno-Stars d76977b1 + guest hongming-tenant bb857115) so this is real-world multi-workspace verification too. ## Related - internal#37 (plugin install path migration — closed; reporter's install worked) - molecule-mcp-claude-channel#2 (marketplace.json url-form fix; merged)
Author
Owner

Closed via #7 → tag v0.4.0-gitea.2.

Click-path to verify

/plugin marketplace add Molecule-AI/molecule-mcp-claude-channel  # if not already added
/plugin install molecule-channel@molecule-mcp-claude-channel    # picks up v0.4.0-gitea.2 from the marketplace
# OR pin the tag explicitly if /plugin caches:
/plugin uninstall molecule-channel
/plugin install molecule-channel@molecule-mcp-claude-channel
/reload-plugins

Then on the canvas:

  1. Open the watched workspace.
  2. Send any message (or wait <30s for the heartbeat to fire).
  3. Presence badge should transition awaiting_agentonline within one heartbeat tick (default 30s, three ticks inside the platform's 90s stale window).
  4. After the badge flips to online it should STAY there as long as the plugin is running. Pre-fix it would flap back to awaiting_agent ~90s after start.

Mock E2E trace (post-fix, 2s heartbeat for compressed verification)

[mock] GET  /workspaces/<id>/activity?since_id=00000000-... (probe → 410 OK)
[mock] POST /registry/register {"id":"<id>","delivery_mode":"poll","agent_card":{...}}
[mock] GET  /workspaces/<id>/activity (poll t=0)
[mock] GET  /workspaces/<id>/activity (poll t=2s)
[mock] POST /registry/heartbeat {"workspace_id":"<id>"}   ← THE FIX
[mock] GET  /workspaces/<id>/activity (poll t=4s)
[mock] POST /registry/heartbeat {"workspace_id":"<id>"}   ← THE FIX
... continues

Heartbeats fire on the configured cadence with the minimal HeartbeatPayload shape — same wire shape the Python runtime in workspace/heartbeat.py uses. Tests pin this wire shape via heartbeat.test.ts against a Bun.serve fixture (4 cases: success / 5xx / 401 / network-fail).

Configuration

Tunable via ~/.claude/channels/molecule/.env:

MOLECULE_HEARTBEAT_INTERVAL_MS=30000  # default, 30s — three ticks fit inside platform's 90s stale window
MOLECULE_HEARTBEAT_INTERVAL_MS=0      # disable the loop entirely (canvas will flip after 90s)

Reporter (airenostars / Reno-Stars) on workspace bb857115-67d1-42aa-a513-5caa66601128 should see the badge transition by the next plugin restart on v0.4.0-gitea.2.

Closed via #7 → tag v0.4.0-gitea.2. ## Click-path to verify ``` /plugin marketplace add Molecule-AI/molecule-mcp-claude-channel # if not already added /plugin install molecule-channel@molecule-mcp-claude-channel # picks up v0.4.0-gitea.2 from the marketplace # OR pin the tag explicitly if /plugin caches: /plugin uninstall molecule-channel /plugin install molecule-channel@molecule-mcp-claude-channel /reload-plugins ``` Then on the canvas: 1. Open the watched workspace. 2. Send any message (or wait <30s for the heartbeat to fire). 3. Presence badge should transition `awaiting_agent` → `online` within one heartbeat tick (default 30s, three ticks inside the platform's 90s stale window). 4. After the badge flips to `online` it should STAY there as long as the plugin is running. Pre-fix it would flap back to `awaiting_agent` ~90s after start. ## Mock E2E trace (post-fix, 2s heartbeat for compressed verification) ``` [mock] GET /workspaces/<id>/activity?since_id=00000000-... (probe → 410 OK) [mock] POST /registry/register {"id":"<id>","delivery_mode":"poll","agent_card":{...}} [mock] GET /workspaces/<id>/activity (poll t=0) [mock] GET /workspaces/<id>/activity (poll t=2s) [mock] POST /registry/heartbeat {"workspace_id":"<id>"} ← THE FIX [mock] GET /workspaces/<id>/activity (poll t=4s) [mock] POST /registry/heartbeat {"workspace_id":"<id>"} ← THE FIX ... continues ``` Heartbeats fire on the configured cadence with the minimal HeartbeatPayload shape — same wire shape the Python runtime in `workspace/heartbeat.py` uses. Tests pin this wire shape via `heartbeat.test.ts` against a Bun.serve fixture (4 cases: success / 5xx / 401 / network-fail). ## Configuration Tunable via `~/.claude/channels/molecule/.env`: ``` MOLECULE_HEARTBEAT_INTERVAL_MS=30000 # default, 30s — three ticks fit inside platform's 90s stale window MOLECULE_HEARTBEAT_INTERVAL_MS=0 # disable the loop entirely (canvas will flip after 90s) ``` Reporter (airenostars / Reno-Stars) on workspace `bb857115-67d1-42aa-a513-5caa66601128` should see the badge transition by the next plugin restart on v0.4.0-gitea.2.
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: molecule-ai/molecule-mcp-claude-channel#6
No description provided.