327 lines
13 KiB
Go
327 lines
13 KiB
Go
package domain
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.cin.su/m/Assistant/contracts/capability"
|
|
"git.cin.su/m/Assistant/contracts/work"
|
|
"git.cin.su/m/Assistant/platform/ids"
|
|
)
|
|
|
|
var ErrInvalidDelegation = errors.New("invalid delegation")
|
|
var ErrInvalidAgentRun = errors.New("invalid agent run")
|
|
var ErrInvalidAgentCheckpoint = errors.New("invalid agent checkpoint")
|
|
var ErrInvalidAgentResult = errors.New("invalid agent result")
|
|
var ErrInvalidDelegationVerification = errors.New("invalid delegation verification")
|
|
|
|
type DelegationRequest struct {
|
|
Goal work.Goal
|
|
Plan work.WorkPlan
|
|
Candidate work.PlanCandidate
|
|
Requirement work.CapabilityRequirement
|
|
Implementation capability.CapabilityImplementation
|
|
ConversationID work.ConversationRef
|
|
OriginalIntentText string
|
|
ContextSummary string
|
|
ContextRefs []string
|
|
VerificationSummary string
|
|
DerivedInstruction string
|
|
StartedAt time.Time
|
|
}
|
|
|
|
type DelegationBundle struct {
|
|
Delegation work.Delegation
|
|
Run work.AgentRun
|
|
}
|
|
|
|
func StartDelegation(input DelegationRequest) (DelegationBundle, error) {
|
|
if input.Goal.ID == "" || input.Plan.ID == "" || input.Candidate.ID == "" || input.Requirement.ID == "" ||
|
|
input.Implementation.ID == "" || input.Implementation.Kind != capability.ImplementationAgent ||
|
|
input.Requirement.CapabilityKind == "" || input.StartedAt.IsZero() || strings.TrimSpace(input.OriginalIntentText) == "" {
|
|
return DelegationBundle{}, ErrInvalidDelegation
|
|
}
|
|
delegationID, err := ids.New()
|
|
if err != nil {
|
|
return DelegationBundle{}, err
|
|
}
|
|
runID, err := ids.New()
|
|
if err != nil {
|
|
return DelegationBundle{}, err
|
|
}
|
|
millis := input.StartedAt.UnixMilli()
|
|
delegation := work.Delegation{
|
|
ID: work.DelegationID(delegationID),
|
|
GoalID: input.Goal.ID,
|
|
PlanID: input.Plan.ID,
|
|
PlanCandidateID: input.Candidate.ID,
|
|
CapabilityRequirementID: input.Requirement.ID,
|
|
CapabilityKind: input.Requirement.CapabilityKind,
|
|
SelectedImplementationID: input.Implementation.ID,
|
|
SelectedImplementationRev: input.Implementation.Revision,
|
|
SelectedImplementationKind: input.Implementation.Kind,
|
|
Protocol: protocolForImplementation(input.Implementation),
|
|
SourceIntentID: input.Goal.SourceIntentID,
|
|
SourceUtteranceID: input.Goal.SourceUtteranceID,
|
|
ConversationID: input.ConversationID,
|
|
ProjectID: input.Goal.ProjectID,
|
|
ProjectDisplayName: input.Goal.ProjectDisplayName,
|
|
DesiredOutcome: input.Goal.DesiredOutcome,
|
|
ConstraintSummary: nonEmpty(input.Goal.Constraints, "no additional constraints"),
|
|
VerificationSummary: nonEmpty(input.VerificationSummary, "verify outcome through tests, repository state, and explicit evidence"),
|
|
ContextSummary: buildContextSummary(input),
|
|
ContextRefs: append([]string{}, input.ContextRefs...),
|
|
State: work.DelegationRunning,
|
|
LatestRunID: work.AgentRunID(runID),
|
|
CreatedAtUnixMillis: millis,
|
|
UpdatedAtUnixMillis: millis,
|
|
}
|
|
run := work.AgentRun{
|
|
ID: work.AgentRunID(runID),
|
|
DelegationID: delegation.ID,
|
|
ImplementationID: input.Implementation.ID,
|
|
ImplementationRevision: input.Implementation.Revision,
|
|
Protocol: delegation.Protocol,
|
|
State: work.AgentRunStarted,
|
|
DerivedInstructionSummary: summarizeInstruction(input.DerivedInstruction),
|
|
StartedAtUnixMillis: millis,
|
|
UpdatedAtUnixMillis: millis,
|
|
}
|
|
if err := ValidateDelegation(delegation); err != nil {
|
|
return DelegationBundle{}, err
|
|
}
|
|
if err := ValidateAgentRun(run); err != nil {
|
|
return DelegationBundle{}, err
|
|
}
|
|
return DelegationBundle{Delegation: delegation, Run: run}, nil
|
|
}
|
|
|
|
func ValidateDelegation(value work.Delegation) error {
|
|
if value.ID == "" || value.GoalID == "" || value.PlanID == "" || value.PlanCandidateID == "" ||
|
|
value.CapabilityRequirementID == "" || value.CapabilityKind == "" || value.SelectedImplementationID == "" ||
|
|
value.SelectedImplementationRev < 1 || value.SelectedImplementationKind != capability.ImplementationAgent ||
|
|
value.Protocol == "" || value.SourceIntentID == "" || value.SourceUtteranceID == "" ||
|
|
strings.TrimSpace(value.DesiredOutcome) == "" || value.State == "" || value.CreatedAtUnixMillis < 1 ||
|
|
value.UpdatedAtUnixMillis < value.CreatedAtUnixMillis {
|
|
return ErrInvalidDelegation
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateAgentRun(value work.AgentRun) error {
|
|
if value.ID == "" || value.DelegationID == "" || value.ImplementationID == "" || value.ImplementationRevision < 1 ||
|
|
value.Protocol == "" || value.State == "" || value.StartedAtUnixMillis < 1 || value.UpdatedAtUnixMillis < value.StartedAtUnixMillis {
|
|
return ErrInvalidAgentRun
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func CheckpointRun(run work.AgentRun, blocker work.AgentBlockerClass, completed, remaining, tests, branch, commit, next string, refs []work.EvidenceRef, now time.Time) (work.AgentRun, work.AgentCheckpoint, error) {
|
|
if err := ValidateAgentRun(run); err != nil || now.IsZero() {
|
|
return work.AgentRun{}, work.AgentCheckpoint{}, ErrInvalidAgentRun
|
|
}
|
|
checkpointID, err := ids.New()
|
|
if err != nil {
|
|
return work.AgentRun{}, work.AgentCheckpoint{}, err
|
|
}
|
|
state := work.AgentRunCheckpointed
|
|
switch blocker {
|
|
case work.AgentBlockerExecutionWindow:
|
|
state = work.AgentRunExecutionWindowExhausted
|
|
case work.AgentBlockerHumanDecisionRequired, work.AgentBlockerMissingExternalCredential, work.AgentBlockerArchitectureDecision, work.AgentBlockerSecurityConflict:
|
|
state = work.AgentRunBlocked
|
|
case work.AgentBlockerTechnicalFailure:
|
|
state = work.AgentRunFailed
|
|
case work.AgentBlockerNone:
|
|
state = work.AgentRunCheckpointed
|
|
default:
|
|
return work.AgentRun{}, work.AgentCheckpoint{}, ErrInvalidAgentCheckpoint
|
|
}
|
|
run.State = state
|
|
run.UpdatedAtUnixMillis = now.UnixMilli()
|
|
checkpoint := work.AgentCheckpoint{
|
|
ID: work.AgentCheckpointID(checkpointID),
|
|
DelegationID: run.DelegationID,
|
|
RunID: run.ID,
|
|
State: state,
|
|
BlockerClass: blocker,
|
|
CompletedSummary: strings.TrimSpace(completed),
|
|
RemainingSummary: strings.TrimSpace(remaining),
|
|
TestSummary: strings.TrimSpace(tests),
|
|
BranchName: strings.TrimSpace(branch),
|
|
CommitHash: strings.TrimSpace(commit),
|
|
NextStep: strings.TrimSpace(next),
|
|
ArtifactRefs: append([]work.EvidenceRef{}, refs...),
|
|
RecordedAtUnixMillis: now.UnixMilli(),
|
|
}
|
|
if err := ValidateAgentCheckpoint(checkpoint); err != nil {
|
|
return work.AgentRun{}, work.AgentCheckpoint{}, err
|
|
}
|
|
return run, checkpoint, nil
|
|
}
|
|
|
|
func ValidateAgentCheckpoint(value work.AgentCheckpoint) error {
|
|
if value.ID == "" || value.DelegationID == "" || value.RunID == "" || value.State == "" || value.BlockerClass == "" || value.RecordedAtUnixMillis < 1 {
|
|
return ErrInvalidAgentCheckpoint
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func CompleteRun(run work.AgentRun, summary, outcomeRef, verification string, refs []work.EvidenceRef, now time.Time) (work.AgentRun, work.AgentResult, error) {
|
|
if err := ValidateAgentRun(run); err != nil || now.IsZero() || strings.TrimSpace(summary) == "" {
|
|
return work.AgentRun{}, work.AgentResult{}, ErrInvalidAgentResult
|
|
}
|
|
resultID, err := ids.New()
|
|
if err != nil {
|
|
return work.AgentRun{}, work.AgentResult{}, err
|
|
}
|
|
run.State = work.AgentRunCompleted
|
|
run.UpdatedAtUnixMillis = now.UnixMilli()
|
|
run.CompletedAtUnixMillis = now.UnixMilli()
|
|
result := work.AgentResult{
|
|
ID: work.AgentResultID(resultID),
|
|
DelegationID: run.DelegationID,
|
|
RunID: run.ID,
|
|
State: work.AgentRunCompleted,
|
|
Summary: strings.TrimSpace(summary),
|
|
OutcomeRef: strings.TrimSpace(outcomeRef),
|
|
VerificationRequired: true,
|
|
VerificationSummary: nonEmpty(verification, "verify result before higher-level work completion"),
|
|
ArtifactRefs: append([]work.EvidenceRef{}, refs...),
|
|
RecordedAtUnixMillis: now.UnixMilli(),
|
|
}
|
|
if err := ValidateAgentResult(result); err != nil {
|
|
return work.AgentRun{}, work.AgentResult{}, err
|
|
}
|
|
return run, result, nil
|
|
}
|
|
|
|
func ValidateAgentResult(value work.AgentResult) error {
|
|
if value.ID == "" || value.DelegationID == "" || value.RunID == "" || value.State == "" ||
|
|
strings.TrimSpace(value.Summary) == "" || value.RecordedAtUnixMillis < 1 {
|
|
return ErrInvalidAgentResult
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ResumeDelegationRun(delegation work.Delegation, previous work.AgentRun, checkpoint work.AgentCheckpoint, now time.Time) (work.Delegation, work.AgentRun, error) {
|
|
if err := ValidateDelegation(delegation); err != nil {
|
|
return work.Delegation{}, work.AgentRun{}, ErrInvalidAgentRun
|
|
}
|
|
if err := ValidateAgentRun(previous); err != nil {
|
|
return work.Delegation{}, work.AgentRun{}, ErrInvalidAgentRun
|
|
}
|
|
if err := ValidateAgentCheckpoint(checkpoint); err != nil || now.IsZero() {
|
|
return work.Delegation{}, work.AgentRun{}, ErrInvalidAgentRun
|
|
}
|
|
if delegation.ID != previous.DelegationID || checkpoint.RunID != previous.ID {
|
|
return work.Delegation{}, work.AgentRun{}, ErrInvalidAgentRun
|
|
}
|
|
if previous.State != work.AgentRunExecutionWindowExhausted && previous.State != work.AgentRunBlocked && previous.State != work.AgentRunCheckpointed {
|
|
return work.Delegation{}, work.AgentRun{}, ErrInvalidAgentRun
|
|
}
|
|
runID, err := ids.New()
|
|
if err != nil {
|
|
return work.Delegation{}, work.AgentRun{}, err
|
|
}
|
|
delegation.State = work.DelegationRunning
|
|
delegation.UpdatedAtUnixMillis = now.UnixMilli()
|
|
delegation.LatestRunID = work.AgentRunID(runID)
|
|
run := work.AgentRun{
|
|
ID: work.AgentRunID(runID),
|
|
DelegationID: delegation.ID,
|
|
ImplementationID: previous.ImplementationID,
|
|
ImplementationRevision: previous.ImplementationRevision,
|
|
Protocol: previous.Protocol,
|
|
State: work.AgentRunStarted,
|
|
DerivedInstructionSummary: summarizeInstruction(checkpoint.NextStep),
|
|
StartedAtUnixMillis: now.UnixMilli(),
|
|
UpdatedAtUnixMillis: now.UnixMilli(),
|
|
}
|
|
if err := ValidateAgentRun(run); err != nil {
|
|
return work.Delegation{}, work.AgentRun{}, err
|
|
}
|
|
return delegation, run, nil
|
|
}
|
|
|
|
func VerifyDelegationResult(delegation work.Delegation, run work.AgentRun, result work.AgentResult, verified bool, summary string, refs []work.EvidenceRef, now time.Time) (work.Delegation, work.DelegationVerification, error) {
|
|
if err := ValidateDelegation(delegation); err != nil {
|
|
return work.Delegation{}, work.DelegationVerification{}, ErrInvalidDelegationVerification
|
|
}
|
|
if err := ValidateAgentRun(run); err != nil {
|
|
return work.Delegation{}, work.DelegationVerification{}, ErrInvalidDelegationVerification
|
|
}
|
|
if err := ValidateAgentResult(result); err != nil || now.IsZero() || strings.TrimSpace(summary) == "" {
|
|
return work.Delegation{}, work.DelegationVerification{}, ErrInvalidDelegationVerification
|
|
}
|
|
verificationID, err := ids.New()
|
|
if err != nil {
|
|
return work.Delegation{}, work.DelegationVerification{}, err
|
|
}
|
|
if verified {
|
|
delegation.State = work.DelegationCompleted
|
|
} else {
|
|
delegation.State = work.DelegationFailed
|
|
}
|
|
delegation.UpdatedAtUnixMillis = now.UnixMilli()
|
|
verification := work.DelegationVerification{
|
|
ID: work.DelegationVerificationID(verificationID),
|
|
DelegationID: delegation.ID,
|
|
RunID: run.ID,
|
|
ResultID: result.ID,
|
|
Verified: verified,
|
|
Summary: strings.TrimSpace(summary),
|
|
EvidenceRefs: append([]work.EvidenceRef{}, refs...),
|
|
RecordedAtUnixMillis: now.UnixMilli(),
|
|
}
|
|
if err := ValidateDelegationVerification(verification); err != nil {
|
|
return work.Delegation{}, work.DelegationVerification{}, err
|
|
}
|
|
return delegation, verification, nil
|
|
}
|
|
|
|
func ValidateDelegationVerification(value work.DelegationVerification) error {
|
|
if value.ID == "" || value.DelegationID == "" || value.RunID == "" || value.ResultID == "" || strings.TrimSpace(value.Summary) == "" || value.RecordedAtUnixMillis < 1 {
|
|
return ErrInvalidDelegationVerification
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func protocolForImplementation(value capability.CapabilityImplementation) work.AgentExecutionProtocol {
|
|
if strings.HasPrefix(string(value.TargetRef), "codex://") {
|
|
return work.AgentProtocolCodexBridgeV1
|
|
}
|
|
return work.AgentProtocolCodexBridgeV1
|
|
}
|
|
|
|
func buildContextSummary(input DelegationRequest) string {
|
|
parts := []string{}
|
|
if input.Goal.ProjectDisplayName != "" {
|
|
parts = append(parts, "project="+input.Goal.ProjectDisplayName)
|
|
}
|
|
parts = append(parts, "human_intent="+strings.TrimSpace(input.OriginalIntentText))
|
|
if text := strings.TrimSpace(input.ContextSummary); text != "" {
|
|
parts = append(parts, "added_context="+text)
|
|
}
|
|
return strings.Join(parts, "; ")
|
|
}
|
|
|
|
func summarizeInstruction(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return "derived execution context prepared from canonical intent, project context, and verification requirements"
|
|
}
|
|
if len(value) > 500 {
|
|
return value[:500]
|
|
}
|
|
return value
|
|
}
|
|
|
|
func nonEmpty(value, fallback string) string {
|
|
if strings.TrimSpace(value) == "" {
|
|
return fallback
|
|
}
|
|
return strings.TrimSpace(value)
|
|
}
|