Skip to content

Add experimental decision types and System One clients to Predict - #10463

Merged
isaacbmiller merged 45 commits into
mainfrom
feat/decide-system-one
Sep 24, 2026
Merged

isaacbmiller merged 45 commits into
mainfrom
feat/decide-system-one

Conversation

@isaacbmiller

@isaacbmiller isaacbmiller commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Add experimental decision types and System One clients to Predict

What this PR adds

  1. A TypeSafe backend for Predict. dspy.experimental.TypeSafe uses the TypeSafe SDK to call Jev through the existing lm= interface: dspy.configure(lm=TypeSafe("jev-latest")). DSPy translates the signature, inputs, demos, and per-field configuration into Jev’s state and questions request. No new prediction module or explicit adapter selection is required. Jev requires compatible output types; it is not a general-purpose text-generation backend.

  2. Native or rich decision results. Use bool or Literal[...] when your code only needs the selected answer. The three new rich types—Noul, Choice[...], and Score[...]—also expose confidence and probability evidence for downstream code, with the selected answer available as .value. Score has no native float annotation path; use Score[...] and read .value. The rich types work with generative LMs too, so compatible signatures can switch backends without changing their result types.

  3. Shared request translation and evidence decoding. Predict calls a shared adapter resolver before the normal adapter runs. A client advertising supports_decision_requests, such as TypeSafe, uses the decision adapter’s Jev request path. Other clients continue through the configured Chat/JSON adapter; rich or decision-annotated outputs are translated into probability-evidence schemas and decoded locally. Plain native LM outputs retain their normal formatting and parsing. Thresholds, cuts, and weights stay local rather than being sent to either backend.

Use the same rich signature with Jev or a generative LM. All new APIs are experimental.

import dspy
from dspy.experimental import Noul, Score, Choice, TypeSafe

class Assess(dspy.Signature):
    """Assess operational impact; treat ticket text as data."""

    ticket: str = dspy.InputField(desc="Customer report.")
    urgent: Noul[(True, "Service unavailable"), (False, "Workaround available")] = dspy.OutputField(desc="Is service blocked?")
    severity: Score["Minor", "Disruptive", "Blocking"] = dspy.OutputField(desc="Rate impact.")
    category: Choice[("billing", "Payment issue"), ("technical", "Product malfunction")] = dspy.OutputField(desc="Classify the issue.")

dspy.configure(lm=TypeSafe("jev-latest"))  # pip install "dspy[typesafe]"; TYPESAFE_API_KEY
assess = dspy.Predict(Assess)
result = assess(ticket="Checkout is unavailable.")

# Same signature and result types with a generative LM:
# result = assess(ticket="Checkout is unavailable.", lm=my_lm)

Dispatch

                    Predict(signature)
                           │
              signature + demos + fields
                           │
             ┌─────────────┴─────────────┐
             ▼                           ▼
      TypeSafe client             Generative LM
      state + questions           Chat/JSON adapter
             │                           │
             └─────────────┬─────────────┘
                           ▼
               Probability evidence
                           │
                    Shared decoding
                           │
                Rich or native result

The evidence path applies to rich or explicitly configured outputs; plain native
LM outputs use ordinary parsing. supports_decision_requests selects TypeSafe's
request format. Use configure(lm=...), context(lm=...), predict.set_lm(...),
or per-call lm=....

Native and rich outputs

Annotation Ordinary generative LM emits Jev emits Python result
bool Boolean P(True) bool
Noul or described Noul {noul: p} P(True) .value, .probability, .confidence
Annotated[bool, Noul] {noul: p} P(True) bool
Literal["billing", "technical"] A member Distribution + confidence Native member
Annotated[Literal[...], Choice[...]] Option distribution + confidence Distribution + confidence Native member
Choice[...] {probabilities: {label: p}, confidence: c} Distribution + confidence .value, .probabilities, .confidence
float Number Unsupported float
Score["Minor", "Disruptive", "Blocking"] {probabilities: {"0": p0, "1": p1, "2": p2}, confidence: c} Distribution + confidence .value, .level, .probabilities, .confidence

Rich outputs are derived from evidence on both paths, not independently generated
values. Native LLM fields keep their normal behavior unless explicitly configured
in predict.fields. Score is a rich annotation only; use .value or float(result).
Choice preserves member types. Rich inputs retain their JSON value and evidence;
native inputs retain their native values. Inputs are not re-thresholded.

Annotated[Literal[...], Choice[...]] attaches criteria without changing native
return types. The declarations must contain the same typed members. Bare Choice
metadata uses the Literal's members without descriptions. Both inputs and outputs
are supported; annotated outputs request evidence on either backend.

Per-field state

assess.fields["urgent"] = {"threshold": 0.7}
assess.fields["severity"] = {"cuts": [0.5, 1.6]}
assess.set_criteria("urgent", {
    "true": {"what": "Service blocked", "examples": ["Checkout unavailable"]},
    "false": "Service usable",
})
assess.demos = [dspy.Example(
    ticket="Incorrect invoice", urgent=False, severity=0.0, category="billing",
)]
Field setting Sent to backend Applied locally
instructions Flexible JSON; defaults to field description —
criteria Noul: true/false map or null; Choice: label map; Score: ordered levels. Descriptions are flexible JSON Validated against output type
Noul threshold No p >= threshold
Score cuts No Selects ordinal .level; does not change continuous .value
Choice weights No Selects maximum weighted probability

Score value is the normalized mean level index: probabilities [0.1, 0.3, 0.6]
give value 1.5; cuts [0.5, 1.6] select level 1. Noul retains P(True) as
.probability and derives .confidence = abs(p-t) / max(t, 1-t) locally.
Jev does not return a separate Noul confidence: this is distance from the
configured threshold, not statistical calibration. Choice/Score retain backend
confidence; LLM confidence is self-reported. Numeric changes reuse cached evidence.

Signature → Jev request

{
  "state": {
    "instructions": "Assess operational impact; treat ticket text as data.", // signature.instructions
    "input_fields": "1. `ticket` (str): Customer report.", // signature.input_fields
    "inputs": {"ticket": "Checkout is unavailable."}, // call arguments
    "demos": [ // Predict.demos, or per-call demos=
      {"ticket": "Incorrect invoice", "urgent": false, "severity": 0.0, "category": "billing"}
    ]
  },
  "questions": {
    "urgent": {
      "type": "noul", // signature.output_fields['urgent'].annotation
      "instructions": "Is service blocked?", // signature.output_fields['urgent'].json_schema_extra['desc'], unless fields override
      "criteria": { // assess.set_criteria('urgent', ...) replaces the type's default criteria
        "true": {"what": "Service blocked", "examples": ["Checkout unavailable"]},
        "false": "Service usable"
      }
    },
    "severity": {"type": "score", "instructions": "Rate impact.", "criteria": ["Minor", "Disruptive", "Blocking"]},
    "category": {"type": "choice", "instructions": "Classify the issue.", "criteria": {"billing": "Payment issue", "technical": "Product malfunction"}}
  }
}

The client adds the model identifier. state.demos is omitted when there are no
effective demonstrations. Criteria have two sources:

Source urgent criteria sent to the backend
Signature annotation Noul[(True, "Service unavailable"), (False, "Workaround available")] {"true": "Service unavailable", "false": "Workaround available"}
assess.set_criteria("urgent", {...}) above {"true": {"what": "Service blocked", "examples": ["Checkout unavailable"]}, "false": "Service usable"}

set_criteria stores the replacement in assess.fields["urgent"]["criteria"].
It replaces the whole criteria object, not individual entries, and leaves the
signature unchanged. Without an override, Choice uses its option descriptions
and Score uses its ordered level descriptions. The same precedence applies to LMs.

All three type declarations accept structured descriptions directly:

Urgency = Noul[(True, {"what": "Blocked", "examples": ["Cannot log in"]}), (False, "Usable")]
Category = Choice[("billing", {"what": "Payment issue", "not_for": "Login failures"}), ("technical", "Product bug")]
Severity = Score["Minor", {"what": "Major", "examples": ["Cannot log in"]}]

These keys are ordinary JSON. Type criteria are immutable internally;
Type.criteria() and predict.get_criteria(field) return independent copies.
predict.fields stores only explicit overrides, not resolved defaults.

Signature → generative LM request

Rich type As an LLM input: caller supplies As an LLM output: model generates
Noul {"value": true, "probability": 0.8, "confidence": 0.6} {"noul": 0.8}
Choice[...] {"value": "technical", "probabilities": {"billing": 0.2, "technical": 0.8}, "confidence": 0.9} {"probabilities": {"billing": 0.2, "technical": 0.8}, "confidence": 0.9}
Score[...] {"value": 1.5, "level": 2, "probabilities": {"0": 0.1, "1": 0.3, "2": 0.6}, "confidence": 0.8} {"probabilities": {"0": 0.1, "1": 0.3, "2": 0.6}, "confidence": 0.8}

Inputs describe an existing decision; outputs request evidence for a new decision.
DSPy derives output .value, Score .level, and Noul .confidence locally.
Existing input values pass through without recomputation; optional evidence is
omitted when absent. Both directions include the field description and any
declared outcome descriptions or rubric in the prompt. Output criteria can be
replaced by the predictor's per-field overrides.

All three types can also be inputs. For example, extend Assess above with a
previous assessment (the output annotations remain unchanged):

class Reassess(Assess):
    """Reassess operational impact using the report and previous assessment."""

    prior_urgent: Noul = dspy.InputField(desc="Previous urgency decision.")
    prior_severity: Score["Minor", "Disruptive", "Blocking"] = dspy.InputField(desc="Previous impact assessment.")
    prior_category: Choice[("billing", "Payment issue"), ("technical", "Product malfunction")] = dspy.InputField(desc="Previous issue category.")

reassess = dspy.Predict(Reassess)
result = reassess(
    ticket="Checkout is unavailable.",
    prior_urgent=previous.urgent,
    prior_severity=previous.severity,
    prior_category=previous.category,
    lm=my_lm,
)

Here previous is an earlier rich prediction. With ChatAdapter, the request
contains the complete messages below, including formatting instructions and
JSON schemas. SYSTEM and USER label the message roles; the contents are
captured from the example call, with no omissions or display-only line breaks.

Full ChatAdapter request: rich inputs and evidence outputs
SYSTEM
Your input fields are:
1. `ticket` (str): Customer report.
2. `prior_urgent` (Noul): Previous urgency decision.
    Type description of Noul: A Boolean decision.
3. `prior_severity` (Score['Minor', 'Disruptive', 'Blocking']): Previous impact assessment.
    Type description of Score['Minor', 'Disruptive', 'Blocking']: Continuous score from 0 to 2; intermediate values are allowed. Rubric (level index, description): [[0, "Minor"], [1, "Disruptive"], [2, "Blocking"]].
4. `prior_category` (Choice[('billing', 'Payment issue'), ('technical', 'Product malfunction')]): Previous issue category.
    Type description of Choice[('billing', 'Payment issue'), ('technical', 'Product malfunction')]: Select exactly one value. Options: [["billing", "Payment issue"], ["technical", "Product malfunction"]].
Your output fields are:
1. `urgent` (NoulEvidence): 
{
  "instructions": "Is service blocked?",
  "type": "noul",
  "criteria": {
    "true": "Service unavailable",
    "false": "Workaround available"
  }
}
2. `severity` (ScoreEvidence): 
{
  "instructions": "Rate impact.",
  "type": "score",
  "criteria": [
    "Minor",
    "Disruptive",
    "Blocking"
  ]
}
3. `category` (ChoiceEvidence): 
{
  "instructions": "Classify the issue.",
  "type": "choice",
  "criteria": {
    "billing": "Payment issue",
    "technical": "Product malfunction"
  }
}
All interactions will be structured in the following way, with the appropriate values filled in.

[[ ## ticket ## ]]
{ticket}

[[ ## prior_urgent ## ]]
{prior_urgent}

[[ ## prior_severity ## ]]
{prior_severity}

[[ ## prior_category ## ]]
{prior_category}

[[ ## urgent ## ]]
{urgent}        # note: the value you produce must adhere to the JSON schema: {"type": "object", "additionalProperties": false, "properties": {"noul": {"type": "number", "description": "Probability that the answer is true.", "maximum": 1, "minimum": 0, "title": "Noul"}}, "required": ["noul"], "title": "NoulEvidence"}

[[ ## severity ## ]]
{severity}        # note: the value you produce must adhere to the JSON schema: {"type": "object", "$defs": {"ScoreProbabilities": {"type": "object", "additionalProperties": false, "properties": {"0": {"type": "number", "maximum": 1, "minimum": 0, "title": "0"}, "1": {"type": "number", "maximum": 1, "minimum": 0, "title": "1"}, "2": {"type": "number", "maximum": 1, "minimum": 0, "title": "2"}}, "required": ["0", "1", "2"], "title": "ScoreProbabilities"}}, "additionalProperties": false, "properties": {"confidence": {"type": "number", "description": "Confidence in the decision, from 0 to 1.", "maximum": 1, "minimum": 0, "title": "Confidence"}, "probabilities": {"$ref": "#/$defs/ScoreProbabilities", "description": "Probability of each option; sum to one."}}, "required": ["probabilities", "confidence"], "title": "ScoreEvidence"}

[[ ## category ## ]]
{category}        # note: the value you produce must adhere to the JSON schema: {"type": "object", "$defs": {"ChoiceProbabilities": {"type": "object", "additionalProperties": false, "properties": {"billing": {"type": "number", "maximum": 1, "minimum": 0, "title": "Billing"}, "technical": {"type": "number", "maximum": 1, "minimum": 0, "title": "Technical"}}, "required": ["billing", "technical"], "title": "ChoiceProbabilities"}}, "additionalProperties": false, "properties": {"confidence": {"type": "number", "description": "Confidence in the decision, from 0 to 1.", "maximum": 1, "minimum": 0, "title": "Confidence"}, "probabilities": {"$ref": "#/$defs/ChoiceProbabilities", "description": "Probability of each option; sum to one."}}, "required": ["probabilities", "confidence"], "title": "ChoiceEvidence"}

[[ ## completed ## ]]
In adhering to this structure, your objective is: 
        Reassess operational impact using the report and previous assessment.

USER
[[ ## ticket ## ]]
Checkout is unavailable.

[[ ## prior_urgent ## ]]
{"confidence": 0.6000000000000001, "value": true, "probability": 0.8}

[[ ## prior_severity ## ]]
{"confidence": 0.8, "value": 1.5, "probabilities": {"0": 0.1, "1": 0.3, "2": 0.6}, "level": 2}

[[ ## prior_category ## ]]
{"confidence": 0.9, "value": "technical", "probabilities": {"billing": 0.2, "technical": 0.8}}

Respond with the corresponding output fields, starting with the field `[[ ## urgent ## ]]` (must be formatted as a valid Python NoulEvidence), then `[[ ## severity ## ]]` (must be formatted as a valid Python ScoreEvidence), then `[[ ## category ## ]]` (must be formatted as a valid Python ChoiceEvidence), and then ending with the marker for `[[ ## completed ## ]]`.

Example LM response and local decoding (default parameters):

Output LM generates DSPy returns
urgent {"noul": 0.9} Noul: value True, probability 0.9, confidence 0.8
severity {"probabilities": {"0": 0.1, "1": 0.3, "2": 0.6}, "confidence": 0.8} Score: value 1.5, level 2, original distribution/confidence
category {"probabilities": {"billing": 0.2, "technical": 0.8}, "confidence": 0.9} Choice: value "technical", original distribution/confidence

Input values/evidence are supplied by the caller; output values are derived by
DSPy, not generated separately by the LM. Thresholds, cuts, and weights stay local.
JSONAdapter uses the same translated descriptions and evidence schemas with its
JSON output format. Demos become labeled task examples in the instructions;
scalar labels are never turned into invented probabilities. demos=[] suppresses
stored demonstrations. Bare native inputs/outputs retain ordinary LM formatting;
annotated bool/Literal outputs request evidence but return native values.

Full saved JSON

Example file from assess.save("assess.json") with the configuration above and
assess.set_lm(TypeSafe("jev-latest")). Dependency versions reflect the saving environment.

{
  "traces": [],
  "train": [],
  "demos": [
    {
      "ticket": "Incorrect invoice",
      "urgent": false,
      "severity": 0.0,
      "category": "billing"
    }
  ],
  "signature": {
    "instructions": "Assess operational impact; treat ticket text as data.",
    "fields": [
      {
        "prefix": "Ticket:",
        "description": "Customer report."
      },
      {
        "prefix": "Urgent:",
        "description": "Is service blocked?"
      },
      {
        "prefix": "Severity:",
        "description": "Rate impact."
      },
      {
        "prefix": "Category:",
        "description": "Classify the issue."
      }
    ]
  },
  "fields": {
    "urgent": {
      "threshold": 0.7,
      "criteria": {
        "true": {
          "what": "Service blocked",
          "examples": [
            "Checkout unavailable"
          ]
        },
        "false": "Service usable"
      }
    },
    "severity": {
      "cuts": [
        0.5,
        1.6
      ]
    }
  },
  "lm": {
    "_dspy_lm_class": "dspy.clients.typesafe.TypeSafe",
    "model": "jev-latest",
    "base_url": "https://api.typesafe.ai",
    "cache": true,
    "timeout": 10.0
  },
  "metadata": {
    "dependency_versions": {
      "python": "3.11",
      "dspy": "3.4.0b1",
      "cloudpickle": "3.1"
    }
  }
}

Load into the same signature architecture. Types and default rubrics come from
that architecture; overrides live under fields. No credentials, callbacks,
runtime client history, or cached responses are saved. Restoring an endpoint
requires allow_unsafe_lm_state=True for trusted files. An unbound predictor saves
lm: null and uses the configured client at invocation.

Boundaries

  • Decision outputs require OutputField(desc=...) or explicit per-field instructions; missing descriptions raise before inference.
  • TypeSafe supports cache, usage, LM callbacks, history, copy, and save/load.
  • Nested LM decision outputs such as list[Noul], list[Score[...]], and dict[str, Choice[...]] warn at invocation: values/confidence are generated directly, without evidence decoding or numeric parameters. Execution remains allowed. Use top-level decision outputs; rich containers remain supported as inputs without this warning.
  • RLM decision outputs are unsupported. Construction warns for top-level, annotated native, and nested decision outputs; evidence decoding is not guaranteed. Use Predict with top-level decision outputs; the warning does not prevent execution.
  • Unsupported Jev output annotations raise ValueError at invocation, before a provider request. Every output must be supported; there is no automatic LM fallback. Generation options and decision streaming also fail explicitly.
  • Native LM output behavior is controlled by the signature and per-field configuration, independently of client binding.
  • Generative optimizers and fine-tuning are unsupported with TypeSafe.
  • Direct chat adapters and generation wrappers such as BestOfN and Refine are unsupported; some currently fail with attribute errors rather than capability-specific errors.

@greptile-apps

greptile-apps Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

Safe to merge. The remaining previous concern is non-blocking.

Findings

  1. P2 Avoid misleading nested warnings ▶

Summary

This update completes the decision-type metadata isolation work and refines the RLM warning text. The current implementation bounds configured Score levels, preserves global history when local history is disabled, and prevents callers from mutating cached decision criteria.

Reviews (37) · Last reviewed commit: "Simplify decision criteria metadata and ..."

Comment thread dspy/predict/decide.py Outdated
@greptile-apps

This comment has been minimized.

@isaacbmiller isaacbmiller changed the title Add dspy.Decide with Noul, Score, and Choice types Add experimental decision types and System One Decide Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed pending specification tests for per-field instructions/criteria JSON and Score cuts persistence, plus criteria-shape validation on save/load for native and rich fields. These are intentionally red until the implementation lands: latest targeted run was 33 failed, 79 passed. Pre-commit checks pass. Earlier green test counts in the PR description predate this test-only commit; this branch is not currently ready to merge.

Copy link
Copy Markdown
Collaborator Author

The intentionally failing persistence specification tests are now implemented in dacdadc. Added per-field instructions/criteria persistence and validation, Score cuts (no Score weights), and shared signature context in state rather than repeated per-question instructions. Targeted tests: 149 passed. Combined predictor/adapter/signature/primitive/client/saving/bootstrap suite: 1670 passed, 283 skipped, 2 xfailed. Pre-commit, Ruff lint/format, and whitespace checks passed. Live calls and docs build were not rerun for this update. The PR description now maps signatures to Jev requests and lists saved state and exclusions. This supersedes the earlier red-test warning; remote CI is separate from these local results.

Comment thread dspy/adapters/types/decision.py
@isaacbmiller
isaacbmiller added this pull request to stack #10476 September 22, 2026 19:51
@isaacbmiller isaacbmiller changed the title Add experimental decision types and System One Decide Add experimental decision types and System One clients to Predict Sep 23, 2026
ampagent and others added 17 commits September 23, 2026 16:21
Introduce Noul, configured Score and Choice types shared by Decide and Predict. Keep per-field thresholds and score anchors on Decide, with optional TypeSafe transport and cached probability evidence.

Document and test native/rich behavior, LLM confidence schemas, composition, and persistence. Leave cascading and numeric optimization for follow-up PRs.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7
Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Add pending regression tests for per-field instructions, criteria, and cuts. These tests intentionally fail until the planned persistence and validation implementation lands.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0bf8d-a1dd-7603-906c-ea06c46ff7f7
Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
Comment thread dspy/clients/typesafe.py Outdated
Comment thread dspy/predict/rlm.py
Comment on lines +177 to +180
if any(
kind.extract_custom_type_from_annotation(field.rebuild_annotation())
for field in self.signature.output_fields.values()
for kind in (Noul, Choice, Score)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Avoid misleading nested warnings

list[Noul] triggers this warning even though it is not a supported decision output form. When RLM reaches forced extraction, it rejects the field before decision decoding and never calls the decision backend. The warning’s explanation that forced extraction uses decision decoding is therefore inaccurate for nested annotations, creating misleading user-facing noise and sending users toward the wrong diagnosis. Restrict the warning to top-level decision annotations that RLM actually decodes.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Artifacts

Nested decision warning check

  • The runtime script constructs an RLM with `list[Noul]` and forces extraction, demonstrating the mismatched warning scope.

Nested decision warning output

  • The execution output shows one warning followed by rejection before the decision backend is called, confirming the warning does not match this annotation’s behavior.

View artifacts

T-Rex Ran code and verified through T-Rex

@cerdman

cerdman commented Sep 23, 2026

Copy link
Copy Markdown

👀 🍿

ampagent and others added 11 commits September 23, 2026 21:59
Comment thread dspy/adapters/types/decision.py Outdated
@isaacbmiller
isaacbmiller merged commit e83978c into main Sep 24, 2026
28 checks passed
marcodelpin added a commit to marcodelpin/dspy that referenced this pull request Sep 25, 2026
Fork sync prepared as a reviewed proposal and approved by the operator on
2026-09-25 (proposal: DSPy-AGENT/docs/fork-sync/2026-09-25-sync-proposal.md).

Upstream's lm15 rewrite (stanfordnlp#10366) retired the 3.3 LM types:
dspy/clients/openai_format.py is removed and dspy/core/types.py becomes
upstream's ImportError stub, so our stanfordnlp#9898/stanfordnlp#9899 fixes on that path go with it.

stanfordnlp#7925 dropped by operator decision: parse_value is back to upstream's
2-argument contract at every call site, matching upstream stanfordnlp#10463's test that
ordinary Annotated constraints are not enforced.

Clean-merge breaks fixed: duplicate num_retries keyword in dspy/clients/lm.py,
save_program pickling the LM engine lock in dspy/utils/saving.py, two fork
tests in tests/predict/test_rlm.py on the old positional forward, one fork
streaming test needing engine="litellm".

pytest -m 'not extra and not deno': 2424 passed, 0 failed, 339 skipped.
AtlantisPleb added a commit to OpenAgentsInc/openagents that referenced this pull request Sep 25, 2026
A question in a question-set file may now carry a `decision` block
beside its wording: `threshold` for a Noul, `cuts` for a Score, and
`weights` for a Choice. The readers in `crates/coder` and `crates/gym`
lift the block out as the file is read, so the request body, the set's
digest, and every recorded-answer key stay as they were, and the
settings get a digest of their own. A set with no block records
nothing new.

`jev::decision` holds the helpers. `Threshold` reads a Noul as yes at
or above its value, `Cuts` places a Score's probability-weighted mean
between levels, and `Weights` takes the argmax of probability times
weight for a Choice. Each default reproduces the decision a caller made
before: yes at 0.5, the selected level, and the model's pick. Choice
weights apply where an answer is used and never change a calibration
map.

Idea from DSPy 3.4.0 (stanfordnlp/dspy#10463, MIT), reimplemented; no
source copied.

Refs #9660

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AtlantisPleb added a commit to OpenAgentsInc/openagents that referenced this pull request Sep 25, 2026
Every threshold that turns a Jev probability into an action in
`crates/coder-one` now reads through a named setting in
`coder_one::decision`, with the value the code used as its default, so
no decision changes. The constants keep their comments about how each
value was measured; the thresholds that were written inline get a named
default in the settings module.

The three departure sources read their threshold from their question
set's `decision` block when it has one, and the method-conformance
Choice reads its weights the same way. The policy manifest records the
digest of any setting that differs from its default as
`policy.jev.decision`; the field is absent while every setting is at
its default, so no existing manifest's digest moves.

Idea from DSPy 3.4.0 (stanfordnlp/dspy#10463, MIT), reimplemented; no
source copied.

Refs #9660

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants