--- name: penguin-orchestration description: 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. --- # 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 `/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: ```bash 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 [--project-id ] [--agent-id ] [--workspace ] [--model-id --provider

] [--approve ] [--thinking ] [--session ] [--background] [--timeout ] [--goal [budget]] [--json] penguin ls [--project-id ] [--agent-id ] [--days ] [-a|--all] [--json] penguin input [session_id] [-m ] [--timeout ] [--project-id ] [--agent-id ] [--json] [--server ] penguin logs [session_id] [--project-id ] [--agent-id ] [--tail ] [-f|--follow] [--timeout ] [--json] penguin agent ls [--project-id ] [--json] penguin agent create --agent-id [--name ] [--description ] [--skills ] [--project-id ] [--json] penguin project ls [--json] penguin cost [--days ] [--from --to ] [--by date|agent|model|session] [--project-id ] [--agent-id ] [--json] penguin schedule ls [--project-id ] [--agent-id ] [--json] penguin schedule add --prompt --start-at [--period ] [--end-at ] [--session-id | --workspace [--model-id --provider

]] [--disabled] [--project-id ] [--agent-id ] penguin schedule update [] [--enable|--disable] [--project-id ] [--agent-id ] penguin schedule rm [--project-id ] [--agent-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 ` 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 ` (`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 ` 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 ` for the last entries, `-f` to follow live. - Session ids embed their creation timestamp — `session-YYYY-MM-DD-HH-mm-ss-<8hex>`. Every `` 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 ```bash 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 # one session's latest complete assistant reply ``` - `--days ` 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: ```bash penguin ls --days 7 --json # pick the sessions penguin logs --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 ```bash 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 ```bash 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 default `penguin cost` card already carries today / last 7 days / total; `--by date|model|session` and `--from --to ` 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 ```bash 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 # 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 ` 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 — `/agents//agent_state/schedule/.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 ```bash penguin run --agent-id -m "" ``` - 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 -m "Focus on X; skip Y" --timeout 0` (deliver and return at once). - Poll the latest answer with bare `penguin input --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 --tail 20`. **(b) Server-side background — survives you.** `penguin run --background --agent-id -m ""` prints the session id and exits; the server keeps running the task with no local process. - Poll the latest answer with bare `penguin input --timeout 30s`, watch live with `penguin logs -f`, and check running state with `penguin ls --json`; steer with `penguin input -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 `. ## 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. - **Opening an agent to programs is the owner's call.** `penguin agent api` manages an agent's Agent API. Its `status` and `keys ls` only read. Never run `enable`, `disable`, `set`, `keys create`, `keys rm` or `server` yourself: the server refuses them to your token (`403 human_required`), and you must not sign in or mint a session to get past that. Ask the user to make the change on the agent's **API** tab, or in their own terminal after `penguin auth login`. To connect a program to an agent, use the penguin-sdk skill. - **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).