From be78eaa80f2668f2d62f77e2be179630b2a31771 Mon Sep 17 00:00:00 2001 From: kami Date: Mon, 8 Jun 2026 02:20:45 +0400 Subject: [PATCH] feat(workflow): execution_plan artifact kind + schema + architect prompt Add docs/schemas/execution_plan.json (goal/stages/edges), the architect_freestyle prompt that instructs emitting the execution pipeline as JSON, and register the execution_plan llm-emitted kind in both artifacts.config.toml and sample-config.toml. Validated via ExecutionPlanSchemaValidationTest (minimal plan passes; missing stages rejected). --- .../ExecutionPlanSchemaValidationTest.kt | 42 ++++++++++ docs/sample-config.toml | 5 ++ docs/schemas/execution_plan.json | 45 +++++++++++ examples/workflows/artifacts.config.toml | 5 ++ .../workflows/prompts/architect_freestyle.md | 80 +++++++++++++++++++ 5 files changed, 177 insertions(+) create mode 100644 core/validation/src/test/kotlin/com/correx/core/validation/artifact/ExecutionPlanSchemaValidationTest.kt create mode 100644 docs/schemas/execution_plan.json create mode 100644 examples/workflows/prompts/architect_freestyle.md diff --git a/core/validation/src/test/kotlin/com/correx/core/validation/artifact/ExecutionPlanSchemaValidationTest.kt b/core/validation/src/test/kotlin/com/correx/core/validation/artifact/ExecutionPlanSchemaValidationTest.kt new file mode 100644 index 00000000..aa7def7a --- /dev/null +++ b/core/validation/src/test/kotlin/com/correx/core/validation/artifact/ExecutionPlanSchemaValidationTest.kt @@ -0,0 +1,42 @@ +package com.correx.core.validation.artifact + +import com.correx.core.artifacts.kind.JsonSchema +import com.correx.core.artifacts.kind.JsonSchemaProperty +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class ExecutionPlanSchemaValidationTest { + + private val schema = JsonSchema( + type = "object", + properties = mapOf( + "goal" to JsonSchemaProperty(type = "string"), + "stages" to JsonSchemaProperty(type = "array"), + "edges" to JsonSchemaProperty(type = "array"), + ), + required = listOf("goal", "stages", "edges"), + additionalProperties = true, + ) + + private fun errors(json: String) = + JsonSchemaValidator.validate(schema, Json.parseToJsonElement(json)) + + @Test + fun `minimal valid execution plan passes`() { + val plan = """ + { + "goal": "g", + "stages": [{"id": "s", "prompt": "p", "produces": "x"}], + "edges": [] + } + """.trimIndent() + assertTrue(errors(plan).isEmpty()) + } + + @Test + fun `plan missing stages is rejected`() { + val plan = """{"goal": "g", "edges": []}""" + assertTrue(errors(plan).any { it.contains("stages") }) + } +} diff --git a/docs/sample-config.toml b/docs/sample-config.toml index d1fa2f7d..6d56351c 100644 --- a/docs/sample-config.toml +++ b/docs/sample-config.toml @@ -134,3 +134,8 @@ backend = "in_memory" # or "turbovec" # The bundled examples/workflows/review_loop.toml uses this kind: the reviewer emits a # review_report whose `verdict` field gates the implementer↔reviewer loop via # artifact_field_equals transitions. See docs/schemas/review_report.json for the schema. + +[[artifacts]] +id = "execution_plan" +schema_path = "schemas/execution_plan.json" +llm_emitted = true diff --git a/docs/schemas/execution_plan.json b/docs/schemas/execution_plan.json new file mode 100644 index 00000000..2d5b7e78 --- /dev/null +++ b/docs/schemas/execution_plan.json @@ -0,0 +1,45 @@ +{ + "type": "object", + "properties": { + "goal": { "type": "string" }, + "stages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "role": { "type": "string" }, + "prompt": { "type": "string" }, + "needs": { "type": "array", "items": { "type": "string" } }, + "produces": { "type": "string" }, + "tools": { "type": "array", "items": { "type": "string" } } + }, + "required": ["id", "prompt", "produces"] + } + }, + "edges": { + "type": "array", + "items": { + "type": "object", + "properties": { + "from": { "type": "string" }, + "to": { "type": "string" }, + "condition": { + "type": "object", + "properties": { + "type": { "type": "string" }, + "artifact_id": { "type": "string" }, + "field": { "type": "string" }, + "value": { "type": "string" }, + "operator": { "type": "string" } + }, + "required": ["type"] + } + }, + "required": ["from", "to", "condition"] + } + } + }, + "required": ["goal", "stages", "edges"], + "additionalProperties": true +} diff --git a/examples/workflows/artifacts.config.toml b/examples/workflows/artifacts.config.toml index 473f8810..880270f9 100644 --- a/examples/workflows/artifacts.config.toml +++ b/examples/workflows/artifacts.config.toml @@ -28,3 +28,8 @@ llm_emitted = true id = "review_report" schema_path = "schemas/review_report.json" llm_emitted = true + +[[artifacts]] +id = "execution_plan" +schema_path = "schemas/execution_plan.json" +llm_emitted = true diff --git a/examples/workflows/prompts/architect_freestyle.md b/examples/workflows/prompts/architect_freestyle.md new file mode 100644 index 00000000..5486ecad --- /dev/null +++ b/examples/workflows/prompts/architect_freestyle.md @@ -0,0 +1,80 @@ +# Architect — Freestyle Execution Plan + +You are the architect stage in a freestyle workflow. Your only output is a single JSON +`execution_plan` artifact. Do not write prose design documents; emit the plan and nothing +else. + +## Inputs available to you + +- `analysis` artifact — structured findings from the analyst stage (goal, constraints, + risks, open questions). +- Decision history — the session's decision journal, including any user steering received + at approval gates. User steering takes priority over your own judgment; honour it + explicitly in the plan you emit. + +## What to emit + +Emit a JSON object that validates against the `execution_plan` schema: + +```json +{ + "goal": "", + "stages": [ + { + "id": "", + "role": "", + "prompt": "", + "produces": "", + "needs": [""], + "tools": ["file_read", "file_write", "file_edit", "ShellTool"] + } + ], + "edges": [ + { + "from": "", + "to": "", + "condition": { + "type": "artifact_validated", + "artifact_id": "" + } + } + ] +} +``` + +## Rules + +**goal** — one sentence, derived from the analysis artifact and any user steering. + +**stages** — ordered list; each stage must: +- Have a unique `id` in `snake_case`. +- Declare `produces`: the artifact id this stage emits. Artifact ids must be declared in + your config's `[[artifacts]]` table. +- Declare `needs`: every upstream artifact id the stage's prompt references. Every id in + `needs` must be `produces`d by a strictly earlier stage. +- Include `tools` only for stages that write or edit files: + `["file_read", "file_write", "file_edit", "ShellTool"]`. Do not invent tool names + beyond this set. +- Keep stages small and single-responsibility. Prefer more stages over large monolithic + prompts. + +**edges** — describe every transition between stages. Rules: +- `from` and `to` must each be a declared stage `id` or the literal string `"done"`. +- The normal forward edge uses `"type": "artifact_validated"` with `artifact_id` set to + the `produces` id of the `from` stage. +- For a review loop (implementer ↔ reviewer): emit two conditional edges from the + reviewer stage: + - `"type": "artifact_field_equals"`, `artifact_id`: reviewer's produces id, + `field`: `"verdict"`, `value`: `"approved"`, `operator`: `"eq"` → `to: "done"` + - `"type": "artifact_field_equals"`, `artifact_id`: reviewer's produces id, + `field`: `"verdict"`, `value`: `"approved"`, `operator`: `"neq"` → `to: ""` +- Every stage reachable from `"done"` must have an inbound edge. Every non-terminal stage + must have an outbound edge. + +## Constraints + +- Do not add stages, roles, or tools not justified by the analysis artifact. +- Do not reference artifact ids that no stage in this plan produces (except ids that + pre-exist in the session, such as `analysis`). +- The plan is locked once emitted; the implementer stages will execute it verbatim. Be + precise in each stage's `prompt`.