package runtime import ( "context" "github.com/krow/krow-backend/go-api/internal/authctx" "github.com/krow/krow-backend/go-api/internal/repo" ) // AgentExecutor is the boundary interface for executing an authored agent. type AgentExecutor interface { ExecuteAgent(ctx context.Context, agent *Agent, input ExecutionInput) (*ExecutionResult, error) } // SkillExecutor is the boundary interface for executing an authored skill. type SkillExecutor interface { ExecuteSkill(ctx context.Context, skill *Skill, input ExecutionInput) (*ExecutionResult, error) } // UnavailableExecutor is the Phase 4F default stub executor that explicitly refuses execution // until real AI / Owliver / LangGraph executors are installed in Phase 5. type UnavailableExecutor struct{} // ExecuteAgent implements AgentExecutor by returning ErrExecutorUnavailable. func (u *UnavailableExecutor) ExecuteAgent(_ context.Context, agent *Agent, _ ExecutionInput) (*ExecutionResult, error) { skillIDs := make([]string, len(agent.ResolvedSkills)) for i, s := range agent.ResolvedSkills { skillIDs[i] = s.ID } return &ExecutionResult{ Success: false, AgentID: agent.ID, AgentVersion: agent.Version, ResolvedSkills: skillIDs, Error: ErrExecutorUnavailable, }, ErrExecutorUnavailable } // ExecuteSkill implements SkillExecutor by returning ErrExecutorUnavailable. func (u *UnavailableExecutor) ExecuteSkill(_ context.Context, skill *Skill, _ ExecutionInput) (*ExecutionResult, error) { return &ExecutionResult{ Success: false, Error: ErrExecutorUnavailable, }, ErrExecutorUnavailable } // Engine coordinates runtime loading, eligibility checks, dependency resolution and execution. type Engine struct { Loader *Loader AgentExec AgentExecutor SkillExec SkillExecutor } // Option configures the runtime engine. type Option func(*Engine) // WithAgentExecutor overrides the agent executor implementation. func WithAgentExecutor(exec AgentExecutor) Option { return func(e *Engine) { if exec != nil { e.AgentExec = exec } } } // WithSkillExecutor overrides the skill executor implementation. func WithSkillExecutor(exec SkillExecutor) Option { return func(e *Engine) { if exec != nil { e.SkillExec = exec } } } // NewEngine builds a runtime engine over a database querier. func NewEngine(db repo.Querier, opts ...Option) *Engine { e := &Engine{ Loader: NewLoader(db), AgentExec: &UnavailableExecutor{}, SkillExec: &UnavailableExecutor{}, } for _, opt := range opts { opt(e) } return e } // RunAgent loads an executable agent with dependencies and dispatches to the executor boundary. func (e *Engine) RunAgent(ctx context.Context, ident authctx.Identity, idOrDefID string, input ExecutionInput) (*ExecutionResult, error) { agent, err := e.Loader.LoadExecutableAgent(ctx, ident, idOrDefID) if err != nil { return &ExecutionResult{ Success: false, Error: err, }, err } res, err := e.AgentExec.ExecuteAgent(ctx, agent, input) if res == nil { res = &ExecutionResult{ Success: err == nil, AgentID: agent.ID, AgentVersion: agent.Version, Error: err, } } return res, err } // RunSkill loads an executable skill and dispatches to the executor boundary. func (e *Engine) RunSkill(ctx context.Context, ident authctx.Identity, idOrDefID string, input ExecutionInput) (*ExecutionResult, error) { skill, err := e.Loader.LoadExecutableSkill(ctx, ident, idOrDefID) if err != nil { return &ExecutionResult{ Success: false, Error: err, }, err } res, err := e.SkillExec.ExecuteSkill(ctx, skill, input) if res == nil { res = &ExecutionResult{ Success: err == nil, Error: err, } } return res, err }