<p>Most of the time Elyra has an audience. You type, it answers, and you see everything it does. But more and more, Elyra runs with nobody looking: in CI jobs, in scripts, and under daemons that hand it a task and collect the result.</p><p>This release is for that audience. It’s small, but every line in it comes from a real failure.</p><h2>A story from the queue</h2><p>We have a project called Félagi. It’s a work portal with a small daemon that runs on a couple of Mac minis. When an issue is assigned to an agent, the daemon picks it up, starts Elyra, streams the output back, and reports the outcome.</p><p>Last week, the first task this workspace ever had went into the queue. The daemon claimed it in under a second and failed one second later:</p><pre><code class="language-text">Error: Unknown option: --output-format
</code></pre><p>The daemon’s adapter was speaking an older dialect of Elyra’s CLI. That part was on the daemon’s side to fix. But while tracing it end to end, we found two things on our side that would have bitten anyone driving Elyra from a program. Both are fixed in 0.9.55.</p><h2>Fix 1: a failed run now looks like a failure</h2><h3>Why</h3><p>When you run Elyra in JSON mode, it streams every event as a line of JSON:</p><pre><code class="language-bash">echo "Fix the failing test" | elyra --mode json
</code></pre><p>If the model request failed (bad API key, provider down, rate limit after all retries), the error was right there in the stream. But the process exited with code 0.</p><p>For a human that’s fine; you’d see the error. For a script, exit code 0 means “all good”. A CI job would turn green. A daemon would mark the task done and bill the time.</p><p>Text mode already got this right. JSON mode didn’t.</p><h3>How it works now</h3><p>If the final request fails or is aborted, JSON mode exits with code 1 and writes the error to stderr, exactly like text mode:</p><pre><code class="language-text">$ echo "Say hi" | elyra --mode json --api-key sk-ant-invalid &gt; run.jsonl
401 invalid x-api-key
$ echo $?
1
</code></pre><p>The stream itself is unchanged, so anything that already reads it keeps working. You just also get a truthful exit code:</p><pre><code class="language-bash">if ! elyra --mode json &lt; task.md &gt; run.jsonl; then
  echo "Agent run failed" &gt;&amp;2
  exit 1
fi
</code></pre><p>A small detail, but checking <code>$?</code> is exactly what a careful script does.</p><h2>Fix 2: <code>--session</code> no longer swallows your prompt</h2><h3>Why</h3><p>You can continue an earlier session with <code>--session</code> and an id or a unique prefix:</p><pre><code class="language-bash">echo "Now add tests" | elyra --mode json --session 01a15593
</code></pre><p>If that session was started in a different directory, interactive Elyra helpfully asks:</p><pre><code class="language-text">Session found in different project: /work/other
Fork this session into current directory? [y/N]
</code></pre><p>That question is read from stdin. In a script, stdin is where your prompt is. So Elyra read the first line of your prompt as the answer, decided it wasn’t “y”, printed “Aborted.”, and exited 0. No output, no error, and no idea what happened.</p><h3>How it works now</h3><p>In print, JSON, and RPC mode, Elyra doesn’t ask. It fails clearly with exit code 1 and tells you what to do:</p><pre><code class="language-text">Session 01a15593 belongs to /work/other.
Use --fork 01a15593 to copy it into this directory.
</code></pre><p>If you meant to bring it over:</p><pre><code class="language-bash">echo "Now add tests" | elyra --mode json --fork 01a15593
</code></pre><p>Interactive mode still asks, because there a question is the friendly thing to do.</p><h2>The JSON stream, documented properly</h2><h3>Why</h3><p>The Félagi adapter broke partly because it guessed at field names. That’s on us too: <code>docs/json.md</code> described the stream as it was a few versions ago. It was missing half the event types and said nothing about how to actually read a run.</p><h3>What’s there now</h3><p>The documentation lists every event the stream emits, including the session-level ones (<code>auto_retry_start</code>, <code>compaction_start</code>, <code>smart_routing</code>, and friends), and has a new section on reading a run:</p><ul><li><p><strong>Session id:</strong> only in the first line, the header. Events don’t repeat it.</p><pre><code class="language-json">{"type":"session","version":3,"id":"01a15593-…","cwd":"/work/shop"}
</code></pre></li><li><p><strong>Tool calls:</strong> <code>tool_execution_start</code> has <code>toolName</code> and <code>args</code>; <code>tool_execution_end</code> has the result and <code>isError</code>.</p></li><li><p><strong>The final answer:</strong> there’s no separate result event. It’s the last assistant message in <code>agent_end</code>.</p></li><li><p><strong>Failure:</strong> that last message has <code>stopReason</code> <code>"error"</code> or <code>"aborted"</code>, and the process exits 1.</p></li><li><p><strong>Cost:</strong> every assistant message carries <code>usage</code> with <code>cost.total</code> already priced in dollars. Tools that call a model themselves, like the oracle, report theirs as <code>details.modelUsage</code>. Add them up and you have the cost of a run.</p></li><li><p><strong>Noise:</strong> <code>message_update</code> fires for every streamed chunk. Most consumers only want <code>message_end</code>.</p></li></ul><p>A quick way to get just the final answer with <code>jq</code>:</p><pre><code class="language-bash">echo "What does this repo do?" | elyra --mode json \
  | jq -r 'select(.type == "agent_end")
           | [.messages[] | select(.role == "assistant")] | last
           | .content[] | select(.type == "text") | .text'
</code></pre><p>And the total cost of a run:</p><pre><code class="language-bash">elyra --mode json &lt; task.md \
  | jq -s '[.[] | select(.type == "message_end" and .message.role == "assistant")
            | .message.usage.cost.total] | add'
</code></pre><p>With that page in hand, the Félagi adapter was rewritten against the real stream, tested against recorded runs, and is now picking up work. The whole recording cost about a fifth of a cent.</p><h2>Fresh models</h2><p>As usual, the model registry has been refreshed:</p><ul><li><p><strong>New:</strong> Meta Muse Spark Open (open weights, $1.25/$4.25 per million tokens), NVIDIA Nemotron 3.5 Super VL (vision and reasoning, with a free tier on OpenRouter), Reflection Beam 1.0, Inception Mercury 3, a new DeepSeek V4.1 Flash snapshot, and FLUX 3 and Krea K3 image models on OpenRouter.</p></li><li><p><strong>Gone from OpenRouter:</strong> Claude Sonnet 4 and DeepSeek R1.</p></li><li><p><strong>Price moves on OpenRouter:</strong> GPT-5.6 Sol doubled; Kimi K3 got noticeably cheaper.</p></li></ul><p>Your default models are unchanged.</p><h2>Upgrade</h2><pre><code class="language-bash">elyra update
</code></pre><p>If you drive Elyra from scripts, CI, or your own tools, this is a release worth having: failures now look like failures, a stray session id can’t eat your prompt, and the stream you’re reading is finally documented the way it actually behaves.</p><p>It’s not a flashy release. It’s a dependable one, and when nobody’s watching, that matters more.</p><p>Happy building.</p><p>— The Elyra team</p>