41 KiB
Runa — Project Specification and Architecture
Status: Architecture specification, revision 4 (Runa naming)
Primary host language: Rust
Initial embedded language: Rune
Inspirations: Smalltalk/Squeak/Pharo, Morphic, Erlang/OTP
Primary interfaces: Self-contained graphical world and VS Code extension
connected to the same live image through a network API
1. Vision
Build a self-contained, persistent, live programming world. Its fundamental computational entities are actors, not shared mutable objects. The environment itself is programmable and inspectable, with live actor spawning, message sending, state inspection, behavior editing, debugging, and hot code upgrades. A VS Code extension provides an equally capable external interface to the same running image via a programmatic API.
Rust supplies a minimal, reliable substrate; increasingly much of the actor framework, graphical world, and development tools should be implemented in Rune. The end state is a Smalltalk-like image with Erlang-inspired actor isolation and modern tooling.
Core goals
- Inspect, spawn, stop, restart, and supervise actors from both graphical world and VS Code.
- Inspect state, mailboxes, behavior versions, failures, and execution status.
- Send messages and evaluate development operations interactively.
- Edit image source files, commit and roll back their history, and reload or become another script without restarting the image.
- Support interruptible execution, pause/resume, and eventually source-level stepping.
- Preserve stable actor identity and optionally mailbox contents across actor incarnation failures.
- Store canonical Rune source and immutable implementations by content identity; cache compilation.
- Build self-inspecting, self-modifiable graphical tools, gradually moving implementation from Rust to Rune.
- Persist and restore meaningful parts of the image without assuming arbitrary stack serialization.
2. Architectural Principles
- One authoritative live image. In-world tools and VS Code are clients of the same semantic development services.
- Actor isolation. No direct mutable cross-actor state sharing. All normal interaction occurs through messages.
- Separate identity, mailbox, state, handler, and incarnation. A failed handler execution need not destroy an actor's identity or mailbox.
- Stateless handlers. Actor-specific mutable state is explicit input/output, not hidden global state.
- Immutable code versions. A behavior name points to an exact implementation; active executions retain their own version.
- Rust mechanisms, Rune policies. Native kernel for scheduling, execution control, memory/resource ownership, platform I/O; Rune for behaviors, UI, supervision policy, and tools where practical.
- Safe-point upgrades and inspection. Never unsafely mutate a currently executing handler.
- Honest guarantees. Distinguish cooperative yielding from true preemption, in-memory retention from disk durability, and at-least-once delivery from exactly-once effects.
- Replaceable language integration. Keep a narrow runtime adapter, but do not over-generalize before Rune feasibility is tested.
3. Layered Architecture
+-----------------------------------------------------------+
| Live graphical world (Morphic-inspired) |
| Windows, widgets, actor browser, inspector, debugger |
+-------------------------------+---------------------------+
| Rune development services | VS Code extension |
| Behavior registry, workspace | Virtual docs, trees, DAP |
+-------------------------------+---------------------------+
| Unified image API / development protocol |
| Actors, mailboxes, state, code, debugging, events |
+-----------------------------------------------------------+
| Rune actor framework |
| Stateless handlers, supervision policies, UI behaviors |
+-----------------------------------------------------------+
| Rune runtime adapter / compilation and execution |
+-----------------------------------------------------------+
| Rust actor kernel |
| Identity, mailboxes, scheduler, transactions, monitoring |
+-----------------------------------------------------------+
| Rust platform substrate |
| Graphics, input, timers, filesystem, networking, storage |
+-----------------------------------------------------------+
The graphical world should use internal service calls to the image API. The VS Code extension, CLI tools, tests, and local automation connect to the same live image through the versioned runa-debug WebSocket transport; trusted embedded callers can use in-process calls. The initial WebSocket transport is unauthenticated and must bind only to loopback; a remote-capable network boundary must authenticate and authorize clients. Client connections do not create a separate actor runtime or image.
4. Fundamental Actor Model
An actor comprises distinct components:
Actor {
actor_ref: ActorRef, // opaque public reference
mailbox: MailboxId, // independent queue; may survive restart
state: StateRecord, // committed private state
behavior: ImplementationId, // immutable handler implementation
incarnation: Generation, // changes on restart
status: ActorStatus,
supervisor: Option<ActorRef>,
}
4.1 Identity
Actor references are opaque, never raw pointers or sequential numbers. The prototype uses 21-character URL-safe NanoIDs for public references. A logical actor retains its reference and mailbox across restart but receives a new incarnation. Random references are not an authentication or authorization mechanism; network access remains loopback-only until access control exists. Distinguish runtime-instance identity from actor identity if remote or persisted references are introduced.
4.2 Mailbox
The mailbox is managed independently of handler execution. Messages have at least queued, reserved, acknowledged, and dead-lettered lifecycle states where durable processing is enabled. Define ordering explicitly; FIFO within a queue does not imply deterministic ordering between concurrent senders.
Start with bounded queues and observable backpressure. For durable mailboxes, initially target at-least-once processing, retry limits, and dead-letter handling. Retention across actor restarts does not imply survival across host process crashes; disk durability requires explicit storage and recovery.
4.3 State
State is owned by the actor runtime, logically private, and supplied to each scheduled handler invocation. The handler works with a controlled draft or value and optionally returns new state; it cannot mutate committed state directly. Returning unit or omitting a return retains the previous committed state and discards draft mutations. State has a schema/version where useful and can be inspected via snapshots at safe points.
4.4 Stateless handler
The handler is a stateless Rune function associated with an immutable implementation ID. Conceptually:
handle(ctx, state, sender, message) -> new_state
on_shutdown(state) -> new_state // optional
ctx.send(recipient, data) records an ordinary send with the calling actor as
sender. ctx.send_from(sender, recipient, data) supports forwarding.
Sender metadata lives in a runtime envelope and is passed separately from data;
host-originated messages have unit sender. Actor-specific state must not be
stored in globals or implicit mutable closures. These handler and send APIs are
implemented against Rune 0.14.2; the handler may await approved operations.
4.5 Effects
Context sends buffer owned effects instead of delivering immediately. State and
all validated sends commit together; later mutation of a Rune value does not
change a buffered message. Timers and durable external requests remain planned.
System lifecycle messages are a separate host-only control path: actors cannot
construct stop, kill, restart, pause, resume, reload, or become commands through
ordinary sends. Stop completes the current handler then invokes optional
on_shutdown(state) before removing the actor; kill removes it immediately
without shutdown. Failed actors remain for inspection/repair until killed.
Removal discards state, mailbox, and retained logs. Restart is a separate operation that retains the
loaded script and state. Reload/become switch immutable implementations only
after an active handler completes.
ctx.send(recipient, data);
new_state
On durable paths, commit the state transition, input-message acknowledgment, and transactional outbox records atomically. Delivery of outbox effects occurs afterward and may be retried. Exactly-once external side effects are not guaranteed; use idempotency keys and deduplication where needed.
5. Scheduling, Awaiting, and Interruption
Each actor processes at most one active handler invocation at a time, including suspended invocations, unless a future explicitly designed concurrency policy says otherwise. Other messages remain queued.
A scheduled invocation receives its committed state, a reserved message, the selected implementation, and execution context. It runs until it:
- Completes, producing a transition to commit.
- Awaits an asynchronous operation, suspending without blocking a native worker thread.
- Is paused/interrupted at a supported safe point.
- Fails or is cancelled.
- Reaches a configured execution budget.
Await is not commit. State and effects from an incomplete invocation remain uncommitted. On crash, the current invocation is discarded or recovered according to documented policy, and its reserved message can be retried. Long awaits can consume resources and complicate recovery; establish limits and cancellation rules.
5.1 Interruptibility requirement
Interruptible Rune execution is a foundational requirement, not a future nice-to-have. The runtime should eventually support pause, resume, cancel, execution budgets, runaway-code detection, breakpoint locations, and source-level stepping.
Phase 0 must verify actual Rune APIs and limitations: cooperative yield, VM instruction hooks/budgets, native callback blocking, async suspension, frame inspection, and safe interruption of CPU-bound loops. If Rune lacks the necessary mechanisms, evaluate bytecode instrumentation, interpreter modifications, or a controlled fork. Do not claim preemptive scheduling until it is actually implemented and tested. Native Rust callbacks must also define their cancellation behavior.
5.2 Failure and supervision
Separate handler exceptions, invocation cancellation, actor incarnation failure, supervisor restarts, host process failure, and machine failure. Supervisors can restart an actor with retained mailbox, last committed state, and the same or a compatible upgraded handler. Supervision strategies (one-for-one, one-for-all, rest-for-one) can initially live in Rune, atop native monitoring and lifecycle events. Add restart-intensity limits and poison-message handling.
6. Content-Addressed Rune Code
Keep four distinct things:
- Original source for editing, diagnostics, and exact text preservation.
- Canonical source normalized deterministically and semantics-preservingly.
- Source identity derived from canonical bytes with a versioned hash scheme.
- Compiled artifact cached using all compilation inputs.
Use a domain-separated hash such as BLAKE3 and a canonicalization version. Never strip arbitrary whitespace/comments without parser-aware analysis; preserve original source and source maps for debugging.
Example identifiers:
rune-source:v1:blake3:<digest>
compiled:<digest-of-source-plus-toolchain-configuration>
A compiled artifact cache key must include source identity, Rune compiler version, compiler settings, enabled native modules, runtime ABI, relevant features, and target constraints. Do not assume artifacts are portable between Rune versions or host configurations.
Mutable image files are organized beneath a source directory:
source/actors/counter.rn -> current working source
actors/counter -> commit history [readable-generated-revision, ...]
Spawn takes actors/counter and compiles the current working source, even
uncommitted. Commit snapshots are version control only and can retain invalid
source. Rollback restores working source, not a running actor's frames.
Saves use expected source identities to prevent conflicting editor updates.
Active frames retain exact immutable implementations independently of source
editing and commit history. Garbage collection must respect those references.
6.1 Hot upgrade procedure
- Save working source with an expected source identity; commit separately if desired.
- Reload the current actor path or become another path; hash and compile current text (or reuse cache).
- Report diagnostics without changing the running behavior on failure.
- Register an immutable implementation.
- Validate state compatibility or execute explicit migration.
- Install at a safe boundary (initially between message handlers).
- Emit change events. Source commit history remains independent.
Do not rewrite suspended execution frames in place.
7. Graphical World
Build a self-contained Morphic-inspired desktop where windows, widgets, and development tools are themselves inspectable and eventually written primarily in Rune.
Required in-world operations: browse actors and behaviors; spawn, stop, restart, pause, and resume actors; inspect state, mailboxes, failures, supervisor relationships, and versions; send messages; edit and compile behavior source; install/rollback versions; inspect execution; view events and diagnostics.
Not every graphical node must be an actor. Use actors for independently scheduled components and ordinary Rune data for geometry, style, and lightweight scene nodes. UI actors update a retained scene representation; a scene manager produces immutable render snapshots consumed by native graphics code. Avoid synchronous arbitrary actor callbacks on the render thread. Native code initially owns windowing, drawing, and input capture; Rune progressively owns composition, layout, event routing, widgets, and tools.
The graphical inspector should support selecting a component, locating its owning actor, inspecting its state and behavior, editing its implementation, and observing live changes. The development environment must be able to inspect itself.
The first native client milestone uses egui/eframe and connects to the existing loopback image over the shared WebSocket protocol. It currently provides an actor browser and inspector with message, lifecycle, log, breakpoint, bounded step, and execution-stack controls, plus a source editor with compilation diagnostics, explicit source commits, committed-revision previews, and confirmed rollback. The actor inspector can edit an actor-local temporary behavior and explicitly update its shared source or reload from that source. Image events can be collected manually; this operation drains the shared queue and therefore consumes events for other clients too. Network requests run on a worker thread, not the UI frame loop. This is a client foundation, not yet a Rune-composed graphical world; frame locals and expression evaluation remain unavailable.
8. Unified Programmatic Image API
The running image exposes a versioned semantic API, used by graphical tools, CLI tools, tests, automation, coding agents, and the VS Code extension. External clients use WebSockets to reach the same running image; there is no stdio transport. Client location does not change actor placement or message-passing semantics.
Suggested operation families (provisional names):
image.info image.snapshot image.restore
image.subscribe
actor.list actor.spawn actor.inspect
actor.send actor.call actor.pause
actor.resume actor.stop actor.restart
actor.subscribe
mailbox.inspect mailbox.peek mailbox.statistics
mailbox.retry mailbox.deadLetters
state.inspect state.snapshot state.history
behavior.list behavior.get behavior.compile
source.write source.commit source.rollback
behavior.history
code.getSource code.getArtifact code.getMetadata
code.listReferences
debug.attach debug.detach debug.pause
debug.resume debug.step debug.breakpoints
debug.stack debug.inspectFrame
These are desired capabilities, not a requirement to implement every endpoint immediately. Define explicit request/response schemas, error codes, authorization, optimistic revision checks, and event subscription semantics. Mutations should return structured outcomes and emit events.
8.1 Security
Normal actors should not automatically receive development privileges. Arbitrary evaluation, state mutation, code replacement, and process control require explicit capabilities. The initial WebSocket bridge accepts unauthenticated connections only on IPv4 or IPv6 loopback and grants each connection the full development API; any local process may connect. It is suitable only for trusted local development. Never bind this endpoint to a non-loopback address or expose it remotely until authentication and authorization are implemented.
9. VS Code Extension
VS Code is an external client of the same live image, not a separate runtime. The initial development transport is versioned JSON over runa-debug WebSockets, bound to loopback and unauthenticated; the extension must connect only to a trusted local image. Remote connections remain out of scope until authentication and authorization are implemented. The extension does not host actors or create a separate image. The REPL uses the same WebSocket protocol.
The first extension increment implements explicit connect/disconnect, an actor
explorer, and read-only state/mailbox/execution snapshots. Server image_changed
notifications automatically refresh the tree and open documents without
scheduler ticks or event drains. Revisions and refreshes are coalesced rather
than replayed, and changes during a fetch trigger a follow-up refresh.
Cached open snapshots are marked disconnected on connection loss and isolated
by connection session so reconnecting to another image cannot overwrite them.
Spawn with explicit source path/state, structured send, system lifecycle commands,
and explicit scheduler ticks are implemented. The extension mounts an editable
source directory with nested Rune files and file-context spawn actions.
Saving writes working source; committing and browsing history are separate
commands. Commit source documents are read-only and isolated by connection
session. Image notifications refresh listings without replacing historical
source text. Remaining UX below is
planned; detailed runtime event subscriptions and
durable replay still need design.
Required extension UX:
- Connection management and runtime status.
- Actor tree with spawn, stop, restart, pause, and resume actions.
- Actor state and mailbox inspector, including message sending.
- Directory-based image source navigation.
- Editable image source files with independent commit/history/rollback actions.
- Read-only virtual documents for exact content-addressed implementations.
- Compilation diagnostics, update conflicts, and runtime event stream.
- Later: Debug Adapter Protocol integration for breakpoints, frames, and stepping.
Suggested virtual URIs:
source/actors/counter.rn
source-history:/<session>/actors/counter/<revision>.rn
Saving the working file does not compile, commit, or install it. Spawn compiles current content; reload/become install at a handler boundary. The historical document is immutable and read-only. Avoid silently overwriting another client's working source.
10. Persistence
Implement persistence incrementally:
- Source: canonical/original source, immutable implementations, registry revisions.
- Actor state: selected actor identities, committed state, handler references, supervision metadata.
- Environment: windows, layouts, workspaces, and development session state.
- Advanced: coordinated actor/message snapshots and, only if technically feasible, suspended execution state.
Start with SQLite or another simple durable store plus Git-friendly source export. Define snapshot consistency explicitly. Never assume arbitrary Rune stacks, closures, and native resources can be serialized and restored. Preserve mailbox and state transaction invariants across recovery.
The first concrete persistence adapter is an explicit, versioned JSON image file,
not a database or transaction journal. Save/restore is serialized by the image
server and requires no unfinished invocation or nonfailed shutdown work. Save
retains pre-pause scheduling status and leaves runnable actors paused; restore
recompiles and validates all saved artifacts/data before replacement, then
restores runnable actors' scheduling eligibility while preserving paused/failed
statuses. Incarnations advance and old debug sessions end. Sources/history,
registry revisions, pending/temporary behavior bindings, committed state,
mailbox envelopes/capacities and retained logs are included. See
runtime guarantees for exact scope.
SQLite-backed per-transition durability, supervised recovery and execution-stack
restoration remain planned.
The server can explicitly open a snapshot on startup using --restore or
RUNA_RESTORE, with the same validation and scheduling rules as live restore.
Restored images are not merged with bundled sources, and failed restoration
does not fall back to a new image or serve clients.
11. Workspace Boundaries and Dependency Direction
The Rust workspace uses acyclic, inward-pointing dependencies. The executable (runa) is the composition root. Higher-level services depend on lower-level contracts; low-level crates never import graphical, transport, or application code.
11.1 Dependency diagram
Arrows mean "depends on", not runtime data flow:
runa-world runa runa-cli
| / \ |
v v v v
runa-image-api runa-protocol
| |
v |
runa-runtime |
/ \ |
v v |
runa-actor runa-rune |
\ / |
v v v
runa-core <-----------+
runa-world also depends on runa-graphics (platform drawing/input
contracts); runa-graphics depends only on runa-core when needed.
runa depends on runa-image-api and runa-protocol.
runa-cli uses runa-client and runa-protocol to connect to the image.
World, storage and platform integration remain planned.
The diagram is conceptual; Cargo dependencies should be kept minimal. A dependency is permitted only when the importing crate actually uses that crate's public contracts. The VS Code extension is a separate TypeScript project and depends on the wire protocol, not Rust crates.
11.2 Crate ownership
| Crate | Owns | May depend on |
|---|---|---|
runa-core |
Small, stable value types shared across independent layers: actor references, source and implementation IDs, portable actor values, common error identifiers | Minimal external libraries only |
runa-code |
Original source retention, versioned source/implementation hashing, compile-input fingerprints, in-memory compiled artifact cache | runa-core, runa-rune, BLAKE3 |
runa-actor |
Mailbox and state-transition semantics, message reservation, acknowledgment, effects, actor lifecycle definitions | runa-core |
runa-rune |
Rune compilation, VM execution, suspension, interruption hooks, language-specific diagnostics | runa-core, Rune libraries |
runa-runtime |
Scheduling, actor execution coordination, state commits, crash recovery, binding actor semantics to Rune execution | runa-core, runa-actor, runa-code, runa-rune |
runa-image-api |
Trusted in-process application services for spawning, inspecting, sending, editing, and upgrading | runa-core, runa-actor, runa-code, runa-runtime |
runa-protocol |
Versioned transport-neutral request/response/event DTOs and validation | serde |
runa |
Image startup, configuration, protocol dispatch, and loopback-only WebSocket transport | runa-protocol, runa-image-api |
runa-cli |
Interactive REPL and connection argument parsing | runa-client, runa-protocol |
runa-client |
Loopback runa-debug WebSocket client | runa-protocol |
runa-graphics |
Native window, rendering and input mechanisms; no actor policy | runa-core if necessary |
runa-world |
Morphic-style graphical composition and in-world development UI | runa-image-api, runa-graphics |
The current prototype has added runa-code for exact-source identity and
in-memory compilation caching, and runa-image-api as the trusted,
in-process application-service boundary used by the local CLI. It deliberately
does not canonicalize source yet; canonicalization needs a semantics-preserving,
source-map-aware policy. The image API is not yet a network API and performs no
authentication or authorization; callers must be trusted by the host.
The initial runa-image-api provides process-local behavior source retrieval,
publication, history and rollback, actor lifecycle and messaging, snapshots,
ticks, and event retrieval. The CLI uses these application services rather
than reaching into the code store and runtime directly. runa-protocol now
defines versioned, serializable request, response, and event DTOs without
depending on a transport or the runtime implementation. runa now
dispatches those commands through the image API and maps service results into
protocol replies and typed errors. The dispatcher is exposed through an
unauthenticated WebSocket listener restricted
to IPv4 or IPv6 loopback, enforcing a 1 MiB request limit. The WebSocket
listener shares one image across at most 16 concurrent clients. Every local
client receives full development API access, so the endpoint is for trusted
local development only. In-process consumers call runa-image-api, and
external local tools can use runa-client to connect to the server. Actors
do not depend on network transport to coexist in one live image. Remote
network access will require authentication and authorization.
Important: runa-core must not contain schedulers, mailbox implementations, persistence engines, Rune VM types, service traits used by only one crate, generic helper collections, or GUI abstractions. Prefer a type defined in its owning crate until two independent consumers demonstrably need a stable shared representation.
11.3 Runtime interfaces and avoiding cycles
runa-actordefines actor rules and data contracts; it does not spawn threads or call Rune.runa-runeexecutes Rune code but does not own actor scheduling, mailboxes, or image APIs.runa-runtimecoordinates both. Initially it can depend directly onruna-rune; introduce an execution-adapter trait only when a second implementation or test double justifies it. Place the trait at the lowest layer that actually consumes it, rather than automatically inruna-core.runa-image-apiis the single application-service entry point for in-world tools and external clients. It centralizes lifecycle invariants and revision checks; host-level permissions and authentication remain required at any external boundary.runa-protocolholds wire DTOs, not runtime service implementations. Translate DTOs at the server boundary; never serialize internal actor locks or VM references.runa-worlduses the sameruna-image-apioperations as the external bridge. It does not reach into runtime internals for convenience.- Native platform handles stay behind
runa-graphics(or a later platform crate), not inruna-core.
For content-addressed code, runa-core owns stable source/implementation ID value types, and runa-code owns source hashing, compile fingerprints, storage, and compilation caching. The current prototype hashes exact source bytes without canonicalization and keeps the cache in memory.
For persistence, begin with a concrete adapter wired by runa. Extract runa-storage only when its durable mailbox, state, outbox and artifact contracts are clear. Keep database-specific details out of actor semantics and out of runa-core.
11.4 Repository layout
runa/
├── Cargo.toml
├── README.md
├── docs/
│ ├── architecture.md
│ ├── actor-model.md
│ ├── runtime.md
│ ├── protocol.md
│ └── decisions/
├── crates/
│ ├── runa-core/
│ ├── runa-actor/
│ ├── runa-code/
│ ├── runa-rune/
│ ├── runa-runtime/
│ ├── runa-image-api/
│ ├── runa-protocol/
│ ├── runa-cli/
│ ├── runa-client/
│ ├── runa-graphics/
│ ├── runa-world/
│ └── runa/
├── runa-std/
│ ├── core/
│ ├── actors/
│ ├── ui/
│ └── tools/
├── scripts/
├── vscode/
├── examples/
└── tests/
This is a target layout, not a request to generate empty crates. For Phase 0, begin with runa-rune and a small executable test harness. Introduce runa-core, runa-actor, and runa-runtime only as the corresponding functionality is implemented. Enforce the dependency direction through Cargo manifests and periodic dependency-graph checks; reject circular dependencies and upward imports in code review.
12. Implementation Roadmap
Phase 0 — Rune feasibility (first priority)
- Embed Rune in a Rust workspace and compile/run a minimal script.
- Register a native Rust function and call it from Rune.
- Demonstrate async suspension and resumption using verified APIs.
- Investigate interruption of CPU-bound code, execution budgets, frame inspection, and debugging hooks.
- Measure VM/context memory overhead and compiled-code sharing.
- Test cross-thread safety and runtime isolation assumptions.
- Document actual Rune limitations and potential mitigation strategies.
Exit criterion: Evidence-based choice of execution model and a running example. Do not start GUI implementation until this is assessed.
Phase 1 — Minimal actor kernel
- Stable IDs, incarnation generations, bounded mailboxes, state records, stateless handlers.
- Spawn/send/receive/terminate, sequential scheduling, basic lifecycle and error events.
- Demonstrate two Rune actors exchanging structured messages without shared mutable state.
Phase 2 — Transactional processing
- Reserve/ack/retry messages; commit state transitions.
- Explicit effects, error handling, and eventually an outbox for durable sends.
- Test crash/restart retaining mailbox and last committed state.
Phase 3 — Content-addressed code
- Original/canonical source storage, hashing, cache keys, behavior registry, revision history.
- Compile or retrieve cached artifacts; retain exact implementation IDs.
Phase 4 — Live code and debugging foundation
- Safe-point behavior upgrades, invalid-source rollback, state migration policy.
- Pause/resume/cancel and initial execution inspection, subject to Rune feasibility.
The current runtime prototype supports implementation installation only at an
idle handler boundary, cooperative pause/resume between scheduler ticks, and an
explicit host-provided migration closure for structured actor state.
Actor snapshots expose active VM instruction progress, instruction pointer,
and call-frame depth. State and messages are owned portable trees of null,
booleans, integers, strings, lists, records, and distinct actor references.
Rune handlers receive (ctx, state, sender, message) and optionally return state;
unit returns retain committed state and discard invocation-local draft mutations.
context sends are buffered and committed atomically with state.
Rune heap objects never become shared committed actor data. State schemas,
version compatibility checks, Rune-authored migration functions, source
mapping, and local-variable inspection remain future work.
Phase 5 — Image API
- Headless server, versioned protocol, actor and mailbox operations, source editing, events.
- CLI client that can spawn, inspect, send messages, and replace behaviors.
The first Phase 5 increments are a trusted in-process application API used by
the CLI, a transport-neutral versioned protocol contract, and a dispatcher
exposed through bounded loopback WebSockets. The interactive runa-cli uses
runa-client to connect over loopback WebSockets to a server-owned image, rather than
embedding a runtime. Multiple clients share actors, working source, and commit history;
client disconnect/reconnect leaves the running image intact. The runa server provides an
unauthenticated runa-debug WebSocket endpoint restricted to loopback for local VS
Code development. Remote access will require authentication and
authorization; neither network transport nor client location changes the
single-image actor model.
Both binaries use Clap derive/env argument parsing. runa starts the server
at 127.0.0.1:3737 by default (--listen / RUNA_LISTEN), and runa-cli
connects to that address (--connect / RUNA_CONNECT). Flags override environment
variables. Mise's start and cli tasks run the server and REPL respectively.
Phase 6 — Graphical world
- Native window/rendering, input, scene manager, Rune-driven button and window.
- In-world actor browser, state inspector, behavior editor, and message sender.
Phase 7 — VS Code extension
- Connection, actor explorer, state/mailbox inspection, spawn/send controls.
- Virtual behavior documents, compilation diagnostics, hot upgrades, runtime events.
Connection management, the actor explorer, and snapshot inspection
are implemented in vscode/, along with spawn/send, lifecycle controls, and
explicit scheduler ticks, editable image source files, commit history, and
immutable revision documents. Reload/become install scripts at safe boundaries.
Actors expand into virtual metadata/config/state/mailbox/log files; inactive
state/mailbox edits and live log configuration are implemented. Sources support
creation, guarded deletion, and confirmed rename/move with actor path updates.
Actor-specific temporary behavior documents compile on save without changing
shared source; reload clears them and a confirmed action can copy them back to
working source. These behaviors remain live in-memory and can be included in explicit disk
image snapshots; there is no automatic per-edit durability.
Structured Rune compilation errors/warnings now flow through the image API and
protocol into VS Code Problems for open sources and temporary behaviors,
including unsaved text. Validation does not mutate the image; source positions
use UTF-16 editor coordinates. Single-source checking is implemented, not an LSP
or a cross-image module resolver. Detailed runtime event subscriptions remain planned.
Debugger foundations now expose bounded head-VM stack frames and UTF-16 source
locations through read-only execution inspection. VS Code navigates immutable
exact-loaded-source documents, including old code during pending replacements.
Await locations represent the last safe snapshot; native/hidden async frames,
locals and post-mortem stacks remain unimplemented. Actor-specific
implementation-bound line breakpoints and bounded step-into/over/out now preserve active
drafts/effects and use exact loaded-source mappings. An attach-only inline DAP
adapter now exposes one actor as one VS Code debug thread with exact loaded
sources, gutter breakpoints, call stacks, pause/resume, and bounded toolbar
stepping. Continue does not implicitly tick the shared scheduler. Sessions bind
to the connection, implementation and incarnation; a change terminates the
session rather than remapping live frames. Detach leaves actor status and
breakpoints unchanged. Read-only DAP scopes expose owned committed actor
snapshots (state, mailbox, configuration and identity), shared across frames,
not live invocation drafts. Handles expire on stepping/resume or snapshot
replacement. Snapshot-path watches (actor/<JSON Pointer path>) and Debug
Console lookups resolve only owned committed data and reuse the same expandable
values. They do not execute Rune or access live VM slots. Rune 0.14.2 lacks
local-name/lifetime-to-slot debug metadata; frame locals/Rune expression
evaluation and conditional stops remain planned.
Failed actors can be attached without lifecycle mutation. DAP exception details
report the sampled actor failure and snapshot watches expose retained committed
state and the reserved message, not a post-mortem stack. Debug run controls reject
failed actors; explicit repair/restart remains separate, and restart ends the
old-incarnation debug session.
Non-destructive image-change notifications and automatic snapshot refresh are
implemented over the shared runa-debug WebSocket transport.
Phase 8 — Persistence and advanced debugger
- Durable mailboxes and state, snapshot/restore, supervised recovery.
- Source-level debugging and richer interruption/stepping as supported by runtime.
13. Critical Risks and Open Decisions
| Topic | Risk / decision |
|---|---|
| Rune interruption | Verify whether CPU-bound execution can be paused or budgeted safely. |
| Async invocation | Define how awaits preserve private state and what is retried after crash. |
| VM isolation | Determine per-actor VM vs shared compiled code and isolated execution contexts. |
| Scheduler fairness | Cooperative yield alone does not prevent a runaway CPU-bound handler. |
| Effects | Commit/retry semantics and idempotency for external side effects. |
| Mailbox durability | In-memory restart retention vs process-crash durable storage. |
| Source normalization | Canonicalization must preserve semantics and debugging locations. |
| Code upgrade | Active frames must retain exact old implementation versions. |
| UI performance | Avoid an actor per tiny graphical node and render-thread actor calls. |
| Persistence | Full execution-stack image restoration may require interpreter changes. |
| API security | Debugging and code installation are privileged operations. |
14. Initial Non-Goals
No full BEAM clone, distributed actors, millions-of-actors guarantee, full Morphic compatibility, custom Rune compiler, complete IDE, full stack serialization, production-grade hostile-code sandbox, or complete source-level debugger in the first prototype. Do not build a large abstraction framework before the Rune experiment validates it.
15. Instructions for a Local Coding LLM
Act as the principal systems architect and implementation partner. Work incrementally. Begin only with Phase 0. Verify exact Rune APIs and syntax against the pinned version; never invent working APIs. Distinguish confirmed capabilities from hypotheses. Produce a minimal Cargo workspace, runnable examples, tests where practical, build/run instructions, measurements, and a concise feasibility report. Record architectural decisions and unresolved risks. Do not silently change the mailbox/state/handler decomposition, collapse development tools into separate runtimes, or bypass actor isolation for inspection.
First task: Implement a minimal Rust + Rune experiment that executes a Rune function, invokes a registered native Rust function, demonstrates supported asynchronous suspension, and investigates interruption and compiled-code reuse. Stop after reporting the results and proposed Phase 1 architecture.
16. Long-Term Acceptance Scenario
A developer launches the graphical world, spawns actors/counter, sends it a
message, inspects its mailbox and committed state, edits
source/actors/counter.rn, optionally commits a source snapshot, then reloads the
actor at a handler boundary and sees subsequent messages use the new script.
New spawns use working source regardless of commit status. The same operations
are possible through VS Code using the image API. A failed incarnation can be
restarted with the same identity and retained mailbox, and the world can
eventually save and restore a meaningful development session.
North star: The world is the program; actors are its living components; development is continuous interaction with that world rather than a separate edit-build-run cycle.