feat: correx-managed model lifecycle slice 1 — config + manager factory + boot/shutdown

[[models]] config (ModelConfig/ModelsSettings) parsed by ConfigLoader;
InfrastructureModule.createModelManager + modelConfigToDescriptor; Main.kt
managed boot path spawns the default llama-server when [[models]] is present
(static [[providers]] path preserved when absent) and kills it on shutdown.

Plan: docs/plans/2026-05-31-model-lifecycle-management.md (slice 1 of 5).
This commit is contained in:
2026-06-01 11:03:05 +04:00
parent 5beb866036
commit e45a626cc4
10 changed files with 576 additions and 39 deletions
@@ -47,6 +47,7 @@ import com.correx.core.toolintent.rules.PathContainmentRule
import com.correx.infrastructure.InfrastructureModule
import com.correx.infrastructure.inference.DefaultProviderRegistry
import com.correx.infrastructure.inference.FirstAvailableRoutingStrategy
import com.correx.core.inference.InferenceProvider
import com.correx.infrastructure.inference.llama.cpp.LlamaCppInferenceProvider
import com.correx.infrastructure.tools.FileEditConfig
import com.correx.infrastructure.tools.FileReadConfig
@@ -71,15 +72,48 @@ fun main() {
logStoresInfo(eventStore, artifactStore)
val correxConfig = ConfigLoader.load()
val providers = buildProviders(correxConfig)
require(providers.isNotEmpty()) { "At least one provider must be configured" }
val infraRegistry = InfrastructureModule.createProviderRegistry(providers)
val llamaProvider = providers.first()
val modelId = llamaProvider.id.value
val modelPath = "(from config)"
val llamaUrl = "(from config)"
val eventDispatcher = EventDispatcher(eventStore)
logModelInfo(modelId, modelPath, llamaUrl, infraRegistry)
// Managed path: [[models]] present → correx spawns llama-server at boot
// Static path: [[models]] absent → legacy [[providers]] / env-var behavior
val firstProvider: InferenceProvider
val infraRegistry: DefaultProviderRegistry
if (correxConfig.models.isNotEmpty()) {
val settings = correxConfig.modelsSettings
val modelManager = InfrastructureModule.createModelManager(settings, eventStore, eventDispatcher)
val targetId = settings.defaultModel ?: correxConfig.models.first().id
val targetModelConfig = correxConfig.models.firstOrNull { it.id == targetId }
?: correxConfig.models.first()
log.info("Managed path: loading model '{}' from '{}'", targetModelConfig.id, targetModelConfig.modelPath)
val descriptor = InfrastructureModule.modelConfigToDescriptor(targetModelConfig)
val managedProvider = runBlocking { modelManager.load(descriptor) }
firstProvider = managedProvider
infraRegistry = InfrastructureModule.createProviderRegistry(listOf(managedProvider))
// Also register any static providers from [[providers]] (non-managed remotes)
correxConfig.providers.mapNotNull { providerConfig ->
when (providerConfig.type.lowercase()) {
"llamacpp" -> buildProviderFromConfig(providerConfig)
else -> { log.error("Unknown provider type: {}", providerConfig.type); null }
}
}.forEach { infraRegistry.register(it) }
// Shutdown hook to kill the managed llama-server
Runtime.getRuntime().addShutdownHook(Thread {
log.info("Shutdown: unloading managed model '{}'", targetModelConfig.id)
runBlocking {
runCatching { modelManager.unload(targetModelConfig.id) }
.onFailure { log.warn("Error during managed model unload: {}", it.message) }
}
})
} else {
val staticProviders = buildProviders(correxConfig)
require(staticProviders.isNotEmpty()) { "At least one provider must be configured" }
firstProvider = staticProviders.first()
infraRegistry = InfrastructureModule.createProviderRegistry(staticProviders)
}
val modelId = firstProvider.id.value
logModelInfo(modelId, infraRegistry)
val repositories = buildRepositories(eventStore)
val approvalEngine = DefaultApprovalEngine()
@@ -107,7 +141,7 @@ fun main() {
)
val toolExecutor = InfrastructureModule.createToolExecutor(
registry = toolRegistry,
eventDispatcher = EventDispatcher(eventStore),
eventDispatcher = eventDispatcher,
workDir = sandboxRoot,
artifactStore = artifactStore,
)
@@ -156,7 +190,7 @@ fun main() {
engines = engines,
retryCoordinator = DefaultRetryCoordinator(eventStore),
artifactStore = artifactStore,
tokenizer = llamaProvider.tokenizer,
tokenizer = firstProvider.tokenizer,
)
val defaultOrchestrationConfig = OrchestrationConfig(
sandboxRoot = sandboxRoot,
@@ -173,7 +207,7 @@ fun main() {
eventStore = eventStore,
inferenceRouter = inferenceRouter,
config = RouterConfig(),
tokenizer = llamaProvider.tokenizer,
tokenizer = firstProvider.tokenizer,
embedder = embedder,
l3MemoryStore = l3MemoryStore,
)
@@ -206,14 +240,10 @@ fun main() {
private fun logModelInfo(
modelId: String,
modelPath: String,
llamaUrl: String,
infraRegistry: DefaultProviderRegistry,
) {
log.info("==========MODEL INFO==========")
log.info(" model id : {}", modelId)
log.info(" model path : {}", modelPath)
log.info(" llama url : {}", llamaUrl)
log.info(" providers : {} registered", infraRegistry.listAll().size)
}