internal/app/agent_kernel.go
Rama main · réplica de lectura del repositorio original.
package app
import (
"context"
"log/slog"
"os"
"strings"
"time"
"github.com/yeomyeonggeori/blueclaw/internal/agentruntime"
"github.com/yeomyeonggeori/blueclaw/internal/capability"
"github.com/yeomyeonggeori/blueclaw/internal/config"
"github.com/yeomyeonggeori/blueclaw/internal/harnessdriver"
"github.com/yeomyeonggeori/blueclaw/internal/harnessselection"
"github.com/yeomyeonggeori/blueclaw/internal/llm"
"github.com/yeomyeonggeori/blueclaw/internal/security"
"github.com/yeomyeonggeori/blueclaw/internal/task"
"github.com/yeomyeonggeori/blueprotocol/agentcontract"
"github.com/yeomyeonggeori/blueprotocol/holdrecord"
"github.com/yeomyeonggeori/blueprotocol/model"
"github.com/yeomyeonggeori/blueprotocol/taskstate"
)
type agentKernel struct {
instructionBundleLoader func() agentcontract.InstructionBundle
agentIdentityProvider func() agentcontract.AgentIdentity
languageModelRuntimeConfiguration config.RuntimeConfiguration
taskTierLanguageModels agentcontract.TaskTierLanguageModels
capabilityClient capability.Client
capabilityRegistry *agentruntime.CapabilityRegistry
embeddingClient llm.ConfiguredEmbedder
intakeLanguageModelProvider llm.LanguageModelProvider
decisionModel model.DecisionModel
toolSelector agentcontract.ToolSelector
terminalService *security.ShellService
toolCatalog toolCatalogEndpoint
harness agentcontract.Harness
harnessName string
skillRetriever agentcontract.SkillRetriever
refreshSkillIndex func(context.Context)
startupError error
languageModelError error
}
type BundledToolSelectorFactory func(model.DecisionModel) agentcontract.ToolSelector
type QuestionWorderFactory func(model.LanguageModelProvider) holdrecord.QuestionWorder
func newAgentKernel(runtimeConfiguration config.RuntimeConfiguration, bundledACPFactory harnessdriver.ACPFactory, newBundledToolSelector BundledToolSelectorFactory, newQuestionWorder QuestionWorderFactory, services taskServices, logger *slog.Logger) agentKernel {
logger.Info("application.initializing", "stage", "agent_kernel")
capabilityClient := newCapabilityClient(runtimeConfiguration)
capabilityRegistry := agentruntime.NewCapabilityRegistry(capabilityClient, capabilityToolDescriptors(runtimeConfiguration.Capabilities.ToolDescriptors))
startupInstructions := loadAgentInstructions(runtimeConfiguration, capabilityRegistry)
logSkillsThisHostCannotSatisfy(logger, startupInstructions.UnavailableSkills)
logRejectedPersonaDocuments(logger, startupInstructions.RejectedDocuments)
taskTierLanguageModels, taskModelsError := resolveTaskTierLanguageModelProviders(runtimeConfiguration, logger)
intakeLanguageModel, intakeModelError := resolveIntakeLanguageModelProvider(runtimeConfiguration, logger)
recordTasklessCall := newTasklessLLMCallRecorder(services.repositories.llmCall, logger).observer()
taskTierLanguageModels = observedTaskTierLanguageModels(taskTierLanguageModels, recordTasklessCall)
intakeLanguageModel = observedLanguageModel(intakeLanguageModel, recordTasklessCall)
kernel := agentKernel{
instructionBundleLoader: func() agentcontract.InstructionBundle {
return loadAgentInstructionBundle(runtimeConfiguration, capabilityRegistry)
},
agentIdentityProvider: func() agentcontract.AgentIdentity {
return loadAgentIdentity(runtimeConfiguration)
},
languageModelRuntimeConfiguration: runtimeConfiguration,
taskTierLanguageModels: taskTierLanguageModels,
intakeLanguageModelProvider: intakeLanguageModel,
languageModelError: firstNonNilError(taskModelsError, intakeModelError),
capabilityClient: capabilityClient,
capabilityRegistry: capabilityRegistry,
}
logger.Info("application.initializing", "stage", "skill_retriever")
embeddingProvider, embeddingError := llm.NewConfiguredEmbeddingProvider(runtimeConfiguration)
if embeddingError != nil {
logger.Error("embedding provider configuration failed", "error", embeddingError.Error())
}
kernel.embeddingClient = embeddingProvider
kernel.decisionModel = newConfiguredDecisionModel(runtimeConfiguration, logger)
kernel.toolSelector = newToolSelector(newBundledToolSelector, kernel.decisionModel)
kernel.terminalService = security.NewShellService(runtimeConfiguration.Terminal)
services.taskRunService.RegisterTaskRunTransitionObserver(task.NewTaskTemporaryDirectoryReclaimer(runtimeConfiguration.Terminal.WorkspaceRootPath, kernel.terminalService.WorkspaceActorFactory(), logger).Observe)
kernel.toolCatalog = newToolCatalogEndpoint(services.taskRunService, newQuestionWorder, kernel.taskTierLanguageModels.High, kernel.decisionModel, kernel.capabilityClient)
harnessFactory, harnessName, selectionError := selectAgentHarness(runtimeConfiguration, bundledACPFactory, kernel, logger)
kernel.harnessName = harnessName
kernel.startupError = selectionError
kernel.harness, kernel.skillRetriever = startAgentHarness(runtimeConfiguration, harnessFactory, kernel, services)
kernel.refreshSkillIndex = newSkillIndexRefresher(kernel.skillRetriever, kernel.instructionBundleLoader)
return kernel
}
func newSkillIndexRefresher(skillRetriever agentcontract.SkillRetriever, instructionBundleLoader func() agentcontract.InstructionBundle) func(context.Context) {
return func(ctx context.Context) {
if skillRetriever == nil {
return
}
skillRetriever.Refresh(ctx, instructionBundleLoader().Skills)
}
}
func selectAgentHarness(runtimeConfiguration config.RuntimeConfiguration, bundledACPFactory harnessdriver.ACPFactory, kernel agentKernel, logger *slog.Logger) (harnessdriver.Factory, string, error) {
selectedHarnessFactory, harnessSelectionError := harnessselection.Select(runtimeConfiguration.Agent.Harness, harnessselection.ToolCatalogEndpoint{
URL: toolCatalogURL(runtimeConfiguration),
Resolver: kernel.toolCatalog.resolver,
Handler: kernel.toolCatalog.handler,
ApprovalGate: kernel.toolCatalog.approvalGate,
BridgeCommandPath: currentExecutablePath(),
}, harnessselection.SandboxProcessBoundary{
Runner: kernel.terminalService.WorkspaceActorFactory(),
WorkspaceRootPath: runtimeConfiguration.Terminal.WorkspaceRootPath,
}, harnessselection.WithBundledACPFactory(bundledACPFactory))
selectedHarnessName := strings.TrimSpace(runtimeConfiguration.Agent.Harness.Name)
if selectedHarnessName == "" {
selectedHarnessName = harnessselection.BundledACPHarnessName
}
if harnessSelectionError != nil {
selectedHarnessName = "unavailable"
logger.Error("application.harness.unavailable", "error", harnessSelectionError)
}
if selectedHarnessFactory == nil {
selectedHarnessFactory = func(harnessdriver.Dependencies) (agentcontract.Harness, agentcontract.SkillRetriever) {
return nil, nil
}
}
return selectedHarnessFactory, selectedHarnessName, harnessSelectionError
}
func startAgentHarness(runtimeConfiguration config.RuntimeConfiguration, harnessFactory harnessdriver.Factory, kernel agentKernel, services taskServices) (agentcontract.Harness, agentcontract.SkillRetriever) {
return harnessFactory(harnessdriver.Dependencies{
RuntimeConfiguration: runtimeConfiguration,
TaskRunStore: services.taskRunService,
InstructionBundleLoader: kernel.instructionBundleLoader,
EmbeddingProvider: kernel.embeddingClient,
EmbeddingModelName: runtimeConfiguration.LanguageModel.Embedding.Model,
SkillIndexPath: skillIndexPath(runtimeConfiguration),
TaskTierLanguageModels: kernel.taskTierLanguageModels,
IntakeLanguageModelProvider: kernel.intakeLanguageModelProvider,
DecisionModel: kernel.decisionModel,
LLMCallRepository: llmCallRepositoryOf(services),
})
}
func llmCallRepositoryOf(services taskServices) taskstate.LLMCallRepository {
if services.repositories.llmCall == nil {
return nil
}
return services.repositories.llmCall
}
func newCapabilityClient(runtimeConfiguration config.RuntimeConfiguration) capability.Client {
return capability.NewClient(capabilityConfiguration(runtimeConfiguration))
}
func capabilityConfiguration(runtimeConfiguration config.RuntimeConfiguration) capability.Configuration {
return capability.Configuration{
Endpoint: runtimeConfiguration.Capabilities.Endpoint,
UnixSocketPath: runtimeConfiguration.Capabilities.UnixSocketPath,
Timeout: time.Duration(runtimeConfiguration.Capabilities.TimeoutSecond) * time.Second,
}
}
func currentExecutablePath() string {
executablePath, errorValue := os.Executable()
if errorValue != nil {
return ""
}
return executablePath
}