Files
kandev/apps/backend/internal/agent/runtime/runtime.go
T
José Almeida f7f1d10996 feat: add parent-controlled task stopping via MCP (#1803)
* feat: add parent-controlled task stopping via MCP

* fix: harden parent task stop lifecycle

* fix: preserve rejected task rollback state
2026-07-19 20:23:46 +01:00

157 lines
6.3 KiB
Go

// Package runtime is the single seam between higher-level coordinators
// (the workflow engine, cron-driven trigger handlers, the orchestrator
// for user-initiated kanban launches) and the agent execution layer
// (lifecycle manager, executor backends, agentctl client).
//
// The runtime knows nothing about tasks, workflows, or office stages.
// It launches agents, resumes them with prompts, stops them, and exposes
// execution state. Anything task-shaped lives a layer above.
//
// Phase 1 of task-model-unification (ADR 0004) introduces this package.
// Sub-packages — `lifecycle` and `agentctl` — are the moved implementations.
// The `Runtime` interface here is the only surface external callers should
// reach for once migration completes; existing callers continue to use the
// sub-packages directly until later phases route them through this seam.
package runtime
import (
"context"
"errors"
"time"
"github.com/kandev/kandev/internal/agent/runtime/lifecycle"
"github.com/kandev/kandev/internal/agentctl/types/streams"
v1 "github.com/kandev/kandev/pkg/api/v1"
)
// Runtime is the public surface for launching, resuming, stopping, and
// observing agent executions.
//
// Implementations must be safe for concurrent use.
type Runtime interface {
// Launch starts a new agent execution against the supplied spec and
// returns a reference to the execution.
Launch(ctx context.Context, spec LaunchSpec) (ExecutionRef, error)
// Resume sends a follow-up prompt to an existing execution.
Resume(ctx context.Context, executionID string, prompt string) error
// Stop terminates an execution and records the supplied reason.
Stop(ctx context.Context, executionID string, reason string) error
// GetExecution returns a snapshot view of an execution by ID.
GetExecution(ctx context.Context, executionID string) (*Execution, error)
// SubscribeEvents returns a channel of stream events for an
// execution. The channel closes when the supplied context is cancelled
// or the execution finishes.
SubscribeEvents(ctx context.Context, executionID string) (<-chan Event, error)
// SetMcpMode swaps the MCP tool mode for a running execution.
SetMcpMode(ctx context.Context, executionID string, mode string) error
}
// LaunchSpec carries everything the runtime needs to start an agent.
//
// The minimum useful surface: profile, executor, workspace, prompt, prior
// ACP session id (for resume), MCP mode, plus a freeform metadata map.
// Richer launch parameters (worktree configuration, multi-repo specs,
// attachments, executor config) flow through `Metadata["launch_request"]`
// as a `*lifecycle.LaunchRequest` until later phases canonicalise them
// onto LaunchSpec directly.
type LaunchSpec struct {
// AgentProfileID identifies the agent profile to run (e.g. "claude-acp-default").
AgentProfileID string
// ExecutorID identifies the executor backend to dispatch on
// (e.g. "local_pc", "local_docker", "sprites").
ExecutorID string
// Workspace describes the workspace the agent operates in.
Workspace WorkspaceRef
// Prompt is the initial prompt sent on session start.
Prompt string
// PriorACPSession is the ACP session id to resume, if any.
PriorACPSession string
// McpMode selects the MCP tool surface ("task", "config", or "office").
McpMode string
// Metadata carries integration-specific fields. Phase 1 supports
// the key "launch_request" with value `*lifecycle.LaunchRequest` to
// pass through fields not yet on LaunchSpec.
Metadata map[string]any
}
// WorkspaceRef identifies the workspace and (optionally) the worktree
// the agent should operate in.
type WorkspaceRef struct {
// Path is the host path to the workspace (or worktree root).
Path string
// RepositoryID, when non-empty, identifies the repository this workspace belongs to.
RepositoryID string
// IsEphemeral marks workspaces that may receive a fallback directory
// when no repository is configured.
IsEphemeral bool
}
// ExecutionRef is a lightweight handle to a launched execution.
type ExecutionRef struct {
// ID is the runtime-assigned execution id.
ID string
// SessionID is the task session this execution belongs to (if any).
SessionID string
// AgentctlURL is the host:port the agentctl HTTP server is reachable on.
AgentctlURL string
// StartedAt is when the runtime registered the execution.
StartedAt time.Time
}
// Execution is a snapshot view of a running execution.
type Execution struct {
ID string
SessionID string
TaskID string
AgentProfileID string
WorkspacePath string
AgentctlURL string // base URL of the agentctl HTTP server (empty when not yet wired)
Status v1.AgentStatus
StartedAt time.Time
FinishedAt *time.Time
ExitCode *int
ErrorMessage string
ACPSessionID string
Metadata map[string]interface{}
}
// Event is the protocol-agnostic event shape produced by an agent
// process. Re-exported from the agentctl streams types for ergonomics:
// callers don't need to import a sibling package for a tiny struct.
type Event = streams.AgentEvent
// ErrNotFound reports that an execution lookup or stop found no live runtime.
var ErrNotFound = errors.New("runtime: execution not found")
// ErrUnsupported is returned by methods that have not yet been wired up
// in the current implementation. Phase 1 leaves SubscribeEvents
// best-effort because the lifecycle manager publishes events via the
// global event bus rather than per-execution channels; callers that
// want raw streams should still use the agentctl client today.
var ErrUnsupported = errors.New("runtime: operation not supported")
// Backend is the lower-level dependency the default Runtime delegates to.
// Defining it as an interface lets tests substitute a fake without
// constructing a full lifecycle Manager.
type Backend interface {
Launch(ctx context.Context, req *lifecycle.LaunchRequest) (*lifecycle.AgentExecution, error)
PromptAgent(ctx context.Context, executionID string, prompt string, attachments []v1.MessageAttachment, dispatchOnly bool) (*lifecycle.PromptResult, error)
StopAgentWithReason(ctx context.Context, executionID string, reason string, force bool) error
GetExecution(executionID string) (*lifecycle.AgentExecution, bool)
SetMcpMode(ctx context.Context, executionID string, mode string) error
}
// Compile-time check: the lifecycle Manager satisfies Backend.
var _ Backend = (*lifecycle.Manager)(nil)