Files
steve ca243a2d50
executus CI / test (push) Failing after 24s
P0: stand up executus harness module above majordomo
Batteries-included agent-harness base, extracted from mort's agent layer.
This first cut establishes the module + the zero-coupling core primitives:

- lane, dispatchguard, pendingattach, run/progress.go: moved verbatim from mort
- config: host config Source seam + env-var default (nil-safe helpers)
- deliver: output-egress seam + Discard/Stdout defaults
- identity: AdminPolicy + MemberResolver seams (nil-safe)
- fanout: programmatic N×M swarm (bounded global + per-key concurrency)
- README/CLAUDE.md with the vibe-coded banner; CI with Go gates +
  the "core stays majordomo+stdlib only" invariant

Core builds with stdlib only today; majordomo enters at P1 (model/structured).
go build/vet/test -race all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:18:37 -04:00

79 lines
2.6 KiB
Go

// Package deliver is executus's output-egress seam.
//
// Where a run's final output and any generated artifacts go is host-specific:
// Mort posts a Discord embed (with a paste fallback and state-react emoji),
// Gadfly consolidates findings into one PR comment, a CLI host prints to stdout.
// The harness depends only on the Delivery interface and ships two defaults so a
// new host needs no wiring: Discard (return output to the caller only) and
// Stdout.
package deliver
import (
"context"
"fmt"
"io"
"os"
)
// Target names where output should land. Its fields are host-interpreted (a
// Discord channel ID, a PR number, etc.); the harness never parses them.
type Target struct {
Kind string // host-defined: "channel", "dm", "thread", "stdout", "comment", ...
ID string
}
// Artifact is a generated file accompanying a run's output (an image, a report,
// a STL, ...). Bytes are owned by the caller; a Delivery must not retain them
// past the call without copying.
type Artifact struct {
Name string
MIME string
Bytes []byte
}
// Delivery is the output seam. Deliver returns a host-defined id for the posted
// output when one exists (a message ID, a paste URL); an empty id is fine.
// Implementations must be nil-safe for a nil/empty artifacts slice.
type Delivery interface {
Deliver(ctx context.Context, t Target, output string, artifacts []Artifact) (id string, err error)
DeliverError(ctx context.Context, t Target, runErr error) error
}
// Discard is the light-host default: it drops the output (the caller already has
// it as the run Result). Gadfly uses this — it reads results in-process and does
// its own consolidation.
type Discard struct{}
func (Discard) Deliver(context.Context, Target, string, []Artifact) (string, error) {
return "", nil
}
func (Discard) DeliverError(context.Context, Target, error) error { return nil }
// Stdout writes output (and an artifact manifest) to an io.Writer, defaulting to
// os.Stdout. Handy for local/dev and example hosts.
type Stdout struct{ W io.Writer }
func (s Stdout) w() io.Writer {
if s.W != nil {
return s.W
}
return os.Stdout
}
func (s Stdout) Deliver(_ context.Context, t Target, output string, artifacts []Artifact) (string, error) {
w := s.w()
if t.Kind != "" || t.ID != "" {
fmt.Fprintf(w, "[%s:%s]\n", t.Kind, t.ID)
}
fmt.Fprintln(w, output)
for _, a := range artifacts {
fmt.Fprintf(w, " <artifact %s (%s, %d bytes)>\n", a.Name, a.MIME, len(a.Bytes))
}
return "", nil
}
func (s Stdout) DeliverError(_ context.Context, _ Target, runErr error) error {
fmt.Fprintf(s.w(), "ERROR: %v\n", runErr)
return nil
}