6.8 KiB
6.8 KiB
FN-4824 — Cross-node assignment-wake propagation contract
1) Scope & Relationship to FN-4819
This contract closes the FN-4819 §3 operational-handoff gap for assignment-driven wakes across nodes by defining push, fallback, and missed-wake recovery behavior for owner and peer runtimes. It does not change FN-4819 §2 distributed checkout mutex work (FN-4822), and it does not cover FN-4819 §5 non-goals: scheduler failover, live-process migration, multi-master writes, or automatic node promotion.
2) Definitions
- owner node: node that currently owns execution responsibility for an agent/task assignment in practice (the node that must wake the assigned agent).
- peer node: another node in the mesh that can observe/forward assignment signals but is not the wake target for that assignment.
- assignment event: cross-node event representing an assignment write.
- wake: a heartbeat trigger equivalent in intent to local
agent:assignedhandling. - push path: delivery via remote event stream (
/api/events/stream) consumed byRemoteNodeClient.streamEvents. - fallback path: bounded pull/poll while push transport is degraded but still reachable.
- missed wake: assignment occurred but no wake fired before stream failure window ended.
- eventual wake bound: maximum allowed time from restoration/recovery condition to wake emission.
Event payload contract
export interface TaskAssignedEventPayload {
taskId: string;
agentId: string;
fromNodeId: string;
toNodeId?: string;
leaseEpoch?: number;
assignedAt: string; // ISO-8601
}
3) Push Path Contract (healthy transport)
- The originating node MUST emit
task:assignedon the same event surface already used fortask:created,task:moved, andtask:updated(/api/events/stream). - Event name is
task:assigned. Payload shape isTaskAssignedEventPayload. - The receiving remote runtime MUST forward this event through
RemoteNodeRuntime.forwardRemoteEventby re-emittingtask:assignedfor owner-side listeners. - Owner-side wake listener MUST treat forwarded
task:assignedas semantically equivalent to localagent:assignedwake intent. - Under healthy transport, p95 assignment-write → wake latency target is <= 2 seconds.
4) Fallback Path Contract (transport degraded but reachable)
- While SSE is disconnected and reconnecting inside
runEventStreamLoopbackoff window (reconnectBaseDelayMs..maxReconnectDelayMs, bounded bymaxReconnectAttempts), assignment wake delivery MUST NOT rely solely on SSE. - Runtime MUST invoke a fallback pull seam during disconnected windows:
RemoteNodeClient.pollPendingAssignments({ since }). - Poll response MUST include assignment rows newer than a cursor (
assignedAt/cursor semantics). - Owner-side listener MUST trigger wake when poll returns assignment newer than the last observed
(taskId, agentId, assignedAt)tuple. - Fallback bound: wake MUST occur within <= 2 × reconnectBaseDelayMs (default <= 10 s) even if zero SSE events succeed in that interval.
- Idempotency requirement: replay of already-observed assignment (
same taskId + same assignedAt) MUST NOT emit a duplicate wake.
5) Missed-Wake Recovery Contract (transport unavailable, then restored)
- If stream errors past
maxReconnectAttemptsand runtime transitions toerrored, assignment writes during outage are considered potential missed wakes. - On next successful transport restoration/health re-entry, owner-side scheduler/runtime MUST run one-shot reconciliation:
- enumerate locally-owned agents,
- detect
assignedTaskIdchanges vs last observed snapshot, - emit exactly one wake per recovered assignment.
- Eventual wake bound after restoration is <= next heartbeat tick + reconciliation pass, hard ceiling <= 60 s under default heartbeat settings.
- Reconciliation MUST emit audit event
wake:cross-node-reconcilewith{ ownerNodeId, peerNodeId, agentIds, recoveredAssignments }.
6) Telemetry & Audit Requirements
For every cross-node wake trigger, telemetry MUST include source discriminator:
source="cross-node-push"source="cross-node-poll"source="cross-node-reconcile"
Required diagnostics:
[wake-trigger-diagnostics]log entry includes source discriminator andtaskId.
Required run-audit event names:
wake:cross-node-pushwake:cross-node-pollwake:cross-node-reconcile
7) Non-Goals
- No distributed mutex/schema definition changes (FN-4822).
- No changes to local single-node
agent:assignedsemantics. - No scheduler failover or live-process state migration.
- No settings consensus or multi-master writes.
- No cross-node settings sync behavior changes (FN-4796).
8) Testability Surface
The following scenarios are required and must be executable with in-process fakes and fake timers:
- Healthy push: injected
task:assignedover stream causes one wake on owner runtime withsource: "cross-node-push"and correct{taskId, agentId}. - Degraded poll fallback: stream disconnect path still wakes within <= 10 s simulated via poll fallback; no duplicate wake on later stream replay.
- Missed-wake reconciliation: runtime reaches
errored, assignment happens during outage, restoration + reconcile emits onecross-node-reconcilewake and audit event. - No-wake invariants: duplicate broadcast with same
(taskId, agentId, assignedAt)does not re-wake; newerassignedAtdoes. - Telemetry shape: wake diagnostics include source discriminator and
taskId.
9) References
docs/design/fn-4819-distributed-multi-node-coordination-gap.md:173-324(§3 unavailable-node handoff policy and testability framing)packages/engine/src/agent-heartbeat.ts:3665-3755(watchAssignments, localagent:assignedwake hook)packages/engine/src/agent-heartbeat.ts:2430-2456(task_assignedreason and[wake-trigger-diagnostics]surface)packages/engine/src/runtimes/remote-node-runtime.ts:153-230(event stream loop, reconnect/backoff lifecycle)packages/engine/src/runtimes/remote-node-runtime.ts:239-271(forwardRemoteEventswitch)packages/engine/src/runtimes/remote-node-client.ts:98-120(streamEventssource)packages/engine/src/runtimes/in-process-runtime.ts:1180-1220(in-process event-forwarding baseline)packages/dashboard/src/sse.ts:384-390,773-775(SSE catalog currently forwardingtask:created|moved|updated)packages/dashboard/src/server.ts: /api/events, /api/events/keepalive, /api/events/disconnect(event stream routes)packages/core/src/agent-store.ts: assignTask +agent:assigned` emission seampackages/engine/src/node-health-monitor.ts(health restoration signal seam)packages/engine/src/mesh-lease-manager.ts(isLeaseRecoverable/recoverAbandonedLeaseseam referenced by FN-4819 §3.3)