Based on AgentRun release · Synced .
@parcha/agentrun-decisions
Published to npm from the next release (@latest), with AgentRun Server, which uses it. Until then it installs from this repository.
Third-party decision models for the AgentRun DSL's decision nodes (judge, pick, sift, a judged route, ask and a verify clause). Each is a DecisionProvider, the seam in @parcha/agentrun-dsl, and runs through judgeFromProvider, with no agent harness. Jev's provider is jevProvider in @parcha/agentrun-jev.
Parallel Decisions
parallelProvider() asks Parallel Decisions (POST https://api.parallel.ai/v1/decisions): yes/no (noul), choice and score questions about a JSON state, answered with probabilities and grounded in live web evidence.
import { judgeFromProvider, runWorkflow } from '@parcha/agentrun-dsl';
import { parallelProvider } from '@parcha/agentrun-decisions';
const result = await runWorkflow(workflow, input, { runJudge: judgeFromProvider(parallelProvider()) });
The key is PARALLEL_API_KEY, read by name when a request is made, or apiKey. With neither, the request fails with a DecisionProviderError of code configuration that names the variable, and nothing is sent.
What it does with Parallel's answers:
Types and limits. It answers
noul,choiceandscore. Before sending, it refuses asinvalid_requestanything over Parallel's documented limits: more than 100 questions, a choice outside 2 to 255 options, a score outside 2 to 10 levels, a question that renders past 5,000 characters, or a state over 96,000 bytes. A choice option with no description is described by its own name.Model. It asks Parallel's
fastmode and declares the one model it serves,parallel-fast. A node that pins another model is refused before the run starts when the host passes its providers to the run's checks, and asinvalid_requestbefore anything is sent otherwise.modes(defaultPARALLEL_MODES,["fast"]) lists the modes a node may pin asparallel-<mode>, for one Parallel adds before this package does; a node that pins none asks the first.Refusals. Parallel may decline a question (
{ "type": "refusal" }). It stays a refusal: the DSL never decodes it to a value. The run escalates for a person (decision_refused), or a route takes itsunsurebranch.Confidence. A choice or score that comes back without a confidence gets one derived from its probabilities, and the decision receipt's
confidence_sourcesays so.Evidence. Parallel's citations apply to the whole request, not to one answer, so each is evidence with
scope: "call".Grounding. Parallel answers from live web evidence as well as the state it is given. A claim the web knows nothing about (a private or fictional fact) can score lower than the state alone supports, with citations to unrelated pages. For a judgment that must rest on the state alone, use Jev (
jevProvider), which judges only what it is given.Cost. The response carries no cost, so the answered request is booked at Parallel's list price,
PARALLEL_LIST_PRICE_USD($0.001 per request), withcost_basis: "estimate". So is one sent whose outcome was lost (below). Itsdecision_idis theprovider_request_id.Replay. The API takes no idempotency key, so the provider declares
replay: "never": a paid request is never re-sent unasked. It is sent again only when it provably never reached Parallel (no DNS answer, no connection, no TLS handshake), or on a 429 that says when to retry (Retry-After). A 408, a 5xx or a connection lost after sending is sent once and failsoutcome_unknown, booked at list price as an estimate. The run then escalates the decision for a person as interrupted (decision_interrupted), and it is never asked again.Errors. Each one is a
DecisionProviderError:- 401 and 403 are
auth; - 400 and 422 are
invalid_request, with Parallel'sref_idasrefIdand the body dropped; - a 429 is retried after its
Retry-After, and israte_limitedwithout one or once the attempts are spent; - a 408, a 5xx or a connection lost after sending is
outcome_unknown, withcostUsdandcostBasison the error; - a request that never reached Parallel is retried with backoff, then
connection; - a body that is not JSON, or answers that do not match the questions, is
invalid_response; - the host's own cancel is
aborted. The deadline (timeoutMs, 30 s by default, or the host's shorter one) ending a request in flight isoutcome_unknown, booked at list price, since Parallel may have answered it. One that ends a wait between attempts, with nothing in flight, istimeout.
No message carries the key, a header or a body.
- 401 and 403 are
OpenRouter Decisions
openrouterProvider() asks any of OpenRouter's Decisions models (POST https://openrouter.ai/api/v1/systemone), the System One request Jev takes ({ state, model, questions }), sent to the model the node's model names, such as perplexity/pplx-decider-v1.2, liquid/d1, cloudflare/clef or typesafe/jev-1.14. A node that names none is refused before anything is sent, unless the provider was given a default model.
import { judgeFromProviders } from '@parcha/agentrun-dsl';
import { openrouterProvider, parallelProvider } from '@parcha/agentrun-decisions';
const runJudge = judgeFromProviders({ providers: { openrouter: openrouterProvider(), parallel: parallelProvider() }, default: 'openrouter' });
The key is OPENROUTER_API_KEY, read by name when a request is made, or apiKey.
- Cost. The response's
usage.costis booked asreported; a response without one is an unknown cost (null, never 0). - Replay. As Parallel:
replay: "never". A request is sent again only when it never reached OpenRouter, or on a 429 withRetry-After(up tomaxAttempts, default 3). A 408, a 5xx, a connection lost after sending or a deadline in flight isoutcome_unknownat an unknown cost. - Images (experimental). OpenRouter takes JSON only, so a request's images go in the body as
assets({ type: "image", format, data_base64 }) with theTypeSafe-Danger-Zone-Imagesheader, and only to a model the public catalog (/v1/models?output_modalities=decisions) lists with image input (takesImagesoverrides the catalog). Jev reads an image token anywhere in the state or a question, and refuses an image over 1568 px on a side, which the provider refuses before sending. Every other model reads an image only as a top-level item of an array state: an object state whose tokens are whole top-level values goes as[{ the other fields, image_fields }, "<token>", ...], whereimage_fieldsnames, in order, the field each image stood in ({ invoice: "<i1>", receipt: "<i2>" }goes as[{ image_fields: ["invoice", "receipt"] }, "<i1>", "<i2>"]), and a token anywhere else is refused before sending. A request waits for that image check only within its own deadline and cancel. The catalog read behind the check is shared by every request and is not cut with one: it goes on under its own 10 s timeout, and its answer serves the requests after it (a read that fails is tried again by the next one). OpenRouter documents image assets as alpha, so the provider listsimageunderexperimental.inputs.
@parcha/agentrun-decisions/testing/faux-system-one is a fake System One endpoint for a host's tests: its answers are made up but stable (drawn from a hash of the model, the question and the state), it lists a small Decisions catalog, and it reads images as OpenRouter does. No request leaves the process.
Tests
npm test -w @parcha/agentrun-decisions runs on a fake fetch. It registers the DSL's provider conformance suite, replays 28 responses recorded from Parallel's decision tool and the live Parallel and OpenRouter responses of 2026-10-30 (five OpenRouter models) through the seam, and checks the request as sent, the limits and the errors. test/parallel.live.test.mjs makes three decisions on the paid REST path (about $0.003) and runs only with PARALLEL_API_KEY set; without it, it is skipped and says so.
License
Apache-2.0