Skip to content

meta.phases rejects string entries with an opaque error (wastes the first workflow call) #28

Description

@kwisser

Summary

validateMeta rejects meta.phases entries that are plain strings with the opaque error each meta phase must have a title string. Models very frequently write phases: ['Setup', 'Fan-out', 'Synthesis'] (an array of strings) instead of phases: [{ title: 'Setup' }, ...], so the first workflow call fails validation and a whole model roundtrip is wasted before any sub-agent spawns.

Evidence

In a benchmark of the same task run with the workflow tool required, 4 of 6 workflow invocations lost their first call to exactly this error. The models then retried the identical script with the phases field dropped, which validated and ran (spawning agentCount: 5). The failure is fast (it happens before any sub-agent starts), so it does not blow the budget — but it costs one roundtrip and latency on the majority of first attempts, and the error text does not tell the model what shape was expected or what it actually sent.

Root cause

src/workflow.ts:378-385:

if (value.phases !== undefined) {
  if (!Array.isArray(value.phases)) throw new Error("meta.phases must be an array");
  for (const phase of value.phases) {
    if (!phase || typeof phase !== "object" || typeof (phase as WorkflowMetaPhase).title !== "string") {
      throw new Error("each meta phase must have a title string");
    }
  }
}

A string entry fails the typeof phase !== "object" guard. meta.phases is documented as optional presentation metadata, so a bare title string carries all the information the object form does.

Suggested fix

Either (or both):

  1. Accept strings as shorthand — normalize phases: string[] to phases: {title: string}[]. Since phases are only a display outline, 'Setup' and {title: 'Setup'} are equivalent, and this removes the most common first-call failure entirely:

    value.phases = value.phases.map((p) =>
      typeof p === "string" ? { title: p } : p
    );
    for (const phase of value.phases) {
      if (!phase || typeof phase !== "object" || typeof phase.title !== "string") {
        throw new Error(
          `each meta.phases entry must be a string or { title: string }; got ${JSON.stringify(phase)}`
        );
      }
    }
  2. Improve the error message regardless — include the expected shape and the offending value so a model can self-correct in one step instead of guessing.

Environment: pi-dynamic-workflows v1.0.1.

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