iop/packages/go/agenttask/ports.go
toki 3c48879c44 feat(agent-runtime): 독립 Agent CLI 런타임 기반을 구현한다
독립 호스트에서 안전한 작업 실행과 복구를 제공하기 위해 런타임 설정, 정책, 상태 저장소, 워크스페이스 격리 및 AgentTask 오케스트레이션을 확장한다.
2026-07-29 13:12:21 +09:00

355 lines
11 KiB
Go

package agenttask
import (
"context"
"errors"
"io"
"os/exec"
"time"
"iop/packages/go/agentconfig"
"iop/packages/go/agentguard"
"iop/packages/go/agentpolicy"
)
var ErrRevisionConflict = errors.New("agenttask state revision conflict")
var ErrDeviceLeaseHeld = errors.New("agenttask device singleton lease is held by another owner")
var ErrLeaseLost = errors.New("agenttask lease ownership lost during operation")
// AgentTaskManager is the host-neutral lifecycle contract. StartProject records
// a durable manual intent; only Reconcile may advance workflow state.
type AgentTaskManager interface {
StartProject(context.Context, StartRequest) error
Reconcile(context.Context) error
StopProject(context.Context, ProjectID) error
}
type Clock interface {
Now() time.Time
}
type StateStore interface {
Load(context.Context) (ManagerState, StateRevision, error)
CompareAndSwap(
context.Context,
StateRevision,
ManagerState,
) (StateRevision, error)
}
// WorkflowAdapter normalizes project-owned task artifacts. Listing projects is
// separate from loading snapshots so one corrupt project cannot stop siblings.
type WorkflowAdapter interface {
RegisteredProjects(context.Context) ([]ProjectID, error)
Snapshot(context.Context, ProjectID) (ProjectWorkflowSnapshot, error)
}
// ArtifactIdentity identifies one project-owned active plan/review artifact
// without making the common manager parse project filesystem conventions.
type ArtifactIdentity struct {
ProjectID ProjectID
WorkspaceID WorkspaceID
WorkUnitID WorkUnitID
AttemptID AttemptID
ArtifactID ArtifactID
}
type ArtifactCompleteness string
const (
ArtifactComplete ArtifactCompleteness = "complete"
ArtifactPlaceholder ArtifactCompleteness = "placeholder"
)
// ArtifactEvidence is the project adapter's normalized observation of the
// active artifact pair. The adapter owns filesystem parsing; the manager owns
// matching the observation against the durable submission identity.
type ArtifactEvidence struct {
Active bool
Identity ArtifactIdentity
Completeness ArtifactCompleteness
RepairIntent *EvidenceRepairIntent
}
type WorkflowEvidenceStatus string
const (
WorkflowEvidenceMatched WorkflowEvidenceStatus = "matched"
WorkflowEvidenceNotActive WorkflowEvidenceStatus = "not_active"
WorkflowEvidencePlaceholder WorkflowEvidenceStatus = "placeholder"
WorkflowEvidenceIdentityMismatch WorkflowEvidenceStatus = "identity_mismatch"
WorkflowEvidenceInvalid WorkflowEvidenceStatus = "invalid"
)
type WorkflowEvidenceRequest struct {
Project ProjectRecord
Work WorkRecord
Submission Submission
}
// EvidenceRepairIntent is valid only for a placeholder from Pi. It carries
// the exact persisted session locator and dispatch ordinal so repair cannot
// attach to a newer attempt or a different native conversation.
type EvidenceRepairIntent struct {
Identity ArtifactIdentity
NativeLocator LocatorRecord
DispatchOrdinal DispatchOrdinal
}
type WorkflowEvidenceRepairRequest struct {
Project ProjectRecord
Work WorkRecord
Submission Submission
Intent EvidenceRepairIntent
}
// WorkflowEvidence is a strict project workflow port. Observe must be
// side-effect free; Repair may change only Pi worker-owned fields in the
// native context identified by EvidenceRepairIntent.
type WorkflowEvidence interface {
Observe(context.Context, WorkflowEvidenceRequest) (ArtifactEvidence, error)
Repair(context.Context, WorkflowEvidenceRepairRequest) error
}
type SelectionRequest struct {
Project ProjectRecord
Work WorkRecord
}
type Selector interface {
Select(context.Context, SelectionRequest) (ExecutionTarget, error)
}
// PolicySelector evaluates the effective project policy identified by
// SelectionContext.ProjectID against an immutable runtime config snapshot,
// returning a durable RouteDecision. The agentpolicy.Evaluator implements this
// interface.
type PolicySelector interface {
SelectPolicy(
context.Context,
agentconfig.RuntimeSnapshot,
agentpolicy.SelectionContext,
) (agentpolicy.RouteDecision, error)
}
// FailureContinuationPolicyRequest identifies the immutable project/work
// policy source for one failed attempt. The source does not receive or return a
// final continuation decision.
type FailureContinuationPolicyRequest struct {
Project ProjectRecord
Work WorkRecord
CurrentTarget ExecutionTarget
}
// FailureContinuationCandidate binds an exact host execution target to the
// ordered eligibility and quota evidence supplied by immutable policy.
type FailureContinuationCandidate struct {
Target ExecutionTarget
Eligible bool
Quota agentpolicy.QuotaObservation
}
// FailureContinuationPolicy contains only declared policy inputs. Manager
// combines these values with its current target, durable attempted-target
// history, normalized observation, and authoritative failure budget before it
// invokes agentpolicy.DecideContinuation.
type FailureContinuationPolicy struct {
Policy agentpolicy.FailurePolicy
Candidates []FailureContinuationCandidate
}
// FailureContinuationPolicySource is an optional extension of Selector. It
// cannot authorize a fabricated action or target because it returns only
// immutable policy and ordered candidate inputs.
type FailureContinuationPolicySource interface {
Selector
ContinuationPolicy(
context.Context,
FailureContinuationPolicyRequest,
) (FailureContinuationPolicy, error)
}
// FailureObservedInvocation exposes an already-normalized, safe observation
// for a failed provider invocation. It contains no provider output or mutable
// diagnostic maps and is the only observation Manager persists.
type FailureObservedInvocation interface {
ProviderInvocation
FailureObservation() agentpolicy.AttemptObservation
}
type IsolationRequest struct {
Project ProjectRecord
Work WorkRecord
Target ExecutionTarget
IdempotencyKey string
}
type PreparedIsolation struct {
Grant *agentguard.WorkspaceGrant
Descriptor *agentguard.IsolationDescriptor
Profile agentguard.ProviderProfile
Confinement InvocationConfinement
}
type IsolationBackend interface {
Prepare(context.Context, IsolationRequest) (PreparedIsolation, error)
}
// ConfinementBinding is the immutable identity that an executable filesystem
// confinement proof must cover. RuntimeRoot and SnapshotRoot identify the
// protected device-local tree; only WritableRoots may be mutated by the child.
type ConfinementBinding struct {
Revision string
IsolationID string
IsolationRevision string
PinnedBaseRevision string
ConfigRevision string
GrantRevision string
ProfileRevision string
BaseRoot string
RuntimeRoot string
SnapshotRoot string
TaskRoot string
WorkingDir string
WritableRoots []string
}
// ConfinementCommand contains only non-I/O launch data. InvocationConfinement
// owns child stdio creation so callers cannot pass a pre-opened descriptor
// through the executable confinement boundary.
type ConfinementCommand struct {
Name string
Args []string
Env []string
}
// StartedConfinement is the exact child and parent-side pipe set created by a
// validated executable confinement proof. BindStarted assumes ownership after
// a successful bind. Before that transfer, Abort closes every pipe endpoint,
// terminates the child, and reaps it.
type StartedConfinement interface {
Child() *exec.Cmd
Stdin() io.WriteCloser
Stdout() io.ReadCloser
Stderr() io.ReadCloser
Abort() error
}
// InvocationConfinement is an executable, identity-bound child launcher.
// Isolation backends issue proofs; provider invokers consume the exact proof
// carried by DispatchRequest rather than asserting a capability boolean.
type InvocationConfinement interface {
Revision() string
Binding() ConfinementBinding
Validate(ConfinementBinding) error
Start(context.Context, ConfinementCommand) (StartedConfinement, error)
}
type DispatchRequest struct {
Project ProjectRecord
Work WorkRecord
Target ExecutionTarget
AdmissionRequest agentguard.AdmissionRequest
Permit *agentguard.Permit
Workspace agentguard.CanonicalWorkspace
Confinement InvocationConfinement
IdempotencyKey string
}
type ProviderInvocation interface {
Locators() []LocatorRecord
Wait(context.Context) (Submission, error)
Cancel(context.Context) error
}
// ProviderLaunch is a side-effect-free provider launch plan. The manager owns
// the only process start: it passes Command to the validated confinement proof
// and then binds the exact returned child and proof-owned pipes to a durable
// invocation handle.
type ProviderLaunch interface {
Command() ConfinementCommand
BindStarted(StartedConfinement) (ProviderInvocation, error)
}
type ProviderInvoker interface {
// Prepare must not start a process, session, or other provider side effect.
// Manager invokes the returned command through InvocationConfinement.Start.
Prepare(context.Context, DispatchRequest) (ProviderLaunch, error)
}
type RecoveryExecutionState string
const (
RecoveryExecutionAbsent RecoveryExecutionState = "absent"
RecoveryExecutionLive RecoveryExecutionState = "live"
RecoveryExecutionSubmitted RecoveryExecutionState = "submitted"
RecoveryExecutionExited RecoveryExecutionState = "exited"
RecoveryExecutionAmbiguous RecoveryExecutionState = "ambiguous"
)
type RecoveryCompletionState string
const (
RecoveryCompletionUnknown RecoveryCompletionState = "unknown"
RecoveryCompletionComplete RecoveryCompletionState = "complete"
RecoveryCompletionPartial RecoveryCompletionState = "partial"
RecoveryCompletionAmbiguous RecoveryCompletionState = "ambiguous"
)
type RecoveryRequest struct {
Project ProjectRecord
Work WorkRecord
Locators map[LocatorKind]LocatorRecord
}
// RecoveryObservation is identity-bound so stale process, session, overlay, or
// archive evidence cannot be rebound to a current attempt.
type RecoveryObservation struct {
ProjectID ProjectID
WorkspaceID WorkspaceID
WorkUnitID WorkUnitID
AttemptID AttemptID
Execution RecoveryExecutionState
Completion RecoveryCompletionState
Submission *Submission
}
type RecoveryInspector interface {
Inspect(context.Context, RecoveryRequest) (RecoveryObservation, error)
}
type ReviewRequest struct {
Project ProjectRecord
Work WorkRecord
Submission Submission
IdempotencyKey string
}
type Reviewer interface {
Review(context.Context, ReviewRequest) (ReviewResult, error)
}
type IntegrationRequest struct {
Project ProjectRecord
Work WorkRecord
ChangeSet ChangeSetIdentity
Ordinal DispatchOrdinal
Attempt IntegrationAttempt
IdempotencyKey string
}
type Integrator interface {
Integrate(context.Context, IntegrationRequest) (IntegrationResult, error)
}
type EventSink interface {
Emit(context.Context, Event) error
}
type nopEventSink struct{}
func (nopEventSink) Emit(context.Context, Event) error { return nil }
type systemClock struct{}
func (systemClock) Now() time.Time { return time.Now().UTC() }