diff --git a/apps/cli/src/main/kotlin/com/correx/apps/cli/commands/RunCommand.kt b/apps/cli/src/main/kotlin/com/correx/apps/cli/commands/RunCommand.kt index 2b6f4235..b1bac171 100644 --- a/apps/cli/src/main/kotlin/com/correx/apps/cli/commands/RunCommand.kt +++ b/apps/cli/src/main/kotlin/com/correx/apps/cli/commands/RunCommand.kt @@ -97,6 +97,9 @@ class RunCommand : CliktCommand(name = "run") { } } + private fun resolveWorkflowId(path: String): String = + java.nio.file.Path.of(path).fileName.toString().removeSuffix(".toml") + private suspend fun startSession( client: HttpClient, host: String, @@ -105,7 +108,7 @@ class RunCommand : CliktCommand(name = "run") { ): String? = runCatching { val resp = client.post("http://$host:$portInt/sessions") { contentType(ContentType.Application.Json) - setBody(StartSessionRequest(workflowId = workflow, sessionId = sessionId)) + setBody(StartSessionRequest(workflowId = resolveWorkflowId(workflow), sessionId = sessionId)) } resp.body().sessionId }.getOrElse { e -> diff --git a/apps/server/build.gradle b/apps/server/build.gradle index bc14e809..a52b1c47 100644 --- a/apps/server/build.gradle +++ b/apps/server/build.gradle @@ -21,6 +21,7 @@ dependencies { implementation project(':core:artifacts') implementation project(':core:artifacts-store') implementation project(':infrastructure') + implementation project(':infrastructure:artifacts-cas') implementation project(':infrastructure:workflow') implementation project(':infrastructure:persistence') implementation project(':infrastructure:inference') diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/Main.kt b/apps/server/src/main/kotlin/com/correx/apps/server/Main.kt index bb165f83..2b8dc14e 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/Main.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/Main.kt @@ -22,6 +22,7 @@ import com.correx.core.sessions.SessionProjector import com.correx.core.sessions.projections.replay.DefaultEventReplayer import com.correx.core.transitions.evaluation.PromptResolver import com.correx.core.transitions.resolution.DefaultTransitionResolver +import com.correx.core.validation.artifact.ArtifactPayloadValidator import com.correx.core.validation.pipeline.ValidationPipeline import com.correx.apps.server.logging.LoggingEventStore import com.correx.infrastructure.InfrastructureModule @@ -70,7 +71,7 @@ fun main() { transitionResolver = DefaultTransitionResolver { condition, ctx -> condition.evaluate(ctx) }, contextPackBuilder = DefaultContextPackBuilder(DefaultContextCompressor()), inferenceRouter = inferenceRouter, - validationPipeline = ValidationPipeline(validators = emptyList()), + validationPipeline = ValidationPipeline(validators = listOf(ArtifactPayloadValidator(artifactStore))), approvalEngine = DefaultApprovalEngine(), riskAssessor = DefaultRiskAssessor(), promptResolver = promptResolver, diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/routes/SessionRoutes.kt b/apps/server/src/main/kotlin/com/correx/apps/server/routes/SessionRoutes.kt index ad246d4e..45816bd9 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/routes/SessionRoutes.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/routes/SessionRoutes.kt @@ -67,8 +67,11 @@ private fun Route.startSessionRoute(module: ServerModule) { val graph = module.workflowRegistry.find(body.workflowId) ?: return@post call.respond(HttpStatusCode.BadRequest, "Unknown workflowId: ${body.workflowId}") val sessionId: SessionId = TypeId(UUID.randomUUID().toString()) + val config = OrchestrationConfig( + defaultSystemPromptPath = "~/.config/correx/prompts/default_system.md", + ) call.application.launch { - runCatching { module.orchestrator.run(sessionId, graph, OrchestrationConfig()) } + runCatching { module.orchestrator.run(sessionId, graph, config) } .onFailure { ex -> log.error("run failed for session={}: {}", sessionId.value, ex.message, ex) module.eventStore.append( diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt b/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt index 4157a6cd..072bf351 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/ws/GlobalStreamHandler.kt @@ -103,7 +103,10 @@ class GlobalStreamHandler(private val module: ServerModule) { val sessionId: SessionId = TypeId(UUID.randomUUID().toString()) log.info("starting session={} workflow={}", sessionId.value, msg.workflowId) session.launch { - runCatching { module.orchestrator.run(sessionId, graph, OrchestrationConfig()) } + val config = OrchestrationConfig( + defaultSystemPromptPath = "~/.config/correx/prompts/default_system.md", + ) + runCatching { module.orchestrator.run(sessionId, graph, config) } .onFailure { ex -> log.error("run failed for session={}: {}", sessionId.value, ex.message, ex) module.eventStore.append( diff --git a/examples/workflows/healthcheck.toml b/examples/workflows/healthcheck.toml new file mode 100644 index 00000000..8b085c9e --- /dev/null +++ b/examples/workflows/healthcheck.toml @@ -0,0 +1,31 @@ +id = "healthcheck" +start = "write_script" + +[[stages]] +id = "write_script" +prompt = "prompts/healthcheck_write.md" +produces = [{ name = "healthcheck_script", kind = "file_written" }] +allowed_tools = ["file_write"] +token_budget = 4096 +max_retries = 2 + +[[stages]] +id = "execute_script" +prompt = "prompts/healthcheck_execute.md" +needs = ["healthcheck_script"] +produces = [{ name = "healthcheck_result", kind = "process_result" }] +allowed_tools = ["ShellTool"] +token_budget = 2048 + +[[transitions]] +id = "write-to-execute" +from = "write_script" +to = "execute_script" +condition_type = "artifact_validated" +condition_artifact_id = "healthcheck_script" + +[[transitions]] +id = "execute-to-done" +from = "execute_script" +to = "done" +condition_type = "always_true" diff --git a/prompts/healthcheck_write.md b/prompts/healthcheck_write.md index 1d0661d9..0667e139 100644 --- a/prompts/healthcheck_write.md +++ b/prompts/healthcheck_write.md @@ -1,10 +1,8 @@ -# healthcheck_write.md - -Write a bash script to `scripts/healthcheck.sh` that reports: +Write a bash healthcheck script that reports: - RAM usage % - CPU usage % - GPU usage % and VRAM usage % if an NVIDIA or AMD GPU is detected Use standard tools only (free, top/vmstat, nvidia-smi, rocm-smi). If no GPU is detected, skip that section gracefully. -Make the script executable. \ No newline at end of file +Output path: scripts/healthcheck.sh \ No newline at end of file diff --git a/testing/contracts/src/test/kotlin/com/correx/testing/contracts/model/ValidationReportContractTest.kt b/testing/contracts/src/test/kotlin/com/correx/testing/contracts/model/ValidationReportContractTest.kt index 23540436..e200ddcc 100644 --- a/testing/contracts/src/test/kotlin/com/correx/testing/contracts/model/ValidationReportContractTest.kt +++ b/testing/contracts/src/test/kotlin/com/correx/testing/contracts/model/ValidationReportContractTest.kt @@ -4,13 +4,14 @@ import com.correx.core.validation.graph.GraphValidator import com.correx.core.validation.model.ValidationContext import com.correx.core.validation.pipeline.ValidationPipeline import com.correx.testing.fixtures.WorkflowFixtures +import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class ValidationReportContractTest { @Test - fun `report must be deterministic`() { + fun `report must be deterministic`(): Unit = runBlocking { val graph = WorkflowFixtures.simpleGraph() diff --git a/testing/deterministic/src/test/kotlin/GraphValidatorTest.kt b/testing/deterministic/src/test/kotlin/GraphValidatorTest.kt index 2c12dda9..a9289b39 100644 --- a/testing/deterministic/src/test/kotlin/GraphValidatorTest.kt +++ b/testing/deterministic/src/test/kotlin/GraphValidatorTest.kt @@ -9,6 +9,7 @@ import com.correx.core.validation.model.ValidationContext import com.correx.core.validation.transition.TransitionValidator import com.correx.testing.fixtures.CycleFixtures import com.correx.testing.fixtures.WorkflowFixtures +import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -18,7 +19,7 @@ class GraphValidatorTest { private val validator = GraphValidator() @Test - fun `should detect dangling transition`() { + fun `should detect dangling transition`(): Unit = runBlocking { val graph = WorkflowFixtures.simpleGraph().copy( stages = mapOf(StageId("A") to StageConfig()) // B removed → dangling @@ -39,7 +40,7 @@ class GraphValidatorTest { } @Test - fun `should expose cycles as informational only`() { + fun `should expose cycles as informational only`(): Unit = runBlocking { val graph = WorkflowFixtures.simpleGraph() val cycles = listOf(CycleFixtures.simpleCycle()) @@ -66,7 +67,7 @@ class GraphValidatorTest { ) @Test - fun `should be deterministic`() { + fun `should be deterministic`(): Unit = runBlocking { val graph = WorkflowFixtures.simpleGraph() @@ -82,7 +83,7 @@ class GraphValidatorTest { } @Test - fun `should flag non-deterministic order when two edges share same from and to`() { + fun `should flag non-deterministic order when two edges share same from and to`(): Unit = runBlocking { val a = StageId("A") val b = StageId("B") diff --git a/testing/deterministic/src/test/kotlin/ValidationPipelineShortCircuitTest.kt b/testing/deterministic/src/test/kotlin/ValidationPipelineShortCircuitTest.kt index 48ecabaa..a6c57f44 100644 --- a/testing/deterministic/src/test/kotlin/ValidationPipelineShortCircuitTest.kt +++ b/testing/deterministic/src/test/kotlin/ValidationPipelineShortCircuitTest.kt @@ -6,6 +6,7 @@ import com.correx.core.validation.pipeline.ValidationOutcome import com.correx.core.validation.pipeline.ValidationPipeline import com.correx.core.validation.pipeline.Validator import com.correx.testing.fixtures.WorkflowFixtures +import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Test @@ -22,7 +23,7 @@ class ValidationPipelineShortCircuitTest { } @Test - fun `pipeline stops after first validator with errors and report contains only that section`() { + fun `pipeline stops after first validator with errors and report contains only that section`(): Unit = runBlocking { var secondCalled = false val trackingValidator = Validator { _ -> secondCalled = true @@ -38,7 +39,7 @@ class ValidationPipelineShortCircuitTest { } @Test - fun `warnings do not short-circuit the pipeline`() { + fun `warnings do not short-circuit the pipeline`(): Unit = runBlocking { var secondCalled = false val warningValidator = Validator { _ -> diff --git a/testing/integration/src/test/kotlin/ValidationPipelineIntegrationTest.kt b/testing/integration/src/test/kotlin/ValidationPipelineIntegrationTest.kt index 79b291f7..33eb0957 100644 --- a/testing/integration/src/test/kotlin/ValidationPipelineIntegrationTest.kt +++ b/testing/integration/src/test/kotlin/ValidationPipelineIntegrationTest.kt @@ -14,6 +14,7 @@ import com.correx.core.validation.semantic.rules.CyclePolicyBindingRule import com.correx.core.validation.transition.TransitionValidator import com.correx.testing.fixtures.CycleFixtures import com.correx.testing.fixtures.WorkflowFixtures +import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.Test @@ -34,7 +35,7 @@ class ValidationPipelineIntegrationTest { ) @Test - fun `full validation pipeline returns NeedsApproval when cycles are unbound`() { + fun `full validation pipeline returns NeedsApproval when cycles are unbound`(): Unit = runBlocking { val graph = WorkflowFixtures.simpleGraph() @@ -52,7 +53,7 @@ class ValidationPipelineIntegrationTest { } @Test - fun `rejected outcome retryable is false - set by validator, not orchestrator`() { + fun `rejected outcome retryable is false - set by validator, not orchestrator`(): Unit = runBlocking { // A dangling transition triggers GraphValidator ERROR → Rejected(retryable=false). // Ownership rule: the validator sets retryable; the orchestrator must only read it. val a = StageId("A") diff --git a/testing/replay/src/test/kotlin/ValidationReplayTest.kt b/testing/replay/src/test/kotlin/ValidationReplayTest.kt index 148b4e0d..f8a7109d 100644 --- a/testing/replay/src/test/kotlin/ValidationReplayTest.kt +++ b/testing/replay/src/test/kotlin/ValidationReplayTest.kt @@ -4,13 +4,14 @@ import com.correx.core.validation.model.ValidationContext import com.correx.core.validation.pipeline.ValidationPipeline import com.correx.core.validation.transition.TransitionValidator import com.correx.testing.fixtures.WorkflowFixtures +import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test class ValidationReplayTest { @Test - fun `validation must be replay deterministic`() { + fun `validation must be replay deterministic`(): Unit = runBlocking { val graph = WorkflowFixtures.simpleGraph()