Skip to main content
When you run research, this is what you get back — and what every later step reads from. Each one is a Pydantic model in metalworks.contract, the part of the API you can depend on. There aren’t many, and they all connect through one report.

The conversations behind a report

Every source — Reddit, Hacker News, the web, your own — produces items in the same two shapes, so the rest of metalworks doesn’t care where a quote came from:
  • CorpusRecord — one thing people are talking about (a Reddit post, an HN story, a web page): id, source, url, title, text, author_hash, engagement, created_at, plus an extra map for anything source-specific (subreddit, domain, rating…).
  • CorpusComment — a comment under a record (a reply), the same fields plus a parent_id.
A quote in a report points back to the exact record or comment it came from, so you can always trace a claim to the real conversation. See your research data.

The demand report and what’s inside it

mw.research(...) returns a Research bundle; the report itself is on .demand. Everything else you generate later reads from this one report.
  • DemandReport — the output of demand research: a one-line demand_summary (demand strength — the go/no-go is assess’s job), the ranked_clusters (the real needs people voiced), web web_findings, and market sizing. (audience_profile is currently always None — demographic inference was cut.) If a best-effort stage degraded, partial is set with a plain-language caveat. version, lineage_id, and parent_report_id track a report’s earlier versions when you update it.
  • InsightCluster — one ranked need. Carries a claim (the need, in plain words), distinct_author_count (how many different people raised it), breadth_count / breadth_unit (the same idea across sources — different people, or different sites for web pages), mention_count, a signal chip, a demand_score that ranks by how many people care over how viral one post was, and the quotes behind it.
  • ResolvedCitation — a verbatim quote. Its text is the exact text of a real comment, and it carries source_url (open it and read it yourself), source / source_name (e.g. reddit / r/Supplements), and engagement. The quote text and link are stored right on it, so a report makes sense on its own — even handed to someone without your saved data. A cluster with zero verified quotes never ships: metalworks drops anything it can’t back with a real quote.
  • WebFinding — a fact pulled from the web. Its source_url comes from the search tool’s citation data, never from model prose. No source, no finding.
  • SegmentChoice / CandidateWedge — the forks the report surfaces instead of silently collapsing: distinct audiences you could target (each with an overlap guard so near-identical ones aren’t offered as a real choice) and the narrowest things someone would pay for (each tied to the clusters behind it). The validation loop’s PIVOT aims at one of these. Deterministic callers read report.default_segment / default_wedge; interactive ones set chosen_*.
  • ReportDiff — what changed between two versions of a report: count deltas (threads, distinct voices, clusters, source distribution) plus themes added / faded / shifted. You get one back from mw.refresh(...) or metalworks research diff. See the corpus.
The input is a ResearchBrief — the question, the subreddits to cover, success criteria, and a relevance rubric. You rarely build one by hand: pass a question string straight to .research(), or let Metalworks().plan(prompt) assemble one for you.

Everything points back to the report

Positioning, the design system, the build spec, and launch copy are all derived objects. Each one is generated from the report and links its claims back to the same quotes:
  • PositioningBrief — your angle: who it’s for and why it’s different, built from the unmet needs in the report.
  • CompetitorMap — the rivals to beat, each gap tied to a real complaint someone posted.
  • BuildSpec — a feature list where every feature maps to a real need (anything that can’t be tied to a quote is dropped), plus the surface to build on (web, mobile, CLI… — chosen with a one-line rationale, or pinned) and the Screens you need, each mapped to real features.
  • ChannelStrategy / Channel — the test→focus channel experiments, each routing_signal tied to a real corpus entity; plus the per-channel assets and plan derived from them — ChannelAsset / AssetPart (channel-shaped drafts), DataReportAsset / DataReportItem (a corpus-derived data report with real counts + permalinks), GeoPlan (with ParticipationTarget / CitabilityProbe / AnswerBrief), DistributionPlan (with Push / Stream), ChannelMetric / ChannelResult, and the build-feed LoopRequirement / ConversionSurfaceRequirement. Every claim-bearing line is backed by a quote; metalworks never posts.
  • Landscape / ExistingSolution — the full “what exists today”: the CompetitorMap plus real shipped products (Product Hunt / web) matched to demand clusters with their traction.
  • Assessment / ForkVerdict — the GO / PIVOT / NO-GO verdict, a deterministic gap over relative demand × landscape (the model only writes the rationale); a PIVOT carries the under-served fork to aim at. fork_verdicts is the un-collapsed per-fork answer (each fork’s GO/NO-GO + demand band + confidence); gap carries the prevalence/percentile the strength self-calibrated from. ValidationResult is the validation loop’s outcome + decision log.
So whatever you generate, the chain runs from a real comment all the way to the line on your landing page — see why you can trust the output.

Reddit objects

The Reddit tools search, read, and (carefully) draft replies.
  • RedditPost / RedditComment — what a thread looks like: title, body, score, permalink, and a salted author_hash (never a raw username).
  • SubredditIntel — community metadata: description, subscribers, rules, top posts.
  • Opportunity — a thread metalworks found, plus a drafted reply and its compliance result. Nothing is ever posted from one without your explicit, per-action approval.
  • ComplianceVerdict — the result of the offline gate over a draft: pass_, violations, confidence.
  • DiscoveryContext — where you inject your own knowledge: voice_guidelines, winning_examples, pinned_notes, avoid, and personas.
  • Persona / PersonaSet — voice profiles keyed by account type. The background field must be authentic; fabricated backstories are not allowed.

How they connect

Both sets of objects run on the same swappable protocols — ChatModel, EmbeddingProvider, SearchProvider, CorpusReader, and the typed storage repos. See Extending metalworks for the protocols, and the Protocols reference for their exact shapes.