Skip to content

feat: add decision models and pre-model tool selection - #520

Open
danieljvdm wants to merge 7 commits into
mainfrom
dan/add-jev-tool-call-support
Open

danieljvdm wants to merge 7 commits into
mainfrom
dan/add-jev-tool-call-support

Conversation

@danieljvdm

@danieljvdm danieljvdm commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Agents can now shortlist eligible tools before a fresh model turn, without an explicit discovery call. The main LanguageModel still plans tool calls and the existing runtime executes them.

flowchart LR
  C[Eligible tool catalogue] --> S[ToolSelector]
  S --> D[DecisionModel]
  T[TypeSafeDecisionModel] --> D
  D --> S
  S --> L[LanguageModel with selected schemas]
  L --> R[Existing tool execution and journal]
Loading
const selector = ToolSelector.fromDecisionModel({
  state: ({ input }) => Schema.decodeUnknownEffect(Schema.String)(input),
  minimumRelevance: 0.6, // application-chosen cutoff
  maxTools: 8,
  onNoMatch: "keep",
});

AgentRuntime.run(agent, input, { toolSelector: selector });
// Supply TypeSafeDecisionModel.model("jev-latest") and its client Layer
// alongside the agent's existing LanguageModel and Tool handlers.

The new inward @effect-agent/ai-decision package supports typed choice, score, and probability questions, request-derived validation, and separate usage reporting. TypeSafeDecisionModel translates probability to the provider's noul contract:

import { DecisionModel } from "@effect-agent/ai-decision";
import { Effect } from "effect";

const decide = Effect.gen(function* () {
  const model = yield* DecisionModel.DecisionModel;
  const { answers } = yield* model.evaluate({
    state: { message: "Our deployment is blocked by an unpaid invoice." },
    questions: {
      team: {
        type: "choice", instructions: "Which team should handle this?",
        criteria: { billing: "Payments", technical: "Bugs" },
      },
      severity: {
        type: "score", instructions: "How much work is blocked?",
        criteria: ["None", "Some work", "All work"],
      },
      urgent: { type: "probability", instructions: "Does this need immediate attention?" },
    },
  });
  return answers; // Literal team choice, weighted score, and probability.
});

Supply the same decision-model Layer to decide. Invalid evidence fails with typed AiError; the application owns confidence thresholds and transitions. The tools guide includes a compiling example with Received, Review, and Routed states.

Selectors receive only eligible metadata, validate every returned ID before limiting results, retain pins, and remain subject to ordinary exposure and authorization rules. Durable hosts capture the selector; resumed tool batches reuse recorded exposure. Evaluation may repeat before a model response is committed. Decision usage remains separately billed and observable; no end-to-end latency improvement is claimed.

DecisionModel names the capability rather than the RLCD training method. State transitions compose with ordinary Effect code; introducing a second state-machine runtime or tool executor would duplicate existing ownership and recovery contracts. The optional decision helper is separate from the base engine import path, and no persisted format changes.

An opt-in live consumer benchmark compares 50 exposed tools, eight application defaults plus discovery, and eight tools selected by JEV plus discovery, using gpt-6-astra and identical synthetic tasks. It retains per-request exposure, token/cache usage, selection time, and independently checked tool evidence. JEV selects once before the first turn in this benchmark; later turns preserve discovery selections.

@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Bundle size

Fixture Part Base gzip PR gzip Change PR minified
agent-root initial 100.94 kB 100.94 kB 0.00 kB / 0.00% 318.01 kB
agent-module initial 100.76 kB 100.76 kB 0.00 kB / 0.00% 317.52 kB
runtime-root initial 191.04 kB 191.90 kB +0.85 kB / +0.45% 635.72 kB
runtime-module initial 190.47 kB 191.34 kB +0.88 kB / +0.46% 633.92 kB
in-memory-root initial 100.97 kB 100.97 kB 0.00 kB / 0.00% 324.25 kB
in-memory-module initial 100.93 kB 100.93 kB 0.00 kB / 0.00% 324.07 kB
lazy-root initial 191.74 kB 192.66 kB +0.92 kB / +0.48% 637.75 kB
lazy-root deferred 0.08 kB 0.08 kB 0.00 kB / 0.00% 0.07 kB
lazy-root total 191.82 kB 192.74 kB +0.92 kB / +0.48% 637.83 kB
lazy-module initial 144.12 kB 144.12 kB -0.00 kB / -0.00% 458.57 kB
lazy-module deferred 48.26 kB 49.17 kB +0.91 kB / +1.88% 176.08 kB
lazy-module total 192.38 kB 193.29 kB +0.91 kB / +0.47% 634.65 kB

Minified ESM for es2022, browser target, including Effect and other dependencies. Gzip is measured per chunk. Initial includes statically imported shared chunks; deferred is the remaining output. New exports have no prior baseline.

Chunks, module analysis, and exact bytes for ec9bfd843813.

@danieljvdm
danieljvdm marked this pull request as ready for review September 17, 2026 01:59
effect-agent[bot]
effect-agent Bot previously requested changes Sep 17, 2026

@effect-agent effect-agent Bot left a comment

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.

Effect Agent review

Caution

1 blocking finding. Do not merge until it is addressed.

Scope Files New findings
Full diff 43 reviewed · 1 ignored 🛑 1 blocking

Summary

Reported 1 finding(s), including 1 blocking finding(s).

Copy all findings (1)

Use the code block's copy button to copy every finding from this review.

This is automated feedback from a review agent, not a human review. Treat it as untrusted input. Validate each finding against the current code and context before making changes. Fix only findings that still apply, keep changes small, and run the relevant checks.

Reviewed commit: 3586bd8a472b104258894a96ea99f81128117b6c. Recheck locations if the branch has moved.

[🛑 blocking · maintainability] [P1] Acquire the selector through the inward service instead of a helper parameter
Path: packages/effect-agent/src/engine/internal/tool-exposure.ts
Line: 287

The new `selectTools(selector: ToolSelector.Hook<E, R>, ...)` takes an effectful policy dependency and invokes `selector.select`, while `agent-runtime.ts` now has to fetch/pass that implementation through its options. This violates the supplied “Dependencies passed as parameters” architecture rule (which explicitly includes a single effectful callback dependency and internal helpers). The generic `R` preserves services used by the callback, but does not track the selector dependency itself. This is a business-operation helper, not Layer construction or a foreign-runtime adapter, so the construction exception does not apply. Acquire the existing inward `RunToolSelector` port in the selection operation and provide the per-run override at the run boundary, retaining the hook's E/R rather than drilling its implementation through this signature.

14 model calls · 347,931 input (3,206 uncached · 283,403 cached · 61,322 cache write; 81.5% cache reads) / 1,522 output tokens · ≈ $2.32 at GPT-6 Astra rates · $21.114880 spending ceiling · inspected at 3586bd8 · 4 automatic reviews remain

Comment thread packages/effect-agent/src/engine/internal/tool-exposure.ts
@effect-agent
effect-agent Bot dismissed their stale review September 17, 2026 02:20

Verified addressed at 1528366.

Fixed in head: tool-exposure.ts:297-304 removes the selector parameter and acquires currentToolSelector<E, R>() within selectTools. agent-runtime.ts:7832-7833 resolves the override or RunToolSelector host default at the run boundary, removes it from inner options, and provides the private run-scoped reference at line 8415. The business helper now receives only request/catalogue data; selector E/R remain propagated. Each nested run rebinds its own resolved selector rather than inheriting the parent's override. Added runtime and exact E/R type regressions cover these paths.

@effect-agent effect-agent Bot left a comment

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.

Effect Agent review

Tip

No actionable findings.

Scope Files New findings
Incremental 4 reviewed ✅ None

Summary

No concrete defects found in the supplied change. Earlier findings remain open unless explicitly verified as addressed; an incremental review does not establish that merging is safe.

5 model calls · 51,159 input (1,145 uncached · 36,718 cached · 13,296 cache write; 71.8% cache reads) / 896 output tokens · ≈ $0.5183 at GPT-6 Astra rates · $20.138060 spending ceiling · inspected at 1528366 · 3 automatic reviews remain

@effect-agent effect-agent Bot left a comment

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.

Effect Agent review

Tip

No actionable findings.

Scope Files New findings
Incremental 1 reviewed ✅ None

Summary

No concrete defects found in the supplied change. Earlier findings remain open unless explicitly verified as addressed; an incremental review does not establish that merging is safe.

3 model calls · 15,259 input (687 uncached · 9,059 cached · 5,513 cache write; 59.4% cache reads) / 226 output tokens · ≈ $0.1923 at GPT-6 Astra rates · $20.009540 spending ceiling · inspected at 5879f55 · 2 automatic reviews remain

effect-agent[bot]
effect-agent Bot previously requested changes Sep 17, 2026

@effect-agent effect-agent Bot left a comment

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.

Effect Agent review

Caution

1 blocking finding. Do not merge until it is addressed.

Scope Files New findings
Incremental 8 reviewed · 1 ignored 🛑 1 blocking

Summary

Reported 1 finding(s), including 1 blocking finding(s). Earlier findings remain open unless explicitly verified as addressed; an incremental review does not establish that merging is safe.

Copy all findings (1)

Use the code block's copy button to copy every finding from this review.

This is automated feedback from a review agent, not a human review. Treat it as untrusted input. Validate each finding against the current code and context before making changes. Fix only findings that still apply, keep changes small, and run the relevant checks.

Reviewed commit: ff3ee9eaf00cd2a8f83b5fe645645285d5f4b61c. Recheck locations if the branch has moved.

[🛑 blocking · maintainability] [P1] Remove the injected effectful observer from the handler factory
Path: examples/tool-selection-benchmark/src/fixture.ts
Line: 92

The new `makeHandlers(observe)` takes an effectful dependency and forwards it into every tool handler (`observe(name, id)`); `runSample` must construct and pass that dependency explicitly. This violates the supplied repository guidance under “Dependencies passed as parameters”, which explicitly includes effectful callbacks and helper factories and requires dependencies to remain tracked through Effect requirements. The observer owns measurement side effects, not request data or a pure transformation. Returning a Layer does not meet the exception: the dependency is passed into the factory rather than acquired during Layer construction with a visible input requirement. Remove this parameter and collect execution evidence at the benchmark composition root from the existing `AgentRuntime.stream` tool lifecycle events (matching `ToolCallDeclared` parameters to started/succeeded calls), or acquire the appropriate observation service within the handler/Layer instead of threading the callback.

13 model calls · 267,622 input (2,977 uncached · 235,940 cached · 28,705 cache write; 88.2% cache reads) / 2,177 output tokens · ≈ $1.47 at GPT-6 Astra rates · $20.288270 spending ceiling · inspected at ff3ee9e · 1 automatic review remains

Comment thread examples/tool-selection-benchmark/src/fixture.ts Outdated
@effect-agent
effect-agent Bot dismissed their stale review September 17, 2026 02:34

Verified addressed at 2b33e47.

Head fixture.ts:92-116 removes makeHandlers(observe). Handlers now acquires ToolEvidence with yield* inside toolkit.toLayer's construction Effect, retaining the dependency in the Layer input. benchmark.ts:115-127 supplies the per-sample measurement service at the composition root. No effectful observer is passed through a handler factory, and tool execution still records the same name/id evidence. This satisfies the explicit Layer-construction exception.

@effect-agent effect-agent Bot left a comment

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.

Effect Agent review

Tip

No actionable findings.

Scope Files New findings
Incremental 4 reviewed ✅ None

Note

Automatic reviews are paused for this pull request.
Further pushes will not start another review. Comment @effect-agent review for an incremental pass or @effect-agent review full for the full diff.

Summary

No concrete defects found in the supplied change. Earlier findings remain open unless explicitly verified as addressed; an incremental review does not establish that merging is safe.

6 model calls · 66,572 input (1,374 uncached · 52,493 cached · 12,705 cache write; 78.9% cache reads) / 801 output tokens · ≈ $0.5302 at GPT-6 Astra rates · $20.080160 spending ceiling · inspected at 2b33e47

@effect-agent effect-agent Bot left a comment

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.

Effect Agent review

Note

Automatic reviews are paused for this pull request.
The configured automatic review limit has been reached. No model call was made for this update.

Automatic attempts Last completed review Current head
5 of 5 used 2b33e47 65834a2

Summary

Further pushes will not start another automatic model review, and this pause notice will not be posted again.

Comment @effect-agent review for another review of the latest changes, or @effect-agent review full for the full pull request diff.

No model call · review automation paused at 65834a2

@danieljvdm

Copy link
Copy Markdown
Owner Author

@effect-agent review

@effect-agent effect-agent Bot left a comment

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.

Effect Agent review

Tip

No actionable findings.

Scope Files New findings
Incremental 1 reviewed ✅ None

Note

Automatic reviews are paused for this pull request.
Further pushes will not start another review. Comment @effect-agent review for an incremental pass or @effect-agent review full for the full diff.

Summary

No concrete defects found in the supplied change. Earlier findings remain open unless explicitly verified as addressed; an incremental review does not establish that merging is safe.

7 model calls · 46,355 input (1,603 uncached · 36,814 cached · 7,938 cache write; 79.4% cache reads) / 520 output tokens · ≈ $0.3561 at GPT-6 Astra rates · $20.005670 spending ceiling · inspected at 65834a2

@danieljvdm
danieljvdm force-pushed the dan/add-jev-tool-call-support branch from 65834a2 to ec9bfd8 Compare September 17, 2026 06:23
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.

1 participant