ember 0.6.0
Tool calling now works in streaming mode, and agent memory got a strict, predictable session contract.
Highlights
Tool calling in streaming mode (#45)
Agent.stream_run() no longer refuses to run with tools — it executes tool calls in a loop exactly like run(): results go back to the model as tool messages, and generation continues until the final text answer.
for fragment in agent.stream_run("What's the weather in Moscow?"):
print(fragment, end="", flush=True) • StreamChunk gained a tool_calls field. The adapter emits it exactly once per round — in the final chunk, fully reassembled from the stream deltas, with delta empty; it is None in every other chunk.
• OpenAIProvider.stream() reassembles tool-call deltas, including parallel calls, which the OpenAI SDK splits across chunks by index and by field (id / name / arguments).
• ToolCallLimitError semantics now match run(): the shared _tool_call_limit_error() helper guards both paths, so max_tool_steps (default 10) applies identically.
Two things worth knowing:
• The stream is not segmented into rounds: interim replies before tool calls and the final answer arrive back-to-back in a single stream, and you cannot tell them apart. Use the non-streaming run() if you need that.
• While a tool runs (a shell command, an MCP server call) the stream is simply silent — Iterator[str] has no way to signal "work in progress".
Stricter session IDs in memory (#46)
Session IDs used to be silently sanitized: every character outside [A-Za-z0-9_.-] was replaced with _. That was not reversible — "ручная" and "ручной" both collapsed into the file ______.json, the second save_session() overwrote the first, and load_session() returned someone else's data with no indication of error. The same collision broke exclude_session_id in search(): the current session was not excluded from recall.
IDs are now validated instead of rewritten. Allowed: Latin letters, digits, _, -, and . (not as the first or last character), up to 128 characters. Anything else raises InvalidSessionIdError — a subclass of ValueError, so existing except ValueError handlers keep working.
The ASCII restriction is deliberate: on APFS/HFS+ filenames are normalized, and Unicode IDs would reintroduce unresolvable collisions. Since a valid ID equals the filename without its extension, the ID is recovered unambiguously from the directory.
▌ Migration: if you pass non-ASCII or otherwise unusual IDs today, they were
▌ silently mangled before. Rename them to valid IDs — or, if you rely on the
▌ old behavior, sanitize them yourself before constructing the agent.
Enumerate sessions: Memory.list_sessions() (#46)
Consumers (CLIs, UIs) no longer need to read *.json files directly just to show a session list.
from ember import FileMemory
memory = FileMemory("/tmp/ember-memory")
for info in memory.list_sessions():
print(info.session_id, info.message_count, info.updated_at)
# user-42 2 2026-09-11 04:25:58+00:00 • New SessionInfo dataclass (frozen, slots): session_id, message_count, updated_at (UTC), and size_bytes (None when the backend doesn't track size).
• list_sessions() is an abstractmethod like the rest of Memory — custom backends (Redis, Postgres, SQLite) must implement it.
• Ordering is part of the contract: newest first, ties broken by ascending session_id, so the output can be rendered as-is.
• FileMemory counts non-empty JSONL lines without parsing content (one corrupt file doesn't break listing the others) and filters stray *.json files by the same rule as search().
Installation
pip install "emberio-labs-ember[openai]" # with OpenAI support
pip install "emberio-labs-ember[mcp]" # with MCP client support
pip install emberio-labs-ember # core (no providers) On PyPI the package is published as emberio-labs-ember; the import name is still
ember.
Compatibility
No public API removals. Behavior changes to be aware of:
• stream_run() with tools set used to raise ValueError; it now runs the tool loop.
• Invalid session_id values that used to be silently sanitized now raise InvalidSessionIdError (a ValueError subclass).