Elyra · · 11 min read

Elyra Desktop 0.4.1 and 0.4.2: the two halves of trust

0.4.1 is about what the agent may not do: run a stranger's code, read your keys, upload your secrets. 0.4.2 is about what it's held to: a measured system prompt, and a turn that isn't done until the code compiles.

Elyra Desktop 0.4.1 and 0.4.2: the two halves of trust

Two releases in two days, and they belong together, so this is one article.

0.4.1 is about what the agent may not do — open a stranger's code and run it, read your keys, upload your secrets, retry the same failing edit until the turn ends. 0.4.2 is about what the agent is told and what it's held to — a prompt that says what the harness knows, and a turn that isn't finished until the code compiles.

Put them side by side and they're the same idea from two directions. Trust in a coding agent has two halves: you have to know it won't hurt you, and you have to know when it's actually done. Neither is worth much without the other.

Half one: what it may not do

Opening a folder used to run its code

Here's a thing that was true until 0.4.1, and it's uncomfortable to write down. A repository can carry .elyra/extensions/ — TypeScript with full Node access — and a .elyra/settings.json that sets the shell binary, a prefix prepended to every bash command, the npm argv, extra extension paths. Cloning a repository and opening it in Elyra was enough to run all of it.

That's not a hypothetical. It's how a "helpful" repository would install something on the machine of everyone who reviewed it.

Now those four settings are ignored, and no extension inside a project loads, until you trust the folder:

Opening ~/code/some-fork
  This project carries 2 extensions and a settings.json that sets
  shellPrefix and npmArgv. Nothing from it has been loaded.

[ Trust this folder ] [ Keep it restricted ]

It asks once, only when a folder actually carries something. /trust and /trust off change your mind later; elyra trust list shows every decision; elyra doctor preflight says what's being withheld right now. Reading files, AGENTS.md, skills, prompts and themes are unaffected — those are data, not code. And npm install for extension packages runs with --ignore-scripts, because a postinstall is code from the package author that used to run the moment you installed.

Commands the model runs no longer see your keys

The whole process environment used to be handed to every command the model ran. So a single env — which models run constantly, because it's a cheap way to learn about a machine — put ANTHROPIC_API_KEY, GITHUB_TOKEN, AWS_SECRET_ACCESS_KEY and your database URL into the transcript, into the session file on disk, and into the searchable archive.

Now variables whose names read as secrets are withheld from tool commands and from the extension host. Everything a build needs passes through — PATH, HOME, CARGO_HOME, and SSH_AUTH_SOCK, so git push keeps working. And the bash tool tells the model it can't see them:

$ env | grep -i key
(no output)

Note: variables that look like credentials are withheld from tool commands. Ask the user if one is needed.

That last line matters. Without it, the model concludes the key isn't set and starts "fixing" your config. Your own !command is untouched; shellEnvAllow/shellEnvDeny adjust the list; shellEnvPolicy: "inherit" restores the old behaviour if you really want it. The read, write and edit tools also refuse the canonical credential files — /.ssh/id_*, /.aws/credentials, .netrc, .npmrc, .git-credentials, Docker and Kubernetes configs. Bash can still reach them, so this removes the accident rather than the ability.

/share asks first, and takes the secrets out

/share used to export the session and create a gist immediately, with whatever the tools had printed — which, see above, could be your .env. Now the export is scanned, anything unmistakably a credential is replaced with a label, and you approve with the facts in front of you:

Share this session as a private gist?
38 entries, 214 KB
Removed: 2 bearer tokens, 1 connection string, 1 AWS secret
A private gist is readable by anyone with the link. The unredacted
session stays on this machine.

[ Upload ] [ Cancel ]

The dialog says, in as many words, that the redaction is a net with holes and not a guarantee. If you shared a session before 0.4.1, go and read that gist. We mean it.

One question before the commands that do damage

A short list gets confirmed rather than just run: a recursive force-delete that leaves the project, sudo, a download piped into a shell, git push --force, git reset --hard, git clean -f, history rewrites, chmod 777, publishing a release, anything that changes what the machine runs at boot, appends to your shell rc.

The agent wants to run:
git push --force origin main
Because: "the rebase rewrote history and the remote refuses a normal push"

[ Run it ] [ No ]

A no goes back to the model as a reason, so it can propose something narrower — --force-with-lease, say. Ordinary work is never interrupted. And a command that arrives with no timeout is killed after ten minutes, so a hung process can't hold a turn open forever.

The agent can see who read the code, not just who wrote it

0.3 gave every agent-written line a path back to its prompt. 0.4.1 turns that into tools the model uses on its own: why(path, line) answers with the instruction behind a line, the model that wrote it, and what it cost; history(path) lists every turn that touched a file. Both are in the default tool set, so the model can check whether odd-looking code was deliberate before rewriting it — and in practice it does.

And there's a new trailer, Elyra-Reviewed. Marking a commit reviewed writes an empty commit with one trailer per reviewed commit and your git identity as the reviewer. In the file viewer, every agent-written line nobody has reviewed carries a dot in the gutter; the header shows the share of agent lines with a recorded review. An LLM /review does not count. Only a person clicking the button does. The record lives in the repository and travels with a clone, and it makes a question answerable that wasn't before: of the code the agent wrote in this repository, how much has a human actually looked at?

Half two: what it's told, and what it's held to

A prompt that says what the harness knows

The system prompt, until 0.4.2, was the reference implementation's: a role sentence, the tool list, and bullets ending in "be concise". Everything else the model had to work out from experience — and it did, expensively, every session.

Elyra's own prompt now says the things the harness already knows. Reads are cheap in parallel; issue them together. A failed edit usually means the file changed after you read it, not that your text was wrong; read again. This project has a type checker and a test command; here they are. why and history exist; ask them instead of guessing. A failing test is reported as failing, not summarised as "some tests need attention".

Four short sections, plus notes that appear only when a given tool is active. And here's the part we're proudest of: it's measured, not argued about. Every prompt carries a version — elyra-1 — stamped on every turn in the routing ledger. systemPromptStyle: "reference" builds the old text byte for byte. So after a week on each:

/routing-stats --by prompt
prompt       turns   success   edit fails/10   retries/10   $/turn
reference      412     81%          2.4           1.9      0.041
elyra-1        388     89%          1.1           0.8      0.033

A prompt change is an experiment with a result, not a matter of taste.

The turn is checked before it's called done

This is the one we'd point to if you asked what 0.4.2 is for.

A turn ended when the model decided it was finished. That's not the same as the code compiling, and everyone who has used a coding agent knows the moment: "Done! I've added the helper and updated the callers" — and cargo check says otherwise. Until now, the only verification loop was /goal, and only for a command you typed yourself.

Now, after any turn that actually wrote files, Elyra runs the check the project already has — no configuration, it looks:

  • Cargo.tomlcargo check

  • package.json with a check or typecheck script → that script, with the right package manager

  • composer.json with phpstan or psalm in vendor/bin → that

  • go.modgo vet

  • pyproject.toml configuring mypy or ruff → those

A failure goes back to the model as work to finish, with the tail of the output:

⟳ verify: cargo check
error[E0425]: cannot find value fmt_cost in this scope
--> src/footer.rs:41:20
...
→ handed back to the model (attempt 1 of 1)

I referenced fmt_cost before defining it — the helper went into format.rs but footer.rs never imported it. Adding the import.

⟳ verify: cargo check ✓ clean

Deliberate limits. It runs once per instruction (verify.maxAttempts), so a model that can't fix it says so instead of looping. It never runs for a turn that only read code or wrote Markdown. It never runs a formatter, and never a test suite — unless you name one in verify.command, because tests are yours to choose and can be slow. verify.enabled: false turns it off.

The same failure twice now says so

Related, smaller, and it fixes the single most irritating thing a coding agent does. A failed tool result went back to the model exactly as the tool wrote it. So "the text to replace was not found" arrived for the third time with nothing to say it was the third time — and the usual outcome was the same edit, with the same text, retried until the turn gave up.

Failures are now counted per call — which tool, what it was about, and a fingerprint of the error that ignores line numbers and byte counts so a repeat is recognised without lumping in a genuinely different error. The second identical failure carries one line:

edit src/footer.rs failed again (2×): the text to replace was not found.
This is the same error as last time. Read the file again before retrying —
it may have changed since you last saw it.

The third says to stop retrying and either solve it another way or say what's blocking. Nothing is blocked and no arguments are rewritten. It's information the model was missing, not a new rule — and it turns out that's all it needed.

Old tool output stops being paid for

A 50 KB file the model read on turn 3 used to be sent again, in full, on turn 40 — and 41, and 42 — until compaction eventually rewrote the whole conversation. Tool results older than ten turns are now sent as one line:

[read src/lib.rs — 1,412 lines, output pruned; run again if needed]

Nothing is lost: the session file, the transcript and the archive keep every byte, and the context inspector marks those blocks pruned and shows what you're saving. One design decision worth explaining: the boundary moves in steps of ten turns, not every turn. Rewriting a message invalidates the provider's prompt cache from that point forward, and a cache read costs a tenth of fresh input. Sliding the boundary every turn would pay a cache miss every turn. Moving it every ten buys ten cheap turns for one miss. The arithmetic is why it's shaped that way.

Thinking per turn, and a router that reads its own ledger

Extended thinking was one switch for the whole session, so "what does this flag do?" paid for the same reasoning as "refactor the payment module". It's now scaled within the level you set: a powerful turn gets all of it, an ordinary one a step less, a short question answered with read-only tools none. Your level is a ceiling that's never exceeded; off stays off; /route shows what the next turn would spend.

And the routing ledger Elyra has been writing after every turn — stop reasons, failed edits, retries, failovers — finally decides something. Within a tier, models are ranked by what those turns measured, before price is considered. A cheap model that fails a third of its edits stops being the default pick, and the reason says so:

/route
tier: balanced → claude-sonnet-5
passed over: gpt-5-mini (edit failure 34% over 61 turns; sonnet 9%)

Five measured turns minimum before the ledger overrides price. Your pins always win.

Why both, and why now

We could have shipped the safety release alone. It would have been a good release. But an agent you've made safe and haven't made reliable is one you'll still babysit — you'll read every "Done!" with suspicion, because you've learned to. And an agent you've made reliable but not safe is one you'll be right not to leave alone.

0.4.1 means you can open a repository you didn't write, in a shell with your keys in it, and not be hurt. 0.4.2 means when the agent says it's finished, the check has already run. Together they're the first version of Elyra Desktop we'd describe with a word we've avoided until now: trustworthy. Not perfect — the redaction is a net with holes, verify runs once and gives up, the router needs five turns of evidence. But honest about each of those, in the interface, where you'll see it.

Get it

Elyra Desktop 0.4.2 is at elyracode.com/elyra — the Desktop tab. macOS on Apple silicon; Linux on x86_64 and ARM64; free to use, every feature. Already installed? /update. If you're on 0.4.0 or earlier and ever ran /share: upgrade, then read the gist.

The desktop guide has both halves — Trust, secrets and the commands that do damage, and What the model is told, and when a turn counts as done.

Open a stranger's repository. Ask the agent to fix something. Watch the check run before it tells you it's done.