The Read That Unlocks Integration: Wiring a Remote GPU Proving Daemon into Curio's PoRep Task

Introduction

In the sprawling architecture of Filecoin's Curio storage proving system, few moments are as deceptively simple — yet as architecturally significant — as a single [read] command. Message <msg id=3421> captures exactly such a moment: the assistant reads the file tasks/seal/task_porep.go to understand its current structure before embarking on a critical integration. This read operation is the fulcrum point where months of prior work on the cuzk remote GPU proving daemon — spanning memory-bandwidth optimization, split API design, low-memory benchmarking, and protocol definition — finally meets the Curio task orchestrator that will consume it. The message itself is unremarkable: a tool call that loads a Go source file into context. But understanding why this particular file, at this particular moment, with this particular set of design decisions already crystallized, reveals the deep structure of a complex systems integration effort.

Context: The Long Road to Remote Proving

To appreciate message <msg id=3421>, one must understand the journey that preceded it. The cuzk project began as an investigation into the SUPRASEAL_C2 Groth16 proof generation pipeline for Filecoin's Proof-of-Replication (PoRep) protocol. Early analysis (see <segment id=0>) mapped the full call chain from Curio's Go task layer through Rust FFI into C++/CUDA kernels, revealing a ~200 GiB peak memory footprint and nine structural bottlenecks. This led to a series of optimization phases: Phase 11 targeted DDR5 memory bandwidth contention, Phase 12 implemented a split GPU proving API with memory backpressure, and the session documented in <segment id=32> consolidated the architecture with a systematic low-memory benchmark sweep.

By the time we reach message <msg id=3421>, the assistant has already accomplished several critical prerequisites:

  1. Designed and implemented the cuzk daemon — a standalone gRPC server that accepts SNARK computation requests and farms them out to GPU workers, with queue-based backpressure and configurable parallelism.
  2. Defined the protobuf protocol — the cuzk/v1/proving.proto file that specifies the gRPC service for submitting proof requests and querying queue status.
  3. Generated the Go gRPC client stubs — using protoc-gen-go and protoc-gen-go-grpc to produce proving.pb.go and proving_grpc.pb.go in the lib/cuzk/ package.
  4. Created the Go client wrapperlib/cuzk/client.go, a convenience layer that wraps the raw gRPC stubs with methods like SubmitPoRepSnark(), SubmitSnapSnark(), and QueueDepth().
  5. Created the FFI integration layerlib/ffi/cuzk_funcs.go, which provides SealCalls methods (PoRepSnarkCuzk, SnapSnarkCuzk, PSProofCuzk) that generate vanilla proofs locally (since they need sector data) and then submit the SNARK computation to the cuzk daemon via gRPC.
  6. Added the configuration structCuzkConfig in deps/config/types.go with fields for the daemon address, TLS settings, and queue depth limits. The stage is now set for the final act: wiring the cuzk client into Curio's task types so that the orchestrator can delegate SNARK computation to the remote daemon instead of running it locally.

The Message Itself: Reading Before Writing

Message <msg id=3421> is a [read] tool call that loads the full contents of /home/theuser/curio/tasks/seal/task_porep.go into the assistant's context. The conversation data shows the file header — package declaration, imports, and the beginning of the file structure. The assistant needs to see the complete file because it plans to make four coordinated edits:

  1. Add a cuzkClient *cuzk.Client field to the PoRepTask struct
  2. Modify the constructor (NewPoRepTask) to accept and store the client
  3. Update the Do() method to call PoRepSnarkCuzk instead of PoRepSnark when the daemon is enabled
  4. Adapt CanAccept() to query the daemon's queue depth for backpressure
  5. Change TypeDetails() to zero out local GPU/RAM requirements when cuzk is active These edits are not independent — they touch multiple parts of the file, and the assistant needs to see the existing code to make precise, syntactically correct changes. The read operation is therefore not a passive information-gathering step; it is an active preparation for surgery. The assistant must understand the exact field names, method signatures, import paths, and control flow patterns used in the file before it can safely modify them.

Design Decisions Embedded in the Read

Although message <msg id=3421> itself contains no decisions — it is purely a read operation — it reflects several design decisions that were made earlier and are now being operationalized:

Decision 1: Split the proving pipeline. The assistant's plan, articulated in <msg id=3420>, is to keep vanilla proof generation local (it requires access to sector data on the storage node) while offloading only the SNARK computation to the cuzk daemon. This is reflected in the lib/ffi/cuzk_funcs.go design, where PoRepSnarkCuzk calls GeneratePoRepVanillaProof locally, then submits the vanilla proof to the daemon via gRPC. The task's Do() method will call this new function instead of the original PoRepSnark when cuzk is enabled.

Decision 2: Bypass local resource accounting. When cuzk is active, the Curio task scheduler should not consider the task's local GPU or RAM requirements, because those resources are provided by the remote daemon. The TypeDetails() method returns resource estimates that the harmony scheduler uses for capacity planning; zeroing them out tells the scheduler that these tasks are "lightweight" from a local perspective.

Decision 3: Use daemon-side backpressure. Instead of Curio's local resource availability determining whether a task can be accepted, the CanAccept() method will query the cuzk daemon's queue depth. If the daemon's queue is full, the task defers acceptance, naturally throttling the pipeline without overloading the remote GPU workers.

Decision 4: Consistent pattern across task types. The assistant plans to apply the same pattern to PoRep, SnapDeals prove, and proofshare (PSProve) tasks. Reading task_porep.go first establishes the baseline pattern that will be replicated across the other task files.

Assumptions and Their Validity

The assistant makes several assumptions when reading task_porep.go:

Assumption 1: The file follows the standard Curio task pattern. Curio's harmony framework defines a TaskInterface with methods Do(), CanAccept(), TypeDetails(), and others. The assistant assumes that PoRepTask implements this interface and that the file contains these methods. This assumption is validated by the earlier research phase (messages <msg id=3374> through <msg id=3382>), where the assistant examined multiple task implementations.

Assumption 2: The enableRemoteProofs field exists and has the semantics discovered earlier. In <msg id=3381>, the assistant discovered that PoRep's enableRemoteProofs field is actually set from cfg.Subsystems.EnablePoRepProof, and that when it is false, proofs are done remotely. This inverted logic (compared to SnapDeals' EnableRemoteProofs) must be preserved.

Assumption 3: The file compiles and has no pre-existing errors. The assistant assumes a clean baseline. This is reasonable given that the codebase is in active development and the file has been previously compiled.

Assumption 4: The cuzk client package compiles and can be imported. The assistant verified this in <msg id=3410> with go build ./lib/cuzk/.

These assumptions are largely correct, though the subsequent edit in <msg id=3422> triggers an LSP error ("imported and not used") because the assistant added the import before adding the field that uses it — a temporary state that is resolved in the next edit.

Input Knowledge Required

To understand message <msg id=3421>, the reader needs:

  1. Curio's task architecture — The harmony framework's task lifecycle: tasks are created, polled for readiness via CanAccept(), executed via Do(), and scheduled based on resource estimates from TypeDetails().
  2. The PoRep proving pipeline — Proof-of-Replication involves two phases: generating a vanilla proof (which requires access to sealed sector data) and running the SNARK computation (which is GPU-intensive and produces the final proof).
  3. The cuzk daemon's role — A standalone gRPC server that accepts SNARK computation requests, manages a queue of pending proofs, and dispatches them to GPU workers. It provides backpressure via queue depth reporting.
  4. The prior optimization work — Phases 11 and 12 of the cuzk project, which optimized memory bandwidth and split the GPU proving API to hide latency. These optimizations are baked into the daemon's architecture.
  5. Go language patterns — Struct embedding, constructor injection, interface implementation, and gRPC client usage.

Output Knowledge Created

Message <msg id=3421> itself produces no permanent output — it loads information into the assistant's working context. However, this information enables the subsequent edits that produce:

  1. Modified task_porep.go — With cuzk client integration, backpressure-aware CanAccept(), zeroed resource requirements in TypeDetails(), and remote SNARK dispatch in Do().
  2. A replicable pattern — The same integration pattern is then applied to task_prove.go (SnapDeals) and ps_prove.go (proofshare), as documented in <chunk seg=33 chunk=1>.
  3. Architectural knowledge — The integration establishes a template for future remote computation offloading in Curio: local vanilla work + remote SNARK dispatch + daemon-side backpressure + zeroed local resource accounting.

The Thinking Process

The assistant's reasoning is visible in the progression from <msg id=3419> to <msg id=3421>. After verifying that the Go code compiles cleanly (message <msg id=3419>), the assistant articulates its four-point plan in <msg id=3420>:

"Now wire cuzk into the PoRep task. The approach: 1. Add cuzkClient *cuzk.Client field to PoRepTask 2. In TypeDetails(), when cuzk is enabled, zero out GPU/RAM requirements 3. In CanAccept(), when cuzk is enabled, use backpressure from daemon 4. In Do(), when cuzk is enabled, call PoRepSnarkCuzk instead of PoRepSnark"

This is followed by [edit] /home/theuser/curio/tasks/seal/task_porep.go — but the edit tool requires the file to be in context. The assistant then issues the [read] command (message <msg id=3421>) to load the file. The sequence reveals a disciplined workflow: plan first, then gather data, then execute. The read is not exploratory — it is targeted, purposeful, and immediately followed by edits.

The assistant's thinking also reflects an understanding of the system's constraints. It knows that the cuzk client must be injected via the constructor (not created ad-hoc) because the task lifecycle in Curio creates tasks once and reuses them. It knows that CanAccept() must be fast and non-blocking (querying queue depth via gRPC is a lightweight operation). It knows that zeroing TypeDetails() resource estimates will cause the harmony scheduler to treat these tasks as having no local resource footprint, which is correct because the GPU work happens remotely.

Conclusion

Message <msg id=3421> is, on its surface, a simple file read. But in the context of the broader cuzk integration effort, it represents the moment where months of optimization, protocol design, and daemon implementation converge into concrete code changes. The read operation is the bridge between architectural planning and implementation — the point where abstract design becomes concrete edits. Understanding this message requires seeing not just the tool call itself, but the entire chain of reasoning that led to it: the memory bottleneck analysis, the split API design, the protobuf definition, the client library, and the FFI integration layer. Each of these prior steps made assumptions about how the final integration would work; message <msg id=3421> is where those assumptions are tested against the actual code that will be modified.