Tunnel
SSH commands
How an agent runs commands and edits files on your computer, what each command runs as, and the layers that decide what it may do.
Once a computer is connected and an agent is allowed to use it, the agent gets the ssh_* tools. They
behave as if the agent had SSH'd in as the computer's run-as user: a real login shell, the user's own PATH and
profile, and nothing more than that user could do.
The same tools work for both kinds of computer AgentParley supports:
- Tunnel — the daemon dials out, so no port is opened (this section).
- Direct — you run an ordinary
sshdand give AgentParley a password or key, which it keeps in an encrypted vault.
The tools
| Tool | What it does |
|---|---|
list_ssh_hosts |
The computers this agent may use, with ids, names and which one is primary |
ssh_run_command |
Run a command and wait for its output: 60 s by default, 120 s at most. With async the command starts detached and the call returns at once; its exit code and the end of its output come back to the chat as a message when it finishes |
ssh_read_file |
Read a file, with line numbers and a content hash |
ssh_write_file |
Create or overwrite a file |
ssh_edit_file |
Replace lines, insert lines, or replace an exact piece of text. Line edits must quote the hash from the last read, so a file changed since then fails instead of being overwritten |
ssh_list_files |
List a folder |
ssh_delete_file |
Delete one file (no recursive delete) |
Every call appears in the chat as a tool step with its arguments and result, so you can always see exactly what ran.
What one command goes through
agent: ssh_run_command("cd /srv/app && git pull")
│
├─ AgentParley: host allowed for this agent? tool allowed?
├─ plugin middleware may inspect, rewrite or refuse it
├─ a one-operation token, scoped to this computer
▼
SSH egress ──▶ your daemon (its outbound connection)
│
├─ daemon: enabled? allowed by allow/deny_commands?
├─ daemon: restore this chat's shell, run as run_as,
│ save the shell state
▼
exit code + output ──▶ sshCommandCompleted ──▶ the agent
Each chat has its own shell
Every command runs as a fresh, non-interactive login shell, but each chat keeps its own working directory and
exported environment on the computer. A cd /srv/app or an export in one call is still in effect in the next call
from the same chat, and two chats never see each other's state. The first command in a chat says so in its result
("fresh shell"), so the agent knows nothing carried over.
This state is held in memory-backed storage on the computer (/dev/shm) and is cleared by a reboot, never written
to a real disk: an exported variable can be a secret.
What commands can't do
- No terminal. Anything that prompts (a
sudopassword, an editor, a pager,-iflags) waits until the timeout. Agents are told to use non-interactive forms:sudo -n,--no-pager,DEBIAN_FRONTEND=noninteractive. - No privilege switch. Commands run as the run-as user, full stop.
sudoworks only if you configured it for that user. - At-least-once results. If the connection drops mid-command the command may have run even though no result came back.
- Short commands only, unless async. A command still running after its timeout (120 s at most) is stopped
together with everything it started. Anything longer — a build, a scan, a download — runs with
async: it starts detached, writes its output to a log file on the computer, and reports back to the chat when it is done. Reporting back needscurlorwgetand outbound network access on the computer.
Limiting what agents can do
Four independent layers decide whether a command runs. Each can only narrow what the one before allowed.
1. Which agents can reach the computer. In the console, a computer is reachable only by the agents you allowed on it. An agent that isn't allowed doesn't get the tools for it at all.
2. Which tools an agent has. You can turn individual tools off for an agent, for example keep ssh_read_file and
ssh_list_files but remove ssh_run_command and the write tools.
3. Plugin middleware. A plugin with SSH hooks sees every remote operation before it is sent and after it ran:
sshConnectionOpening— once per computer per turn. Observe, log, run a setup command, or refuse.sshCommandSending— every command and file operation. Refuse it, or rewrite the command, path or content.sshCommandCompleted— every result. Redact or withhold output before the model sees it.
A team can, for example, refuse rm -rf anywhere, force --dry-run on deploy scripts, or keep secrets out of
command output. See Middleware.
4. Your computer's own policy. The daemon checks /etc/agentparley/config.yaml before every operation, on the
computer, regardless of anything AgentParley decided:
enabled: true # false: refuse everything (a kill switch)
read_only: false # true: refuse every file write, edit and delete
allow_commands: # empty means any command
- "systemctl status *"
- "journalctl *"
- "docker ps*"
deny_commands: # deny always beats allow
- "rm -rf *"
- "*shutdown*"
Patterns match the whole command line exactly as the agent wrote it, never the shell wrapper the daemon adds
around it. * matches any run of characters, including / and spaces, so rm -rf /* also matches rm -rf /etc.
Everything else must match literally. A refused command returns an error to the agent; nothing runs.
Because this layer lives on your computer, it holds even if your AgentParley account were compromised.
Many commands at once
A computer runs up to max_concurrent_operations (default 8) operations at the same time, across all chats and
agents. An operation beyond that waits for a free slot, and the wait counts against its own timeout: a command that
could not start before its timeout is never started late.