Fileaify agent API — Executor — The workspace executor: SSH destinations
Part of the Fileaify agent protocol. Start page and index: /agent. Everything in one document: /agent/full.
Executor
The executor is a small server dedicated to the workspace that uses the workspace's declared SSH keys for the agent, so the key never reaches the agent's machine or conversation. A human starts it from the workspace header; the time it may stay on depends on the plan (Solo 1 h per 6-hour window, Pro up to 12 h per day, Premium always on, Free none; the outgoing IP changes at every start unless a Pro or Premium owner turns on the "Fixed IP" option). Its only inbound door is the direct-SSH port range (TCP 41000–48999, one port drawn at each start, key authentication only; see below); the header shows its state (red, orange, green), remaining time and outgoing IP.
- Where the executor may connect is the
## Hoststable of the project's infrastructure document (infra.md, or the first document that has one):| ref | host | user | port | key | via | notes |(example row:| web1 | web1.example.net | deploy | 22 | vault://ovh/my-key | | prod |). Any document of the project may hold it:infra.mdwins when several do, otherwise the first file name alphabetically, and the others are listed inproblemsasmultiple_hosts_tables.keyis thevault://…reference of an SSH key file of the project (optional when the project has exactly one),viais therefof another row used as a bastion (same key). Only the owner-accepted text counts.GET /api/v1/projects/{project_id}/executor/targetslists the effective destinations, the document they come from and the rows that could not be used (problems). Humans may also declare a destination in the workspace (Services → Executor SSH targets) or byPOSTwith an optionaljumpobject. - Direct SSH into your project's sandbox:
POST /api/v1/projects/{project_id}/executor/ssh-accesswith{"public_key":"ssh-ed25519 …"}(create one withssh-keygen -t ed25519; without a key Fileaify generates a pair and returns the private key once). It needsexecutor.runand a ready executor (409 executor_not_ready/ssh_not_readyotherwise). The reply giveshost,port(drawn at random at each executor start, so always read it again),host_key_line(use it asknown_hosts, never accept an unknown key),expires_at(the end of yourexecutor.runpermission) andsession_id. Connect withssh -p PORT -i KEY root@HOST "command": you land in this project's folder inside the sandbox, nothing else is reachable.scp,sftpandrsyncwork and are not filtered; port forwarding and agent forwarding are refused. In a command,${vault://service/key}is replaced by Fileaify for that command only (it needssecrets.useon the key) and masked in what comes back, with a key file placed in a private folder when the secret is a file;${vault-agent://service/key}loads an SSH key into an ssh-agent for that command instead of a file. A reference inside single quotes is refused: use double quotes. The shell without a command is line-based (no full-screen programs). List your sessions withGET …/executor/ssh-access, end one withDELETE …/executor/ssh-access/{session_id}; MCP toolsexecutor_ssh_access,executor_ssh_sessions,executor_ssh_revoke. The masking is a safety net against accidental leaks: do not try to print or transform a secret. POST /api/v1/projects/{project_id}/executor/ssh-runwith{"host_ref":"web1","command":"…"}(or{"target_id","command"}) queues one non-interactive command. It needs the project permissionexecutor.runand asecrets.usegrant on the key's binding. Reading a job result (GET …/executor/jobs/{job_id}) needsexecutor.runtoo. The server key is pinned on first contact and never silently replaced: a changed key fails the command until a human forgets the pin.- If the executor is off,
409 executor_not_ready: ask for it instead of waiting.GET /api/v1/projects/{project_id}/executor/sandboxtells whether the account has one (executor.available,executor.state); the executor belongs to the workspace owner's account and is shared by all their workspaces, while every project keeps its own/workfolder. If available,POST /api/v1/projects/{project_id}/access-requestswith{"capability":"executor.start","reason":"…"}: a human accepts in the inbox and the executor starts at once. It grants nothing that outlives the start (409 executor_unavailablewhen the plan has no executor;200 already_runningwhen it is on). Do not poll in a loop:GET /api/v1/access-requests?wait=120keeps the call open until a pending request is decided or expires (0–120 seconds); withoutwait, read it no more than once a minute. Then retry. - Sandbox:
POST /api/v1/projects/{project_id}/executor/runwith{"command":"…"}(throughsh -c) or{"argv":[…]}(no shell), optionalenv({"TOKEN":"vault://service/key"}),network(default true),workspace(default true) andtimeout(seconds, max 300). Needs the project grantexecutor.run; every secret inenvconsumes onesecrets.useof its binding. The command runs in a disposable bubblewrap jail on the workspace's executor VM, never on the Fileaify server: read-only system, private/tmp, no vault, no Fileaify files, unprivileged user, no cloud metadata. A{{vault://…}}placeholder inside the command is refused (400): pass secrets byenv.GET …/executor/sandboxdescribes the jail and its limits: read it rather than guessing. /workis the project's persistent workspace inside the jail (clone, venv, builds survive between calls). It lives on the executor VM's disk: it is erased when that VM is terminated (stop on Solo, end of budget, plan change). Everything else in the jail is erased after each call. Use{"workspace":false}for a run that leaves nothing.- File transfer: through the direct SSH access above (
scp,sftp,rsync, unfiltered). Withexecutor/runalone, produce files with the command itself (heredoc, base64) or have the host pull from its repository. - Waiting for a result: add
"wait":120(seconds, 0–120) toexecutor/runorssh-runto receive the output in the same call; the request stays open on the server, which is cheaper than polling. Without it, read the job no more than once a minute. GET /api/v1/projects/{project_id}/executor/jobs/{job_id}returnsstatus(queued,running,done,failed),exit_codeandoutput. Output is truncated at 64 KiB and secret values are redacted. Redaction is a safety net, not a boundary.
If the executor is stopped (budget used up, plan change, user), pending jobs fail with executor_stopped; do not retry automatically. The same calls exist as MCP tools: executor_sandbox, executor_targets, executor_ssh_run, executor_run, executor_job, executor_ssh_access, executor_ssh_sessions and executor_ssh_revoke (same names as REST paths, same permissions). A connector obtains executor.run and executor.start through request_access; consent never grants them.