Repository navigation
v0.1.22
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_generatesaved, an attachment underinbox/— 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_generatesaid "You cannot see it"
andfs_readreturns 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 armsprivate_data, likefs_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 (repeatedPicturesinChat.svelte). -
A picture is typed by its bytes, not its name, at every door. A file
named.pngthat is not a picture used to pass through unread under
5 MB, and a real JPEG named.pngwent out labelledimage/png; the
provider rejects either, and a rejected picture fails every later request
of that chat. The first is refused, the second sent asimage/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 (theimagecrate'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_viewdoes not describe it. -
A picture uploaded in the web chat reaches the model as pixels
(REMOTE-SURFACE-DESIGN.mdD6, 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_readcannot read a picture. The page now lists its
uploads beside the text andserveputs 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 toimage_generate. Up to 8 per turn; an attached picture
armsprivate_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.
ComparisonStorehonoursMECHA_COMPARISONS_DIR(as the learning store
honoursMECHA_LEARNING_DIR),scripts/retirement-drill.shsets it, and
the drill fails withDRILL LEAKEDon any exit that left the live
comparisons.jsonlchanged. Two runs on 2026-09-27 had each added a
validationrow 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_contextserves now count an unchanged release only when #343's
stamp saysowner; 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 runsmecha model useon 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 --jsonnow reports each router'spending_switchand each model's
would_not_follow.mecha model use X --nowhurries 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(andpolls pick)
stampedited_by—owner,owner-approvedorunknown, as rejects are
stamped — folded across edits so an owner's later edit never launders a
run's.mecha reflectmines 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
stampedowner; otherwise the edit is described, not shown. Edits from
before this readunknown. An edit not stampedownersays so when it is
saved, in the terminal and the web review.outbox showand the TUI name
the editor. -
Image generation. With
[image]configured, theimage_generate
tool draws a picture with a local image model (ComfyUI with Qwen-Image 2.1
today) and saves the PNG underimages/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 whenmecha servestops. 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 (--nowstops them;
mecha model cancel-switchwithdraws a stuck switch), and each run records
the model that answered. Opt in withfollow_loaded = trueon 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,reviewandapprovestamp
resolved_byon the item —ownerat the owner's terminal or a
surface's own child (web, TUI, Slack, voice),owner-approvedunder an
interactive run'sshell,unknownotherwise — decided by the task
closure store's rules (closure::attribute). A rejection's reason reads
as the owner's words only underowner: the reflector no longer mines a
reason a model'sshelltyped, 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 showand the TUI printby <actor>beside the reason,
sessions appraisecounts the other reasons apart
(unattributed_rejections), and a reject whose reason is not stamped
ownersays why — in the terminal, the TUI status line and the web
review. -
Contrast evidence for the reflector (
APPRAISAL-WIRING-DESIGN.mdL2,
2e-4b-2, R43), behind[agent] contrast_evidence/
StageLever::ContrastEvidence, a stage lever that ships off. On,
mecha reflectreflects 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'sconfig.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 evalforces it off, and the
diagnostician's run is narrowed off it). On,goal_contextanswers a
request toward a goal with the tool sequence of a session the owner
verified toward it — a task closeddonethat 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 --exampleslists what would be served and why each success lends none
(planning_examplesin--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.mdM5, 3a-2, R42). The brief gains an
attemptsfield: for a run anchored to a board task, up to three earlier
tasksessions 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 healthreports 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/showprintowner: <tenure> · region: <quiet>per learned rule, and--jsongainsownerandquiet.
Tenure is the Wilson lower bound (z = 1.96) of the owner-accept rate over
the owner's verdicts on runs whoserule_idscarried 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-retirementsgives 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-boardskips both walks. -
goal_contextcan 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 evalforces 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'sappraisals/is seeded into each
trial home. -
calendar_list_eventsandcalendar_freebusytakenow,today,
tomorrow,yesterdayand±Ndand resolve them on the server, in
mecha-mail,mecha-googleandmecha-outlookalike (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 inMECHA_TZalone, never
the machine'sTZ; without it a relative term is refused by name. (#243) -
Every
[[mcp]]server is handed[agent] timezoneasMECHA_TZ, so the
zone is set once rather than copied into each server'senv(an explicit
value there still wins).mecha setupreports 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 listnow counts such rules at the top and marks each
LOADS NOWHEREwith the reason (goal_closedin--json), and
mecha learnrepeats 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 handspreparethe goal it read from a store the owner wrote
(GlobalOpts::goal:tasks workits task, a trigger run its trigger,
run --goalthe 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.md2c-1) -
Mail is classified through the working day, not only overnight.
mecha-mail-classify-day.timerruns 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.shgates each run: it skips while any local-model
slot is busy or the GPU is above 30%, and fails the unit — somecha doctorsees 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 compareis bounded by the clock and by the
corpus (scripts/ruminate.sh, owner 2026-09-27).timeoutcaps its
share of the night
(MECHA_COMPARE_TIMEOUT, default30m; a pass cut short keeps what it
compared, and a seat it held is reclaimed by the next caller), and
--daysbounds 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 beforesessions compareorlearn --autospends any
wall clock, andlearnmeasures its candidate without it. The nightly scans a
second time afterlearn, 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, andmecha 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'sinconclusive.--jsonaddsmodel,
other_modelsandno_model(rows with no model recorded that are not
unposed points: unknown, never counted) tocomparisons;records,
inconclusiveand 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 listshows the count
and when the next retry is due;classify --forcestill retries at once,
inside the window and out, and out of it without the per-sweep cap. A
thread retried from the store is markedread_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 --nowthen 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 everytodoresult 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
donewhen 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, socommand = "~/.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]]commandandargs;
~userand a~mid-string are left as written. -
mecha setupofferedmecha-docs auth personalto authorise documents,
which fails:mecha-docs authtakes the account as--account. It now
offersmecha-docs auth. -
mecha setup --writesaid "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 cancelandmecha charterdescribed 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 withmecha charter edit.
/entitycompleted in the TUI but was missing from/help, and/mail
(alias/inbox) opened the classified inbox while neither/helpnor 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 --writekeys its "no local server was checked" message on the
provider being hosted, not on it having a credential, so a llama-server
behind anapi_key_envthat 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 whenegoes 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.