CRITICAL: P11-2 — The Fs sandbox (P2-2: "correctly unexported") is
bypassed by core/agent using unsafe.Pointer to overwrite Fs.root.
The security boundary exists in theory but is broken in practice.
P11-1: Every service has God Mode — full access to everything
P11-2: Fs.root bypassed via unsafe.Pointer (paths.go, detect.go)
P11-3: core.Env() exposes all secrets (API keys, tokens)
P11-4: ACTION event spoofing — fake AgentCompleted triggers pipeline
P11-5: RegisterAction installs spy handler (sees all IPC)
P11-6: No audit trail — no logging of security-relevant ops
P11-7: ServiceLock has no authentication (anyone can lock)
P11-8: No revocation — services join but can never be ejected
The conclave trust model: all first-party, no isolation.
Acceptable for v0.8.0 (trusted code). Needs capability model
for v0.9.0+ (plugins, third-party services).
Eleven passes, 88 findings, 3,400+ lines.
Co-Authored-By: Virgil <virgil@lethean.io>
P10-1: S17 and S18 contradict on Process return type
P10-2: S17 uses ACTION for request/response — should be PERFORM
P10-3: Subsystem count wrong (22 methods, not 14 subsystems)
P10-4: Four API patterns on one struct — undocumented categories
P10-5: Registry resolves old issues — needs cross-reference table
P10-6: Design Philosophy before reasoning — correct for RFC format
P10-7: v0.8.0 checklist missing 56 findings — added severity classification
(3 critical, 12 high, 25 medium, 16 low)
P10-8: No section numbers for passes — findings need index
Meta-finding: the spec can now audit itself. Cross-referencing sections
reveals contradictions that weren't visible when each section was written
independently. The RFC is detailed enough to be self-checking.
Ten passes, 80 findings, 3,300+ lines.
Co-Authored-By: Virgil <virgil@lethean.io>
P9-1: core/go imports os/exec in app.go — violates its own rule
P9-2: reflect used for service name — magic, fragile on pkg rename
P9-3: No JSON primitive — every consumer imports encoding/json
P9-4: No time primitive — 3 different timestamp formats
P9-5: No ID generation — 3 different patterns (rand, counter, fmt)
P9-6: No validation primitive — path traversal check copy-pasted 3x
P9-7: Error codes exist but nobody uses them
P9-8: No health/observability primitive — go-process has it per-daemon only
Key finding: core/go imports os/exec (P9-1), violating the rule it
established in Section 17. App.Find() uses exec.LookPath — a process
concern that belongs in go-process.
Missing primitives: ID generation, validation, health checks.
Judgment calls: JSON wrapping (maybe noise), time formatting (convention).
Nine passes, 72 findings, 3,100+ lines.
Co-Authored-By: Virgil <virgil@lethean.io>
CRITICAL: 79% of type assertions on Result.Value are bare (no comma-ok).
50 panic sites in core/go, ~100 in core/agent. Every r.Value.(string)
is a deferred panic that (string, error) would catch at compile time.
P8-1: 50/63 assertions bare — panic on wrong type
P8-2: Result is one type for everything — no compile-time safety
P8-3: Message/Query/Task all 'any' — no type routing
P8-4: Option.Value is 'any' — config becomes untyped bag
P8-5: ServiceFor returns (T,bool) not Result — Go generics limitation
P8-6: Fs validatePath returns Result then callers bare-assert string
P8-7: HandleIPCEvents wrong signature silently fails to register
P8-8: The Guardrail Paradox — Core trades compile-time safety for LOC
The fundamental tension: Result reduces LOC but every type assertion
is a deferred panic. Resolution: AX-7 Ugly tests + typed convenience
methods + accept that Result is a runtime contract.
Eight passes, 64 findings, RFC at 2,930+ lines.
Co-Authored-By: Virgil <virgil@lethean.io>
CRITICAL findings:
P7-1: New() returns half-built Core on option failure (no error return)
P7-2: ServiceStartup fails → os.Exit(1) → no cleanup → resource leak
P7-3: ACTION handlers have NO panic recovery (PerformAsync does)
P7-4: Run() has no defer — panic skips ServiceShutdown
P7-5: os.Exit(1) bypasses ALL defers — even if we add them
Additional:
P7-6: Shutdown context timeout stops remaining service cleanup
P7-7: SafeGo exists but nobody uses it — the safety primitive is unwired
P7-8: No circuit breaker — broken handlers called forever
The error path through Core is: log and crash. No rollback, no cleanup,
no recovery. Every failure mode ends in resource leaks or silent state
corruption. The fix is: defer shutdown always, wrap handlers in recover,
stop calling os.Exit from inside Core.
Seven passes, 56 findings, 2,760+ lines.
Co-Authored-By: Virgil <virgil@lethean.io>
P5-1: ServiceRuntime not used by core/agent — two registration camps exist
P5-2: Register returns Result, OnStartup returns error — consumer confusion
P5-3: No service dependency declaration — implicit order, non-deterministic start
P5-4: HandleIPCEvents auto-discovered via reflect — magic method name
P5-5: Commands registered during OnStartup — invisible timing dependency
P5-6: No service discovery by interface/capability — only lookup by name
P5-7: Factory can see but can't safely USE other services
P5-8: MCP aggregator pattern undocumented — cross-cutting service reads all Actions
Key finding: two camps exist (manual .core vs ServiceRuntime). Both work,
neither documented. HandleIPCEvents is magic — anti-AX.
RFC now 2,436 lines. Five passes, 40 findings.
Co-Authored-By: Virgil <virgil@lethean.io>
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>
Standard llm.txt with package layout, key types, service pattern.
Points to CLAUDE.md and docs/RFC.md for full specs.
Co-Authored-By: Virgil <virgil@lethean.io>
Root cause: Result.New didn't mark single-value results as OK=true,
breaking Mount/ReadDir/fs helpers that used Result{}.New(value, err).
Also: data_test.go and embed_test.go updated for Options struct,
doc comments updated across data.go, drive.go, command.go, contract.go.
All tests green. Coverage 82.2%.
Co-Authored-By: Virgil <virgil@lethean.io>
WithService: full name discovery + IPC handler auto-registration via reflect
WithName: explicit service naming
RegisterService: Startable/Stoppable/HandleIPCEvents auto-discovery
MustServiceFor[T]: panics if not found
WithServiceLock: enable/apply split (v0.3.3 parity)
Cli: registered as service via CliRegister, accessed via ServiceFor
@TODO Codex: Fix data_test.go and embed_test.go — embed path resolution
after Options changed from []Option to struct. Mount paths need updating.
Co-Authored-By: Virgil <virgil@lethean.io>
WithService now: calls factory, discovers service name from instance's
package path via reflect.TypeOf, discovers HandleIPCEvents method,
calls RegisterService. If factory returns nil Value, assumes self-registered.
Also fixes: Cli() accessor uses ServiceFor, test files updated for Options struct.
Co-Authored-By: Virgil <virgil@lethean.io>
- WithService now calls factory, discovers service name from package path via
reflect/runtime (last path segment, _test suffix stripped, lowercased), and
calls RegisterService — which handles Startable/Stoppable/HandleIPCEvents
- If factory returns nil Value (self-registered), WithService returns OK without
a second registration
- Add contract_test.go with _Good/_Bad tests covering all three code paths
- Fix core.go Cli() accessor: use ServiceFor[*Cli](c, "cli") (was cli.New())
- Fix pre-existing })) → }}) syntax errors in command_test, service_test, lock_test
- Fix pre-existing Options{...} → NewOptions(...) in core_test, data_test,
drive_test, i18n_test (Options is a struct, not a slice)
Co-Authored-By: Virgil <virgil@lethean.io>
Cli as service with ServiceRuntime, incomplete.
Need to properly port v0.3.3 service_manager, message_bus,
WithService with full name/IPC discovery.
Co-Authored-By: Virgil <virgil@lethean.io>
App.New() creates from Options. App.Find() locates programs on PATH.
Both are struct methods — no package-level functions.
8 tests passing.
Co-Authored-By: Virgil <virgil@lethean.io>
Options is now a proper struct with New(), Set(), Get(), typed accessors.
Result gains New(), Result(), Get() methods on the struct.
WithOption("key", value) convenience for core.New().
options_test.go: 22 tests passing against the new contract.
Other test files mechanically updated for compilation.
Co-Authored-By: Virgil <virgil@lethean.io>
Restores v0.3.3 service manager capabilities:
- RegisterService(name, instance) stores the raw instance
- Auto-discovers Startable/Stoppable interfaces → wires lifecycle
- Auto-discovers HandleIPCEvents → wires to IPC bus
- ServiceFor[T](c, name) for typed instance retrieval
- Service DTO gains Instance field for instance tracking
WithService is a simple factory call — no reflect, no magic.
discoverHandlers removed — RegisterService handles it inline.
No double-registration: IPC wired once at registration time.
Co-Authored-By: Virgil <virgil@lethean.io>
WithService is now a simple factory call — no reflect, no auto-registration.
New() calls discoverHandlers() after all opts run, scanning Config for
service instances that implement HandleIPCEvents.
This eliminates both double-registration and empty-placeholder issues:
- Factories wire their own lifecycle via c.Service()
- HandleIPCEvents discovered once, after all services are registered
- No tension between factory-registered and auto-discovered paths
Co-Authored-By: Virgil <virgil@lethean.io>