Repository navigation
Shared Memory
Underneath all the cognition — the Supervisor reasoning, the Mood Advisor classifying, the Mouth Tool framing, the Time Actor heartbeating, the modality actors perceiving — there has to be a substrate that lets all those coroutines coordinate without fighting each other for locks. The agent's reasoning loop shouldn't pause because the fade pipeline holds a mutex. The Cross-Perspective Bus shouldn't drop nudges because the queue is contended. The substrate underneath every actor needs to be lock-free: every operation makes progress, no operation waits on another, throughput scales with cores rather than contention.
That substrate is the shared-memory primitives layer. It's the plumbing — invisible when it works, catastrophic when it doesn't — and the architecture takes it seriously enough to give it a top-level feature folder.
The shared-memory layer is one of the convergent points where the SRAF lineage and the Magentic lineage independently arrived at the same answer.
The SRAF thread (§8) called for Reflection Memory implemented with lock-free queue; periodic batch flush to disk/DB. The framing was performance: the agent's reasoning loop runs at millisecond cadences, and a memory substrate that takes locks will stall the loop the moment a fade-pipeline batch happens to be running.
The Magentic thread (CoroutineSharedMemory.kt, journal entry
2025-11-20) called for a coroutine scheduler shared-memory
retrofit: replace JS-object queues with SharedArrayBuffer + Atomics
for lock-free coordination. The framing was correctness across
runtimes: when the framework runs in WASM and JavaScript
environments, the JavaScript runtime's "object queue" abstraction
doesn't compose with concurrent workers, and the only portable
answer is a typed buffer with atomic operations.
Both threads converge on the same primitive: lock-free queues with atomic counters, expressed in a way that works on both JVM and Native and (eventually) JS/WASM. The SRAF lineage gave the reason (don't stall the agent loop); the Magentic lineage gave the shape (typed buffer + atomics, not language-specific queue objects).
Kotlin has had three options for atomics over the years. The project's commitment is firm enough to be worth saying explicitly:
Use kotlin.concurrent.atomics.AtomicInt / AtomicLong. This
is the stdlib multiplatform API since Kotlin 2.1. It works on JVM,
Native, and JS. Same API, same semantics, no platform-specific
imports.
Do not use java.util.concurrent.atomic.*. That's the JVM-only
API. Reaching for it in commonMain breaks the native-first
commitment of the project. Even when JVM is the active target, the
stdlib API is the one to write against.
Do not use kotlinx.atomicfu. atomicfu was the right answer
for a window of years before stdlib atomics existed; that window is
closed. The stdlib API is preferred now.
The Magentic-vendored CoroutineSharedMemory.kt shows
java.util.concurrent.atomic.AtomicInteger because it was written
before Kotlin 2.1 stabilised the multiplatform API. The
implementation lifted from it should use the stdlib types instead.
The architecture distinguishes two layers of shared-memory use, with different access patterns and different cost characteristics.
The lower layer holds the runtime's coordination state: which
coroutines are runnable, which workers are parked, how many CPU
permits are available, what's queued for execution. Four
structures, each implementing the same SharedMemoryStructure
interface:
| Structure | Purpose |
|---|---|
SharedWorkQueue |
Ring buffer for runnable task envelopes. Lock-free enqueue/dequeue with compareAndSet. |
SharedDescriptorQueue |
Specialisation of work queue for descriptor envelopes (handler dispatch). |
SharedSchedulerControlState |
Atomic counters for created, blocking, cpuPermits. The scheduler's running state. |
SharedParkedStack |
Stack of parked worker IDs; compareAndSet for push/pop. |
class SharedWorkQueue(override val size: Int) : SharedMemoryStructure {
override val buffer = ByteArray(size * TASK_ENVELOPE_SIZE)
private val head = AtomicInt(0)
private val tail = AtomicInt(0)
fun enqueue(task: TaskEnvelope): Boolean {
while (true) {
val currentTail = tail.load()
val currentHead = head.load()
if ((currentTail + 1) % size == currentHead) return false
if (tail.compareAndSet(currentTail, (currentTail + 1) % size)) {
writeTaskEnvelope(currentTail, task)
return true
}
}
}
// ... dequeue similar shape
}The CAS loop is the canonical lock-free idiom. Every operation either succeeds (advancing the queue) or yields (a contended slot gets retried by a different coroutine). No operation blocks; no operation holds a lock that other operations wait on.
The upper layer is the InferenceCube data
plane: the tensors, hidden states, and signatures that the
LTC + Transformer hybrid passes between cubes. The primitive here is
the SharedMemoryManager — a manager that hands out slices
of a single underlying buffer to multiple coroutines without
copying data.
interface SharedMemoryManager {
fun allocate(bytes: Int): MemorySlice
fun release(slice: MemorySlice)
fun snapshot(slice: MemorySlice): MemorySlice // copy-on-read view
}
data class MemorySlice(
val offset: Int,
val length: Int,
val buffer: SharedByteArray
)The slice abstraction is what makes the inference path zero-copy. A cube's output buffer can be the next cube's input buffer directly — no intermediate copy, no allocation churn — because both cubes hold slices of the same underlying buffer with non-overlapping windows.
This matters because LTC inference is bandwidth-bound. The hidden state is small per-step but updated continuously, and copying it between cubes on every tick would dominate the CPU budget. The slice abstraction lets the inference pipeline avoid the copies entirely.
The Cross-Perspective Bus is one
direct consumer. Its lock-free pub/sub is exactly a
SharedDescriptorQueue with one descriptor envelope per nudge.
Every modality actor publishes by appending to the queue;
subscribers consume by dequeuing. No locks, no contention.
The Reflection Memory substrate is
another. Its in-memory ring is a SharedWorkQueue with
ReflectionEntry envelopes. Periodic flush to durable storage
reads from the ring and writes to disk; the ring stays available
for new entries throughout the flush.
The working memory tier is a third.
Its in-process structure is a small set of slices managed by the
SharedMemoryManager, sized to the agent's working budget. Reads
are direct buffer accesses; writes use atomic counters for the
fade-related metadata (referenceCount, emotionalWeight).
The pattern repeats throughout the architecture. Anywhere two coroutines need to coordinate without locks — and everywhere in SolaceCore qualifies — the shared-memory primitives are the floor they stand on.
A SharedMemoryStructure is more than a wrapped byte array. The
contract it implements:
interface SharedMemoryStructure {
val buffer: ByteArray // SharedArrayBuffer on JS/WASM
val size: Int
fun initialize()
}Three things matter.
The buffer is typed. Despite the ByteArray shape, every slice
of every buffer has a known structure — task envelope, descriptor
envelope, control state, parked worker ID. The shape is fixed at
buffer creation; readers and writers agree on it by convention.
The buffer survives across runtimes. On JVM the underlying
storage is a JVM ByteArray. On Native it's a pinned native
allocation. On JS/WASM it's a SharedArrayBuffer. The atomic
operations work the same way on all three — AtomicInt.load(),
AtomicInt.store(), AtomicInt.compareAndSet() — because the
stdlib API hides the platform difference.
The buffer is single-allocation. A SharedWorkQueue of size
1024 is one allocation of 1024 × TASK_ENVELOPE_SIZE bytes, not
1024 separate allocations. This is what keeps allocation pressure
low in the inference and scheduler hot paths; the structures don't
churn the heap as they fill and drain.
The lock-free idiom has its own discipline.
Every CAS loop has a bounded retry. In practice, contention is low and retries succeed within one or two attempts. But under pathological contention — one core hammering a slot every cycle — a CAS loop can spin indefinitely. The implementations include retry caps and back-off when the cap is hit, falling through to a parked-and-wake path that yields to the scheduler.
load() and compareAndSet use the right memory order. On
JVM the stdlib API defaults to sequentially-consistent semantics,
which is what these structures need. On Native and JS/WASM the
same API maps to equivalent fences. The implementations don't
reach for relaxed atomics unless a specific structure has been
analysed and proven to need only weaker ordering.
Visibility tests run in CI. Lock-free correctness is hard to verify by inspection. The test suite includes stress tests that hammer the structures from many coroutines and check for duplicates, drops, or stale reads. The tests run on JVM as the primary target with smaller runs on Native to verify the same shape.
ABA in the parked-stack push/pop. The classic lock-free
hazard: a worker pushes ID X, another pops X, a third pushes X
again, and the original push's CAS sees X-then-X and assumes
nothing happened. The mitigation in SharedParkedStack is a
generation counter packed into the top word; CAS compares both
top and generation, so an ABA cycle changes the generation and
the CAS fails as it should.
Queue full / queue empty. Both states are returned cleanly
(enqueue returns false, dequeue returns null) rather than
blocking. Callers handle the failure: enqueue full means the
producer slows down or drops; dequeue empty means the consumer
parks.
Cross-thread visibility on weakly-ordered platforms. On
platforms with weak memory models, naive use of atomic load/store
can permit reordering that breaks the lock-free invariants. The
stdlib compareAndSet includes the necessary fences; structures
that use raw load/store for hot-path reads include explicit
fences where ordering matters.
Magentic-side scaffold exists. The
wiki/sketch-architecture/CoroutineSharedMemory.kt file has the
shape of all four scheduler structures — SharedWorkQueue,
SharedDescriptorQueue, SharedSchedulerControlState,
SharedParkedStack. The shape is right; the file uses
java.util.concurrent.atomic.AtomicInteger instead of the
multiplatform stdlib API and would need that conversion when
moved into lib/.
SRAF-side reference exists in §8 but no scaffold; SRAF described the requirement, not the implementation.
The SharedMemoryManager for inference data is fully implemented under io.github.solaceharmony.core.inference.SharedMemoryManager in the lib codebase, managing block-level slice mapping for the coroutine inference flow.
The work order:
- Convert the Magentic scaffold to use
kotlin.concurrent.atomicsand move intolib/src/commonMain/. Stress test on JVM. - Verify the same code compiles and passes on Native targets.
- Build the
SharedMemoryManagerfor inference data. Wire to the InferenceCube data plane when that lands. - Migrate the Cross-Perspective Bus and Reflection Memory ring to sit on the converted scaffolding.
- JS/WASM target ETA. SharedArrayBuffer requires cross-origin isolation, which has deployment implications for the web target. Compose Multiplatform's web build supports it but only with specific server configuration. The decision affects whether shared-memory primitives are uniformly available across all runtimes or whether the JS path needs a fallback.
- CAS retry cap values. Default values are guesses; real values come from observing contention patterns in production.
-
Whether to use
CountedReferenceforMemorySlicelifecycle. Currently the SharedMemoryManager hands out slices and trusts callers to release them; counted references would close the loophole.
- reflection-memory — sits on the shared-memory ring; the substrate's lock-free queue is one of these structures.
- multimodal-nudging — Cross-Perspective Bus is a SharedDescriptorQueue.
- memory — working tier holds slices; long-term vector index uses atomic counters for fade metadata.
-
inference-cube — Layer 2 inference data
plane uses
SharedMemoryManagerfor zero-copy slices. - pipeline — pipeline DSL backs onto these primitives for stage-to-stage data flow.
Everything else. The architecture commits to running every component continuously without pausing the agent's reasoning loop, and that commitment requires the substrate underneath to be non-blocking. Lock-based designs work; they just stall under load, and the failure mode of stalling under load is exactly the failure mode the agent's continuity depends on avoiding. The shared-memory primitives are the choice to pay the complexity cost of lock-free design once, in this layer, so that everything above it can assume non-blocking coordination as a property of the substrate.
That's what the floor is for.
SolaceCore SSOT wiki · published from wiki/ by .github/workflows/publish-wiki.yml · edit the source in the repo, not the wiki.
Orientation
- Architectural Deep Dive
- Architecture Overview
- Design vs Implementation
- Framework Actor System
- Framework Architectural Vision
- Framework Concurrency and Communication
- Framework Data Storage and Management
- Framework Deployment and Containerization
- Framework Development Roadmap
- Framework Hot-Pluggable System
- Framework Implementation Status
- Framework Observability and Monitoring
- Framework Port System
- Framework System Architecture
- Framework Workflow Management
- Project Status
- Project Status Report
- Quick Status
- Solace Core Framework Architecture
- SolaceCore Architecture Overview
- Vision & Solace AI
Runtime
- Actor Builder
- Actor Communication Sequence Diagram
- Actor Core Definitions
- Actor Graph View
- Actor Metrics
- Actor Module Architecture
- Actor Queue Hibernation and Correlation
- Actor Roadmap
- Actor State Recovery Subsystem
- Actor State Serialization Subsystem
- Actor Supervision Module
- Actor System Architecture
- Actor System Class Diagram
- Actor Usage Examples
- Compose App Features
- JVM Scripting Implementations
- Kernel & Ports
- Kernel Channel System
- Kernel Future Enhancements
- Kernel Module Architecture
- Kernel Port Implementations and Exceptions
- Kernel Port Usage Example
- Kernel Testing Strategy
- Lifecycle Class Diagram
- Lifecycle Management Architecture
- Pipeline DSL
- Real-Time UI Implementation
- Scripting Module Architecture
- Scripting Module Design
- Scripting Supporting Components
- Shared Memory
- Storage & Persistence
- Storage Abstractions Architecture
- Storage Caching Subsystem
- Storage Checklist
- Storage Compression Subsystem
- Storage Core Interfaces
- Storage Encryption Subsystem
- Storage File-Based Architecture
- Storage File-Based Implementations
- Storage In-Memory Architecture
- Storage In-Memory Implementations
- Storage JVM Serialization Utilities
- Storage Module Architecture
- Storage Serialization Compression Encryption
- Storage Specialized Interfaces Architecture
- Storage Status and Future Plans
- Storage Testing
- Storage Thread Safety Guide
- Storage Thread Safety and Deadlock Prevention
- Storage Transactions
- Storage Usage Examples
- Supervisor and Hot Swap
- SupervisorActor
- System Architecture Diagram
- Workflow Management Architecture
- Workflow Management Design Concept
- Workflow Orchestration
Solace AI
- Confusion Corrector
- Inference Cube
- Inference Cube Technical Architecture
- Long-Term Memory
- MCP and Tool Format
- Memory & Reflection
- Memory Compression
- Memory Feature Overview
- Memory Retrieval
- Mood & Emotional Model
- Mood Module Implementation
- Mouth Tool Technical Spec
- Multimodal Nudging
- Perception Actors
- Provider Specs
- Reflection Memory
- Solace AI Overview
- Supervisor AI
- Supervisor Emotional Model Integration
- Time Actor
- Voice & Mouth Tool
- Working Memory
- Zoom Level Technical Spec
- Zoom Levels
Reference
- Advanced Workflow Example
- Basic Actor Usage
- Build System and Dependencies
- Development Tooling and Practices
- Documentation Catalog
- Documentation Index
- Feature Index
- Glossary
- How the Wiki Publishes
- JVM Utilities
- Kotlin Implementation Details
- Kotlin-Aligned Architecture Overview
- Kotlin-Aligned Contributing
- Kotlin-Aligned Core Architectural Principles
- Kotlin-Aligned Daily Development Workflow
- Kotlin-Aligned Development Examples
- Kotlin-Aligned Development Workflow
- Kotlin-Aligned Documentation
- Kotlin-Aligned Implementation Status
- Kotlin-Aligned Key Concepts
- Kotlin-Aligned Known Issues
- Kotlin-Aligned Quick Start
- Kotlin-Aligned Running the System
- Kotlin-Aligned System Architecture
- LangChain Actor Code Changes
- LangChain Actor Usage Improvements
- LangChain ActorInterface Code Changes
- LangChain Best Practices
- LangChain Bugs
- LangChain Chain Implementation
- LangChain Code Changes
- LangChain Code Changes Rollout and Impact
- LangChain Configuration Management Improvements
- LangChain Configuration Recommendations
- LangChain Core Architecture Recommendations
- LangChain Directory Structure Changes
- LangChain Documentation Improvements
- LangChain Dynamic Wiring Rollout Notes
- LangChain Fix Proposal
- LangChain Implementation Priorities
- LangChain Lifecycle Management Improvements
- LangChain Memory Integration Recommendations
- LangChain Metrics and Observability Recommendations
- LangChain Migration Strategy
- LangChain New Files Needed
- LangChain New Packages to Add
- LangChain Package-by-Package Improvements
- LangChain Patterns
- LangChain Port Code Changes
- LangChain Port System Recommendations
- LangChain Port Usability Improvements
- LangChain Prompt Management Recommendations
- LangChain Recommendations
- LangChain Recommendations Rollout Plan
- LangChain Required Interface Changes
- LangChain Testing Changes
- LangChain Testing Improvements
- LangChain Testing Recommendations
- LangChain Tool Integration Recommendations
- LangChain Type-Safe Dynamic Wiring
- LangChain Type-Safe Dynamic Wiring System
- LangChain Usage Design Improvements
- Master Checklist
- Roadmap
- Roadmap Issues
- Roadmap Phase 1 Stability and Testing
- Roadmap Phase 2 Production Infrastructure
- Roadmap Phase 3 Documentation and Developer Experience
- Roadmap Phase 4 Graph Database Integration
- Roadmap Phase 5 Security Framework
- Roadmap Phase 6 Distributed System
- Roadmap Phase 7 Advanced Features
- Roadmap Phase 8 Ecosystem Development
- Roadmap Timeline and Success Metrics
- Setup Instructions
- Sketch Architecture
- Status Documentation
- Task 1 Core Tests
- Task 2 Connection Wiring
- Task 3 Concurrency Issues
- Task 4 Dynamic Registration
- Task 5 Integration Tests
- Task 6 Deadlock Detection
- Task Documentation
- Test Coverage Checklist
- Testing Strategy