P4-1: ServiceStartup order non-deterministic (map iteration)
P4-2: ACTION dispatch synchronous and blocking
P4-3: ACTION !OK stops chain (wrong for broadcast)
P4-4: IPC clone-and-iterate safe but undocumented
P4-5: PerformAsync has no backpressure (unlimited goroutines)
P4-6: ConfigVar.Set() has no lock (data race)
P4-7: PerformAsync shutdown TOCTOU race
P4-8: Named lock "srv" shared across all service ops
Key finding: ACTION stopping on !OK is a bug for broadcast semantics.
Registry[T] resolves P4-1 (insertion order) and P4-8 (per-registry locks).
RFC now 2,269 lines. Four passes, 32 findings total.
Co-Authored-By: Virgil <virgil@lethean.io>
P3-1: Startable/Stoppable return error, not Result — change to Result
P3-2: Process returns (string,error), Action returns Result — unify on Result
P3-3: Three getter patterns (Result, typed, tuple) — document the two real ones
P3-4: Dual-purpose methods anti-AX — keep as sugar, Registry has explicit verbs
P3-5: error leaks despite Result — accept at Go stdlib boundary, Result at Core
P3-6: Data has overlapping APIs — split mount management from file access
P3-7: Action has no error propagation — inherit PerformAsync's panic recovery
P3-8: Registry.Lock() one-way door — add Seal() for hot-reload (update yes, new no)
RFC now at 2,194 lines. Three passes complete:
- Pass 1: 16 known issues (all resolved)
- Pass 2: 8 architectural findings
- Pass 3: 8 spec contradictions
Co-Authored-By: Virgil <virgil@lethean.io>
Issue 10 (Resolved): Array[T] is a guardrail primitive, not speculative.
Same role as string helpers — forces single codepath, model-proof,
scannable. Ordered counterpart to Registry[T].
Issue 11 (Resolved): ConfigVar[T] promoted to documented primitive.
Solves "was this explicitly set?" tracking for layered config.
Typed counterpart to Option (which is any-typed).
Both follow the guardrail pattern: the primitive exists not because
Go can't do it, but because weaker models (Gemini, Codex) will
reinvent it inline every time — badly. One import, one pattern.
Added primitive taxonomy table showing the full picture:
strings→core.Contains, paths→core.JoinPath, errors→core.E,
maps→Registry[T], slices→Array[T], config→ConfigVar[T]
Co-Authored-By: Virgil <virgil@lethean.io>
Registry[T] is the brick that all named collections build on:
- map[string]T + mutex + optional locking
- Set/Get/Has/Names/List/Each/Lock/Delete
- c.Registry("services"), c.Registry("actions"), c.Registry("drives")
Resolves Issues 6 + 12:
- serviceRegistry/commandRegistry become exported, embed Registry[T]
- IPC is safe to expose — reads from registry, doesn't own write path
- Registration goes through c.Action(), locking through c.Registry().Lock()
Typed accessors (c.Service, c.Action, c.Drive) are sugar over
c.Registry(name).Get(). Universal query layer on top.
Replaces 5 separate map+mutex+lock implementations with one primitive.
Co-Authored-By: Virgil <virgil@lethean.io>
Issue 1 (Resolved): UPPERCASE vs CamelCase naming convention:
- CamelCase = primitive (the brick): c.Action(), c.Service(), c.Config()
- UPPERCASE = consumer convenience (sugar): c.ACTION(), c.QUERY(), c.PERFORM()
- Current code has this backwards — ACTION is mapped to raw dispatch
Issue 12 (Resolved): IPC owns the Action registry:
- c.IPC() = owns the data (registry, handlers, task flows)
- c.Action() = primitive API for register/invoke/inspect
- c.ACTION() = convenience shortcut for broadcast
- Three layers, one registry — same pattern as Drive/API
Co-Authored-By: Virgil <virgil@lethean.io>
HTTP/WebSocket/SSE/MCP are all streams. The transport is irrelevant.
- c.IPC() = local conclave (in-process)
- c.API() = remote streams (cross-machine)
- c.Drive() = connection config (WHERE), c.API() = transport (HOW)
- Protocol handlers register like Actions (permission by registration)
- Remote Action dispatch: "charon:agentic.status" → transparent cross-machine
- Maps current manual HTTP/SSE/MCP code in core/agent to single-line calls
- Full 13-subsystem map documented
Proved by: PHP5 stream lib that replaced curl when certs were broken.
Same principle — depend on stream primitive, not transport library.
Co-Authored-By: Virgil <virgil@lethean.io>
9. CommandLifecycle — daemon skeleton, never wired to go-process
10. Array[T] — generic collection used nowhere, speculative
11. ConfigVar[T] — typed config var, only used internally
12. Ipc data-only struct — no methods, misleading accessor
13. Lock() allocates wrapper struct on every call
14. Startables/Stoppables return Result instead of []*Service
15. contract.go comment says New() returns Result (stale)
16. task.go mixes execution + registration concerns
Each issue has: what it is, what the intent was, how it relates
to Sections 17-18, and a proposed resolution. These are the
sliding-context ideas saved in quick implementations that need
the care they deserve.
Co-Authored-By: Virgil <virgil@lethean.io>
Full API spec covering all 16 subsystems: Core container, primitives
(Option/Options/Result), service system, IPC (ACTION/QUERY/PERFORM),
Config, Data, Drive, Fs, CLI, error handling, logging, strings, paths,
utils, locks, ServiceRuntime.
An agent can write a Core service from this document alone.
Co-Authored-By: Virgil <virgil@lethean.io>