Skip to content

Neutron N5 — Executable Task Graph and Subagents Maintainer Brief ​

Status ​

Slice 1 implemented in @intentloom/application/neutron-scheduler (graph execution validation, deterministic scheduling classification/selection, pure state transitions). Slice 2 implemented — executeNeutronTaskNode runs exactly one scheduler-selected ready node through N3 → N2 → N4 under a read-only capability clamp and returns an application-level result wrapping N1 NeutronSubagentResult. Slice 3 implemented — local-first leases and one bounded concurrent scheduling wave (executeReadyNeutronTaskNodes). Slice 4 implemented — bounded retry, cancellation propagation, timeout recovery, and stale-attempt protection on that wave. Slice 5 implemented — deterministic graph aggregation, stale project/checkpoint/profile detection, and parent-child/attempt/tool/context provenance on a one-wave reconciliation boundary. N5 runtime milestone complete for the authorized read-only scheduler. No graph runner loop. Mutation Slice 5 later attached graph-linked proposal/review evidence without giving N5 Apply authority.

Mutation-routing brief is NEUTRON_MUTATION_ROUTING_BRIEF.md. Mutation implementation remains deferred.

Evidence baseline: origin/main @ 957756e12c6de488a943f49735816eb6ac2e498a (2026-09-04; legitimate advancement over N5 handoff 279eacd — Dependabot deps only). N5 handoff merge: 279eacd4fddcd1a08f51e1b32b19e79eb6e1a94c (#444).

Maintainer decision for this increment: choose N5 before mutation routing. N5 must prove controlled scheduling and subagent execution using current read-only authority boundaries.

Authoritative roadmap gate: NEUTRON_RUNTIME_ROADMAP.md §N5.


1. Current baseline ​

ItemEvidence
Post-N4 handoff main SHA3b504d143146b95a1215529208d6ce75f565b2c2 (#442)
Legitimate later advancementd28baf570a70a60ab536c10228a0de3f51e41e5e (#433 deps only)
N4 Slice 1PR #439 @ 5d8c6e16427466e6b1627a0320f1d96a12c5dc43 — router + inspect
N4 Slice 2PR #441 @ e19d50edf3d2bb013f129c838725472c7195dd7a — read-only catalog
N3 runtime milestoneComplete (Slices 1–4); optional Slice 5 unauthorized
Routed read-only toolsinspect, doctor, memorySearch, timeline, conformance, securityAudit, projectDiff

N1–N4 capability summary ​

MilestonePackage surfaceRole today
N1@intentloom/protocol/neutron-runtime, @intentloom/validator/neutron-runtime, prepareNeutronRuntimeContractSnapshotVersioned contracts: session, adapter, context bundle, tool envelope, task graph, subagent result, usage budget, runtime event
N2@intentloom/application/neutron-n2 — runNeutronN2ReadOnlyLoop, OllamaModelAdapterOne read-only model turn loop; fingerprint proof; AbortSignal; one in-flight turn per session
N3@intentloom/application/neutron-context-assembly — assembleNeutronContext; N2 hook prepareNeutronN2ModelPromptDeterministic context assembly with budget, provenance, profile/task/memory integration
N4@intentloom/application/neutron-tool-router — routeNeutronToolInvocationFail-closed capability-scoped routing over seven read-only tools; no parallel execution path

2. Existing foundations inventory ​

2.1 Task graph contracts (N1 — schema only, no scheduler) ​

Location: packages/protocol/src/neutron-runtime.ts, packages/validator/src/neutron-runtime-records.ts

Field / conceptCurrent state
URNurn:intentloom:schema:neutron-task-graph:1
NeutronTaskGraphroot, sessionId, nodes[]
NeutronTaskNodetaskId, parentId, dependencies[], role, requiredCapabilities[], state, expectedOutput
Lifecycle states (NEUTRON_TASK_STATES)pending, ready, running, blocked, cancelled, timed-out, failed, completed
Budget / retry on nodeNot present in N1 node schema — N5 adds scheduler metadata additively
ValidatorvalidateNeutronTaskGraph, validateNode — shape only; no cycle or dependency semantics
Fixturetests/fixtures/neutron-runtime/contract-snapshot.v1.json — single completed node
ExecutionNone — graph is contract snapshot, not a running scheduler

Gap: N5 must add graph validation (references, cycles, dependency semantics) and scheduler-owned state transitions without renaming N1 states.

2.2 Subagent records (persisted orchestration foundation) ​

Two related but distinct records:

A. Legacy persisted task records ​

Location: packages/application/src/index.ts — spawnNeutronSubagentTask, getNeutronSubagentTask, listNeutronSubagentTasks

AspectEvidence
Protocol typeNeutronSubagentTaskRecord (schemaVersion: "1")
Rolesresearch, arch-checker, test-runner, conformance-auditor, custom
Statusespending, running, completed, failed
Persistence.aif/neutron/subagents/{taskId}.json
Parent relationshipNone — flat records, no graph linkage
Current behaviorStub: spawn writes completed immediately with synthetic resultOutput
CLI`intentloom neutron subagent spawn
Teststests/neutron-orchestration.test.ts

B. N1 subagent execution result envelope ​

Location: packages/protocol/src/neutron-runtime.ts

FieldPurpose
taskId, sessionId, rootBinding
statuscompleted | failed | cancelled
outputDigestsha256:… over normalized output
mutationAttemptedMust be false

Gap: N5 bridges graph nodes → subagent execution → enriched NeutronSubagentResult with provenance, usage, tool envelopes, and attempts. Legacy NeutronSubagentTaskRecord remains the CLI-facing persistence shape until a migration slice is justified.

2.3 Checkpoints (L5 — separate from Neutron task graph) ​

Location: packages/application/src/index.ts

OperationPersists toStates
createTaskCheckpoint.aif/memory/checkpoints/{id}.jsoninitial active
pauseTasksamepaused
cancelTasksamecancelled
redirectTasksameredirected
resumeTasksameresumed
deleteTaskCheckpointremoves file—
listTaskCheckpoints, getTaskCheckpointread—

N3 integration: assembleNeutronContext reads latest checkpoint by taskId for context excerpts (neutron-context-state-collectors.ts). N3 never mutates checkpoints.

N5 relationship: Checkpoints are user/task orchestration state, not scheduler lease state. N5 may read checkpoint metadata for stale detection when a graph node references taskId, but must not conflate TaskCheckpoint.state with NeutronTaskState. Scheduler lifecycle persistence lives under .aif/neutron/scheduler/ (proposed below).

2.4 Delegation and capability clamping ​

Location: delegateTaskRole in packages/application/src/index.ts

AspectBehavior
InputDelegationRequest with profileName, role, optional requestedCapabilities
Profile lookupgetProfile — throws if missing
Role validationRole must be in profile.activeRoles
Read-only enforcementcontext-scout and reviewer force readOnly: true; cannot request readOnly: false
Effective capabilitiesIntersection/clamp: readOnly, allowedPaths, allowedTools, maxBudget, allowNetwork
Persistence.aif/memory/delegations/{delegationId}.json
N4 reuseAgentRoleCapabilities passed to routeNeutronToolInvocation; N4 requires readOnly: true, allowNetwork: false, tool in allowedTools

N5 effective grant formula:

text
effectiveCapabilities =
  sessionCapabilities
  ∩ profileCapabilities(profileName)
  ∩ delegationResult.effectiveCapabilities   (when delegateTaskRole used)
  ∩ nodeRequiredCapabilities(requiredCapabilities)
  ∩ readOnlyCatalogConstraint              (N5 initial: all nodes read-only)

N5 must call delegateTaskRole (or equivalent pure clamp function extracted from it) before node execution; never inherit parent capabilities implicitly.

2.5 Neutron runtime session ​

Location: packages/protocol/src/neutron-runtime.ts, N2 loop

AspectEvidence
Session statescreated, discussing, inspecting, planning, cancelled, timed-out, failed, completed
mutationAllowedAlways false in N1–N4
N2 concurrency guardinFlightSessions Set — one in-flight N2 turn per sessionId
CancellationAbortSignal checked in N2 and N4 authorization
Fingerprint proofN2 compares before/after project fingerprint; throws on mutation
TimeoutN4: per-invocation timeoutMs, optional deadlineMs; session timed-out blocks tools

N5 session coordination extends this: graph-level cancellation must propagate to child node AbortSignal, N2 turns, and N4 tool calls.

2.6 N4 router boundary (subagents must reuse, not bypass) ​

Location: packages/application/src/neutron-tool-router.ts

Subagent tool execution path:

text
scheduler node worker
→ build NeutronToolInvocation + NeutronRuntimeSession + AgentRoleCapabilities
→ routeNeutronToolInvocation({ dispatch: createNeutronReadOnlyDispatch(fs) })
→ existing application operation

Forbidden: Direct calls to inspectProject, doctorProject, etc. from scheduler workers bypassing routeNeutronToolInvocation. Forbidden: A second tool registry or shell dispatch for subagents.

Checks already enforced: root/session binding, active session lifecycle, deadline, AbortSignal, read-only tool definition, capability clamp, result byte limits, normalized error envelopes.

2.7 Workspace synchronization ​

Location: syncLocalWorkspaceState in packages/application/src/index.ts

AspectEvidence
Shared project rootYes — all subagents share the selected root
IsolationMetadata only under .aif/neutron/subagents/; no git worktrees
FingerprintUses inspectProject readiness + doctor findings + security score
Read-only N5No worktree or mutation-isolation machinery

Stale detection compares scheduler-start fingerprint vs pre-execution fingerprint.


3. N5 definition — precise objective ​

N5 extends persisted Neutron subagent foundations into a deterministic, bounded, cancellable execution scheduler that coordinates already-authorized N2/N3/N4 capabilities across a validated task dependency graph.

What N5 is ​

LayerN5 responsibility
Persisted task graphValidate structure, dependencies, roles; persist scheduler state
SchedulerDeterministic ready-node selection, concurrency limits, leases, retries, cancellation propagation — outside model weights
Worker / subagent executionPer-node: capability clamp → N3 context → N2 model turn → N4 tools → structured result
Model turnsN2 adapter boundary; one bounded turn sequence per node (reuse N2 loop composition)
Routed tool executionN4 only; same path as parent
Task result aggregationDeterministic parent merge from graph order, not completion timing
CheckpointsRead for stale context; scheduler writes separate execution records
Mutation approvalOut of scope — no Apply, no write tools, no project bytes changed

What N5 is not ​

  • Autonomous mutation or mutation routing
  • Generic shell or parallel tool execution path
  • Model-as-scheduler (the model may propose graphs via future APIs; the scheduler executes validated graphs deterministically)
  • Desktop UI (N6)
  • New provider support
  • Replacement of L5 checkpoints or legacy subagent CLI (initial slices compose alongside)

N5 exit gate ​

Deterministic multi-task fixtures prove: dependency handling, cancellation, timeout recovery, budget enforcement, provenance, stable aggregation, and no hidden background mutation (project fingerprint unchanged under read-only roles).


4. Runtime ownership decision ​

Pros:

  • N1–N4 already live in application subpaths (neutron-n2, neutron-context-assembly, neutron-tool-router); N5 composes them directly
  • No new package consumer exists yet (Desktop N6 deferred)
  • Roadmap: dedicated package only when contract and real consumer justify boundary
  • Tests already inject FileSystem through application APIs
  • Dependency direction preserved: application → protocol/validator, not reverse

Cons:

  • packages/application/src/index.ts is already oversized; N5 modules must not land in the monolith — dedicated neutron-scheduler-*.ts modules with ≤250-line budget
  • Risk of orchestration monolith if slices skip extraction

Option B — Introduce packages/neutron-runtime (deferred) ​

Pros:

  • Clean boundary for execution-session coordination named in roadmap decomposition
  • Future Desktop/daemon consumer could depend on runtime without full application surface
  • Enforces dependency direction earlier

Cons:

  • No second consumer yet; would duplicate or re-export N2/N3/N4 composition boundaries
  • Violates "introduce only when justified" without N6 or daemon RPC consumer
  • Large migration cost for existing test imports

Decision ​

Option A for N5 foundation slices. Extract cohesive modules under packages/application/src/neutron-scheduler-*.ts and export via @intentloom/application/neutron-scheduler subpath.

Re-evaluate Option B when N6 Desktop Neutron Workspace needs a runtime consumer or when scheduler modules exceed sustainable file count (>6 production modules or repeated cross-package import pressure from apps/desktop).

Do not create packages/neutron-runtime in N5 Slice 1.


5. Task-node state machine ​

Reuse NEUTRON_TASK_STATES verbatim. Do not invent succeeded (use completed), paused (use blocked with scheduler pause reason metadata), or stale (transition to failed with stale-state error code in result metadata).

StateMeaning
pendingRegistered; dependencies not yet satisfied
readyAll dependencies terminal-success; eligible for scheduling
runningLease held; worker executing N3/N2/N4
blockedDependency failed/cancelled, budget exhausted awaiting parent decision, or explicit pause
cancelledCancel propagated or node explicitly cancelled
timed-outNode, lease, or model/tool deadline exceeded
failedNon-retryable error or retries exhausted (includes stale-state rejection)
completedTerminal success; result persisted

Transitions ​

FromToTriggerPersisted evidenceCancel behaviorDependency effect
pendingreadyAll deps completedreadyAt, dep snapshot digest——
pendingblockedAny dep failed/cancelled/timed-outblockedReason: dependency-{state}—Blocks descendants to blocked
pendingcancelledSession/node cancel before startcancelledAt, cancelSourceImmediatePropagate block to dependents
readyrunningLease acquired, worker startedleaseId, attempt, startedAtPre-start cancel → cancelled—
readycancelledCancel while queuedcancelledAt—Dependents → blocked or cancelled per policy
runningcompletedWorker successNeutronSubagentResult, usage, tool envelopes—Unblock dependents → evaluate ready
runningfailedNon-retryable errorNormalized error, attemptsAbort in-flight workDependents → blocked
runningtimed-outDeadline/lease expiryTimeout kind metadataAbort in-flightDependents → blocked; may retry if policy allows
runningcancelledCancel during executioncancelledAtAbortSignal to N2/N4Dependents → blocked/cancelled
runningreadyRetryable failure, attempts remainIncrement attempt, clear lease——
failed/completed/terminal—No outbound transitionsFinal result immutable——

Scheduler pause (maintainer cancel of scheduling without cancelling session): moves ready → blocked with blockedReason: scheduler-paused; resume re-evaluates deps.


6. Dependency semantics ​

Graph validation runs before any execution (validateNeutronTaskGraphForExecution).

CaseSemantics
Zero dependenciesNode may reach ready immediately after graph activation
Multiple dependenciesAll must reach completed before ready
Dependency successDependent transitions pending → ready (deterministic batch)
Dependency failureDependent → blocked with reason; never auto-run
Cancelled dependencyDependent → blocked or cancelled (config: default blocked with cancel cascade optional)
Timed-out dependencyTreat as failure → blocked downstream
Stale dependency resultIf dep completed but fingerprint/provenance stale flag set → dependent → failed at schedule time
Cyclic graphReject at validation with validation-failed / cycle path in error
Missing dependency IDReject at validation
Duplicate taskIdReject at validation
parentIdProvenance and aggregation ordering only; not an execution dependency unless also listed in dependencies

Deterministic cycle detection: DFS with nodes sorted by taskId (code-point order); first cycle found is reported in stable order.


7. Runnable-node selection and concurrency ​

Ready selection (deterministic) ​

When capacity available, select runnable nodes in order:

  1. Nodes in state ready with valid lease slot
  2. Sort by: (a) optional explicit priority if added to scheduler metadata — lower number first; (b) taskId code-point ascending (default when no priority)
  3. Never use filesystem order, completion timing, or model output for ordering

Concurrency limits ​

SourceDefaultMaximum
Scheduler option maxConcurrentNodes14 (hard cap for N5 initial)
Session / profile maxBudgetMay further restrict parallel token use—
Per-parent limitDefault: unlimited within global cap; optional maxConcurrentChildren on graph metadata2
Per-role limitOptional map role → maxConcurrent1 for same role initially

When capacity opens: Re-run ready selection on next scheduler tick; fill slots in deterministic order. No Promise.all over unbounded ready set.

Conservative default: maxConcurrentNodes: 1 for Slice 1–2 tests; concurrency slice proves cap at 2–4 with fixtures.


8. Execution leases and heartbeats ​

Local-first, single daemon/process assumption. No distributed consensus.

ConceptDefinition
Lease owner ID{sessionId}:{taskId}:{attempt}
AcquisitionAtomic write of lease record; fails if unexpired lease exists
Lease TTLDefault nodeTimeoutMs or 120_000 ms, whichever is smaller
HeartbeatWorker renews lease every ttl/3 while running
ExpiryIf heartbeat missing at TTL → node → timed-out, lease cleared, retry if allowed
Lost worker recoveryOn scheduler tick, expired lease → timed-out → retry or failed
Duplicate execution preventionSecond acquirer gets lease-held; must not enter N2/N4

Persistence: .aif/neutron/scheduler/leases/{leaseId}.json


9. Retry and timeout model ​

Retry policy ​

Failure classRetryableNotes
permission-denied, capability-deniedNoClamp error
validation-failed, root-mismatch, invalid schemaNo
unsupported-toolNo
cancelledNo
budget-exceededNo
timeout (transient provider)YesMax attempts default 2
operation-failed (transient)YesBounded
Worker interruption / expired leaseYesIf attempts remain

Defaults: maxAttempts: 2 (initial attempt + 1 retry). Scheduler metadata per node may lower, not raise above graph-level maxAttempts cap (default 3 absolute max).

Timeout layers (do not collapse) ​

LayerOwnerDefault
Model turn timeoutN2 adapter / node worker60_000 ms
Tool invocation timeoutN4 invocation.timeoutMs30_000 ms
Node execution timeoutScheduler worker wraps full N3+N2+N4120_000 ms
Lease timeoutScheduler≤ node execution timeout
Scheduler/session timeoutGraph-level optional graphDeadlineMsunset = no graph timeout

Interaction: inner timeouts fire first; node worker maps to normalized NeutronErrorCode; cancellation aborts all layers via shared AbortSignal.


10. Cancellation propagation ​

ScopeBehavior
Cancel one nodeIf pending/ready → cancelled; if running → abort signal + cancelled; dependents → blocked
Cancel parent nodeOptional cascade to child nodes (default: cancel children)
Cancel whole sessionAll non-terminal nodes → cancelled; session state → cancelled; no new leases
Cancelled dependencyDependents blocked/cancelled per §6
Running childAbortSignal checked before N3, before each N2 turn, passed to N4
Queued childrenNever start; transition to cancelled on session cancel

AbortSignal flow:

text
scheduleCancel(sessionId | taskId)
→ set session/node cancel flag + AbortController.abort()
→ scheduler tick skips cancelled ready nodes
→ running worker catches abort → normalized cancelled result
→ N2 executeTurn(signal) + N4 routeNeutronToolInvocation(signal)
→ retry loop checks signal before retry

Guarantee: After session cancel completes scheduler drain, no in-flight N2/N4 for that session. Tests assert zero tool operations after cancel acknowledgment.


11. Context and token budgets ​

Reuse NeutronUsageBudget and N3 assembleNeutronContext limits.

BudgetSourceExhaustion behavior
Per-node context tokensNode metadata maxContextTokens or default 4000Node → failed, budget-exceeded
Per-node model I/OAccumulate from N2 adapter usageSame
Whole-graph token budgetGraph metadata graphTokenBudget optionalRemaining nodes → blocked; running node completes or fails per policy
Tool result bytesN4 NEUTRON_TOOL_MAX_RESULT_BYTESNormalized budget-exceeded
Graph node countValidation cap default 32 nodesReject at validation

Accounting: After each node, merge usage into graph accumulator persisted at .aif/neutron/scheduler/sessions/{sessionId}/usage.json. Failures are auditable in result provenance.


12. Role and capability clamp ​

Every node execution:

  1. Resolve profileName from graph/session metadata (required when role ≠ default)
  2. Call delegateTaskRole or pure clampCapabilitiesForNode(parent, profile, node)
  3. Pass result effectiveCapabilities to N4 and N3 (role filter)
  4. Verify requiredCapabilities ⊆ granted tools/capabilities
  5. Force readOnly: true, allowNetwork: false for N5 initial implementation

Child cannot exceed parent: effectiveChild = intersect(parentEffective, …) when parent node executed with known grant; root session starts from session baseline (read-only catalog only).


13. Read-only guarantee ​

Allowed tools: the seven N4 read-only tools only.

Forbidden for any child/subagent:

  • Write / Apply / memory mutation / checkpoint mutation via model tools
  • Generic shell / git mutation
  • Direct application mutation operations

Allowed scheduler persistence (not project mutation):

  • Task state, lease, heartbeat, attempt count, result metadata, cancellation flags under .aif/neutron/scheduler/
  • Legacy subagent record updates under .aif/neutron/subagents/

Proof: Project fingerprint (N2-style) before graph start and after graph terminal; must match for read-only roles. Existing runNeutronN2ReadOnlyLoop fingerprint check reused per node.


14. Parent-child provenance ​

Extend NeutronSubagentResult additively (Slice 5) or wrap in application NeutronNodeExecutionRecord (preferred initially to avoid protocol bump):

FieldSource
graphId / session graph digestScheduler
nodeId (taskId)Graph node
parentNodeIdparentId
sessionId, rootSession
role, profileNameNode + delegation
providerKind, modelIdN2 adapter capability
contextBundleProvenanceN3 source IDs + digests
toolsInvokedN4 envelope invocation IDs
attempts, timingScheduler lease records
finalStateNeutronTaskState
childResultDigestsAggregation slice

Do not persist chain-of-thought or raw model hidden reasoning.


15. Result contract and deterministic aggregation ​

Node result ​

Prefer wrapping existing NeutronSubagentResult with scheduler envelope:

typescript
interface NeutronNodeExecutionResult {
  readonly subagent: NeutronSubagentResult;
  readonly taskState: NeutronTaskState;
  readonly usage: NeutronUsageBudget;
  readonly toolEnvelopes: readonly NeutronToolEnvelope[];
  readonly provenance: NeutronNodeProvenance;
  readonly attempts: number;
  readonly warnings: readonly string[];
}

Parent aggregation ​

InputRule
Child orderingSort by taskId ascending (graph structure order)
Successful childrenInclude output digest + summary in parent payload
Failed/cancelled childrenInclude status + error code; do not omit
Partial graphParent may completed with warnings if policy allows partial success; default: parent failed if any required child failed
ConcatenationNever order by completedAt timestamp

16. Stale-state detection ​

Stale kindDetectionAction
Project fingerprint changedCompare scheduler-start vs pre-node fingerprintNode → failed, stale reason
Checkpoint changedupdatedAt or checksum vs context assembly snapshotReject or re-assemble once; then fail
Workspace sync stalesyncLocalWorkspaceState.syncedAt older than thresholdWarning or block
Parent state changedParent not terminal when child completesChild result flagged invalid
Lease expired§8timed-out path
Profile/capabilities changedRe-delegate before run; mismatch → failNo silent widen

Reuse createdSnapshotChecksum from checkpoints and N2 fingerprint helpers.


17. Persistence and crash recovery ​

Allowed persistence layout ​

text
.aif/neutron/scheduler/
  graphs/{graphExecutionId}.json      # graph + node states
  leases/{leaseId}.json
  sessions/{sessionId}/usage.json
  results/{taskId}.json               # NeutronNodeExecutionResult

Recovery on process restart ​

SituationRecovery
running + valid unexpired leaseAmbiguous — treat as expired after TTL; do not assume worker alive
Expired lease→ timed-out, retry if policy allows
completed + result fileIdempotent; do not re-execute
Partial attempt (no result)Increment attempt or fail deterministically

Guarantee statement: At-most-once node execution per {taskId, attempt} under single-process scheduler. Not exactly-once across crashes. Retries may produce duplicate model calls; dedupe by attempt ID in provenance.


18. Proposed runtime flow ​

text
User / parent API
  → validate graph (cycles, refs, caps)
  → persist graph execution record (all nodes pending)
  → scheduler tick loop
      → select ready nodes (deterministic order, concurrency cap)
      → acquire lease
      → clamp capabilities (delegateTaskRole)
      → fingerprint check (stale gate)
      → assembleNeutronContext (N3)
      → runNeutronN2ReadOnlyLoop or deterministic test worker (N2)
          → routeNeutronToolInvocation (N4) for each tool call
      → persist NeutronNodeExecutionResult
      → release lease; update node state
      → propagate dependency releases
  → aggregate parent results (deterministic)
  → terminal graph state + fingerprint proof

The scheduler tick is pure TypeScript control flow (testable with fake clock and injected workers). The model never selects execution order.


19. Scheduler API (minimal) ​

Application subpath @intentloom/application/neutron-scheduler:

OperationPurpose
validateNeutronTaskGraphForExecution(graph)Pre-flight validation
createGraphExecution(input)Persist initial execution record
tickGraphExecution(executionId, options)Run one scheduler step (ready select + start workers up to cap)
cancelGraphExecution(executionId, scope)Cancel session/node
getGraphExecutionStatus(executionId)Inspect states
recoverGraphExecution(executionId)Post-crash lease cleanup + resume tick

First vertical slice: validateNeutronTaskGraphForExecution + selectReadyNodes + pure state transition functions — no model execution.


20. N2/N3/N4 reuse (mandatory composition) ​

MilestoneN5 usage
N3assembleNeutronContext per node with taskId, profileName, role
N2runNeutronN2ReadOnlyLoop with createNeutronReadOnlyDispatch(fs) as runTool; or slim deterministic worker in tests
N4All tools via routeNeutronToolInvocation

N5 modules must not duplicate context assembly, adapter protocol, or authorization logic.


21. Threat analysis ​

ThreatMitigation
Task amplificationGraph node cap; session node budget; validation rejects oversized graphs
Runaway concurrencyHard cap maxConcurrentNodes ≤ 4; default 1
Infinite retrymaxAttempts absolute cap; non-retryable error taxonomy
Child capability escalationIntersect clamp; N4 read-only enforcement
Stale authorizationRe-delegate before each run; fingerprint gate
Cancelled work continuingAbortSignal + session cancel drain + tests
Duplicate executionLease acquire fails closed
Cross-project contextRoot binding on every layer (N1 contract rule)
Cross-profile memoryN3 projectId isolation unchanged
Malicious parent taskGraph validation; capability clamp; no shell
Tool-call loopsN2 loop bounded turns per node; tool count cap per node
Budget exhaustionNormalized budget-exceeded; graph budget stops scheduling
Result injection between childrenSigned digests; aggregation from persisted results only

22. Implementation slices ​

Slice 1 — Graph validation and deterministic scheduling core ​

ItemDetail
StatusImplemented on @intentloom/application/neutron-scheduler
ObjectiveValidate graphs; deterministic ready selection; pure state transitions
Modulesneutron-scheduler-validate.ts, neutron-scheduler-select.ts, neutron-scheduler-transitions.ts, neutron-scheduler-errors.ts, neutron-scheduler-sort.ts
APIsvalidateNeutronTaskGraphForExecution, planNeutronTaskScheduling, selectReadyNodes, validateNeutronTaskStateTransition, applyNeutronTaskStateTransition
Reused APIsvalidateNeutronTaskGraph, NEUTRON_TASK_STATES
Teststests/neutron-n5-task-graph.test.ts, tests/neutron-n5-scheduling.test.ts
DecisionsparentId is provenance only; duplicate dependency IDs rejected; ready order is taskId code-point ascending; priority deferred; scheduling classification (ready/waiting/blocked) is separate from protocol node state; default maxConcurrency 1, hard cap 4; cycle paths reported in DFS discovery order
RisksConfusion with L5 checkpoints — document separation
Non-goalsModel execution, N3 assembly, N4 tool calls, persistence, leases, retry loops, concurrency workers
Exit gatePure functions prove ready order and transitions on fixture graphs (met)

Slice 2 — Single-worker node execution (N3/N2/N4 composition) ​

ItemDetail
ObjectiveExecute one ready node through full read-only stack
Modulesneutron-scheduler-worker.ts, neutron-scheduler-execute.ts
Reused APIsassembleNeutronContext, runNeutronN2ReadOnlyLoop, routeNeutronToolInvocation, delegateTaskRole
Teststests/neutron-n5-scheduler-execute.test.ts — deterministic adapter, fingerprint unchanged
RisksN2 in-flight session guard vs graph concurrency — use distinct session IDs per node
Non-goalsParallel nodes, retries
Exit gateSingle-node linear graph executes with provenance + no mutation

Slice 3 — Leases and bounded concurrency ​

ItemDetail
ObjectiveLease acquire/renew/expire; maxConcurrentNodes
Modulesneutron-scheduler-lease.ts, persistence under .aif/neutron/scheduler/
Teststests/neutron-n5-scheduler-lease.test.ts, concurrency cap fixtures
Exit gateNo duplicate active lease; cap enforced deterministically

Slice 4 — Retry, cancellation, timeout recovery ​

ItemDetail
ObjectiveBounded retries; cancel propagation; layered timeouts
Teststests/neutron-n5-scheduler-cancel.test.ts, retry/timeout fixtures
Exit gateCancel mid-run aborts N2/N4; retries respect taxonomy

Slice 5 — Aggregation, stale-state, provenance enrichment ​

ItemDetail
StatusImplemented on @intentloom/application/neutron-scheduler
ObjectiveDeterministic graph aggregation; fingerprint/checkpoint/profile stale gate; full provenance record
Modulesneutron-scheduler-graph-result.ts, neutron-scheduler-stale.ts, neutron-scheduler-provenance.ts, neutron-scheduler-aggregate.ts
APIsaggregateNeutronTaskGraphResults, detectNeutronGraphStaleness, reconcileNeutronTaskGraphExecution
Teststests/neutron-n5-aggregation.test.ts, tests/neutron-n5-stale-state.test.ts
Exit gateMulti-node graph completes with stable aggregation; stale project/checkpoint/profile rejected without rerun (met)
Non-goalsGraph runner loop, result persistence, mutation routing, N6, N3 Slice 5, generic shell

Exactly one: Slice 1 — Graph validation and deterministic scheduling core.

Rationale: Smallest testable increment without concurrent model execution; confirms dependency semantics and state machine against existing N1 types; unblocks all later slices.

Authorization requested after brief merge: explicit maintainer sign-off for N5 Slice 1 only.


24. Test strategy ​

CategoryCases
GraphLinear chain, diamond, independent tasks, cycle rejection, failed/cancelled/timed-out dependency
SchedulingDeterministic ready order, concurrency cap, no duplicate execution
LeaseAcquire, renew, expire, recovery
RetryRetryable vs non-retryable, max attempts
CancellationNode, parent, session, propagation to N2/N4
BudgetPer-node limit, graph limit, exhaustion
CapabilitiesParent/child clamp, read-only child, denied tool
Stale stateFingerprint, profile, checkpoint change
AggregationDeterministic ordering, partial failure, cancellation
SafetyProject bytes unchanged (MemoryFileSystem + fingerprint)

All tests use injected FileSystem, fake clock, and deterministic model adapter — no live Ollama required for scheduler proofs.


25. Performance considerations ​

ConcernN5 initial bound
Graph size≤32 nodes default validation cap
Runnable nodes per tick≤4
Scheduler tickO(n log n) sort on ready set; n ≤32
Persisted writesOne lease + one state file per transition; batch where safe
Context/model costDominates; scheduler overhead must stay <5% of node wall time in fixtures

No queues, worker pools, or external services until evidence requires them.


26. Module boundaries (file-size planning) ​

ModuleResponsibilityTarget lines
neutron-scheduler-validate.tsGraph validation, cycle detection≤200
neutron-scheduler-select.tsReady selection ordering≤120
neutron-scheduler-transitions.tsPure state machine≤200
neutron-scheduler-lease.tsLease acquire/renew/expire≤200
neutron-scheduler-worker.tsN3/N2/N4 node worker≤250
neutron-scheduler-execute.tsTick loop orchestration≤250
neutron-scheduler-aggregate.tsParent result merge≤180
neutron-scheduler-persist.ts.aif/neutron/scheduler/ I/O≤200

No monolithic neutron-scheduler.ts. Extract before any file exceeds 300 effective lines.


27. Acceptance criteria (N5 milestone) ​

  • [x] Deterministic graph scheduling with stable ready order
  • [x] Dependency correctness including failure/cancel propagation
  • [x] Bounded concurrency with configurable cap (default 1, max 4)
  • [x] No duplicate active lease for same node
  • [x] Bounded retries with non-retryable taxonomy enforced
  • [x] Cancellation propagates to N2 model turns and N4 tools
  • [x] Timeout recovery per layered timeout model
  • [x] Context/token budgets enforced and auditable
  • [x] Child capability clamp ⊆ parent/session/profile/node
  • [x] Parent-child provenance on every node result
  • [x] Stable aggregation order by taskId
  • [x] Stale-state rejection (fingerprint, profile, checkpoint)
  • [x] Project fingerprint unchanged under read-only roles
  • [x] pnpm verify green; cross-platform compatible

(pnpm verify on this Slice 5 branch: 294 files, 2551 passed, 3 skipped.)


28. Mutation-routing decision ​

MUTATION ROUTING REMAINS OUTSIDE N5 APPLY AUTHORITY.

N5 must not Apply, retry mutation, or design around write tools. Mutation Slice 5 (PR #507, merge 0eb326986537f125abfefc573e6709824914b37c) composes graph proposal/review evidence onto existing host Apply; Slice 5.1 (PR #510) hardened stale proposal materialization and production proposal-capability clamping. Additive to the scheduler: does not alter task states, lease semantics, retry semantics, the node mutationAttempted invariant, or read-only project execution. Host Apply remains outside scheduler node execution. Desktop Approve/Apply UX still requires separate maintainer authorization. Scheduling alone is not sufficient justification for mutation.


29. Open decisions ​

#DecisionRecommendationBlocker for
1Add priority field to scheduler metadata vs taskId-only orderDeferred — Slice 1 uses taskId code-point ascending onlySlice 2+
2Unify NeutronSubagentTaskRecord with graph node persistenceKeep separate; link by taskId in Slice 2Slice 2
3Protocol bump for enriched NeutronSubagentResultResolved in Slice 5 — application NeutronGraphExecutionResult; no protocol bump—
4Graph-level partial success policyResolved in Slice 5 — strict graph status; partial is observational only—
5When to introduce packages/neutron-runtimeRe-evaluate at N6 Desktop consumerN6

30. Recommendation ​

N5 RUNTIME MILESTONE COMPLETE — Slice 5 aggregation, stale-state detection, and provenance completion are implemented. Mutation routing and N6 still require separate maintainer authorization. No graph runner loop.


31. Slice 2 implementation record ​

Evidence baseline: origin/main @ 6a5c17aee9f9ae04b38f6df4d497a8503d44f410 (N5 Slice 1 handoff #446). Explicit maintainer authorization covered Slice 2 only.

DecisionRecord
Execution APIexecuteNeutronTaskNode on @intentloom/application/neutron-scheduler
Inputsgraph, taskId, active session, projectId, model adapter, filesystem, session capabilities, fingerprint, optional profile/signal/budget
Outputsin-memory result: updated graph/node, N1 NeutronSubagentResult, attempt 1, capabilities, N3 context/usage, N4 tool envelope, N2 adapter, fingerprints, normalized error
N3/N2/N4 compositionN2 runNeutronN2ReadOnlyLoop calls assembleNeutronContext via the existing pre-turn hook; model tool calls go through routeNeutronToolInvocation
Node objectiveN1 expectedOutput is the only evidence-backed intent field; it is the N2 prompt and N3 query
Role / capabilityresolveNeutronNodeCapabilities intersects session, optional profile grant, parent requiredCapabilities, node requiredCapabilities, and the N4 read-only catalog; readOnly=true, allowNetwork=false
State transitionspending→ready when classified ready, then ready→running; success running→completed; failure running→failed; cancel→cancelled; timeout→timed-out
Result contractN1 NeutronSubagentResult reused unchanged (completed/failed/cancelled; timed-out nodes map subagent status to failed); richer audit fields stay on the application wrapper (brief §29 #3)
Errorsscheduling/not-runnable fail closed with executed: false and no provider call; post-start failures return executed: true with one attempt and a staged error
No persistenceno .aif/neutron/scheduler/ writes
No retryattempt = 1; provider/tool/context failures terminate the node
No concurrencyone operation executes one requested ready node; capacity forced to 1; a running peer blocks start

delegateTaskRole is not called: it writes delegation files. Clamp is pure.


32. Slice 3 implementation record ​

Evidence baseline: origin/main @ 1165d044f461ddbaf90297aa74b1be13c11982ed (N5 Slice 2 handoff #448). Explicit maintainer authorization covered Slice 3 only.

DecisionRecord
Batch APIexecuteReadyNeutronTaskNodes on @intentloom/application/neutron-scheduler — one scheduling wave, not a graph runner
AdmissionReuses Slice 1 planNeutronTaskScheduling; taskId code-point ascending; availableCapacity = maxConcurrency - runningCount; default 1, hard cap 4
Lease identity{sessionId}:{taskId}:{attempt}; default attempt 1; attempt is not incremented
OwnerInjectable ownerId; default scheduler:{sessionId}; renewal and release require the current owner
TTL / heartbeatDefault TTL min(nodeTimeoutMs, 120_000) ms (120s when unset); renew every ttl/3; injected clock; no leftover timers after the wave
AcquisitionAtomic-enough local acquire via in-process lock + .aif/neutron/scheduler/leases/{urlencoded(leaseId)}.json; active/released → lease-held; expired → lease-expired (no silent re-acquire or retry)
ReleaseOwner release on completion, failure, and cancellation; released records remain for audit
OrderingAcquire sequentially in admitted taskId order, then execute concurrently; outcomes sorted by taskId regardless of completion order
Slice 2 reuseEach admitted node calls executeNeutronTaskNode with allowConcurrentPeers; distinct N3/N2/N4 capability clamps; N2 in-flight key is sessionId + taskId
State transitionsLease acquired before ready → running; Slice 2 owns running → terminal; failed lease acquire does not mark the node running
Persistence boundaryScheduler lease metadata under .aif/neutron/scheduler/leases/ only; project source fingerprint must ignore that prefix
No retryExpired lease is fail-closed; caller decides; no attempt increment, backoff, or retry queue
No graph runnerOne call executes at most availableCapacity ready nodes and stops
Mutation routingRemains deferred

Clock, lease, heartbeat, and batch modules stay in @intentloom/application. No protocol version bump.


33. Slice 4 implementation record ​

Evidence baseline: origin/main @ c06dbd4f6599e02265c46153dd4b96ba8e5fb0e1 (N5 Slice 3 handoff #450). Explicit maintainer authorization covered Slice 4 only.

DecisionRecord
Retry APIclassifyNeutronRetry — typed error codes only; model cannot request retries
Max attempts2 total (attempt 1 + one retry). Absolute cap is also 2
Retryableoperation-failed, timeout, expired lease, lease-lost / renewal invalid-owner
Non-retryablevalidation, capability/permission denied, root mismatch, unsupported tool, cancellation, budget exceeded, malformed/context failures
Attempt identityAttempt 2 uses {sessionId}:{taskId}:2; attempt 1 lease is never reused
EvidenceApplication-level NeutronAttemptEvidence on the wave outcome; N1 NeutronSubagentResult unchanged
CancellationSession signal or session.state === "cancelled" admits nothing; node AbortSignal is local; session signal cancels all running nodes; no retry after cancel
Timeout layersModel/tool remain N2/N4; optional nodeTimeoutMs wrapper; lease TTL stays min(nodeTimeoutMs, 120_000); no graph deadline
Expired leaseSame-attempt re-acquire still lease-expired; recovery acquires attempt 2 only
Renewal failureHeartbeat onError aborts the attempt (lease-lost); retry policy decides
Stale-attemptAuthority invalidates attempt 1 before attempt 2; late success after timeout/lease-loss is rejected
ConcurrencyRetry stays in the same wave slot; no extra worker; default 1 / hard cap 4 unchanged
CapabilitiesAttempt 2 recomputes clamp and intersects attempt-1 ceiling; never widens
Context / N4Fresh N3 via Slice 2; each attempt still routes tools through N4
PersistenceLease files only; no .aif/neutron/scheduler/results/
No graph runnerOne call remains one wave, with in-wave retries for admitted nodes only
Mutation routingRemains deferred

Tests: tests/neutron-n5-retry.test.ts, tests/neutron-n5-cancellation-timeout.test.ts.


34. Slice 5 implementation record ​

Evidence baseline: origin/main @ 426858b5d687930ff9fbbe38861b8b83a24dedab (N5 Slice 4 handoff #452). Explicit maintainer authorization covered Slice 5 only.

DecisionRecord
Aggregation APIaggregateNeutronTaskGraphResults — pure merge over graph nodes + optional wave outcomes
Stale APIdetectNeutronGraphStaleness — project fingerprint, optional checkpoint authority, optional profile authority
Reconcile APIreconcileNeutronTaskGraphExecution — detect then aggregate; fail-closed; never reruns
Graph contractApplication NeutronGraphExecutionResult. No protocol bump. N1 NeutronSubagentResult remains node-level
Graph statusPrecedence: stale → incomplete (pending/ready/running) → cancelled → timed-out → failed (includes blocked) → completed
Partial-successStrict: accepted only when status === "completed". partial is observational (some completed and not fully successful). Cancellation/timeout are not hidden behind sibling success
OrderingNodes by taskId code-point ascending. Attempts by attempt number ascending. Digests use canonical key order and omit observational timestamps
DependenciesBlocked descendants keep blockingDependencyIds and scheduling reasons. They are not executed and contribute no model usage
AttemptsFull Slice 4 attempt history retained. Authoritative output is the last non-stale attempt. Superseded late results remain audit evidence only
Project staleBaseline vs current source fingerprint. .aif/neutron/scheduler/ lease metadata is excluded by existing isNeutronSchedulerStatePath fingerprint helpers
Checkpoint staleCompare id, taskId, state, updatedAt, createdSnapshotChecksum when the caller supplies checkpoint authority
Profile staleCompare profile name + capability fingerprint (allowedTools, roles, read-only/network/budget/paths). Created-at is not authority
ProvenancegraphId, parentId, dependencies, role, requested vs effective capabilities, lease IDs, adapter, N3 source IDs/digests/warnings, N4 tool envelopes as digests, usage, mutationAttempted: false. No chain-of-thought
UsageSum existing NeutronUsageBudget fields across attempts. budget-exceeded is preserved as the node error code
PersistenceNone added. Lease files remain the only scheduler persistence
Read-onlyAggregation and stale detection never write source project bytes. No apply, shell, or mutation tools
No graph runnerCallers may invoke one or more existing waves, then reconcile. Slice 5 does not loop
Mutation routingRemains deferred. N5 evidence is a prerequisite, not authorization

Tests: tests/neutron-n5-aggregation.test.ts, tests/neutron-n5-stale-state.test.ts.