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 sshd and 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 sudo password, an editor, a pager, -i flags) 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. sudo works 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 needs curl or wget and 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.