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
Provider refs route by namespace:
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.
If you’re inside a project, the run is automatically persisted to
.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.
Key fields you’ll read on
.demand:
Each
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, with partial=True + a caveat
when there’s no defensible wedge. (The competitive map lives inside landscape() below — there’s
one “what exists today” door now, not two.)
The validation loop
The discovery loop — frame an idea, weigh demand against what already exists, get an honest verdict. See the validation loop for the full picture.ideate(idea-first) sharpens a raw idea into a testable hypothesis + aResearchBrief;ideate_from_evidence(evidence-first) surfaces a report’s forks — candidate wedges, else top clusters — as groundedIdeaSketches to pick from.landscapeis the full “what exists today”: the nestedcompetitor_map(direct/adjacent/status-quo rivals, each gap cited and each tagged with the clusters it competes for) plus an empirical existing-solutions scan (real shipped products, matched to demand clusters).assessis the heart: a deterministic GO / PIVOT / NO-GO gap over demand × landscape (the LLM only writes the rationale). PIVOT carries apivot_target— a real fork id to aim at. A partial landscape never yields a hard GO.validateruns the loop headlessly: ideate → demand → landscape → assess, looping on PIVOT. It pulls the corpus once and reuses it for every in-corpus pivot — a fresh pull happens only if a pivot leaves the corpus.
Design stage
design authors a grounded-but-directional DesignSystem (an aesthetic direction + a SAFE/RISK
choice per dimension), read from a real browser teardown of competitor sites where available and
honest about its grounding tier.
Build stage
build_spec maps demand to a feature list (each feature tied to ≥1 real quote; ungrounded ones
are dropped), picks the surface with a one-line rationale when surface="auto" (the default), and
sketches a feature-grounded screen skeleton. 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.
Distribution stage
channel_strategy routes the report’s real named entities + signals into test→focus channel
experiments (D2) — every routing_signal traces to a real corpus entity. distribution_requirements
emits the embedded loops + conversion surface distribution designs INTO the product as BUILD
requirements (D3): a LoopRequirement per embedded_loop channel (loop kind → concrete build
requirements grounded in the channel’s signal) plus a ConversionSurfaceRequirement for the
destination the channels point at — feed it to build_spec(..., distribution_requirements=...).
channel_assets drafts channel-SHAPED, drafting-only assets per channel (D4) — one ChannelAsset
per selected channel, shaped to its surface (Product Hunt tagline + maker comment; Show HN title +
first comment; X thread; LinkedIn carousel); demand/factual claims are grounded, persuasive hooks
free, platform invariants enforced — it never posts. data_asset projects the report into a
corpus-derived data report (D5), the on-brand flagship asset: a deterministic ranking of the
report’s clusters carrying their REAL distinct-author / mention counts, real permalinks, and a
verbatim quote per row; kind picks the framing (complaint_index | feature_ranking |
state_of). distribution_plan
sequences those channels into a campaign (D7): pushes (the spike-cadence channels placed into
concentrated launch moments, their timing READ from a deterministic playbook table — Product Hunt
12:01am PT Tue/Wed, Show HN Tue-Thu 8-10am, … — never invented hours; one all-day-attention channel
per day, never Product Hunt + a big HN push the same day; pre-launch warming → push week → a 30-day
post step) + streams (the compounding-cadence channels run continuously). channel_metrics
closes the loop (D8): one ChannelMetric per channel — its success_metric (what “worked”) + its
instrumentation (how to track it), read deterministically by surface_type; record a
ChannelResult per channel after the push and pass them as prior_results to
build_channel_strategy / plan_distribution to re-rank the next push (winners rise). geo is the
GEO / LLM-citability stream (D6): participation targets (real threads to engage, from the
report’s permalinks), citability probes (conversational queries to test you’re cited, from the
cluster claims), and answer-first answer briefs (grounded — each evidence_refs resolves against
report.evidence, stat_anchors carry the cluster’s real counts, ungrounded answers dropped).
distribution_engage is the participation/execution arm (D9) — the one channel metalworks can
OPERATE rather than merely plan: it takes one of geo’s participation_targets (a real thread) and
drafts a disclosed, founder-voiced reply for that exact thread, reusing the Reddit reply machinery +
the single voice system’s invariants (no upvote ask, native-first, no AI tells), then runs the shared
honesty gate (heuristic_check) over it — returning a ParticipationReply (the draft + the compliance
verdict + requires_human / posting_gated, both always true). All are drafting only — nothing
posts; a human posts a participation reply through the gated mw.reddit.post(...) path.
.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.
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.