Imp is a framework for typed, optimizable language-model programs on the BEAM.
This release shows the model the turns it actually had, stops a plain-text
ReActV2 step from telling the model two ways to finish, keeps images that MCP
tools return, makes the ATIF export read as the model saw the run, and counts
streamed calls in Imp.Usage. What the model receives changes (history
turns, the system message's field list and opening line, and opt-in tool
images), and so do the meanings of ATIF fields, so this is a minor release
rather than a patch.
Install
{:imp, "~> 0.10"}Every dependency comes from Hex. Use a path dependency only while developing against a local checkout.
ExMCP and erlexec are declared runtime: false, so an OTP release that uses
Imp.ACP or Imp.MCP must list applications: [ex_mcp: :load, erlexec: :load]
in its release definition; see releases that use MCP or
ACP.
Ordinary Imp startup starts no protocol endpoint.
Changes and upgrading from 0.9
Change the Imp dependency to {:imp, "~> 0.10"} (~> 0.9 does not take
0.10), run mix deps.update imp, and commit mix.lock. No data migration is
needed: stored history, saved programs and run events are read as before and
rendered in the new form. Each change below says what a caller has to
change, if anything.
- History turns are shown as recorded. A stored turn no longer gives an output it did not record the filler "Not supplied for this conversation history message."; it leaves that output out, and a turn that recorded no output is its user message alone. A predictor that names a tool-calls output, but is not a tool loop, now keeps its recorded outputs as the assistant text when its history is replayed with native tool calls, where it showed an empty message. With native tool calls, a tool call whose result renders as blank text keeps its tool message instead of being left without an answer. Migration: a test, fixture or scripted response that matched the filler, or the empty message, must expect the turn without it.
- No history input in the system message. In every format, the system
message no longer lists a history input or shows a
[[ ## history ## ]]section, since history arrives as earlier turns. This covers a field typedhistoryand any input the call supplies as anImp.History. Migration: a test or custom renderer that matched the old field list or structure must match the new one; a custom:system_rendererreceives the signature without those inputs. Both history changes alter the request, so a provider prompt cache keyed on the old system message misses once after the upgrade. - ReActV2's plain-text opening line agrees with how it finishes. For an
LM that calls tools natively and a task with one unconstrained text output
(no
submittool), the system message opened with "Use the supplied tools to produceanswer" and later said the answer is the reply sent without a tool call. The opening line now reads "You are an Agent. Produceanswerfromintent, using the supplied tools to gather information and take actions." Steps withsubmit, written tool calls, and the JSON and XML formats keep the old line. Migration: a test that matched the old opening line for this mode must match the new one. - Images from MCP tools (opt-in). Import a server with
result_mode: :multimodalto keep its image content asImp.Adapter.Types.Imagevalues beside the result's text. The Chat adapter keeps the text in the tool message and sends the images in user messages after all of the turn's tool responses, each labelled with the tool name and call ID as tool data.Imp.Adapter.Chat.format_tool_content/1is new and is the default tool-result renderer;format_tool_result/1stays textual. Migration: none for the default:textand:structuredmodes. A custom:tool_result_rendererthat should pass images through callsformat_tool_content/1and bounds only the strings. Any tool, not only an MCP one, whose result is a list of strings and images with at least one image now has those images sent; such a list was rendered as text before. - ATIF shows a run as the model saw it. In
Imp.Trajectory.to_atif/2each model response is one agent step (llm_call_count: 1, null when served from Imp's cache) carryingmodel_name,metrics, the provider's own reasoning asreasoning_contentand the tool calls run for it. A tool result'scontentis the tool message the model read next, with the tool's own output inextra.outputwhen it differs andextra.seen_by_modelsaying whether a later request carried it. Replayed history keeps its tool calls, results and reasoning;agent.model_name,agent.tool_definitionsandfinal_metricsare filled in. Migration: a reader that tookreasoning_contentas the visible thought readsextra.next_thought; one that expected each tool call as its ownllm_call_count: 0step readstool_callson the response step (only a call a host dispatched itself is still its own step); one that read a call'sextra.outcomeon the step reads it on the call; and one that displayedcontentas the tool's raw output readsextra.outputwhen present. An:agentoption is now merged over the computed agent rather than replacing it. (On main this entry had been filed under 0.9.0 after that release was tagged; 0.9.0 does not contain it.) - Streamed calls are counted in
Imp.Usage.Imp.Usage.track/1now counts a streamed call (Imp.collect/3,Imp.stream/3withprovider_stream: true), successful, failed or halted, and a call made byImp.Predictwithtrack_usageon, including a failed one. A streamed call has ReqLLM'sestimated_costwhen ReqLLM prices the model, as a non-streamed call does. Migration: a host that worked around the 0.9 limit by adding usage from:model_responseevents, or a prediction'sget_lm_usage/1, into its own tracker now counts those calls twice and must stop.
Rollback means restoring the previous dependency requirement, lock and application release; it brings back the filler text, the history section, the contradictory opening line, dropped tool images, the 0.9 ATIF shape and uncounted streamed usage. Provider-free tests assert the new request messages, the image bytes and their order on the provider wire, the ATIF projection, and usage counted across processes; this release does not establish how any particular model responds to the revised prompts or whether it perceives the images. See the changelog for every change.
Known limits
- The usage map (
:usageon the:model_responseevent,Imp.Usage,Imp.Prediction.get_lm_usage/1) is ReqLLM's, unchanged. Its:costand:total_costare ReqLLM's catalog estimate, not a charge, for streamed and non-streamed calls alike, and OpenRouter's charge is its"cost". Read money fromcostandestimated_cost. - An error the provider sends inside a stream has
statusnilandretryable: true, because ReqLLM's stream decoder keeps only its message; the same error in a non-streamed response may carry a status that says not to retry. - A flat two-element name-first list that is not itself an element of a
list (at the top level, in a tuple, or under a key that is not a credential
name), such as
["password", "hunter2"], is not redacted, where 0.5.0 redacted it. Such a list is read as a key and value only as an element of a list, so that lists of names such aswith_inputs([:api_key, :question])survive saving. - The GEPA, SIMBA, MIPROv2, InferRules and random-search checkpoints redact their failure reasons but otherwise hold the resume state as it is: GEPA's candidates, evaluation cache, proposed instructions and reflection data, and the others' instructions, demos and scores. Fast-Slow and Playbook checkpoints likewise hold their prompts, rollouts and playbooks. Treat checkpoint files as sensitive.
- A ReActV2 step whose prose quotes its own field names (a JSON object with
next_thoughtortool_callskeys, a[[ ## field ## ]]line, a<next_thought>tag) is not read as prose and goes to the JSON fallback, which usually costs one more call. If the model repeats the same reply, the step ends:incomplete. Beside native tool calls, such a thought becomesnil; the calls still run. - Tool images reach the model as user messages after the turn's tool responses, labelled with the call, not nested in the tool result, even for providers that accept images in tool results. Only MCP text and image blocks are converted; other block types stay text, image URLs in text are never fetched, and the selected model must accept image input of that format and size.
- ATIF's
extra.seen_by_modeland the read-ascontentcome from matching a tool message in the next captured request. A result that request does not carry, including one that ended the run, keeps the tool's own output and is marked unseen.agent.tool_definitionscomes only from:tools_sentevents, andfinal_metricstotals are left out when any response lacks that figure. - When a streamed ReqLLM request fails, ReqLLM's own warning log prints the error with its response headers. Imp strips them from the error it returns, but cannot change that log line.
- In command output, a
Bearertoken of 12 to 15 characters, or one with no digit, is shown when more text or a newline follows it. 0.5.0's command pipeline hid any such token of 12 or more characters. - A GEPA checkpoint taken between preparing and starting a full validation resumes by rejecting that candidate, as before.
The CHANGELOG records every user-visible change in this
release. Generated module documentation is the complete API reference. Start
with Imp, Imp.Signature, Imp.Module, Imp.Evaluate, Imp.Optimizer,
Imp.ACP, Imp.MCP, and Imp.Telemetry.