IronCore v0.3.0
The frontier-parity release. A three-lens review measured IronCore against OpenAI
Codex CLI and xAI grok-build: the engine, safety kernel and envelope stack came out
frontier-grade (a dozen attempted jail/gate/injection bypasses all failed), but the
2026 platform layer was missing. This release closes it — skills (the SKILL.md
open standard), headless ironcore exec, AGENTS.md/CLAUDE.md compatibility, and a
web_search tool — and fixes the concrete bugs the review surfaced (/loop never
ran, gitignoring .ironcore/ silently killed undo, /goal verify: didn't arm the
engine, the task wasn't auto-pinned as durable state) plus a security finding (the
verify: command now goes through the policy gate). 1903 offline tests.
Added
- Headless exec —
ironcore exec "<prompt>"(PKG-5). IronCore is now
scriptable:execruns one turn against the real engine and renders its event
stream (ironcore/headless.py) with no TUI. In the default human mode the
model's streamed text goes to stdout and every other event (tool calls,
approvals, verify/repair status, the completion line) to stderr, so
ironcore exec "…" > answer.txtcaptures only the answer;--jsonemits one
serialized event per line to stdout for a machine consumer (thecore/events
dataclasses are an additive contract). Default--mode planis read-only and
CI-safe;--moderaises it. Approvals fail closed and invent no new
decision path: the engine's ownApprovalBrokeris built withtimeout=0,
so anyaskgate (there is no human to prompt) resolves through the broker's
existing timeout-DENY, with a one-line hint on stderr. Exit codes: 0 on
TurnCompleted, 1 onTurnError, 2 on aConfigErrorduring setup.
Stays import-light likedoctor/demo/init(the engine is lazy-imported in
the handler). web_searchtool (PKG-5). A second NET tool besidefetch_url
(ironcore/tools/search.py): it queries a configurable HTML search endpoint
([tools] search_url— a SearXNG instance or the DuckDuckGo HTML endpoint, the
default) and returns the top results as text (title · url · snippet). Results
are parsed with the stdlib HTML parser (linear, no regex backtracking on
adversarial markup), capped, and secret-redacted before they reach the
model or the transcript. It inherits the NET policy untouched — registered
only whensafety.network_toolsis true (and a non-emptysearch_urlis
set), and every call ASKS even in AUTO (NET is never auto-allowed).- Skills — the
SKILL.mdopen standard (PKG-4). IronCore now discovers, surfaces and
invokes skills: a<dir>/SKILL.mdfile (YAMLname/descriptionfrontmatter over a
Markdown instruction body), the same on-disk shape Claude Code, Codex and grok-build read
— so a skill authored for any of them works here unchanged. Discovery
(ironcore/skills.py, modeled onplugins.py) scans~/.ironcore/skills/(trusted) and
the workspace's.ironcore/skills/(clone-borne, gated);[skills] compat_dirs = true
additionally reads.claude/.codex/.grok/skillsdirs for zero-setup ecosystem
compatibility. A malformedSKILL.mdis skipped with a reason, never a crash.- Surfacing: a compact catalog (name + one-liner each) rides the SYSTEM share beside
project memory, charged viaestimate_tokensagainst the measuredhonest_context—
the envelope-native twist: on a tiny-context model it degrades to top-N (or nothing)
rather than silently eating the window. The composer budget invariant is provably
unchanged (a newskills_catalog=param, default()). - Invocation (both lazy-body per the standard):
/skilllists skills and/skill <name>injects one's body into the next turn; the model reads a skill via the new
READ-riskuse_skill(name=...)tool, riding the existing tool loop / transcript / audit. - Safety: user skills are trusted like
IRONCORE.md; a project skill is confirmed
once per workspace before first use (T8, the pattern/workflowuses) and never
reaches the model-facing catalog until approved. A skill body is display text — any
script it references runs through the EXEC-gated command tool, and noverify:directive
is ever parsed out of a skill (that path stays sourced from the projectIRONCORE.md
alone). Off switch:[skills] enabled = false. A copy-ready template ships at
examples/skills/hello-skill/; the authoring guide isdocs/SKILLS.md.
- Surfacing: a compact catalog (name + one-liner each) rides the SYSTEM share beside
- Instruction-file compat + user-global memory (PKG-3). When a workspace has
noIRONCORE.md, project memory now falls back to an existingAGENTS.md,
thenCLAUDE.md(first found wins) — so a repo cloned with a frontier
instruction file gets first-run value instead of being silently ignored
(ironically the IronCore repo itself ships anAGENTS.mdthe product used to
overlook). A user-global~/.ironcore/IRONCORE.mdis composed alongside
the project file — user-global first, then project — within the same
SYSTEM-share budget, each honestly truncated so a tiny-context model degrades
gracefully rather than dropping a source silently. A lone source stays
byte-identical to before (verbatim, no labels); only when both are present are
they joined under##provenance labels. Security: the fallback widens
display memory only. Theverify:directive is still sourced from the
projectIRONCORE.mdalone (core/verify.pyreads that one file
directly) — never fromAGENTS.md/CLAUDE.md/user-global — because a verify
command executes unattended after the first edit, so a cloned repo must not be
able to arm one. Zero-config; the engine call site is unchanged. - Auto-pinned objective (engine M1). On a session's first turn the goal is
seeded from the opening prompt (a normalized one-line copy) when/goaldid
not set one first — sostate.goalis durable and the standing-context anchor
always carries a real objective instead of "Goal: (none set)". The goal is
also re-presented as a compact one-line system message on the off-cadence
turns where the full anchor is not injected, so a compaction can never leave
the model without its objective ("re-present, don't rely on recall"). No model
call is added to the hot path; the goal line and the full anchor are mutually
exclusive and share the anchor budget, so the context budget invariant is
unchanged. /goal verify:now arms the engine's in-turn stop-condition. Attached
checks are mirrored onto the durablestate.goal_verify, and the engine's
verifier prioritizes them above IRONCORE.md / auto-detect — so a check attached
via/goalgenuinely holds the turn open ("won't call itself done until it
passes") even in a workspace with nopytest/npm/cargomarkers. Matches
what SPEC §5.5 already promised./help <command>prints that command's usage. The per-command syntax
strings (/goal verify:,/workflow run,/loop 5m,/model <name>) were
registered but unreachable from inside the product —/helponly ever showed
the one-line summaries./help <name>now prints the named command's usage +
summary (with a nearest-match hint on a miss); a bare/helpstill lists the
whole index and key reference.
Fixed
/loopactually runs now. The command parsed intervals and registered a
loop, but the app implemented neitherregister_loopnorstop_loop, so
every registration fell through to "stored; runs when the session drives it"
and nothing ever executed. The TUI now drives a real loop: a registered
loop re-submits its prompt as a genuine turn on its interval (self-paced loops
re-submit when the prior tick completes), a tick never fires while a turn is
running, and/loop stopcancels the driver. Ticks ride the ordinary turn
path, so they are gated, rendered, and session-recorded like any other turn.- Gitignoring
.ironcore/no longer silently kills undo/redo. The natural
way to quiet the?? .ironcore/line ingit statusis to add.ironcore/
to.gitignore— which, on git ≥2.50, made everysnapshot()exit 1 ("paths
ignored by .gitignore … Use -f") because the shadow-index add named an ignored
path explicitly, so/undoand/redoquietly degraded to "[snapshot
skipped]". The snapshot store now detects an already-ignored.ironcoreand
drops the redundant exclude pathspec (letting.gitignoredo the excluding),
so undo/redo keep working byte-exactly. It does not use--force, which
would have started capturing the user's other ignored files. read_imagefailure reasons are visible to the model. Only the no-vision
refusal was mirrored intoToolResult.output; unsupported-format, missing-
file, too-big and missing-pathreasons lived only in.error(UI-facing),
so the model received an empty failed result and blind-retried the same
doomed call. Every failure branch now carries its actionable reason in
outputas well, so the model can self-correct.
Security
- Verify commands are gated through the deny-list before they run. A
verify:line in a cloned repo's IRONCORE.md is repo-borne, unsandboxed
execution that fires automatically after the first edit in accept-edits/auto.
Every verify command now passesclassify_commandfirst: a deny-listed
command (rm -rf /,curl | sh, …) is refused and never executed, in every
mode; a risky-pattern command (git push,sudo, …) is skipped with a note
rather than run unattended in the autonomous modes. Either way the turn fails
closed — an unverifiable turn is never reported as done (SAFETY T7).