Repository navigation
feat(agents): triage each new issue with an agent running in an OpenShell sandbox #4897
Description
Scope update (2026-10-24)
- Assessment content changed. The agent would no longer combine
triage-issuewithassess-issue-routing. #4896 now splits triage into three separately runnable skills, built in draft PR #4899:triage-submitter-profile,triage-validate-issue(renamed fromtriage-issue), andtriage-recommend-actions(replacesassess-issue-routing; the routing check was dropped). This issue would consume those skills once they land. - Slack layout moved out. The eventual Slack shape (a top-level post with a four-emoji code, one thread reply per skill, thread commands such as
/accept,/decline, and/valid, and Slack formatting) belongs to a later Slack bot project. Whether that project is #4898 or a new issue is not decided. - Not decided: how much of the existing local implementation survives. It was built against the old design: two skills, one top-level webhook post. See "What exists" and Open questions 1 and 2.
- Unchanged: the trigger, sandboxed execution, no GitHub writes, the credential constraints, and the per-session cost cap.
User Story
I'm on OpenShell triage duty, and the queue grows faster than a batch review can keep up with: more than 170 issues are waiting in state:triage-needed. I need each new issue assessed when it is opened, with the result posted where maintainers already look, so that triage happens continuously instead of in occasional large passes. I'd also like this to run on OpenShell itself, as a working example of an agent operating inside an OpenShell sandbox.
Description
Build an automated triage agent that runs once for each newly opened issue in NVIDIA/OpenShell and posts its assessment to the #openshell-triage Slack channel.
The intended shape:
- Trigger: a GitHub Actions workflow on
issues: openedwould start a Claude Managed Agents session for the new issue. - Execution: the session would use a self-hosted environment whose tools run inside an OpenShell sandbox, following the published Managed Agents and OpenShell self-hosted sandbox example. One sandbox per session, with a network policy that allows only the routes the agent needs.
- Assessment: the agent would apply the
triage-*skills from #4899.triage-validate-issuewrites to GitHub (a📋 triage-agentcomment and astate:*label), so the agent would use a read-only adaptation of it, as the local branch already does fortriage-issue. Which skills run, and in what order, is open. - Output: v1 would post through the existing channel webhook, which can post only top-level messages. It would make no GitHub writes: no comments, labels, or assignees. Whether v1 puts all skill output in one top-level message or waits for threaded posting (#4898) is open.
Security constraints for v1:
- The Managed Agents environment key would stay on the worker host and never enter a sandbox.
- GitHub and Slack credentials would be injected through OpenShell providers, or held on the host, so they are not readable inside the sandbox.
- The per-session Managed Agents token is currently piped into the sandbox, following the published example. Delivering it through an OpenShell provider instead is open (Open question 3).
- Hosted
web_fetchandweb_searchtools would be disabled, since they run outside the sandbox policy. - The issue title and body would be treated as untrusted input.
- Each session would have a cost cap.
What exists (local branch, not pushed)
Branch feat/4897-issue-triage-agent/purp holds 15 signed commits, local to the maintainer's machine. It is stacked on the first three commits of #4899. No live session, post, or pilot run has happened.
- Workflow
.github/workflows/issue-triage-agent.yml: onissues: opened, or a manualworkflow_dispatchwithissue_number, it would create a version-pinned session with a budget. The session message names only the repository and issue number, never issue text. Every run is skipped unless the repository variableTRIAGE_AGENT_ENABLEDis exactlytrue. - Worker host scripts in
scripts/agents/issue-triage/worker/:start.shrunsant beta:worker poll.on-work.shcreates one sandbox per session on a dedicated gateway with thegithub-triageprovider, runs the agent under a wall-clock cap, downloadsassessment.json, and deletes the sandbox.post-assessment.shandrender-post.jqvalidate and neutralize the file and post it through the webhook, or post a failure notice the host composes. Neutralization covers mentions, links outsideNVIDIA/OpenShell, credential-like strings, and the exact session token. A ledger prevents duplicate posts.
- Sandbox:
Dockerfile: pinnedantandgh, the skills, and a read-only repo snapshot.policy.yaml: allows only the Managed Agents session routes onapi.anthropic.com, withboundary.yamlchecked byopenshell-prover.providers/github-triage.yaml:GETonNVIDIA/OpenShellREST paths,/search/issues, and GraphQL queries, for/usr/bin/ghonly.
- Agent:
agents/issue-triage.md, with a Sonnet-class model and the hosted web tools disabled, plusskills/triage-issue-readonly/, pinned to.agents/skills/triage-issue/SKILL.mdby SHA-256. - Tests and docs:
mise run test:issue-triage-agentruns the tests, which are wired into Branch Checks. Setup, secrets, the runbook, and unverified API shapes are inscripts/agents/issue-triage/README.mdandCI.md.
Context
- Depends on the
triage-*skills (#4896, draft PR #4899).triage-recommend-actionsreadsREVIEWERS.mdfrom #4863 and falls back when it is missing. - Threaded replies need a Slack app rather than a webhook (#4898).
- Blocking prerequisites, none settled yet:
- Anthropic access (Console admin): an org and Workspace with the Managed Agents beta and self-hosted environments, NVIDIA approval to send public issue content to Anthropic, a Workspace-scoped API key and spend limit, a per-session cost cap, and a pilot exit-gate cost.
- Repository secret and variables (someone with repo-secrets write):
ANTHROPIC_API_KEY_TRIAGE, plusTRIAGE_AGENT_ID,TRIAGE_AGENT_VERSION,TRIAGE_ENVIRONMENT_ID,TRIAGE_MAX_COST_CENTS, andTRIAGE_AGENT_ENABLED. - GitHub identity for the bot (token owner): a maintainer's fine-grained PAT, a machine account, or a GitHub App. App installation tokens expire hourly and would need a refresher.
- Worker host (operator): a pilot laptop, where posts arrive late after sleep, or a cloud VM. The host also needs a pinned, checksum-verified
ant,ant apply, the environment key minted in the Console, Docker, and a dedicated OpenShell gateway (v0.1.5 or later). - Webhook (webhook owner): the existing URL or a dedicated webhook, stored in a mode-0600 file on the host.
Definition of Done
- Opening an issue in
NVIDIA/OpenShellproduces triage output in#openshell-triage, built from thetriage-*skills of #4899, in the layout settled under Open question 1. - The agent's tools execute inside an OpenShell sandbox with a restrictive network policy.
- No GitHub write occurs during a triage session.
- Setup, required secrets, and the cost cap are documented next to the workflow.
Next steps for an implementer
- Get the maintainer's answers to Open questions 1 and 2 before changing the local branch.
- After #4899 merges, rebase only the 15 agent commits onto
main(git rebase --onto main 05a7a1a07). The threeassess-issue-routingcommits underneath them belong to that PR, and a squash merge would not drop them automatically. - Re-grep
scripts/agents/issue-triage/fortriage-issueandassess-issue-routing, then:- Rename
skills/triage-issue-readonly/to matchtriage-validate-issue, and update itsmetadata.sourceand SHA-256 digest. The digest test fails as soon as the source moves. - Update those paths in
agent_definition_test.sh,policy_test.sh,worker/build-image.sh,Dockerfile,README.md, andagents/issue-triage.md. - Replace
agents/issue-triage.md:44(it readsassess-issue-routing) and its 9-linerouting_blockoutput contract with thetriage-recommend-actionsoutput: a four-emoji code and Markdown sections (see #4896).render-post.jq, its fixtures, andpost_assessment_test.shvalidate the old block and would need the same change.
- Rename
- Check each skill's GitHub calls against
providers/github-triage.yaml(Open question 5). - Prototype session-token delivery through a provider (Open question 3).
- Hand each blocking prerequisite to someone with the listed role.
- Before the first live run, set
TRIAGE_MAX_COST_CENTS=1and confirm in the Console that the session stopped at about $0.01. The cap's unit is unverified, and a wrong unit fails open. - Run
mise run test:issue-triage-agentand the prover check in the README. Verify every commit's signature before any push. Push and open a PR only when the maintainer asks.
Open questions
- What survives the redesign? Does v1 still ship one top-level webhook post, now carrying
triage-*output, or wait for threaded posting through #4898? The #4896 design defers this. - Which skills, in which order?
- Does the agent also run
triage-submitter-profile? triage-recommend-actionsreads the validation result from the📋 triage-agentcomment and thestate:*label. A read-only agent writes neither, so every new issue would report "not validated". How should the agent's own validation reach the recommendation step?
- Does the agent also run
- Session token through a provider. Can OpenShell credential injection rewrite the
Authorization: Bearerheader thatant beta:worker runsends? That header form is itself unverified. Wouldantaccept a placeholder token, and would the host create and delete a provider for each session? Today the exact-token check before posting is the only guard. - Malformed IDs.
on-work.shchecks the environment, session, and work IDs against formats guessed from the docs. A bad environment or work ID posts a "sandbox" failure notice; a bad session ID only writes a host log line. The maintainer has deferred reviewing this behavior. - GitHub identity and reach.
triage-submitter-profilecallsusers/<login>andorgs/<org>/members/<login>, which the provider allowlist does not include.triage-recommend-actionschecks write access throughrepos/NVIDIA/OpenShell/collaborators/<login>/permission, which needs a token with push access. The current design assumes a read-only token.- Should the policy widen, the token change, or those signals be dropped in the agent?
- Slack security standard. Does NVIDIA's Agentic Slackbot Security Standard apply to agent-generated posts sent through the already-approved webhook? Until someone answers, the webhook URL would live in a 0600 file on the worker host.
- Model. Sonnet-class, as in the published example, or a larger model at higher cost?
- Unverified API shapes. The README lists them: session-create request and budget unit, session-token header, session metadata readback, and the interrupt event. Each needs one test session before the workflow is enabled.
Activity