Skip to content

Releases: vstaln/gray

v0.1.9

Choose a tag to compare

@github-actions github-actions released this 30 Sep 07:32

Fixed

  • The REPL composer stays on the last rows of the screen. Once the transcript overflows the
    viewport, a latched bottom_anchored keeps the input box and the footer pinned to the screen's
    last rows instead of parking them above cleared rows; a shrink (status dock, live cards clearing at
    a tool result, end of turn) slides the viewport down and repaints what it vacated with the surface
    colour. Band budgeting makes the text area and the footer un-trimmable and sheds the most transient
    band first, so a busy screen loses the status dock before the transcript.

Changelog

v0.1.8

Choose a tag to compare

@github-actions github-actions released this 30 Sep 07:24

Fixed

  • Heredocs survive the cwd-report suffix. bash appended
    ; __gray_rc=$?; printf ... to the command text, so a command whose last
    line was a heredoc terminator read EOF; __gray_rc=$? and the terminator
    never matched: the whole suffix landed inside the heredoc body — a shell file
    written with a garbage trailer, or a SyntaxError for an interpreter
    heredoc, silently (rc=0). Each piece of the suffix now sits on its own
    line, which also stops a trailing # comment from eating it and makes
    cmd & legal. 33 of 47 DeepSWE runs in the 2026-09-29 retro reported this;
    it cost each a wasted turn at best.

    • The windows-runtime CI gate stops hanging on the search-index bench.
      Every ci run since the search-as-command merge (#145) died at
      index_vs_spawn_tax — "running for over 60 seconds", then silence until
      the job's 60-minute budget was spent. The hang had no reachable timeout:
      the bench's deadline assert lived inside its own poll loop, but the call it
      polled (SearchPool::warm_picker) never returned — it took the pool's
      resident mutex with a blocking lock(), and index construction held that
      same mutex across everything fff does, which on Windows stalled inside
      unbounded dependency waits (LMDB writer lock, git status, watcher init).
      The probe is now non-blocking (try_lock with a ~50ms budget, then "not
      warm" → fd/rg answers), construction runs with no pool lock held and is
      deduped by root, and every bench phase carries a hard timeout that fails
      naming the phase. A wedged build now costs one search its fallback lane —
      and costs CI a two-minute failure with a name on it — instead of a 60-minute
      silent hang. windows-focused takes a test-path input, so a native
      single-test iteration no longer means editing the workflow.

v0.1.7

Choose a tag to compare

@github-actions github-actions released this 29 Sep 14:40
9e4d924

Added

  • The composer keeps its place, and its footer stops flickering. A latched
    viewport floor (latched_viewport_floor) grows the frame by exactly what
    the pre-computed dock estimate short-cuts mid-stream, and the footer gauge
    paints on the frame's last row (footer_paint_row) instead of being pushed
    past the bottom and skipped for that frame — Ω 223.8k/300k and the pad band
    under it now ride out a mid-turn text wrap. Shrinking the viewport keeps its
    top edge fixed, so no blank scrollback is painted behind the box.

  • Bare Enter continues. While the last turn is resumable, the empty input box
    shows a dim Please continue… ghost hint and submitting empty text continues
    the conversation; a box with text (or an attachment) still submits it.

  • Streaming rows hold orphan punctuation. A lone ./,/? arriving as its own
    delta is held until the next chunk says whether it belongs to it, then
    released glued to what follows — no more a frozen lone-. transcript row at
    an interrupt.

Fixed

  • ~/.gray/provider_models.json no longer accepts loopback base URLs. A local
    server's port changes on every start (and a unit test's is random) and
    nothing evicted old keys, so every one was a permanent dead entry — 43 had
    piled up. Remote providers still persist, so /model still paints its cached
    list instantly.

  • The provider model-list fetch no longer rides the ambient Tokio runtime: a
    background refresh that outlives REPL shutdown used to kill its thread with
    Tokio 1.x context ... being shutdown. It runs in its own short-lived
    current-thread runtime.

  • Binary detection in the read guard no longer magic-sniffs the first bytes —
    the NUL check decides — and a regular-file-to-FIFO swap between the guard and
    the open is now caught by an O_NONBLOCK open instead of hanging the turn.

Changed

  • Self-update hashing uses the sha2 crate that is already a dependency
    instead of shelling out to sha256sum/shasum and writing the tarball to a
    temp file.

  • gray-markdown drops the incremental open-block cache (its measured cost was
    maintenance, not reads) and the LaTeX-to-unicode stack — raw TeX shows as
    typed. Unicode repair uses a minimal Latin accent table instead of the
    unicode-normalization crate, so the dep count holds.

  • The shell lane is one type carrying the live child, its process-group guard,
    the output pump and the liveness clock, instead of four parallel ones.

Chore

  • The internal working journal (superpowers plans, study notes, session
    records) is untracked from the public repo; reference and product docs stay
    tracked.

v0.1.6

Choose a tag to compare

@github-actions github-actions released this 28 Sep 14:42
e1f3322

Added

  • /update and /restart in the REPL, the two halves of landing a
    self-update. /update checks the channel, asks before installing, and says
    what to do next; /restart puts the running gray on the binary that is on
    disk — the gateway daemon first (it keeps running the build it started
    with), then this session re-exec'd into the new one, resuming the
    conversation with resume --last. Neither codex nor hermes has either: codex
    shows an update popup at startup and hands you a brew upgrade line, and
    hermes-rs's restart tears down LSP clients. A CLI that installs its own
    updates owes you a way to land on them.

    The gateway half distinguishes who owns the process. Installed under a
    supervisor, the supervisor restarts it. Running with nobody supervising it
    — a hand-launched gray gateway run — it is stopped and started again on
    the new build, because stopping without the relaunch would take a working
    gateway down and call it a restart.

  • Foreign plugin packages work with zero per-plugin code: gray plugin install <git-url> now also takes a package's commands/*.md and
    .opencode/command/*.md prompt files, any installed package's AGENTS.md
    is served into every turn, and its command files become slash commands
    that run as prompts (ponytail's ruleset + six commands included). An
    optional package gray.json declares a state file so /x mode-style
    switches persist. Package code is still never executed: hooks, MCP
    servers, and lifecycle scripts stay out of scope.

Changed

  • The gray mark animates. assets/logo-animated.svg grows out of its own
    centre: the gem blooms first, then each of the 25 facet lines draws
    outward from the vertex nearest the centre, staggered by distance so the
    core lands before the outline. The source path — one path, nonzero fill
    rule, facet lines as windings that cancel — is reused verbatim as the mask
    the lines grow inside, so a line can only ever paint pixels the finished
    logo already has as line: the last frame is the logo, verified pixel for
    pixel. Pure CSS with no JS, it runs inside <img>, and it degrades to the
    finished mark when animation is unsupported or prefers-reduced-motion is
    set. scripts/animate_logo.py regenerates it (--lines for the
    transparent variant on dark surfaces, --frames N dir for previews);
    assets/logo-grow.mp4 is the 1.9s preview.

  • The startup banner is the gray ASCII logo again. The graychan art is
    /hehe only, and /hehe is a toggle: press it again to drop the art and
    get the logo back.

  • The /model picker no longer waits on the network every open: the last
    fetched provider list is cached on disk and paints instantly, the live
    fetch refreshes it in the background, and a ─ recent ─ divider separates
    your recent models from the full list.

  • bash takes wait_ms on action:output and action:status, so a single
    call can await a job instead of polling it once per turn. Capped at 600s,
    and every wait comes back with a liveness verdict: the log either grew
    while we waited (still producing) or stayed silent (possibly stuck — the
    agent's cue to inspect or cancel it).

Fixed

  • A command that goes silent no longer blocks its tool call forever. The
    blocking bash lane waited on child.wait() with no bound and reported
    nothing while it waited, so an external process that wedged at shutdown —
    a Playwright browser.close() race, reproduced in
    docs/shell-hang-postmortem-2026-09-28.md — held one call for 9m44s until
    the user cancelled, and the evidence that would have explained it (the
    job's log, finished at spawn time) was invisible until the call returned.
    A command that sets no timeout and emits no new output for 600s is now
    handed to a background job and the call returns immediately: still running · job <id> · silent: no new output for 600s · log <path>, not
    killed, with the agent's cue to inspect, await or cancel it. A chatty
    command is never touched — every output chunk resets the bound — and the
    reverted 30s/120s defaults stay reverted: this reports, it does not kill,
    because a long build and a wedged process have to stay distinguishable.
  • The band between the last transcript row and the input box stops showing
    a stripe of the terminal's default background. Plugin-widget rows, the
    queued-follow-up preview and the ask modal were rendered as bare
    Paragraphs — transparent — so on a terminal whose default background
    differs from the theme they painted as a foreign block through the middle
    of the composer whenever a turn streamed (worst with a widget installed:
    its rows appear only while it has something to say). Those rows now carry
    the composer's own background like the live tool cards and the input box.
  • The REPL composer holds its place. The inline viewport only re-anchored
    when its rows overran the bottom of the screen, so every viewport resize
    walked the input box and footer with it: a tool call grew them (status
    dock + live tool card) and the tool result shrank them again, leaving the
    footer parked above dead terminal rows while the turn kept streaming.
    Once the transcript has filled the screen the composer's fixed rows now
    stay on the screen's last rows, the dock/card rows above them scroll
    instead, and a shrink repaints the rows it vacated with the composer's
    own background. A transcript shorter than the screen still hugs the
    conversation, as before.
  • The REPL footer's right segment stops walking across the bar mid-stream.
    Whether the reasoning-effort badge paints depends on a reasoning flag that
    background discovery keeps writing after startup (the provider's /models
    at repl/mod.rs, then models.dev), so on models whose two answers disagree
    — StepFun's own gateway reports step-5-preview as non-reasoning while
    models.dev marks it reasoning — the badge appeared and vanished a frame or
    two into a turn, shifting Step 5 Preview · xhigh eight columns. The
    visibility is now snapshotted once at the beginning of each turn, so the
    footer holds its shape while anything streams; the provider's converged
    answer still lands between turns.
  • A 503 burst outlasting the provider's own 5-attempt budget no longer kills
    the turn the user is waiting on: the agent loop retries the whole request
    (up to 2 more times, short ramp, nothing streamed yet — a visible delta
    still ends the turn instead of replaying text you already read).
  • 401 responses whose body says the model isn't supported (OpenRouter's
    ModelError shape) classify as a bad request, not an auth failure — no
    more "check API key" advice for a model problem.
  • Empty tool-call padding deltas (gateways that append {index}-only
    entries to the final chunk) no longer materialize ghost fragments, so the
    dropping tool call index N with empty name warning spam is gone.
  • gray view inside a compound shell command no longer reads as success with
    nothing attached: the bash tool appends a note that the image was NOT
    attached and how to re-run it bare, and the CLI fallback line says the
    terminal has no image protocol instead of a bare viewed …
    (docs/bug-gray-view-compound-command.md).

v0.1.5

Choose a tag to compare

@github-actions github-actions released this 27 Sep 03:55
bf76ca7

Changed

  • The search index is a command, not a lane inside the find and grep
    tools. 0.1.4 left those two tools answering differently depending on the
    directory, the pattern and a decade of glob semantics — and a missed rule
    there is a wrong answer, not a slow one. find and grep are fd/rg
    again, unconditionally, and gray find PATTERN [PATH] / gray grep PATTERN [PATH] own the index: the same answer, served warm, with the fallback lane
    being the very tool the command stands in for. Both are claimed by bash like
    gray view, so a model reaches them without gray on its PATH.

    The policy is cost-first, which the tool placement had hidden: the index
    lives in process memory, so a fresh process holding none paid a full scan —
    1.4s against fd's 20ms on a 20k-file repo, a "speedup" 70x slower than the
    shell it replaced. Now the first search in a process goes to fd/rg and
    starts the index in the background; every search after that is index-served.
    Cold gray find on that repo: 21ms. What the index is actually worth is
    grep on repeat searches (2.9x the tool lane); find is a wash.

  • cat is no longer a second way to see a picture. It returned a
    full-resolution vision block while gray view capped at the shared 2000px,
    so the model had two paths with two answers and picked by habit. gray view
    is now the only one: cat <media> says so in one line instead of streaming
    binary into the context. Same text, same tool, one fewer thing to learn.

Fixed

  • A search cancelled before it starts now answers cancelled by user
    instead of racing the spawned fd/rg child's first line, which could
    return a finished result for a call the caller had already given up on.

v0.1.4

Choose a tag to compare

@github-actions github-actions released this 26 Sep 07:53
3305c28

Added

  • find and grep answer from a resident file index instead of spawning
    fd/rg per call. A watcher-backed fff-search index is built lazily per
    search root, ranks hits by frecency, and serves the next search out of warm
    memory: measured on this repo, find 9.7ms against fd's 17.8ms and grep
    17ms against rg's 95ms, with identical result sets. The tool surface does
    not change — anything the index cannot answer exactly (a non-git root,
    ignoreCase, a negated or depth-anchored glob, an invalid pattern, a file
    target) falls straight through to the old lanes, and a cancel still stops at
    once instead of waiting out a cold scan. Design and decline rules:
    docs/fff-search-index.md.

  • gray view PATH... shows an image file as an image (downscaled to the 2000px
    cap, the one shared with the read tool and pasted attachments). The view
    tool that 9ae15d3 deleted comes back as a command: bash's one vision path
    now claims gray view <path>... before the shell runs, alongside
    cat <path> (full resolution) — so an agent checking a rendered chart,
    screenshot or diagram gets an image instead of pixel soup, with no new tool
    in its toolset. Multi-path by design; a leading ~ is expanded, since
    nothing else would do it before the shell runs.

  • gray view movie.mp4 shows a contact sheet of sampled frames, so an agent
    can see a recording without shipping the whole file into the context;
    --frames N sets how many. Gemini and Gemma models get the native video
    instead of the sheet, and an oversized file says so rather than silently
    downscaling past the cap.

  • /gateway is the connections picker: every installed app, its own status,
    and Enter on a needs-setup row runs that app's setup — the channel picker
    covers DMs, setup is token-first, the configs an app writes land in the
    user's home rather than gray's, and gray gateway setup <app> does the same
    headlessly for scripts.

  • gray --json progress narrates a turn: tool_started / tool_ran /
    tool_finished rows carrying disclosed, bounded, redacted output plus the
    internal call id, and thinking phase rows. A front end can render live
    tool activity and a separate persistent tool card without parsing prose.

  • Memory carries a turn: a one-sentence profile summary is injected with it,
    the prompt asks the model to show a memory's KEY before leaning on it, and
    the daily ingest is mechanical and bounded — append-only edits, daily caps,
    and no verbatim duplicates under a new key.

  • Provider plugins join the plugin protocol at 1.2: host-owned credential
    refs, sidecar RPCs, cache/runtime roles, /connect plugin login, and an
    OpenAI dynamic provider profile. Codex/ChatGPT OAuth ships as a first-party
    optional plugin (plugins/codex-auth), so /connect discovers auth
    providers only when one is actually installed and enabled.

Fixed

  • An interrupted sidecar or provider stream no longer turns a usable partial
    answer into an error. A provider stream that ends without its completion
    marker is finished with a capped, nonfatal interruption notice rather than a
    CoreError, because the visible delta is already committed to history and
    replaying it would duplicate text the user read. On Windows a blocked pipe
    write is bounded by a worker task instead of stalling the runtime thread, so
    cleanup can actually terminate the child; child termination after a write
    timeout is bounded, and an already-exited child id no longer wedges cleanup.

  • The default prompt points the agent at gray view, not cat, for images —
    cat still returns bytes, but the agent is told which one to reach for.

  • Prompt caching drops two pieces of pi-parity over-engineering
    (cache_control masking and x-session-id), and /resume previews the
    latest message of a session rather than its opener.

  • A 23-finding source audit landed with its sweep: drive-named archive entries
    refused on every platform, symlink-cycle and atomic-pid-claim guards,
    serialized config read-modify-write, a log-rotation guard that acquires
    before it opens, and the remaining secret-redaction gaps in tool output.
    Dispositions: docs/audit-fixes-2026-09-22.md.

v0.1.3

Choose a tag to compare

@github-actions github-actions released this 22 Sep 09:09

Added

  • Onboarding points at one place: a run with no model configured, and /model
    with nothing set, both say run /connect to set up your provider & key (with
    /model provider/id and /help as the alternates) instead of naming
    /provider, which /help never listed.

  • Official plugins now ship in this monorepo under plugins/ rather than a
    separate grayplugins repo, so gray plugin install <name> can never point
    at a repo that does not exist. Each directory is one sidecar — a single
    plugin.sh speaking protocol v1 NDJSON over stdio, with echo/ as the
    reference implementation — and plugins/index.json is the catalog
    gray plugin install reads. Push tag plugins-v<version> (which must equal
    every manifest's version) to run plugins-release.yml: it syntax-checks
    each sidecar, tarballs the directories, and publishes a plugins-v<version>
    release with SHA256SUMS-plugins. discord/, background/ and
    permissions/ are scaffolds until the real bridge/runner/gate logic is
    ported in — each file's TODO says where.

Fixed

  • Compaction pinned the stable anchor by value: the retained tail was
    filtered with retain(|m| m != &anchor), which deleted every message
    equal to the original intent — including a later turn that legitimately
    repeated the prompt — and left the request ending on an assistant message
    instead of a user turn. The walk now starts past candidate[0], the
    anchor's own position, so only that one copy is excluded. As a side effect
    the anchor's tokens are no longer charged to both the pinned segment and
    the tail (arXiv:2512.22087).

  • The context-overflow path compacted the unscrubbed history: a salvaged
    partial that the CCRM scrub (arXiv:2605.08563) had flagged rode the
    compaction trigger in full, so the failed trajectory was baked into the
    summary and reached every later request — exactly what the scrub exists to
    prevent. The scrub is now one view (Agent::scrubbed_messages) shared by
    the outbound request and the compaction input, so both carry the one-line
    marker. The persisted transcript still keeps the full text the user saw.

v0.1.2

Choose a tag to compare

@github-actions github-actions released this 22 Sep 01:30

Added

  • /gateway (alias /gw) opens a connections panel: one toggleable row per
    installed app (the merged plugin registry, so a transport appears the day it
    is installed), a rule, then one-line pointers at daemon, cron and memory —
    gray gateway status, /cron and /memory already print everything about
    those, so the panel names the command instead of restating its output. An app
    row carries what it still needs (needs setup when its default config file
    is absent — existence only, never the file, which holds the token) and the
    commands its own manifest declares; nothing is invented on its behalf.
    space flips an app's enabled flag through the same registry path
    /plugin uses, /gateway on|off still flips the persisted gateway master
    switch, and piped stdin prints the rows as text. /gateway and /gw are
    real commands again (they previously answered "the TUI gateway is gone")

  • gray login, gray whoami, gray logout (and /login, /whoami,
    /logout in the REPL): enroll this machine with gray.alignment.id. The
    site's account page mints a one-time 5-minute code from a Supabase session;
    gray login exchanges it for a long-lived gray_... registry token stored
    in ~/.gray/registry-token.json (mode 0600, same atomic writer as
    auth.json). Bare gray login prints the walkthrough and prompts for the
    code; gray login <code> is the non-interactive form the site's copy
    command emits. gray logout revokes the token server-side before dropping
    it, and logging in again revokes the token it replaces so a re-login never
    leaves a working credential the machine has forgotten. All three run before
    provider configuration, so a fresh machine can enroll before it can run a
    turn. GRAY_REGISTRY_URL points at a local registry
    (pnpm backend:dev); cleartext http is accepted only on loopback, since
    every call carries the token. Nothing in gray is gated on an account — the token
    only names the caller on registry calls — and the onboarding banner now says
    so instead of implying a login exists

  • /cron and /memory are interactive on a TTY, riding the same picker loop
    as /plugin and /skills: /cron lists every job (name, id, schedule, next
    run, last status) plus the ticker's liveness row, and space pauses/resumes
    in place through CronStore::set_paused; adding and removing stay on the
    gray cron CLI. /memory lists every curated entry (key, scope, first
    line) read-only — forgetting stays gray memory remove <key>, because a
    picker must not make deletion a keystroke. Headless output is unchanged for
    both. The shared manager loop gained the axes these panels needed:
    per-row read_only (separators and pointers carry no switch), a
    supports_remove flag (listing panels leave removal to their command), and
    an errors_tab flag (package-install errors are noise on a cron listing)

Changed

  • Memory entries carry their latent reasoning. The policy now asks every saved
    entry to record the failure or correction that prompted it (quoted), whether
    it has recurred since, what was already tried and falsified, and the verbatim
    text of any entry it replaces — the keep/delete rule from arXiv 2608.11095,
    whose finding is that an instruction nobody can justify is an instruction
    nobody can safely delete, which is why prompt files only ever grow. A why
    without its outcome is worse than none. The rule itself: if an entry's failure
    has not recurred since the entry was added it is probably preventing that
    failure, so keep it; delete only when the failure kept recurring anyway or the
    entry duplicates another's target, and carry the removed entry's falsified
    attempts into its replacement
  • gray memory audit reports which entries lack a why, which duplicate
    another's target, and which record a falsified outcome, with the rule above
    printed beside them. It deletes nothing — the paper's own warning is that
    automating the deletion emptied one prompt in eight and lost satisfaction on
    exactly those — so the decision stays with a human
  • Repeated net growth with no removal (three consecutive saves) now prints a
    one-line warning pointing at the audit: unbounded growth is the disease, and
    it is visible in the entry count long before it is visible in behavior
  • AGENTS.md / CLAUDE.md may carry # r<n>: ... rationale comments for a
    rule. They stay in the file for whoever edits it and are stripped before the
    rules reach the model, so rationale never costs the executor tokens — and
    the served block says so, so an editor preserves them. Files without such
    comments render byte-identically to before
  • Windows installs natively by default. dist/install.ps1 no longer routes a
    bare invocation into WSL: with no arguments it installs gray.exe into
    %LOCALAPPDATA%\Programs\gray\bin, verifies the archive checksum, extracts
    only gray.exe / LICENSE / THIRD_PARTY_NOTICES.md from the ZIP, and
    updates the user PATH. -Wsl remains an explicit compatibility route that
    pipes install.sh into a distro, and a native failure never falls back to
    it. The "experimental" and "acceptance pending" wording is gone from the
    installers and docs, and the README platform table lists Windows as native
    x86_64. Still documented as unsupported, unchanged by this: gateway and cron
    execution, self-update, Unix-shebang plugins, and ACL hardening of
    credential files

v0.1.1

Choose a tag to compare

@github-actions github-actions released this 21 Sep 14:28
  • Windows builds ship with the release: gray-<channel>-x86_64-windows.zip
    alongside the four tarballs, checksummed into the same SHA256SUMS file.
    install-native.ps1 installs it without elevation, probes the binary with a
    bounded 10s timeout, and replaces a running gray.exe through a staged
    copy-plus-backup rather than an in-place write. It is exercised in CI on
    windows-2025 under both PowerShell 7 and Windows PowerShell 5.1. The public
    install.ps1 still defaults to installing inside WSL; native is opt-in via
    -Native

Added

  • Prompt-cache warmth timer + cache-miss warning in the composer. The
    footer carries a ◷ 4m countdown next to the cache-hit percentage —
    how long the last request's prompt cache stays warm before an idle gap
    re-bills the whole prompt (Anthropic's cache_control and OpenAI's
    automatic prefix caching both expire after ~5 idle minutes), fading in
    the last minute and hidden entirely when the provider never reports
    cache activity. When a request re-bills tokens the previous one should
    have served from cache — an idle gap past the TTL, a model switch, or a
    provider-side eviction — the transcript gets a warning row
    (⚠ Cache miss after 6m idle: 100k tokens re-billed (~$0.35)) once the
    miss crosses 20k tokens or $0.10 over a full hit, so routine breakpoint
    noise stays silent. Detection is a port of pi's
    packages/coding-agent/src/core/cache-stats.ts (reference checkout
    under reference/pi-mono) onto gray's per-round StepUsage reports;
    compaction and /new reset the baseline, since a fresh summary is new
    content rather than re-billed content

  • Remove a provider from the connect modal: shift+enter on a highlighted
    provider (the modal footer advertises it only for a row that holds a stored
    credential) opens a confirmation naming the provider and its endpoint, and
    enter deletes the credential — auth.json entry plus the active
    provider's second copy in config.json, so a removal cannot resurrect on
    the next start. /provider reloads the agent and reports the removal
    instead of announcing a connection

  • Gateway daemon (gray gateway ...): the always-on host that fires cron with
    no REPL open. run is the foreground daemon (60s cron ticker with the same
    HeadlessRunner/SaveLocalDeliver as tick/serve, plus a control socket
    at $GRAY_HOME/gateway.sock answering identify/status — one JSON line
    in, one out, hermes wire shape); install writes a user service (runit
    run+log/run on Void, systemd --user unit elsewhere, --print previews,
    --no-start defers), start/stop/restart drive it, uninstall removes
    it, status reports daemon + service + ticker health (exit 1 when down).
    Process shape follows the hermes gateway: O_EXCL pid claim with
    /proc start-time against PID reuse, gateway.state.json recording why the
    last run stopped, socket-first liveness with pid-file fallback, 0600 socket,
    supervisor detected from the real init (never inferred outside-in), and a
    bounded 65s drain of in-flight fires on SIGTERM/SIGINT. install/start
    wait up to 10s for runsvdir to pick up a fresh service dir (it rescans
    every ~5s) instead of racing it, and stop tells runit-stopped vs
    tick-draining apart (was a ⚠ still running either way).

  • Cron in-chat firing: background REPL tick, /cron dashboard, delivery seam.

  • Cron workstream B: gray cron tick|serve|pause|resume|run, job skills +
    pre-run scripts, local-file delivery (cron/output/<id>/<ts>.md).

  • Cron ticker liveness: every tick pass writes a heartbeat
    ($GRAY_HOME/cron/.last_tick), and gray cron list, gray cron add, and the
    /cron dashboard report it — a store nobody is ticking now says so instead of
    printing a next= that will never arrive.

  • Plugin system: gray-plugin crate with Plugin trait, builtin tools as profile-ordered plugins, gray.yml profile loader + sidecar entries, sidecar hook protocol over stdio with timeout/crash degradation

  • Gateway daemon: gray-gateway crate (Telegram/Discord/Slack), real Discord adapter with slash commands, OAuth2 invite URL, full /gateway REPL suite, delegation durability

  • Cron jobs: schedule/store/CLI + REPL, local wall-clock daily schedules, AI self-scheduling via schedule_task

  • Dynamic context window via models.dev + disk cache (LiteLLM table, proportional reserve/keep), /context visual modal with suffix completion and thousand-separator parsing

  • Usage/cost tracking: session cost from LiteLLM rates, footer + /usage, persisted session totals

  • Skills: skill tool loading SKILL.md bodies, /skills command, discovery across opencode plugins/agents/claude skills

  • Anthropic prompt caching always-on with cache hit-rate display; reasoning summaries on Responses API

  • request_user_input tool (question overlay) so the agent can ask the user mid-turn

  • Codex-style session resume (--last/--all, picker, transcript replay)

  • /effort thinking-level selector and /thinking toggle

  • Auto-compact on threshold/overflow; exploration-stall guard

  • Noir space marketing site (landing/docs/pricing, favicon/OG/robots/sitemap) deployed to gray.alignment.id

  • Multi-platform releases (darwin x86_64/aarch64, linux aarch64) with release channels (RELEASING.md)

  • Startup update check + gray update subcommand

  • Bulk hermes→Rust 1:1 port slices across core/cli/tui/tools/plugins/gateway/provider/sandbox/state/cron/acp

  • Effort picker filtering: online models.dev capability filter with offline static ARM fallback

  • Wire is_error flag, DeepSeek provider path, resume-replay, retention policy, and chat-shard routing

  • Windows cfg gates for platform-specific code paths

  • Resume messages for restored sessions

  • Shift+Enter newline handling in composer

  • Footer/badge/panel TUI chrome updates

  • Attach guards for file attachment paths

  • Glob tool for file pattern matching

  • Session-ID threading across turns

  • prompt_cache_key passthrough for chat requests

  • gray sessions prune --older-than-days N for session-store GC; persist_redacted: true gateway option to scrub secrets from persisted gateway transcripts

  • Verified installs: SHA256SUMS published per release, checked by install.sh (S1)

  • GRAY_NO_UPDATE_CHECK=1 and 24h update-check cache (L4)

  • Gateway autostart defaults off; corrupt gateway.yaml warns instead of silently resetting (S2, S3)

  • Safety / Subcommands / Platform / gateway docs in README (D2, S4)

  • The connect modal's footer and the install manager's per-tab footers share
    extracted same-file helpers instead of repeating the render scaffolding
    three times each (-188 net lines across the two files). Behavior is
    unchanged, including the connect modal's conditional shift+enter hint,
    which only appears for a row that actually holds a stored credential

Changed

  • gray plugin install discord compiles the plugin from its pinned commit
    (cargo build --release --locked) instead of pip-installing a Python
    package, so installing a first-party plugin needs no interpreter. The
    catalog is Rust-only; user-written plugins stay language-agnostic
    (GRAY_PLUGIN_PATH, plugin.sh). A source pin must be a full commit ID,
    and the built binary has to answer plugin/manifest with the expected
    name before it is registered

  • Clipboard/image paste is core again: arboard + image are always compiled in, no --features clipboard needed (kept as a no-op alias)

  • Removed the native messaging gateway: deleted crates/gray-gateway (adapters, daemon, pairing, delivery, systemd), the plugins/gateway sidecar, gray gateway .../gray send, and the telegram/discord/slack/all-platforms features. Chat returns as a plugin; gray cron --deliver targets are stored opaquely until a delivery backend exists. Dropped the --all-features CI checks.

  • Multi-line input is no longer clipped by the inline viewport. The viewport
    cap was pinned near 14 rows regardless of terminal height, so a pasted
    paragraph that wrapped to 12 content rows lost its last row and anything
    longer was cut hard. The cap is the terminal height now (minus the shell
    prompt row); the idle 14-row transcript is unchanged

  • The default tool surface stays bash-only, and bash now carries image
    vision: cat <image> returns the file as a vision block at full
    resolution (decode, EXIF orientation, re-encode at native size — no
    downscale, no halving) instead of the binary garbage a shell would
    stream. The claim is deliberately narrow — exactly cat plus one bare
    path — so flags, pipes, redirects, globs, and multi-file cats run
    normally, and missing or mislabeled files fall through to the shell's
    own error. A separate view tool was tried and removed the same day,
    on one principle: a capability the existing surface can carry does not
    need a new tool. tools-minimal is pinned bash-only by a test

  • Project rules arrive without being asked for: the nearest AGENTS.md /
    CLAUDE.md above the working directory is served every turn as a
    self-describing <project_context> block (nearest ancestor wins, the
    gray-home file is skipped since it is the stored system prompt, 32K-char
    cap), so repo rules stay in the permanent prompt instead of arriving as
    prunable tool observations that can fall out of context mid-session

  • An explicit /skills <name> invocation now pastes a binding directive
    ahead of the skill body — the instructions are binding for the current
    task, drop any conflicting plan — because a bare body pasted mid-task
    reads as background material and the model resumed its previous plan.
    The per-turn <available_skills> block gained the matching mid-task
    clause: stop and read a matching skill before continuing

  • The default system prompt is 31 lines, down from 39: the three prose
    sections folded into workflow steps and two guideline bullets (both
    verify-contract sentences preserved verbatim), and the project-rules /
    skills n...

Read more

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 24 Aug 17:12