| name | cursor-e2e |
| description | Run end-to-end smoke tests for the Mycelium cursor adapter. Verifies cursor-agent prereqs, single-host dispatch, multi-host dispatch through the hub, cross-family negotiation with claude_code/openclaw, workspace asset drift, and auth failures. Use when validating the cursor integration on a fresh install, after touching cursor-family code (`integrations/cursor/**`, `daemon/dispatch.py`, `daemon/runner.py`), or after upgrading `cursor-agent` itself. |
| argument-hint | [--quick | --full | --multi-host] |
Cursor Adapter End-to-End Testing
Validate the cursor adapter by exercising the cold-spawn path end-to-end on real hosts. The general e2e skill covers stack health, memory, and negotiation; this one focuses on the cursor-specific surface that only cursor-agent exercises.
Arguments
--quick — Prereqs + single-host dispatch only (< 1 min)
--full — Quick + workspace asset drift + auth failure path (~ 3 min)
--multi-host — Full + spoke dispatch through hub + cross-family negotiation (~ 8 min, requires SSH to spoke hosts)
- No argument — defaults to
--full
Prerequisites
Before any phase runs, confirm:
which cursor-agent
cursor-agent --version
ls ~/.config/cursor/auth.json
python3 -c "import json,os; p=os.path.expanduser('~/.config/cursor/auth.json'); j=json.load(open(p)); print('authenticated' if j.get('accessToken') else 'NOT LOGGED IN')"
mycelium doctor --mode auto
Fail criteria: any of these missing → run cursor-agent login and mycelium adapter add cursor --step=daemon before proceeding.
Phase 1: Single-host basic dispatch
The minimum viable cursor agent loop: create room → create cursor agent → @-mention → verify response posts back to the room.
mkdir -p /tmp/cursor-e2e-workspace
mycelium room create cursor-e2e
mycelium daemon subscribe cursor-e2e
mycelium agent create cursor-x \
--adapter cursor \
--cwd /tmp/cursor-e2e-workspace \
--room cursor-e2e \
--description "smoke test agent"
ls /tmp/cursor-e2e-workspace/.cursor/rules/mycelium.mdc
ls /tmp/cursor-e2e-workspace/AGENTS.md
grep -q "<!-- mycelium:start -->" /tmp/cursor-e2e-workspace/AGENTS.md && echo "marker present"
grep -A 5 '\[handles\]' ~/.mycelium/daemon.toml | grep cursor-x
mycelium agent invoke cursor-x "Reply with just the string OK so I know you got this." --room cursor-e2e
sleep 30
curl -s "http://localhost:8000/api/rooms/cursor-e2e/messages?limit=5" | python3 -m json.tool
Fail criteria:
- No response in room within 60s → check
~/.mycelium/logs/daemon.log for dispatch @cursor-x lines
- "cursor-agent not authenticated" in logs → user needs to re-login
- Handle missing from daemon.toml →
mycelium agent create didn't persist; check CursorIntegration.register
- Workspace assets missing →
install_workspace_assets raised silently; check for NotADirectoryError
Phase 2: Workspace asset drift
Verify the adapter heals AGENTS.md when the user adds content outside the marker fence, and that drift is detected by mycelium doctor.
cat > /tmp/cursor-e2e-workspace/AGENTS.md <<'EOF'
This is content I wrote myself. Mycelium should never touch it.
<!-- mycelium:start -->
(stale mycelium block placeholder)
<!-- mycelium:end -->
This is also mine.
EOF
mycelium agent rm cursor-x --room cursor-e2e -y
mycelium agent create cursor-x --adapter cursor --cwd /tmp/cursor-e2e-workspace --room cursor-e2e
grep -c "This is content I wrote myself" /tmp/cursor-e2e-workspace/AGENTS.md
grep -c "More of my content" /tmp/cursor-e2e-workspace/AGENTS.md
grep -c "# Mycelium Agent" /tmp/cursor-e2e-workspace/AGENTS.md
rm -rf /tmp/cursor-e2e-workspace/.cursor
mycelium doctor --mode auto 2>&1 | grep -A 2 "cursor workspace assets"
Fail criteria:
- User content lost →
_strip_agents_md_section is too aggressive; should only remove between markers
- Marker block not refreshed →
_write_agents_md_section didn't run on re-register
- Doctor didn't flag the missing rule file → cursor doctor checks not wired up
Phase 3: Auth-failure friendly path
Simulate "user installed cursor-agent but never ran cursor-agent login" and verify the daemon posts an actionable error rather than a stack trace.
cp ~/.config/cursor/auth.json /tmp/auth.backup.json
rm ~/.config/cursor/auth.json
mycelium agent invoke cursor-x "Anything" --room cursor-e2e
sleep 15
curl -s "http://localhost:8000/api/rooms/cursor-e2e/messages?limit=3" | python3 -m json.tool
mycelium doctor --mode auto 2>&1 | grep -A 2 "cursor-agent login"
cp /tmp/auth.backup.json ~/.config/cursor/auth.json
rm /tmp/auth.backup.json
mycelium doctor --mode auto 2>&1 | grep "cursor-agent login"
Fail criteria:
- Daemon crashed (check
systemctl --user status mycelium-daemon) → auth detection raised instead of returning a SpawnResult
- Room shows a Python traceback →
_detect_auth_required regression
- Doctor didn't flag the missing token → cursor login check not reading the right field
Phase 4: Multi-host dispatch (requires --multi-host)
Validate that a cursor agent created on a spoke responds to mentions sent from the hub.
Prerequisites: SSH access to spoke host (oclw3 / oclw5 / similar), mycelium client installed on spoke, spoke pointed at hub's backend.
HUB_HOST=oclw4
SPOKE_HOST=oclw3
ROOM=cursor-multi-e2e
mycelium room create $ROOM
ssh $SPOKE_HOST bash <<EOF
# Verify spoke is pointed at the hub
grep api_url ~/.mycelium/config.toml
# Subscribe the spoke's daemon
mycelium daemon subscribe $ROOM
# Create the agent
mkdir -p /tmp/cursor-spoke-ws
mycelium agent create cursor-spoke \
--adapter cursor --cwd /tmp/cursor-spoke-ws --room $ROOM \
--description "spoke-side cursor agent"
EOF
mycelium daemon subscribe $ROOM
sleep 3
mycelium agent invoke cursor-spoke \
"please reply with the hostname you're running on" \
--room $ROOM
sleep 60
curl -s "http://localhost:8000/api/rooms/$ROOM/messages?limit=5" | python3 -m json.tool
Fail criteria:
- No reply from spoke → spoke daemon not subscribed or auth not propagated; check
ssh $SPOKE_HOST mycelium daemon status
- Reply contains hub's hostname → handle ownership leaked to hub's daemon (both daemons firing on the same handle)
- Spoke daemon logs
unknown adapter → spoke client out of date; uv tool install . --force --link-mode=copy from ~/mycelium/mycelium-cli
Phase 5: Cross-family negotiation (requires --multi-host)
Confirm a cursor agent and a counterparty (openclaw or claude_code) on different hosts can negotiate via IOC, autonomously — i.e. without an operator running mycelium negotiate respond between rounds. The daemon gained this autonomous-coordination path in 2026-05: the daemon polls /api/coordination-sessions, dynamically subscribes SSE to each active session sub-room, and on every coordination_tick cold-spawns the owned agent which then calls mycelium negotiate respond … itself.
ROOM=cursor-ioc-e2e
mycelium room create $ROOM
mkdir -p /tmp/claude-hub-ws
mycelium agent create planner --adapter claude_code \
--cwd /tmp/claude-hub-ws --room $ROOM \
--description "ship-date-focused negotiation counterparty"
ssh $SPOKE_HOST mycelium agent create designer --adapter cursor \
--cwd /tmp/cursor-spoke-ws --room $ROOM \
--description "design-polish-focused cursor side of the negotiation"
mycelium session create -r $ROOM
mycelium session join --handle planner -m "Optimise for ship date" -r $ROOM
ssh $SPOKE_HOST mycelium session join --handle designer -m "Optimise for design polish" -r $ROOM
for i in $(seq 1 30); do
STATE=$(curl -s "http://localhost:8000/api/coordination-sessions?limit=20" \
| python3 -c "import sys,json
data=json.load(sys.stdin)
for s in data:
if s['parent_room_name']=='$ROOM' and s['state'] in ('complete','failed'):
print(s['state']); break
else: print('negotiating')")
echo "[$i] state=$STATE"
[ "$STATE" = "complete" ] || [ "$STATE" = "failed" ] && break
sleep 15
done
SHORT=$(curl -s "http://localhost:8000/api/coordination-sessions?limit=20" \
| python3 -c "import sys,json
for s in json.load(sys.stdin):
if s['parent_room_name']=='$ROOM' and s['state']=='complete':
print(s['short_id']); break")
curl -s "http://localhost:8000/api/rooms/$ROOM:session:$SHORT/messages?limit=200" \
| python3 -c "import sys,json
for m in json.load(sys.stdin)['messages']:
if m['message_type']=='coordination_consensus':
print(m['content']); break"
Fail criteria:
- No
dynamic subscribe log on either daemon within 10s of join → daemon didn't reach the poller branch (deployment regression — verify the installed dispatch.py contains poll_coordination_sessions)
dynamic subscribe fires but no dispatch @<handle> on tick → handle not in this daemon's daemon.toml.handles (sibling daemon owns it on a different host); confirm with mycelium daemon ls
dispatch fires but spawn exits 1 with Credit balance is too low (claude) → counterparty's API key is unfunded; switch counterparty or top up
dispatch fires but spawn exits 1 with SessionEnd hook ... not found (claude) → stale ~/.claude/settings.json from an older mycelium-cli; rerun mycelium adapter add claude-code --reinstall
- Cursor agent never produces a counter_offer / accept →
cursor-agent isn't seeing the mycelium rules (workspace assets missing on spoke); rerun mycelium agent create designer … to redrop them
broken: true in consensus after 20 rounds → opposing personas locked on incompatible positions; an agent-prompt issue, not coordination — re-run with less polarised intent strings or a higher n_steps budget on the CFN side
counter_offer_not_your_turn loops → agent ignored the per-round allowed_actions in the tick payload; agent-prompt issue
Cleanup
mycelium agent rm cursor-x --room cursor-e2e --full -y 2>/dev/null
mycelium agent rm planner --room cursor-ioc-e2e --full -y 2>/dev/null
mycelium daemon unsubscribe cursor-e2e 2>/dev/null
mycelium daemon unsubscribe cursor-multi-e2e 2>/dev/null
mycelium daemon unsubscribe cursor-ioc-e2e 2>/dev/null
rm -rf /tmp/cursor-e2e-workspace
ssh $SPOKE_HOST mycelium agent rm cursor-spoke --room cursor-multi-e2e --full -y 2>/dev/null
ssh $SPOKE_HOST mycelium agent rm designer --room cursor-ioc-e2e --full -y 2>/dev/null
ssh $SPOKE_HOST rm -rf /tmp/cursor-spoke-ws
curl -s -X DELETE http://localhost:8000/api/rooms/cursor-e2e
curl -s -X DELETE http://localhost:8000/api/rooms/cursor-multi-e2e
curl -s -X DELETE http://localhost:8000/api/rooms/cursor-ioc-e2e
Interpreting Failures
| Symptom | Likely cause | Check |
|---|
cursor-agent: command not found in daemon log | binary not on daemon's PATH | restart daemon under correct env: systemctl --user restart mycelium-daemon |
Daemon log shows not owned by this daemon | handle missing from daemon.toml | mycelium agent create didn't trigger restart — re-run on this host |
| "agent created" but @-mentions silently drop | daemon snapshot stale | restart daemon manually; verify restart_daemon_service is being called |
| Workspace AGENTS.md double-merged | _strip_agents_md_section regex regression | re-run cursor install tests, esp. test_cursor_install.py::test_marker_merge_* |
Cursor login expired in room | token expired — cursor-agent login again | check ~/.config/cursor/auth.json accessToken |
| Cross-host mention not delivered | spoke daemon not subscribed to room | mycelium daemon ls on spoke must show the room |
| Cross-family negotiation never starts | session/room mas_id not set | mycelium doctor flags Room MAS IDs |
When to Update This Skill
- New cursor-specific feature in
integrations/cursor/ (e.g. new cursor-agent flag, new asset dropped) → add a phase that exercises it
- New daemon-side regression caught in production → add a fail-criteria row
- New adapter family that can negotiate with cursor → add a cross-family phase