Skip to content

Design: define full-lifecycle PhasePreset and runtime spec schemas #198

@devkade

Description

@devkade

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:

  1. Inline only the state required for current runtime decisions.
  2. Keep large payloads, logs, reports, diffs, test output, evaluation bodies, and worker output out of operational state.
  3. Link details through ArtifactRef, EvidenceRef, event ids, or external refs.
  4. Prefer version, status, refs, blockers, and timestamps over embedded documents.
  5. 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

  • Convert the above shapes into validated runtime schema definitions.
  • Define RuntimeRef, ArtifactRef, EvidenceRef, EventRef, and ExternalRef validation.
  • Define URI scheme validation and RefResolver lookup behavior.
  • Add examples for all eight default phase presets.
  • Add fixtures for pass, block, repair, human decision, and abort paths.
  • Define serialization and migration behavior.
  • Link schema validation to RunState replay and gate evaluation tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions