Skip to content

IronCore v0.3.0

Choose a tag to compare

@github-actions github-actions released this 19 Jul 21:57
· 10 commits to main since this release

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: exec runs 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.txt captures only the answer; --json emits one
    serialized event per line to stdout for a machine consumer (the core/events
    dataclasses are an additive contract). Default --mode plan is read-only and
    CI-safe; --mode raises it. Approvals fail closed and invent no new
    decision path
    : the engine's own ApprovalBroker is built with timeout=0,
    so any ask gate (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 on TurnError, 2 on a ConfigError during setup.
    Stays import-light like doctor/demo/init (the engine is lazy-imported in
    the handler).
  • web_search tool (PKG-5). A second NET tool beside fetch_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 when safety.network_tools is true (and a non-empty search_url is
    set), and every call ASKS even in AUTO (NET is never auto-allowed).
  • Skills — the SKILL.md open standard (PKG-4). IronCore now discovers, surfaces and
    invokes skills: a <dir>/SKILL.md file (YAML name/description frontmatter 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 on plugins.py) scans ~/.ironcore/skills/ (trusted) and
    the workspace's .ironcore/skills/ (clone-borne, gated); [skills] compat_dirs = true
    additionally reads .claude/.codex/.grok /skills dirs for zero-setup ecosystem
    compatibility. A malformed SKILL.md is skipped with a reason, never a crash.
    • Surfacing: a compact catalog (name + one-liner each) rides the SYSTEM share beside
      project memory, charged via estimate_tokens against the measured honest_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 new skills_catalog= param, default ()).
    • Invocation (both lazy-body per the standard): /skill lists skills and /skill <name> injects one's body into the next turn; the model reads a skill via the new
      READ-risk use_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 /workflow uses) 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 no verify: directive
      is ever parsed out of a skill (that path stays sourced from the project IRONCORE.md
      alone). Off switch: [skills] enabled = false. A copy-ready template ships at
      examples/skills/hello-skill/; the authoring guide is docs/SKILLS.md.
  • Instruction-file compat + user-global memory (PKG-3). When a workspace has
    no IRONCORE.md, project memory now falls back to an existing AGENTS.md,
    then CLAUDE.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 an AGENTS.md the product used to
    overlook). A user-global ~/.ironcore/IRONCORE.md is 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. The verify: directive is still sourced from the
    project IRONCORE.md alone (core/verify.py reads that one file
    directly) — never from AGENTS.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 /goal did
    not set one first — so state.goal is 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 durable state.goal_verify, and the engine's
    verifier prioritizes them above IRONCORE.md / auto-detect — so a check attached
    via /goal genuinely holds the turn open ("won't call itself done until it
    passes") even in a workspace with no pytest/npm/cargo markers. 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 — /help only 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 /help still lists the
    whole index and key reference.

Fixed

  • /loop actually runs now. The command parsed intervals and registered a
    loop, but the app implemented neither register_loop nor stop_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 stop cancels 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 in git status is to add .ironcore/
    to .gitignore — which, on git ≥2.50, made every snapshot() exit 1 ("paths
    ignored by .gitignore … Use -f") because the shadow-index add named an ignored
    path explicitly, so /undo and /redo quietly degraded to "[snapshot
    skipped]". The snapshot store now detects an already-ignored .ironcore and
    drops the redundant exclude pathspec (letting .gitignore do 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_image failure reasons are visible to the model. Only the no-vision
    refusal was mirrored into ToolResult.output; unsupported-format, missing-
    file, too-big and missing-path reasons 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
    output as 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 passes classify_command first: 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).