Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ User-visible changes to Imp are recorded here.

## Unreleased

## 0.15.0 — 2026-10-16

### Added

- ReActV2 `finish: :tools`: only a `finish_on` tool ends a turn. Text without
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ text or JSON you can score, such as an agent's tool descriptions.
## Install

```elixir
{:imp, "~> 0.14.0"}
{:imp, "~> 0.15.0"}
```

Imp needs Elixir 1.19 or later and a C and C++ compiler, for the native code
Expand All @@ -162,7 +162,7 @@ large-scale benchmarking. Bug reports and pull requests are welcome.
- [Getting started](docs/getting-started/index.md) builds one program step by
step, from the first call to a supervised server, with real scores.
- [Coming from DSPy](docs/coming-from-dspy.md) maps DSPy's names to Imp's.
- [Tutorials](https://github.com/deepfates/imp/tree/v0.14.1/livebooks) are Livebook notebooks
- [Tutorials](https://github.com/deepfates/imp/tree/v0.15.0/livebooks) are Livebook notebooks
you can run offline or with a key.
- The [cheatsheet](docs/cheatsheet.cheatmd) has the common calls on one page.

Expand Down
80 changes: 40 additions & 40 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
# Imp v0.14.1
# Imp v0.15.0

Imp is a framework for typed, optimizable language-model programs on the BEAM.
This release fixes how ReActV2 replays a history that crosses the two finish
modes 0.14.0 introduced. Under `finish: :submit`, a plain-text answer recorded
under `finish: :auto` is no longer taken for an unsent thought. Under
`finish: :auto`, a thought-only step recorded under `finish: :submit` is
followed by "Your message above was not sent." instead of standing as a sent
message. It adds no options and changes no public API. A history written
only in a plain-text loop replays byte-for-byte as in 0.14.0, and under
`finish: :submit` only steps that carry the signature's outputs are treated
differently. These are fixes to the opt-in `finish: :submit` that 0.14.0
added, so under Imp's versioning rule this is a patch release.
This release adds two ReActV2 options a caller opts into: `finish: :tools`,
which ends a turn only through a `finish_on` tool, for a program that acts
only through its tools, and `history_replay: :actions`, which shows earlier
turns without the text and reasoning written beside their calls. Programs that
use neither send byte-for-byte the requests 0.14.1 sent and save as before.
New options make this a minor release under Imp's versioning rule.

## Install

```elixir
{:imp, "~> 0.14.0"}
{:imp, "~> 0.15.0"}
```

`~> 0.14.0` takes later 0.14 patch releases and nothing newer. The two-part
form, `~> 0.14`, means `>= 0.14.0 and < 1.0.0` in Mix, so `mix deps.update
`~> 0.15.0` takes later 0.15 patch releases and nothing newer. The two-part
form, `~> 0.15`, means `>= 0.15.0 and < 1.0.0` in Mix, so `mix deps.update
imp` under it takes every later 0.x release, including minor releases that
change what callers receive.

Expand All @@ -32,35 +28,39 @@ in its release definition; see [releases that use MCP or
ACP](docs/production.md#releases-that-use-mcp-or-acp).
Ordinary Imp startup starts no protocol endpoint.

## Changes and upgrading from 0.14.0
## Changes and upgrading from 0.14

Under `{:imp, "~> 0.14.0"}`, run `mix deps.update imp` and commit `mix.lock`.
No code or data migration is needed.
Change the Imp dependency to `{:imp, "~> 0.15.0"}`, run `mix deps.update imp`,
and commit `mix.lock`. No code or data migration is needed: saved programs
without the new options load with their defaults.

- **Fixed: an answer step is never a thought.** Under `finish: :submit`, a
step recorded under `finish: :auto` as the turn's plain-text answer (text
and the signature's outputs both) was treated as a thought-only step when
its history was passed on: the "Your message above was not sent." line
followed each answer that had been delivered, and models sent those
answers again. A step that carries the signature's outputs is an answer:
it gets no line, and the forced `submit` never moves it to the user side.
Only a step with text, no calls and no outputs is a thought.
- **Fixed: a plain-text loop keeps roles alternating.** In a plain-text
ReActV2 loop (`finish: :auto`), a history written under `finish: :submit`
replayed each thought-only step as an assistant message, which there reads
as a message that was sent, often right before the answer's: two assistant
messages in a row. Each such step is now followed by a user message, "Your
message above was not sent." (the line's factual half; a plain-text loop
has no `submit` to name). A history written only in a plain-text loop has
no thought-only step, so its requests are byte-for-byte unchanged.
- **Known gap.** Under `finish: :submit`, a forced request with nothing new
to ask, on a history that ends with a plain-text answer step, still ends
on that assistant message, since an answer is not moved. Resume with new
inputs or a `last_request_note`.
- **Added: `finish: :tools`.** Only a `finish_on` tool ends a ReActV2 turn.
There is no `submit`; the outputs come from the `finish_on` functions. A
step that writes text and calls no tool is a thought, followed by one
ordinary step and a line naming the ending tools (`unsent_note:` replaces
it). Nothing is forced and no last request is made: a failed request gets
one more ordinary step, once per turn and not on the last allowed step; a
second thought in a row, a step that says nothing, the step limit or a
second failure ends the turn `:incomplete`, with the request's error in
`termination_error`, or `:deadline_exceeded` when the deadline has passed.
A program with `finish: :tools` needs a `finish_on` tool and refuses
`last_request_note` and `last_request_tools`. A call may give its own
`finish:` (when the task has no input of that name), so a `:tools` program
can ask for one text answer, such as a summary, with `finish: :auto`.
- **Added: `history_replay: :actions`.** Earlier turns are shown as their
inputs, the calls with their results, and their outputs; the text and
reasoning written beside the calls, and steps that only wrote text, are left
out. The current turn is shown whole: it starts with the call's new inputs
or its own `last_request_note`, or, on a resume with neither, at the last
step that carries inputs. The returned history keeps every step. This
diverges from DSPy's ReActV2, which replays every step whole; the default,
`:trajectory`, still does. Some thinking APIs reject an earlier tool-call
message without its reasoning content; check your provider.
- **Added: `unsent_note:`.** The line shown after each thought-only step, in
place of Imp's. A blank line is refused.

Rollback means restoring `0.14.0` in the lock; mixed-mode histories then
replay as described above before each fix. See the
[changelog](CHANGELOG.md) for every change.
Rollback means restoring `0.14.1` in the lock and removing the new options.
See the [changelog](CHANGELOG.md) for every change.

## Known limits

Expand Down
4 changes: 2 additions & 2 deletions docs/coming-from-dspy.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ brought in.
## The five-minute version

```elixir
# pip install dspy -> {:imp, "~> 0.14.0"}
# pip install dspy -> {:imp, "~> 0.15.0"}
# lm = dspy.LM("openai/...") -> lm = Imp.req_llm("openai:gpt-5.4-mini", api_key: ...)
# dspy.Predict("q -> a") -> program = Imp.predict("q -> a", lm: lm)
# program(q="...") -> {:ok, pred} = Imp.call(program, %{q: "..."})
Expand Down Expand Up @@ -87,7 +87,7 @@ error row too. Each model request has a receive timeout, and
has no time limit of its own (an MCP tool call does, 30 seconds by default),
so to bound an agent, start it with `Imp.start_run/3` and cancel it when you
choose. The
[deployment example](https://github.com/deepfates/imp/blob/v0.14.1/examples/deployment/README.md)
[deployment example](https://github.com/deepfates/imp/blob/v0.15.0/examples/deployment/README.md)
is a complete OTP application.

**Optimizers see what you name.** DSPy finds predictors by walking a module's
Expand Down
2 changes: 1 addition & 1 deletion docs/diving-deeper/modules-and-composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ A custom module is your code, so it is not saved as a whole. What an optimizer
chose for it is data: `Imp.ProgramParameters.values/1` reads it,
`Imp.ProgramParameters.apply_values/2` puts it on a freshly built program, and
`Imp.Optimizer.Artifact` writes it to a checksummed file. The
[deployment example](https://github.com/deepfates/imp/blob/v0.14.1/examples/deployment/README.md)
[deployment example](https://github.com/deepfates/imp/blob/v0.15.0/examples/deployment/README.md)
does this in a supervised application.

### Built-in variants
Expand Down
2 changes: 1 addition & 1 deletion docs/diving-deeper/saving-and-artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Save the whole program when it is made of Imp's modules: `Imp.predict/2`,
when the program is your own struct implementing `Imp.Module`, or when you
want the program's code to live in your release and only its tuned text to
cross the persistence boundary. The
[deployment example](https://github.com/deepfates/imp/tree/v0.14.1/examples/deployment)
[deployment example](https://github.com/deepfates/imp/tree/v0.15.0/examples/deployment)
does the second.

### 2. JSON with a checksum
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/running-in-your-application.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ Changing models is then a configuration change, and rotating a key is a
restart.

The
[deployment example](https://github.com/deepfates/imp/blob/v0.14.1/examples/deployment/README.md)
[deployment example](https://github.com/deepfates/imp/blob/v0.15.0/examples/deployment/README.md)
is the complete version of this page: a two-stage program like `TicketTriage`,
learned parameters loaded and reloaded as an `Imp.Optimizer.Artifact`, crashed
and timed-out calls contained, and the whole thing restarted in a fresh OS
Expand Down
4 changes: 2 additions & 2 deletions docs/getting-started/setting-up.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ Add Imp to a Mix project:
~~~elixir
def deps do
[
{:imp, "~> 0.14.0"}
{:imp, "~> 0.15.0"}
]
end
~~~

or, for a script or a Livebook notebook:

~~~elixir
Mix.install([{:imp, "~> 0.14.0"}])
Mix.install([{:imp, "~> 0.15.0"}])
~~~

Imp needs Elixir 1.19 and a C and C++ compiler, for the native code in two
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/where-to-go-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The module documentation is the reference for every function.
[Running Imp in production](../production.md) covers what the last page began:
supervision, provider failures, runs you can observe and cancel, and
telemetry. The
[deployment example](https://github.com/deepfates/imp/blob/v0.14.1/examples/deployment/README.md)
[deployment example](https://github.com/deepfates/imp/blob/v0.15.0/examples/deployment/README.md)
is a complete application to copy from.

## Try it in a notebook
Expand Down
8 changes: 4 additions & 4 deletions docs/production.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ where its credentials come from, how long a call may take and what it may
cost. This page covers those decisions in the order you meet them: starting
Imp, credentials, concurrency, timeouts, cost and caching, persistence and
telemetry. The
[deployment example](https://github.com/deepfates/imp/tree/v0.14.1/examples/deployment)
[deployment example](https://github.com/deepfates/imp/tree/v0.15.0/examples/deployment)
is a complete OTP application that puts them together.

## Imp in your supervision tree

Imp is an OTP application, and adding `{:imp, "~> 0.14.0"}` to your
Imp is an OTP application, and adding `{:imp, "~> 0.15.0"}` to your
dependencies starts it with yours. It supervises its settings, its response
cache and the task pools that bound its work. It opens no listener and
starts no subprocess, and the MCP and ACP runtimes start only when you use
them. In a script, `Mix.install([{:imp, "~> 0.14.0"}])` starts it too.
them. In a script, `Mix.install([{:imp, "~> 0.15.0"}])` starts it too.

## Credentials at runtime

Expand Down Expand Up @@ -263,7 +263,7 @@ standard error.

## The reference application

[`examples/deployment`](https://github.com/deepfates/imp/tree/v0.14.1/examples/deployment)
[`examples/deployment`](https://github.com/deepfates/imp/tree/v0.15.0/examples/deployment)
is a small OTP application that loads a checksummed artifact at startup, reads
its key from the environment, serves calls from bounded supervised tasks,
answers overload and timeouts with errors instead of blocking, and reloads
Expand Down
2 changes: 1 addition & 1 deletion examples/deployment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,4 @@ Use a whole-program artifact for a supported portable Imp shape. Use
its selected predictor parameters should cross the persistence boundary.

During source development set `IMP_PATH` to the Imp checkout. Other
applications use the Hex dependency in `mix.exs`, `{:imp, "~> 0.14.0"}`.
applications use the Hex dependency in `mix.exs`, `{:imp, "~> 0.15.0"}`.
2 changes: 1 addition & 1 deletion examples/deployment/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule ImpDeployment.MixProject do

defp deps do
case System.get_env("IMP_PATH") do
nil -> [{:imp, "~> 0.14.0"}]
nil -> [{:imp, "~> 0.15.0"}]
path -> [{:imp, path: path}]
end
end
Expand Down
2 changes: 1 addition & 1 deletion livebooks/01_real_lm_front_door.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ imp_path = System.get_env("IMP_PATH")
lockfile = imp_path && Path.join(imp_path, "mix.lock")

cond do
is_nil(imp_path) -> Mix.install([{:imp, "~> 0.14.0"}])
is_nil(imp_path) -> Mix.install([{:imp, "~> 0.15.0"}])
File.regular?(lockfile) -> Mix.install([{:imp, path: imp_path}], lockfile: lockfile)
true -> Mix.install([{:imp, path: imp_path}])
end
Expand Down
2 changes: 1 addition & 1 deletion livebooks/02_without_a_provider.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ imp_path = System.get_env("IMP_PATH")
lockfile = imp_path && Path.join(imp_path, "mix.lock")

cond do
is_nil(imp_path) -> Mix.install([{:imp, "~> 0.14.0"}])
is_nil(imp_path) -> Mix.install([{:imp, "~> 0.15.0"}])
File.regular?(lockfile) -> Mix.install([{:imp, path: imp_path}], lockfile: lockfile)
true -> Mix.install([{:imp, path: imp_path}])
end
Expand Down
2 changes: 1 addition & 1 deletion livebooks/03_evaluate_and_optimize.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ imp_path = System.get_env("IMP_PATH")
lockfile = imp_path && Path.join(imp_path, "mix.lock")

cond do
is_nil(imp_path) -> Mix.install([{:imp, "~> 0.14.0"}])
is_nil(imp_path) -> Mix.install([{:imp, "~> 0.15.0"}])
File.regular?(lockfile) -> Mix.install([{:imp, path: imp_path}], lockfile: lockfile)
true -> Mix.install([{:imp, path: imp_path}])
end
Expand Down
2 changes: 1 addition & 1 deletion livebooks/04_tools_agents_mcp_rlm.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ imp_path = System.get_env("IMP_PATH")
lockfile = imp_path && Path.join(imp_path, "mix.lock")

cond do
is_nil(imp_path) -> Mix.install([{:imp, "~> 0.14.0"}])
is_nil(imp_path) -> Mix.install([{:imp, "~> 0.15.0"}])
File.regular?(lockfile) -> Mix.install([{:imp, path: imp_path}], lockfile: lockfile)
true -> Mix.install([{:imp, path: imp_path}])
end
Expand Down
4 changes: 2 additions & 2 deletions livebooks/05_operating_imp.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ imp_path = System.get_env("IMP_PATH")
lockfile = imp_path && Path.join(imp_path, "mix.lock")

cond do
is_nil(imp_path) -> Mix.install([{:imp, "~> 0.14.0"}])
is_nil(imp_path) -> Mix.install([{:imp, "~> 0.15.0"}])
File.regular?(lockfile) -> Mix.install([{:imp, path: imp_path}], lockfile: lockfile)
true -> Mix.install([{:imp, path: imp_path}])
end
Expand Down Expand Up @@ -103,7 +103,7 @@ Enum.each(busy, &Task.await/1)
refused
```

The [deployment example](https://github.com/deepfates/imp/tree/v0.14.1/examples/deployment)
The [deployment example](https://github.com/deepfates/imp/tree/v0.15.0/examples/deployment)
wraps the same pattern in a `ProgramServer` that also reloads parameters while
serving.

Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Imp.MixProject do
use Mix.Project

@version "0.14.1"
@version "0.15.0"

def project do
[
Expand Down
2 changes: 1 addition & 1 deletion priv/public_api.json
Original file line number Diff line number Diff line change
Expand Up @@ -10359,7 +10359,7 @@
"types": []
}
],
"package_version": "0.14.1",
"package_version": "0.15.0",
"schema_version": 3,
"scope": "Curated Imp API compiled from package-shipped sources. Exports come from non-hidden documentation entries; callbacks, types, and struct fields are category- and policy-gated."
}
2 changes: 1 addition & 1 deletion test/package_contract_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ defmodule PackageContractTest do
version = Mix.Project.config()[:version]
dependency = hex_dependency(version)

assert version == "0.14.1"
assert version == "0.15.0"
assert File.read!("RELEASE_NOTES.md") =~ "# Imp v#{version}"
assert File.read!("CHANGELOG.md") =~ "## #{version}"
assert File.read!("examples/deployment/mix.exs") =~ dependency
Expand Down
Loading