From 05c4fc0a7800011e2fa8b86b5da52f90c1b83b95 Mon Sep 17 00:00:00 2001 From: "user.email" Date: Wed, 25 Mar 2026 17:35:21 +0000 Subject: [PATCH] =?UTF-8?q?feat(rfc-025):=20update=20AX=20spec=20to=20v0.8?= =?UTF-8?q?.0=20=E2=80=94=203=20new=20principles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated all code examples to match core/go v0.8.0 implementation: - process.NewService → process.Register - process.RunWithOptions → c.Process().RunIn() - PERFORM removed from subsystem table - Startable/Stoppable return Result - Added Process, API, Action, Task, Entitled, RegistryOf to subsystem table New principles from validated session patterns: - Principle 8: RFC as Domain Load — load spec at session start - Principle 9: Primitives as Quality Gates — imports are the lint rule - Principle 10: Registration + Entitlement — two-layer permission model Co-Authored-By: Virgil --- docs/specs/RFC-025-AGENT-EXPERIENCE.md | 109 +++++++++++++++++-------- go.mod | 9 ++ 2 files changed, 85 insertions(+), 33 deletions(-) diff --git a/docs/specs/RFC-025-AGENT-EXPERIENCE.md b/docs/specs/RFC-025-AGENT-EXPERIENCE.md index 73400a6..ee61516 100644 --- a/docs/specs/RFC-025-AGENT-EXPERIENCE.md +++ b/docs/specs/RFC-025-AGENT-EXPERIENCE.md @@ -2,7 +2,7 @@ - **Status:** Draft - **Authors:** Snider, Cladius -- **Date:** 2026-03-19 +- **Date:** 2026-03-25 - **Applies to:** All Core ecosystem packages (CoreGO, CorePHP, CoreTS, core-agent) ## Abstract @@ -152,16 +152,16 @@ Every component in the ecosystem registers with Core and communicates through Co ```go c := core.New( core.WithOption("name", "core-agent"), - core.WithService(process.NewService(process.Options{})), + core.WithService(process.Register), core.WithService(agentic.Register), core.WithService(monitor.Register), core.WithService(brain.Register), core.WithService(mcp.Register), ) -c.Run() +c.Run() // or: if err := c.RunE(); err != nil { ... } ``` -`core.New()` accepts functional options. `WithService` registers a service factory that receives `*Core` and returns `Result`. Services are auto-discovered: name from package path, lifecycle from `Startable`/`Stoppable` interfaces, IPC handlers from `HandleIPCEvents`. +`core.New()` returns `*Core`. `WithService` registers a factory `func(*Core) Result`. Services auto-discover: name from package path, lifecycle from `Startable`/`Stoppable` (return `Result`), IPC from `HandleIPCEvents`. #### Service Registration Pattern @@ -177,19 +177,25 @@ func Register(c *core.Core) core.Result { #### Core Subsystem Accessors -| Accessor | Analogy | Purpose | -|----------|---------|---------| -| `c.Options()` | argv | Input configuration used to create this Core | -| `c.App()` | — | Application metadata (name, version) | -| `c.Data()` | /mnt | Embedded assets mounted by packages | -| `c.Drive()` | /dev | Transport handles (API, MCP, SSH, VPN) | -| `c.Config()` | /etc | Configuration, settings, feature flags | -| `c.Fs()` | / | Local filesystem I/O (sandboxable) | -| `c.Error()` | — | Panic recovery (`ErrorPanic`) | -| `c.Log()` | — | Structured logging (`ErrorLog`) | -| `c.Cli()` | — | CLI command framework | -| `c.IPC()` | — | Message bus (ACTION, QUERY, PERFORM) | -| `c.I18n()` | — | Internationalisation | +| Accessor | Purpose | +|----------|---------| +| `c.Options()` | Input configuration | +| `c.App()` | Application metadata (name, version) | +| `c.Config()` | Runtime settings, feature flags | +| `c.Data()` | Embedded assets (Registry[*Embed]) | +| `c.Drive()` | Transport handles (Registry[*DriveHandle]) | +| `c.Fs()` | Filesystem I/O (sandboxable) | +| `c.Process()` | Managed execution (Action sugar) | +| `c.API()` | Remote streams (protocol handlers) | +| `c.Action(name)` | Named callable (register/invoke) | +| `c.Task(name)` | Composed Action sequence | +| `c.Entitled(name)` | Permission check | +| `c.RegistryOf(n)` | Cross-cutting registry queries | +| `c.Cli()` | CLI command framework | +| `c.IPC()` | Message bus (ACTION, QUERY) | +| `c.Log()` | Structured logging | +| `c.Error()` | Panic recovery | +| `c.I18n()` | Internationalisation | #### Primitive Types @@ -230,16 +236,14 @@ c.RegisterAction(func(c *core.Core, msg core.Message) core.Result { #### Process Execution — Use Core Primitives -All external command execution MUST go through `go-process`, not raw `os/exec`. This makes process execution testable, observable via IPC, and managed by Core's lifecycle. +All external command execution MUST go through `c.Process()`, not raw `os/exec`. This makes process execution testable, gatable by entitlements, and managed by Core's lifecycle. ```go -// AX-native: go-process via Core -out, err := process.RunWithOptions(ctx, process.RunOptions{ - Command: "git", Args: []string{"log", "--oneline", "-20"}, - Dir: repoDir, -}) +// AX-native: Core Process primitive +r := c.Process().RunIn(ctx, repoDir, "git", "log", "--oneline", "-20") +if r.OK { output := r.Value.(string) } -// Not AX: raw exec.Command — untestable, no IPC, no lifecycle +// Not AX: raw exec.Command — untestable, no entitlement, no lifecycle cmd := exec.Command("git", "log", "--oneline", "-20") cmd.Dir = repoDir out, err := cmd.Output() @@ -247,6 +251,8 @@ out, err := cmd.Output() **Rule:** If a package imports `os/exec`, it is bypassing Core's process primitive. The only package that should import `os/exec` is `go-process` itself. +**Quality gate:** An agent reviewing a diff can mechanically check: does this import `os/exec`, `unsafe`, or `encoding/json` directly? If so, it bypassed a Core primitive. + #### What This Replaces | Go Convention | Core AX | Why | @@ -255,7 +261,9 @@ out, err := cmd.Output() | `func Must*(v) T` | `core.Result` | No hidden panics; errors flow through Result.OK | | `func *For[T](c) T` | `c.Service("name")` | String lookup is greppable; generics require type context | | `val, err :=` everywhere | Single return via `core.Result` | Intent not obscured by error handling | -| `exec.Command(...)` | `process.Run(ctx, cmd, args...)` | Testable, observable, lifecycle-managed | +| `exec.Command(...)` | `c.Process().Run(ctx, cmd, args...)` | Testable, gatable, lifecycle-managed | +| `map[string]*T + mutex` | `core.Registry[T]` | Thread-safe, ordered, lockable, queryable | +| untyped `any` dispatch | `c.Action("name").Run(ctx, opts)` | Named, typed, inspectable, entitlement-checked | ### 7. Tests as Behavioural Specification @@ -302,6 +310,42 @@ done | Unnamed table tests | Explicit Good/Bad/Ugly | Categories are scannable without reading test body | | Coverage % as metric | Missing categories as metric | 100% coverage with only Good tests is a false signal | +### 8. RFC as Domain Load + +An agent's first action in a session should be loading the repo's RFC.md. The full spec in context produces zero-correction sessions — every decision aligns with the design because the design is loaded. + +**Validated:** Loading core/go's RFC.md (42k tokens from a 500k token discovery session) at session start eliminated all course corrections. The spec is compressed domain knowledge that survives context compaction. + +**Rule:** Every repo that has non-trivial architecture should have a `docs/RFC.md`. The RFC is not documentation for humans — it's a context document for agents. It should be loadable in one read and contain everything needed to make correct decisions. + +### 9. Primitives as Quality Gates + +Core primitives become mechanical code review rules. An agent reviewing a diff checks: + +| Import | Violation | Use Instead | +|--------|-----------|-------------| +| `os/exec` | Bypasses Process primitive | `c.Process().Run()` | +| `unsafe` | Bypasses Fs sandbox | `Fs.NewUnrestricted()` | +| `encoding/json` | Bypasses Core serialisation | Core JSON helpers (future) | +| `fmt.Errorf` | Bypasses error primitive | `core.E()` | +| `errors.New` | Bypasses error primitive | `core.E()` | +| `log.*` | Bypasses logging | `core.Info()` / `c.Log()` | + +**Rule:** If a diff introduces a disallowed import, it failed code review. The import list IS the quality gate. No subjective judgement needed — a weaker model can enforce this mechanically. + +### 10. Registration IS Capability, Entitlement IS Permission + +Two layers of permission, both declarative: + +``` +Registration = "this action EXISTS" → c.Action("process.run").Exists() +Entitlement = "this Core is ALLOWED" → c.Entitled("process.run").Allowed +``` + +A sandboxed Core has no `process.run` registered — the action doesn't exist. A SaaS Core has it registered but entitlement-gated — the action exists but the workspace may not be allowed to use it. + +**Rule:** Never check permissions with `if` statements in business logic. Register capabilities as Actions. Gate them with Entitlements. The framework enforces both — `Action.Run()` checks both before executing. + ## Applying AX to Existing Patterns ### File Structure @@ -371,9 +415,11 @@ c.Command("issue/get", core.Command{ ### Process Execution ```go -// AX-native: go-process helpers, testable +// AX-native: Core Process primitive, testable with mock handler func (s *PrepSubsystem) getGitLog(repoPath string) string { - return gitOutput(context.Background(), repoPath, "log", "--oneline", "-20") + r := s.core.Process().RunIn(context.Background(), repoPath, "git", "log", "--oneline", "-20") + if !r.OK { return "" } + return core.Trim(r.Value.(string)) } // Not AX: raw exec.Command, untestable without real git @@ -381,9 +427,7 @@ func (s *PrepSubsystem) getGitLog(repoPath string) string { cmd := exec.Command("git", "log", "--oneline", "-20") cmd.Dir = repoPath output, err := cmd.Output() - if err != nil { - return "" - } + if err != nil { return "" } return strings.TrimSpace(string(output)) } ``` @@ -415,6 +459,5 @@ Priority order: ## Changelog -- 2026-03-25: Major update — aligned all examples to v0.7.0 API (Option{Key,Value}, WithService, Result without generics). Added Principle 7 (Tests as Behavioural Specification). Added go-process rule to Principle 6. Updated all code examples to match actual implementation. Added command extraction and process execution patterns. -- 2026-03-20: Updated to match implementation — Option K/V atoms, Options as []Option, Data/Drive split, ErrorPanic/ErrorLog renames, subsystem table -- 2026-03-19: Initial draft +- 2026-03-25: v0.8.0 alignment — all examples match implemented API. Added Principles 8 (RFC as Domain Load), 9 (Primitives as Quality Gates), 10 (Registration + Entitlement). Updated subsystem table (Process, API, Action, Task, Entitled, RegistryOf). Process examples use `c.Process()` not old `process.RunWithOptions`. Removed PERFORM references. +- 2026-03-19: Initial draft — 7 principles diff --git a/go.mod b/go.mod index 5250fca..04f6aa7 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,15 @@ require ( ) require ( + dappco.re/go/core v0.5.0 + dappco.re/go/core/api v0.2.0 + dappco.re/go/core/i18n v0.2.0 + dappco.re/go/core/io v0.2.0 + dappco.re/go/core/log v0.1.0 + dappco.re/go/core/process v0.3.0 + dappco.re/go/core/scm v0.4.0 + dappco.re/go/core/store v0.2.0 + dappco.re/go/core/ws v0.3.0 forge.lthn.ai/core/go v0.3.3 // indirect forge.lthn.ai/core/go-i18n v0.1.8 // indirect forge.lthn.ai/core/go-inference v0.1.7 // indirect