43b2471737
Continues finishing the executor's run.Ports wiring (after C0's Palette).
Critic (run/critic.go): when Ports.Critic is set and the agent enables it, the
executor calls Monitor at run start, feeds RecordStep/RecordToolStart from the
step observer, drains the critic's Steer messages into the loop via
agent.WithSteer, and binds the run's hard cancellation to the critic's
(extendable) Deadline through a watch goroutine — a healthy-but-slow run gets
room while a hung one is killed. Stop() on run end. Soft timeout from
Defaults.CriticSoftTimeout (default 90s). nil-safe: no critic / not-enabled =
no-op.
Delivery (run/executor.go deliver): after the run, when Ports.Delivery is set
and inv.DeliveryID is non-empty, the executor posts Result.Output (or
DeliverError on failure) to a host-interpreted deliver.Target
{inv.DeliveryKind, inv.DeliveryID}. Empty target = caller reads Result.Output
itself (the synchronous default; the `.agent run` canary). Best-effort +
detached.
tool.Invocation gains DeliveryKind/DeliveryID (host-set egress target).
Tests: critic monitored/fed/steered/stopped when enabled, untouched when not;
delivery posts on a target, skips without one. Deferred: Checkpointer (needs a
majordomo hook to snapshot the running message history).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
88 lines
2.4 KiB
Go
88 lines
2.4 KiB
Go
package run
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"gitea.stevedudenhoeffer.com/steve/majordomo/agent"
|
|
)
|
|
|
|
// criticDeadlineCheck is how often the deadline-watch goroutine polls the
|
|
// critic's hard deadline. Small relative to any realistic soft timeout.
|
|
const criticDeadlineCheck = time.Second
|
|
|
|
// criticBinding wires a CriticHandle into a run: the executor forwards activity
|
|
// (steps + tool starts) to it, binds the run's hard cancellation to the critic's
|
|
// extendable deadline, and exposes the critic's Steer messages as an agent
|
|
// RunOption. All methods are nil-safe so the executor can call them
|
|
// unconditionally when no critic is configured.
|
|
type criticBinding struct {
|
|
h CriticHandle
|
|
}
|
|
|
|
// startCritic begins critic monitoring for this run when one is configured and
|
|
// the agent enables it. It launches a goroutine that cancels runCtx (via cancel)
|
|
// the moment the critic's hard deadline passes — the critic may extend that
|
|
// deadline, so a healthy-but-slow run is given room while a hung one is killed.
|
|
// Returns (nil, no-op stop) when there is no critic. The caller MUST defer the
|
|
// returned stop.
|
|
func (e *Executor) startCritic(runCtx context.Context, cancel context.CancelFunc, ra RunnableAgent, info RunInfo) (*criticBinding, func()) {
|
|
noop := func() {}
|
|
if e.cfg.Ports.Critic == nil || !ra.Critic.Enabled {
|
|
return nil, noop
|
|
}
|
|
soft := e.cfg.Defaults.CriticSoftTimeout
|
|
if soft <= 0 {
|
|
return nil, noop
|
|
}
|
|
h := e.cfg.Ports.Critic.Monitor(runCtx, info, soft)
|
|
if h == nil {
|
|
return nil, noop
|
|
}
|
|
done := make(chan struct{})
|
|
go func() {
|
|
t := time.NewTicker(criticDeadlineCheck)
|
|
defer t.Stop()
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
case <-runCtx.Done():
|
|
return
|
|
case <-t.C:
|
|
// A zero deadline = no hard cap (not yet set); otherwise cancel
|
|
// once we're at or past it.
|
|
if d := h.Deadline(); !d.IsZero() && !time.Now().Before(d) {
|
|
cancel()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
return &criticBinding{h: h}, func() {
|
|
close(done)
|
|
h.Stop()
|
|
}
|
|
}
|
|
|
|
func (b *criticBinding) recordStep(iter int) {
|
|
if b != nil {
|
|
b.h.RecordStep(iter)
|
|
}
|
|
}
|
|
|
|
func (b *criticBinding) recordToolStart(name, args string) {
|
|
if b != nil {
|
|
b.h.RecordToolStart(name, args)
|
|
}
|
|
}
|
|
|
|
// steerOptions returns the agent RunOptions that drain the critic's steer
|
|
// messages into the loop. Empty when there is no critic.
|
|
func (b *criticBinding) steerOptions() []agent.RunOption {
|
|
if b == nil {
|
|
return nil
|
|
}
|
|
return []agent.RunOption{agent.WithSteer(b.h.Steer)}
|
|
}
|