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>
89 lines
2.9 KiB
Go
89 lines
2.9 KiB
Go
package run_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"gitea.stevedudenhoeffer.com/steve/majordomo/llm"
|
|
"gitea.stevedudenhoeffer.com/steve/majordomo/provider/fake"
|
|
|
|
"gitea.stevedudenhoeffer.com/steve/executus/deliver"
|
|
"gitea.stevedudenhoeffer.com/steve/executus/run"
|
|
"gitea.stevedudenhoeffer.com/steve/executus/tool"
|
|
)
|
|
|
|
type recordingDelivery struct {
|
|
target deliver.Target
|
|
output string
|
|
errored error
|
|
delivers int
|
|
}
|
|
|
|
func (d *recordingDelivery) Deliver(_ context.Context, t deliver.Target, output string, _ []deliver.Artifact) (string, error) {
|
|
d.target, d.output, d.delivers = t, output, d.delivers+1
|
|
return "msg-1", nil
|
|
}
|
|
func (d *recordingDelivery) DeliverError(_ context.Context, t deliver.Target, e error) error {
|
|
d.target, d.errored = t, e
|
|
return nil
|
|
}
|
|
|
|
func TestDeliveryWired(t *testing.T) {
|
|
d := &recordingDelivery{}
|
|
fp := fake.New("fake")
|
|
fp.Enqueue("m", fake.Reply("the output"))
|
|
m, _ := fp.Model("m")
|
|
ex := run.New(run.Config{
|
|
Registry: tool.NewRegistry(),
|
|
Models: func(ctx context.Context, _ string) (context.Context, llm.Model, error) { return ctx, m, nil },
|
|
Ports: run.Ports{Delivery: d},
|
|
})
|
|
// With a delivery target, the executor posts the output.
|
|
ex.Run(context.Background(),
|
|
run.RunnableAgent{Name: "x", ModelTier: "m"},
|
|
tool.Invocation{RunID: "r", DeliveryKind: "channel", DeliveryID: "chan-9"}, "go")
|
|
if d.delivers != 1 || d.output != "the output" || d.target.ID != "chan-9" || d.target.Kind != "channel" {
|
|
t.Fatalf("delivery wrong: %+v out=%q", d.target, d.output)
|
|
}
|
|
}
|
|
|
|
func TestNoDeliveryWithoutTarget(t *testing.T) {
|
|
d := &recordingDelivery{}
|
|
fp := fake.New("fake")
|
|
fp.Enqueue("m", fake.Reply("x"))
|
|
m, _ := fp.Model("m")
|
|
ex := run.New(run.Config{
|
|
Registry: tool.NewRegistry(),
|
|
Models: func(ctx context.Context, _ string) (context.Context, llm.Model, error) { return ctx, m, nil },
|
|
Ports: run.Ports{Delivery: d},
|
|
})
|
|
// No DeliveryID → executor delivers nothing (caller reads Result.Output).
|
|
ex.Run(context.Background(),
|
|
run.RunnableAgent{Name: "x", ModelTier: "m"},
|
|
tool.Invocation{RunID: "r"}, "go")
|
|
if d.delivers != 0 {
|
|
t.Errorf("no target should mean no delivery, got %d", d.delivers)
|
|
}
|
|
}
|
|
|
|
func TestDeliveryErrorPath(t *testing.T) {
|
|
d := &recordingDelivery{}
|
|
ex := run.New(run.Config{
|
|
Registry: tool.NewRegistry(),
|
|
Models: func(ctx context.Context, _ string) (context.Context, llm.Model, error) {
|
|
return ctx, nil, errors.New("resolve boom") // forces a run error
|
|
},
|
|
Ports: run.Ports{Delivery: d},
|
|
})
|
|
ex.Run(context.Background(),
|
|
run.RunnableAgent{Name: "x", ModelTier: "m"},
|
|
tool.Invocation{RunID: "r", DeliveryKind: "channel", DeliveryID: "chan-9"}, "go")
|
|
// A model-resolve error returns before the run context exists, so delivery
|
|
// isn't reached — assert no spurious Deliver. (DeliverError on in-loop errors
|
|
// is exercised by the wiring; this guards the early-return path.)
|
|
if d.delivers != 0 {
|
|
t.Errorf("early failure should not Deliver, got %d", d.delivers)
|
|
}
|
|
}
|