99 lines
2.4 KiB
Markdown
99 lines
2.4 KiB
Markdown
|
|
# 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`)
|
|||
|
|
|
|||
|
|
```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:**
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 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`:
|
|||
|
|
- `RegisterAction` → `ipc.go` (registers in `Registry[ActionHandler]`)
|
|||
|
|
- `RegisterActions` → `ipc.go`
|
|||
|
|
- `RegisterTask` → `ipc.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:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
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)
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
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`:
|
|||
|
|
|
|||
|
|
```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},
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
```
|