Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ff7147b
LLM: per-request response_format, parallel_tool_calls and temperature…
sathish-mg Oct 19, 2026
a0c3c9b
RUN: the scheduler honours a node's llm block, locked params and dyna…
sathish-mg Oct 19, 2026
43e0854
RUN: freeze a plan's per-node llm block and tools_from list; apply lo…
sathish-mg Oct 19, 2026
7ec95d3
Bindings: runStart/runResume and run_start/run_resume take llm_profil…
sathish-mg Oct 19, 2026
35462ff
RUN: llm.dispatch pins a Definition no node binds; dispatch.args_into…
sathish-mg Oct 19, 2026
d58687d
docs: configuring an LLM node (llm), tools_from behind mcp.call, the …
sathish-mg Oct 19, 2026
f5a550e
Version 1.13.0 (unreleased): bump every version site and the internal…
sathish-mg Oct 19, 2026
d7f726d
RUN: per-node tool-offer narrowing (llm.narrow) and dynamic tools des…
sathish-mg Oct 20, 2026
e29ea60
Accept and drop JSON Schema annotation keywords in tool schemas (#397)
sathish-mg Oct 20, 2026
9f57977
RUN: close dynamic entries, validate the stripped schema, refuse unsa…
sathish-mg Oct 20, 2026
f75c5fa
RUN: a node's system text and a dynamic tool's prose cross the model …
sathish-mg Oct 20, 2026
04d1994
RUN: shadow takes locks from the incumbent; a dispatch pin may not sh…
sathish-mg Oct 20, 2026
c8d4166
LLM/RUN: report and record the temperature that actually went out (re…
sathish-mg Oct 20, 2026
0af605a
Profiles everywhere a model is resolved: trigger paths, env twins, ar…
sathish-mg Oct 20, 2026
a139661
Tests/docs: a resume test that parks and resumes; where locked_params…
sathish-mg Oct 20, 2026
6d5ab71
Release v1.13.0 pre-flight: date the changelog, record the review's d…
sathish-mg Oct 20, 2026
d6cacba
deps: rustls 0.23.45 -> 0.23.46 (RUSTSEC-2026-0346..0349)
sathish-mg Oct 20, 2026
1c27e33
RUN: a wrapped dynamic entry honours its own schema; only a flat one …
sathish-mg Oct 20, 2026
90e4b5f
RUN: two llm.dispatch pins may not be different versions of one tool_…
sathish-mg Oct 20, 2026
39182e9
RUN: the strict-mode check walks anyOf, schema-valued additionalPrope…
sathish-mg Oct 20, 2026
c494283
Bindings: read the AREEV_RUN_LLM_* twins only when a model is configu…
sathish-mg Oct 20, 2026
395b4ad
CHANGELOG: --llm-no-temperature false (space form) no longer means of…
sathish-mg Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2586,6 +2586,40 @@ semantics while widening the import surface the by-name refusal keeps closed;
an interpreter module was declined because the pinned address would name the
interpreter while the tool's logic rode in its input, outside every pin.

### A plan configures its model nodes; what reaches the provider rides the journal (#391–#394)

**Decision (2026-10-20):** a Workflow's `llm` field configures each abstract
node: system text, instruction (literal or a pointer into the input), state
selection, a tool subset, structured output, temperature or none, the output
ceiling, `parallel_tool_calls`, and whether the attempt's tool calls reach the
nodes below. It also names tools a host declares in the run input
(`tools_from`), each dispatched through a Definition the plan pins. Three
rules carry the design.

1. **Frozen at start, like `reads`.** The block, and the `tools_from` list
resolved from the input, are validated and frozen onto the node's pin.
Resume and `verify` run the frozen block, and neither the plan nor a later
input can change it.
2. **Every request field rides the journal.** The scheduler, which is pure,
writes the first message and each turn's `input.llm` (with
`response_format` in wire shape). The driver builds the request from that
journaled input alone, which is the rule the fold's no-tools marker and the
narrowed offer already follow. A node with no block journals what it always
did.
3. **The input names data, never code.** A dynamic tool may dispatch only
through a Definition the plan pinned, by a binding or by `llm.dispatch`. It
runs under that Definition's host pin and broker grants, with `locked` keys
the model cannot override, and journals under its own name with the
Definition's address. `locked_params` on a Definition, stored but never
applied before 1.13, follows the same merge.

**Rejected:** per-run host flags for these knobs (the bindings had none, and a
plan-level setting is what a reviewer reads); offering `mcp.call` itself to the
model (one opaque tool, with the server and tool chosen by the model); and
letting `tools_from` name a Definition by hash that the plan does not pin,
which would let host data choose code. `docs/run.md` "Configuring an LLM node"
is the reference.

### A host pause is a transient hold, never scheduler state (#344)

**Decision (2026-09-23, #344):** a run gains a third stop beside cancel
Expand Down
115 changes: 114 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,118 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [1.13.0] — 2026-10-20

### Added

- **A plan configures each LLM node (`llm`)** (#391). A Workflow's new `llm`
field sets, per abstract node: `system` (replaces the built-in prompt);
`instruction` or `instruction_from`, a JSON pointer into the node's input
that may be nested, such as `/prompt/text`; `state` (`"all"`, `"none"` or a
list of keys); `tools` (a subset of the pinned host tools); `response`
(`{"json_schema": …}` or `{"json": true}`); `temperature` (a number, or
`"omit"` to send none, for the GPT-5 family); `max_tokens` (the node's
ceiling and reservation); `parallel_tool_calls`; and `tool_results_into`,
which hands every tool call of the attempt, built from the journal, to the
nodes below. The block is validated at start (`RUN-E019` for a shape error,
`RUN-E004` for an unpinned tool name) and frozen into the manifest, so
`run inspect` shows it and resume and `verify` use it. Each `mg:llm` intent
journals the request fields as sent under `input.llm`. The runtime checks a
structured answer whatever the provider did, with one corrective re-prompt
before the node fails `schema_validation_failed`. A node with no block
journals byte-identically, so the scheduler epoch did not move.
- **Tools declared in the run input (`tools_from`)** (#392). An abstract
node's `llm.tools_from` points at a list in the run input:
`{name, description, input_schema, dispatch: {definition, locked,
args_into}}`. The list is pinned at start and offered under its own names
and schemas. A call is validated against the entry's schema, wrapped under
`args_into`, locked, validated against the dispatch Definition's
`input_schema`, executed as that Definition (same pin, sandbox and broker
grants) and journaled under the entry's name. `llm.dispatch: [name | hash]`
pins a Definition that no node binds, such as `mcp.call`. It resolves as an
unbound node's Definition does and is never offered to the model by itself.
A dispatch target that is not pinned, a duplicate name, or a name that
collides with a pinned tool is refused at start, naming the entry.
- **Per-node tool-offer narrowing** (#396). `llm.<node>.narrow: false` keeps
a node's whole offer when a calibrated decision backend is pinned, and
`narrow: {"top_k": n}` sets the K for that node. Both are frozen in the
manifest, and `true` (the default) keeps the run-wide top-8 rule. The
narrowing question now describes a `tools_from` tool by the first line of
its own `description`, as it describes a pinned Definition by its
`tool_description`. Before this change it listed a dynamic tool by name
only.
- **`llm_profile` on the bindings** (#394). `runStart`/`runResume` (Node) and
`run_start`/`run_resume` (Python) take a trailing JSON string,
`{"temperature": "omit" | n, "token_field": …, "extra_body": {…}}`, so an
embedding host can run a model that refuses a temperature.
`ToolCallRequest` gains `response_format`, `parallel_tool_calls` and
`omit_temperature`, and now derives `Default`. `RequestProfile::from_json`
and `resolve_toolcall_with_profile` are new.

### Fixed

- **`locked_params` is applied** (#393). A Definition's locked parameters
were stored, validated and displayed, and never used: a model-issued call
was dispatched with whatever the model sent. Locks are now merged over the
model's arguments (the lock wins), stripped from the offered schema
(`properties` and `required`), checked by a strict Definition on the merged
input, journaled merged, and frozen with the pin. They are applied by run
dispatch (`areev-run`), the one place a model's tool call is executed. They
are not applied by `areev-loop`, `areev-server` or `areev-mcp`, which
dispatch no model-issued calls, nor to a bound host node. The issue's
acceptance read "merged at invoke", and invoke is run dispatch. A client or
decision Definition carrying `locked_params` still starts: the locks are
ignored, as in 1.12, with a `RUN-W001` warning in the manifest. `shadow`
rehearses a run with the locks that run froze, so a 1.12 run still scores
`same` on an unchanged plan.
- **Hardening from a review of the 1.13 branch:**
- Without `args_into`, a dynamic tool's schema is closed: a key the entry
does not name, or a key the host locked, is an argument violation. With
`args_into`, the entry's own schema decides, and only
`additionalProperties: false` closes it.
- The model's arguments are checked against the schema it was shown, so a
required key the host locks is never demanded.
- A dotted lock key, a lock on the `args_into` key, a dispatch pin that is
another version of a bound Definition's name, and a `json_schema` that
OpenAI's strict mode would refuse are all refused at start (`RUN-E019`).
- `{"json": true}` adds "Answer with a JSON object." to a node system text
that never says JSON.
- A fenced (```` ```json ````) final answer is accepted.
- The `tools_from` list is left out of the whole-state prompt.
- A node's `system` text, and a dynamic tool's description and schema
prose, are pseudonymized under an `egress` policy.
- Transports report the temperature they actually send, and a result
records it as `temperature_sent`.
- Request profiles reach `triggerRun`/`trigger_run`, `triggerDeliver`/
`trigger_deliver` and `areev eval`. The bindings read the
`$AREEV_RUN_LLM_*` variables when given no profile, and only when a model
is configured, so a stale variable cannot fail a model-less run. `off`
and `no` mean off, and a bare `--llm-no-temperature` no longer swallows
the next argument. **Behaviour change:** the space form
`--llm-no-temperature false` therefore no longer means off. `false` is
now a positional argument; write `--llm-no-temperature=false`.
- **Schema annotations no longer refuse a tool** (#397). A `tools_from`
entry's `input_schema` (or a `response.json_schema`, or a Definition's
`input_schema`/`output_schema` at write time) that carried `$schema`,
`$id`, `$comment`, `deprecated`, `readOnly`, `writeOnly` or `x-*` was
refused (`RUN-E019`, "unsupported keyword $schema"). MCP servers publish
these routinely. They are now accepted and dropped at every level
(`json_schema_subset::strip_annotations`). `title`, `description`,
`examples` and `default` were already accepted and are kept, and the
structural subset rule is unchanged.
- **A streamed turn records `served_model`** (#394). The OpenAI-compatible,
Anthropic and Ollama stream accumulators now read the served model as the
whole-response parsers do. A run observed through `onEvent`, which streams,
no longer journals less provenance than the same run unobserved.
- **An abstract node's call to a code-carrying Definition runs its blob.** The
driver looked up a model-issued call's code by node, found the abstract
node's own pin, and sent the call to `--tool-cmd`. It now finds the
Definition by its frozen content address.
- **The CLI's `--llm-token-field`, `--llm-no-temperature` and
`--llm-extra-body` take effect.** `areev run`'s usage text has listed them
since #285, but nothing read them. They now build the request profile
(`$AREEV_RUN_LLM_*` twins).

## [1.12.2] — 2026-10-06

### Fixed
Expand Down Expand Up @@ -4794,7 +4906,8 @@ ecosystem adapters, and the enterprise plane.
`crates/areev-bench` (`RESULTS.md` has the numbers), with perf gates
(`bench`, `voice_loop`) run as examples.

[Unreleased]: https://github.com/AreevAI/areev/compare/v1.12.2...HEAD
[Unreleased]: https://github.com/AreevAI/areev/compare/v1.13.0...HEAD
[1.13.0]: https://github.com/AreevAI/areev/compare/v1.12.2...v1.13.0
[1.12.2]: https://github.com/AreevAI/areev/compare/v1.12.1...v1.12.2
[1.12.1]: https://github.com/AreevAI/areev/compare/v1.12.0...v1.12.1
[1.12.0]: https://github.com/AreevAI/areev/compare/v1.11.1...v1.12.0
Expand Down
36 changes: 18 additions & 18 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ dependencies = [

[[package]]
name = "areev"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev-cal",
"areev-context",
Expand All @@ -177,7 +177,7 @@ dependencies = [

[[package]]
name = "areev-bench"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev-cal",
"areev-context",
Expand All @@ -195,7 +195,7 @@ dependencies = [

[[package]]
name = "areev-cal"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev-core",
"areev-store",
Expand All @@ -217,7 +217,7 @@ dependencies = [

[[package]]
name = "areev-conformance"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev",
"areev-cal",
Expand All @@ -233,7 +233,7 @@ dependencies = [

[[package]]
name = "areev-context"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev-cal",
"areev-core",
Expand All @@ -246,7 +246,7 @@ dependencies = [

[[package]]
name = "areev-core"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"hex",
"insta",
Expand All @@ -262,7 +262,7 @@ dependencies = [

[[package]]
name = "areev-llm"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev-core",
"areev-loop",
Expand All @@ -274,15 +274,15 @@ dependencies = [

[[package]]
name = "areev-loop"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"serde",
"serde_json",
]

[[package]]
name = "areev-loop-adapter"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev-cal",
"areev-core",
Expand All @@ -297,7 +297,7 @@ dependencies = [

[[package]]
name = "areev-mcp"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev-cal",
"areev-core",
Expand All @@ -312,7 +312,7 @@ dependencies = [

[[package]]
name = "areev-py"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev",
"areev-cal",
Expand All @@ -329,7 +329,7 @@ dependencies = [

[[package]]
name = "areev-run"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev-cal",
"areev-core",
Expand All @@ -347,7 +347,7 @@ dependencies = [

[[package]]
name = "areev-run-core"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev-core",
"serde",
Expand All @@ -357,7 +357,7 @@ dependencies = [

[[package]]
name = "areev-server"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev-cal",
"areev-core",
Expand All @@ -379,7 +379,7 @@ dependencies = [

[[package]]
name = "areev-store"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"aes-gcm 0.11.1",
"areev-core",
Expand All @@ -405,7 +405,7 @@ dependencies = [

[[package]]
name = "areev-trigger"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"areev-cal",
"areev-core",
Expand Down Expand Up @@ -3200,9 +3200,9 @@ dependencies = [

[[package]]
name = "rustls"
version = "0.23.45"
version = "0.23.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
dependencies = [
"log",
"once_cell",
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ exclude = ["areev-sandbox", "areev-tools"]
members = ["crates/areev-core", "crates/areev-store", "crates/areev-cal", "crates/areev-cli", "crates/areev-mcp", "crates/areev-context", "crates/areev-server", "crates/areev-py", "crates/areev-bench", "crates/areev-loop", "crates/areev-loop-adapter", "crates/areev-llm", "crates/areev-conformance", "crates/areev-run-core", "crates/areev-run", "crates/areev-trigger"]

[workspace.package]
version = "1.12.2"
version = "1.13.0"
edition = "2021"
license = "MIT OR Apache-2.0"
authors = ["MindGryd Software Private Limited"]
Expand Down
1 change: 1 addition & 0 deletions ERROR_CODES.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ in source.
| `RUN-E029` | `NotPausable` | A pause was asked of a run that cannot be paused (#344): it already finished — completed, failed, stalled, canceled or out of budget — or a cancel is pending against it (cancel wins over pause; `cancel` on a paused run finalizes it). Pausing an already-paused or already-pause-requested run is NOT this error: it is idempotent and answers with the standing request |
| `RUN-E030` | `NoDecider` | A plan binds a decision node (a Tool Definition whose `executor_uri` is `areev://decide`) and this host has no decision backend installed. Raised at run start, the V7 freeze, before the run exists and naming the node. `resume` and a forking `fork` raise it too, before the lease is taken, on a host without a backend. Install one (`--decide <chain>` / `--decide-cmd`, `$AREEV_DECIDE`, `Runner::with_decider`) or bind the node to an ordinary tool. The scheduler's OPTIONAL asks (the decision-guided fold and the tool-offer narrowing) never raise it: without a backend they are not asked |
| `RUN-E031` | `SavedQueryUnavailable` | A plan's `op: query` read (#370) cannot be served. At run start (the V7 freeze, before the run exists, naming the node): no saved query has the name, the declaration supplies a parameter the query does not declare, or it leaves a required parameter unsupplied. At dispatch (the node fails, not retried): the body pinned into the manifest at start is missing, or no longer matches the SHA-256 recorded beside it. The run reads the PINNED body, never the live `qry:` row, so a query redefined or dropped mid-run is not this error — it cannot reach a running run at all |
| `RUN-W001` | (warning, `RunManifest.warnings`) | A node binds a client or decision Definition that declares `locked_params` (#393). No model calls such a tool, so there is nothing to lock. The locks are ignored, as 1.12 ignored them, and the start records this warning, which `run inspect` shows under `warnings`. The run is not refused |

### `TRG` — triggers (`areev-trigger/src/error.rs`)

Expand Down
2 changes: 1 addition & 1 deletion areev-sandbox/Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = 4

[[package]]
name = "areev-sandbox"
version = "1.12.2"
version = "1.13.0"
dependencies = [
"serde",
"serde_json",
Expand Down
2 changes: 1 addition & 1 deletion areev-sandbox/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "areev-sandbox"
version = "1.12.2"
version = "1.13.0"
edition = "2021"
# wasmi's stable line sets this; deliberately NOT inherited from the workspace,
# because this package is not a member of it.
Expand Down
10 changes: 5 additions & 5 deletions crates/areev-bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ homepage.workspace = true
publish = false

[dependencies]
areev-core = { path = "../areev-core", version = "1.12.0" }
areev-core = { path = "../areev-core", version = "1.13.0" }
# `cargo run -p areev-bench --features postgres --bin pg_bench` — the
# server-tier latency harness (needs DATABASE_URL).

areev-store = { path = "../areev-store", version = "1.12.0" }
areev-cal = { path = "../areev-cal", version = "1.12.0" }
areev-context = { path = "../areev-context", version = "1.12.0" }
areev-server = { path = "../areev-server", version = "1.12.0" }
areev-store = { path = "../areev-store", version = "1.13.0" }
areev-cal = { path = "../areev-cal", version = "1.13.0" }
areev-context = { path = "../areev-context", version = "1.13.0" }
areev-server = { path = "../areev-server", version = "1.13.0" }
areev-loop = { path = "../areev-loop" }
areev-loop-adapter = { path = "../areev-loop-adapter" }
areev-llm = { path = "../areev-llm" }
Expand Down
4 changes: 2 additions & 2 deletions crates/areev-cal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ keywords = ["query-language", "parser", "memory", "cal", "ai-agents"]
categories = ["parser-implementations", "database-implementations"]

[dependencies]
areev-core = { path = "../areev-core", version = "1.12.0" }
areev-store = { path = "../areev-store", version = "1.12.0" }
areev-core = { path = "../areev-core", version = "1.13.0" }
areev-store = { path = "../areev-store", version = "1.13.0" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
logos = "0.14"
Expand Down
Loading
Loading