go/docs/RFC.implementation.3.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.4 KiB
Raw Blame History

Implementation Plan 3 — Action/Task System (Section 18)

Depends on: Plan 2 (Registry). The execution primitive.

Phase A: Action Registry

New file: action.go (renamed from task.go)

type ActionDef struct {
    Name        string
    Handler     ActionHandler
    Description string
    Schema      Options        // expected input keys
    enabled     bool           // for Disable()
}

type ActionHandler func(context.Context, Options) Result

Core accessor:

// Dual-purpose (like Service):
c.Action("process.run", handler)           // register
c.Action("process.run").Run(ctx, opts)     // invoke
c.Action("process.run").Exists()           // check
c.Action("process.run").Def()              // metadata

Internally uses Registry[*ActionDef].

Phase B: Move Registration to IPC

Move from task.go:

  • RegisterActionipc.go (registers in Registry[ActionHandler])
  • RegisterActionsipc.go
  • RegisterTaskipc.go

Keep in action.go:

  • Perform → becomes c.Action("name").Run()
  • PerformAsync → becomes c.Action("name").RunAsync()
  • Progress → stays

Phase C: Panic Recovery on All Actions

Every Action.Run() wraps in recover:

func (a *ActionDef) Run(ctx context.Context, opts Options) (result Result) {
    defer func() {
        if r := recover(); r != nil {
            result = Result{E("action.Run", Sprint("panic: ", r), nil), false}
        }
    }()
    return a.Handler(ctx, opts)
}

Phase D: Task Composition (v0.8.0 stretch)

type TaskDef struct {
    Name  string
    Steps []Step
}

type Step struct {
    Action string
    With   Options
    Async  bool     // run in background
    Input  string   // "previous" = output of last step
}

c.Task("name", def) registers. c.Task("name").Run(ctx) executes steps in order.

Resolves

I16 (task.go concerns), P6-1 (cascade → Task pipeline), P6-2 (O(N×M) → direct dispatch), P7-3 (panic recovery), P7-8 (circuit breaker via Disable), P10-2 (PERFORM not ACTION for request/response).

Migration in core/agent

After Actions exist, refactor handlers.go:

// Current: nested c.ACTION() cascade
// Target:
c.Task("agent.completion", TaskDef{
    Steps: []Step{
        {Action: "agentic.qa"},
        {Action: "agentic.auto-pr"},
        {Action: "agentic.verify"},
        {Action: "agentic.ingest", Async: true},
        {Action: "agentic.poke", Async: true},
    },
})