Agent: explore
The long, read-only investigation: agent:explore clones its bound repository with gh repo clone or git clone "$GIT_DOOR_REMOTE" into a cold sandbox on a read-scoped credential, runs whatever the question needs — builds, test suites, pipelines, benchmarks — for up to two hours, searches the web beside it, and reports a claim table with the commands it ran and the numbers they produced. It never opens a pull request. It is the first preset on the repo-cold machine class (record 0026): a request needing a shell, web search, a read credential and two hours runs without a new agent being written.
- Code:
src/agents/registry.ts(explore,EXPLORE_SYSTEM),src/tools/toolsets.ts(theexploretoolset: the tools the bot relays; the shell and the file reads are pi's own in the cold sandbox),deploy/cloudflare-sandbox/worker.ts(the exit-124 hint namessetsid -f),src/core/dispatch/resolve.ts+src/core/dispatch/authorize.ts(the class's repository vet and refusals — execution.md item 18), default model in the deployed config (config/config.example.yamldocuments the key) - Docs: The agents and their toolsets, execution.md items 2 and 18, routing-and-config.md items 1–4 (directives, the effective profile, the profile gate)
- Tests:
src/agents/registry.test.ts(theexplore agentblock),src/tools/github.test.ts+src/tools/web.test.ts(the toolset),src/core/dispatcher.test.ts(theagent:exploreblock),src/execution/sandboxLifecycle.test.ts(the sandbox Worker's hint) - Budgets: 120 min / 64k tokens (the turn guard is derived: 720, six a minute — harness-pi.md item 15) · toolset
explore· machinerepo-cold· identityread· cache TTL1h
Behavior
- A preset, chosen by name.
agent:exploreresolves throughparseDirectives/AGENTS["explore"]like every directive and is sticky in its thread like every agent (routing-and-config.md items 1 and 3); no router picks it yet. Its declared profile is machinerepo-cold, identityread, 120 minutes — the two-hour budget no other preset holds — with the turn guard derived from it like every preset's and the token cap a per-call limit sized for a long loop of batched checks. The cache TTL is1h: a detached job polled across calls makes steps longer than the default cache entry lives. No built-in effort: the config layers decide, as forcoding. Open to everyone unlessrestrict.agentsnames it; a self-serve MCP server never reaches it (MCP_SELF_SERVE_AGENTS, mcp-tools.md), because it holds a shell. - Reach: the
exploretoolset —bash,read_file,update_status,web_fetch,web_search,list_skills/use_skilland the GitHub reads (github_repos,github_tree,github_file,github_search_code,github_issue_list,github_issue_get,github_pull_get), and nothing that writes: nowrite_file, nosubmit_*(no verdict, description, dispositions or handoff), no issue writes. It is the one toolset besidewebthat holdsweb_search(web-tools.md item 4). Read-only is a toolset-and-prompt contract; the wall is the identity. Noattach_fileeither (agent-coding.md item 10: the tool is full-toolset only), and the prompt says so in plain words — the whole answer is text, a file is named by its workspace path and described, never announced as attached — so a run does not promise the person a file it has no way to post (theunkept_promisefriction category, run-friction.md item 1, is what records it when one does). - Machine
repo-cold, identityread(execution.md items 5 and 18): every run is a fresh per-thread sandbox with the checkout, even when the repository has a serviceable resident — a two-hour, memory-hungry job shares no container with the reviews that depend on the resident — and the resident registry and Worker are never consulted, so an outage there can neither refuse nor delay it. A bareowner/nameis vetted against GitHub with the run's own read-scoped credential (oneGET /repos/<owner>/<name>; a repository the installation cannot see refuses asrepo_not_visible, GitHub's silence asrepo_unverified). The sandbox holds a read-scoped token, so the run can clone and read and can never push, whatever the caller holds; an investigation that must push is a second preset, never a directive. The prompt says so and tells the run to clone the repository itself (the cold sandbox pre-clones nothing). - The deliverable is a claim table. The prompt turns the request into claims — the explicit ones and the ones a careful engineer would check — and has each verified by running it: one row per claim with the exact command, the number or output it produced, and a verdict (holds / does not hold / could not check, with why). Numbers over adjectives; what was not reached is said. The final message leads with a one-line verdict, then the table (aligned rows in a code block, since Slack has no table markup), then what a follow-up should do.
- A job past the command cap is detached with
setsid -f. Every/execruns undertimeout … bash -c, whose process group is reaped when the command returns (execution.md item 2), so a plain background job dies with the command that started it while asetsid -fjob outlives it. The prompt names the per-command ceiling (BASH_TIMEOUT_MAX_MS, twenty minutes, the one constant insrc/execution/bashTimeout.ts), thetimeoutMsknob, and the detach-and-poll recipe (setsid -f sh -c '<command> > /tmp/job.log 2>&1; echo $? > /tmp/job.exit' </dev/null >/dev/null 2>&1, thentailthe log on later calls — the wrapper's own stdio to /dev/null, or the command's output never ends and the call dies at the executor's deadline, execution.md item 24); the sandbox Worker's exit-124 hint names the same tool with the same redirects and nevernohup. Neither text names the tool that does not work. - Never a pull request. The prompt forbids opening a pull request, committing and pushing, and every PR or issue write; a change the investigation calls for is described in the write-up — exactly what and where — with the plain-words way to ask for it in a new message (
in <owner/name>: <the change>), which routes to the write door (routing-and-config.md item 21); the prompt forbids handing back a command or anagent:…line to type, so an investigation never ends in a directive the person must paste. The toolset has no submission tool and the token cannot push, so the rule is held three times: prompt, toolset, identity. - Knows its own settings — and itself. Like every agent, its prompt carries the config block (routing-and-config.md item 8) — the resolved agent, model and effort, its budget and what clipped it — and the About block (item 11), and it maintains the status card through
update_status. - Its budget is a profile field a caller narrows per message.
agent:explore budget:30runs thirty minutes withboundedBy: directive— at the factory, on the runner's def, on the card (budget 30 min (budget directive; preset asks 120)), in the config block and on the record;budget:200runs the preset's 120 and the card says the directive narrowed nothing; in a channel bounded to 45 minutes the run is 45 and the record sayschannel, abudget:60beside it narrows nothing and the card says so, abudget:30under it is the directive's clip (routing-and-config.md items 1, 2 and 4). The budget never reaches the identity or the class: a directive cannot turn the read-only investigation into anything else.
Roadmap (gaps)
[gap]A router that picksexplorewhen no directive names it (record 0026, slice two).[gap]A background-job primitive of the sandbox's own, should a detached process prove not to survive a run's whole life (the record's first "what would change our mind" row; the live row below is its cheapest test).
Validation criteria
| Criterion | Proof |
|---|---|
1: explore declares machine repo-cold, identity read, toolset explore, 120 minutes, a turn guard of at least 100 (720, derived), at least 64k tokens, cache TTL 1h, no built-in effort and no resident prompt; getAgent("explore") resolves it | [unit] src/agents/registry.test.ts::explore agent (docs/reference/specs/agent-explore.md)::explore: repo-cold machine class, read identity, the explore toolset, 120 minutes… |
1: every preset names a valid identity; explore is read beside review | [unit] src/agents/registry.test.ts::agent registry matches the feature specs::identity declarations… |
2: the explore toolset is exactly bash, read_file, update_status, web_fetch, web_search, the two skill tools and the GitHub reads — no submit_*, no issue writes; the reads are in every toolset with a tool loop | [unit] src/tools/github.test.ts::toolset wiring::explore relays update_status, web_fetch, web_search, the skill tools, the session tools and the GitHub reads — no submit_*, no issue writes, and none of pi's own workspace tools, ::reads are in every toolset with a tool loop… |
2: web_search is held by the web and explore toolsets and no other | [unit] src/tools/web.test.ts::toolset + agent wiring::gates web_search to the research and explore toolsets… |
| 2: the description the router reads says the preset cannot attach or post files, the same fact the prompt tells the model, so an ask that names a posted file has a reason on the table to route elsewhere | [unit] src/agents/registry.test.ts::explore agent (docs/reference/specs/agent-explore.md)::explore's prompt: the deliverable is a claim table… |
3: end to end on a deployment with a sandbox and a resident fleet — agent:explore in <owner/repo> reaches the factory with the profile { repo-cold, read, 120 } and a sandbox backend, the runner gets the explore def, the repository is vetted with exactly one GitHub lookup and the resident Worker is never called, and the record carries the profile under the preset's name | [unit] src/core/dispatcher.test.ts::agent:explore — the first repo-cold preset::agent:explore against a deployment with a resident fleet… |
4, 5, 6: the prompt names the claim table, the exact command and the numbers, the twenty-minute cap and setsid -f (never nohup), forbids opening a pull request and committing or pushing, points a needed change at a plain-words ask in a new message and never at an agent:ship or agent:coding line to type, names no submission tool, names every tool it holds, says the credential is read-scoped, drives the status card, and says it cannot attach or post files (never announcing a file as attached, never naming attach_file) | [unit] src/agents/registry.test.ts::explore agent (docs/reference/specs/agent-explore.md)::explore's prompt… |
8: budget:30 → 30 minutes as directive everywhere the profile is read, the shared preset untouched, the block attributing the directive; budget:200 → 120 with the card and block saying the directive narrowed nothing; a channel bounded to 45 → 45 as channel, budget:60 beside it narrowing nothing, budget:30 under it the directive's clip; budget:1 text, never a refusal (routing-and-config.md item 1) — the run proceeds on the preset's own budget | [unit] src/core/dispatcher.test.ts::agent:explore — the first repo-cold preset::\agent:explore budget:30` runs 30 minutes…, ::`agent:explore budget:200` runs the preset's 120…, ::`agent:explore` in a channel bounded to 45 minutes…, ::`budget:1` is text, not a refusal…` |
5: the sandbox Worker's exit-124 hint names setsid -f with the wrapper's stdio redirected, and never nohup | [unit] src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::the timeout hint tells the model to detach a long job with setsid -f and the wrapper's stdio redirected, and never names nohup |
| 5: a detached job outlives the command that started it and is polled to completion across tool calls (a two-hour run crosses dozens of call boundaries) | [agent] In Slack, in a thread: @switchboard agent:explore in <owner/repo>: start 'sleep 1500 && echo DONE' detached with setsid -f, redirecting to /tmp/job.log, then poll the log every few minutes and report the exact time DONE appeared and how many tool calls the wait took. — expect the reply to quote DONE from the log about 25 minutes after the start, a poll count above one, and no exit 124; the run page shows the bash calls spanning the wait on one sandbox (no re-clone). Human-gated. |
3, 4: the comparison run — agent:explore against a large onboarded monorepo lands in a cold sandbox with a read token (the card and record say repo-cold / read; the repository's resident shows no attach for the thread), runs a detached job past twenty minutes and reports a claim table with commands and numbers | [agent] @switchboard agent:explore in <owner/monorepo>: run the full typecheck and the test suite locally and validate the timings the CI audit claims. — expect a claim table in the reply, the run page's first bash calls to be the clone and install, a setsid -f start and later polls of one job spanning more than twenty minutes, and runs get <id> showing profile.machine: repo-cold, profile.identity: read; repo list shows no thread binding for the thread on that repository's resident. Human-gated. |