Concepts
JoyMux architecture
JoyMux is a local execution runtime. Its canonical record is an append-only, per-session event stream plus normalized session state. Terminal rendering is an optional client concern and is never required for hidden sessions.
Boundaries
JoyMux owns process and PTY lifecycle, byte transport, event normalization, resource observation, persistence, replay, bounded retention, and local APIs. It does not plan agent work, select coding-agent models, verify missions, or import JoyMesh internals. A zero process exit code means only that the process exited normally.
JoyMux also owns a self-contained embedded intent layer that converts ordinary
language into a validated ActionIntent before any JoyMesh call. That layer is
not a coding harness and never executes shell commands. Production startup must
not depend on Ollama. See embedded-inference.md.
Components
joymux-protocol: versioned wire and persistence contracts.joymux-core: state transitions, bounded views, decoding, and conservative heuristics.- adapter crates: local subprocess and PTY process primitives.
joymux-daemon: isolated session actors, SQLite/event persistence, raw chunk files, resource observation, and the local Unix-socket server.joymux-client: asynchronous Rust client (transports, discovery, registration).joymux-sdk: stable public harness SDK (ergonomic connect/session/exec API).- Language SDKs: Python and TypeScript packages under
sdk/(same public API). joymux-cli: operator CLI; seedocs/quickstart.mdfor onboarding.
Adapters are intentionally below the daemon boundary. Future container and SSH adapters can implement the same lifecycle without changing the protocol.
Phase 2 harness boundary
Codex / any CLI harness
|
vendor-neutral HarnessAdapter or Python SDK
|
stable JoyMux v1 local API
|
session actors -> subprocess / PTY adapters
|
SQLite events + append-only raw evidence
Harness knowledge ends above the API. The runtime neither imports vendor packages nor branches on executable names.
State machine
created -> starting -> running <-> waiting_for_input
| | | |
| | +-> cancelling <-+
| | | |
| | | +-> cancelled
| | +-> completed
| | +-> timed_out
| | +-> failed
| +-> failed/timed_out/cancelling
+-> cancelled
Every transition is validated. Terminal states are immutable. Exit/cancel races are serialized by the session actor; an observed natural exit wins if it is received before cancellation takes effect.
The exact transition table and recovery semantics are in state-machine.md.
Data flow and backpressure
Each session owns its process handles, sequence counter, bounded memory buffer,
and broadcast channel. Process readers persist raw chunks before notifying
subscribers. Slow subscribers may lag and must resume with ReadEvents after a
known sequence. They never block process output. SQLite stores metadata and
events; raw bytes are written to append-only per-session chunk files.
SQLite uses WAL mode, synchronous=FULL, a five-second busy timeout, and schema
version validation. Each event and updated session sequence commit together.
Restart recovery commits both terminal events and the failed state in one
transaction. Raw bytes are fsynced before their chunk reference is inserted; a
crash can leave unreferenced trailing bytes but not a reference to unwritten data.
Local security
The daemon binds only to a Unix socket created with owner-only permissions. Commands use direct exec by default; shell invocation is explicit. Working directories must exist and be directories. Environment names can be filtered, derived views can be redacted, and environment values are never persisted or logged. Raw evidence can contain secrets and must be protected accordingly.
Portability
The first implementation targets Linux and macOS-like Unix process semantics.
Process groups and PTYs are adapter details. The only unsafe block is the Unix
pre_exec hook that calls setsid() before direct execution so the daemon can
signal the process group. Resource observation is best effort;
observed_resource_usage is never represented as an enforced limit.