Skip to content

v0.1.22

Choose a tag to compare

@github-actions github-actions released this 28 Sep 02:04
· 3483 commits to main since this release

Added

  • The model can look at an image, on request (image_view, the owner's
    ruling of 2026-09-27). It takes a workspace path — a picture
    image_generate saved, an attachment under inbox/ — and the loop puts
    the picture in the user turn beside the results (ToolOutput::image),
    for a model that can see; a blind one is told in words. Before it, a model
    could not check what it drew: image_generate said "You cannot see it"
    and fs_read returns a PNG as noise, so positional edits ("the second
    person from the left") were composed blind. Not returned by default,
    because a look costs ~1000 tokens for the rest of the conversation; a
    look arms private_data, like fs_read. Registered for any model that
    can see; a small picture (≤ 1 MiB, a screenshot most often) reaches it
    byte for byte, a larger one as JPEG.

Fixed

  • The web chat draws a picture once. A model that looks (image_view)
    at the picture it just made (image_generate) put the same image on the
    page twice, because each row drew its own. A row whose picture is already
    shown higher up keeps its row and shows the picture only when you tap it
    open (repeatedPictures in Chat.svelte).

  • A picture is typed by its bytes, not its name, at every door. A file
    named .png that is not a picture used to pass through unread under
    5 MB, and a real JPEG named .png went out labelled image/png; the
    provider rejects either, and a rejected picture fails every later request
    of that chat. The first is refused, the second sent as image/jpeg.
    Reachable from Slack and the web chat.

  • A picture over 128 megapixels is refused as too large to show, from
    its header, rather than reported as a failed decode (the image crate's
    default 512 MiB allocation cap already stopped the allocation; the error
    said the wrong thing). The bound is by area, so a long full-page
    screenshot is still shrunk and shown.

  • The web chat says when an attached picture was not shown — a model
    that cannot see, more than 8 on one message, or a file that will not open
    — rather than clearing the chip as if it had been. image_generate's
    result again tells a run it has not seen the picture, so one without
    image_view does not describe it.

  • A picture uploaded in the web chat reaches the model as pixels
    (REMOTE-SURFACE-DESIGN.md D6, built half-way on 2026-08-24). The page
    named an upload's path and nothing else, so the model could not see what
    it was sent — fs_read cannot read a picture. The page now lists its
    uploads beside the text and serve puts each image on the turn, capped at
    the door, for a model that can see; the path stays in the text, so it can
    still be handed to image_generate. Up to 8 per turn; an attached picture
    arms private_data, as the Slack door's does — so a web chat with a
    picture in it now refuses a model-chosen send (http_fetch) once outside
    content has also entered, where before only an opened file armed it.

Changed

  • A previous attempt says whose act it was (APPRAISAL-WIRING-DESIGN.md
    R42, the owner's ruling of 2026-09-27). The situation brief's previous
    attempts put a draft reject or edit under "The owner:" only when it is
    stamped as the owner's; a run's own reject or edit, and one from before
    the stamp, is said apart as not recorded as the owner's, with the item id
    and never the reason. mecha outbox list's drafting line reads "sent
    unedited" (it counts whoever released), not "sent as drafted".

  • The retirement drill no longer writes into the live comparison store.
    ComparisonStore honours MECHA_COMPARISONS_DIR (as the learning store
    honours MECHA_LEARNING_DIR), scripts/retirement-drill.sh sets it, and
    the drill fails with DRILL LEAKED on any exit that left the live
    comparisons.jsonl changed. Two runs on 2026-09-27 had each added a
    validation row about a throwaway session to the owner's store.

  • A draft released unchanged is your verdict only when you released it
    (APPRAISAL-WIRING-DESIGN.md, R16a's ruling D3 carried to releases). The
    appraisal's +1.0, the owner-verified success set (mecha sessions successes), its writing exemplars and the planning success examples
    goal_context serves now count an unchanged release only when #343's
    stamp says owner; a run's shell that approves its own draft earns none,
    and releases from before the stamp count as neither, so the success set
    starts empty of that history. A run that rejected its own draft sets no
    point-wise rejected-draft point.

Added

  • The web chat's model chip switches the model (REMOTE-SURFACE-DESIGN.md
    §14, step 5). Tap it for what the router serves, with a dot beside the
    loaded model; tap another to switch the whole machine. It runs mecha model use on the server, so R1's rollback, R4's refusal and D13's wait for runs
    all apply unchanged. While a switch waits, the chip reads → model,
    and the menu names the runs it waits for, with switch now and
    cancel. A failed switch turns the chip amber and says why. mecha model list --json now reports each router's pending_switch and each model's
    would_not_follow. mecha model use X --now hurries a switch already
    waiting on X instead of refusing it as a second switch.

  • Every outbox edit records who made it, and an edit is your writing only
    when you made every edit and the send
    (APPRAISAL-WIRING-DESIGN.md,
    R16a's ruling D3 carried to edits). mecha outbox edit (and polls pick)
    stamp edited_by — owner, owner-approved or unknown, as rejects are
    stamped — folded across edits so an owner's later edit never launders a
    run's. mecha reflect mines a diff as a writing lesson, the appraiser is
    shown it as "the owner edited it", and a point-wise comparison takes the
    released text as its gold only when every edit and the release were
    stamped owner; otherwise the edit is described, not shown. Edits from
    before this read unknown. An edit not stamped owner says so when it is
    saved, in the terminal and the web review. outbox show and the TUI name
    the editor.

  • Image generation. With [image] configured, the image_generate
    tool draws a picture with a local image model (ComfyUI with Qwen-Image 2.1
    today) and saves the PNG under images/ in the conversation's workspace;
    web chat shows it inline, and the same tool edits a picture you attach or
    one it drew. The workflow the server runs is fixed in mecha's code; the
    model fills in values, never the graph. See Image generation in the
    published docs.

  • Incognito web chat. A second new-chat button opens a chat that writes
    no transcript, title or count, keeps its files on tmpfs, and is gone when
    you tap End, after 30 idle minutes, or when mecha serve stops. It reaches
    an allowlist (mail and calendar reads, web search, its own folder), runs
    only on a local provider without fallbacks, and refuses to open rather than
    weaken any of that (docs/INCOGNITO-DESIGN.md).

  • Model switching behind a llama-server router. mecha model use <name>
    loads a model and every default run follows it, including web chat, voice,
    Slack and the trigger daemon, which re-check before each turn or fire.
    A switch waits for the runs still using the old model (--now stops them;
    mecha model cancel-switch withdraws a stuck switch), and each run records
    the model that answered. Opt in with follow_loaded = true on the default
    provider entry (REMOTE-SURFACE-DESIGN.md §14).

  • Every outbox resolve records who made it (APPRAISAL-WIRING-DESIGN.md,
    R16a's ruling D3). mecha outbox reject, review and approve stamp
    resolved_by on the item — owner at the owner's terminal or a
    surface's own child (web, TUI, Slack, voice), owner-approved under an
    interactive run's shell, unknown otherwise — decided by the task
    closure store's rules (closure::attribute). A rejection's reason reads
    as the owner's words only under owner: the reflector no longer mines a
    reason a model's shell typed, the appraiser's input shows a typed word
    in its place, and the meeting-poll sweep puts "No time found" rather than
    that text on the participants' page. Items resolved before this load as
    unknown. outbox show and the TUI print by <actor> beside the reason,
    sessions appraise counts the other reasons apart
    (unattributed_rejections), and a reject whose reason is not stamped
    owner says why — in the terminal, the TUI status line and the web
    review.

  • Contrast evidence for the reflector (APPRAISAL-WIRING-DESIGN.md L2,
    2e-4b-2, R43), behind [agent] contrast_evidence /
    StageLever::ContrastEvidence, a stage lever that ships off. On,
    mecha reflect reflects a steer, denial or followup with a verified
    success from its region beside it — the newest trace of a clean, scoped
    session the owner verified, from another session, whose run records the
    correction's scope matches (the loader's match, R43). The reflector sees
    the owner's act in fixed words and the session's tool sequence; its
    behaviour frame gains one sentence and the correction one block. Off, the
    reflector's prompt is byte-identical to before. mecha reflect --dry-run
    names the success each correction would be shown. An experiment arm
    measures it on through its environment's config.toml; stages_off = ["contrast_evidence"] forces it off.

  • Planning examples from what the owner verified (APPRAISAL-WIRING-DESIGN.md
    L2, 2e-4b-1, R40), behind [agent] success_examples /
    Lever::SuccessExamples, which ships off until an experiment measures
    it (levers_on = ["success_examples"]; mecha eval forces it off, and the
    diagnostician's run is narrowed off it). On, goal_context answers a
    request toward a goal with the tool sequence of a session the owner
    verified toward it — a task closed done that no reopen undoes, a workflow
    closed, a question answered whose session completed — ahead of the
    declared-check examples, in its own shape (tools_in_order, verified_by,
    the act in words, and a limit saying it is a call trace, not a plan). Only
    a session whose recorded taint is clean to its end, whose every run
    recorded a matched workspace and surface, and whose situation the asking
    run is in lends one. Derived from the success set at run start, never
    stored, so a reopen withdraws the example. mecha sessions successes --examples lists what would be served and why each success lends none
    (planning_examples in --json). Nothing reaches the prefix; the answer
    with the lever off is unchanged.

  • A task run's situation brief names the task's previous attempts
    (APPRAISAL-WIRING-DESIGN.md M5, 3a-2, R42). The brief gains an
    attempts field: for a run anchored to a board task, up to three earlier
    task sessions on the same task from the last 90 days, newest first, each
    said in two lines — what the owner did with its work (a draft rejected,
    sent as written or after edits, a question answered or let go, the task
    closed or reopened, an artifact check failing, what still waits), by
    record id; and how the run ended, as the harness recorded it, never as a
    verdict. A reopen reason is quoted only when the owner wrote it with their
    own hand; one written in a conversation with the owner's approval is
    said to exist and not repeated, and an outbox rejection is said as "draft
    rejected" and the item id, without its reason. A walk cut by its bound, or a session file that cannot be read,
    says "at least". Recorded on every run; delivered only behind [agent] situation_brief, which still ships off. sessions health reports the
    field's completeness beside the other nine, and counts it unread only
    when something failed: an unreadable file or store, or a session kind
    this build cannot name — never the walk's own bound.

  • A rule's owner tenure, beside retirement (APPRAISAL-WIRING-DESIGN.md
    2e-5b/2e-5c, R41). mecha rules list/show print owner: <tenure> · region: <quiet> per learned rule, and --json gains owner and quiet.
    Tenure is the Wilson lower bound (z = 1.96) of the owner-accept rate over
    the owner's verdicts on runs whose rule_ids carried the rule: no bound
    under 20 verdicts, tenured at 0.65, unknown when a session that carried it
    cannot be read in full. rules propose-retirements gives a tenured rule on
    probation the ordinary leash (3) for that pass, keeping the probation mark;
    retirement still runs on measured regressions alone, and nothing leaves a
    prompt on the bound. A rule whose scope no recent run matched is named
    QUIET — a report only; nothing is evicted. --no-board skips both walks.

  • goal_context can serve past clean appraisals (APPRAISAL-WIRING-DESIGN.md
    I2, 2c-2), behind [agent] past_appraisals / Lever::PastAppraisals,
    which ships off until an experiment measures it (levers_on = ["past_appraisals"]; mecha eval forces it off). On, the tool answers a
    request toward the run's goal with up to three appraisals of runs in the
    same situation toward the same goal — only appraisals of runs that read
    no third-party content — framed as a model's interpretation of an
    earlier run. Nothing reaches the prefix; the answer with the lever off is
    unchanged. An experiment environment's appraisals/ is seeded into each
    trial home.

  • calendar_list_events and calendar_freebusy take now, today,
    tomorrow, yesterday and ±Nd and resolve them on the server, in
    mecha-mail, mecha-google and mecha-outlook alike (time::window), and
    every answer states the window it covered and the clock it was resolved
    against — so a run working from a stale date is contradicted by the result
    instead of confirmed by it. A window resolves in MECHA_TZ alone, never
    the machine's TZ; without it a relative term is refused by name. (#243)

  • Every [[mcp]] server is handed [agent] timezone as MECHA_TZ, so the
    zone is set once rather than copied into each server's env (an explicit
    value there still wins). mecha setup reports an unset [agent] timezone
    once a server is wired. (#243)

  • A rule toward a goal that has closed is named, not silent (R34).
    A rule scoped to a task that is done or dropped, or
    to a trigger that was removed or disabled, keeps its scope and loads
    nowhere until a lesson restated toward another goal widens it.
    mecha rules list now counts such rules at the top and marks each
    LOADS NOWHERE with the reason (goal_closed in --json), and
    mecha learn repeats the count, with the waiting reflections toward a
    closed goal, on every pass. The board is read only when a rule or
    reflection names a task; a board that cannot be read is reported as
    unknown (goal_unknown), never as nothing dark, and so is a task the
    board no longer carries. The TUI and the web settings page call the
    roster with --no-board, since neither can wait on an MCP start. The
    roster's region line prints a goal bare (shell · trigger:morning).

  • A learned rule can be scoped to a goal. The front-end that owns a
    run hands prepare the goal it read from a store the owner wrote
    (GlobalOpts::goal: tasks work its task, a trigger run its trigger,
    run --goal the owner's reference, a question continuation the asking
    run's), the learned-rules block is matched toward it, and the run record
    keeps what was matched (RunConfig::rules_goal) for the miner, the
    backfill, the validator, the probe, the planning examples and the roster
    to read — never the conversation's anchor. A rule learned from
    reflections all toward one goal loads only in runs toward it, and widens
    by restatement from another goal's batch. A run toward no goal matches no
    goal-scoped rule, and a stored goal this build cannot name is kept
    verbatim, matches nothing and is reported at startup. Rules from before
    the key carry no goal and load under every goal as they did.
    (APPRAISAL-WIRING-DESIGN.md 2c-1)

  • Mail is classified through the working day, not only overnight.
    mecha-mail-classify-day.timer runs the same sweep every 20 minutes,
    07:30–21:50 in the owner's zone, clear of the morning briefing and the
    front door's hourly run; the 05:30 UTC nightly stays as the catch-up.
    scripts/model-idle.sh gates each run: it skips while any local-model
    slot is busy or the GPU is above 30%, and fails the unit — so mecha doctor sees it — when the model server does not answer, answers with a
    slot list it cannot read, or has been stuck loading for three hours.

Changed

  • The nightly's sessions compare is bounded by the clock and by the
    corpus
    (scripts/ruminate.sh, owner 2026-09-27). timeout caps its
    share of the night
    (MECHA_COMPARE_TIMEOUT, default 30m; a pass cut short keeps what it
    compared, and a seat it held is reclaimed by the next caller), and
    --days bounds its read (MECHA_COMPARE_DAYS, default 30).

  • The retirement brake runs right after validate — in the nightly
    (scripts/ruminate.sh) and in an experiment lifetime's schedule
    (Schedule::due_after: reflect, validate, retire, learn, ruminate), owner
    2026-09-27. It reads only validate's rows, so a rule measured harmful
    leaves the prompt before sessions compare or learn --auto spends any
    wall clock, and learn measures its candidate without it. The nightly scans a
    second time after learn, whose cross-region widening can re-widen a
    rule the first scan narrowed — so it is re-narrowed that night.

  • The counterfactual comparison summary counts one model (the owner's
    ruling of 2026-09-27). Since the chat server became a router, background
    passes follow whichever model is resident, and mecha sessions appraise
    summed the production model's comparisons with a comparison arm's into one
    separated share. It now counts the model of the newest comparison on
    record (mecha sessions compare: the model it drove) and says the rows
    under other models beside the summary without counting them. Unposed
    points, which no model was run on, are counted on their own (unposed)
    rather than inside one model's inconclusive. --json adds model,
    other_models and no_model (rows with no model recorded that are not
    unposed points: unknown, never counted) to comparisons; records,
    inconclusive and the rest now count that one model's rows. Nothing
    stored changes.

  • A mail thread whose classification fails for a reason of its own (a
    verdict that will not parse, a refusal, a rejected request) is retried on
    a backoff — an hour after the first failure, doubling, at most a day apart
    — rather than on every sweep, so it costs one model call a day instead of
    one per daytime tick and no longer keeps the daytime unit failed. A
    provider-wide failure (transport, server error, overload, an expired key
    or a lapsed account), or a failure at least as long as the length where
    the model is found to start failing a canary (searched among the sweep's
    own failure lengths, a few calls at most), is not counted against any
    thread: those threads are retried every sweep as before and
    caught up as soon as the server is back. A failed thread that has left the
    newest-N window the sweep reads is retried from the store when due (up to
    10 a sweep), so the long waits of a busy mailbox never strand it, and each
    failure is on disk the moment it happens rather than at the end of the
    sweep. A re-read of a stored thread that fails is the thread's own unless
    that account's surface is down — the sweep's own read could not reach it, or
    every re-read for it failed and a re-read of a thread the read just returned
    from it failed too — the mail surface's (a lapsed token, a
    503), counted against none. Dead threads (deleted, moved) therefore back
    off rather than fail the unit on every tick. mecha mail list shows the count
    and when the next retry is due; classify --force still retries at once,
    inside the window and out, and out of it without the per-sweep cap. A
    thread retried from the store is marked read_whole, outside the
    escalation measurement. A body too long for the model, or a request it
    rejects, stays the thread's own even in a sweep judged an outage — a
    length-matched canary overflows just as those threads do.

Fixed

  • The model chip's "switch now" is offered only while it can act. Once a
    switch stops waiting for runs, it records that beside its switch file.
    The chip then drops "switch now" and cancel, where it used to show buttons
    that did nothing for the seconds before the router reports the new model
    loading. mecha model use X --now then says the switch is no longer
    waiting. Two "switch now" taps at once both land; one could report failure
    for a hurry that had worked, and leave a temp file nothing swept.

  • The web chat's plan has its own read (GET /api/chat/{key}/todo).
    The page re-reads the plan on every todo result a run streams, and took
    it from the transcript read — which, since the entry below, carries the
    whole history mid-run, rendered and serialised once per plan revision.

  • A web chat opened or reconnected mid-run keeps its history. While a
    run held the conversation the transcript read returned no entries, and
    the page replaced everything it showed with that — so a phone whose
    stream dropped in the background during an image edit lost the whole
    chat, and the picture with it, until a manual reload. The read now
    returns the history the run started from, and the page re-reads at
    done when it may have missed part of the run.

  • A leading ~ in a config path now means the home directory. Nothing ran
    these through a shell, so command = "~/.cargo/bin/mecha-mail" — the line
    the mail and documents pages printed — named a directory called ~, the
    server never spawned, and [web] assets = "~/.mecha/web/dist" served no
    app. It covers every path-valued key and [[mcp]] command and args;
    ~user and a ~ mid-string are left as written.

  • mecha setup offered mecha-docs auth personal to authorise documents,
    which fails: mecha-docs auth takes the account as --account. It now
    offers mecha-docs auth.

  • mecha setup --write said "nothing answered … Start the server" when it
    had not looked: with a hosted default provider that has its key, no local
    server is probed. It now says so, and how to record a llama-server.

  • mecha workflow cancel and mecha charter described behaviour they do
    not have: cancel blocks further runs of that task, not triggers (a trigger
    is not tied to a task), and a charter is edited with mecha charter edit.
    /entity completed in the TUI but was missing from /help, and /mail
    (alias /inbox) opened the classified inbox while neither /help nor Tab
    completion named it; both are listed and completable now.

  • Recording a cloned voice on the web settings page could silently replace a
    voice of the same name when two uploads landed together, although the page
    refuses to overwrite: the name check and the write were two steps. The
    recording is now linked into place, which fails if the name exists.

  • mecha setup --write keys its "no local server was checked" message on the
    provider being hosted, not on it having a credential, so a llama-server
    behind an api_key_env that is down is still told to start the server.

  • The mail desk's batch keys work with the selecting modifier still held.
    A selection is built with ⇧-click or ⌘/Ctrl-click, and the hand is often
    still on that key when e goes down: ⇧E was bound to nothing and ⌘E was
    dropped whole, so the batch button worked while its hotkey did nothing.
    ⇧E, ⇧D and ⇧T now mean e, d and t; ⌘/Ctrl-E and -D archive or dismiss
    while more than one thread is selected, and stay the browser's otherwise.