ideate-core
v0.4.0
Published
A provider-agnostic, evidence-based ideation engine: independent multi-agent generation, blind→pool brainwriting rounds, embedding-dedup + clustering + split-axis convergence, and an evaluate→regenerate feedback loop — as a zero-dependency injectable func
Maintainers
Readme
ideate-core

Quick start
npm i ideate-core
# Zero-setup smoke test — folds human ideas with no model client or API key:
echo '{"context":{"slug":"demo"},"humanIdeas":["a seed idea","another"]}' | npx ideateThen wire your own model client to generate ideas — the minimal ideateCore call
is a few lines (full example below). The rest of this README explains
what the engine does and why; jump straight to Install for the API.
Use case: you need a pool of genuinely different ideas from an LLM — for a campaign brief, a product-naming pass, a strategy option set — not five rephrasings of the same idea with the temperature turned up.
Differentiator: most "ideation" wrappers are one model call with a "be creative" system prompt. "Ask the model for 5 ideas" reliably produces 5 idea phrasings. ideate-core instead engineers the diversity the way human brainwriting does: independent, blind generator agents (persona is the lever, not temperature — the research backs this), pooled through real build-on rounds, then optionally converged with embedding-dedup + split-axis scoring instead of one LLM-judge "best idea." It's a zero-dependency injectable function, not a framework — bring your own model client.
A provider-agnostic, evidence-based ideation engine — independent multi-agent generation, blind→pool brainwriting rounds, a divergent→convergent selection half, and an evaluate→regenerate feedback loop, as a zero-dependency injectable function. Not a framework, not model-locked: you bring the model client, the embedder, and the prompts.
What it does
Turns a domain context into a pool of idea candidates by running a panel of independent generator agents (each a separate model call with no shared context — the nominal-group analog), optionally running an expansion round over round one, and folding in any human-supplied ideas so they ride the same downstream gates.
Every research claim below (Wang et al. 2023, Meincke et al. 2024, Rohrbach 1968, …) is sourced in full — author + year + URL — in docs/ideation-method.md, alongside a defaults→evidence table.
- Independent multi-agent round 1 — N agents (default 5), each a separate blind model call. Diversity is engineered via per-agent levers — persona (default: pragmatist / contrarian / domain-expert / outsider-analogy / visionary), prompt strategy (chain-of-thought), and, where the model supports it, temperature. "Be diverse" alone fails; persona beats temperature as a lever (Wang et al. 2023; Meincke et al. 2024). Note: current Anthropic frontier models (Opus 5, Sonnet 5, Opus 4.8/4.7, Fable 5) reject
temperature/top_p/top_k; Haiku 4.5 still accepts them. The example adapters strip a rejected sampling param and warn (docs/ideation-method.md §2), so persona is the primary structural lever on frontier models. - Cross-provider panel — route each agent to a different provider/model via an injected
clientsmap orresolveClientresolver (Anthropic + OpenAI + xAI/Grok + …). No vendor SDK is baked in; heterogeneous models give real variance and sidestep self-preference bias (Wataoka et al. 2024). - Build-on rounds with a sharing policy — round 1 is blind; build-on rounds (2+) default to pool sharing: agents build on the shared, deduped pool, not their own seeds — real brainwriting / 6-3-5 (Rohrbach 1968; Paulus & Yang 2000). Dedupe-before-share is mandatory for pool rounds (a raw pool triggers fixation — Kohn & Smith 2011). Per-round
sharing,incubation,buildOnDirective, andmaxRoundsare all config-driven. - Human-idea folding — caller ideas are normalized into the candidate shape and merged; a near-identical human idea wins the dedup tie.
- Robust parsing — tolerates ```json fences, surrounding prose, and
{candidates|ideas|posts:[...]}wrappers; drops malformed candidates rather than throwing. - Convergence (opt-in,
ideate-core/converge) — the divergent→convergent second half: embedding-cosine dedup (default 0.83; collapses semantic near-dups a text key misses), clustering (k auto) so selection samples across themes, split-axis scoring (novelty ⟂ feasibility, never one "best" — Rietzschel et al. 2010), a cross-cluster shortlist, a human-rerank hook (LLM-judge is a filter, not a novelty ranker — Zheng et al. 2023), and a diversity metric vs a floor. The embedder is injected (offline-mockable). - Evaluate→regenerate feedback loop (opt-in,
ideate-core/feedback) — a Delphi-style controlled-feedback loop (Dalkey & Helmer 1963): an injected external evaluator (panelistis the intended first one) critiques the pool, and only the flagged ideas are targeted-regenerated against their specificdealKillers/keepReasons;keeppasses,killdrops,reviseregenerates, then the pool re-dedupes. The evaluator model must differ from the generators (self-preference bias — Wataoka et al. 2024). The feedback-in contract itself is provider-agnostic; seeexampleAdapterFromPanelistinlib/feedback.mjsfor a worked example of adapting one evaluator's output shape onto it — the adapter is illustrative, not a required or canonical format. - Global dedup, provider-agnostic injectable client + embedder (tests stay offline), zero domain code.
Install
npm i ideate-coreApache-2.0, published to the public npm registry (zero runtime dependencies, Node.js >= 20). Bring your own model client, embedder, and prompts.
Prerequisite — you supply your own complete() model-calling function (no API
client is bundled). Two shapes are contractual, and getting either wrong is
silently dropped — you get candidates: [] with no error thrown:
complete(req)must resolve to{ ok: true, text: string }. Anything else (a bare string,{ text }withoutok: true, or a thrown error) yields no candidate.req.promptis the string yourbuildRound1Promptreturned.- Each candidate in the model's JSON reply must be shaped
{ text: "..." }(a non-empty stringtext) — not{ title, body }. A bare JSON array, or a{ candidates | posts | ideas: [...] }wrapper, are all accepted; any object without a non-empty stringtextis dropped.
Input validation is the caller's job. ideate-core does not validate the
shape of input.context — the engine passes it straight through to your
buildRound1Prompt/prompt builders, so shape-validating context (and any other
input fields your adapter reads) is the calling adapter's responsibility, not the
engine's. This is intentional: the engine stays domain-agnostic and never throws on
your input.
import { ideateCore } from "ideate-core";
// ── You bring two functions; ideate-core calls YOUR model through them. ──
// 1) complete(req) MUST resolve to { ok: true, text: string }. `text` is the
// model's raw reply. Anything else is silently dropped (candidates: []).
const complete = async (req) => {
const text = await callYourModel(req.prompt); // your provider call
return { ok: true, text }; // ← required return shape
};
// 2) buildRound1Prompt({ context, stance, persona, ... }) returns the prompt
// string. Tell the model to reply with candidate objects each shaped
// { "text": "..." } — NOT { title, body }.
const buildRound1Prompt = ({ context, stance }) =>
`${stance ?? ""}\nGive 6 ideas for: ${context.brief}.\n` +
`Reply ONLY with a JSON array like [{"text": "first idea"}, {"text": "second idea"}].`;
// Single-client panel (the default 5 personas all route to one client):
const { candidates } = await ideateCore(
{ context: { slug: "demo", brief: "ways to promote a launch" } },
{ complete, buildRound1Prompt /* , buildRound2Prompt, normalizeExtra */ },
);
// Cross-provider panel — one agent per provider (each client is its own
// `complete(req) => { ok: true, text }`, same contract as above):
const { candidates: pool } = await ideateCore(
{ context: { slug: "demo", brief: "…" } },
{
buildRound1Prompt,
clients: { "claude-x": anthropicComplete, "gpt-x": openaiComplete },
agents: [
{ persona: "pragmatist", model: "claude-x" },
{ persona: "contrarian", model: "gpt-x" },
],
},
);CLI
A thin standalone CLI (bin/ideate.mjs, installed as ideate) wraps the same
engine for shell/pipeline use:
echo '{"context":{"slug":"demo"},"humanIdeas":["a seed idea"]}' \
| ideate --adapter ./my-adapter.mjs # ESM module exporting `deps` for ideateCore
ideate --version # print the installed version
ideate --help # full usageWithout --adapter the CLI runs fold-only: it folds humanIdeas from stdin
and prints them, no model client or API key required — useful for sanity-checking
the human-idea path before wiring a real adapter.
Integrations (example adapters)
ideate-core bundles a small set of optional, interchangeable example
adapters under integrations/. Each supplies a complete(req)
=> { ok, text } implementation you can drop into deps.complete — they are
worked examples, not core dependencies: the engine has no import-time
dependency on any of them, and your own HTTP client is equally first-class.
- headless-CLI (
ideate-core/integrations/headless-cli) — runscomplete()against a locally-authenticated headless Claude Code CLI session (claude -p --output-format json) instead of a metered API key, so any Claude Code user can ideate on their existing session auth. Fails loudly (preflight + throwingcomplete) when the CLI is missing/unauthenticated — never a silent empty pool. - subagent-dispatch (
ideate-core/integrations/subagent-dispatch) — maps round 1's N independent persona agents onto a host's own subagent / Task-dispatch primitive (one dispatch per persona), a natural fit for agent runtimes like Claude Code's interactive installs. Fails loudly (construction throws + preflight) when no dispatch capability is wired — never a silent empty pool.
Each adapter ships hermetic tests (the subprocess/dispatch primitive is injected, so no real CLI, agent runtime, or network is needed to pass CI).
Writing your own adapter? The package bundles a self-contained, user-invocable
skill — skills/adapter-authoring — that
walks through the complete() / candidate {ok, text} contract, a minimal
adapter from scratch, the loud-failure discipline, and both bundled adapters as
worked examples.
How it works (and why)
ideate-core is a small evidence-based pipeline that diverges then
converges — independent multi-agent generation, blind→pool build-on rounds,
embedding dedup + clustering + split novelty/feasibility selection, and an
optional Delphi-style evaluate→regenerate loop.
The full rationale lives in one place: docs/ideation-method.md is the single source of truth for why the engine is shaped this way — every design decision and default justified by a cited finding (author + year + URL), plus a "defaults & their evidence" table. Start there.
Honesty note: synthetic ideation is a drafting aid, not a substitute for real customer discovery — treat its shortlist as hypotheses to test with people, not answers.
Status
Developed by Kromatic for internal use, then open sourced (Apache-2.0) and published to public npm. The configurable multi-agent engine (independent generators + blind→pool build-on rounds, nominal-group / brainwriting style), the divergent→convergent selection half, and the generate→evaluate→regenerate feedback loop are all implemented (feature-complete) — see the method doc above.
Stability: ideate-core is pre-1.0 (0.x) — feature-complete, but the public API may still change before 1.0 (per the versioning convention: while the major version is 0, a 0.x.0 minor may carry breaking changes). See SECURITY.md and CONTRIBUTING.md; the exact published version is shown by the npm badge above.
