Penguin Orchestration
The penguin CLI is a thin client of the PenguinHarness server. Inside a harness agent session it reaches the same server that is running you, so you can orchestrate the platform yourself: list and create agents, start conversations with them, steer those conversations while they run, and query costs and scheduled tasks.
Before you start
If the user's message only invokes this skill (e.g. "use penguin-orchestration skill") without a concrete request, ask the user what they want to orchestrate. Read-only commands (project ls, agent ls, ls, logs, cost, schedule ls) are always safe; do not create agents, start sessions or send messages until the goal is clear.
How the connection works
- Inside a harness agent session (you, now): every command subprocess has
PENGUIN_API_URL, PENGUIN_API_TOKEN, PENGUIN_PROJECT_ID, PENGUIN_AGENT_ID and PENGUIN_SESSION_ID injected, so penguin commands automatically reach your own server with your project and agent as the defaults — no login step.
- Outside an agent (a human shell): the CLI attaches to the running local server via its lock file, or auto-starts one; the local
<data-root>/api-token file (0600) authenticates it.
- You are operating the same server that runs you: sessions and agents you create appear live in the web UI, where the user sees and owns everything you spawn.
- The injected token is admin-equivalent. Act accordingly: stick to what the task requires, and prefer read-only commands until a mutation is clearly needed.
Orient first
Before mutating anything, see what exists:
penguin project ls # projects on this server
penguin agent ls # agents in the current project
penguin ls --json # the project's sessions, with running state
--json on any listing gives machine-parseable output.
Command surface
penguin run -m <msg> [--project-id <id>] [--agent-id <id>] [--workspace <path>]
[--model-id <id> --provider <p>] [--approve <mode>] [--thinking <level>]
[--session <session_id>] [--background] [--timeout <duration>]
[--goal [budget]] [--json]
penguin ls [--project-id <id>] [--agent-id <id>] [--days <n>] [-a|--all] [--json]
penguin input [session_id] [-m <text>] [--timeout <duration>]
[--project-id <id>] [--agent-id <id>] [--json] [--server <url>]
penguin logs [session_id] [--project-id <id>] [--agent-id <id>] [--tail <n>]
[-f|--follow] [--timeout <duration>] [--json]
penguin agent ls [--project-id <id>] [--json]
penguin agent create --agent-id <id> [--name <s>] [--description <s>] [--skills <a,b>]
[--project-id <id>] [--json]
penguin project ls [--json]
penguin cost [--days <n>] [--from <d> --to <d>] [--by date|agent|model|session]
[--project-id <id>] [--agent-id <id>] [--json]
penguin schedule ls [--project-id <id>] [--agent-id <id>] [--json]
penguin schedule add <name> --prompt <s> --start-at <ISO|now> [--period <duration>]
[--end-at <ISO>] [--session-id <id> | --workspace <path>
[--model-id <id> --provider <p>]] [--disabled]
[--project-id <id>] [--agent-id <id>]
penguin schedule update <name> [<same field flags>] [--enable|--disable]
[--project-id <id>] [--agent-id <id>]
penguin schedule rm <name> [--project-id <id>] [--agent-id <id>]
run starts a task and waits, rendering the conversation, unless --background — then it prints the new session id and exits while the server keeps running the task. --session <session_id> runs the task in an existing session instead of creating one; the model reference is the --provider + --model-id pair (both or neither); --goal [budget] runs in goal mode — the session loops until the agent declares the goal complete, with an optional spend budget.
- Caller-context defaults. Inside a harness agent, a session-creating
run fills every field you leave unspecified from your own live session, per field independently: --workspace, the --model-id/--provider pair, --approve and --thinking inherit the caller's values — the same convention as run_subagent parent inheritance. Precedence: explicit flag > caller value > plain fallback (cwd, the Project default model, allow-all, none — used wholesale if the caller lookup fails, with a dim stderr note). So inside an agent, penguin run -m "..." alone typically does the right thing; pass flags only to diverge.
--timeout <duration> (30s, 5m, 2h, or bare seconds) bounds the wait of a foreground run, an input, or a logs -f. Expiry is a soft yield, not an error: the command exits 0 while the task keeps running server-side, printing a still-running note that names the follow-up commands (--json prints {sessionId, status: "running", text} with the text so far). --timeout 0 (also 0s) returns immediately after delivery — the same note without collected text (--json: {sessionId, status: "running"}); on a bare poll it snapshots a running session instantly. run --background stays the idiomatic fire-and-forget for new tasks and rejects --timeout; logs --timeout requires -f.
input with -m steers a running session mid-turn (the agent absorbs it as a course correction within the current task) or starts a new turn on an idle one; it waits for the reply unless a --timeout bounds the wait (--timeout 0 = deliver and return at once). Bare input [session_id] (no -m) polls: it prints the session's most recent complete assistant text — an idempotent snapshot that skips user/thinking/tool output and never touches approvals, mirroring input_subagent's empty-prompt semantics. A running session is waited on first (bounded by --timeout, else indefinitely); a session with no reply yet prints (no assistant reply yet). --json reports {sessionId, status, text} — idle/running when polling, completed/aborted/running with -m.
ls spans every agent of the project, newest first (by last active); archived sessions are left out unless -a/--all includes them, and --days <n> keeps only sessions last active since local midnight n−1 days ago — today counts as day 1, so --days 2 is yesterday and today, --days 7 this week. logs renders a session's transcript: --tail <n> for the last entries, -f to follow live.
- Session ids embed their creation timestamp —
session-YYYY-MM-DD-HH-mm-ss-<8hex>. Every <session_id> argument takes any unique substring of an id; the 8-hex tail is the recommended short form, and an ambiguous fragment errors listing the candidates. On input and logs, --project-id scopes that fragment search (unnecessary with a full id).
- On
input and logs the id is optional altogether: omitted, it is the agent's most recent session, off the same newest-first listing chat --resume uses, with --agent-id picking whose. The chosen id is announced as a dim [latest] line on stderr, so the target is never ambiguous and --json on stdout stays parseable. Bare penguin logs is therefore "what just happened" and bare penguin input is "what did my agent last say"; an agent with no session at all gets one line pointing at penguin run and a non-zero exit.
Recipes
Yesterday's or this week's sessions, with their latest replies
penguin ls --days 2 --json # yesterday + today (today counts as day 1)
penguin ls --days 7 --json # this week; add -a to include archived sessions
penguin input <session_id> # one session's latest complete assistant reply
--days <n> keeps sessions last active since local midnight n−1 days ago. For strictly-yesterday, take --days 2 and drop today's entries client-side — ids embed the creation date and the JSON carries last-active.
- Bare
input prints the latest reply; add --timeout 0 to snapshot a running session instantly instead of waiting for its turn to finish.
Summarize this week's history in a new session
A fresh session gets a fresh context window for the summary; feed it through a file, not the prompt:
penguin ls --days 7 --json # pick the sessions
penguin logs <session_id> --tail 100 # gather each transcript (widen if cut short)
# write what you gathered into a workspace file with your file tools, then:
penguin run -m "Read ./weekly-material.md and write the weekly summary to ./weekly-summary.md"
- Exchange big material through workspace files. Caller-context defaults mean the new session shares your workspace — write the gathered transcripts to
./weekly-material.md and have the new session read it there. Pages of transcript do not belong in -m.
- Read the result from
./weekly-summary.md (the foreground run also renders the reply).
Create an agent and say hello
penguin agent create --agent-id greeter --name "Greeter" --description "Welcomes people"
penguin run --agent-id greeter -m "Hello! Introduce yourself."
- Agent ids must match
^[a-z][a-z0-9_]{1,63}$: a lowercase letter first, then lowercase letters, digits and underscores — no hyphens.
- A newly created agent starts with no skills preinstalled: seed it at creation with
--skills a,b (library names) — include penguin-orchestration itself when the new agent must drive the harness too.
--agent-id switches only the agent: workspace, model, approval and thinking still inherit from your own session (caller-context defaults) — add those flags to change them too. Each run without --session opens a fresh session; reuse a session id to continue a conversation.
Summarize each agent's costs in a new session
penguin cost --days 7 --by agent --json # who spent what this week
penguin run -m "Summarize this per-agent cost report and flag anomalies: <the JSON>"
- The default
penguin cost card already carries today / last 7 days / total; --by date|model|session and --from <d> --to <d> give the other cuts, --project-id / --agent-id narrow the scope.
- A
--by agent report is small enough to inline in -m; for long breakdowns (--by session over a busy week), use the file-exchange pattern from the weekly-summary recipe.
Set up a scheduled task for the current agent
penguin schedule add build-watch --prompt "Check the build results and report the failures" \
--start-at now --period 12h --session-id "$PENGUIN_SESSION_ID" \
--end-at <ISO instant> # only when the request has a horizon
penguin schedule add daily-report --prompt "Summarize yesterday's conversations" \
--start-at now --period 1d # no target: a fresh session per firing
penguin schedule ls # verify
penguin schedule update daily-report --period 12h # adjust; --enable/--disable to toggle
penguin schedule rm daily-report # remove — no confirmation prompt
--agent-id defaults to yourself from the caller env, so this schedules the current agent. --start-at takes ISO 8601 or now; --period is at least 5m (30m/12h/1d/7d), omit it for a one-shot; --end-at bounds recurrence. Target the session you are in — --session-id "$PENGUIN_SESSION_ID" — unless the user asked for somewhere else: the prompt then arrives in this conversation, with its context. Leave the target off when the user wants a separate session, or when the task is better off starting clean (a nightly report that should not inherit this conversation); each firing then opens a new session, which --workspace <path> and the --model-id + --provider pair configure.
add creates the schedule enabled (--disabled stages it off) — deliberately diverging from the raw file, where enabled defaults to false. update is read-modify-write: unspecified fields keep their stored values, and switching the target form clears the other one.
- In
schedule ls, read: the AGENT column (without --agent-id the listing spans agents), enabled (a disabled entry never fires), startAt (first firing), period (absent means one-shot), the target — an existing session versus a new session per firing — and lastFiredAt.
- The CLI writes through the schedules API, so mistakes are rejected synchronously. The TOML file stays the single source of truth —
<app_data_dir>/agents/<agent_id>/agent_state/schedule/<name>.toml, fields mirroring the flags (prompt, enabled — false by default in the file, start_at, period, end_at, session_id / workspace+provider+model_id) — and remains editable with file tools; your system prompt's schedule roster lists yours. A hand edit is only validated by the periodic reconcile (roughly every 30s), with errors landing in error records rather than your terminal — prefer the CLI.
Run a conversation in the background and steer it mid-flight
Two patterns; both leave you free while the conversation runs.
(a) Background CLI process — you get a completion report. Run the CLI itself as a background command: exec_command with run_in_background: true and the command
penguin run --agent-id <agent_id> -m "<long task>"
- The harness delivers a
[background_task_done] report when the CLI exits — no polling needed for completion.
- Meanwhile, find the session with
penguin ls --json (it shows as running, with the newest id) and steer it: penguin input <session_id> -m "Focus on X; skip Y" --timeout 0 (deliver and return at once).
- Poll the latest answer with bare
penguin input <session_id> --timeout 30s — a bounded wait that exits 0 with a still-running note when the reply is not in yet — or read the raw transcript with penguin logs <session_id> --tail 20.
(b) Server-side background — survives you. penguin run --background --agent-id <agent_id> -m "<long task>" prints the session id and exits; the server keeps running the task with no local process.
- Poll the latest answer with bare
penguin input <session_id> --timeout 30s, watch live with penguin logs <session_id> -f, and check running state with penguin ls --json; steer with penguin input <session_id> -m ... the same way.
Prefer (a) when you stay around for the result — the completion report comes to you. Prefer (b) when the work must survive your own session ending, or when fanning out many tasks without holding a process per task. A bounded foreground run is the middle ground: penguin run --timeout 5m -m "..." renders up to the bound, then soft-yields with the task still running — pick up the answer later with a bare penguin input <session_id>.
Cautions
- One active task per session.
penguin input at a busy session steers the running task rather than starting a second one; a new task sent at a busy session waits its turn. For parallel work, start parallel sessions.
- Unattended sessions must not need a human. A spawned session inherits your approval mode (
allow-all when there is no caller to inherit from); if you yourself run under always-ask, pass --approve allow-all (trusted work) or --approve read-only explicitly — an unattended always-ask session hangs waiting for approval in the web UI.
- No runaway loops. An agent that messages itself — directly, through a chain of agents, or through a schedule aimed back at its own session — keeps spending until someone stops it. Make every automated conversation terminate: a recurring schedule pointed at your own session takes an
--end-at whenever the request has a natural horizon (or no --period at all, for a one-time reminder), and a prompt whose per-firing work stays small — that session's context grows with every firing. When the user wants it open-ended, leave --end-at off and tell them it runs until they remove it.
- Spawned work bills the project. Everything you start lands in the same project's usage (
penguin cost shows it); a fan-out of sessions multiplies spend.
- Configuration stays CLI-managed. Never read or hand-edit
.project_config.toml or agent_state/.vault.toml — models and secrets go through penguin config (see the penguin-cli skill).
1---2name: penguin-orchestration3description: Drive PenguinHarness itself from a shell — list and create agents and sessions, send and steer messages mid-flight, and query costs and scheduled tasks via the penguin CLI over the local server.4---56# Penguin Orchestration78The `penguin` CLI is a thin client of the PenguinHarness server. Inside a harness agent session it reaches the same server that is running you, so you can orchestrate the platform yourself: list and create agents, start conversations with them, steer those conversations while they run, and query costs and scheduled tasks.910## Before you start1112If the user's message only invokes this skill (e.g. "use penguin-orchestration skill") without a concrete request, ask the user what they want to orchestrate. Read-only commands (`project ls`, `agent ls`, `ls`, `logs`, `cost`, `schedule ls`) are always safe; do not create agents, start sessions or send messages until the goal is clear.1314## How the connection works1516- **Inside a harness agent session** (you, now): every command subprocess has `PENGUIN_API_URL`, `PENGUIN_API_TOKEN`, `PENGUIN_PROJECT_ID`, `PENGUIN_AGENT_ID` and `PENGUIN_SESSION_ID` injected, so `penguin` commands automatically reach your own server with your project and agent as the defaults — no login step.17- **Outside an agent** (a human shell): the CLI attaches to the running local server via its lock file, or auto-starts one; the local `<data-root>/api-token` file (0600) authenticates it.18- You are operating the same server that runs you: sessions and agents you create appear live in the web UI, where the user sees and owns everything you spawn.19- The injected token is admin-equivalent. Act accordingly: stick to what the task requires, and prefer read-only commands until a mutation is clearly needed.2021## Orient first2223Before mutating anything, see what exists:2425```bash26penguin project ls # projects on this server27penguin agent ls # agents in the current project28penguin ls --json # the project's sessions, with running state29```3031`--json` on any listing gives machine-parseable output.3233## Command surface3435```36penguin run -m <msg> [--project-id <id>] [--agent-id <id>] [--workspace <path>]37 [--model-id <id> --provider <p>] [--approve <mode>] [--thinking <level>]38 [--session <session_id>] [--background] [--timeout <duration>]39 [--goal [budget]] [--json]40penguin ls [--project-id <id>] [--agent-id <id>] [--days <n>] [-a|--all] [--json]41penguin input [session_id] [-m <text>] [--timeout <duration>]42 [--project-id <id>] [--agent-id <id>] [--json] [--server <url>]43penguin logs [session_id] [--project-id <id>] [--agent-id <id>] [--tail <n>]44 [-f|--follow] [--timeout <duration>] [--json]45penguin agent ls [--project-id <id>] [--json]46penguin agent create --agent-id <id> [--name <s>] [--description <s>] [--skills <a,b>]47 [--project-id <id>] [--json]48penguin project ls [--json]49penguin cost [--days <n>] [--from <d> --to <d>] [--by date|agent|model|session]50 [--project-id <id>] [--agent-id <id>] [--json]51penguin schedule ls [--project-id <id>] [--agent-id <id>] [--json]52penguin schedule add <name> --prompt <s> --start-at <ISO|now> [--period <duration>]53 [--end-at <ISO>] [--session-id <id> | --workspace <path>54 [--model-id <id> --provider <p>]] [--disabled]55 [--project-id <id>] [--agent-id <id>]56penguin schedule update <name> [<same field flags>] [--enable|--disable]57 [--project-id <id>] [--agent-id <id>]58penguin schedule rm <name> [--project-id <id>] [--agent-id <id>]59```6061- `run` starts a task and waits, rendering the conversation, unless `--background` — then it prints the new session id and exits while the server keeps running the task. `--session <session_id>` runs the task in an existing session instead of creating one; the model reference is the `--provider` + `--model-id` pair (both or neither); `--goal [budget]` runs in goal mode — the session loops until the agent declares the goal complete, with an optional spend budget.62- **Caller-context defaults.** Inside a harness agent, a session-creating `run` fills every field you leave unspecified from your own live session, per field independently: `--workspace`, the `--model-id`/`--provider` pair, `--approve` and `--thinking` inherit the caller's values — the same convention as `run_subagent` parent inheritance. Precedence: explicit flag > caller value > plain fallback (cwd, the Project default model, `allow-all`, none — used wholesale if the caller lookup fails, with a dim stderr note). So inside an agent, `penguin run -m "..."` alone typically does the right thing; pass flags only to diverge.63- `--timeout <duration>` (`30s`, `5m`, `2h`, or bare seconds) bounds the wait of a foreground `run`, an `input`, or a `logs -f`. Expiry is a soft yield, not an error: the command exits 0 while the task keeps running server-side, printing a still-running note that names the follow-up commands (`--json` prints `{sessionId, status: "running", text}` with the text so far). `--timeout 0` (also `0s`) returns immediately after delivery — the same note without collected text (`--json`: `{sessionId, status: "running"}`); on a bare poll it snapshots a running session instantly. `run --background` stays the idiomatic fire-and-forget for new tasks and rejects `--timeout`; `logs --timeout` requires `-f`.64- `input` with `-m` steers a **running** session mid-turn (the agent absorbs it as a course correction within the current task) or starts a new turn on an idle one; it waits for the reply unless a `--timeout` bounds the wait (`--timeout 0` = deliver and return at once). Bare `input [session_id]` (no `-m`) **polls**: it prints the session's most recent complete assistant text — an idempotent snapshot that skips user/thinking/tool output and never touches approvals, mirroring `input_subagent`'s empty-prompt semantics. A running session is waited on first (bounded by `--timeout`, else indefinitely); a session with no reply yet prints `(no assistant reply yet)`. `--json` reports `{sessionId, status, text}` — `idle`/`running` when polling, `completed`/`aborted`/`running` with `-m`.65- `ls` spans every agent of the project, newest first (by last active); archived sessions are left out unless `-a`/`--all` includes them, and `--days <n>` keeps only sessions last active since local midnight n−1 days ago — today counts as day 1, so `--days 2` is yesterday and today, `--days 7` this week. `logs` renders a session's transcript: `--tail <n>` for the last entries, `-f` to follow live.66- Session ids embed their creation timestamp — `session-YYYY-MM-DD-HH-mm-ss-<8hex>`. Every `<session_id>` argument takes any unique substring of an id; the 8-hex tail is the recommended short form, and an ambiguous fragment errors listing the candidates. On `input` and `logs`, `--project-id` scopes that fragment search (unnecessary with a full id).67- On `input` and `logs` the id is optional altogether: omitted, it is the agent's most recent session, off the same newest-first listing `chat --resume` uses, with `--agent-id` picking whose. The chosen id is announced as a dim `[latest]` line on stderr, so the target is never ambiguous and `--json` on stdout stays parseable. Bare `penguin logs` is therefore "what just happened" and bare `penguin input` is "what did my agent last say"; an agent with no session at all gets one line pointing at `penguin run` and a non-zero exit.6869## Recipes7071### Yesterday's or this week's sessions, with their latest replies7273```bash74penguin ls --days 2 --json # yesterday + today (today counts as day 1)75penguin ls --days 7 --json # this week; add -a to include archived sessions76penguin input <session_id> # one session's latest complete assistant reply77```7879- `--days <n>` keeps sessions last active since local midnight n−1 days ago. For strictly-yesterday, take `--days 2` and drop today's entries client-side — ids embed the creation date and the JSON carries last-active.80- Bare `input` prints the latest reply; add `--timeout 0` to snapshot a running session instantly instead of waiting for its turn to finish.8182### Summarize this week's history in a new session8384A fresh session gets a fresh context window for the summary; feed it through a file, not the prompt:8586```bash87penguin ls --days 7 --json # pick the sessions88penguin logs <session_id> --tail 100 # gather each transcript (widen if cut short)89# write what you gathered into a workspace file with your file tools, then:90penguin run -m "Read ./weekly-material.md and write the weekly summary to ./weekly-summary.md"91```9293- **Exchange big material through workspace files.** Caller-context defaults mean the new session shares your workspace — write the gathered transcripts to `./weekly-material.md` and have the new session read it there. Pages of transcript do not belong in `-m`.94- Read the result from `./weekly-summary.md` (the foreground run also renders the reply).9596### Create an agent and say hello9798```bash99penguin agent create --agent-id greeter --name "Greeter" --description "Welcomes people"100penguin run --agent-id greeter -m "Hello! Introduce yourself."101```102103- Agent ids must match `^[a-z][a-z0-9_]{1,63}$`: a lowercase letter first, then lowercase letters, digits and underscores — no hyphens.104- A newly created agent starts with **no skills preinstalled**: seed it at creation with `--skills a,b` (library names) — include `penguin-orchestration` itself when the new agent must drive the harness too.105- `--agent-id` switches only the agent: workspace, model, approval and thinking still inherit from your own session (caller-context defaults) — add those flags to change them too. Each `run` without `--session` opens a fresh session; reuse a session id to continue a conversation.106107### Summarize each agent's costs in a new session108109```bash110penguin cost --days 7 --by agent --json # who spent what this week111penguin run -m "Summarize this per-agent cost report and flag anomalies: <the JSON>"112```113114- The default `penguin cost` card already carries today / last 7 days / total; `--by date|model|session` and `--from <d> --to <d>` give the other cuts, `--project-id` / `--agent-id` narrow the scope.115- A `--by agent` report is small enough to inline in `-m`; for long breakdowns (`--by session` over a busy week), use the file-exchange pattern from the weekly-summary recipe.116117### Set up a scheduled task for the current agent118119```bash120penguin schedule add build-watch --prompt "Check the build results and report the failures" \121 --start-at now --period 12h --session-id "$PENGUIN_SESSION_ID" \122 --end-at <ISO instant> # only when the request has a horizon123penguin schedule add daily-report --prompt "Summarize yesterday's conversations" \124 --start-at now --period 1d # no target: a fresh session per firing125penguin schedule ls # verify126penguin schedule update daily-report --period 12h # adjust; --enable/--disable to toggle127penguin schedule rm daily-report # remove — no confirmation prompt128```129130- `--agent-id` defaults to yourself from the caller env, so this schedules the current agent. `--start-at` takes ISO 8601 or `now`; `--period` is at least 5m (`30m`/`12h`/`1d`/`7d`), omit it for a one-shot; `--end-at` bounds recurrence. Target the session you are in — `--session-id "$PENGUIN_SESSION_ID"` — unless the user asked for somewhere else: the prompt then arrives in this conversation, with its context. Leave the target off when the user wants a separate session, or when the task is better off starting clean (a nightly report that should not inherit this conversation); each firing then opens a new session, which `--workspace <path>` and the `--model-id` + `--provider` pair configure.131- `add` creates the schedule **enabled** (`--disabled` stages it off) — deliberately diverging from the raw file, where `enabled` defaults to false. `update` is read-modify-write: unspecified fields keep their stored values, and switching the target form clears the other one.132- In `schedule ls`, read: the AGENT column (without `--agent-id` the listing spans agents), `enabled` (a disabled entry never fires), `startAt` (first firing), `period` (absent means one-shot), the target — an existing session versus a new session per firing — and `lastFiredAt`.133- The CLI writes through the schedules API, so mistakes are rejected synchronously. The TOML file stays the single source of truth — `<app_data_dir>/agents/<agent_id>/agent_state/schedule/<name>.toml`, fields mirroring the flags (`prompt`, `enabled` — false by default in the file, `start_at`, `period`, `end_at`, `session_id` / `workspace`+`provider`+`model_id`) — and remains editable with file tools; your system prompt's schedule roster lists yours. A hand edit is only validated by the periodic reconcile (roughly every 30s), with errors landing in error records rather than your terminal — prefer the CLI.134135### Run a conversation in the background and steer it mid-flight136137Two patterns; both leave you free while the conversation runs.138139**(a) Background CLI process — you get a completion report.** Run the CLI itself as a background command: `exec_command` with `run_in_background: true` and the command140141```bash142penguin run --agent-id <agent_id> -m "<long task>"143```144145- The harness delivers a `[background_task_done]` report when the CLI exits — no polling needed for completion.146- Meanwhile, find the session with `penguin ls --json` (it shows as running, with the newest id) and steer it: `penguin input <session_id> -m "Focus on X; skip Y" --timeout 0` (deliver and return at once).147- Poll the latest answer with bare `penguin input <session_id> --timeout 30s` — a bounded wait that exits 0 with a still-running note when the reply is not in yet — or read the raw transcript with `penguin logs <session_id> --tail 20`.148149**(b) Server-side background — survives you.** `penguin run --background --agent-id <agent_id> -m "<long task>"` prints the session id and exits; the server keeps running the task with no local process.150151- Poll the latest answer with bare `penguin input <session_id> --timeout 30s`, watch live with `penguin logs <session_id> -f`, and check running state with `penguin ls --json`; steer with `penguin input <session_id> -m ...` the same way.152153Prefer (a) when you stay around for the result — the completion report comes to you. Prefer (b) when the work must survive your own session ending, or when fanning out many tasks without holding a process per task. A bounded foreground run is the middle ground: `penguin run --timeout 5m -m "..."` renders up to the bound, then soft-yields with the task still running — pick up the answer later with a bare `penguin input <session_id>`.154155## Cautions156157- **One active task per session.** `penguin input` at a busy session steers the running task rather than starting a second one; a new task sent at a busy session waits its turn. For parallel work, start parallel sessions.158- **Unattended sessions must not need a human.** A spawned session inherits your approval mode (`allow-all` when there is no caller to inherit from); if you yourself run under `always-ask`, pass `--approve allow-all` (trusted work) or `--approve read-only` explicitly — an unattended `always-ask` session hangs waiting for approval in the web UI.159- **No runaway loops.** An agent that messages itself — directly, through a chain of agents, or through a schedule aimed back at its own session — keeps spending until someone stops it. Make every automated conversation terminate: a recurring schedule pointed at your own session takes an `--end-at` whenever the request has a natural horizon (or no `--period` at all, for a one-time reminder), and a prompt whose per-firing work stays small — that session's context grows with every firing. When the user wants it open-ended, leave `--end-at` off and tell them it runs until they remove it.160- **Spawned work bills the project.** Everything you start lands in the same project's usage (`penguin cost` shows it); a fan-out of sessions multiplies spend.161- **Configuration stays CLI-managed.** Never read or hand-edit `.project_config.toml` or `agent_state/.vault.toml` — models and secrets go through `penguin config` (see the penguin-cli skill).