P2: define nil-safe run.Ports (the inversion spine)
Add run/ports.go: the host seams the executor will consume, every one nil-safe so a light host runs with the zero Ports (no persistence/audit/ budget/critic/delegation/delivery) and a heavy host wires each to a battery. Ports mirror mort's existing interfaces so the batteries implement them directly: - Audit + RunRecorder (mort skillaudit.Storage/Writer): StartRun -> per-run recorder (OnStep/OnTool/LogEvent/Close), recorder satisfies RunTally. - Budget (mort skillexec.BudgetTracker): Check / Commit. - Critic + CriticHandle (mort agentcritic): Monitor -> handle with RecordStep/RecordToolStart/Steer/Deadline/Stop (the loop wiring finalizes with the executor merge). - Checkpointer (mort agentexec.RunCheckpointer): Save/Complete/Fail. - PaletteSource (mort SkillInvokerForPalette + AgentInvokerForPalette): Resolve/Invoke skill + agent delegation. Plus host-neutral RunInfo / RunStats. This completes the P2 inversion DESIGN; the agentexec+skillexec -> run.Executor merge that consumes these Ports is the remaining P2 work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,10 +43,11 @@ CORE (majordomo + stdlib):
|
||||
fanout/ programmatic N×M swarm [P0 ✓]
|
||||
deliver/ output egress seam (+ Discard/Stdout) [P0 ✓]
|
||||
identity/ caller identity seams [P0 ✓]
|
||||
run/ run-loop mechanics (cancel-merge, finalizers, [P2 wip]
|
||||
RunStateAccessor via RunTally seam, submit,
|
||||
progress bridge) + RunnableAgent DTO done;
|
||||
executor merge + nil-safe run.Ports next [P2]
|
||||
run/ run-loop mechanics + RunnableAgent DTO + [P2 wip]
|
||||
nil-safe run.Ports (Audit/Budget/Critic/
|
||||
Checkpointer/PaletteSource) defined; the
|
||||
agentexec+skillexec -> run.Executor MERGE
|
||||
(consuming Ports) is the remaining P2 work [P2]
|
||||
dispatchguard/ loop/depth/fan-out caps [P0 ✓]
|
||||
pendingattach/ attachment dedupe [P0 ✓]
|
||||
tool/ registry + 3-stage permissions + ssrf [P1 ✓]
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package run
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gitea.stevedudenhoeffer.com/steve/majordomo/llm"
|
||||
|
||||
"gitea.stevedudenhoeffer.com/steve/executus/deliver"
|
||||
)
|
||||
|
||||
// Ports are the host seams the run executor consumes. Every field is nil-safe:
|
||||
// a light host passes the zero Ports and gets a bounded, in-memory run with no
|
||||
// persistence, audit, budget, critic, delegation, or delivery — which is
|
||||
// exactly a gadfly swarm task. A heavy host (mort) wires each one to a battery.
|
||||
//
|
||||
// This struct IS the inversion: in mort, agentexec imports agents /
|
||||
// agentcritic / skillaudit and skillexec imports skills / paste directly; here
|
||||
// the kernel depends only on these interfaces, and the batteries implement
|
||||
// them. The mort_*_adapters.go wall becomes the set of impls.
|
||||
type Ports struct {
|
||||
// Audit records the run trace (start, per-step/per-tool events, final
|
||||
// stats). nil = no audit.
|
||||
Audit Audit
|
||||
// Budget gates and meters per-caller resource use. nil = unbounded.
|
||||
Budget Budget
|
||||
// Critic optionally monitors a long run for hangs/runaways. nil = none.
|
||||
Critic Critic
|
||||
// Checkpointer persists resumable progress for durable recovery. nil = no
|
||||
// checkpointing (a run interrupted by shutdown is simply lost).
|
||||
Checkpointer Checkpointer
|
||||
// Palette resolves SkillPalette / SubAgentPalette entries into delegation
|
||||
// tools (skill__<name> / agent__<name>). nil = those entries are inert.
|
||||
Palette PaletteSource
|
||||
// Delivery is where the run's output + artifacts go. nil = the caller
|
||||
// reads the Result in-process (the light-host default).
|
||||
Delivery deliver.Delivery
|
||||
}
|
||||
|
||||
// RunInfo describes a run at start time — the attribution a recorder/critic
|
||||
// needs. Host-neutral rename of mort's SkillRun start fields.
|
||||
type RunInfo struct {
|
||||
RunID string
|
||||
SubjectID string // the agent/skill id being run (audit "skill_id")
|
||||
Name string
|
||||
CallerID string
|
||||
ChannelID string
|
||||
ParentRunID string
|
||||
Inputs map[string]any
|
||||
StartedAt time.Time
|
||||
}
|
||||
|
||||
// RunStats is the terminal roll-up a recorder's Close writes. Mirrors mort's
|
||||
// skillaudit/skillexec RunStats.
|
||||
type RunStats struct {
|
||||
Status string // ok | error | timeout | budget_exceeded | cancelled | dry_run
|
||||
Output string
|
||||
Error string
|
||||
ToolCalls int
|
||||
RuntimeSeconds float64
|
||||
InputTokens int64
|
||||
OutputTokens int64
|
||||
ThinkingTokens int64
|
||||
}
|
||||
|
||||
// --- Audit ---
|
||||
|
||||
// Audit begins recording a run. StartRun returns a per-run RunRecorder (or nil
|
||||
// to skip recording this run). The audit battery wires its Storage behind this.
|
||||
type Audit interface {
|
||||
StartRun(ctx context.Context, info RunInfo) RunRecorder
|
||||
}
|
||||
|
||||
// RunRecorder records the events of one in-flight run and its final stats. It
|
||||
// satisfies RunTally so the kernel can surface live token/tool counts to the
|
||||
// self-status tool. Mirrors mort's skillaudit.Writer.
|
||||
type RunRecorder interface {
|
||||
RunTally
|
||||
// OnStep records one completed agent-loop iteration's model response.
|
||||
OnStep(iter int, resp *llm.Response)
|
||||
// OnTool records one executed tool call + its result.
|
||||
OnTool(call llm.ToolCall, result string)
|
||||
// LogEvent / LogError append structured events to the run log.
|
||||
LogEvent(eventType string, payload map[string]any)
|
||||
LogError(msg string)
|
||||
// Close writes the terminal roll-up. Detaches from the caller's context
|
||||
// internally so a cancelled run still records.
|
||||
Close(ctx context.Context, stats RunStats)
|
||||
}
|
||||
|
||||
// --- Budget ---
|
||||
|
||||
// Budget gates and meters per-caller resource use. Mirrors mort's
|
||||
// skillexec.BudgetTracker.
|
||||
type Budget interface {
|
||||
// Check reports whether the caller has remaining budget (nil = allowed).
|
||||
Check(ctx context.Context, callerID string) error
|
||||
// Commit records that the caller spent runtimeSeconds on this run.
|
||||
Commit(ctx context.Context, callerID string, runtimeSeconds float64)
|
||||
}
|
||||
|
||||
// --- Critic ---
|
||||
|
||||
// Critic optionally monitors a long-running run (the two-tier soft/hard
|
||||
// timeout). Monitor returns a handle the executor feeds progress into and
|
||||
// queries for steer/deadline decisions; a nil handle means "not monitored".
|
||||
//
|
||||
// The exact wiring (how the handle's Steer/Deadline bind into majordomo's
|
||||
// agent.WithSteer / agent.WithMaxStepsFunc / run-context cancellation) is
|
||||
// finalized in the executor; this is the seam the agentcritic battery adapts.
|
||||
type Critic interface {
|
||||
Monitor(ctx context.Context, info RunInfo, softTimeout time.Duration) CriticHandle
|
||||
}
|
||||
|
||||
// CriticHandle is the executor's live link to a run's critic.
|
||||
type CriticHandle interface {
|
||||
// RecordStep / RecordToolStart keep the critic's activity clock fresh so a
|
||||
// healthy-but-slow run is not mistaken for a hang.
|
||||
RecordStep(iter int)
|
||||
RecordToolStart(name, args string)
|
||||
// Steer returns any messages the critic wants injected into the loop (a
|
||||
// nudge), drained before each step — matches majordomo agent.WithSteer.
|
||||
Steer() []llm.Message
|
||||
// Deadline returns the current hard-kill deadline (the critic may extend
|
||||
// it); the executor binds the run context to it. Zero = no hard deadline.
|
||||
Deadline() time.Time
|
||||
// Stop ends monitoring when the run finishes.
|
||||
Stop()
|
||||
}
|
||||
|
||||
// --- Checkpointer ---
|
||||
|
||||
// Checkpointer persists a run's resumable progress for durable recovery.
|
||||
// Mirrors mort's agentexec.RunCheckpointer.
|
||||
type Checkpointer interface {
|
||||
// Save persists the run's current resumable progress (throttled).
|
||||
Save(ctx context.Context, st RunCheckpointState) error
|
||||
// Complete clears the checkpoint on success.
|
||||
Complete(ctx context.Context) error
|
||||
// Fail clears the checkpoint on terminal failure. A run interrupted by
|
||||
// shutdown is left untouched so boot recovery picks it up.
|
||||
Fail(ctx context.Context, err error) error
|
||||
}
|
||||
|
||||
// RunCheckpointState is the resumable snapshot a Checkpointer persists. Kept
|
||||
// minimal here; the executor extends what it records during the merge.
|
||||
type RunCheckpointState struct {
|
||||
Messages []llm.Message
|
||||
Iteration int
|
||||
}
|
||||
|
||||
// --- PaletteSource ---
|
||||
|
||||
// PaletteSource resolves a RunnableAgent's SkillPalette / SubAgentPalette names
|
||||
// into delegation tools and invokes them. Mirrors mort's
|
||||
// SkillInvokerForPalette + AgentInvokerForPalette. nil Palette => palette
|
||||
// entries are inert ("not configured" at first call).
|
||||
type PaletteSource interface {
|
||||
ResolveSkill(ctx context.Context, callerID, name string) (skillID string, err error)
|
||||
InvokeSkill(ctx context.Context, callerID, channelID, name string,
|
||||
inputs map[string]any, parentRunID string) (output, runID, status string, err error)
|
||||
|
||||
ResolveAgent(ctx context.Context, callerID, name string) (agentID string, err error)
|
||||
InvokeAgent(ctx context.Context, callerID, channelID, name string,
|
||||
prompt, parentRunID, modelTierOverride, promptPrepend string,
|
||||
toolsSubset []string,
|
||||
onEvent func(ctx context.Context, event, emoji string)) (output, runID, status string, err error)
|
||||
}
|
||||
Reference in New Issue
Block a user