The Integration Blueprint: Wiring a Remote GPU Proving Daemon into Curio's Task Orchestrator

Introduction

In the long arc of optimizing Filecoin's proof generation pipeline, there comes a moment when the focus shifts from optimizing individual components to architecting how those components fit together. Message 3364 in this opencode session represents exactly such a pivot point. After dozens of rounds spent designing, implementing, and benchmarking the cuzk GPU proving daemon—a high-performance Groth16 prover that achieved 37.7-second proofs with sophisticated memory backpressure—the assistant now turns to the equally challenging problem of integrating that daemon into Curio's production task orchestrator. This message captures the transition from "how do we make the proof fast?" to "how do we make the proof fit into the existing system?"

The message itself is deceptively brief. It contains a reasoning block and a single bash command. But within that compact surface area lies a rich story about architectural decision-making, the tension between clean separation and tight integration, and the careful work of understanding an unfamiliar codebase before making surgical changes.

The Message in Full

Agent Reasoning Constructing the Integration Plan

>

I've got a firm grasp of the necessary details and am now crafting a structured integration plan. This initial step involves extending the CurioConfig into a CurioProvingConfig for Cuzk.

>

Developing the gRPC Client

>

I'm currently focused on the gRPC client implementation. I've defined the necessary updates within the deps/config/types.go file. The package lib/cuzk/ is now taking shape, along with the generate.go file to handle protoc bindings. I am moving towards writing the client to wrap the ProvingEngineClient.

>

[bash] grep -A 20 "func (p \*ProveTask) Do" ./tasks/snap/task_prove.go

The command then reveals the first 20 lines of the ProveTask.Do method, showing how the SnapDeals prove task reads its sector parameters from the database.

Why This Message Was Written: The Context and Motivation

To understand why this message exists, one must understand the broader arc of the project. The session had spent the preceding segments (28 through 32) designing, implementing, and benchmarking the cuzk proving daemon itself—a standalone C++/CUDA service that could generate Groth16 proofs for Filecoin's Proof-of-Replication (PoRep), SnapDeals, and WindowPoSt. Phase 12 had just been completed: the split GPU proving API with memory backpressure, documented in cuzk-project.md and committed as 9bb657e5. The daemon worked. It was fast. It was memory-efficient.

But a daemon that sits in isolation is useless. The entire purpose of cuzk was to serve as a remote proving engine that Curio—Filecoin's task orchestrator—could offload SNARK computation to. Without integration, the daemon was a beautiful but inert piece of engineering.

The motivation for message 3364, then, is the recognition that the integration work must begin. The assistant has spent the previous messages ([msg 3332] through [msg 3363]) exploring Curio's task system: examining task_porep.go, task_synth_proofs.go, task_prove.go, compute_task.go, and the configuration types in deps/config/types.go. It has traced how tasks define their resource requirements via TypeDetails(), how they accept work via CanAccept(), and how they execute via Do(). It has studied the ffi.SealCalls interface that currently handles local proof generation. It has read the protobuf definitions in extern/cuzk/cuzk-proto/proto/cuzk/v1/proving.proto to understand the gRPC API that the daemon exposes.

Now, in message 3364, the assistant declares that it has "a firm grasp of the necessary details" and is ready to craft the integration plan. This is the moment of synthesis—when exploration transforms into action.

The Thinking Process: A Window into Architectural Deliberation

The reasoning block in this message is particularly revealing because it shows the assistant working through the integration at multiple levels of abstraction simultaneously.

At the highest level, the assistant is "crafting a structured integration plan." This is not a single code change but a coordinated set of modifications across the Curio codebase. The plan must touch configuration, client code, task definitions, and the build system. The assistant is thinking in terms of layers.

The first layer is configuration. The assistant identifies that deps/config/types.go needs a new CuzkConfig section. This is the entry point: how does an operator enable remote proving? The assistant's reasoning shows it has already considered placement—earlier messages debated whether to put CuzkConfig directly in CurioConfig or nest it inside CurioProvingConfig. The mention of "extending the CurioConfig into a CurioProvingConfig for Cuzk" suggests the latter approach was chosen, which makes architectural sense: proving configuration is a natural home for a proving daemon's settings.

The second layer is the gRPC client. The assistant mentions lib/cuzk/ taking shape, with a generate.go file for protoc bindings. This is a significant architectural decision: rather than embedding gRPC client logic directly in each task file, the assistant is creating a dedicated client library. This follows the principle of separation of concerns—the client encapsulates all knowledge of the gRPC protocol, connection management, and error handling. Task code only needs to call cuzkClient.Prove(...) or cuzkClient.GetStatus(...).

The third layer is the task integration itself. The bash command grep -A 20 "func (p \*ProveTask) Do" ./tasks/snap/task_prove.go is not idle curiosity. The assistant is studying the SnapDeals prove task's Do method to understand exactly where the SNARK computation call happens, what data it needs, and how to replace the local call with a remote one. The output shows the task reading SpID, SectorNumber, UpdateProof, RegSealProof, and CIDs from the database—this is the input knowledge the assistant needs to design the gRPC request payload.

Assumptions Embedded in the Approach

The assistant's reasoning reveals several assumptions that shape the integration design.

Assumption 1: The daemon remains independent. The assistant consistently refers to cuzk as a separate process with its own gRPC API and resource management. This is a deliberate architectural choice, not an accident. Earlier messages in the session explored whether to embed the prover directly in Curio's process, but the decision was made to keep it independent. This assumption has profound implications: it means Curio does not manage the daemon's lifecycle, does not share its memory, and communicates only over the network. It also means the daemon can be deployed on separate hardware—a key requirement for the heterogeneous cloud rental market the project targets.

Assumption 2: Backpressure is delegated to the daemon. The assistant assumes that when cuzk is enabled, Curio's local resource accounting (GPU count, RAM) should be bypassed. Instead, CanAccept() will query the daemon's queue status. This is a fundamental shift in scheduling philosophy: rather than Curio deciding whether it can run a proof based on local resources, it asks the daemon whether it will accept more work. The daemon becomes the authority on its own capacity.

Assumption 3: The protobuf definitions are correct and complete. The assistant references proving.proto and the ProvingEngineClient without questioning whether the protobuf schema needs modification. This assumes that the existing SubmitProofRequest, AwaitProofRequest, and ProveRequest messages are sufficient for all proof types (PoRep C2, SnapDeals, WindowPoSt). The assistant had earlier verified the protobuf contents ([msg 3363]) and confirmed that VanillaProof fields exist for the various proof types.

Assumption 4: The task lifecycle pattern is consistent across proof types. By studying task_porep.go and task_prove.go, the assistant assumes that all proof-related tasks follow the same structural pattern: a Do() method that calls into ffi.SealCalls, a CanAccept() that checks local resources, and a TypeDetails() that declares resource costs. This assumption turns out to be largely correct, which is why the eventual integration can use a consistent pattern across PoRep, SnapDeals, and proofshare tasks.

Input Knowledge Required

To understand this message fully, one needs substantial context about the system architecture:

  1. Curio's task orchestration model: Tasks in Curio implement harmonytask.TaskTypeDetails (declaring resource costs), CanAccept (deciding whether to accept work), and Do (executing the work). The scheduler uses these to allocate work across machines.
  2. The ffi.SealCalls interface: This is Curio's existing abstraction for local proof generation. It wraps CGO bindings to Rust/C++ proving code. The integration plan involves replacing calls to ffi.SealCalls.PoRepSnark() with gRPC calls to the cuzk daemon.
  3. The protobuf schema: The ProvingEngine gRPC service exposes SubmitProof, AwaitProof, Prove, and Cancel RPCs. The ProveRequest message carries proof type identifiers and vanilla proof data.
  4. The proof types: PoRep (seal), SnapDeals (prove replica update), WindowPoSt, and WinningPoSt each have different data requirements and vanilla proof formats.
  5. The configuration system: Curio uses a TOML-based config with Go structs in deps/config/types.go. Adding a CuzkConfig section requires defining the struct, adding it to the parent config, and updating the config loading and documentation generation.

Output Knowledge Created

This message produces several forms of knowledge:

  1. The integration plan itself: The reasoning block outlines the three major workstreams—config changes, gRPC client creation, and task wiring. This plan guides the subsequent implementation.
  2. Understanding of the SnapDeals prove task: The bash command reveals the exact structure of ProveTask.Do, showing the database query that fetches sector parameters. This is essential knowledge for designing the gRPC request that will replace the local SNARK call.
  3. Confirmation of the architectural approach: By articulating the plan and executing the research command, the assistant validates that the approach is feasible and that the codebase conforms to its expectations.
  4. A decision point: The message implicitly records the decision to place CuzkConfig in the proving configuration section, to create a dedicated lib/cuzk/ client package, and to use generated protobuf stubs rather than hand-written gRPC code.

Mistakes and Incorrect Assumptions

While the message itself does not contain overt errors, there are potential pitfalls in the assumptions that deserve examination.

The assumption that all proof tasks follow the same pattern is strong but not perfectly accurate. The WindowPoSt task (compute_task.go) uses a ProverPoSt interface rather than ffi.SealCalls, and its integration path differs from PoRep and SnapDeals. The assistant would discover this in subsequent work, requiring a slightly different approach for PoSt tasks.

The assumption that the protobuf schema is complete may also prove optimistic. The existing ProveRequest message might lack fields needed for specific proof types, requiring schema modifications that would ripple through both the Go client and the C++ server.

The assumption that CanAccept can simply query the daemon's queue status glosses over the complexity of network failures and timeouts. If the daemon is unreachable, should CanAccept fall back to local computation? Should it reject the task? The assistant's reasoning does not address these edge cases, which would need to be handled in production code.

Conclusion

Message 3364 is a hinge point in the session. It marks the transition from exploration to construction, from understanding to acting. The assistant has spent the preceding messages reading code, tracing call chains, and understanding Curio's task architecture. Now, with the declaration that it has "a firm grasp of the necessary details," it begins to build.

The message is also a testament to the value of structured reasoning in complex system integration. Rather than diving into code changes without a plan, the assistant articulates the three layers of work—configuration, client, and task wiring—and then executes a targeted research command to fill in the remaining unknowns. This methodical approach reduces the risk of missed dependencies and contradictory assumptions.

In the messages that follow, this plan would be executed: the CuzkConfig would be added to deps/config/types.go, the Go gRPC client would be created in lib/cuzk/client.go, and the task files would be modified to delegate SNARK computation to the daemon. But message 3364 captures the moment before the work begins—the quiet confidence of having understood the problem well enough to solve it.