from metalworks import Metalworks — one object you construct, and everything hangs off it.
This page is the full reference: every public method, its signature, what it takes, and what
it returns. New to the library? Start with the walkthrough; come here when
you need the exact surface.
The one rule that shapes every return type: nothing is invented. Every claim carries an
EvidenceRef that resolves to a real quote or web finding on the report’s evidence
list. When a method can’t ground something, it drops it or marks the result partial — it
does not fill the gap with plausible text.
Constructing the client
| Argument | What it does |
|---|---|
model | The main model, as "provider/model" or "provider:model". If omitted, resolved from the first env key present: ANTHROPIC_API_KEY → OPENAI_API_KEY → GOOGLE_API_KEY. |
fast_model | A cheaper model for triage/filtering. Falls back to model when unset. |
chat / fast_chat / embeddings / search / reader / comments | Pass a fully-built object to swap any single layer (e.g. an OpenAI-compatible endpoint, a custom corpus). See Extending metalworks. |
store | Where runs/reports persist. Defaults to your project’s .metalworks/corpus.db if one exists, else in-memory. See Projects & memory. |
anthropic, openai, google/gemini are native; anything
else (openrouter/..., meta-llama/...) routes through an OpenAI-compatible endpoint. To point
at a local or custom endpoint, construct an OpenAIChatModel(base_url=..., api_key_env=...) and
pass it as chat=.
Research
.research(...)
Research bundle.
| Argument | Default | Notes |
|---|---|---|
question | — | A plain sentence, or a fully-built ResearchBrief (from .plan()) for full control. |
subreddits | planner picks | Names without r/. Omit to let the planner choose. |
time_window_months | 12 | How far back the corpus window reaches. |
per_sub_limit | pipeline default | Cap submissions pulled per subreddit. |
max_findings | 10 | Max demand clusters to surface. |
.metalworks/runs/<report_id>/. Casual use (no project) leaves no footprint.
.plan(prompt)
ResearchBrief you can inspect, edit, and pass straight back into .research(brief).
The Research bundle
research() returns a frozen Research. The demand report is on .demand; .evidence is the
flat, resolvable evidence list every downstream method’s EvidenceRefs point at.
| Attribute | Type | Notes |
|---|---|---|
.demand | DemandReport | The report (see Data model). |
.evidence | list[EvidenceRecord] | The grounded evidence, surfaced for resolving refs. |
.competitors / .positioning | … | None | Reserved accessors; None today (forward-compatible). |
.demand:
| Field | Type | Meaning |
|---|---|---|
verdict | str | None | The go/no-go summary line. |
ranked_clusters | list[InsightCluster] | The demand clusters, ranked by demand_score. |
total_distinct_authors | int | Distinct people across the corpus (the honest base rate). |
price_finding | PriceFinding | None | Price band, if the corpus carried price signal. |
segments / audience_profile | … | Inferred audience, when grounded. |
web_findings | list[WebFinding] | External findings, each with a source URL. |
partial / caveat | bool / str | None | Set when the signal was too thin to be confident. |
InsightCluster carries rank, claim, demand_score, distinct_author_count,
mention_count, signal, and quotes (verbatim ResolvedCitations with text, source_url,
source/source_name, author_hash, engagement).
The stage methods
Each method below runs on a finishedresearch() bundle (or a bare DemandReport). They’re
the Research → Design → Build → Launch → Grow arc. All read from the same report, so every
output traces back to the same evidence.
Research stage
positioning returns a Dunford-style wedge + price hypothesis. competitors returns
direct/adjacent/status-quo rivals, each gap backed by a real complaint. Both set partial=True
with a caveat when there’s no defensible wedge / the named set couldn’t be grounded.
Design stage
surface picks sdk/web/mobile/cli/… with a cited rubric; ux sketches 3–5 screens,
each flagged validated (evidence-backed) or hypothesis. site builds marketing copy where
every load-bearing line is a verbatim quote; render_site turns it into a self-contained
index.html string.
Build stage
build_spec maps demand to a feature list (each feature tied to ≥1 real quote; ungrounded ones
are dropped). scaffold writes a cite-or-die build harness under dest and returns the paths
written. metalworks writes the spec, not the product — your coding agent builds from the
scaffold.
scaffold raises ValueError if spec.report_id doesn’t match the research bundle.
Launch & Grow stages
launch drafts channel-native assets (Product Hunt / Show HN / X), each claim carrying a
ClaimCitation with exact character spans — it never posts. channel_plan is a
deterministic, human-executed checklist (every step is requires_human=True). content_plan
is deterministic and zero-key: one page per demand cluster, with FAQ blocks and citation
targets.
.deps
build_positioning_brief(mw.deps, report) —
without rebuilding the providers by hand.
.reddit — Reddit surfaces
Reads are zero-key; the rate limiter is shared across all calls on one client.
.inbox and .post are the authenticated surfaces. .post is gated: it runs the
deterministic compliance check first and refuses on a block verdict (returning a failed
PostResult), and every attempt — blocked or sent — is appended to
~/.metalworks/post-log.jsonl. It needs REDDIT_CLIENT_ID / REDDIT_CLIENT_SECRET and a
previously connected account. See Reddit engagement.
.discovery — find threads, draft replies
run is the full loop (search → filter → generate → compliance-gate) and returns draft
Opportunity objects — it never posts. filter and generate are the building blocks if
you want to drive the loop yourself.
Persona.background must be authentic — fabricated personas and invented backstories are
prohibited by the usage policy.
Exceptions
All inherit fromMetalworksError, which carries an optional fix hint and docs_url. Import
them from metalworks.errors.
| Exception | Raised when | Fix |
|---|---|---|
MissingExtraError | A feature needs an optional dependency | pip install "metalworks[<extra>]" |
MissingKeyError | A required API key isn’t set | Export the env var it names |
RateLimitedError | An upstream returns 429 after retries | Back off and retry |
StructuredOutputError | A model can’t match the required schema | Retry, simplify, or use a stronger model |
GroundingUnavailable | The model can’t do grounded web search | Use a grounding-capable model or pass a SearchProvider |
ReauthRequiredError | A Reddit OAuth token expired/was revoked | metalworks reddit auth login |
EmbeddingModelMismatch | A cached index was built with a different embed model | Re-embed, or switch back to that model |
StoreError / RedditError | The storage backend / Reddit API failed | Check the backend / credentials |
See also
- Projects & memory — how runs and artifacts persist so commands chain.
- Data model — the objects you get back, field by field.
- CLI — the same surface from the command line.
- Extending metalworks — swap any model, store, or corpus.