research pipeline v2: topology-aware start + persisted draft
Three connected changes that turn "Start research" from a status flip into a real pipeline that produces a reviewable artifact: - Migration 0036: adds research_topics.topology_kind (default 'hub_spoke') and a new research_outcomes table (id, topic_id, version DESC, body_md, produced_by_run_id, created_at) so each run's final synthesis is versioned and persistent. - Wizard now has a topology picker in the Outcome step — hub_spoke / pipeline / hierarchical / star_moe — with copy that steers users to the right shape (Pipeline for research → distill → analyze → implement rosters, hub_spoke for the coordinator- and-specialists default). - start_topic reads the chosen topology_kind, parses it into a cm_topology::TopologyKind, and dispatches a per-shape coordinator prompt via build_coordinator_task. Pipeline explicitly tells stage 1 not to write the final artifact and propagates a "final stage MUST emit a complete markdown document with measurable acceptance criteria" instruction downstream. The graph builder is called with the topology the user actually picked instead of hard-coded HubSpoke. - topology_worker::freeze_research_outcome fires after every successful complete(). It looks up research_topic_id on the run; if set and final_output is non-empty, it inserts a new research_outcomes row (version auto-derived server-side via coalesce(max(version), 0) + 1). Best-effort — a DB hiccup logs but doesn't fail the run. - TopicDetail now includes topology_kind and latest_outcome. ResearchCanvas swaps in the outcome's body_md (rendered as pre-wrap markdown, versioned header, produced-at timestamp) whenever an outcome exists; the original prompt collapses into an "Original prompt" <details> below so it's still one click away. Pre-run topics still show the description as before. Follow-ups still open: reject-with-revision loop feeding the coordinator, publishing → published transition + real artifact export (md / pdf), and an approvals inbox surface for reviewers.
This commit is contained in:
@@ -237,27 +237,82 @@ export function ResearchCanvas({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
|
||||
<div style={sectionHeader}>Description</div>
|
||||
<pre
|
||||
style={{
|
||||
padding: 16,
|
||||
borderRadius: 10,
|
||||
background: "#101014",
|
||||
border: "1px solid rgba(255,255,255,.06)",
|
||||
color: "#eaeaee",
|
||||
fontFamily: mono,
|
||||
fontSize: 12.5,
|
||||
lineHeight: 1.6,
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{topic.description}
|
||||
</pre>
|
||||
</div>
|
||||
{/* Draft (post-run) or Description (pre-run). Once the pipeline has
|
||||
produced an outcome, reviewers should see that instead of the
|
||||
prompt they started from. The prompt is still shown as a
|
||||
collapsed reference below. */}
|
||||
{topic.latest_outcome ? (
|
||||
<>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<div style={sectionHeader}>Draft · v{topic.latest_outcome.version}</div>
|
||||
<div style={{ fontFamily: mono, fontSize: 10, color: "#6a6a72" }}>
|
||||
produced {new Date(topic.latest_outcome.created_at).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
<pre
|
||||
style={{
|
||||
padding: 16,
|
||||
borderRadius: 10,
|
||||
background: "#0d0d10",
|
||||
border: "1px solid rgba(201,138,240,.2)",
|
||||
color: "#eaeaee",
|
||||
fontFamily: mono,
|
||||
fontSize: 12.5,
|
||||
lineHeight: 1.6,
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
margin: 0,
|
||||
maxHeight: "62vh",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
{topic.latest_outcome.body_md}
|
||||
</pre>
|
||||
</div>
|
||||
<details>
|
||||
<summary style={{ ...sectionHeader, cursor: "pointer" }}>Original prompt</summary>
|
||||
<pre
|
||||
style={{
|
||||
padding: 14,
|
||||
marginTop: 8,
|
||||
borderRadius: 10,
|
||||
background: "#0a0a0c",
|
||||
border: "1px solid rgba(255,255,255,.05)",
|
||||
color: "#8a8a92",
|
||||
fontFamily: mono,
|
||||
fontSize: 11.5,
|
||||
lineHeight: 1.55,
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{topic.description}
|
||||
</pre>
|
||||
</details>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
|
||||
<div style={sectionHeader}>Description</div>
|
||||
<pre
|
||||
style={{
|
||||
padding: 16,
|
||||
borderRadius: 10,
|
||||
background: "#101014",
|
||||
border: "1px solid rgba(255,255,255,.06)",
|
||||
color: "#eaeaee",
|
||||
fontFamily: mono,
|
||||
fontSize: 12.5,
|
||||
lineHeight: 1.6,
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{topic.description}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stage explainer + primary action */}
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
|
||||
Reference in New Issue
Block a user