@x12i/graphenix-plan-format
v2.7.1
Published
ExecutableGraphPlan format: plan schema validation and canonicalization.
Readme
@x12i/graphenix-plan-format
ExecutableGraphPlan format — validates the engine-facing artifact produced by @x12i/graphenix-plan-compiler.
Defines and checks what a correct compiled plan looks like: resolved phase model profiles, ordered execution units per run phase, finalizer plans, embedded normalized graph snapshot, and forbidden runtime fields.
This package does not compile from authoring graphs — it only validates plan documents. Use @x12i/graphenix-plan-compiler to produce plans.
Canonical vocabulary: GLOSSARY.md at monorepo root.
Finalization brief: COMPILE-FORMAT-FINALIZATION.md · execute/trace: EXECUTE-TRACE-FORMAT-FINALIZATION.md.
When task run phases or plan-phase terms change, update this README and GLOSSARY.md §5 together.
Who should use this
| Role | Use this package? |
| ---- | ----------------- |
| Execution engine | Yes — validate plan before run |
| Compiler / execution prep | Yes — validate output before handoff |
| Run history / observability | Yes — bind traces to validated plan shape |
| Graph designer / Studio | Only if showing compile preview |
| Authoring-only services | No — use @x12i/graphenix-authoring-format |
Install
npm install @x12i/graphenix-plan-formatTypes (ExecutableGraphPlan, ExecutableGraphPlanV2, ExecutionUnitPlanV2, …) live in @x12i/graphenix-executable-contracts.
Lifecycle position
AuthoringGraphDocument + GraphRuntimeObject
│ compileExecutablePlan() (@x12i/graphenix-plan-compiler)
▼
ExecutableGraphPlan ← THIS PACKAGE validates
│ executeGraph()
▼
GraphExecutionTrace (@x12i/graphenix-trace-format)The engine consumes only the plan (+ runtime binding). It never reads authoring graphs or Studio shadow documents directly.
Task run phases (runtime topology)
One task node invocation is three run phases (GLOSSARY §1):
| Run phase | Meaning | Typical plan units |
| --------- | ------- | ------------------ |
| prePhase | Optional utility work before the skill | externalPreUtility (v2) / preAction (v1) |
| mainPhase | Skill / MAIN execution | mainSkill, pipelinePhase, localSkill (v2) / skill (v1) |
| postPhase | Optional utility work after the skill | externalPostUtility (v2) / postAction (v1) |
This is phase topology — not model choice and not catalog action ids.
Design → plan mapping (do not conflate)
Authoring declares phase utility strategies on the task node. The compiler expands them into plan execution units:
prePhase.strategyKey (design: aiTaskStrategies.pre)
│ compile
▼
PRE-phase execution units (plan: externalPreUtility / preAction)
mainPhase.executionStrategies + skill input synthesis profile (design)
│ compile
▼
MAIN-phase execution units (plan: mainSkill, pipelinePhase, …)
postPhase.strategyKey (design: aiTaskStrategies.post)
│ compile
▼
POST-phase execution units (plan: externalPostUtility / postAction)| Design (authoring) | Plan (this package) | Glossary |
| ------------------ | ------------------- | -------- |
| aiTaskStrategies.pre | PRE-phase units with strategyKey / actionKey | PRE-phase utility strategy → plan PRE-phase action list |
| executionStrategies, aiTaskProfile.inputSynthesis | mainSkill, pipelinePhase, … | MAIN-phase wrappers / skill input synthesis profile |
| aiTaskStrategies.post | POST-phase units | POST-phase utility strategy → plan POST-phase action list |
| preActions[] / postActions[] (optional on design) | Same shape at plan layer | Plan-phase action lists — not synonyms for aiTaskStrategies.pre |
Explicit rule: strategyKey: "synthesis" (PRE-phase utility) ≠ inputSynthesis.enabled (MAIN skill input synthesis profile). The plan may contain pipelinePhase units from the latter without any PRE-phase utility unit.
PRE synthesis (2.4.0): inputsConfig and smartInput.paths are MAIN only. PRE invokeContract.reads / unitParams.memoryPaths come from aiTaskProfile.inputSynthesis.sources or are empty ([]) for runtime default trio. When sources is authored, reads and memoryPaths must be identical.
Each execution unit carries a phase model profile binding (GLOSSARY §4):
| modelSlot | Run phase |
| ----------- | --------- |
| preActionModel | PRE-phase model profile |
| skillModel | MAIN-phase model profile |
| postActionModel | POST-phase model profile |
| finalizerModel | Finalizer (graph output) |
Utility strategy = what runs. Model profile = which AI profile runs it.
Plan document shape
v1 — graphenix.executable-plan/v1
ExecutableGraphPlan
├─ format, planId, createdAt
├─ source (graphId, graphHash, …)
├─ profileRegistry (@x12i/ai-profiles version)
├─ runtimeBinding (jobId, mode, environment, hashes)
├─ graph — embedded NormalizedExecutableGraphDocument
├─ caseSelection — graph + per-node selected cases
├─ nodePlans[nodeId]
│ ├─ selectedCases, modelSlots (preActionModel / skillModel / postActionModel)
│ └─ executionUnits[] — ordered preAction → skill → postAction
└─ finalizerPlans (if applicable)v1 unit kinds: preAction | skill | postAction | finalizer.
v2 — graphenix.executable-plan/v2 (preferred for new engines)
ExecutableGraphPlanV2
├─ format, planId, createdAt, planHash
├─ source, profileRegistry, runtimeBinding, runtimeExpectations
├─ normalizedGraph — embedded or ref
├─ policies (directModelPolicy, fallback, trace, …)
├─ nodeIndex[], deferredGates[]
├─ nodePlans[nodeId] — NodeExecutionPlan
│ ├─ invokeContract (pipeline snapshot from authoring)
│ ├─ modelSlots, selectedCases
│ └─ executionUnits[] — ordered by run phase
└─ finalizerPlans[nodeId] — FinalizerExecutionPlanv2 unit kinds include:
| unitKind | Typical run phase | invokeMode |
| ---------- | ----------------- | ------------ |
| externalPreUtility | prePhase | externalRunTask |
| mainSkill | mainPhase | mainRunTask |
| pipelinePhase | mainPhase (wrapper / synthesis inside MAIN) | pipelinePhase |
| localSkill | mainPhase | local |
| externalPostUtility | postPhase | externalRunTask |
| finalizer | graph output | finalizer |
| validation, mapping, callGraph | special | none / … |
Each ExecutionUnitPlanV2 exposes: unitId, nodeId, unitKind, invokeMode, order, actionKey, strategyKey, skillKey, modelSlot, modelSelection, modelSource, invokeContract, retry/fallback/trace policies, and sourcePath (JSON pointer into embedded graph).
Per-task node plan (diagram)
NodeExecutionPlan (one task node, one runTask wave)
│
├─ prePhase units[] externalPreUtility / preAction
│ modelSlot: preActionModel
│
├─ mainPhase units[] mainSkill | pipelinePhase | localSkill
│ modelSlot: skillModel (pipelinePhase may share MAIN profile)
│
└─ postPhase units[] externalPostUtility / postAction
modelSlot: postActionModelOrder is strict: PRE units → MAIN units → POST units. Validators reject missing MAIN skill, duplicate orders, and slot/kind mismatches.
Examples (JSON)
v2 plan header (minimal)
{
"format": "graphenix.executable-plan/v2",
"planId": "plan:abc123",
"createdAt": "2026-06-06T12:00:00.000Z",
"planHash": "sha256:…",
"source": {
"graphId": "graph:content-pipeline",
"graphRevision": "1.0.0",
"graphHash": "sha256:…",
"formatVersion": "2.0.0"
},
"profileRegistry": {
"package": "@x12i/ai-profiles",
"version": "3.2.0",
"registryHash": "sha256:…"
},
"runtimeBinding": {
"jobId": "job-001",
"mode": "live",
"environment": "prod",
"sourceGraphHash": "sha256:…",
"profileRegistryHash": "sha256:…"
}
}v2 nodePlans — PRE + MAIN execution units (content pipeline)
Compiled from authoring aiTaskStrategies.pre: "synthesis" + executionStrategies: [] on node:audience-insights. No POST units unless design declares aiTaskStrategies.post:
"nodePlans": {
"node:audience-insights": {
"nodeId": "node:audience-insights",
"nodeType": "task",
"executionClass": "aiTask",
"skillKey": "professional-answer",
"selectedCases": {
"graphModelCaseId": "default"
},
"modelSlots": {
"preActionModel": {
"selection": { "kind": "profileChoice", "key": "cheap/default" },
"source": "graphCase"
},
"skillModel": {
"selection": { "kind": "profileChoice", "key": "vol/default" },
"source": "graphCase"
}
},
"executionUnits": [
{
"unitId": "unit:node:audience-insights:pre:0",
"nodeId": "node:audience-insights",
"unitKind": "externalPreUtility",
"invokeMode": "externalRunTask",
"order": 0,
"strategyKey": "synthesis",
"actionKey": "synthesis",
"modelSlot": "preActionModel",
"modelSelection": { "kind": "profileChoice", "key": "cheap/default" },
"sourcePath": "/graph/nodes/node:audience-insights/parameters/taskConfiguration/aiTaskStrategies/pre"
},
{
"unitId": "unit:node:audience-insights:main:1",
"nodeId": "node:audience-insights",
"unitKind": "mainSkill",
"invokeMode": "mainRunTask",
"order": 1,
"skillKey": "professional-answer",
"modelSlot": "skillModel",
"modelSelection": { "kind": "profileChoice", "key": "vol/default" },
"sourcePath": "/graph/nodes/node:audience-insights/parameters/skillKey"
}
],
"sourcePath": "/graph/nodes/node:audience-insights"
}
}v2 nodePlans — PRE / MAIN / POST
When authoring declares aiTaskStrategies.post, the compiler appends POST-phase units (same mapping as PRE via externalPostUtility).
v2 MAIN pipelinePhase from skill input synthesis profile (not PRE utility)
When authoring uses aiTaskProfile.inputSynthesis.enabled (not aiTaskStrategies.pre):
{
"unitId": "unit:node:task:pipeline:0",
"unitKind": "pipelinePhase",
"invokeMode": "pipelinePhase",
"order": 0,
"modelSlot": "skillModel",
"sourcePath": "/graph/nodes/node:task/parameters/taskConfiguration/aiTaskProfile/inputSynthesis"
}v1 plan unit shape (legacy engines)
"executionUnits": [
{
"unitId": "unit:1:pre",
"unitKind": "preAction",
"order": 0,
"actionKey": "synthesis",
"modelSlot": "preActionModel",
"modelSelection": { "kind": "profileChoice", "key": "cheap/default" }
},
{
"unitId": "unit:1:skill",
"unitKind": "skill",
"order": 1,
"skillKey": "professional-answer",
"modelSlot": "skillModel",
"modelSelection": { "kind": "profileChoice", "key": "vol/default" }
}
]Example (API)
import {
validateExecutablePlan,
validateExecutablePlanV1,
validateExecutablePlanV2
} from "@x12i/graphenix-plan-format";
import type { ExecutableGraphPlan } from "@x12i/graphenix-executable-contracts";
const result = validateExecutablePlan(plan);
if (!result.valid) {
console.error(result.errors.map((e) => `${e.path}: ${e.message}`));
}
// Or target a specific format version:
validateExecutablePlanV2(planV2);
validateExecutablePlanV1(planV1);validateExecutablePlan() auto-selects v1 vs v2 from plan.format.
Typical compile + validate flow:
import { compileExecutablePlan } from "@x12i/graphenix-plan-compiler";
import { validateExecutablePlan } from "@x12i/graphenix-plan-format";
const plan = compileExecutablePlan(authoringGraph, runtime, {
profileRegistry: { package: "@x12i/ai-profiles", version: "3.2.0" }
});
const validation = validateExecutablePlan(plan);
if (!validation.valid) throw new Error("Invalid plan");Key exports
| API | Purpose |
| --- | ------- |
| validateExecutablePlan(plan) | Validate v1 or v2 (detects plan.format) |
| validateExecutablePlanV1(plan) | Validate graphenix.executable-plan/v1 only |
| validateExecutablePlanV2(plan) | Validate graphenix.executable-plan/v2 only |
Types and constants are imported from @x12i/graphenix-executable-contracts:
| Symbol | Value / role |
| ------ | ------------ |
| EXECUTABLE_PLAN_FORMAT | "graphenix.executable-plan/v1" |
| EXECUTABLE_PLAN_FORMAT_V2 | "graphenix.executable-plan/v2" |
| ExecutableGraphPlan | v1 plan document |
| ExecutableGraphPlanV2 | v2 plan document |
| NodeExecutionPlan | Per-node v2 plan with executionUnits |
| ExecutionUnitPlanV2 | Single v2 execution unit |
| ExecutionUnitPlan | v1 unit (deprecated for new work) |
What validation enforces
- Plan metadata:
format,planId,createdAt, source graph refs, profile registry ref - Embedded graph is normalized (
validateNormalizedExecutableGraphvia authoring-format) - Case selection frozen on plan — graph + node cases with explanations
- Model slots resolved per node;
profileChoicekeys well-formed; direct model policy (v2) - Execution units: ordering, unique ids, kind ↔
modelSlotalignment, required MAIN skill - Optional fact-guard on sub-action
unitParams(synthesis,polish,audit);enabled: truerequirespolicyId;mainSkillforbidden — see GLOSSARY §6 - Forbidden fields never on plan: credentials, trace events, provider responses, execution logs, …
- v2:
invokeModematchesunitKind, invoke contracts, deferred gates, finalizer plans
Validation errors use glossary-friendly paths (e.g. PRE/MAIN/POST phase model slots). Do not label plan units as “Synthesis PRE” — use PRE-phase utility strategy vs skill input synthesis profile per GLOSSARY.md.
Dependencies
| Package | Why |
| ------- | --- |
| @x12i/graphenix-executable-contracts | Plan and unit types, error codes |
| @x12i/graphenix-authoring-format | Validates embedded normalized authoring graph |
Peer (via compiler, not required at runtime for validation alone): @x12i/graphenix-core.
Related packages
| Package | Role |
| ------- | ---- |
| GLOSSARY.md | Canonical phase + strategy + model vocabulary |
| @x12i/graphenix-plan-compiler | Produces plans from authoring + runtime |
| @x12i/graphenix-authoring-format | Design authoring; phase utility strategies |
| @x12i/graphenix-task-node-format | Task-node body validation (design layer) |
| @x12i/graphenix-executable-profile-format | Phase model profiles on authoring graph |
| @x12i/graphenix-trace-format | Run evidence bound to a validated plan |
| Compiler role guide | End-to-end compile client guide |
README sync policy
When documenting or changing task run phases, update these together:
| Layer | Package README | | ----- | -------------- | | Design task body + utility strategies | task-node-format/README.md | | Phase model profiles (authoring) | executable-profile-format/README.md | | Compiled plan + execution units | this file — include example JSON | | Compile pipeline | plan-compiler/README.md | | Terms of record | GLOSSARY.md |
