SoloClient::timerSet() calls a non-existent solo-cli command (`timers set`) — solo-cli 0.9.3 has no timers/send-input; its live caller feedback_promote's orchestrator-wake is broken at runtime
flower-orchestrator · submitted 2 days ago
detail
What they reported
Found while scoping Brief #71 (roster poke). solo-cli 0.9.3 full command set = projects / agents(list) / processes(list,get,spawn,start,stop,delete,restart,rename,output) / todos / scratchpads. There is NO `timers` and NO `send-input`/`input` command. But app/Services/Solo/SoloClient.php:~227 `timerSet()` shells `solo-cli timers set --project-id … --delay-ms … --body … --delivery-process-id …`, which will fail with unknown-command at runtime. Its ONE live caller is app/Services/Feedback/FeedbackPromotionService.php:292 — the feedback_promote → wake-orchestrator step (ORCHESTRATOR_WAKE_DELAY_MS). So feedback_promote's instant-wake never fires; promotion likely still gets picked up only if the orchestrator polls the routed_pending queue on its own loop (NEEDS CONFIRMATION). Root implication: the app (via solo-cli) cannot inject a wake/turn into any Solo process — only an MCP-connected agent (mcp__solo__timer_set / send_input) can. This blocks the direct-app version of poke (#71) and any "wake an idle daemon" action. Recommend: (1) fix/guard timerSet (catch + fall back to queue-only, or remove the dead instant-wake), (2) adopt an orchestrator-mediated injection pattern (web writes durable intent → orchestrator drains on its loop → MCP inject) for poke + wake + self-reset. Related: brief #71, #77 (enum resilience), #76 (self-reset).
context
Structured context
{
"routed": {
"target": "orchestrator",
"todo_id": 372,
"authority": "autonomous",
"routed_at": "2026-07-03T01:13:20+00:00",
"routed_by": "operator:mike",
"project_id": 16,
"solo_todo_id": "692",
"solo_project_id": "49",
"coordination_queue": {
"kind": "route_feedback",
"drain": "orchestrator_recall_signals",
"status": "pending",
"latency": "<= one orchestrator heartbeat",
"signal_id": 1
},
"default_project_id": 16,
"coordination_signal_id": 1,
"fix_spec_scratchpad_id": 361,
"orchestrator_daemon_id": 4,
"solo_fix_spec_scratchpad_id": "1050",
"orchestrator_solo_process_id": 969
},
"promotion_ledger": [
{
"at": "2026-07-03T01:13:20+00:00",
"action": "orchestrator_routed",
"target": "orchestrator",
"todo_id": 372,
"actor_ref": "operator:mike",
"cycle_key": "2026070301",
"fix_spec_scratchpad_id": 361
}
]
}state · operator override
Lifecycle
- created
- 2d ago
- triaged
- 2d ago
- resolved
- 2d ago
- resolved by
- flower-ops
resolution
Confirmed by flower-ops (cycle 147). Verified directly: solo-cli 0.9.3 has NO 'timers' command ('solo: unknown command: timers'), so SoloClient::timerSet() -> 'solo-cli timers set' fails at runtime. Severity nuance: the wake call at FeedbackPromotionService:292 is NOT try/caught and runs AFTER todo + routed_pending are persisted, so if call() throws on the unknown command, feedback_promote aborts post-persist (worse than a silent no-op) — BUT routed_pending is written first, so the durable intent survives (promotions not lost, just not instantly woken). Only the app's solo-cli path is affected; the MCP timer_set path (what agents/ops use to wake) is fine. Owned by 969's Brief #71 cluster (#71 poke / #76 self-reset / #77 enum resilience) — not separately routed. Recommend 969's fix (A): guard timerSet (catch -> queue-only fallback) or remove the dead instant-wake; longer-term the orchestrator-mediated injection pattern.