Skip to content

Future artifacts: reference a task output before it exists (up-front graph authoring) #1576

Description

@legreffier

Context

Diary concept entry: 0a9ca296-2e13-455b-9ce6-4ca2c03154c2

This issue was over-scoped during refinement (a quorum-negotiation / reusable-
manifest subsystem) and then cut back to its actual core. The record of the
rejected superstructure and why it was cut lives in the diary; this body is the
lean version.

The core idea: author a task graph up front — create downstream tasks
before their upstream producers have run — by letting a downstream task
reference an output that does not exist yet, then having the server gate that
consumer until the output resolves and rewrite the reference to the real CID.

That is the one thing today's pattern cannot do. The Node-RED deep-review flow
works around it by creating each downstream task only after a moltnet-task-wait
resolves the upstream (so the CID is already known). That works, but the graph
lives in flow wiring, drip-fed one step at a time, instead of being declared as a
whole.

What already exists (do not rebuild)

Assumed "new" Reality (file:line)
Claim conditions / dependency gating tasks.claimCondition jsonb (schema.ts:1064); grammar wire.ts:366; task_accepted + all/any
"claimable only as prereqs resolve" shipped: unsatisfied condition → waiting (invisible to daemons) → promoted to queued (task-conditions.ts:101)
Per-task output contract task.input.successCriteria gates (success-criteria.ts:195), auto-injected at create, edge-enforced + server-re-verified
correlationId grouping column + index (schema.ts:1052), auto-generated (validation.ts:99)
Cancel a running task POST /tasks/:id/cancel + DBOS signal (#938)

The producer half of "futures" is essentially SuccessCriteria. Declaring "task
X must produce an artifact titled review-bundle, kind json" and enforcing it at
completion is a thin new artifact-required gate on the existing gate union — not
a new subsystem.

The genuinely new part is the consumer half: a task referencing another task's
output before that output exists. TaskRef.artifact requires a concrete
{ cid, attemptN }, both mandatory (wire.ts:225) — so a not-yet-existing output
cannot be referenced today. SuccessCriteria governs a task's own output, not
cross-task consumption. Neither covers this.

Scope

Core — the future primitive

  1. artifact-required gate (producer side) — extend the SuccessCriteria
    Gate union (success-criteria.ts:107) with { title, kind, schema? },
    auto-injected for the producer at validation.ts:111. Schema derived from the
    producer's registered output schema where possible. This is the producer's
    declared promise — optimistic, edge-enforced like every other output. No
    negotiation.
  2. TaskRef.futureArtifact variant (consumer side, the real gap) — extend
    TaskRef (wire.ts:201) so a downstream task can reference a producer's
    promised-but-unproduced artifact by (producerTaskId, title). The server
    rewrites it to a concrete { cid, attemptN } at dispatch, once the artifact
    exists, so the executor only ever sees a normal TaskRef.artifact.
  3. artifact_resolved claim leaf — one new leaf in the ClaimCondition
    grammar + the evaluate/collect switches (claim-condition.ts:74,100),
    gating the consumer until the referenced artifact resolves. Reuses the
    existing waiting → queued promotion engine verbatim.
  4. Edge enforcement — the producer's artifact-required gate is enforced
    where output already is: prompt injection (buildFinalOutputBlock,
    final-output.ts:35), in-session nudge (mirror promptUntilSubmitted,
    execute-pi-task.ts:2134), pre-complete local check (captureAttemptOutput,
    execute-pi-task.ts:1439), server backstop (validateTaskOutput,
    validation.ts:217).

Three small independent gaps (prerequisites / standalone value)

  1. Expose claimCondition on MCP tasks_create + the SDK builder — it's
    REST-only today (mcp-server/src/schemas/task-schemas.ts,
    sdk/src/tasks/builder.ts), so agents cannot declare dependencies at all.
    Prerequisite for any agent-authored graph.
  2. Correlation-scoped cancel — one call cancels all non-terminal tasks in a
    correlation. Fixes the genuine "failed workflow leaves tasks running" pain
    (extends POST /tasks/:id/cancel + the maintenance sweeper). Ships alone.
  3. artifacts[] on fulfill_brief output — only freeform has it today
    (fulfill-brief.ts:44), so the type most likely to be a producer cannot cite
    what it uploaded. Trivial.

Build order: 7 → 5 → 6 → 3 → 2 + 1 → 4. Items 5–7 are small and independently
valuable; the primitive (1–4) lands on top.

Deliberately cut (recorded in the diary, not built)

  • Quorum negotiation / counter-proposals / refine rounds — a consensus
    protocol to author the contract. Cut: the producer contract is just a
    SuccessCriteria declaration, so there is nothing to negotiate. If the
    declaration is wrong, the task fails its gate and the proposer fixes it — same
    as any schema mismatch today.
  • Reusable / content-addressed / versioned manifests — a manifest asset with
    its own lifecycle. Cut: no recurring/streaming use case exists in-repo to
    justify it; revisit only if one appears.
  • Correlation seals for plans — the word seal is already taken by a
    contradictory judge-snapshot mechanism (Async task-create validation: resolve referenced ids and prevent cross-task races #1096, schema.ts:1156). Not reused.
  • Batch/sealed-plan declaration API, materialized plan table, adaptive/branching
    workflows, afterHumanApproval, manualRetryOf, unlessWorkflowFailed
    — out
    of scope. unlessWorkflowFailed is subsumed by correlation-scoped cancel.

Open questions

  • Future referent stability. (producerTaskId, title) is a stringly-typed
    key. Is a per-producer title unique enough, or does a producer emitting multiple
    artifacts need something firmer? (This is the one place the cut "manifest id"
    idea had a point — evaluate if title collisions become real.)
  • Multiple artifacts from one producer. Does the artifact-required gate need
    to enumerate several slots, and can a consumer reference one specific slot?
  • Node-RED surface. Expose futures in the deep-review example without turning
    it into a workflow engine — declare the artifact-required gate + gate the
    consumer on artifact_resolved using existing nodes, dropping the hand-wired
    moltnet-task-wait + flow-context sequencing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    rfcRequest for Comments

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions