Design: define full-lifecycle PhasePreset and runtime spec schemas
Parent: #167
Purpose
Define the concrete schema contract for the full-lifecycle Strong PhasePreset system described in #167.
#167 should remain the structural roadmap. This issue owns the implementation-facing schema details for:
PhasePreset
GateSpec
EvidenceSpec
BranchSpec
ArtifactSpec
PolicyHintSpec
BlockerMessageSpec
PresetInputSpec
PresetOutputSpec
Structural decisions inherited from #167
- PhasePreset is strong by default.
- PhasePreset catalog is full-lifecycle: Intake, Objective, Policy, Execute, Evaluate, Integrate, Learn, Close.
- MVP implementation remains bounded at DAG runtime Phase 3.
- Learn follows Integrate because reward/calibration must include integration outcome, conflicts, cleanup/retention, and final evidence.
- RunObjective can strengthen PhasePreset requirements but cannot weaken preset contracts or hard runtime invariants.
- Gate failure is fail-closed and deny-without-mutation unless an explicit recovery transition records the repair path.
onFail remains a coarse failure class; concrete recovery behavior lives in BranchSpec.
- Policy hints are advisory only; actual strategy choice is recorded by PolicySelection events.
- Runtime state follows Index-first Runtime State: operational state is an index, not the payload store.
Index-first Runtime State
Runtime state objects should be small operational indexes, not payload stores.
Applies to:
RunState
PhaseStatus
TaskGraph
RuntimeTask
WorkerState
ClaimLease
SideEffectRecord
Rules:
- Inline only the state required for current runtime decisions.
- Keep large payloads, logs, reports, diffs, test output, evaluation bodies, and worker output out of operational state.
- Link details through
ArtifactRef, EvidenceRef, event ids, or external refs.
- Prefer
version, status, refs, blockers, and timestamps over embedded documents.
- State objects must be compact enough for version checks, replay/debug, and operator inspection.
Operational state = index
ArtifactStore / EvidenceStore / EventStore = payload and history
Examples:
- logs do not live in WorkerState;
- test output does not live in RuntimeTask;
- diffs do not live in RunState;
- evaluation body does not live in PhaseStatus.
MVP minimal shapes:
type RunState = {
runId: string;
version: number;
status: "draft" | "active" | "blocked" | "completed" | "aborted";
currentPhase: RunPhase;
phaseStatus: Record<RunPhase, PhaseStatus>;
objectiveRef?: ArtifactRef;
policySelectionRef?: ArtifactRef;
taskGraphRef?: ArtifactRef;
evidenceRefs: EvidenceRef[];
artifactRefs: ArtifactRef[];
blockers: Blocker[];
sideEffects: SideEffectRecord[];
createdAt: string;
updatedAt: string;
sealedAt?: string;
};
type PhaseStatus = {
status: "not_started" | "running" | "blocked" | "completed" | "skipped" | "failed";
startedAt?: string;
completedAt?: string;
blockedAt?: string;
resultRef?: ArtifactRef;
evidenceRefs: EvidenceRef[];
blockerRefs: string[];
};
type TaskGraph = {
graphId: string;
version: number;
tasks: RuntimeTask[];
createdAt: string;
updatedAt: string;
};
type RuntimeTask = {
taskId: string;
title: string;
status:
| "pending"
| "ready"
| "claimed"
| "in_progress"
| "verifying"
| "completed"
| "blocked"
| "failed"
| "repair_required";
dependsOn: string[];
claimRef?: string;
evidenceRefs: EvidenceRef[];
resultRef?: ArtifactRef;
blockerRefs: string[];
createdAt: string;
updatedAt: string;
};
type WorkerState = {
workerId: string;
status:
| "ready"
| "busy"
| "unhealthy"
| "completed_retained"
| "safe_to_close"
| "stale_registry"
| "cleanup_released"
| "closed";
currentTaskId?: string;
leaseRef?: string;
heartbeatAt?: string;
reportRefs: ArtifactRef[];
evidenceRefs: EvidenceRef[];
createdAt: string;
updatedAt: string;
};
type ClaimLease = {
claimId: string;
taskId: string;
workerId: string;
status: "active" | "renewed" | "expired" | "released" | "recovered";
tokenRef: string;
expiresAt: string;
createdAt: string;
updatedAt: string;
};
type SideEffectRecord = {
id: string;
requestRef: ArtifactRef;
status: "planned" | "running" | "succeeded" | "failed" | "cancelled";
resultRef?: ArtifactRef;
eventRefs: string[];
createdAt: string;
updatedAt: string;
};
RuntimeRef and URI rules
All operational state references use a RuntimeRef-compatible shape.
type RuntimeRef = {
refId: string;
kind: "artifact" | "evidence" | "event" | "external";
uri: string;
producedBy?: RunPhase | "worker" | "runtime" | "human" | "external";
createdAt: string;
};
type ArtifactRef = RuntimeRef & {
kind: "artifact";
};
type EventRef = RuntimeRef & {
kind: "event";
};
type ExternalRef = RuntimeRef & {
kind: "external";
};
type EvidenceRef = RuntimeRef & {
kind: "evidence";
freshness?: "current_run" | "current_phase" | "after_last_change";
verifier?: string;
};
Rules:
refId is the runtime-local identity.
uri points to payload location or external resource.
- operational state stores refs, not payload bodies.
- PhaseEngine and RunState must not manually dereference URI strings.
MVP URI schemes:
artifact://<runId>/<artifactId>
evidence://<runId>/<evidenceId>
event://<runId>/<eventId>
external://<provider>/<resourceType>/<resourceId>
file://<relative-or-absolute-path>
Examples:
artifact://run-123/objective
artifact://run-123/task-graph
evidence://run-123/test-result-001
event://run-123/evt-00042
external://github/issue/167
external://github/comment/123456
file://.ilchul/runs/run-123/artifacts/final-report.md
RefResolver is an MVP store utility, not a standalone engine.
Responsibilities:
- RuntimeRef -> payload lookup;
- URI scheme validation;
- artifact/evidence/event/external/file resolution;
- missing ref detection;
- stale ref detection where metadata allows.
RuntimeRef is stored.
RefResolver resolves.
PhaseEngine does not dereference by hand.
PhasePreset base schema
interface PhasePreset {
id: string;
phase: "intake" | "objective" | "policy" | "execute" | "evaluate" | "integrate" | "learn" | "close";
requiredInputs: PresetInputSpec[];
producedOutputs: PresetOutputSpec[];
gates: GateSpec[];
requiredArtifacts: ArtifactSpec[];
requiredEvidence: EvidenceSpec[];
policyHints: PolicyHintSpec[];
allowedBranches: BranchSpec[];
blockerMessages: BlockerMessageSpec[];
extensions?: Record<string, unknown>;
}
Design rule:
The common PhasePreset schema is runtime-owned and stable.
Phase-specific behavior extends the schema; it does not redefine the protocol.
GateSpec
interface GateSpec {
id: string;
layer: "hard-invariant" | "phase-preset" | "run-objective";
scope: "run" | "phase" | "task" | "worker" | "artifact";
condition: string;
requiredEvidence: EvidenceSpec[];
onFail: "deny" | "block" | "repair_required" | "human_decision_required";
recoveryBranches?: BranchSpec[];
blockerMessage: string;
}
onFail classifies why a gate stopped. recoveryBranches describes how the runtime may recover.
Implementation may add evaluatorRef or conditionRef later, but must not remove the fixed base fields.
EvidenceSpec
interface EvidenceSpec {
id: string;
kind:
| "test_result"
| "typecheck_result"
| "lint_result"
| "artifact"
| "diff"
| "worker_report"
| "runtime_event"
| "human_approval"
| "external_check";
required: boolean;
producer:
| "runtime"
| "worker"
| "verifier"
| "human"
| "external";
freshness: "current_run" | "current_phase" | "after_last_change";
verifier?: string;
}
Evidence is not an agent claim. It is an observable record that can justify a state transition.
BranchSpec
interface BranchSpec {
id: string;
kind:
| "retry"
| "renew_lease"
| "recover_stale_worker"
| "supersede_task"
| "spawn_repair_task"
| "rollback_integration"
| "request_human_override"
| "abort_run";
allowedFrom: Array<"run" | "phase" | "task" | "worker" | "artifact">;
requiresEvidence: EvidenceSpec[];
maxAttempts?: number;
createsTask?: boolean;
requiresHuman?: boolean;
}
abort_run is included because recovery is not always the correct answer.
ArtifactSpec
interface ArtifactSpec {
id: string;
kind:
| "run_contract"
| "run_objective"
| "policy_selection"
| "task_graph"
| "worker_report"
| "diff"
| "test_output"
| "evaluation_result"
| "integration_candidate"
| "reward_record"
| "final_report";
required: boolean;
producedBy:
| "intake"
| "objective"
| "policy"
| "execute"
| "evaluate"
| "integrate"
| "learn"
| "close"
| "worker"
| "verifier"
| "human";
storage:
| "run_state"
| "event_log"
| "artifact_file"
| "external_ref";
retention: "ephemeral" | "run_lifetime" | "audit";
}
ArtifactSpec defines what the runtime must produce or retain. EvidenceSpec defines what can justify a gate transition.
PolicyHintSpec
interface PolicyHintSpec {
id: string;
dimension:
| "worker_count"
| "agent_mix"
| "decomposition_strategy"
| "scheduler_mode"
| "isolation_mode"
| "verification_depth"
| "repair_budget"
| "integration_mode"
| "exploration_rate";
value: string | number | boolean;
strength: "weak" | "normal" | "strong";
source:
| "phase_preset"
| "run_objective"
| "policy_simulation"
| "reward_ledger"
| "human";
advisory: true;
}
PolicyHintSpec is advisory only. PolicySelection events must record actual strategy choice, rationale, and prediction id.
BlockerMessageSpec
interface BlockerMessageSpec {
id: string;
severity: "info" | "warning" | "blocking" | "fatal";
audience: "operator" | "worker" | "verifier" | "human";
message: string;
suggestedAction?: string;
relatedGateId?: string;
relatedBranchId?: string;
}
BlockerMessageSpec is a readiness/recovery contract, not just UX copy.
PresetInputSpec and PresetOutputSpec
interface PresetInputSpec {
id: string;
kind:
| "run_contract"
| "run_objective"
| "policy_selection"
| "task_graph"
| "worker_registry"
| "evidence_ref"
| "artifact_ref"
| "human_answer"
| "external_ref";
required: boolean;
freshness?: "current_run" | "current_phase" | "after_last_change";
sourcePhase?: "intake" | "objective" | "policy" | "execute" | "evaluate" | "integrate" | "learn" | "close";
}
interface PresetOutputSpec {
id: string;
kind:
| "run_contract"
| "run_objective"
| "policy_selection"
| "task_graph"
| "worker_registry"
| "evidence_ref"
| "artifact_ref"
| "evaluation_result"
| "integration_candidate"
| "reward_record"
| "final_report";
required: boolean;
targetPhase?: "objective" | "policy" | "execute" | "evaluate" | "integrate" | "learn" | "close";
}
These specs define explicit phase-to-phase contracts and prevent hidden phase coupling.
Acceptance criteria
Design: define full-lifecycle PhasePreset and runtime spec schemas
Parent: #167
Purpose
Define the concrete schema contract for the full-lifecycle Strong PhasePreset system described in #167.
#167 should remain the structural roadmap. This issue owns the implementation-facing schema details for:
PhasePresetGateSpecEvidenceSpecBranchSpecArtifactSpecPolicyHintSpecBlockerMessageSpecPresetInputSpecPresetOutputSpecStructural decisions inherited from #167
onFailremains a coarse failure class; concrete recovery behavior lives in BranchSpec.Index-first Runtime State
Runtime state objects should be small operational indexes, not payload stores.
Applies to:
RunStatePhaseStatusTaskGraphRuntimeTaskWorkerStateClaimLeaseSideEffectRecordRules:
ArtifactRef,EvidenceRef, event ids, or external refs.version,status, refs, blockers, and timestamps over embedded documents.Examples:
MVP minimal shapes:
RuntimeRef and URI rules
All operational state references use a RuntimeRef-compatible shape.
Rules:
refIdis the runtime-local identity.uripoints to payload location or external resource.MVP URI schemes:
Examples:
RefResolveris an MVP store utility, not a standalone engine.Responsibilities:
PhasePreset base schema
Design rule:
GateSpec
onFailclassifies why a gate stopped.recoveryBranchesdescribes how the runtime may recover.Implementation may add
evaluatorReforconditionReflater, but must not remove the fixed base fields.EvidenceSpec
Evidence is not an agent claim. It is an observable record that can justify a state transition.
BranchSpec
abort_runis included because recovery is not always the correct answer.ArtifactSpec
ArtifactSpec defines what the runtime must produce or retain. EvidenceSpec defines what can justify a gate transition.
PolicyHintSpec
PolicyHintSpec is advisory only. PolicySelection events must record actual strategy choice, rationale, and prediction id.
BlockerMessageSpec
BlockerMessageSpec is a readiness/recovery contract, not just UX copy.
PresetInputSpec and PresetOutputSpec
These specs define explicit phase-to-phase contracts and prevent hidden phase coupling.
Acceptance criteria