Skip to content

Arena core

August 22, 2026

Back to the architecture overview.

Cogs owns one CogArenaCore. The core is the context-local bridge from stable public descriptor/key names to dense graph rows, typed values, and cold runtime sidecars.

Ownership

The hot side is data visited during ordinary resolution, propagation, or settlement: scalar arrays, integer edges, typed value cells, descriptor indexes, and reused scalar work stacks. The cold side is needed only for setup, boundaries, async work, lifetime, diagnostics, or closures: descriptor records, registrar objects, tasks, keys, sleepers, and debug history.

Rendering diagram…

The core owns:

PiecePurpose
arenaCogArenaStorage: aligned scalar row columns and slot allocator.
edgesCogLinkedEdgePool: dependency/subscriber topology.
propagationReused invalidation stack and changed-boundary queue.
revisionLatest turn revision, never allowed to wrap.
contextIdentityNever-reused context token for declaration memos.
slotsCogStateIdentity to exact live CogArenaSlot.
recordsByIdentityStrong descriptor-object registry.
recordsDense Unmanaged descriptor dispatch array for row walks.
pullFrames, captures, computingPathReused iterative settlement and nested capture state.
reactionPullRootsStable producer snapshot while a terminal settles.
observationEntriesCold registrar boundary plus exact slot.
lifetimeEntriesCold sleeper generation and task per value row.
historyLogFixed-capacity integer history in debug builds only.

CogTurn and the FIFO remain on Cogs because they coordinate the public publication boundary. Async tasks live in descriptor-local CogArenaAsyncColumn sidecars because their concrete Value type belongs to the descriptor, not the scalar graph.

Value rows and terminal rows

A value row has a descriptor index. Its record identifies manual, synchronous automatic, or async behavior and restores a typed column. A reaction terminal is deliberately value-less: descriptor == -1, no boundary, no subscribers, but a normal ordered dependency list and settlement flags.

Row kindDescriptorTyped valueDependenciesSubscribersBoundary
manualyescurrent + pendingnonepossibleoptional
automaticyescached, pending during recomputeorderedpossibleoptional
async statusyesCogStatus current + pendingordered selection readspossibleoptional
reaction/export terminalnononeordered tracked readsforbiddenforbidden

Example after Dashboard and one mechanism reaction read adviceCog:

text
row 0  advice automatic   deps → row 1   subs → terminal row 2
row 1  temperature manual deps → none    subs → row 0
row 2  reaction terminal  deps → row 0   subs → none

Rows are allocated by demand, so these numbers are examples, not declaration order.

Cold identities, hot rows

Public references must remain stable across release and recreation, so they cannot embed arena slots. Resolution is the only transition:

Rendering diagram…

Once a slot is resolved, dirty propagation, pull frames, computing paths, and edges carry Int32 rows. They do not repeatedly hash keys or retain descriptor objects.

Trace: one automatic UI read

For let advice = cogs[adviceCog], the exact handoffs are:

StepSymbolWork
1Cogs.subscript(_ valueReference: Cog<Value>)Selects the Observation-tracked UI path.
2CogArenaCore.observedAutomaticValueResolves the automatic location and requests settlement.
3automaticLocationTries the keyless descriptor memo and validates contextIdentity plus slot generation.
4resolvedAutomaticLocationOn miss, calls automaticRecord, forms CogStateIdentity, finds or installs a slot, and marks a new row DIRTY.
5automaticRecordRestores or creates CogArenaValueColumn<String> and the erased descriptor closures.
6settlePushes an enter frame; a cold DIRTY row reaches descriptor recompute.
7recomputeCalls withDependencyCapture, then AutomaticCogDescriptor.compute.
8Reader.subscript(_ Cog<Value>.Manual)Calls CogArenaCore.read for _temperatureCog.
9manualLocation / resolvedManualLocationResolves the manual descriptor, slot, and CogArenaValueColumn<Double>, inserting 68 on first use.
10recordDependencyReuses the next matching edge or appends an edge from temperature to advice.
11CogArenaValueColumn.insertInstalls the cold automatic result; recompute stamps changedAt and checkedAt.
12accessObservationBoundaryLazily creates or accesses the exact row's CogObservationBoundary.
13CogArenaValueColumn.currentReturns the concrete cached String.

A warm keyless read normally takes steps 1–3, a clean settle, boundary access, and typed current load. It bypasses both descriptor/slot dictionaries and the checked downcast.

Trace: one manual write and dependent read

Assume the rows above exist and a domain op stages 86.

text
temperature row: current 68, pending absent, checkedAt 0
advice row:      current "Go outside", clean, checkedAt 0
StepSymbolWork
1Cogs.turn(_:to:name:)Opens or joins an accumulating turn.
2Cogs.writerStageProves the writer's turn identity.
3CogArenaCore.writerStageResolves the manual location and stages 86.
4CogArenaValueColumn.stageWrites the pending typed cell.
5touchArenaSourceSets touched; appends the slot once to CogTurn.
6Cogs.runOuterTurnCloses accumulation and calls CogTurn.flushPendingSources.
7Cogs.advanceRevisionAdvances core revision from 0 to 1.
8CogArenaCore.flushPendingSourcesDispatches through the manual record's publishSource.
9CogArenaValueColumn.publishSourceCompares 68 and 86, publishes current, stamps source changedAt=checkedAt=1.
10CogArenaDirtyPropagation.invalidateSubscribersQueues the source boundary if any; marks direct advice DIRTY and descendants CHECK.
11flushObservationBoundariesSelects only queued boundary rows and settles advice.
12settle / recomputeReuses the temperature edge, computes “Stay inside,” and publishes the changed cache at revision 1.
13descriptor notifyObservationMutates the boundary's value key path.
14Cogs.flushReactionsOffers exports, then settles and runs effect terminals.
15finishTurn / drainQueuedTurnsReturns idle and drains write-back/system turns FIFO.

Rendering diagram…

If 70 produces the same advice, step 12 advances advice checkedAt to 1 but keeps its older changedAt; step 13 is skipped.

Dispatch records

CogArenaDescriptorRecord is retained once per declaration per context. It stores immutable identity, label, dense index, kind, lifetime policy, erased typed column references, and descriptor-level closures for source publication, recomputation, Observation notice, value removal, teardown, and memo eviction. Keys form a cold descriptor-owned sparse side table by global row.

Indexed graph walks load arena.descriptor[row], then use the dense records array. There is one closure per descriptor, not per state row. records stores unretained references because recordsByIdentity is the strong owner for the same context lifetime.

Reused work storage

The core and its helpers retain high-water capacity:

  • CogTurn.touchedArenaSources for ordered source publication;
  • dirty-propagation stack and changedBoundaryRows;
  • pullFrames for iterative enter/exit settlement;
  • captures for nested dependency reconciliation;
  • computingPath for cycle detection;
  • reactionPullRoots for topology-stable terminal settlement; and
  • per-reaction current/scratch lease arrays.

Steady turns therefore reuse buffers instead of allocating work items.

Invariants

  • Cogs and every graph mutation remain MainActor-confined.
  • Public references never expose or retain slots.
  • Every live value row has one valid descriptor dispatch index; every terminal has none.
  • Hot rows contain no objects, closures, keys, or values.
  • Exact slots validate occupant generation at typed and cold boundaries.
  • Descriptor records outlive all rows that refer to their dense index.
  • Settlement/capture/propagation buffers are empty at their public idle barriers.

Next: arena identity and caching.

Released under the MIT License.