Repository navigation
Conversation
…ction Loop node results larger than LoopOutputInlineLimitBytes are written to the output blob store and GenerationOutput.OutputRef holds a "sha256:..." ref instead of the payload. Nothing read that blob back, so runtimeNamespace built node entries from the raw ref and every template reaching into a large node's output resolved against the hash string. Fan-out is where it failed loudly: resolveFanOutCollection could not walk .output.tasks inside a string and aborted the run with "fan-out collection reference is unavailable". Gate criteria, action params and prompt templates degraded silently through the same path. Resolve content-addressed refs into their payloads right after the coordinator loads generation outputs, so externalization stays a storage detail. The read side already existed (getLoopOutputByRefWithExecutor, ErrOutputRefNotFound) and was simply never wired; this exposes it as GetGenerationOutputPayload and calls it. Hydrating here also covers fan-out materializations that exceed the limit themselves, keeping .item available to the fan-out body. The HTTP surface keeps returning refs: hydration is scoped to the two coordinator load sites, not to the store repository. Fixes compozy#285
|
@maaark11 is attempting to deploy a commit to the Compozy Team on Vercel. A member of the Team first needs to authorize it. |
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Implement the complete Loop node lifecycle across runtime, persistence, APIs, CLI, native tools, web UI, SDKs, documentation, and QA coverage. Incorporate and extend the externalized Loop output hydration fix from PR #286. Co-authored-by: Marco Aurélio <[email protected]>
|
Closing this PR because its externalized Loop-output hydration fix was incorporated and extended in #305, which has now been merged as the complete Loop node lifecycle landing change. The merged implementation preserves inline outputs, hydrates external references before namespace evaluation, caches loaded payloads, covers fan-out and gate consumers, and reports missing blobs deterministically. @magoamz is credited as a co-author on the final merged commit. Thank you for the original fix and investigation. |
Fixes #285.
Problem
Loop node results larger than
LoopOutputInlineLimitBytes(16 KB) are externalized toloop_output_blobs, andGenerationOutput.OutputRefthen holds asha256:…ref rather than thepayload. Nothing read that blob back.
runtimeNamespacebuilds each node entry straight from theref:
outputValuetries to parse the ref as JSON, fails, and returns the raw"sha256:…"string. Everytemplate reaching into that node's output then resolves against a hash.
Fan-out is where it fails loudly —
resolveFanOutCollectioncannot walk.output.tasksinside astring, so the run dies with
fan-out collection reference … is unavailableright after its producersucceeded. Gate criteria, action params and prompt templates degrade silently through the same path.
This breaks the first-party
dev-cyclesoftware-deliveryLoop for realistic inputs:load_tasksinlines each task's full markdown body, so four ordinary task files produced a 41 KB output and the
fan-out driving the whole delivery could not resolve.
Change
Resolve content-addressed refs into their payloads immediately after the coordinator loads generation
outputs, so externalization stays a storage detail instead of something every consumer must know
about.
The read side already existed and was simply never wired —
getLoopOutputByRefWithExecutorhad noproduction caller, and
ErrOutputRefNotFoundwas already defined for exactly this. This exposes itas
LoopRepo.GetGenerationOutputPayload, adds it toGenerationOutputReader, and calls it fromhydrateGenerationOutputs.Two properties worth calling out:
limit itself and becomes a blob, which would break
fanOutItemserving.itemto the fan-outbody. Hydrating at the load site fixes the collection and
.itemin one place; fixing onlyresolveFanOutCollectionwould have failed at the next step.than to
LoopRepo.ListGenerationOutputs, soGET /loop-runs/:idkeeps returning refs instead ofinlining full payloads into API responses.
output_refalready holds either inline JSON or a ref depending on size, so replacing a ref withits payload restores the invariant
outputValuehas always assumed rather than introducing newsemantics.
Scope
Read-path only. The write path (
OutputPayloadRequiresRef,writeGenerationOutputBlob) and the16 KB limit itself are untouched — the limit is a reasonable inline cap that was just missing its
matching read.
Tests
internal/loop/generation_output_hydration_test.gocovers:ErrOutputRefNotFoundinstead of degrading to the refresolves to its items after
go vet ./internal/loop/... ./internal/store/globaldb/... ./internal/daemon/...is clean, as isgofmt.Verification against the reported failure
The 16 KB boundary was measured by running
software-deliveryin a throwaway workspace against asynthetic task file of increasing size and reading back
loop_generation_outputs.output_ref: outputsof 2 713 B / 9 789 B / 15 822 B stayed inline and ran, while 18 142 B / 19 302 B became blobs and
failed. Details in #285.
I have not been able to run the full repository suite locally (the sandbox only has the Go packages
checked out), so CI is the real signal for anything outside
internal/loop.