@one-agent/reason
v0.0.11
Published
Structured reasoning interface and CLI for ONE.
Maintainers
Readme
@one-agent/reason
Structured reasoning interface and CLI for ONE.
Within this repository's Re in Act implementation, @one-agent/reason implements the required reason() interface: a bounded local judgment step that turns prompt text plus a required JSON shape into structured output.
Relevant public reference:
In the public spec, reason() is the only required interface. @one-agent/reason is this repository's reference implementation of that core contract.
Model Configuration
reason auth configures the model used by the reason CLI and by reason() calls inside the Reason-able Action Space.
This is separate from one auth:
one authconfigures the mainoneagent modelreason authconfigures the model used byreason()
By default, reason auth writes ~/.config/one/reason.json.
Environment variables still override file config. Common overrides include:
ONE_REASON_PROVIDERONE_REASON_MODELONE_REASON_OPENAI_API_KEYONE_REASON_OPENAI_BASE_URLONE_REASON_REASONING_EFFORT
REASONING_EFFORT controls how much reasoning/thinking the model does. Valid
values are off, low, medium, and high; when unset the provider's
default is used. one-reason forwards it the way each provider expects:
- OpenAI-compatible chat-completions endpoints receive the standard
reasoning_effortfield:low/medium/highpass through, andoffsends"none", OpenAI's documented "no reasoning" value. Models that honor it include OpenAIgpt-5.1+and Kimi K2.x; always-reasoning models (for example MiniMax M2) may ignore it or reject the parameter. - OpenAI receives
reasoningEffort: "minimal"foroff(the lowest effort the current AI SDK accepts) andlow/medium/highotherwise.minimalis not accepted by every OpenAI model, so treatoffas best-effort there too. - Anthropic enables thinking per model generation. Claude 4.6 and later plus
the Claude 5 family (Sonnet 4.6/5, Opus 4.6/4.7/4.8/5, Fable 5) receive
adaptive thinking (
thinking: { type: "adaptive" }) together with theeffortparameter, which is the only accepted form on Opus 4.7+/Claude 5. Older Claude models (4/4.1/4.5, Haiku 4.5, 3.x) receive the legacythinking: { type: "enabled", budgetTokens }form with a fixed thinking budget per level (2048/8192/16384 tokens forlow/medium/high).offsendsthinking: { type: "disabled" }, so models that default to thinking on (for example Sonnet 5) stop; this requires@ai-sdk/anthropic= 3.0.93 because older SDK versions silently dropped the field.
The fields above are not universal: some providers gate thinking with their own
body fields instead (for example Zhipu GLM uses
thinking: { type: "enabled" | "disabled" } and ignores reasoning_effort),
and some APIs reject values they do not recognize. Use a non-thinking model when
you need thinking guaranteed off. The scoped fallback is ONE_REASONING_EFFORT,
and it can also be set as REASONING_EFFORT in reason.json.
Example:
# Configure the model/provider used by reason()
reason auth
# Or use the namespaced binary
one-reason authUsage
cat build.log | reason --prompt "goal: detect failures" - '{"failed":false,"reason":""}'The structure argument is required and must be valid JSON.
TypeSafe Jev providers
Jev is for control-node judgments (route / retry / boolean / score). It returns noul/choice/score probabilities only — it cannot generate free-form summaries or arbitrary structured prose.
Summary / synthesis reason() calls (e.g. { summary, findings, next }) stay on the
LLM streamText + submit_result path. When a Jev backend is configured, reason()
falls back to the LLM for free-text examples in mode: "jev" (see mode below).
Standalone Jev backend (recommended)
You can keep your primary LLM (ONE_REASON_PROVIDER=openai-compatible, anthropic, etc.)
intact and configure Jev alongside it as an independent fast-decision engine. These
variables only make Jev available; ONE_REASON_MODE controls the routing policy:
# Toggle Jev decision backend on or off (default: 1 if configured)
export ONE_REASON_JEV_ENABLED=1
# Execution mode: llm (default, backward-compatible), or jev
export ONE_REASON_MODE=jev
# TypeSafe official:
export TYPESAFE_API_KEY=apikey_...
# Or Vercel AI Gateway:
export AI_GATEWAY_API_KEY=vck_...llm is the default for backward compatibility. jev enables automatic routing:
decision-shaped examples go to the configured Jev backend and free-text/synthesis
examples fall back to the LLM. Having Jev credentials present does not itself
change the mode.
When TYPESAFE_API_KEY or AI_GATEWAY_API_KEY is present, reason() auto-detects the Jev
provider. You can also explicitly specify ONE_REASON_JEV_PROVIDER=typesafe or gateway.
CLI mode & switches
In the CLI, control behavior via flags:
# Force Jev evaluation
reason --mode jev "The user wants a refund" '{"is_refund":false}'
# Force LLM generation
reason --mode llm "Summarize findings" '{"summary":""}'
# Explicitly use Jev routing for decision-shaped examples
reason --mode jev "Check status" '{"ok":false,"route":{"$jev":"choice","options":["approve","manual","deny"]}}'TypeSafe official API details
export ONE_REASON_JEV_PROVIDER=typesafe
export TYPESAFE_API_KEY=apikey_...
export ONE_REASON_JEV_BASE_URL=https://api.typesafe.ai/v1 # optional default
export ONE_REASON_JEV_MODEL=jev-latest # optional defaultCalls POST {baseURL}/systemone with Bearer auth. Question types on the wire are
noul | choice | score.
Vercel AI Gateway details
export ONE_REASON_JEV_PROVIDER=gateway
export AI_GATEWAY_API_KEY=vck_...
export ONE_REASON_GATEWAY_BASE_URL=https://ai-gateway.vercel.sh/v4/ai # optional default
export ONE_REASON_GATEWAY_MODEL=typesafe-ai/jev # optional defaultGateway evaluation calls:
POST {baseURL}/evaluation-model
with headers:
ai-evaluation-model-specification-version: 4ai-model-id: <model>ai-gateway-protocol-version: 0.0.1Authorization: Bearer <key>
Question types on the wire are boolean | choice | score (Gateway uses
boolean instead of TypeSafe's noul). No ai@7 dependency is required.
Legacy Jev provider configuration
If you explicitly set ONE_REASON_PROVIDER=typesafe or gateway, reason() continues
to support this. When free-text synthesis is needed in mode: "jev", it falls back to
ONE_REASON_FALLBACK_* or ~/.config/one/one.json:
export ONE_REASON_PROVIDER=typesafe
export ONE_REASON_OPENAI_API_KEY=...
export ONE_REASON_FALLBACK_PROVIDER=openai-compatible
export ONE_REASON_FALLBACK_OPENAI_API_KEY=...
export ONE_REASON_FALLBACK_MODEL=gpt-4.1-minireason(prompt, example, options?)
The third argument is optional and backward compatible:
import { reason } from "@one-agent/reason";
// Decision / control node → Jev (when PROVIDER=typesafe|gateway)
await reason("goal: should we retry?", {
retry: false,
route: {
$jev: "choice",
options: ["a", "b", "c"],
},
});
// Summary / synthesis → LLM fallback under Jev mode
await reason("goal: summarize the log", {
summary: "",
findings: "",
next: "",
});
// Force LLM even if PROVIDER is typesafe
await reason(prompt, example, { mode: "llm" });
// Force Jev (errors if example has free-text)
await reason(prompt, { retry: false }, { mode: "jev" });
// Optional state / threshold overrides
await reason(prompt, { ok: false }, {
mode: "jev",
state: { goal: "...", observation: "..." },
booleanThreshold: 0.6,
});| mode | Behavior |
| --- | --- |
| omitted / llm | Always use the LLM path (backward-compatible default) |
| jev | Decision-shaped example → Jev; free-text example or unavailable Jev → LLM fallback |
Example → question mapping (decision-shaped)
For Jev, prompt (or options.state) becomes evaluation state, and questions
are always derived from example:
| Example field | Jev question |
| --- | --- |
| boolean | noul / boolean |
| number | score (auto level 0 .. level N criteria) |
| plain string | not Jev → LLM synthesis / Jev-mode fallback |
| { "$jev": "noul"\|"choice"\|"score", ... } | explicit question |
Choice options must be declared explicitly in the example; a string value or string array is not reinterpreted as a choice set:
{
route: {
$jev: "choice",
options: ["approve", "manual", "deny"],
},
}This keeps reason(prompt, example) aligned with the example's JSON shape: an array
remains an array, and a string remains a string.
Score ranges can be declared explicitly without parsing prompt text:
{
risk: {
$jev: "score",
range: [0, 100],
instructions: "评估退款风险",
},
}The Jev backend still receives at most 10 score levels; the returned level is
mapped back into the declared range. Use levels (2–10) or custom criteria when
you need different score granularity.
Nested objects are flattened to dotted question ids and rebuilt into the example shape when answers are applied.
Errors 401 / 422 / 429 / 529 return clear messages; 429 and 529 are
retried lightly with backoff.
