go/docs/RFC.implementation.4.md
Snider 9cd83daaae feat: 6 implementation plans for v0.8.0
Plan 1: Critical bug fixes (v0.7.1, zero breakage)
  - ACTION chain, panic recovery, defer shutdown, stale code removal

Plan 2: Registry[T] primitive (Section 20)
  - Foundation brick, migration of 5 internal registries

Plan 3: Action/Task system (Section 18)
  - Named callables, task composition, cascade fix

Plan 4: c.Process() primitive (Section 17)
  - go-process v0.7.0, proc.go migration, atomic writes

Plan 5: Missing primitives + AX-7
  - core.ID(), ValidateName, WriteAtomic, RunE(), test coverage

Plan 6: Ecosystem sweep (Phase 3)
  - 44 repos in 5 batches, Codex dispatch with RFC as spec

Each plan lists: files to change, code examples, what it resolves,
dependencies on other plans, and migration strategy.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-25 13:31:11 +00:00

2.8 KiB

Implementation Plan 4 — c.Process() Primitive (Section 17)

Depends on: Plan 2 (Registry), Plan 3 (Actions). go-process v0.7.0 update required.

Phase A: Update go-process to v0.7.0 Core API

Repo: core/go-process

  1. Change NewService factory to return core.Result (not (any, error))
  2. Add Register function matching WithService signature
  3. Remove ProcessRegister bridge from core/agent
  4. Register process Actions during OnStartup:
func (s *Service) OnStartup(ctx context.Context) core.Result {
    c := s.Core()
    c.Action("process.run", s.handleRun)
    c.Action("process.start", s.handleStart)
    c.Action("process.kill", s.handleKill)
    return core.Result{OK: true}
}

Phase B: Add Process Primitive to core/go

New file in core/go: process.go

type Process struct {
    core *Core
}

func (c *Core) Process() *Process { return c.process }

func (p *Process) Run(ctx context.Context, command string, args ...string) Result {
    return p.core.Action("process.run").Run(ctx, NewOptions(
        Option{Key: "command", Value: command},
        Option{Key: "args", Value: args},
    ))
}

func (p *Process) RunIn(ctx context.Context, dir, command string, args ...string) Result {
    return p.core.Action("process.run").Run(ctx, NewOptions(
        Option{Key: "command", Value: command},
        Option{Key: "args", Value: args},
        Option{Key: "dir", Value: dir},
    ))
}

No new dependencies in core/go — Process is just Action sugar.

Phase C: Migrate core/agent proc.go

Replace standalone helpers with Core methods:

// Current: proc.go standalone with ensureProcess() hack
out, err := runCmd(ctx, dir, "git", "log")

// Target: method on PrepSubsystem using Core
out := s.core.Process().RunIn(ctx, dir, "git", "log")

Delete proc.go. Delete ensureProcess(). Delete ProcessRegister.

Phase D: Replace syscall.Kill calls

5 call sites in core/agent use syscall.Kill(pid, 0) and syscall.Kill(pid, SIGTERM).

Replace with:

processIsRunning(st.ProcessID, st.PID)  // already in proc.go
processKill(st.ProcessID, st.PID)       // already in proc.go

These use process.Get(id).IsRunning() when ProcessID is available.

Phase E: Atomic File Writes (P4-10)

Add to core/go fs.go:

func (m *Fs) WriteAtomic(p, content string) Result {
    tmp := p + ".tmp." + strconv.FormatInt(time.Now().UnixNano(), 36)
    if r := m.Write(tmp, content); !r.OK { return r }
    if err := os.Rename(tmp, m.path(p)); err != nil {
        m.Delete(tmp)
        return Result{err, false}
    }
    return Result{OK: true}
}

Migrate writeStatus in core/agent to use WriteAtomic. Fixes P4-9 (51 race sites).

Resolves

I7 (no c.Process), P9-1 (os/exec in core), P4-9 (status.json race), P4-10 (Fs.Write not atomic), P11-2 (unsafe.Pointer Fs bypass — add Fs.NewUnrestricted instead).