Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
# Azure SDK Tools MCP
#####################
/eng/common/instructions/azsdk-tools/ @maririos @praveenkuttappan @MrJustinB
/.github/extensions/rpi/ @JennyPng
# PRLabel: %azsdk-cli
/tools/ai-evals/azsdk-mcp/ @jeo02 @smw-ms @praveenkuttappan @maririos
# PRLabel: %azsdk-cli
Expand Down
2 changes: 2 additions & 0 deletions .github/extensions/rpi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.rpi/
160 changes: 160 additions & 0 deletions .github/extensions/rpi/EXECUTION-LOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Execution Log — CLI-Extension Phased Workflow

Implementation of `cli-extension-impl.md`. Records every action taken, the justification, and any
deviation from the plan. The plan's design principle (lean on the agent, minimize deterministic
code) was followed throughout: the result is `extension.mjs` + `prompts/` with no `lib/` of
validators, gate parsers, artifact tools, or a state machine.

## Step 0 — Prerequisites

- Created `.github/extensions/agentic-workflow/package.json` (`type: module`, dependency
`@github/copilot-sdk ^1.0.4`).
- `npm install` → installed SDK v1.0.4 (confirmed `node_modules/@github/copilot-sdk/package.json`
version 1.0.4).
- Verified import: `node -e "import('@github/copilot-sdk/extension')…"` → `ok`.
- **Verification of SDK surface (v1.0.4 .d.ts):** confirmed every API the plan/design name before
using it — `joinSession` (extension.d.ts), `CustomAgentConfig {name,model,tools:string[]|null,
prompt}` (types.d.ts:1157), `customAgents`/`infiniteSessions` on the join config
(1626/InfiniteSessionConfig), `session.rpc.agent.select
({name})` (rpc.d.ts), `session.sendAndWait(prompt|MessageOptions,timeout)` returning
`AssistantMessageEvent` with text at `.data.content` (session.d.ts:126, session-events.d.ts:2634),
`session.ui.{confirm,select,input,elicitation}` gated on `session.capabilities.ui?.elicitation`
(types.d.ts SessionUiApi), `session.setModel` (session.d.ts:268), `session.rpc.commands.enqueue
({command})` (rpc.d.ts:14553), `session.abort()` (254), `session.log` (280), and
`commands: CommandDefinition[]` with `handler(ctx)` + raw `ctx.args` (types.d.ts:425,1424).

## Step 1 — Prompt templates (durable IP)

Created `prompts/{01-research,02-assumptions,03-classify,04-research-item,05-plan,06-implement,
critique}.md`, carried over from the prototype's reasoning content (the durable IP) with the
plan's three strips applied:

1. `write_artifact`/`read_artifact` custom-tool references + run-context preamble → "write/read this
file under the run directory (`{{runDir}}`) with your normal file tools".
2. The machine-readable `stages:`/`gate:` YAML block the orchestrator parsed → "define your stages
**in prose** with explicit gate commands and **run the gates yourself**" (05-plan.md, 06-implement
reads prose stages).
3. Fresh-isolated-session / external-validator wording → the `PHASE_RESULT: pass|fail|needs_input`
self-report. Blocking assumptions now surface as `needs_input` instead of an orchestrator-parsed
`blocking: true` pause.

Kept `{{task}}` and added `{{runDir}}`/`{{priorErrors}}` placeholders (verified each template uses
only those three — supplied by `dispatch`). Each template now self-checks its output; 06-implement
additionally runs the gate commands.

**Deviation D1 — research-item is one agent over all sub-items, not N dispatches.** The prototype
dispatched phase 4 once per sub-item with `{{item}}`/`{{itemId}}` placeholders. To stay agent-first
and keep `dispatch` uniform (only `{{task}}/{{runDir}}/{{priorErrors}}`), `04-research-item.md`
instructs the single `aw-research-item` agent to read `subitems.json` itself and produce
`research/<id>.md` for every item in dependency order. Lower-maintenance, no per-item orchestration
code. (Aligns with @JennyPng's "lean on the agent" preference.)

**Deviation D2 — implement is one agent over all stages, not one dispatch per stage.** The prototype
ran a fresh session per stage with `{{stage}}`/`{{handoff}}`/`{{cumulativeDiff}}` injected. Here the
single `aw-implement` agent reads the whole prose plan and works stage-by-stage, running each stage's
gates before advancing. This removes the stage state machine and the YAML gate parser entirely — the
explicit goal of the plan ("no state machine, run gates yourself").

**Deviation D3 — `/aw-judge` revises via the author phase, no separate `revise.md`.** The prototype
had a `revise.md` adjudication prompt. The impl plan's `/aw-judge` spec is "run critique, then re-run
the author phase with the critique as `priorErrors`", so the author phase's own template handles the
revision. `revise.md` was intentionally dropped.

## Steps 2–5 — `extension.mjs`

Single file: a phase registry (`PHASES` + `CRITIQUE`) carrying per-phase model, tool access,
template, and the sentinel artifact for completion detection; `dispatch`; `autoRun`; thin command
handlers; and the `joinSession` config.

- **Step 2 (agents + tool access).** Built `customAgents` (one per phase incl. critique) with
`tools:null` and a stable role `prompt`, plus `infiniteSessions.enabled`. Every phase gets all
tools so it can create/write its own artifacts under the run directory.

**Deviation D4 — models repinned at runtime via `setModel`, not pinned in `customAgents[].model`.**
The plan's table pins a model per agent and `/aw-model` is supposed to repin live. But
`customAgents` is fixed at join time, and a pinned `customAgents[].model` would *win* over
`session.setModel`, making `/aw-model` a no-op without an `/extensions reload`. To make `/aw-model`
genuinely work (the design's "repin models" goal) I omit `customAgents[].model`, store each phase's
model in the registry, and apply it via `session.setModel(phase.model)` immediately before
`agent.select` in `dispatch`. Agents with no pinned model fall back to the session model
(CustomAgentConfig.model docs), so this is clean. `/aw-model` mutates `phase.model` for the next
dispatch. Net: same per-phase pinned-model behavior, but live-repinnable.

**Deviation D5 — agent `prompt` is a short role line; full filled template delivered per dispatch.**
The plan put "the phase prompt" in `customAgents[].prompt`. But the templates contain
`{{task}}/{{runDir}}` unknown at join time, so a statically-pinned template would leak literal
`{{…}}` into agent context. Instead each agent's pinned `prompt` is a concise role/identity line
and the fully-substituted phase instructions are sent per-dispatch via `sendAndWait`. The prompt
files remain the durable IP; only their delivery moment changed. Avoids double-injection and
placeholder leakage.

- **Step 3 (dispatch + commands).** Run dir = `<cwd>/.aw/<task-slug>/` (repo-agnostic, derived from
`process.cwd()`); active run tracked in memory; next phase derived by `fs.existsSync` checks on
each phase's sentinel artifact (no `state.json`). `dispatch(phase,{priorErrors})` selects the
agent, repins model, substitutes the three placeholders, `sendAndWait`s, and parses the
`PHASE_RESULT` sentinel with one regex (`parsePhaseResult`). Thin handlers for `/aw-start`,
`/aw-research`…`/aw-implement`, `/aw-continue [n]`, `/aw-judge`, `/aw-redo`, `/aw-model`,
`/aw-status` (artifacts + `git diff --stat`, degrades on non-git).

**Minor D6 — templates embed `{{task}}`, so `dispatch` does not also prepend the raw task.** The
plan sketch wrote `prompt: task + "\n\n" + template`. Since every template already has a `## Task
{{task}}` section, prepending would duplicate it; `dispatch` just substitutes.

- **Step 4 (auto-runner + elicitation).** `autoRun({from,to,unattended,pauseAt})` walks the phase
order; per phase branches on `PHASE_RESULT`: `pass`→advance; `needs_input`→`askHuman` (UI
`input` when `capabilities.ui?.elicitation` && not unattended, else a safe-default answer);
`fail`→retry up to `MAX_RETRIES=2` with the agent's reason as `priorErrors`, then `resolveFailure`
(UI `select` retry/skip/abort, or abort when unattended/no-UI). `/aw-pause` sets a cooperative flag
checked each boundary; `pauseAt` honored as a breakpoint. `from` defaults to the next incomplete
phase, `to` defaults to `implement`.

- **Step 5 (context hygiene).** `/aw-compact` enqueues `/compact` via
`session.rpc.commands.enqueue`; `dispatch` auto-enqueues `/compact` before the `implement` phase;
`infiniteSessions:{enabled:true}` set in the join config. All state on disk → resume by re-deriving
the next phase from existing files.

- **Removed the old shim.** The prior `extension.mjs` (a thin front-door delegating to the built
prototype `dist/`) was deleted and fully replaced.

## Step 6 — Replace prototype, document

- Deleted `tools/agentic-workflow/` (prototype). Verified no remaining active references in
`.github/`, `eng/`, or `README.md`.
- `README.md` index row for `agentic-workflow` repointed to
`.github/extensions/agentic-workflow/README.md` with an updated description.
- `.github/CODEOWNERS`: `/tools/agentic-workflow/ @JennyPng` →
`/.github/extensions/agentic-workflow/ @JennyPng`.
- Wrote `.github/extensions/agentic-workflow/README.md` (purpose, layout, phase/agent table,
install, command list, manual/auto modes, `PHASE_RESULT` convention, develop/test).
- Added `.gitignore` (`node_modules/`, `.aw/`) and a `test` npm script.
- **Light test (optional in plan):** `test/smoke.test.mjs` (node:test) covers the only non-trivial
pure logic — `parsePhaseResult`, `parseKv`, `isTruthy`, `slugify` — plus the `joinConfig` shape
(seven agents, all-tools phase access, command surface). Guarded the top-level
`joinSession` behind `AW_SKIP_JOIN` so the module imports cleanly under test without a live host.

Left the older `agentic-workflow-design.md` (the *prototype's* historical design doc) untouched —
out of scope for this implementation; only `cli-extension-design.md`/`cli-extension-impl.md` drive
this work.

## Validation performed

- `node --check extension.mjs` → clean.
- `npm test` → 8/8 pass (pure logic + join-config shape).
- `npm ci` → lockfile consistent, 0 vulnerabilities.
- SDK import smoke (`@github/copilot-sdk/extension`) → ok.

**Not run here:** the live `/extensions info agentic-workflow` and a real end-to-end `/aw-start →
… → /aw-implement` run require the interactive Copilot CLI host, which isn't available in this
environment. The join config is verified against the v1.0.4 type definitions and the
`joinConfig`-shape test; these are the Step 1–6 "Verify" items achievable offline. The remaining
live verifications are the first manual dogfood per the plan's Rollout step 1 (spike on a throwaway
branch).

## What is code vs delegated to the agent (as built)

Irreducible code: `customAgents` registry (tool access + role) + runtime `setModel` repin;
thin `commands` + `parseKv`/`isTruthy` arg parsing;
`dispatch` + `parsePhaseResult` (one regex); `autoRun` loop + stop branching; `session.ui`
elicitation calls. Delegated to the agent: artifact correctness/self-check, running gate commands,
stage breakdown/sequencing, reading/writing artifacts, classification + research fan-out, judging
critiques.
150 changes: 150 additions & 0 deletions .github/extensions/rpi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# RPI — Copilot CLI extension

A [Copilot CLI](https://github.com/github/copilot-cli) extension that drives a disciplined
**research → plan → implement** workflow. Each phase runs with all tools and its own prompt, handing
off to the next through **artifacts on disk** under `.rpi/`. Drive it one phase at a time and inspect
artifacts between steps, or let the auto-runner chain phases to completion. Every phase runs on your
configured session model — the workflow never switches models.

## Install

> Requires experimental mode: run `/experimental on`, or start with `copilot --experimental`.

The CLI auto-discovers extensions in `.github/extensions/` (project) and `~/.copilot/extensions/`
(user). This extension ships under `.github/extensions/rpi/`. Install its deps once:

```bash
cd .github/extensions/rpi && npm install
```

Then `/clear` (extensions reload on `/clear`, or restart the CLI) and run `/env` to confirm `rpi` loaded.

### Use it globally (any repo)

The project install only loads inside this repo. To get `/rpi-*` in **every** repo, put the
extension in the user directory (`~/.copilot/extensions/`), which the CLI scans from any working
directory. Two ways:

**Symlink** — stays in sync as you `git pull`, and reuses the repo's already-installed deps:

```bash
mkdir -p ~/.copilot/extensions
ln -s "$(git rev-parse --show-toplevel)/.github/extensions/rpi" ~/.copilot/extensions/rpi
```

> Caveat: a symlink tracks a working-tree path, so it breaks if you switch the repo to a branch that
> doesn't contain `.github/extensions/rpi/` (the folder disappears until you switch back). Use a
> detached copy if you want it independent of the repo's current branch.

**Detached copy** — a snapshot independent of the repo's branch; update it manually by re-copying:

```bash
mkdir -p ~/.copilot/extensions
cp -r "$(git rev-parse --show-toplevel)/.github/extensions/rpi" ~/.copilot/extensions/rpi
cd ~/.copilot/extensions/rpi && npm install
```

Either way: `/clear` (or restart) then `/env` to confirm `rpi` loaded. Uninstall with
`rm ~/.copilot/extensions/rpi`. Run artifacts still land in `.rpi/` of whatever repo you launch the
CLI from, so each project keeps its own runs.

## Quickstart

```
/rpi-auto Add CSV export to the report endpoint # start + auto-run the whole flow to implement
```

Or drive it one phase at a time:

```
/rpi-start Add CSV export to the report endpoint # init the run + run the first phase
/rpi-status # phase checklist + artifacts + git diff
/rpi-continue # run the next phase, then stop
/rpi-auto # or auto-run the rest to implement
```

Want a shorter loop? `/rpi-auto-simple` (or `/rpi-start-simple`) skips the research, classify, and
per-item research phases:

```
/rpi-auto-simple Fix null deref in TokenCache
```

Everything a run produces lives under `<cwd>/.rpi/<task-slug>-<hash>/` — specs, assumptions, the
plan, and a `state.json` tracking which phases passed. Inspect or edit those files between phases. If
your session reloads, pick the run back up with `/rpi-resume`.

## Commands

| Command | Behavior |
| --- | --- |
| `/rpi-auto <task>` | Start a run (if given a task) and auto-run to completion. On an existing run with no task, auto-runs the rest. Accepts `[from:<p>] [to:<p>] [unattended:true] [pause-at:<p>]`. |
| `/rpi-auto-simple <task>` | Same as `/rpi-auto` but the short flow (assumptions → plan → implement). |
| `/rpi-start <task>` | Init the run dir and run the first phase (full flow). |
| `/rpi-start-simple <task>` | Same, but the short flow. Equivalent to `/rpi-start <task> simple`. |
| `/rpi-resume [name-or-text]` | Resume a run after a reload. No arg resumes the only/most-recent run; otherwise matches by dir name or task text. |
| `/rpi-continue [n]` | Run the next phase (or next `n`), then stop. |
| `/rpi-pause` | Stop the auto-runner at the next phase boundary. |
| `/rpi-research` … `/rpi-implement` | Run one phase by name (`research`, `assumptions`, `classify`, `research-item`, `plan`, `implement`). |
| `/rpi-redo <phase> <feedback>` | Re-run a phase with steering notes appended to its prompt. |
| `/rpi-judge [artifact\|diff]` | Rubber-duck an artifact (appends a critique to the file), or `diff` to critique the current git diff. No arg critiques current work inline. |
| `/rpi-autojudge [on\|off]` | Toggle auto-judge: rubber-duck each new artifact right after its phase passes, and the git diff after `implement`. No arg flips it. |
| `/rpi-subagents [on\|off]` | Toggle execution mode (see below). No arg flips it. |
| `/rpi-status` | Show execution mode, auto-judge state, phase checklist, artifacts, and `git diff --stat`. |
| `/rpi-help` | Brief in-session explanation of the workflow and main commands. |
| `/rpi-compact` | Reclaim context by queuing `/compact`. |

## Execution modes

- **Manual** — `/rpi-<phase>` or `/rpi-continue`; stops after every phase so you can inspect artifacts.
- **Ranged auto** — `/rpi-auto from:assumptions to:plan`; stops at the boundary.
- **Full auto** — `/rpi-auto <task>` (add `unattended:true` to skip prompts); runs to `implement`,
halting only at `needs_input` or hard failure. Interactively, a `needs_input` stop opens a dialog;
a `fail` retries up to twice, then asks retry/skip/abort. Unattended runs auto-resolve with safe
defaults and halt only on hard failure.

**Compact vs sub-agents** (`/rpi-subagents`, persisted per run): by default (**COMPACT**) phases run
inline in your session and stream natively, with a `/compact` before the heavy implement phase.
Toggle **SUB-AGENTS** on to run each phase in its own fresh context window (activity relayed live) —
this engages **only during an unattended auto-run** (`/rpi-auto … unattended:true`); every
interactive path stays COMPACT.

## Configuration

- **Model** — every phase uses your configured session model (recommended: `claude-opus-4.8`); set it
before starting a run.
- **Critique** — `/rpi-judge` for on-demand review, `/rpi-autojudge` for automatic per-phase review.
Critiques are append-only and never rewrite your artifacts.

## Run directory & resume

State lives in `<cwd>/.rpi/<task-slug>-<hash>/`. A phase counts as complete only when it reported
`pass` **and** its artifact exists. Run metadata (task, flow, toggles) is persisted to `state.json`,
so after a reload:

- `/rpi-resume` — resumes the only run under `.rpi/` (lists them if there are several).
- `/rpi-resume <dir-name>` — e.g. `/rpi-resume add-csv-export-1a2b3c4d`.
- `/rpi-resume <task-substring>` — e.g. `/rpi-resume CSV export`.

`.rpi/` is auto-added to the repo's `.gitignore` on `/rpi-start`, so run artifacts never leak into commits.

## Phases

| Phase | Writes |
| --- | --- |
| research | `specs/*.md`, `manifest.json` |
| assumptions | `assumptions.md` |
| classify | `subitems.json`, `classification.md` |
| research-item | `research/<id>.md` |
| plan | `plan.md` |
| implement | code edits + `execution-log.md`, `handoff.md` |

## Develop / test

```bash
npm test # node --test: arg/sentinel parsing, phase-continuity, and resume state-machine tests
```

Two `*.live.mjs` checks (require an authenticated Copilot CLI; they make real model calls) exercise
the sub-agent path end to end. The durable IP is the per-phase prompts in `prompts/` plus the on-disk
artifact contract — keep prompts repo-agnostic and let the agent discover repo specifics at runtime.
Loading
Loading