You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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
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.
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.
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.
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)
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.
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.
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.
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.
Context
Diary concept entry:
0a9ca296-2e13-455b-9ce6-4ca2c03154c2This 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-waitresolves 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)
tasks.claimConditionjsonb (schema.ts:1064); grammarwire.ts:366;task_accepted+all/anywaiting(invisible to daemons) → promoted toqueued(task-conditions.ts:101)task.input.successCriteriagates (success-criteria.ts:195), auto-injected at create, edge-enforced + server-re-verifiedcorrelationIdgroupingschema.ts:1052), auto-generated (validation.ts:99)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, kindjson" and enforcing it atcompletion is a thin new
artifact-requiredgate on the existing gate union — nota new subsystem.
The genuinely new part is the consumer half: a task referencing another task's
output before that output exists.
TaskRef.artifactrequires a concrete{ cid, attemptN }, both mandatory (wire.ts:225) — so a not-yet-existing outputcannot be referenced today. SuccessCriteria governs a task's own output, not
cross-task consumption. Neither covers this.
Scope
Core — the future primitive
artifact-requiredgate (producer side) — extend theSuccessCriteriaGateunion (success-criteria.ts:107) with{ title, kind, schema? },auto-injected for the producer at
validation.ts:111. Schema derived from theproducer's registered output schema where possible. This is the producer's
declared promise — optimistic, edge-enforced like every other output. No
negotiation.
TaskRef.futureArtifactvariant (consumer side, the real gap) — extendTaskRef(wire.ts:201) so a downstream task can reference a producer'spromised-but-unproduced artifact by
(producerTaskId, title). The serverrewrites it to a concrete
{ cid, attemptN }at dispatch, once the artifactexists, so the executor only ever sees a normal
TaskRef.artifact.artifact_resolvedclaim leaf — one new leaf in theClaimConditiongrammar + the
evaluate/collectswitches (claim-condition.ts:74,100),gating the consumer until the referenced artifact resolves. Reuses the
existing
waiting → queuedpromotion engine verbatim.artifact-requiredgate is enforcedwhere output already is: prompt injection (
buildFinalOutputBlock,final-output.ts:35), in-session nudge (mirrorpromptUntilSubmitted,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)
claimConditionon MCPtasks_create+ the SDK builder — it'sREST-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.
correlation. Fixes the genuine "failed workflow leaves tasks running" pain
(extends
POST /tasks/:id/cancel+ the maintenance sweeper). Ships alone.artifacts[]onfulfill_briefoutput — onlyfreeformhas it today(
fulfill-brief.ts:44), so the type most likely to be a producer cannot citewhat 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)
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.
its own lifecycle. Cut: no recurring/streaming use case exists in-repo to
justify it; revisit only if one appears.
sealis already taken by acontradictory judge-snapshot mechanism (Async task-create validation: resolve referenced ids and prevent cross-task races #1096,
schema.ts:1156). Not reused.workflows,
afterHumanApproval,manualRetryOf,unlessWorkflowFailed— outof scope.
unlessWorkflowFailedis subsumed by correlation-scoped cancel.Open questions
(producerTaskId, title)is a stringly-typedkey. 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.)
artifact-requiredgate needto enumerate several slots, and can a consumer reference one specific slot?
it into a workflow engine — declare the
artifact-requiredgate + gate theconsumer on
artifact_resolvedusing existing nodes, dropping the hand-wiredmoltnet-task-wait+flow-context sequencing.