You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Define a canonical execution event schema for browser clients consuming AceClaw runtime state.
This issue is the protocol foundation for #430. It must be completed before the WebSocket bridge or React reducer are treated as stable.
Why
AceClaw already emits CLI-oriented streaming notifications (stream.text, stream.tool_use, stream.tool_completed, permission.request, stream.plan_*, etc.), but browser clients need a stable event model that can:
reconstruct an in-flight execution tree
recover after refresh/reconnect
correlate tool calls, permission pauses, and future plan/subagent nodes
evolve without forcing UI heuristics into the reducer
The goal is not to replace existing daemon events. The goal is to define an additive, browser-facing contract.
Key Architectural Decision: Reducer Builds the Tree (Not Schema)
Decision: Schema layer defines flat wire format (= daemon events as-is). Reducer layer (#435) does all tree mapping.
Layer
Responsibility
Does NOT do
Schema (this issue)
TypeScript types for daemon JSON-RPC notifications (flat)
"One kernel. Many windows." (PRD-Frontend.md §2)
Future clients need DIFFERENT tree shapes from same events:
- CLI → no tree, just text stream
- Web UI → deep nested tree with animations
- VS Code → TreeView API format (different structure)
- Mobile → simplified 2-level tree
- Slack bot → plain text summary
If daemon sends flat events: each client reduces/transforms independently → perfect
If daemon sends tree: all clients get same shape → mismatch for most
4. Parallel Detection MUST Be in Frontend
Daemon sends (time order):
t=0ms stream.tool_use(id="t1", name="read_file")
t=2ms stream.tool_use(id="t2", name="grep") ← starts while t1 running
t=100ms stream.tool_completed(id="t1", duration=100)
t=200ms stream.tool_completed(id="t2", duration=198)
Reducer logic:
"t2 arrived while t1 status is still 'running' → they are parallel"
→ set parallel=true on both nodes
If daemon had to do this:
Thread executing t1 and thread executing t2 are different VTs
Daemon would need: "before sending tool_use for t2, check if t1 completed"
→ cross-thread state query → race condition → complex and fragile
Reducer does this trivially because events arrive sequentially in JS.
5. Network Efficiency
Flat events (chosen)
Tree objects (rejected)
~100 bytes per event
Full tree snapshot or JSON diff
Stream as they happen
Batch or compute diff
O(1) per event
O(n) where n = tree size
WebSocket text frame
Needs custom diff protocol
Implication for this deliverable
Schema = TypeScript union type mirroring daemon JSON-RPC notifications exactly
Schema defines DaemonEvent discriminated union (by method field)
Do NOT add parentId, nodeType, or tree semantics to daemon events
Missing Events — Daemon Must Add
Critical discovery: Some events exist only in EventBus (internal) and are never sent as JSON-RPC notifications to any client.
Missing Event
Priority
Current State
Required Change
stream.turn_started
HIGH
TurnStarted only in EventBus (metrics/journal)
~5 lines in StreamingAgentHandler.java
stream.turn_completed
HIGH
TurnCompleted only in EventBus (metrics/journal)
~5 lines in StreamingAgentHandler.java
stream.session_started
MEDIUM
SessionEvent.Created in EventBus only
~5 lines when session is created
stream.plan_step_fallback
LOW
Fallback triggers but no notification emitted
~5 lines in SequentialPlanExecutor.java
Impact: Without stream.turn_started/turn_completed, frontend CANNOT draw Turn nodes. It would have to use fragile heuristics (infer turn boundaries from tool_use timing gaps). Adding these is ~10 lines total.
This issue should:
Define the params schema for these new notifications
// Wire format — mirrors daemon exactly. FLAT, no tree structure.typeDaemonEvent=|{method: 'stream.turn_started';params: TurnStartedParams}|{method: 'stream.turn_completed';params: TurnCompletedParams}|{method: 'stream.session_started';params: SessionStartedParams}|{method: 'stream.text';params: TextDeltaParams}|{method: 'stream.tool_use';params: ToolUseParams}|{method: 'stream.tool_completed';params: ToolCompletedParams}|{method: 'stream.plan_created';params: PlanCreatedParams}|{method: 'stream.plan_step_started';params: PlanStepStartedParams}|{method: 'stream.plan_step_completed';params: PlanStepCompletedParams}|{method: 'stream.plan_step_fallback';params: PlanStepFallbackParams}|{method: 'stream.plan_replanned';params: PlanReplannedParams}|{method: 'stream.plan_completed';params: PlanCompletedParams}|{method: 'stream.plan_escalated';params: PlanEscalatedParams}|{method: 'stream.subagent.start';params: SubAgentStartParams}|{method: 'stream.subagent.end';params: SubAgentEndParams}|{method: 'stream.compaction';params: CompactionParams}|{method: 'stream.usage';params: UsageParams}|{method: 'stream.heartbeat';params: HeartbeatParams}|{method: 'permission.request';params: PermissionRequestParams}|{method: 'permission.response';params: PermissionResponseParams}// NOTE: ExecutionNode is NOT here. That is #435 Reducer's output type.// This schema is ONLY about what comes over the wire from daemon.
2. Params type definitions for each event
Document the exact JSON shape daemon sends. Derived from:
StreamEventHandler.java callbacks
StreamingNotificationHandler.java serialization
PlanEventListener.java callbacks
3. New events specification (for daemon to implement in #431)
Overview
Define a canonical execution event schema for browser clients consuming AceClaw runtime state.
This issue is the protocol foundation for #430. It must be completed before the WebSocket bridge or React reducer are treated as stable.
Why
AceClaw already emits CLI-oriented streaming notifications (
stream.text,stream.tool_use,stream.tool_completed,permission.request,stream.plan_*, etc.), but browser clients need a stable event model that can:The goal is not to replace existing daemon events. The goal is to define an additive, browser-facing contract.
Key Architectural Decision: Reducer Builds the Tree (Not Schema)
Decision: Schema layer defines flat wire format (= daemon events as-is). Reducer layer (#435) does all tree mapping.
Why Reducer, Not Schema (Detailed Rationale)
1. Separation of Concerns
2. Pure Function vs Multi-Thread State
(oldState, event) => newState3. Multi-Client Future
4. Parallel Detection MUST Be in Frontend
5. Network Efficiency
Implication for this deliverable
ExecutionNode— that is Reducer output type (feat(frontend): WebSocket client + EventReducer (event → tree state machine) #435)DaemonEventdiscriminated union (bymethodfield)Missing Events — Daemon Must Add
Critical discovery: Some events exist only in EventBus (internal) and are never sent as JSON-RPC notifications to any client.
stream.turn_startedTurnStartedonly in EventBus (metrics/journal)StreamingAgentHandler.javastream.turn_completedTurnCompletedonly in EventBus (metrics/journal)StreamingAgentHandler.javastream.session_startedSessionEvent.Createdin EventBus onlystream.plan_step_fallbackSequentialPlanExecutor.javaImpact: Without
stream.turn_started/turn_completed, frontend CANNOT draw Turn nodes. It would have to use fragile heuristics (infer turn boundaries from tool_use timing gaps). Adding these is ~10 lines total.This issue should:
Non-Negotiable Constraints
Deliverables
1. TypeScript wire-format types (DaemonEvent union)
2. Params type definitions for each event
Document the exact JSON shape daemon sends. Derived from:
StreamEventHandler.javacallbacksStreamingNotificationHandler.javaserializationPlanEventListener.javacallbacks3. New events specification (for daemon to implement in #431)
4. Snapshot interoperability rules
lastEventIdmonotonic ordering5. Forward-compatibility hooks
Reserve extension points for:
stream.swarm_*,stream.worker_*Success Criteria
aceclaw-dashboard/src/types/events.ts)Out of Scope
Related