Reading Before Writing: How a Task-Based Exploration Unlocked the Filecoin Proving API

In the middle of building a pipelined SNARK proving daemon called cuzk, the assistant encountered a moment that exemplifies one of the most fundamental software engineering patterns: read before write. Message 136 is not a code change, a command execution, or a design decision in the traditional sense. It is a research probe — a carefully scoped delegation to a sub-agent to explore the public API surface of an external dependency. Understanding why this message exists, what assumptions it carries, and what knowledge it produces reveals the invisible scaffolding that supports every successful integration.

The Context: A Working Skeleton That Cannot Yet Prove

To appreciate message 136, we must first understand what came before it. The assistant had just completed the Phase 0 scaffold of cuzk — a six-crate Rust workspace with a gRPC API, a priority scheduler, a daemon binary, and a benchmarking client. The workspace compiled cleanly with zero warnings (see [msg 130]), and all four unit tests passed (see [msg 131]). The user, satisfied with the progress, simply said "continue" (see [msg 134]).

But there was a critical gap. The prover module — the heart of the daemon — contained only stubs. The function prove_porep_c2 could parse and validate a C1 JSON input, but it did not actually call seal_commit_phase2(). The daemon was a beautifully structured empty shell: it could receive requests, schedule them, and return errors, but it could not generate a single proof.

The assistant recognized this immediately. In [msg 135], it updated its todo list to mark "Wire up real filecoin-proofs-api calls in prover.rs" as in-progress and then announced: "Let me check what APIs filecoin-proofs-api exposes so I can wire up the real proving calls correctly." This was the trigger for message 136.

The Message Itself: A Task-Based API Exploration

Message 136 is structured as a task invocation. The assistant writes:

[task] {"description":"Explore filecoin-proofs-api surface","prompt":"I need to understand the public API surface of filecoin-proofs-api v19.0.0 to wire up real proving calls. Thoroughness: very thorough.\n\nFind and return the FULL contents of these files from ~/.cargo/registry/src/ (look in the index dir...}

The task result then returns the complete public API surface of filecoin-proofs-api v19.0.0, organized by file. The result includes the contents of lib.rs, seal.rs, post.rs, update.rs, and the cache mechanism from filecoin-proofs v19.0.1.

This is not a simple "look up a function signature" operation. The assistant is asking for the full contents of multiple files from the Cargo registry cache. It wants to see every public export, every function signature, every struct definition, and every module boundary. The thoroughness parameter is set to "very thorough."

The Reasoning: Why This Step Was Necessary

Why couldn't the assistant just import filecoin-proofs-api and start calling functions? Several reasons:

  1. Unknown function signatures: The assistant knew that seal_commit_phase2 existed, but it didn't know the exact parameter types, return types, or error handling patterns. Would the function take a deserialized struct or raw bytes? Would it return a result type or panic on failure?
  2. Unknown serialization format: The C1 input arrives as a JSON file containing a base64-encoded blob. The assistant needed to understand how SealCommitPhase1Output is serialized and deserialized — is it bincode, JSON, or a custom format? This determines how the prover must decode the incoming request.
  3. Unknown cache semantics: The filecoin-proofs library uses a GROTH_PARAM_MEMORY_CACHE for SRS (Structured Reference String) residency. The assistant needed to understand whether SRS preloading requires an explicit API call or happens lazily on first proof invocation. This directly affects the daemon's warm-up strategy.
  4. Unknown proof type surface: Beyond PoRep C2, the daemon will eventually need to support Window PoSt, Winning PoSt, and sector update proofs. The assistant needed to see the full module structure to plan for extensibility.
  5. Unknown dependency graph: The filecoin-proofs-api crate re-exports types from filecoin-proofs, which in turn depends on storage-proofs-core, bellperson, and other crates. Understanding the module boundaries helps the assistant write correct import statements and avoid compilation errors.

Assumptions Embedded in the Exploration

The assistant made several assumptions when crafting this task:

Assumption 1: The registry cache is present and accessible. The task prompt asks the sub-agent to look in ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/. This assumes that the dependencies have already been downloaded during the earlier cargo check commands. If the cache were missing or corrupted, the task would fail silently or return incomplete results.

Assumption 2: The public API is sufficient. The assistant assumes that reading the public exports of filecoin-proofs-api will reveal everything needed to wire up the prover. In reality, some critical details — such as the exact JSON field names in SealCommitPhase1Output or the ProverId construction from miner ID — required additional investigation. The assistant later had to run a second task (see [msg 138]) to check the Go-side serialization format and a grep command (see [msg 139]) to find the toProverID function.

Assumption 3: Version 19.0.0 is the correct target. The assistant explicitly specifies v19.0.0 in the task prompt. This is correct because the workspace's Cargo.toml pins filecoin-proofs-api = "19.0.0", but it assumes that no breaking changes exist between the published crate and the version used by Curio's Go FFI layer.

Assumption 4: Thorough exploration is worth the cost. The task takes time and compute resources. The assistant implicitly assumes that reading the full API surface upfront will prevent more costly debugging later. This is a classic tradeoff: invest in understanding now to avoid guesswork later.

The Knowledge Produced

The task result produced several critical pieces of knowledge that directly shaped the subsequent implementation:

  1. seal_commit_phase2 signature: The function takes a SealCommitPhase1Output (which is serde::Deserialize) plus prover_id and sector_id as [u8; 32] byte arrays, and returns a SealCommitPhase2Output containing the proof as Vec<u8>.
  2. Module structure: The API is organized into three public modules — seal, post, and update — each containing functions for their respective proof types. This told the assistant exactly where to find the other proving functions it would need in later phases.
  3. Cache mechanism: The GROTH_PARAM_MEMORY_CACHE in filecoin-proofs is populated lazily — the first proof call triggers parameter loading. There is no explicit preload API. This meant the assistant's planned "preload SRS" RPC would need to either trigger a dummy proof or implement its own parameter loading outside the filecoin-proofs-api path.
  4. No registry module: The registry module is private, meaning the assistant cannot directly manipulate the parameter cache. Any SRS management must happen through the existing API or by loading parameters independently.
  5. Serialization confirmation: The SealCommitPhase1Output struct uses serde for deserialization, confirming that the C1 JSON can be parsed directly with serde_json::from_slice. This knowledge was immediately actionable. In the very next message (see [msg 137]), the assistant summarized the key insights and began reading and modifying the prover source code to wire up the real calls.

The Thinking Process Visible in the Task Design

The task prompt reveals the assistant's mental model of the problem. It asks for "the FULL contents of these files" — not summaries, not function lists, but the complete source. This suggests a deep-seated need for certainty. When integrating with an unfamiliar API, partial information can be worse than no information because it leads to incorrect assumptions about error handling, type conversions, and edge cases.

The assistant also asks for files organized by module path: lib.rs, then seal.rs, then post.rs, then update.rs. This ordering reflects a logical progression: start with the top-level exports, then drill into each functional area. The task is not just exhaustive but structured.

The "very thorough" thoroughness parameter is telling. It signals that the assistant is willing to pay a higher cost in tokens and latency to get complete information. This is a strategic decision: the cost of re-reading a file later (or debugging a wrong function call) is higher than the cost of reading everything now.

What This Message Is Not

It is important to recognize what message 136 is not. It is not a decision about architecture. It is not a code change. It is not a debugging session. It is not a user interaction. It is a knowledge acquisition step — a pause in the implementation flow to gather the raw material needed for correct code.

In many coding session transcripts, such steps are invisible. The developer already knows the API, or they look it up in a browser without leaving a trace. But in this recorded session, the assistant's research step is captured in full, revealing the epistemic foundation on which the subsequent implementation rests.

Conclusion: The Hidden Cost of Integration

Message 136 is a testament to the fact that integration work is often dominated by reading, not writing. The assistant spent more time and tokens exploring the filecoin-proofs-api surface than it would spend actually modifying the prover code. This is not inefficiency — it is the natural rhythm of building on someone else's abstractions.

The message also reveals a key insight about the assistant's operating model: when faced with uncertainty about an external API, it does not guess. It does not write code and hope it compiles. It stops, reads the source, and only then proceeds. This "read-first" discipline is what separates a robust implementation from a fragile one.

In the end, the knowledge produced by this single message — the function signatures, the module structure, the cache semantics, the serialization format — became the blueprint for the real proving pipeline. The daemon would not generate its first proof without this exploration. Message 136 is the silent partner to every subsequent code change, the research that made the implementation possible.