DocsGuides
Files and shell
Each session has its own private directory on the Computer's runtime, with two parts:
files/is the session's working directory. The shell starts there and the agent writes its outputs there. File paths in the API are relative to it.attachments/holds files you add for the agent. They also appear inside the working directory asattachments/<name>.
Browser downloads and artifacts are stored beside them, in the same session.
Session files last as long as the Computer's runtime, including across a
restart of the Computer's services. They are not restored after
computer.stop or reattachment. Artifacts are tracked session outputs, not
backups: they belong to the active runtime, so save important files in your own
storage before you stop the Computer or destroy the session.
session.destroy keeps the session's directory on the runtime but out of
reach of the API. session.destroy(clean: true) deletes it, with its working
files, attachments and artifacts.
Files and artifacts
Files and artifacts overlap, but they aren't two views of the same bytes. The shell and the agent work in the working directory, which can change at any time. An artifact is a fixed copy with its own id: a file you uploaded, a browser download, or a file the agent named in a turn's final result. Uploads and agent outputs are copied into artifact storage. A browser download is stored once, in the session's downloads area, and listed as an artifact.
| Files | Artifacts | |
|---|---|---|
| Address | a path (out/report.csv) |
an id (art_...) |
| Best for | shell pipelines and the agent's own reads | knowing which turn produced a file, and downloading results |
| Lifetime | the session, while the runtime runs | the session, while the runtime runs; deleted by destroy(clean: true) and not restored after reattachment |
| Linked to a turn | no | agent outputs carry a turn_id; uploads and browser downloads belong to the session |
When a turn ends, each file the agent names in its result is checked and
copied into an immutable artifact tagged with that turn's turn_id, even when
the turn was canceled or ran out of time. A scratch
file the agent writes without naming it isn't registered. So you can list what
the agent delivered in a turn (the turn's events carry its turn_id), fetch
the bytes while the session exists, and keep them in your own store. The same
list also holds your uploads and the session's browser downloads.
Read and write files
# The working directory, as files:
session.list_files(pattern: "*.csv") # one directory level; pass path: for a subdirectory
bytes = session.read_file("out/report.csv")
# The session's artifacts:
session.list_artifacts # newest first
session.list_artifacts(turn_id: turn_id) # one turn's agent outputs only, without downloads or uploads
session.upload_artifact("logo.png", File.binread("logo.png"))
session.read_artifact(artifact_id)
session.download_artifacts_zip # all of them, as a .zip
Each artifact has a filename (for example filled_w9.pdf). Show and save it
under that name, not under its id.
Give the agent files
write_file puts a file in attachments/, not in the working directory
itself. The agent is told about new attachments when its next turn starts, or
when a paused turn resumes after your answer:
session.write_file("brief.md", File.read("brief.md"))
# The next session.agent.run sees "brief.md" as an attachment.
session.read_file("attachments/brief.md") # read it back
Use write_file for input documents the agent needs. The name must be a single
file name, with no directories. Attachments are separate from the files the
agent creates in its working directory, and they aren't artifacts.
Collect browser downloads
A download that finishes in a session's browser is registered with that
session as an artifact with created_by: "download". Your application reads it
through the session's artifacts; there is no separate claim step. A download
the Computer can't link to a session waits as an unowned download until you
move it into a session with computer.claim_orphan_download(guid:, session_name:) or discard it; computer.orphan_downloads lists them.
List without turn_id to include browser downloads. Only the agent's outputs
carry a turn ID, so a list or ZIP filtered by turn leaves out downloads and
uploads, even ones that arrived while that turn was running.
session.list_artifacts.each do |artifact|
next unless artifact.created_by == "download"
bytes = session.read_artifact(artifact.id)
# Save bytes in your application's own file store, under artifact.filename.
# Record artifact.id only after that save succeeds, so retries skip it.
end
To collect every output, include created_by: "agent" as well, and skip
"upload" so the inputs you sent don't come back as new outputs. Keep the set
of IDs you have saved for each session, and list again to retry failed saves
before you release the Computer. The end of a turn is a good time to collect,
but it doesn't prove every browser download has finished. These files stay on
the runtime until your application saves them.
The shell
Each session has its own bash shell. It starts in the working directory, with
$PINE_SESSION set to the session's name.
Run commands with exec
failed_with = nil
result = session.exec("make test", timeout_ms: 30_000) do |event|
case event.type
when :stdout then puts event.text # output, standard error included
when :error then failed_with = event.error&.dig("evalue") # the exit code, as text
end
end
result.stdout # everything the command printed
result.exit_code # 0 when it succeeded
exec runs one command per call and streams its output back as it runs.
Standard error is merged into standard output, so everything arrives as
:stdout events and in result.stdout; result.stderr stays empty. A command
that succeeds ends with result.exit_code set to 0. A command that exits
with another code ends with an :error event whose error["evalue"] holds the
code, and in the Ruby SDK 0.3.12 result.exit_code is then
nil. Every call runs in the session's same shell, so the current directory
and exported variables carry over from one command to the next. Pass cwd: to run in another
directory; like cd, it carries over too. timeout_ms: stops the command when
the time is up; without it, a command can run for up to 24 hours.
A dropped exec stream can't be resumed: sending the request again runs the
command again. exec raises PineSandbox::ConflictError (409) in two cases:
a person holds control of the session, or the session's shell was lost. Read
session.control_state to tell them apart: if no person holds control, call
session.recreate_terminal! and retry.
The stream sends the same 20-second keep-alive as the agent's events (see Agent: keep-alive).
For an interactive program (a REPL, an editor), run it without interaction
through exec: pass its input as arguments or a heredoc, or split the work
into a few exec calls. There is no interactive terminal for your code.
Outbound SSH and generated keys
The Computer includes the ssh, scp and sftp clients, for connections to
public hosts. It doesn't run an SSH server or accept inbound shell
connections. SSH from the shell leaves through the Computer's normal internet
connection, which can have a different country and IP address from the one
the browser uses.
For work by Pine's agent, keep long-lived private keys and certificates in your
own secret store. Send them through the run's secrets field, under names you
choose. Pine adds no SSH-specific secret name or format; the goal says which
credential to use. Send the expected host key or fingerprint too, and don't
turn off SSH host verification.
The agent can generate a new identity when you ask it to. That identity works for the current Computer runtime, but its private key isn't kept by default. Before you install the public key as a lasting way to sign in, keep another verified administrator path, unless you have already arranged secure storage of the private key outside the Computer. An ordinary artifact is no place to store a private key.
API reference
The routes are in the Computer API: files and artifacts
(/v1/sessions/{name}/files, /v1/sessions/{name}/artifacts/*) under Agent,
/sessions/{name}/exec under Shell and Tabs, and unowned downloads under
State and Recovery.
What persists across stop and start, including the browser checkpoint, is in Persistence.