Repository navigation
Description
What happened
The implement shell allows only git, uv, ruff and mypy. Calls to find, cat, python and others are refused and count against a limit, and "if you use the limit up, the step stops and all your work is lost". The agent cannot use devops ci, the AST graph, repomap, RAG, devops review or the mutation check. It guesses where the project's own tools would tell.
What should happen
- A declared tool catalog for the agent, with a policy per tool (read-only, scoped write, network or none) and a budget:
devops ci test <path>,devops ciscoped to the changed paths,devops ci fail-to-pass,devops ai ast-graph,devops ai repomap,devops ai rag-query,devops reviewon the diff,devops docs generate,devops openspec check,kubectlandkube-linterread-only for manifests. - The runner image carries every tool in the catalog (see F4). A refused call names the tool to use instead, and does not count against the stop limit when the agent then uses it.
Acceptance criteria
- The catalog lives in one table that the prompt, the shell guard and the docs read.
- Each tool has a test that the agent can call it in the runner shell and that its policy holds (a write tool cannot leave the checkout).
- The refusal limit counts only calls that the catalog has no entry for.
- The prompt lists the tools from the table, and a test fails if the table and the prompt disagree.
Related: tracker for this set (filed with it), #2908 (issue workflow), #2867 (draft PR evaluator), #3005 (task kind), #3010 to #3015 (plan gates), #2919 (stuck-item metrics), #2953 (Ollama parser).
Delivery
The pull request of this issue lists the local devops commands (ci test, ai repomap, ai ast, docs generate, openspec check) in the shell catalog and the prompt, from one table (LOCAL_COMMANDS). Each part of the item is here, in an earlier pull request, or in another issue:
- Catalog in one table that the prompt, the shell guard and the docs read: done for programs by feat(runner): the runner image carries every development tool, and the shell policy is one table #3072 (PR feat(roadmap): add gate tools to the runner, one shell policy #3111). For the local
devopscommands, this pull request addsLOCAL_COMMANDS, which the prompt and the docs read. The shell guard does not read it: a call ofuv run devops ...passes becauseuvis a program ofSHELL_PROGRAMS. A guard that reads the access class of a local command is part of feat(roadmap): enforce scoped writes and refusal budgets for catalog tools #3565. - The prompt lists the tools from the table, and a test fails when they differ: delivered by feat(runner): the runner image carries every development tool, and the shell policy is one table #3072 and by this pull request.
- A test that the agent can call each local command in the runner shell: delivered by this pull request. The test that a write tool cannot leave the checkout: feat(roadmap): enforce scoped writes and refusal budgets for catalog tools #3565.
- The refusal limit counts only calls that the catalog has no entry for: feat(roadmap): enforce scoped writes and refusal budgets for catalog tools #3565.
kubectlandkube-linterin the catalog and the runner image: feat(roadmap): Add kubectl and kube-linter to runner tool catalog #3562.devops reviewanddevops ai rag-queryfrom the shell: feat(roadmap): allow scoped review and RAG calls from the implement shell #3563.
Alternatives check of 2026-11-04: partly.
Tool: open-policy-agent/opa (https://github.com/open-policy-agent/opa), Apache-2.0, last activity 2026-11-04, maintained.
Proposed design
Problem statement
The implement agent does not know which local devops commands it may run. The shell already allows uv, so these commands run, but the prompt lists only uv run devops ci test. The agent guesses or uses general commands. This change adds the local read, check and docs commands to the existing shell catalog and prompt table.
Acceptance criteria
Requirement: Local devops commands in catalog
The implement shell catalog SHALL list devops ai ast, devops ai repomap, devops docs generate, and devops openspec check as local commands with a read-only or checkout-write policy.
Scenario: Catalog entries exist
- WHEN The shell guard loads the catalog
- THEN Each of the four commands has an entry with its policy and no network policy.
Requirement: Prompt lists local commands
The implement prompt SHALL list each local devops command from the catalog table.
Scenario: Prompt matches catalog
- WHEN The prompt is generated for an implement step
- THEN Every local devops command in the catalog appears in the prompt and no other local devops command appears.
Requirement: Catalog and prompt consistency
A test SHALL fail when the catalog table and the implement prompt disagree on a local devops command.
Scenario: Missing prompt entry
- WHEN A local devops command is added to the catalog but not to the prompt
- THEN The consistency test fails and names the missing command.
Requirement: Cataloged command does not count
A call to a cataloged local devops command SHALL not increment the refusal counter.
Scenario: Cataloged local call
- WHEN The agent calls
uv run devops ai repomap - THEN The call is allowed and the refusal counter is unchanged.
Existing Tool Considered
open-policy-agent/opa (Apache-2.0, maintained) covers per-tool allow and deny decisions and parameter-level rules. It lacks a single catalog table that the prompt and docs read, per-tool per-step budgets and a refusal counter, the project's own tools and runner image, scoped-write confinement, and a refusal message that names the tool to use instead. The project keeps its typed Python catalog module. The written cost is about 10,000 tokens to build and test the table, guard, prompt and docs, and about 2,000 tokens per month to keep them in sync, over 2 to 3 days.
Key questions
- Where does the catalog table live in the codebase?: The catalog stays in
src/devops_cli/roadmap/implement_shell_policy.pyas a typed Python table. The guard, the prompt generator and the docs read it. A TOML, YAML or JSON file would add a parser and a second source. (best practice) [sources: src/devops_cli/roadmap/implement_shell_policy.py, docs/ROADMAP_WORKFLOW.md]- Chosen: Typed Python module. The guard is Python code. Importing the table is the shortest path. Type checking catches errors at build time.
- Rejected: TOML or YAML file. Adds a parser and a separate file to keep in sync. The guard needs a parser dependency.
- Rejected: JSON file. Adds a parser and no schema validation without another dependency.
- What is the budget per tool?: The budget is a maximum call count per tool per step. The catalog entry holds the number, and the guard resets the count at each step. This change does not change the budget. The enforcement of refused arguments stays deferred. (best practice) [sources: src/devops_cli/roadmap/implement_shell_policy.py, docs/ROADMAP_WORKFLOW.md]
- Chosen: Per-step call count. The step is the unit of work. A reset each step keeps a long session from using up the budget.
- Rejected: Per-session call count. A session spans steps. This is too restrictive for multi-step items.
- Rejected: No budget. The issue requires a budget.
- What are the policy values for each tool?:
devops ai astanddevops ai repomapare read-only.devops openspec checkis read-only.devops docs generateis checkout-write and may write only inside the checkout.kubectl,kube-linter,devops reviewanddevops ai rag-queryare out of this change. (best practice) [sources: src/devops_cli/roadmap/implement_shell_policy.py, src/devops_cli/roadmap/implement_run.py, docs/ROADMAP_WORKFLOW.md]- Chosen: Read-only and checkout-write. The four local commands read code or write only generated docs in the checkout. They need no network.
- Rejected: Network policy for review and RAG. The shell has no key. The job process holds the credentials. This part waits on a separate security design.
- Rejected: Read-only for every local command.
devops docs generatewrites files. A read-only label would be false.
- How does the refusal message work?: The guard reads the refusal table and names the cataloged tool for the refused program. An uncataloged program increments the refusal counter. The change adds the local commands to that table and keeps the existing hint path. [sources: src/devops_cli/roadmap/implement_shell_policy.py, docs/ROADMAP_WORKFLOW.md]
- Which tools are in the initial catalog?: The initial catalog keeps the current shell programs and adds
uv run devops ci test,devops ai ast,devops ai repomap,devops docs generateanddevops openspec check. It leaves outkubectl,kube-linter,devops reviewanddevops ai rag-query. (best practice) [sources: src/devops_cli/roadmap/implement_shell_policy.py, src/devops_cli/roadmap/runner_tools.py, docs/ROADMAP_WORKFLOW.md]- Chosen: Current programs plus local devops commands. The shell already allows
uv, so these commands run. The prompt only needs to name them. - Rejected: Add kubectl and kube-linter.
NOT_IN_RUNNERleaves them out. A Job has no cluster token, and a scanner on PATH fails tests that pass in the gate. - Rejected: Add review and RAG. The shell has a scratch HOME and no key. The job process holds the credentials.
- Chosen: Current programs plus local devops commands. The shell already allows
- Should the project adopt open-policy-agent/opa for the catalog?: No. OPA covers per-tool allow and deny decisions, but it has no single catalog table that the prompt and docs read, no per-tool per-step budget or refusal counter, and no scoped-write confinement. The project keeps its typed Python table. (best practice) [sources: src/devops_cli/roadmap/implement_shell_policy.py]
- Chosen: Typed Python catalog. It is the single source that the guard, prompt and docs already read. It needs no new service.
- Rejected: OPA policy engine. It covers policy evaluation but lacks the catalog, budget, prompt consistency and scoped-write enforcement.
- Rejected: OPA plus a Python catalog. Two sources must stay in sync. The current table already covers the need.
- Is scoped-write enforcement part of this change?: No. The shell check reads the first word only and is no security boundary. The pod is the sandbox. A test that a write cannot leave the checkout needs the real sandbox of feat(roadmap): run the implement agent shell in a real sandbox #2695. This part is deferred. (best practice) [sources: src/devops_cli/roadmap/implement_run.py, CONTEXT.md]
- Chosen: Defer to feat(roadmap): run the implement agent shell in a real sandbox #2695. The current shell cannot enforce path confinement. The sandbox item is open and is a person item.
- Rejected: Enforce in the shell guard.
uv run pythoncan write anywhere the user can. A first-word check cannot stop it. - Rejected: Drop the requirement. The requirement protects the checkout. It stays as a deferred issue.
Fit
Fits in one pull request.
Suspected block
None.
Dependencies
None.
Activity
dan-petty commentedon Nov 1, 2026
Intake placed this item to the backlog: intake puts every new item in the backlog. A person plans the milestones.
featprefix of the title gives the type.(roadmap)scope of the title gives the scope.The implement agent cannot finish tasks without the project tools, but it is a feature build, not a blocking outage.The implement agent is the core worker of the roadmap; without its tools it guesses and loses completed work on every run.Requires a catalog table, per-tool policy enforcement, runner image changes, shell guard updates, prompt generation, and a test per tool.Both give a roadmap agent access to project tools; #3037 targets the stage planner with read-only investigation tools, while the candidate targets the implement agent with scoped-write development tools.devops-cli-service commentedon Nov 1, 2026
Refine cannot set this item to Ready at aad0115 until a person answers the questions below. Reply in a comment or edit the body, and the next refine round looks at the item again.
devops-cli-service commentedon Nov 4, 2026
Alternatives check: partly
Tool: open-policy-agent/opa (https://github.com/open-policy-agent/opa)
Covers:
Lacks:
Searches run:
Sources:
Checked on 2026-11-04. A person decides what this means for the item. The check changes no label and closes no issue.
dan-petty commentedon Nov 5, 2026
This item cannot be built as written. Checked on main at 9e439fa.
Part of the item is already done (#3072, PR #3111).
SHELL_PROGRAMSandSHELL_REFUSALSinsrc/devops_cli/roadmap/implement_shell_policy.py. The guard, the refusal hint, the prompt (roadmap_implement_step.md) anddocs/ROADMAP_WORKFLOW.mdread it. Tests fail when they differ.git,uv,ruff,mypy,grep,cat,ls,head,tailandfind. The text of the issue ("only git, uv, ruff and mypy";findandcatrefused) is out of date.uvis allowed, souv run devops ci test <file>and every localdevopscommand already run. The prompt namesuv run devops ci test.The rest conflicts with decisions that exist.
kubectlandkube-linterin the image:NOT_IN_RUNNERinrunner_tools.pyleaves them out on purpose. A Job has no cluster token, and a scanner on PATH fails tests that pass in the gate (test(floor): every test sees the same tokenizer cache, mounts, process table, PATH and programs on every host, and no skip depends on the host #2165). To add them is a new decision for a person.devops review,devops ai ragand any call to the gateway: the shell has a scratch HOME and no key. The job process holds the credentials, clears its dumpable flag so the shell cannot read them (feat(k8s): the implement job runs as a cluster Job on a runner image with uv, the dev dependencies and the pinned tools, submitted by the Service #2667), and runs nogit/ghcall in the shell. To give the shell a key undoes that design.implement_run.pystates that the shell check reads the first word only and is no security boundary, and that the pod is the sandbox.uv run pythoncan write anywhere the user can. A test of that policy needs the real sandbox of feat(roadmap): run the implement agent shell in a real sandbox #2695, which is a person item and is open.find -exec) too. The change is small, but it needs a decision whether a loop of such calls may run unbounded. The refine round left the three questions (policy values, refusal text, first catalog) open for a person.What is left, if a person wants it. A small item: list
devops ai repomap,devops ai ast,devops docs generateanddevops openspec checkin the prompt table as local read-or-checkout-write tools, after #2695 sets what "scoped write" means. I wrote no code, as no part of the item stands alone without these decisions.devops-cli-service commentedon Nov 5, 2026
Status set to Ready at e38c8f3 (see proposed design in issue body).
Moved to new items: #3562, #3563, #3565.
dan-petty commentedon Nov 6, 2026
Answer from project standards, under the owner's instruction of 2026-11-05.
Question: what stays of this issue after the refine round moved parts to #3562, #3563 and #3565, and after #3072 (PR #3111)? Checked on origin/main at be31875.
Answer: one small part stays, and I build it.
SHELL_PROGRAMS/SHELL_REFUSALSinsrc/devops_cli/roadmap/implement_shell_policy.py, the refusal hint, the prompt and the document read it, and tests fail when they differ.uv run devops ci test. It does not listdevops ai repomap,devops ai ast,devops docs generateanddevops openspec check. The change adds a typed tableLOCAL_COMMANDS(accessreadorwrite, nevernetwork), a third placeholder in the prompt, the same table indocs/ROADMAP_WORKFLOW.md, and tests: table, prompt and document agree; each command is a command of thedevopsapp; a call throughuv runis no refusal.kubectlandkube-linterto feat(roadmap): Add kubectl and kube-linter to runner tool catalog #3562,devops reviewand RAG to feat(roadmap): allow scoped review and RAG calls from the implement shell #3563, scoped writes and the refusal budget to feat(roadmap): enforce scoped writes and refusal budgets for catalog tools #3565.Rule: AGENTS.md rules 1 and 2 (one issue, one pull request; the premise is checked against the real system), and the rule on partial work. The issue body now lists what the pull request delivers and which issue holds each moved criterion.
devops-cli-service commentedon Nov 6, 2026
Released in v0.66.0: https://github.com/dan-petty/devops-cli/releases/tag/v0.66.0
devops-cli-service commentedon Nov 6, 2026
Live since 2026-11-06 06:16 UTC