Connecting to External Resources
A project reaches PROOF Level 2 by being published, and Level 3 also needs its reproduction recipe and environment archived. Vaibify connects a project to the four places where that happens: GitHub for version control, Zenodo for permanent archives, Overleaf for the manuscript, and arXiv for the preprint. It pushes files to the first three, reads all four back, and compares SHA-256 hashes of every published file with the copy on disk, so an agent (or anybody else) cannot quietly change a file that has already left the machine without the dashboard saying so.
The last part of this page covers driving a vaibify hub on another computer over SSH.
What each connection is for
Service |
What vaibify does with it |
Direction |
Role on the ladder |
|---|---|---|---|
GitHub |
The project repository’s |
Push and verify |
Level 2 mirror; Level 3 checks that the reproducibility envelope on GitHub matches the local one. |
Zenodo |
Uploads selected files to a deposit and publishes it with a DOI; separately, archives the container image. |
Push and verify |
Level 2 archive; Level 3 requires the envelope, the rebuild attestation and the environment image in permanent (non-sandbox) records. |
Overleaf |
Pushes figures into the manuscript project, and reads the manuscript sources back. |
Push, pull and verify |
Optional at Level 2. |
arXiv |
Downloads the e-print source of a posted preprint and compares its figures with yours. |
Verify only |
Optional at Level 2; checked only once a submission is recorded. |
Requirement tables for each level are on The PROOF Ladder; what the envelope contains and how it is archived is on Reproducibility.
How vaibify holds your credentials
Every connection except arXiv needs a token, and the threat model assumes an AI agent works inside the container. The rules are:
No token is ever placed in an environment variable, a configuration file,
vaibify.yml,project.json, or a command line. Environment variables and command lines are visible to process inspection anddocker inspect; files in a repository get published.Tokens live in established credential stores on your machine. That is your operating system’s keyring, or your existing
gh authlogin for GitHub. A Zenodo token is kept in a slot of that keyring named for the project it belongs to (its randomsProjectUuidin the registry) and the instance, so two projects never share one and nothing inside a container can read it: every Zenodo deposit, publish and token check runs on your machine.Tokens reach programs through short-lived channels. Git is given a mode-700 helper script that reads the token at the moment git asks and is deleted afterwards. A GitHub credential the container itself needs is mounted read-only as a Docker secret file and handed to git by a credential helper only for requests to GitHub.
Error messages are redacted before they reach the dashboard, so a server that echoes a credential back does not put it on screen.
Where each token lives depends on the service and on the kind of project:
Service |
Container project |
Host-mode project |
|---|---|---|
GitHub |
Your host’s |
Same |
Zenodo |
The host keyring, one slot per project and instance |
The same |
Overleaf |
The host keyring |
The host keyring |
arXiv |
No credential needed |
No credential needed |
What the in-container agent may do
The agent drives the dashboard through vaibify-do, a curated and tightly controlled list of actions the agent may perform. Every action
is marked as agent-safe or researcher-only. Pushing to Overleaf, and
every Zenodo deposit, publication or metadata edit, are
researcher-only. Pushing selected files to GitHub is agent-safe, so in
a public repository the agent can make files public.
Action |
Agent may run it |
|---|---|
Push selected files to GitHub |
Yes |
Push to Overleaf |
No |
Publish to Zenodo, edit Zenodo metadata, Make Permanent |
No |
Verify any remote, refresh the Overleaf mirror, read the manuscript back |
Yes |
Configure arXiv, declare an extra Zenodo record |
Yes |
Pull a file from the container onto the host |
No. The agent authors the bytes and the write lands on your machine, so it asks you to pull the file from the dashboard or with |
Run vaibify-do --list inside a container for the complete catalog.
Setting up a connection
The first time you push to a service, the Setup Connection dialog asks for what that service needs:
Overleaf: the project ID (the string after
/project/in the project’s Overleaf URL) and a Git authentication token, generated in the Git integration section of your Overleaf account settings. Your Overleaf login password does not work.Zenodo: the instance (Sandbox, the default and the right choice for practice, or Production, which mints real DOIs) and a personal access token from that instance with the
deposit:writeanddeposit:actionsscopes. Sandbox and production are separate sites with separate accounts and tokens.GitHub: nothing; the dialog does not open for GitHub. Vaibify uses a token stored in the host keyring slot
github_token:<owner>/<repo>if there is one, and otherwise your host’sgh authlogin. When neither resolves, or the project repository cannot reach GitHub, the dashboard says “GitHub uses gh auth. Run ‘gh auth login’ on your host machine.”
Confirming where a token may be spent
The project ID and the Zenodo record a workflow names live in
project.json, which the in-container agent can edit. The copy vaibify
trusts lives on your machine, under ~/.vaibify/bindings/, in a record
keyed by the project’s own identity (a random sProjectUuid in the
registry, kept across renames) and by the workflow (its file path and
name). Only you write that record, one confirmation at a time, and it
holds three kinds of entry for each workflow:
Overleaf: the project your Overleaf token may push to and pull from.
The Zenodo project record, one entry for each site. On the site
project.jsondeclares, the entry names the record (concept DOI and deposition) that site’s deposits continue, andproject.jsonmust agree with it. On the other siteproject.jsonsays nothing, and the entry governs alone. An entry that names no record yet authorizes a new record there: promoting a project from the Zenodo sandbox (testing) to zenodo.org (permanent) needs exactly that.The Zenodo environment archive, one entry for each site: whether the container image may be deposited there. It names no record, is never compared with
project.json, and no project deposit touches it.
Notices and the review. When a project opens, a notice appears for
each entry that applies and that project.json disagrees with, and its
Review and confirm… button opens the review. An entry you have not
confirmed on this computer has no notice, so a project opened on a
second computer is quiet: an action that needs such an entry opens the
same review instead of failing. The exception is Auto Archive,
which asks nobody: while it is on, an unconfirmed Overleaf project or
project record it would send to carries a notice. The review sets
side by side what project.json names, what you confirmed and what
vaibify could see with your own token, and says what Confirm
authorizes. Where nothing names a record (a new record, or the
environment archive below), it says what confirming allows instead of
comparing. Confirm records
exactly that entry, and the refused action runs again, once. If the
project view changes while you are confirming, the review closes and
nothing is run again. A disagreement carries the sentence “If you did
not make this change, an agent edited project.json.”
The evidence, strongest first, is checked by the hub before anything is recorded:
a deposition your token can read;
a published record, whose title and owner are shown for you to recognize; a public record proves no ownership;
for a new record or an environment archive, that the site accepts your token;
for Overleaf, that your token can read the project, with its file count and latest commit. Overleaf’s Git access gives no title, so the review claims none, and links to the project instead.
With no token stored for the site, the review offers to store one and then checks again. Evidence that could not be fetched reads “Couldn’t check”, in neutral gray, and Confirm stays off until Retry succeeds.
On the host, vaibify bindings -p <project> prints the same review
and the exact vaibify do confirm-remote-binding ... sRecordKind=... sWorkflowKey=... dictTarget='{...}' command that confirms what it
showed. A confirmation carries what was reviewed: one made for another
workflow, or for a target that has moved since, is refused as stale,
and a bare sService=... with nothing reviewed is refused too.
Every token-bearing action (each Overleaf push, pull and mirror
refresh; each Zenodo deposit, publish, promotion and recovery; Auto
Archive) reads its target from that record and refuses with a coded
reason: BINDING-REQUIRED (nothing confirmed), BINDING-MISMATCH
(project.json disagrees, naming both values), ZENODO-TOKEN-MISSING
or BINDING-RECORD-INVALID. A workflow you move or rename has no
binding until you confirm again; the confirmation can carry the old one
across. After a project deposit vaibify made, including one it adopted
after an interrupted promotion, the project-record entry follows the new
deposition itself; an environment deposit never changes the record.
A record vaibify cannot read is never treated as “nothing
confirmed” and never converted. The record is written in schema version
2, and a file in any other form, including one written before November
2026, refuses every action, naming the file and the remedy: move it
aside (vaibify reads only <project id>.json) and confirm again. The
notice’s Move it aside button does that for you, as does
vaibify bindings --set-aside -p <project> on the host. Either one
renames the file beside itself to
<project id>.json.unreadable-<UTC time>, so nothing is deleted, and
each entry is confirmed again the first time an action needs it. Both refuse with
BINDING-RECORD-VALID while the record can be read, so a valid
confirmation is never moved, and neither is reachable from inside the
container.
Connect stores the token and immediately tests it against the service. If the test fails, vaibify restores the token you had before (or removes the new one if there was none), and the message says which happened. A connection that cannot be made because the container runs with network isolation is refused at once rather than after a timeout.
Zenodo tokens moved out of an older container
Before October 2026 a container project’s Zenodo token lived in the container’s own keyring, which the in-container agent could read and write. It now lives only on your machine, and every time a project is opened vaibify moves whatever Zenodo tokens its container still holds out of it, in a way that can neither lose a token nor put one to use behind your back:
Each container slot (sandbox, production, the older unnamed slot and the two snapshot slots) is read as the container user.
Its value is written to a pending slot of your keyring and read back; only an equal read-back counts. A value your keyring already holds active is recognized at once; a different value already on your machine is a conflict, and nothing is written or deleted until you choose.
The container copy is deleted only after the pending write is recorded, and a second read confirms it is gone.
The pending token is checked against Zenodo from your machine. A rejection, or no answer at all, discards nothing; an unanswered check is retried at the next open.
You decide. The dashboard’s banner shows each token’s state, how the check went and the titles of the deposits Zenodo reports for it, with Confirm: this is my token (it becomes the active token) and Decline: discard it. On the host,
vaibify do decide-migrated-zenodo-token sSlot=zenodo_token_sandbox sDecision=confirmdoes the same; the unnamed older slot also takessInstance=sandbox|production. Until you confirm, every Zenodo action refuses and says so.
The record of the move lives in ~/.vaibify/migrations/ and holds
slot names and states, never a value. Moving a token does not revoke
copies an agent may already have read, so the banner also asks you to
regenerate your Zenodo tokens (and your Overleaf token) on each
service’s account page; vaibify cannot revoke them for you. Answer it
with I’ve regenerated them, after which vaibify asks for each new
token, or Keep the current tokens. Either answer, with its date,
retires the notice, which returns only if a token is found in a
container again (a token held in a conflict is found at every open). On
the host: vaibify do decide-token-rotation sDecision=regenerated|kept.
No credentials volume is created for a new container; one that already
exists stays mounted until the move out of it has finished.
GitHub
The GitHub mirror is the project repository’s own origin remote.
There is nothing extra to create: initialize the repository, add a
GitHub remote, and push.
Sync → Push to GitHub lists the project’s files with their state. Choose files, give a commit message, and vaibify stages exactly those files, commits, and pushes. A retry after a failed push publishes what is already committed instead of stopping on “nothing to commit”.
The Repos panel’s push commits changes to files git already tracks. Untracked files are never swept in; publish a new file through the file selection above.
Before every push, vaibify asks GitHub which account the token belongs to and refuses unless that account name matches the owner named in the remote’s URL. A repository owned by an organization therefore cannot be pushed from vaibify; push it from a terminal with your own git credentials.
A container with no git author configured asks for one (Set Git Identity). The name and email are written into the project repository only, never into a global git configuration.
Verification compares against the repository the project last pushed
to from this hub, recorded at push time. A changed origin or a
project.json that names a different repository is refused rather
than trusted, because both can be edited from inside the container. A
project that has not pushed from this hub is asked to push once.
If commits are pushed outside the dashboard (for example by an agent
running git push), run Verify now so the
badges catch up.
Zenodo
Sync → Archive to Zenodo opens the push dialog with the project’s
publication files: step outputs, the scripts that made them, the input
data they read, test standards, the AI declaration, project.json, and
the reproducibility envelope. That is the same set the Level 2 and
Level 3 Zenodo checks compare. Edit
Zenodo metadata sets the title, creators, license and keywords.
Push Selected or Push All uploads the files and publishes.
Four properties of Zenodo shape what vaibify does:
Deposits are flat. Zenodo does not store directories, so files upload under their base names. A selection in which two files share a base name is refused before anything is uploaded, because the second would overwrite the first. Test files that vaibify generates carry a step-specific suffix so this never happens to them.
Published versions are immutable. Changing a published file means publishing a new version of the record. Each version has its own DOI; vaibify always records the version DOI, never the concept DOI, because the concept DOI resolves to whatever is deposited next.
The sandbox is not an archive. Sandbox records receive test DOIs, carry no preservation promise, and can be cleared at any time. A sandbox deposit can satisfy the Level 2 Zenodo check, but never Level 3. Nothing transfers between the sandbox and production, so Make Permanent on the affected row of the Project block publishes the current files as a new production record and keeps the sandbox identifiers in a superseded note.
Other records can hold your files. Zenodo’s own GitHub integration archives each GitHub release as a separate record. The Zenodo checks consult every record you declare, under View → Zenodo Status, and a file agrees when any declared record serves its exact bytes. A record that holds only a release tarball cannot be compared file by file.
When the recorded deposit and the instance the project is set to use disagree (for example, a production deposit and a project switched back to the sandbox), publishing is refused with two remedies: publish where the record is, or start a new record on the chosen instance.
Archiving the container image to Zenodo is a separate action on the Environment archive row, described in Reproducibility.
Overleaf
Every Overleaf operation runs on your machine with the token in your
keyring: a push copies the selected figures out of the container
through the confined reader and commits them from the host; a pull
clones the project on the host and lands the sources in the container
through the confined write, as the container user, inside the
manuscript directory. No program in the container ever receives the
token, and both act on the project you confirmed in the binding
record, never on an id the agent may have edited into project.json.
Sync → Push to Overleaf copies figures into the bound Overleaf project through Overleaf’s Git integration. The dialog:
asks for a target directory, chosen from a tree of the Overleaf project, and refuses absolute paths and
..;shows which files are new, which would overwrite a different remote file, and which are unchanged;
warns when Overleaf already holds a directory under different capitalization (Overleaf stores names case-insensitively but lists both spellings) and offers the existing spelling;
asks you to confirm with Overwrite anyway before replacing a file that changed on Overleaf since your last push.
Only figure formats travel to a manuscript, so Overleaf rows list figure files only. A successful push re-verifies the Overleaf row automatically.
Reading the manuscript back
The pull-manuscript action copies the Overleaf project’s .tex,
.bib, .bbl, .sty and .cls files into the project repository’s
.vaibify/manuscript/ directory. The copy is ignored by git, so it can
never dirty the repository; it exists so the in-container agent can
read the paper and check it against the project’s results rather than
answer from memory.
arXiv
arXiv offers no upload interface for tools, so vaibify only reads it.
Sync → Configure arXiv… records the preprint’s identifier (for
example 2401.12345). Vaibify then downloads the e-print source of
the latest version, extracts it safely, and compares each local
figure with the figure of the same base name. When a base name is
ambiguous, or a figure was renamed for the submission, add an explicit
mapping from local path to path inside the e-print. Removing the
identifier stops tracking. An untracked submission reads “not tracked”
and never blocks Level 2.
How vaibify checks the published copies
A verification downloads each published file, computes its
SHA-256, and compares it with the SHA-256 of the same file on disk
right now. It does not compare against MANIFEST.sha256: the manifest
is a Level 3 artifact, and this check asks whether the files you hold
match the files you published.
Service |
How the remote copy is read |
|---|---|
GitHub |
Each file is fetched from the pushed branch over HTTPS. |
Zenodo |
Each file of every declared record is downloaded. |
Overleaf |
A local mirror of the Overleaf project is refreshed through git. |
arXiv |
The e-print source of the latest version is downloaded and unpacked. |
Results are cached in the repository’s .vaibify/syncStatus.json, so
the dashboard can show them without contacting the services on every
refresh.
When checks run
When you open a project, and when the dashboard reconnects, it starts a check of every configured remote. Each badge pulses until its own answer arrives.
In the background, a loop re-verifies every configured remote of every loaded project every 6 hours. Its first pass runs a minute or two after the hub starts, and it resumes its schedule across restarts.
A result older than 24 hours is stale. It is shown as needing a check, never as passing.
A check that cannot finish (no network, a container with network isolation, a service that is down) is reported as uncheckable with the reason. It writes nothing, so the last good result stands, and it is never shown as a divergence: red means the bytes were compared and differ.
Forcing a check
Verify now on any row of the Published copies section of the Main tab’s Project block.
Sync → Verify Reproducibility, then Re-verify on one service.
The ↻ Refresh remote status button above the step list re-reads the git remotes.
From a command line inside the container,
vaibify-do verify-remote github(orzenodo,overleaf,arxiv).
What the results look like
Each Published copies row groups the files it covers: Differs from the published copy, Not on the remote, Not checked yet, Matching, and Not compared by vaibify. Each file row in the Steps and Project blocks also carries a small badge per configured remote, tinted by that remote’s state.
The Verify Reproducibility dialog shows one row per service:
Field |
Meaning |
|---|---|
Status pill |
Green dot: the last verification matched every file and is less than 24 hours old. Red dot: at least one file differs. Yellow half-filled dot: anything else, including never verified and stale. |
Summary |
|
Last verified |
How long ago the last verification ran. |
Re-verify |
Runs a verification of that service now. |
A line at the top summarizes all services (“not yet verified”, all in
sync, or how many files drifted across how many remotes), and a line at
the bottom says when the background loop last ran. Verify Manifest
checks the files on disk against MANIFEST.sha256 instead.
Automatic pushes
The Auto Archive checkbox in the project settings (the ⚙ button above the step list) pushes a step’s files that you have marked for Overleaf and Zenodo automatically when that step’s verification first brings the project to Level 1. It is off by default. A failed automatic push is logged and never blocks the step that triggered it; the Sync menu remains the way to retry. Auto Archive sends only to remotes you have confirmed on this computer, so while it is on, a notice names each one still to confirm.
Revoking credentials
Removing a stored token from the host keyring, and revoking it upstream where the service allows it, is done from the command line; see CLI Reference. Overleaf and Zenodo offer no revocation interface to tools, so revoke the token on the service’s own account page too, because a cleared local copy does not invalidate a token that may have been copied elsewhere.
A Zenodo token belongs to one project, so vaibify revoke zenodo and
vaibify credential delete zenodo take --project (or run inside the
project directory) as well as --instance.
Working on a remote machine
Vaibify can drive a hub running on another computer you reach with
ssh, such as a lab workstation or a departmental server. The
dashboard runs in the browser in front of you; everything else
happens over there.
A remote session is started from the command line (see CLI Reference). It opens one SSH connection, forwards a loopback port, starts or adopts a vaibify hub on the remote machine, and opens a signed-in browser tab. The remote hub listens only on its own loopback interface, so nothing puts vaibify on a network.
Three places
Once the hub is elsewhere, a file can be in three places:
Where |
What it means |
|---|---|
Observer machine |
The computer you are sitting at, running the browser. |
Execution host |
The remote machine running the vaibify hub. |
Execution environment |
The container, or the project directory in host mode. |
In container mode the execution host and environment are different filesystems; in host mode they are the same. While you drive another machine, the dashboard shows a REMOTE badge naming it.
Prerequisites
The same vaibify version on both machines. The helper refuses to drive a hub of another version and names both versions.
Vaibify on the remote user’s non-interactive PATH. A non-interactive SSH command does not read your usual shell profile, so an install that is activated there (a
pip install --user, a virtual environment, a conda environment) is invisible to it. The CLI Reference explains how to check and fix this.SSH settings belong in
~/.ssh/config. Proxy jumps, identity files, ports and usernames go there.You choose the project in the dashboard once the tunnel is up, which keeps project names out of a remote shell command.
When the connection drops
The remote hub is started detached and keeps running, so closing your laptop does not stop a pipeline. Its idle timeout is raised for remote sessions.
For 15 minutes, your session is held: the client keeps rebuilding the tunnel, and the hub keeps your session valid at least that long.
Back within the window, the dashboard reconnects. Output streamed while you were away is not replayed, but run state is reconciled by ordinary polling.
Back after the window, start the remote session again from the command line. If exactly one session lost its browser, it is handed back to you (“Picking up where you left off”). If several are waiting, you sign in fresh and choose.
A run is never interrupted by any of this. Losing a browser ends that browser’s authority over the project, never the project’s work.
Terminals
A dropped connection ends your shell, and the pane returns with a new one and says so. Closing the socket ends the recorded session and proves it ended, which is what lets vaibify report honestly whether a project is quiet. Anything that must survive a disconnection belongs in a step, not in a terminal: steps are durable and their output is recorded. Each remote shell’s banner names the machine it runs on.
Moving files
Download to this computer streams a file through the browser to the observer machine. It is what right-clicking a file offers.
Upload from this computer sends a local file into the execution environment.
Copy to execution-host path copies from a container to the remote machine’s own filesystem. It appears only when those are different places.
What a remote session does not change
Security. SSH encrypts the connection and proves you are the remote user. After that, the browser redeems a one-time capability for a session, claims the project, and passes the hub’s loopback Host and Origin checks exactly as it would locally, because through the tunnel it is a loopback client.
Host mode is still uncontained. Commands run with your full user authority on the remote machine, and PROOF Level 3 remains unavailable to a host-mode project.
Actions that hand the browser an address are hidden: the New vaibify window buttons on the hub and project-list screens, and Open in VS Code. Through a tunnel those addresses resolve to the computer you are sitting at.
Batch schedulers are not supported
Slurm, PBS and LSF are not (yet) supported. Putting sbatch in a step’s
command is not a workaround: the submission exits successfully while
the job is still queued, so vaibify would report unfinished work as
finished. A remote machine is one you run things on directly.
Troubleshooting
The messages a remote session prints when it cannot start or reconnect, and what to do about each, are in CLI Reference.