# Rho Agent Run

`rho agent-run` is a generic helper for running a single Pi prompt either:

- directly on the host, or
- inside the existing `rho run` Gondolin sandbox flow, or
- from recorded fixtures in replay mode for fast offline tests

The goal is to make scenario steps declarative without hardcoding `rho`, Pi extensions, and sandbox wiring into the scenario runner itself.

## Core Idea

You tell `rho agent-run`:

- which Pi user to run as
- which Pi provider/model to run with
- which prompt to send
- which Pi extensions to load
- whether to run through Gondolin
- which mounts and allowlisted hosts the sandbox should use
- whether to run online, record a fixture, or replay a fixture

The command then either:

- calls `./rho <user> <prompt>` directly, or
- generates a temporary `rho run` YAML and executes it

## Host Mode

```bash
./rho agent-run \
  --user user1 \
  --provider openai-codex \
  --model gpt-5.4 \
  --prompt "Say exactly: harness is working"
```

## Sandbox Mode

```bash
./rho agent-run \
  --sandbox \
  --user user1 \
  --provider openai-codex \
  --model gpt-5.4 \
  --prompt "Read /input/dataset/dataset.yaml and create /output/sum_prices.py" \
  --mount "$PWD/sandbox/two-console-demo/shared/datasets/<uuid>:/input/dataset:ro" \
  --mount "$PWD/sandbox/two-console-demo/shared/workspace:/output:rw" \
  --mount "$PWD/sandbox/two-console-demo/users:/app/users:rw" \
  --allow-host chatgpt.com \
  --allow-host auth.openai.com
```

By default, sandbox mode also mounts the repo root at `/workspace/repo`.

## Options

- `--user <name>`: Pi user to run as
- `--provider <name>`: Pi provider name, for example `openai-codex`
- `--model <id>`: Pi model id or pattern, for example `gpt-5.4`
- `--thinking <level>`: Pi thinking level
- `--prompt <text>` or `--prompt-file <path>`: prompt source
- `--extension <path>`: repeatable Pi extension paths
- `--sandbox`: run through Gondolin using `rho run`
- `--mount HOST:GUEST:MODE`: repeatable extra sandbox mounts
- `--allow-host <host>`: repeatable outbound host allowlist
- `--env NAME=value`: repeatable sandbox env vars
- `--auth-json-source <path>`: stage a Pi `auth.json` into the sandbox agent dir
- `--repo-guest-root <path>`: guest mount point for the repo, default `/workspace/repo`
- `--guest-cwd <path>`: sandbox working directory, default repo guest root
- `--repo-mount-mode ro|rw`: repo mount mode, default `ro`
- `--dry-run`: dry run sandbox execution
- `--print-config`: print the generated sandbox config

## Replacing Stage Workspace Script

The existing scenario copies a fixture into:

- `shared/workspace/sum_prices.py`

The intended next step is to replace that with a Pi-authored file generation step, for example:

```bash
./rho agent-run \
  --sandbox \
  --user user1 \
  --provider openai-codex \
  --model gpt-5.4 \
  --prompt-file ./tmp/prompt.txt \
  --auth-json-source /Users/madhavajay/.pi/agent/auth.json \
  --mount "$PWD/sandbox/two-console-demo/shared/datasets/<uuid>:/input/dataset:ro" \
  --mount "$PWD/sandbox/two-console-demo/shared/workspace:/output:rw" \
  --mount "$PWD/sandbox/two-console-demo/users:/app/users:rw" \
  --allow-host chatgpt.com \
  --allow-host auth.openai.com
```

Where `prompt.txt` says something like:

```text
Read /input/dataset/dataset.yaml and /input/dataset/mock/prices-mock.csv.
Write a Python script to /output/sum_prices.py that sums the price column and prints the result.
```

## Current Limitation

`rho agent-run` is generic, but Pi itself still needs whatever authenticated user state and model access are required for that user. In sandbox mode, you must mount the relevant writable paths explicitly so Pi can see the expected `users/` and `shared/` trees.

If you use `--auth-json-source`, `rho agent-run` copies that file into a temporary host directory and mounts it into:

```text
<repo-guest-root>/users/<user>/agent/auth.json
```

That makes Pi work with your existing credentials inside the sandbox.

## Security Note

`--auth-json-source` is a compatibility path for Pi's current auth model. It exposes the staged credentials to the guest process.

This is weaker than Gondolin's placeholder-based host secret substitution.

For `openai-codex`, there is an additional constraint today:

- direct model calls go to `chatgpt.com` with bearer auth in headers, which fits Gondolin's header substitution model
- OAuth refresh goes to `auth.openai.com` with the refresh token in the POST body, which Gondolin does not substitute by default

So a future fully non-readable credential path likely needs both:

- Pi support for a non-file credential path for `openai-codex`
- host-side request rewriting for refresh flows, or host-managed token refresh before the guest starts

Use it when you need Pi to work inside the sandbox today. For stricter isolation, the next step is to add first-class provider secret injection to `rho run` / `rho agent-run` and prefer env/API-key based provider auth where possible.

## Fixture Capture And Replay

Scenario configs can add a `fixture:` section:

```yaml
fixture:
  mode: online
  root: tests/fixtures/two-console-demo-agent-approval/agent-run
  key: agent2-generate-code
  outputs:
    - /output/sum_prices.py
    - /Users/me/dev/rho/main/sandbox/.../audit/agent2-generate-code.http.jsonl
```

Modes:

- `online`
  - run Pi normally
- `record`
  - run Pi, then save stdout/stderr plus the declared output files
- `replay`
  - skip Pi entirely, restore the declared output files, and replay cached stdout/stderr

Fixture layout:

- `tests/fixtures/<scenario>/agent-run/<key>/result.json`
- `tests/fixtures/<scenario>/agent-run/<key>/files/...`

This is intended for fast offline scenario tests where the protected host actions still run normally, but the expensive Pi steps are replayed from recorded fixtures.
