Repository navigation
Released 2026-08-11 · Changelog · PyPI
0.1.11 shipped tool-call recognition as text scraping: an Axon's Neuron
answered, and a regex-and-fence parser went looking through the reply for
something that looked like a call. That worked for models that narrate JSON in
prose, and it silently failed for the one provider that doesn't - Anthropic
returns a tool call as a separate structured tool_use content block, never as
text, and the wrapper around it kept only the text. The claude dialect was
unreachable against the real API. 0.1.12 fixes that by reading structured
calls off the provider's raw response first, on every provider, and falling
back to text scraping only when there is nothing structured to read.
Alongside the fix: calls that used to vanish now don't. Multiple tool calls in
one reply, a call wrapped in chain-of-thought JSON, a truncated <tool_call>
tag, a zero-argument call sent as "arguments": "" - each of these previously
resolved to "no call found," and an agent that stops mid-task on a dropped
call reports success. This release also gives Genesis a way to edit the one
part of an LLM Neuron that never appeared in its config form - the system
prompt - and fixes a handful of real bugs: a Prism button that degraded the
Brain View on every click, a Genesis synapse that could hang with no crash and
no traceback, and a wheel that shipped a stale Genesis UI because nothing
built it at release time.
Native tool calls
extract_native_calls() reads a structured call straight off the raw
provider response instead of parsing the rendered text:
| Provider shape | Where it reads from |
|---|---|
| OpenAI, vLLM, TGI, Together, Groq, OpenRouter, Mistral, Azure | choices[0].message.tool_calls |
| Anthropic | content[] blocks with type == "tool_use" |
| Ollama | message.tool_calls |
The Anthropic path is the actual bug fix. _AnthropicNeuron returns text-only
in response; a tool_use block sat untouched in meta, which is exactly
where extract_native_calls now reads it from. Nothing about the Neuron
wrapper's output changed - Genesis and Prism see the same response field
they always did - only where the Axon looks for a call.
Recognition order flipped: structured calls are tried first on every Axon;
Neuron-specific text recognizers (Hermes ChatML, Claude-in-prose, Codex JSON)
now only run when nothing structured is there to read. tool_standard= still
picks the text dialect when one is needed, and is now inferred when not
given explicitly - from the wrapped provider (claude for an Anthropic
Neuron) or from the model name (hermes for qwen/nous/functionary-family
models, codex for gpt/llama/mistral/deepseek/gemma-family models). An
explicit tool_standard= always wins over inference.
Calls that were silently dropped now aren't
Every text parser gained an _all variant that returns every call in a reply,
in order, instead of the first one found:
- A reply with more than one call used to return only the first. Now every
call is recovered; by default only the first still runs (see
parallel_toolsbelow), but the rest show up in the observation instead of
disappearing. - A call wrapped in chain-of-thought JSON -
{"thoughts": "...", "name": "read", "arguments": {...}}, the shape several Llama/DeepSeek prompt
templates produce - used to fail the exact-key match required for Codex
detection. A widened guard now ignores a known set of narration keys
(thoughts,reasoning,observation,id,call_id,type,index)
around the two that matter. - A truncated
<tool_call>tag - a reply cut off atmax_tokensbefore
the closing tag, or a served endpoint that corrupts one of the pair - used
to degrade into "no call found," the worst available failure, since the
agent then reports whatever half-formed text came back as a final answer.
The Hermes parser now salvages a call from an unclosed or malformed tag. - A small model narrating before calling -
I'll check the weather. {"name": "get_weather", "arguments": {...}}with no fence around the
object - used to be dropped by a strict fence-or-nothing rule. It's now
recovered, deliberately scoped to only the trailing object in a reply so a
call mentioned mid-sentence (The call {"name": "bash", ...} would be dangerous.) is never mistaken for a real one. - A zero-argument call sent as
"arguments": ""used to read as a parse
failure instead of{}. - The same call recovered twice by overlapping scan passes (whole reply,
fenced block, trailing object) is now de-duplicated before it's returned.
parallel_tools, and arguments checked before dispatch
Axon("assistant", neuron_fn, effectors=TOOLS, parallel_tools=True)Off by default. With one call recognized, nothing changes. With more than one:
by default only the first runs and the rest are reported under
dropped_calls (name, args, call_id) rather than vanishing; with
parallel_tools=True all of them run, sequentially, in the order the model
returned them - sequential because a batch of tool calls is still an ordered
list of side effects, not an unordered set.
Every call - single or parallel - now has its arguments checked against the
Effector's declared schema before dispatch. A call that fails validation never
reaches the tool; the Axon returns an error the model can read and correct
from, instead of a stack trace from the far side of the wire or a tool that
half-runs on bad input.
cosmonapse.effector.schema - schemas and validation, as a module
from cosmonapse import tool_schema, ToolSchema, validate_args
schema = tool_schema(search_web) # name, description, and params from the function
binding = EffectorBinding(tools=("search_web",), schemas=(schema,), ...)ToolSchema renders to any provider's tool-declaration shape
(.to_openai(), .to_anthropic(), .to_ollama()); render_tools() picks the
right one from a dialect name. tool_schema(fn) builds one from an ordinary
function - name from __name__, description from the first paragraph of the
docstring, per-parameter type from the annotation, required = parameters
without a default. EffectorBinding.schemas is new: set it and, if tools=
wasn't given explicitly, the routing table is derived from the schema names,
so the two can't drift apart. The rendered payload lands in
input_data["tools"] after before_task hooks run, so a hook that rebuilds
the input from scratch can't accidentally drop it.
tool_result_messages() is the other half - turning an Axon's tool
observations back into the message pairs each provider expects on the next
turn. It matters most for Anthropic, which requires every tool_result for
one assistant turn to arrive in a single user message, in the same order; a
per-provider id scheme (call_auto_N) is synthesized where a dialect (Hermes)
sends none, so results still correlate to calls on the round trip.
New at package top level: ToolSchema, tool_schema, validate_args,
render_tools, extract_native_calls, extract_tool_calls,
tool_result_messages.
Genesis: the Neuron's prompt
An LLM Neuron's system prompt is the text that gets rewritten most often and
the one thing that never showed up in the Code tab's config form - only
ollama, openai, and anthropic factories take a system= keyword, so the
corpus writes it as a module constant that a @AXON.before_task hook folds
into the request. That left the most-edited part of the module sitting in a
read-only chunk at the bottom of the tab, below every keyword nobody touches.
The new prompt card puts it at the top, editable, prose-formatted rather than
syntax-highlighted (a prompt is English; running the Python tokenizer over it
would color every "if" and "return" in the instructions). A few things it
gets right on purpose:
- Read-only when saving would lose something. A prompt built from an
f-string, concatenation, or with a real comment between its pieces is shown
but not editable - saving from the card would flatten it. Comment detection
is tokenize-based, not a#regex scan, because prompts routinely contain a
literal#in their own text (a "# Tools" heading) that would otherwise
false-positive as a comment. - Warns when nothing reads it. If the module never references the prompt
constant by name, the card says so - the SDK doesn't look forSYSTEMby
convention, a hook has to put it in the request, so an unread constant never
reaches the model no matter how carefully it's written. - Round-trips before it confirms. After a save, the file is re-parsed and
compared against what was typed; a mismatch raises rather than silently
writing something subtly different. The literal renderer also prefers single
quotes when a prompt contains"but not', since a prompt full of JSON
examples otherwise turns into a wall of backslashes under the default double
quote.
New route:POST /api/prompt. New incosmonapse/genesis-ui: a first
package-levelREADME.mddocumenting the full local API surface and the one
thing that trips up a source checkout -genesis_dist/is not rebuilt from
src/automatically;npm run build:into-wheelhas to run and the output has
to be committed, or the served UI is stale. (This is exactly the gap closed in
the release workflow below.)
Fixes
- Prism's Clear button no longer degrades the Brain View.
clear()used
to wipe the neuron registry along with the signal log. The registry is built
fromREGISTERsignals, which arrive once per participant and never again -
wiping it stripped every node of its kind, capabilities, and version with no
way back short of a reconnect. Clear now zeroes each node's counters and
leaves its identity alone. - Prism connects over
wss://on an https page. The socket URL was
hardcoded tows://; it's now derived fromlocation.protocol, since a
plainws://socket from an https page is blocked outright. - A Genesis-managed synapse could hang with no crash and no traceback.
Its stderr went to a fixed-size OS pipe that was only drained on a failure
path; once a long-running synapse filled that buffer, its next write to
stderr blocked inside the syscall forever - the process stayed alive, the
port stayed bound, and the namespace just stopped answering. Stderr now
goes to a temp file that's read by name instead of by blocking on a pipe,
so nothing the child writes can back it into a wall. MemorySynapseno longer swallows a handler's exception. Fan-out
dispatch passedreturn_exceptions=Truetoasyncio.gatherand never
looked at the results. This is the default bus forcosmo initprojects
and for Genesis's Run button, so a handler that raised used to be the first
error a new user hit - and it arrived as silence. It's now logged.- Genesis's Test-tab terminal could miss a brain's early output. The
WebSocket handler sent buffered scrollback before subscribing to new
output, dropping whatever the child process emitted in between - on a
chatty boot, the interesting part. Order is now: snapshot, subscribe, then
send. - The Genesis UI build was missing from the release workflow. Prism's
build step (npm ci,npm run build:into-wheel) existed in
release.yml; Genesis's did not, so a shipped wheel carried whatever
genesis_dist/happened to be committed rather than a fresh build. Any
Genesis UI change released without a manual build went out stale and
silent. Genesis now gets the same build step, in the same shape. - Genesis's dev-server proxy is missing
/api/brain/ws, added withws: true- without it the Test tab's terminal socket was dead undernpm run dev, which read as a Genesis bug rather than a config gap. - A multi-line value typed into any Genesis form field (a prompt, a docstring)
could render into the file as a literal with a real line break in it - a
SyntaxErroron the next parse. String escaping now covers\r/\t/
\n, not just\/". cosmo inithad ~15 lines of dead scaffolding logic duplicating the
scaffold_project()helper it was meant to call; removed, no behavior
change. Its "populated directory" error message now says--force
(matching the CLI flag) instead of "force," since the same error is now
also raised through Genesis's HTTP import path, where there is no flag.- CI's dependency install referenced a
flaskextra that hasn't existed
since thereceptorextra replaced it in 0.1.11 -ci.ymlnow installs
receptor.pyproject.toml's own[project.optional-dependencies]was
already correct; this only affected what CI exercised, not whatpip installresolves.
Test count is up to 469 across 25 files, from 401 across 22 at 0.1.11 -
46 new tests cover the tool-call transport end to end (structured beats
narrated text, dropped-vs-parallel calls, argument validation, id
correlation), 15 more cover the prompt feature's AST surgery, and a new
test_cli_init.pylocks downcosmo init's behavior directly.
Compatibility
Additive. No new signal types, no envelope changes. An Axon that never passed
effectors= behaves exactly as before - dialect inference and the native
tool-call channel only activate when there's something to call. An Axon that
already passed an explicit tool_standard= is unaffected by inference, and
extract_tool_call (singular) still works as a thin wrapper over the new
extract_tool_calls.
If you're on Anthropic with effectors= set: this release changes
observable behavior, in the direction of correctness. A tool call that used
to be silently missed will now be recognized and dispatched. If anything
downstream depended on that call never firing, it will now fire.
Install
pip install cosmonapse
# HTTP and chat Receptors, and now what CI actually tests against
pip install 'cosmonapse[receptor]'
cosmo genesis # -> 127.0.0.1:7072, edit a Neuron's prompt from the Code tab
cosmo prism # -> 127.0.0.1:7071
Links
- Changelog
- Envelope spec · SDK design · Receptor design
- Contributing
- PyPI
Licensed under Apache 2.0.