Repository navigation
feat: export a whole Agent from the dashboard, past the 25 MiB chat limit - #1136
Conversation
Copy https://move.finite.place/export.py byte for byte (sha256 29021b5325280f4db80fc407047b932edaabe1f8626042ba957131ea4bc39563, fetched 2026-10-29) so the dashboard export and the move guide run one exporter. Later commits format it and lift the chat-attachment limits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mechanical only: `ruff format` and `ruff check --fix` (datetime.UTC, unused noqa). No behavior change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The move guide sends its zip as a chat attachment, so the exporter drops media and binaries and any file over 5 MiB. A download has no such limit: `survey --everything` and `build --everything` keep both, while credentials, dependencies and build output stay out as before. Archives and installers still stay out of the workspace, because the files inside them can't be checked for credentials. The credential scan now reads text files of any size in chunks and carries each unfinished word into the next chunk, so no match is split; a single line over 16 MiB can't be checked and keeps its file out. This also tightens the move guide's export, which left text files over 2 MiB unread. Media, archives and binaries are stored in the zip rather than deflated again, and the workspace never includes an earlier export folder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Documentation censusThis PR adds 1 new Markdown file and 73 lines of Markdown, and removes 0 Markdown files and 0 lines of Markdown. Net documentation change: +73 lines. Counts |
Add a finite-export Hermes dashboard plugin, packaged beside the Brain and Sites plugins. The owner's native Hermes grant starts a survey, then an export with the brains and sites they choose; the exporter runs in the background as its own process, never through the model or chat. The job's folder under the Finite Home records how it ended, so a Hermes restart cannot lose a finished export or report a dead one as running. One job runs at a time, a new export replaces the last, and a finished one is deleted after seven days. The download is a plain file response, so Range requests can resume it. The disk is shared with the host's other Agents. An export starts only when 20 GiB would stay free after its staging copy and zip, and a running export is stopped, and its staging copy removed, if free space falls below 10 GiB: the survey can't size sites or brains not yet on the Agent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent navigation gains Export. The page asks the agent's finite-export plugin what it has, lets the person pick brains and sites (their own brains and owned sites start chosen), starts the export, checks in every three seconds while it runs, and offers Download and Delete. The download is a same-origin route: the dashboard gets a grant from Core, fetches the zip from the agent and streams it through, forwarding only a single well-formed Range and the exporter's own file name. Requests from other sites are refused. Typing /export in web chat opens the page instead of reaching the agent; Hermes' own /export is CLI-only. The design fixture can now issue the native Hermes grant to a test's stand-in agent, and the browser test drives the flow end to end over HTTPS with a throwaway certificate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Who may export, where its state lives, its limits, what the zip holds and leaves out, and why it needs a Runtime rollout but is no backup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
28b26c2 to
ce2d618
Compare
alexlwn123
left a comment
There was a problem hiding this comment.
Fail for now. The dashboard flow works, but the export's credential and shared-disk guarantees have reachable gaps at ce2d61801ba60eca66f1f14e6fb6fb35c55a3d75.
-
[P1] Apply the credential exclusion policy to every exported section.
export.py:1443–1452copies cron JSON and serializes prompts/output without filtering;export_memoriesalso snapshotsmemory_store.dbverbatim. Independently runningbuild --everythingon disposable fixtures preserved a known-pattern synthetic GitHub token in both cron files and the memory DB. The same token survived a UTF-16 workspace text file becausesecret_in_textreturns safe at the first NUL, and a compressed custom-skill archive because archive exclusion only applies to the workspace. These contradict the page/README's “passwords, keys and tokens are never included” promise and can move credentials to the destination importer. Cover these paths with a consistent filtering/redaction or exclusion policy and regression tests that inspect decoded/decompressed archive members. Any intentionally unfiltered content, including chats, must be explicitly disclosed instead of promising a credential-free zip. -
[P1] Include surveys in shared-disk protection.
plugin_api.py:197–201watches onlyexport, and_start_surveyhas no space admission check. A survey copies the entirestate.dbinto its scratch directory (survey_chats→open_sessions→snapshot_sqlite). With a large chat DB and insufficient remaining disk, simply clicking “Check what to export” can exhaust the disk shared by Agents before export admission runs. With free space mocked to zero, I observed survey admission return 202 andguard()leave its process alive. Apply admission/watch/cleanup to this disk-writing phase too; test low space before and during a survey. -
[P2] Stop the spawned process when persisting its job record fails.
plugin_api.py:164–174starts the process before writing/replacingjob.json, without failure cleanup. Injecting ENOSPC atjob.json.partleft the child alive,busy()false, and DELETE unable to kill it. Subsequent requests can start overlapping jobs while the original remains outside the disk watcher. On any post-spawn setup failure, kill and reap the process group and clean its partial state; cover both record write and rename failures.
Validation: 458 dashboard tests passed, one unrelated Brain deletion proof skipped on the preceding head; the delta only changes the Python contract test’s process-reaping assertion. The real dashboard browser test passed with synthetic account/Core/hosted-device fixtures and an HTTPS stand-in Agent; an independent extension passed repeated exports, cancellation, leave/revisit, and failed/interrupted recovery at 390×844 and 320×844, with no horizontal overflow. Desktop viewport: 1440×1000. Download bytes, Range, cross-site rejection, deletion and /export navigation passed. Python reproductions used the real exporter/plugin source with synthetic files, mocked CLI discovery/disk values, and disposable sleeper processes for lifecycle failures.
Current-head CI is still running. The preceding head failed the Packaged Agent export contract; the new commit changes its Linux process-reaping assertion. All three reproductions and the extended browser flow were rerun on the current head. Direct plugin checks also passed download/suffix Range/416, input validation, outside-path and symlink refusal, deletion and expiration. Local native-Hermes contract tests stopped before executing (hermes_state missing); Nix/package execution remains unavailable. Browser fixtures do not prove native Hermes authentication, packaging, or live Core integration. Screenshots were captured and inspected locally; authorized Drive publication remains blocked by unverified sharing access, so no inline image claim is made.
Review of #1136 found the credential promise held only for the workspace, and the shared-disk rules only for exports. Credentials: the exporter now handles them the same way everywhere. Scheduled jobs are redacted like memories, string by string in the decoded JSON (in serialized JSON an escape such as \n hides a token's start), and redaction removes a whole private key, not only its header. A SQLite database, memory_store.db included, goes in only as a copy rebuilt from its live rows with VACUUM INTO and checked value by value, so no deleted rows, free pages or write-ahead log come along; its -wal, -shm and -journal files stay out. Its read-only SQLite URI is escaped, so `#` or `?` in a file name can't open another file. Every other file is read as UTF-8, or UTF-16 with a byte order mark; archives stay out in every section, skills and sites included. Chats and media go as they are, and the page, the zip's README and the doc now say so instead of promising a credential-free zip. Disk: a survey copies the chat store, so it now starts only with room for that plus the 20 GiB reserve, and the low-disk guard stops a running survey as well as an export. A job whose record can't be written or renamed is killed and reaped at once instead of running untracked, and a full disk answers notEnoughSpace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Row actions only take pointer events while the row is hovered or focused. The agent creation browser test clicked Archive without hovering, and passed only because Playwright's retry scrolling happened to bring the row under the resting mouse. One more agent navigation item (Export) moves the row, and the click then times out. Hover first, as a person does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Note 🤖 Claude Opus 5.5 responding on behalf of AustinKelsay @alexlwn123 Thanks. All three findings reproduced, and all three are fixed in 69aec86, now at head 1576682. Each fix has a regression test that fails without it; I checked that by breaking the code on scratch copies.
Also in this push, 1576682 makes The native-Hermes contract test passes 8/8 against the plugin as packaged by |
alexlwn123
left a comment
There was a problem hiding this comment.
Fail for now — the prior reproductions are fixed, but the new SQLite credential check has one remaining leak. Reviewed 1576682a9129b11a3718aa79a820c5e4c4039dc3.
[P2] Check schema SQL before accepting a rebuilt database. export.py:418–426 enumerates user tables and scans their rows, but never scans sqlite_master.sql. VACUUM INTO retains column defaults, views and triggers. An otherwise empty app.db with a credential-shaped synthetic GitHub token in a column default or view therefore returns None from secret_in_database and is included by the real build --everything path. I restored the exported member and recovered the token through both SELECT from the view and an insert using the column default. This leaves the credential-exclusion contract incomplete even though live-row and deleted-page cases now pass. Scan the persisted schema SQL as well as table values, reject the database on a match, and add default/view/trigger fixtures that inspect the exported database.
Independently verified resolution of the previously reported concrete cases: cron JSON and whole private keys are redacted; live secret-bearing memory databases, BOM-marked UTF-16 credentials and skill archives are excluded; deleted SQLite bytes and stale WAL credentials do not survive; clean #, ? and % filenames resolve correctly; row IDs and an external-content FTS query survive VACUUM INTO. Survey admission counts DB+WAL, its low-disk guard kills the worker and removes staging, and both job-record write and rename ENOSPC failures return 507 with the worker killed/reaped and partial state removed.
Validation: nine focused dashboard tests and both tests in the complete agent-creation.browser.ts suite passed, including its updated hover-before-archive path. Real-dashboard browser checks passed at 1440×1000, 390×844 and 320×844, including repeated exports, cancel/revisit, interrupted/failed recovery, download/Range, and /export; no horizontal overflow. The changed notice is readable on mobile. I traced the archive action's hover/focus CSS and independently exercised hover → archive → restore through the real sidebar. Account/Core/hosted-device services and the HTTPS Agent endpoint were synthetic fixtures; native Hermes/authentication and packaging were not exercised locally. The current packaged Agent export CI step passes, per GitHub step metadata. Local native contract execution remains unavailable (hermes_state/hermes_cli missing). Screenshots are inspected and retained locally; Drive publication remains blocked by the previously unverified sharing access. No Nix or CI-log denial was retried.
VACUUM INTO keeps a database's schema, so a credential in a column default, a view or a trigger survived the rebuilt copy: the check read only table rows. It now reads every sqlite_master SQL statement first and leaves the database out on a match. Review of #1136 found it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Note 🤖 Claude Opus 5.5 responding on behalf of AustinKelsay @alexlwn123 Agreed:
The contract test passes 8/8 in source mode and against the packaged plugin. A real-dashboard run against the real Hermes web server and the packaged plugin exported 26 files, with none of 20 planted credentials found after opening every member, nested archive, UTF-16 file and database. |
alexlwn123
left a comment
There was a problem hiding this comment.
Pass for the reviewed fixes. At d7fcc45408a238e8c2eeeb19b0857385ce58a656, the schema scan resolves my remaining SQLite finding; I found no new blocker in this delta. Posting a COMMENT, not an approval.
Independently reran the original combined default/view reproduction plus separate credential-bearing defaults, views and triggers. The scanner rejects all four databases; compact_database removes rejected copies; the real build --everything path excludes them and records the reason in the manifest. Clean equivalents remain included and their defaults, views and triggers work after restoring the exported members. Exported member bytes contain none of the synthetic token, #/? filenames work, and source rows remain unchanged.
Also reran the prior credential, deleted-page/live-WAL, escaped-URI, row-ID/external-content FTS, survey admission/watchdog, and job-record write/rename ENOSPC checks successfully. These execute the actual exporter/plugin source with disposable files and processes; CLI discovery and disk capacity are mocked. No customer data was used.
Coverage limits: native Hermes dependencies (hermes_state, hermes_cli) are absent locally, so this is not packaged-runtime/authentication integration coverage. Current-head CI is still running. This delta changes only the exporter and its Python contract test; desktop/mobile export and hover→archive→restore browser coverage remains the prior execution at 1576682a9129b11a3718aa79a820c5e4c4039dc3, not a rerun on this head. No screenshots were published, and prior Nix, CI-log and Drive-access restrictions were not retried.
alexlwn123
left a comment
There was a problem hiding this comment.
Pass for this follow-up. No new findings at 815a3c060a8d7b733d7bf94ba4233514a94ca14f. The delta from d7fcc454 changes only the Python contract fixtures and compatibility documentation; production code and UI are unchanged.
Independently exercised the added filename collision cases through the real build --everything path: clean keys.db?draft retains its own rows even though keys.db contains a synthetic credential; credential-bearing plants.db?keys is excluded even though plants.db is clean. Manifest reasons, absence of leaked token bytes, and absence of stray source files passed. The prior schema/default/view/trigger, credential, WAL/URI, survey disk-limit and failed-job-persistence checks also passed again.
18 existing focused dashboard tests passed, plus an independent mocked-404 test through readExportStatus confirming the exact documented message: “This agent does not support this page yet.” Python checks used disposable data and real exporter/plugin code, with CLI discovery and disk capacity mocked. No customer exports or external service integration was involved.
Native Hermes/package tests remain unavailable locally (hermes_state/hermes_cli absent); current-head CI is still running. No browser rerun was needed for this test/docs-only delta: prior desktop/mobile evidence belongs to 1576682a9129b11a3718aa79a820c5e4c4039dc3. Prior Nix/CI-log/Drive restrictions were not retried, and no screenshots were published. Submitted as COMMENT, not approval.
Superseded findings verified fixed, not waived. Alex independently rechecked the original credential, survey-disk and failed-job-persistence cases and posted passes at d7fcc45 (review 5708755342) and current 815a3c0 (review 5714359243). Current-head CI run41077983130 passes all15jobs; independent Fable5.1 source/synthetic review of815a3c06 and retained34b30e29 approves, including sealed8/8tests and8caughtmutants. Maintainer reconciliation for the user-authorized release; this does not represent an Alex approving review or production qualification. Normal required checks and merge queue remain in force.
Summary
The prompt-driven migration flow delivers exports as chat attachments and stops at 24 MiB. This adds an authenticated Export page and background exporter, with a streaming, resumable download that has no attachment-size cap. Typing
/exportin web chat opens the page.The plugin packages the move-site exporter as
finite-agent-export/1, adds--everything, and keeps media and large workspace files. The manifest lists file hashes, exclusions and incomplete items. Native Hermes and the existing Agent-access grant enforce authority; this adds no Core, Runner or schema changes.Credential files and archives stay out. Memories and scheduled-job strings are redacted for known credential patterns. Other supported text and SQLite databases are checked; SQLite copies are rebuilt from live rows and their schema SQL is scanned. Chats, media and other binary files are exported unchanged, so this is not a credential-free archive. The page and archive README disclose that boundary.
One survey/export runs at a time. Admission checks and a running low-disk guard cover both phases; job-record failure kills the worker. Cancellation removes staging, completed downloads survive a Hermes restart, and completed exports expire after seven days. Exporting does not retire or delete the Agent.
Evidence
fits: falseabove 24 MiB.After: real retained dashboard + source-built Hermes/exporter on a synthetic Agent produced 1,373,919,185 bytes. Browser download and a transfer dropped at 512 MiB then resumed had the same hash as the on-Agent file. All 228 manifest hashes matched; none of 20 planted credentials appeared. This is local synthetic proof, not destination-import acceptance.
815a3c06: adds a regression for?in SQLite filenames and aligns older-Runtime documentation with the actual unsupported-page message. The packaged suite passes 8/8. The added test catches a credential-leaking URI mutant that the previous suite missed. Ruff and diff checks pass. Product implementation is unchanged fromd7fcc454.815a3c06in his follow-up review. Fable independently approves source815a3c06and retained34b30e29, including 8 packaged tests, 8 credential-leak mutation checks and 18 focused dashboard tests. All selected jobs in final CI run 41077983130 passed. Root dismissed the two superseded changes-requested reviews with reasons tied to the reproduced fixes and later pass reports; this is a maintainer reconciliation, not an approving review from Alex. Normal merge-queue checks remain. These are source and synthetic qualifications, not production verification.Merge Danger
Door: two-way
Dashboard and Runtime may ship in either order. An older Runtime shows “This agent does not support this page yet” and exposes no export controls. Reverting removes the feature, but existing exports remain on the Agent's disk and the old Runtime no longer expires them. Do not silently delete those owner files during rollback.
Blast Radius: dashboard/runtime
Every Agent navigation gains Export; downloads use dashboard bandwidth and Agent disk. A Runtime image rollout is required for the plugin. This is content portability, not identity/key backup or a Recovery Set. The move-site copy and destination upload/import page are outside this PR, and no destination-import compatibility is claimed. Agents that cannot start cannot use this path.
Contract: Agent export.