* fix(desktop): align nested state selections with aliases * fix(desktop): check build runtime export capability * fix(desktop): provide complete runtime repack command * fix(desktop): retain runtime probe failures * test(desktop): identify capture independently of schema preflight * fix(desktop): preserve document URLs and value formats * fix(desktop): bound added checks and omit noisy text guards * fix(desktop): exclude unseeded locks from task checks * fix(desktop): preserve draft bounds and launch URLs * fix(desktop): render draft values using their source format * fix(desktop): preserve launch URLs and JSON draft values * fix(desktop): recognize captured file URL documents * fix(desktop): render captured file URLs for runtime home * fix(desktop): distinguish phone numbers from save metadata * fix(desktop): preserve launch URLs and user text * fix(desktop): preserve captured URLs and user text * fix(desktop): preserve local file URL authority * fix(desktop): preserve local file URL authority * fix(desktop): preserve local file URL authority * fix(desktop): render captured file URLs at launch * fix(desktop): classify numeric timestamps using field context * fix(desktop): classify numeric timestamps using field context * fix(desktop): classify numeric timestamps using field context * fix(desktop): retain context for generated draft values * fix(desktop): retain context for generated draft values * fix(desktop): retain context for generated draft values * test(desktop): cover generated IDs in text guards * test(desktop): cover generated IDs in text guards * test(desktop): cover generated IDs in text guards * fix(desktop): isolate launcher URL encoding * fix(desktop): isolate launcher URL encoding * fix(desktop): isolate launcher URL encoding * fix(desktop): isolate launcher URL encoding * fix(desktop): retain clean text in noisy record fields * fix(desktop): classify added line values in one pass * fix(desktop): apply XML quote rules to text drafts * fix(desktop): distinguish XML attributes from quoted text * fix(desktop): preserve parsed XML line context * Format appTaskChecks.ts with the pinned oxfmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(desktop): retain partial XML syntax facts * fix(desktop): recognize markdown autolinks in drafts * fix(desktop): reuse export exclusions in task drafts * fix(desktop): report unchecked client build paths * docs(desktop): clarify gzip scrub limits * fix(desktop): reuse export exclusions in task drafts * docs(desktop): clarify gzip scrub limits * fix(setup): own desktop build process cleanup * fix(doctor): compare desktop settings with resolved defaults * style(desktop): format the noisy-record draft files with the pinned oxfmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(desktop): give the root-array draft test its own describe The records describe grew past the 150-line lint limit with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(doctor): inspect client-recorded desktop runners * fix(doctor): reuse matching runner probes * fix(setup): propagate cancellation to desktop actions * fix(setup): stop Hopper on cancel, never report a cancelled setup ready, and keep a failed action's client setting - The Hopper install gets setup's signal: Linux already honoured it, and the macOS installer now stops its downloads and stops before mounting or copying. - runSetup checks the signal after the final doctor and close, so a cancellation there raises instead of returning ready. - A failed pack or agentenv install no longer writes its archive or runner to client configs, so the setting the client already had stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(setup): keep the completed setup result in a cancellation after cleanup A cancellation during the final doctor or host close carried only the applied actions, dropping each client's result and backup path and the doctor report. It now carries the whole result as setup_result, as doctor's cancellation carries its report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(desktop): move launch execution to boundary suite * fix(desktop): redact build schema failure output * refactor(desktop): normalize schema failure once * fix(desktop): omit unsafe schema parser snippets * fix(desktop): preserve draft change and selected path evidence * test(desktop): group retained dconf note regressions * fix(desktop): respect archive entry format in draft checks * test(desktop): separate unowned-file draft note cases * fix(desktop): preserve explicitly selected journal documents * fix(xml): avoid scanning selected ancestors per tag * fix(desktop): exclude covered text runs from draft counts * fix: preserve text when drafting desktop checks * fix: restore nested desktop state aliases * refactor: share UUID recognition across draft checks * fix: retain multiline XML attribute context * fix: retain relationship identifiers in task drafts * docs: clarify desktop draft identifier handling * test(desktop): type formatting run cases as pairs * fix(desktop): carry document format into selector evaluation * test(desktop): separate markup boundary regression group * fix(desktop): propagate formats through draft selectors * fix(desktop): switch selector format within archive entries * test(desktop): remove duplicate seed filename case * fix(setup): retain diagnostics when cancellation overlaps failure * refactor(setup): keep command cancellation at its owner * test(setup): verify cancellation at the application boundary * test(desktop): pass JSON format in exported index parity * docs(desktop): clarify task checks before scratch export * fix(desktop): respell selected paths in file URLs * test(desktop): group captured document spelling cases * fix(desktop): bound draft preview serialization * fix(desktop): restore selected state aliases * fix(desktop): respect entry format in draft noise * test(desktop): match exported alias evidence at its JSON boundary * fix(desktop): retain app state logs in exported seeds * fix: align desktop numeric seed comparisons * test(desktop): keep one draft exclusion fixture import * test(desktop): retain log eligibility across draft test groups * fix(doctor): resolve empty registered desktop runner * style: fix desktop launch test formatting * style: format native command path schema * fix(desktop): map selected external launch paths * style(desktop): format seed document regressions * test(desktop): group launch URL regressions * fix(desktop): omit redundant nested state aliases * fix(desktop): compare added free text as a whole * fix(desktop): check recompressed seeds as shipped * docs(desktop): clarify recovered seed format * test(desktop): group shipped gzip seed checks * fix: retain quoted identifier text in task drafts * fix: recognize Unicode XML in desktop state readers * fix(desktop): bound nested draft checks * fix(desktop): explain missing arrays in truncated views * fix(desktop): find XML type references in archives * test(desktop): remove duplicated capture assertions * fix(desktop): retain nested omitted text boundaries * style(desktop): format draft value imports * test(desktop): group model and draft value checks * fix(desktop): map captured launch paths once * refactor: remove unused whole-text guard state * refactor: stop passing unused text children * test: align free-text checks with whole-note policy * fix: validate recompressed desktop archive seeds * style(desktop): format archive seed regression * fix(desktop): retain placed-item sibling guards * fix(desktop): preserve archive paths and bound doctor reads * fix(doctor): refuse non-files before descriptor admission * fix(setup): track remaining cleanup resources across retries * test(desktop): specify XML format in heap pairing fixture * fix(doctor): query agentenv for model endpoint * fix(doctor): preserve uncertainty for empty endpoint override * fix(desktop): redact runtime schema errors * fix(desktop): normalize captured file URLs * fix(desktop): report seeded files by capture path * fix(desktop): retain startup readiness failure stage * fix(desktop): retain JSON date checks * fix(desktop): preserve archive formatting checks * fix(desktop): preserve explicitly selected journal files * fix(desktop): report build command transport limits * docs(desktop): clarify unreadable dconf versions * test(desktop): remove duplicate history regression * fix(desktop): align XML names with exported reader * fix(desktop): create selected state alias targets * fix(desktop): inspect supported runtime archives * fix(desktop): keep ordinary text checks exact * fix(desktop): verify registered build host paths * fix(desktop): retain record identity in draft checks * fix(desktop): scrub XML independently of reader compatibility * fix(desktop): preserve file aliases and reader diagnostics * style(desktop): format review fixes * fix(desktop): inspect archives with registered runner * test(desktop): verify registered archive interpreter * style(desktop): format capture review fixes --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
55 KiB
Desktop application tasks
A desktop application export can carry one task: a prompt for a computer-use agent and checks that grade what the agent left in the application's files. An agent, not REA, writes the prompt and the checks from the change the capture recorded. REA validates the task against the capture and keeps it only when its checks fail on the seed and all pass on the captured end state. REA runs no model and no code the agent wrote: the checks are declarative, and the exported verifier evaluates them the same way REA does.
rea describe-desktop-application-task ./mousepad-capture --format json > draft.json
# edit the draft's task into task.json: a name, the user's request, the checks
rea export-desktop-application ./mousepad-capture ./mousepad-bundle --task task.json
agent-env run ./mousepad-bundle --task add-milk --agent claude-code
Describe the change
rea describe-desktop-application-task <capture-folder> reads the capture and
its state view and returns Evidence with
operation describe_desktop_application_task and predicate type
rea.desktop-application-task-draft:
application: the environment name the export would use, the title and the window class.owned_paths: the paths the export owns, the only ones whose files the app'sdata/getserves and checks can read.checkpoints: the two checkpoints compared,baselinefor the seed andfinalfor the end. They are the capture's first and last unless--seed-checkpointand--end-checkpointname others, as for the export, which takes the same options; a name the capture does not hold, or an end before the seed, is refused.files: each file under them whose state differs between those checkpoints, with its home path, role, its versions at both (baselineandfinal, the last recorded at or before each) and the net change between them, from the state view's own diff (lines,values,keysorrowswith what was added, removed and changed, orcreated,deleted,format,content,unreadable;contentalso when the views hold only part of a file and match while the file changed). A database is compared by its rows, since its hash leaves out the-walfile. Text changes list each line once and addline_counts, the lines found at both ends whose number of occurrences changed. An XML file's change names its members by the selectors checks use, as an archive's already does, and by position only where nothing names a member or where only the members' order changed. Views appear only for the files the suggestions cover, with arrays past 200 items and strings past 2,000 characters cut (view_cut).values, in each of those files: the values its change added, changed and removed, each with thepointera check names it by, itskind(added,changedorremoved), andbeforeandafter: the scalar, an element as compact XML with the text the XML reader kept, spaces between runs included, or a JSON value as compact JSON, cut at 600 characters, and null at the end that lacks it. A file created or deleted lists what its one end holds,addedorremoved: an archive's entries, an XML file's root element, a JSON file's members or a key file's keys, each whole once. A value only position reaches hasnote: "by_position", and its pointer cannot go into a check; noise an app writes as it saves hasview_state,renumbered,volatile,timestamp,upkeeporlayout(seetask). A record of text fields added to a list of its kind, as GnuCash's<gnc:transaction>, is listed field by field, and a field that lists records, as its<trn:splits>, record by record, so the cut never hides one.refers_tonames up to five other elements a value equals thekeyorid(xmi:idamong them) of, outside a view the app keeps of the document, as Umbrello'slistviewmirroring its model by id, with theattributeholding it, as HomeBank'scategory="44"names<cat key="44" name="Groceries"/>, or the id a child element holds, as a GnuCash split's<split:account>names the account whose<act:id>it is, shown with its name,<gnc:account …><act:name>Groceries</act:name>…; an element added whole lists those of the records of attributes it nests too, as a KMyMoney transaction's splits name their<ACCOUNT>and<PAYEE>, each once. Where several elements share that key, the one whose tag shares the attribute's stem, aspayeeand<pay>, and of those the one named for the attribute, asaccountan<ACCOUNT>before an<ACCOUNTID>. A plain number names an element only through an attribute so named or ending inid,reforkey, so a list'scount="5"names no<FIXVERSION id="5"/>. A value that changed lists, inalternatives, its pointer naming the element by each other identifier or name-like attribute it holds alone among its tag's members, up to three, as an Inkscape text's[@inkscape:label=Headline]beside its[@id=text12], for a check to name it by instead; an element its tag alone names inside the first one a condition names, as an EPUB chapter'sh1, by its[@id=chapter-two]. What a check can name comes first, text an edit changed before the rest, so no cut drops the edit, then what only position reaches, then noise. A file lists at most 20 values and 8 KB, the draft 24 KB, andvalues_cutsays when a limit left some out. Values cover XML, archives, JSON and key files; a text or SQLite file's change and suggestions already carry its lines and rows.task: a draft with the namechange, a placeholder prompt the export refuses, and suggested checks derived from the net change of the files with roledocument, or of the app's own state files when no document changed, excluding locks the export leaves out of the seed. App state files ending in.logremain eligible for checks. A text file gainslines_presentfor its new lines andlines_absentfor lines it lost (when either is at most ten), andline_countfor each line holding a letter whose count changed; a new line holding an XML attribute REA recognizes as generated, such as a named epoch time orID_329023692, gets aline_countof the linescontainsits text up to that value, as<node TEXT="Water filter" ID=", at the end's number, where both views hold the whole file, instead of alines_presentanother save would fail, or avolatile_linesnote when no text comes before it. UUID-shaped text stays exact because its shape alone does not show that it was generated; JSON and key files gainvalue_equalsandvalue_absentby pointer, a JSON array's records named by an id field each holds alone at both ends (id,uid,uuid,guid,keyor a name ending in_idorId), as/lists/[id=chores], whatever order the app saves them in; where the app generated that id, a record both ends hold is named by its kind field and its first name-like string field (a key ending inname,title,labelorcaption), nearest first and up to three keys deep, that holds the same string at both ends and names it alone at each, as TreeLine's category by/nodes/[format=CATEGORY][>data~1NAME=Science], and by its id where none does, as/nodes/[uid=7584d494d9f511f1a873edd4f1920549]once its name changed; and a record only one end holds is named by such a field that names it alone at its end and nothing at the other, as TreeLine's new book by/nodes/[format=BOOK][>data~1Title=The Silk Roads], its other fields checked through that selector and the fields the selector reads not, nor a field holding what every like record (one of its kind) at the end holds there, as the emptychildrenof every book; and by position only where nothing names a record, as a new book whose title another book holds, with aby_positionnote listing those checks; never a value that is a uid the app generates (a GUID, or 32 hex digits as TreeLine's uuid1), nor a check the seed already passes, as a new record'sformatwhere another record of that kind held its place; a removed record is checked gone by its own pointer, as/lists/[id=chores], and only where the end holds nothing there, so a record only position reached, whose place another took, is left to the count;member_countchecks of the arrays whose members were added or removed, of the members' kind where records share atype,kind,formatorcategoryfield, as/nodesmatching[format=BOOK], and a count guard of the innermost array an edit went through, as for XML; SQLite tables gain rows added, changed and removed, by primary key, and their new row count. An XML file, and the JSON, key file and XML views of a zip archive's entries, gain checks by selectors and never through an element by position: in XML, firsttext_equalsfor each element whose text changed, then elements added or removed whole, then values changed, added and removed, an entry or a JSON value added whole through its members, and an empty array or object as it is.-
A selector names a member by its tag, then by its identifiers (
name,id,xid,xml:id), then by one other attribute, a name-like key such as Scribus'sANNAMEorinkscape:label, a worded value or an integer of up to five digits first, then by two such attributes together, as Gnumeric's cells by[@Col=3][@Row=2], then by a coordinate, a long number, a length with its unit, assvg:height="2.629cm", anxml:idordraw:idof the formid2that LibreOffice numbers anew as it saves, so a Draw shape is named by itsdraw:name, or a token, such asXPOSorItemID, or an id of random case, as Umbrello'subcgF2AWfBmHh(uand twelve letters and digits), an automatic style name an app numbers as it saves and renumbers on reload, as LibreOffice'spresentation:style-name="pr1"(P1,T1,MT1,L2on a*:style-name), so a slide's frames are named bypresentation:class, or an attribute naming view state, such as which layer isselected, and last by a child element's text,[>CHILD=VALUE], a name-like tag first, as Osmo's record by[>first_name=Delphine], MuseScore's<Text>by[>style=Title], a spreadsheet row by its label cell, or GnuCash's transaction by[>trn:description=Greenleaf Market]: text of at most 64 characters, never a time or a token or id a program generates; then by an identifier a child carries,[>CHILD@ATTR=VALUE], as a GtkBuilder<child>by[>object@id=submit_button], never one an app generates or numbers anew as it saves; and then by text a child joins from elements of its own, as Glade's[>packing=TrueTrue1]from a widget's packing properties, which moving the widget changes. A pair counts every member holding both its values, whichever attributes that member ranks first. An element with identifiers is named by those alone; an id the app generates, as Freeplane'sID_329023692, or a GUID in any attribute, braced or not, as VYM'suuid="{18eadb5d-…}", ranks last, after a child's text, so a new node is named by itsTEXTand a new VYM branch by[>heading=Sunset boat tour], while a member both versions hold pairs by such an id first, so records that keep their UUIDs and swap titles read as two title edits. A value that stands for none, as Grisbi'sId="(null)"on every transaction, names nothing, so a new transaction is named by itsNb. A child's text is counted on every sibling, one an identifier names included, so a title two records share names neither. The selector must name the member alone at both ends, with the same values; a child's text names a member that is in one version only when the list's length changed, so a row whose one cell an edit changed stays that row. An element whose one identifier changed, at its place among its tag's members, with every other attribute and its children alike, is that element renamed, as a MyPaint layer renamed fromLayertoSun: it is paired by its first other attribute naming it alone at both ends, as[@src=data/sun.png], so the rename reads as itsnamechanged, and the old name gets a check that it names nothing, unless its change is noise, as an id the app renumbered. Where that attribute's values number the members in document order, as MyPaint 2 writessrcasdata/layer-00.png,data/layer-01.pngand on along the stack and renumbers them when a layer moves, each version is named by its own value of the renamed identifier instead,[@name=Sun]and[@name=Layer]. -
A child of the document's root that its tag alone names, and that holds a list (two or more elements of one tag and no text of its own) and has an identifier, also gets a condition that keeps it named if the app adds a sibling of its tag, as Dia adds an empty
Backgroundlayer each time it reopens and saves a diagram: an id other thannameboth ends hold, as Inkscape's[name=g][@id=layer1], which a new sibling would not share; else the text of a child both ends hold, as Dia's[name=dia:layer][>dia:object=#Receive order#], which an empty sibling lacks; else itsname, which a new one may repeat. Deeper, a member of a list named for its tag, as Gnumeric'sgnm:Sheetingnm:Sheets, holding elements and its tag alone naming it, gets an id, else a name-like child's text,[name=gnm:Sheet][>gnm:Name=Budget], else itsname. Other steps, and elements with neither, stay as they are. An identifier the app numbers by position names nothing: where the members of a tag hold it as 1..N (or 0..N-1) in document order at both ends, members were inserted or removed, and every member another attribute names alone at both ends kept its number or moved it the way the count did, one at least, as Planner's taskidafter a task is inserted, members are paired by their other attributes, so an insertion reads as one task added. A value two members swap, or an id appended at the end, as Tellico's next<entry id="4">, leaves the ids identity. An identifier numbering the members 0..N-1 in document order at both ends, where members came or went, as Parley numbers its<entry id="6">by where it saves it, ranks after a child's text where a member has one, so a new entry is[>translation=library]and its kept neighbours are named alike; Tellico's ids, which count from 1, Tiled's and Rosegarden's, whose members hold no text, stay identity. An element a change added that holds nothing but the id of a record it added elsewhere, as Parley's lesson membership<entry id="6"/>, isupkeep, listed withrefers_toon that record, never a check or a count. -
The text check names the paragraph rather than a run inside it: it goes up from an element sitting in its parent's text, and from one named by its tag or position alone to the nearest ancestor named by an attribute, as AbiWord's
pby itsxid, unless it is a field its tag names in a record of two or more fields holding text, as QElectroTech's<text>beside<info_name>in a<dynamic_elmt_text>, or KmPlot's<equation-0>beside<arg-min>, which is checked itself rather than the record whose text joins its fields; one line of text the app lays out between line breaks, as KmPlot's formula before the next element, is checked, and its siblings guarded, withnormalize_whitespace: true, and text whose whitespace alone changed, as a formula the app moved to an indented line of its own, gets no check; where no attribute names one, to the nearest ancestor within three levels its tag names with no step by position, as MuseScore'sVBoxaround untitled<Text>elements; and where only position reaches the element, to the ancestor above the outermost step by position, by an attribute of its own that names it alone where one does, as an ODS sheet'stable:table[@table:name=Q1 Sales]above its rows. The text nodes and runs inside it get no other check, but an element holding no text that is added or removed there, such as an image or a line break, keeps its own, since the text check never reads it. A decimal that is an element's only text, such as a spreadsheet cell's, is checked by its number instead. Free text that gains a line, such as a shopping list, usestext_equalson the whole text: the current XML checks cannot compare an unstructured line as a whole item. This can reject a correct edit that puts the item elsewhere or changes spacing. Text an app keeps in an attribute, one element per run, as Scribus's<StoryText><ITEXT CH="Spring "/><ITEXT CH="Fair"/>, gets atext_equalswithtext_attributeon the nearest ancestor an attribute names, as[name=PAGEOBJECT][@ANNAME=Headline]with"text_attribute": "CH", where that ancestor holds no text nodes and the attribute's holders are runs, none inside another and none carrying an identifier or a name-like attribute beside it, as Umbrello's<ownedAttribute xmi:id="…" name="email" visibility="private"/>, a record whose attributes are checked one by one, two or more or one holding words with a space: an element's own attribute, as a Freeplane node'sTEXTbeside the nodes in it, or a lone value, as<Bool value="false"/>, is checked where it is. The check covers that attribute and the runs holding it; another change in the frame, as a run'sFONT, keeps its own check. A record added or removed whole, an element holding only elements, its fields, such as Tellico's<entry>or a record of repeated<field>elements, never makes the list holding records of its kind a check, whose text would hold the records the edit left alone, except a record removed whole that only position names, whose absence only the list's text shows. Text made of runs is no record: an element whose tag names a paragraph or a piece of text (p, Writer'stext:pandtext:h,h1toh6,li,para, SVG'stextandtspan, aspanor ana), one inside such an element, as Word's<w:r>run in its<w:p>, one whose elements are all inline formatting (b,i,u,em,strong,bold,italic, aspan,a,code,sub,supand the like, Gnote'ssize:andlink:runs), as<list-item><bold>olive oil</bold></list-item>, or whose runs split a sentence at a space, asMeetand a linkedMonday. Nor does an element holding a date the app stamps in parts take a text check. -
An element added to a list, where another element of its tag is at either end, gets a
value_equalsfor each of its attributes, best ranked first, at most six, leaving out its selector's attributes, identifiers, times, volatile values, view state and values standing for none, as HomeBank's new<ope>gets itsamount,categoryandpayee; a value two or more like siblings all hold, as Grisbi'sCu="1", comes after those that vary, as itsPa,Ca,DtandAm. Records of attributes alone that it nests, as a KMyMoney<TRANSACTION>its<SPLITS>of<SPLIT>, get avalue_equalsof each attribute whose values vary among the like records, its own and those under its like siblings, at most three a record and six in all, as each split'svalueandpayee, but not one every split holds alike, asprice="1/1", nor one each holds equal to an earlier one, assharesrepeatsvalue. Each record is named by an identifier that the app neither generates nor numbers anew in every record holding such a list, as KMyMoney'sS0001in every transaction, else by its best ranked attribute short of a coordinate or an amount, as[name=SPLIT][@account=A000001]. An element whose other attributes, two or more, a like sibling holds every one of, as a new Planner task copies its neighbour's defaults, and any other element added whole, get a check that it exists; of those copied, an attribute naming an element the seed holds by an id that is no plain number, as an Umbrello attribute'stypeits data type, is the user's choice and keeps its check. A record added to a list that a selector names, as Tellico's<entry id="4">or Osmo's<record>by its first name, gets atext_equalsfor each field, at most five, each by its selector among the record's fields, as[name=title], leaving out a field repeating the record's identifier, the field its selector names it by unless it is the only one left, as a new VYM branch's<heading>, a value every like record holds in that field, as Osmo's<group>None</group>, noise, a field holding ids of its own, a date the app stamps in parts, and blank text or times; a field that lists records, as GnuCash's<trn:splits>, gets its records' fields instead, each record by its own selector. An attribute every like record holds alike, as GnuCash'sversion, is the format's and gets no check. A record only position names also gets atext_containsfor each new field of three or more characters, at most five, at the nearest ancestor a check can name, each absent from that ancestor's text at the seed. -
Where members of a list were added or removed, a
member_countof the list's members of their tag, and of their kind where they share one (type,kindorshape, as[name=dia:object][@type=Flowchart - Box]), unless every member of the tag holds a value of its own, as Tiled's objects a free-formtype(well,door,gate), which labels each rather than grouping them, equal to the end's number, which differs from the seed's: at most two a file, the shallowest lists first, after the other checks. None for a list of one member at both ends, as a style an app adds for a run, for members of an element a text check reads, which are runs of its text, for an element in mixed content, or for noise. -
A decimal written as text, such as
146.83, is suggested as a number withtolerance: 0.001, unless the seed's value, read as the evaluator reads a number (10and1e1included), is that close too. Avalue_equalsthat would pass on the seed's value is not suggested. -
An element placed by its own position, holding numeric
xandyas a like sibling does, as Xournal++ places each<text>note on a layer, is an item of its own, never a run of the others' text: one added whole gets atext_containsof its text on the element holding the items, which passes wherever it is placed, and that list'smember_count. -
Noise leaves the suggestions when the file's change holds anything else, a change only position reaches included: view state, which a tag or attribute on the way names (the cursor, a selection, as VYM's
<select>, the layer, frame, view or colour the app has current, as Pencil2D'scurrentLayer, scrolling, zoom, the viewport, windows, splitters, a list or tree view mirroring the document, as Umbrello'slistview, Inkscape'snamedview, view and configuration settings, the printer, a save's history, edit time, editing duration and cycles, last-saved time, the generator and document statistics, and a preview or thumbnail the app renders, as Xournal++'s<preview>), or the value of a name-like attribute orkeyon the way, when it is one token of several words, as LibreOffice'sconfig:config-item[@config:name=CursorPositionX]inside[@config:name=ooo:view-settings], or a settings path ending in how a panel shows (visible,hidden,expanded,geometry,sizeand the like), as VYM's<setting key="/treeeditor/visible">; an identifier the app numbers by position. AnIdsuffix alone does not show that an attribute is generated, so relationship fields such asaccountIdare retained; other save-generated IDs may need manual review; volatile values, UUIDs and epoch times, binary data encoded as base64 (a PNG'siVBORw0KGgo, or 256 or more base64 characters mixing cases and digits), an id counter (uniqueid,nextobjectid), a value equal to an id the change gave a new element where a counter or that element holds it, as Umbrello'slastobjectholds the new attribute'sxmi:id, but never a reference on an element both ends hold, as a KMyMoney split moved to a new account, and durations and counts under a document's metadata; times the app stamps, also as a date written in parts whose tag, or one it sits in, names when the app made, changed or saved something, as Tellico's<cdate>of<year>,<month>and<day>(cdate,mdate,created,modified,saved,updated,entered), or written whole under such a tag, as GnuCash's<trn:date-entered><ts:date>2026-11-06 08:53:08 +0000, which no check ever reads, while a date a user enters in parts, as a birthday, is an edit like any other; an id the app assigns, anidelement withtype="guid", as GnuCash's<trn:id>, and text of control characters, as its unset options hold;upkeep, a change inside an app'sslots, as GnuCash'sbook:slots, which it adds to and fills on reopening a book, a count an app keeps of its members, an attribute whose key ends incountor an element whose tag names a count, whose integer changed and at each end equals how many elements of one tag it, or the element holding the text, holds there, the counter left out, as VYM's rootbranchCount, KMyMoney's<TRANSACTIONS count="5">or GnuCash's<gnc:count-data>, or, for a counter the change added, how many of one tag, more than one, it holds as children, as FreeCAD's<ObjectDeps Count="2">, while a count a user enters, as an item's<count>, is an edit, and theloext:tab-stop-distanceattributes LibreOffice fills instyles.xmlwhile another entry holds the edit, and, while a change outside them is the edit, any change instyles.xml, in a document's style containers or its master, handout and notes pages, or to an attribute naming an automatic style (P1,MT1) at both ends, as Impress after a reload wraps its master pages' fields in a newMT1style and rewrites list styleL2, and a text showing another field that changed alike, its element beside a field naming that one's key, as QElectroTech's<dynamic_elmt_text>shows a component's label in its<text>beside<info_name>label</info_name>; andlayout, attributes holding a list of numbers, as Dia'sobj_bb6.3225,6.15;12.5775,8.15, or a number a name on its way calls layout (a position, point, box, corner, width, height, side or offset, aselem_width), that changed in an element up to three levels below one whose text changed, below the root's own children, while their own element's text did not, as Dia moves and resizes a box around its new text, or below an element whose tag names a diagram, widget, canvas or scene while the model change is elsewhere, as Umbrello grows a class'sclasswidgetheightwhen an attribute is added; an order's<amount value="12"/>beside its new description is content. Geometry that changed with no text beside it, as a CAD sketch's, is content. However much noise comes first, it never takes a check's place. When the change holds nothing else, the checks stay and a note flags them: up to three in a document, and as many as fit in the app's own state, where a view setting may be the task. -
Whitespace an app lays out between elements is not checked, and values that look like a time the app stamps, in ISO 8601, as C's
asctimewrites it (Thu Nov 5 09:20:31 2026) or as a time of day to the second (05:16:21.675302540), are left out; noise never makes its ancestor's whole text a check.
-
guards: checks an author may add to show the edit left the rest alone, kept out oftask.checks, with idsguard-1and on: for each suggestedvalue_equalsortext_equalson a member of an XML list, or avalue_equalsthrough a JSON record added whole, as TreeLine's new book'sdata/Authorgives/nodes/[format=BOOK][>data~1Title=Small Engines of Life]/data/Author(its like records are those of its kind, named as both ends pair them, and it reads only the check's own field), a field on up to two like siblings, those sharing most of the attributes its selector names it by and its type, as a cell'sRoworColandValueType, then the nearest first, at its seed value, which the end holds too, trying at most the 16 nearest, so a guard costs the same among thousands of siblings; for a member named as a field, by a name-like attribute alone, as QElectroTech'selementInformation[@name=label], the like siblings of the nearest record out that hold that field too come first, so the other components' labels guard a component's label rather than its other fields: the check's own field where like siblings hold different values in it, and otherwise one that varies among them, as Planner'sworkrather than thetype="normal"every task shares, but not one numbering the members in order, as MyPaint'ssrc; never through a sibling only position pairs. A guard never selects its sibling by what it reads: a sibling its selector names by the attribute the guard checks, or by a value that is or holds the text it reads, as Glade's[>object=CancelTrueTrueTrue]for a button'sCancellabel, is named by another identifier, name-like attribute or child's text it holds alone at both ends instead, as Tiled's objects by[@id=2]rather than thenamethe guard reads, or Glade's button by[>packing=TrueTrue0], or else the guard reads another field, or there is none. Atext_equalswithtext_attributereads its siblings the same way. A text file gets alines_presentof the nearest two unchanged lines of its first new line's kind, list items with the same marker, as Zim's other[ ]action items, or lines the same command opens, as the other headings after LyX's\begin_layout Section, or lines of the same keyword orkey:in other records, as a gettext catalogue's othermsgstrlines rather than the edited entry's ownmsgid, or else its nearest lines with words in them; each text both ends hold once, never a line with no text of its own, a command such as\end_layout, tags alone such as<fonts>or</node>, or a key alone such as a RedNotebook day's24:, nor a line that repeats; an item placed by its own position that an edit added, atext_containsof each of up to four kept items' text, nearest first. Where members were only edited, amember_countof the innermost list holding the edited element that an edit goes through by a member with like siblings, a record rather than a field of one (a member named by a name-like attribute alone that the record's like siblings hold too), of the edited member's tag and kind, at the seed's number, as Dia's boxes in their layer, MyPaint's layers in their stack or Glade's buttons in their button box: no member of the kind added or removed; never of runs in an element a text check reads, and only where the end holds the same number. A guard of a coordinate comes after those of text. At most four a file besides the count guard, which sits outside that cap, so sibling guards never squeeze it out. They pass on the seed, which the export allows, since one check failing there is enough.notes: whyfilesis empty, or why a file's suggestions are thin. Each note has acodeand amessage. For a draft with no files:no_changewhen no file under the home changed between the two checkpoints and nor did the application's dconf keys;unowned_changeswhen files changed but none under the owned paths, with up to five of them infiles, each with its home path and role (desktop services' files last), and how many more inmore;uncheckable_changeswhen the application's keys in a dconf database changed, with the database infiles: no check reads dconf, and the export only writes the keys as they were at the seed checkpoint, so make the task's change one the app saves in a file;unreadable_changeswhen a dconf database changed but REA could not read it, with the read failure inerror(installdconf-clior setREA_DCONF_COMMANDwhen it is missing), since whose keys changed is then unknown; andno_documentwhen no file has the document role, as when no window title shows the file's name. A capture again with--state-pathnaming the file, orstate_pathson a build computer'sstart_build_capture, makes it the app's. For a drafted file, named infile:by_positionwhen changes reach their element only by position, the tags no attribute names alone inelements, or when only the elements' order changed;view_state_onlywhen every change is noise, the checks kept inpointers;long_textwhen an element's text changed past the 2,000 characters a text check compares, the elements inpointers;cut_viewwhen the views hold only part of the file;unreadable_viewwhen no check can read it, as a document REA reads as binary;volatile_lineswhen a text file gained lines holding a value the app generates with no text before it to count them by;generated_referencewhen an array references a newly generated record ID that cannot be checked exactly across runs, with up to five affectedpointers; andread_as_textwhen a text file opens like XML, with a declaration, a DOCTYPE or an element, but reads as text because its DOCTYPE has an internal subset or it is not well formed, so its checks compare lines that hold attributes in the order the app wrote them.
The suggestions are a starting point. Keep the ones that say what the user
did; drop the ones that only follow from how the app saves its file. Read
values for what changed and its pointers, and refers_to for what a number
stands for, before writing the prompt and any check of your own. A LibreCAD
capture that added four holes to a drawing rewrote it from DXF R12 to DXF 2007,
so most of its lines changed; the draft suggests counts of SECTION, ENDSEC
and CIRCLE, and only CIRCLE going from 5 to 9 is the user's change.
The task file
{
"name": "add-milk",
"prompt": "In Mousepad, add buy milk to my to-do list and save it.",
"checks": [
{
"id": "milk",
"kind": "lines_present",
"file": "Documents/todo.txt",
"lines": ["buy milk"]
}
]
}
name is a portable bundle name other than smoke (lowercase letters, digits
and hyphens); prompt is what the user asks for, naming the app, since a
solving agent's own shell cannot see the computer's files and the app's name
sends it to the screen; checks holds 1 to 32
checks. Each check has a kind, a file given as a path under the home such as
Documents/todo.txt, and an optional id (check-1, check-2 and so on by
position otherwise). A check reads the typed view data/get returns for that
file, in the shapes of the state view.
| Kind | Fields | Passes when |
|---|---|---|
file_present |
The file exists | |
file_absent |
The file does not exist | |
lines_present |
lines |
Every listed line is a whole line of the text view |
lines_absent |
lines |
None of the listed lines is |
line_count |
exact or contains; equals, at_least or at_most |
The number of lines equal to exact, or holding contains, compares so |
value_equals |
pointer, value; optional tolerance |
The pointer names a value equal to value, or within tolerance of it |
value_absent |
pointer |
The pointer names no value |
text_equals |
pointer, value; optional normalize_whitespace, text_attribute |
The XML element's whole text equals value |
text_contains |
pointer, value; optional normalize_whitespace, text_attribute |
The XML element's whole text holds value |
member_count |
pointer; optional matching; equals, at_least or at_most |
The number of members of the array, or of those matching names, compares so |
row_present |
table, where |
A row of the SQLite table has every column in where equal |
row_absent |
table, where |
No row does |
row_count |
table; optional where; equals, at_least or at_most |
The number of rows, or of rows matching where, compares so |
The rules both evaluators follow:
- An absent file reads as empty: no lines, values or rows. So
lines_absent,value_absentandrow_absentpass on it and a count of lines or rows is 0;member_countfails, since its pointer names no array. - A file that has no view (binary, over the read limit, or unreadable) fails
every check but
file_presentandfile_absent. Line checks need a text view, row checks a SQLite view and text checks an XML view, in a file or an archive entry; another format fails. - Equality is JSON equality: numbers by value, never a boolean for a number, objects by their keys whatever their order, arrays in order.
- Pointers follow RFC 6901 (
~1for/,~0for~) and must start with/; on an array a segment may be a selector, below. Key files read as{"groups": {"GROUP": {"KEY": "VALUE"}}}, so a setting is/groups/GROUP/KEY; XML as{"root": {"name", "attributes", "children"}}; SQLite as{"tables": {"TABLE": {"columns", "rows"}}}; a zip archive as{"entries": [{"name", "format", "view", …}]}; a gzip file, such as a Gnumeric.gnumeric, as the document inside it, so its pointers are those of the same document uncompressed. - With
tolerance, a non-negative number,valuemust be a number, and the value found passes when it is a number, or a string that is a decimal numeral such as"5.0000000000000000", at mosttolerancefromvalue.tolerance: 0compares numerically, so"1.2500"equals1.25. Anything else fails as not a number. containsmatches a line that holds it anywhere, case and all. Checks take no regular expressions: a pattern run on what the agent wrote could stall the verifier, and JavaScript and Python read some patterns differently.- A check decided by what a view leaves out (
lines_absent,value_absent,row_absent, the counts,value_equalsof a whole object or array, and the text checks of an element) fails when that view is truncated; a check satisfied by what the view holds is decided on it. An element's text is not: the reader may have dropped text from between the runs it kept, sotext_containsfails on a truncated view even where the kept runs hold its value. A text node or attribute value is whole, and decided.
The export refuses, naming the check, a file outside the owned paths, a file
spelled with a leading slash, . or .., a pointer into a file that is text
at the seed, as the bundle ships it, or at the end checkpoint, the empty
pointer (the whole file) except in member_count, a matching that is no
selector, a value that names the captured home, or the
capture host's name or user@host anywhere (which the
export removes from the seed), a
tolerance with a value that is not a number, an empty text_contains
value, or one of only whitespace with normalize_whitespace, an empty
text_attribute, a count without exactly one of equals, at_least and
at_most, and a value that looks like a date and time, in ISO 8601 or
asctime's form. Other volatile
content, such as a
counter the app bumps on every save, is for the author to leave out.
Pointers and selectors
An app that rewrites a document moves things: FreeCAD 1.0 records more properties than 0.21, so a hole's radius sits at another position among its siblings, and a plate's entry moves once a hole is added before it. A pointer segment applied to an array may therefore name a member by what it holds rather than by its position:
-
[KEY=VALUE]names the member whose string fieldKEYisVALUE, such as a zip entry by[name=Document.xml]or an XML element by its tag,[name=Object]. -
[@KEY=VALUE]names the member whoseattributesholdKEY="VALUE", as the XML view keeps an element's attributes. -
[>PATH=VALUE]names the member that holdsVALUEat a path of one to three/-separated steps inside it.>reads XML and JSON differently:Form In an XML element In a JSON record [>CHILD=VALUE]a child element CHILDwhose text, every text node in it with each run of whitespace collapsed to one space and none at either end, isVALUEthe string field CHILD, compared whole[>A/B=VALUE], up to[>A/B/C=VALUE]a child Awith a childBof that textthe string at member.A.B, compared whole[>A@ATTR=VALUE],[>A/B@ATTR=VALUE]a child A(or its childB) whose attributeATTRisVALUEthe field named A@ATTR:@is part of a JSON keyRecords that keep their fields as elements are named by a child's text: Osmo's
<contact><first_name>Delphine</first_name>…is[name=contact][>first_name=Delphine]. A GtkBuilder<child>is named by the id its widget carries,[name=child][>object@id=submit_button], and a TreeLine node by a field of itsdata,[format=BOOK][>data/Title=The Silk Roads]. Any element on the way will do; a path with more than three steps, or an empty one, is no selector; and a missing step, or a value at its end that is not a string, matches nothing. -
Conditions can follow one another,
[name=Object][@name=Hole], and all must hold. They compare strings only, the selector is escaped like any pointer segment, so a path's/is~1inside a pointer,/nodes/[format=BOOK][>data~1Title=The Silk Roads], and a/in a key cannot be a step; a value cannot hold][. A JSON field whose key begins with@or>is selected through a path,[>@type=Book].
A selector must name exactly one member. When several match, or none does
before the last segment, the check fails and says which selector matched what.
When the last segment's selector matches none, the pointer names no value:
value_absent passes, which is how a check says an element was removed, and
value_equals fails. On an object, a bracketed segment is an ordinary key.
A FreeCAD task that checks the user cut a 10 mm hole through the plate, in FreeCAD 0.21 and 1.0 alike:
{
"name": "cut-hole",
"prompt": "In FreeCAD, cut a 10 mm hole through the middle of the plate and save it.",
"checks": [
{
"id": "cut",
"kind": "value_equals",
"file": "Documents/plate.FCStd",
"pointer": "/entries/[name=Document.xml]/view/root/children/[name=Objects]/children/[name=Object][@type=Part::Cut]/attributes/type",
"value": "Part::Cut"
},
{
"id": "radius",
"kind": "value_equals",
"file": "Documents/plate.FCStd",
"pointer": "/entries/[name=Document.xml]/view/root/children/[name=ObjectData]/children/[name=Object][@name=Hole]/children/[name=Properties]/children/[name=Property][@name=Radius]/children/[name=Float]/attributes/value",
"value": 5,
"tolerance": 0.01
}
]
}
Before the edit the archive has no Part::Cut and no Hole, so both checks
fail on the seed; after it, Radius is "5.0000000000000000" and both pass.
A check decided by what a view leaves out depends, in an archive, on the entry
its pointer goes into: it fails when that entry's view is missing or
truncated, or when the entry is not listed and the listing is truncated. A
selector must see every member to know it names one, so a check whose pointer
searches an array the view may hold only part of fails as undecided: any array
of a truncated XML view, of a truncated entry's view, or the listing when it
is cut. A position is decided on what the view holds. Like a file, an entry
that has no view (binary, not read, or a database or archive inside the
archive) fails every check into its view; its null there is no value, while
an entry holding the JSON document null reads as null. Checks on its name,
size, sha256 and format still read.
XML that uses namespaces is named as the document spells it, in both
evaluators: an element or attribute by its prefixed name, such as text:p
or inkscape:label, an element in a default namespace by its bare name, such
as svg, and namespace declarations as the attributes xmlns and
xmlns:PREFIX. Text is a string among an element's children. These checks
hold a poster's headline in an Inkscape SVG and a date in a LibreOffice Writer
document:
[
{
"id": "headline",
"kind": "value_equals",
"file": "Pictures/poster.svg",
"pointer": "/root/children/[name=g][@inkscape:label=Layer 1]/children/[name=text][@id=headline]/children/[name=tspan]/children/0",
"value": "Summer Bake Sale"
},
{
"id": "date",
"kind": "value_equals",
"file": "Documents/flyer.odt",
"pointer": "/entries/[name=content.xml]/view/root/children/[name=office:body]/children/[name=office:text]/children/[name=text:p][@text:style-name=Subtitle]/children/0",
"value": "Monday 24 November"
}
]
Inkscape writes a line of text as a tspan inside its text element, and
Writer keeps a paragraph's style in text:style-name. A prefix is the one the
document uses, so a check names the prefixes the app writes.
Whole text
A text node is only the stretch of text between two pieces of markup, and
apps split a paragraph into runs as they save it. AbiWord wraps text typed
into a reopened document in c elements, so changing a time to 3:30 saves
<p>The team meeting starts at <c>3</c>:<c>30</c> in room 4.</p>; Writer,
which records revision IDs by default, writes text typed in one session as
text:span runs. A value_equals on the paragraph's children/0 then reads
"The team meeting starts at " and fails. text_equals and text_contains
read the element's whole text instead:
{
"id": "time",
"kind": "text_equals",
"file": "Documents/meeting.abw",
"pointer": "/root/children/[name=section]/children/[name=p][@xid=3]",
"value": "The team meeting starts at 3:30 in room 4."
}
- The pointer names an element of an XML view, in a file or in an archive
entry's
view, or a text node or attribute value in one, whose text is its value. Another format, a pointer into an archive outside an XML entry's view, or any other value fails, an element'snameand thedoctype's fields included: they are strings of the view, not text of the document. - An element's text is XPath's
string()of what the view keeps: every text node in it and in its descendants, CDATA included, in document order, with nothing between them. The view drops whitespace-only text between child elements only in an element that holds no other text, outsidexml:space="preserve", so the space between two runs of a paragraph is part of its text. Elements that stand for characters, such as Writer'stext:s,text:tabandtext:line-break, add none. text_equalspasses when the text equalsvalueexactly, case and all;text_containswhen it holdsvalue, as a time within a sentence, so the check holds whatever the rest of the sentence says.normalize_whitespace: truecollapses each run of XML whitespace (space, tab, carriage return, line feed) in both the text andvalueto one space and trims both ends before comparing, as XPath'snormalize-space()does, for an app that reflows text or a value written across lines. A no-break space is not whitespace there.text_attributereads text an app keeps in an attribute: the element's text is then that attribute's values on it and its descendants, joined in document order, and its text nodes are left out. Scribus keeps a text frame's text as<StoryText><ITEXT CH="Spring "/><ITEXT CH="Fair"/></StoryText>, oneITEXTper run of formatting, so the check below readsSpring Fairhowever the runs are split. The pointer must name an element; a text node or attribute value fails.
{
"id": "headline",
"kind": "text_equals",
"file": "Documents/flyer.sla",
"pointer": "/root/children/[name=DOCUMENT]/children/[name=PAGEOBJECT][@ANNAME=Headline]",
"value": "Spring Fair",
"text_attribute": "CH"
}
Counting members
member_count counts the members of the array its pointer names: a JSON
array, or an XML element's children. matching, a selector of one or more
conditions as above, [>PATH=VALUE] included,
counts only the members it matches, however many; without it every member
counts. An element's children hold its text nodes too, so give matching
for XML, such as [name=entry]. A Tellico task that adds one book to a
collection of 12:
{
"id": "one-more-book",
"kind": "member_count",
"file": "Documents/books.tc",
"pointer": "/entries/[name=tellico.xml]/view/root/children/[name=collection]/children",
"matching": "[name=entry]",
"equals": 13
}
It takes exactly one of equals, at_least and at_most. The empty
pointer "" names the whole file, for a JSON file whose top level is an
array. A pointer that names no array, an absent file's included, fails and
says what it found there. Like the other counts, it is undecided on a view
that may hold only part of the array.
Validation and export
rea export-desktop-application <capture> <directory> --task <task.json>
evaluates the checks on the capture's state view: on each file as it was at
the seed checkpoint, which is the seed, as the bundle ships it with the
capture host's name and user removed, and at the end checkpoint, the
captured end state. Those are the capture's first and last checkpoints unless
--seed-checkpoint and --end-checkpoint name others.
It refuses a task whose every check passes on the seed, and a check that fails
on the end state, with that check's reason. Only a failure on what the seed's
view shows counts: a task whose checks fail on the seed only because a view
could not be read or may have left out what they look for, such as a line past
the 5,000 a text view keeps, is refused, naming each failure. A capture yields
one task, so the option is taken once. The result's task names the task, its
number of checks and those that fail on what the seed shows. A refused task
writes no bundle.
The bundle then holds, besides the app and tasks/smoke.json:
| Path | Contents |
|---|---|
tasks/<name>.json |
Deploy the app, deploy the agent and its skills, record the screen, prompt the agent, run the verifier, stop recording |
artifacts/<name>-check/verify.py |
Reads the app's data/get through the computer and evaluates the checks, one result per check |
evals/<env>.toml |
The task, so a run of the bundle runs it; the smoke task runs with --task smoke |
The verifier finds the app among the computer's child environments, calls its
data/get under the run's grant of the computer's control token, and reads
the state data part. It reads again until the app's files settle, as
grading once the app has saved
describes, and each result's settled says whether they did, and read_cut
whether the bound cut a read still running. It raises when the app cannot be reached, so the run
fails rather than scoring the agent. The README lists the task, its prompt, its
checks and the recipe below.
Solve rate
REA does not run agents. To measure how often an agent solves the task, run it
k times, three by default, and count the runs whose summary line says
passed:
for run in 1 2 3; do
agent-env run ./mousepad-bundle --task add-milk --agent claude-code --model MODEL
done
The agent defaults to Claude Code and the model is the caller's choice (Claude
Sonnet 5.5 is a reasonable reference). The agent calls a model endpoint, and
agentenv refuses the run without one: set LITELLM_BASE_URL and
LITELLM_API_KEY where the runs start, or base_url and api_key under
[model] in .agentenv/config.toml. A run reported as scored below 1
failed the checks and counts as a miss; one reported as failed did not finish,
as when the computer or the app could not be reached, and is run again rather
than counted. Each run's instance keeps the verifier's results per check under
verifications.