npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@spendgraph/workflows

v0.6.0

Published

Ready-made workflows assembled from the spendgraph packages.

Readme

@spendgraph/workflows

Ready-made workflows assembled from the spendgraph packages. One so far: noesis, a question-answering loop that decides how much of itself a question is worth.

npm install @spendgraph/workflows
import { run } from "@spendgraph/workflows";

const result = await run(client, llm, "Has the price changed since March?", {
  tools: bus,
  context: { now: new Date().toISOString(), locale: "IN / INR" },
});

result.text; // the answer, as a person reads it
result.status; // "answered" | "abstained" | "escalated"
result.route; // which of the four it earned

client is a PromptClient and llm an Llm. The two are separate on purpose: the prompt client pulls the stored wording and records what the call cost, and the model client makes it.

Three routes, chosen in code

Stage 0 is a router prompt that returns five signals — difficulty, stakes, confidence, ambiguity, tool need — and never a route. routeFor in src/noesis/route.ts turns those into one, from thresholds you can move after an eval run without touching a prompt.

| route | what it runs | when | | --- | --- | --- | | direct | 6C alone | easy, unambiguous, already known | | standard | frame, split, plan, execute, check, close | everything in between | | full | standard plus four audits | high stakes, expert difficulty, or real ambiguity |

Stakes are tested first, because that is the one signal where being wrong is expensive in only one direction: an easy question answered the long way costs tokens, a critical one answered directly costs more than that.

The stages

| | | | | --- | --- | --- | | 0 | triage | five signals | | 1 | question brief | the question interrogated, never answered | | 2 | decompose | ordered sub-questions, at most seven, depth one | | 3A | plan candidates | approaches, the one chosen, the steps that follow | | 3B | premortem | the plan assumed dead, and what killed it | | 3C | cqot gate | eight questions of the plan | | 4 | execution | code — every sub-question through the tool its hint named | | 4A | think checkpoint | predicted against actual, against 3A's own tolerance | | 4B | discriminating test | the one test that tells the live hypotheses apart | | 5A | cove questions | the verification questions the draft invites | | 5B | cove answers | each answered blind | | 5 | verification | code — 5A and 5B paired back together by id | | 5C | standards review | the answer against eight standards | | 6A | closure checklist | the last questions before it may close | | 6B | red team | the answer attacked | | 6C | toulmin | the artifacts compiled into a claim and its qualifier | | R | reflection | the earliest wrong decision behind a failed gate |

Two of them are code and not model calls. Stage 4 dispatches and does not reason, because deep_recall and web_search already return a synthesised answer. Stage 5 pairs 5A's claims back to 5B's blind answers, and it has to be code: whatever does it sees both the claim and the answer written without it, and a model given both is no longer a blind check of anything.

Choosing models

stage sets the model every stage runs on. stages overrides it by name, for the stages worth paying more or less for.

await run(client, llm, question, {
  stage: { model: "claude-haiku-4-5" },
  stages: {
    plan_candidates: { model: "claude-sonnet-4-6" },
    red_team: { model: "claude-opus-4-8" },
    answer: { model: "claude-sonnet-4-6" },
  },
});

The keys are the stage names from the table above with the spaces closed up — triage, question_brief, plan_candidates, red_team, answer — and STAGE_KEYS maps every id to its own. triage and answer also have opts.triage and opts.answer, which take more than a model — a current task, and the answer's escalating tiers — and win where both are set.

params does not follow a model it was not written for. A stage naming its own model gets its own params or none: Haiku takes temperature, Sonnet 5 rejects it outright, and one shared request body fails on whichever it was not written for. A stage overriding only params still merges over stage — except answer, which escalates through tiers, so its params reach it only alongside a model.

What a run cost

usage is the model spend: six numbers rather than two, summed across every stage and every retry, split byStage and byModel because the answer can cascade. A back edge refunds nothing.

usage.tools is what the tools reported, and it is deliberately not folded in — a tool call is somebody else's API at somebody else's rates and is no rollout of ours.

result.usage.inputTokens;              // the model side
result.usage.byModel["claude-sonnet-4-6"];

result.usage.tools.calls;              // the tool side
result.usage.tools.failed;             // and what did not come back
result.usage.tools.costMicros;         // where a tool priced itself
result.usage.tools.byTool.web_search;  // tokens, keyed by the tool that ran

Bill both or you undercharge every run that touched the web: web_search reports Perplexity's four token counts and deep_recall reports micro-USD, and stage 4 records zero tokens of its own.

The signals

Stage 0 estimates five numbers and never a route. The prompt block that produces them is a versioned prompt on the server (triage-router-v1-81c2cc), which nothing in this repository can gate — a revision promoted there takes effect on every consumer without a deploy.

The ledger

Every stage files its result, its rollouts and its tokens in one place. That record is what 6C compiles from, keyed by stage id and name together — "3A plan candidates" — because the model writes one of them back as grounds[].source_stage and either has to lead to the entry.

It also carries the retry budget. A gate that will not let the answer through spends one, runs R to find the earliest wrong decision, and cuts the ledger back to where the loop restarts, so nothing stale is compiled into the answer. What it cost stays: tokens were spent and rollouts were recorded, and going back does not un-spend them. The nth retry escalates instead of looping again.

Asking, and stopping

Two different problems, so two mechanisms.

onAsk is asking. Stage 1 is the only stage that reaches for a person mid-run, and only when it says the asker aimed at the wrong thing; a replacement question restarts the framing with it, and with nobody to ask the run proceeds on the question as put.

result.status is stopping. answered is an answer to act on. abstained is 6C saying the grounds supported no claim. escalated is a gate, or a spent retry budget, asking for a person — and it outranks an abstention, because a gate that asked for somebody said so about the whole run. Both still carry text, because saying what is not known is worth reading.

onDeliver runs once, after the loop, and only for answered. Anything that renders a deliverable belongs there: nothing downstream reads it, no gate can check it, every back edge would make it again, and generating a finished-looking document out of an abstention is the worst thing this loop could produce.

Where it sits

@spendgraph/harness holds the patterns — refine, route, chain, parallel, loop, orchestrate, cascade — each general and unaware of any particular job. This package holds finished workflows built out of them, each solving one job end to end. Nothing that belongs to all seven ever lands here.

Nothing here talks to the database or holds a provider key. run() is handed its clients and its ToolBus; where a workflow needs the server it goes through @spendgraph/sdk.

Layout

src/
  index.ts                the public surface
  noesis/
    route.ts              the thresholds, and nothing else
    stage.ts              pull, ask through {question}, force the schema
    question.ts           askThrough — the unmigrated-prompt floor
    router/ producers/ gates/ repair/    one directory per stage
    execute/ draft/ verify/              the code stages, and what they render
    run/                  the ledger, the routes, and the gate loop
    tests/                excluded from dist
GAPS.md                 what is wrong, what was fixed, what needs a decision

Not yet met a real prompt

Every test runs against a fake PromptClient and Llm, which is what made building this cheap. Two assumptions carry the design and neither has been exercised against the server:

  1. The stored prompts render {question}. Where one does not, askThrough overwrites the box and warns once on stderr. The run still succeeds and quietly answers whatever was saved on the prompt, so a warning per stage on the first live run is the signal, and publishing is the fix.
  2. 6C reads the artifact keys back, so a citation names the stage that produced its evidence.

examples/ runs each route against the real prompts for a few pennies. Start with 05-standard.mjs: it is the cheapest that looks anything up, and it exercises triage, the dispatcher, the ledger and 6C. The routes that look something up need one of the two lookups configured — LOCUSGRAPH_AGENT_SECRET for deep_recall, or PERPLEXITY_API_KEY for web_search — and say so rather than half-running.

License

Apache-2.0