The interface needs more than a spinner
Field Notes turns context about a product into a research dossier, collected signals, and an analysis report. Those are different kinds of work with different failure modes. A repository can be understood even when a later research source is unavailable. A collection of posts can remain useful even when the model cannot turn it into a defensible report.
The interesting design decision is to make that structure visible. The backend keeps HTTP framing in main.py, orchestration in pipeline.py, and the actual stages in their own modules. That separation lets the interface follow the process without making the transport layer responsible for the research itself.
Events describe progress; results hold the work
The pipeline is an asynchronous generator. T0 emits events while preprocessing the supplied website, repository, and form. The orchestrator passes those events through and also captures the dossier event for later stages. It does not have to assemble a finished response before the user can see anything.
At the HTTP boundary, each typed event becomes a Server-Sent Events frame. The named event tells a client what kind of update it received; the JSON payload carries the data. This fits a mostly one-way workflow: the browser submits a request and then listens to the run.
Credential checks happen before streaming starts. This matters because, once the response has begun, a late exception cannot be expressed by changing the original HTTP status. The streaming wrapper instead emits an error event. A consumer must handle that event explicitly; a successful HTTP connection does not prove a successful analysis.
event: <event type>
data: <serialized event payload>
Failure belongs to a stage
If T0 produces no dossier, the pipeline emits a fatal error and returns. That is a real dependency: there is no meaningful research target without the initial context. Target selection in T1 has a different policy. If selection raises an exception, the pipeline emits a nonfatal error and uses fallback targets based on the dossier.
Analysis only runs when harvested posts exist and the request has not deliberately stopped at an earlier stage. Before calling analysis, posts are normalized into signals. A post is quotable when its body or one of its comments contains text. Empty material is filtered out rather than treated as evidence.
If analysis or validation fails, the system can preserve T0 and T1 work. The final FieldNote contains the dossier, harvest, and an optional report. “No report” is a state the interface needs to explain, not an excuse to erase the evidence the user already waited for.
Validation is a step, not a guarantee
The analysis path runs findings through a validation step and associates validation results with finding titles before building the report. That provides a place to inspect evidence. It does not turn model output into ground truth. The distinction matters especially when a polished report makes an uncertain conclusion feel settled.
I would keep source material reachable from each finding and expose incomplete or failed stages next to the report. The code’s event model makes that possible, but the architecture alone cannot tell us whether every conclusion is supported. That requires evaluating actual runs.
Where the prototype needs a stronger foundation
Reports are saved as JSON files under out/reports using a normalized slug. This is easy to inspect and useful for a small prototype. A second report with the same slug overwrites the first; concurrent writes and multiple server instances need a more deliberate persistence model.
For a durable service, I would add a run identifier, append-only stage records, atomic report writes, and a replay cursor for interrupted clients. Those are proposed next steps, not features already present. I would also distinguish cancellation from failure so a disconnected browser cannot leave expensive work running accidentally.
The existing pipeline tests replace stages with controlled outputs. They are useful for checking orchestration and event forwarding, but they are not an evaluation of external sources or model quality. The next test layer should deliberately fail each stage and check that the user gets exactly the usable work that survived.