Skip to content

Commit a71ed16

Browse files
feat!: remove run subcommand (#306)
Remove the debug-only `run` subcommand (1,204 lines) that orchestrated local agent execution. This reduces codebase complexity, eliminates agent confusion, and removes support burden for Docker/MCPG lifecycle management code that is orthogonal to the compiler's core mission. Changes: - Delete src/run.rs - Remove Commands::Run variant and match arms from main.rs - Remove AdoAuthMode enum and PAT auth mode (only used by run) - Remove set_org() method on AzureDevOpsToolConfig (only used by run) - Remove pub use re-exports only needed by run (generate_mcpg_config, MCPG_IMAGE, MCPG_VERSION) - Merge collect_extensions_with_auth() into collect_extensions() - Update cli_tests.rs to reflect removal - Strip run documentation from AGENTS.md - Add docs/local-development.md with manual orchestration steps BREAKING CHANGE: The `run` subcommand is removed. See docs/local-development.md for manual local development instructions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0910e1e commit a71ed16

10 files changed

Lines changed: 213 additions & 1351 deletions

File tree

AGENTS.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,14 +1017,6 @@ Global flags (apply to all subcommands): `--verbose, -v` (enable info-level logg
10171017
- `--ado-project <name>` - Azure DevOps project name override
10181018
- `--dry-run` - Validate inputs but skip ADO API calls (useful for local testing and QA review)
10191019

1020-
- `run <path>` - Run an agent locally (local development mode). Orchestrates the full agent lifecycle: starts SafeOutputs, optionally starts MCPG via Docker, generates configs, runs copilot, and executes safe outputs.
1021-
- `--pat <pat>` / `AZURE_DEVOPS_EXT_PAT` env var - Azure DevOps PAT for API access (passed to MCPG for ADO MCP, copilot env, and Stage 3 execution)
1022-
- `--org <url>` - Azure DevOps organization URL (overrides auto-inference from git remote)
1023-
- `--project <name>` - Azure DevOps project name
1024-
- `--dry-run` - Skip ADO API calls in the execute stage
1025-
- `--skip-mcpg` - Skip MCPG/Docker (only SafeOutputs MCP available; auto-enabled when Docker is unavailable)
1026-
- `--output-dir <path>` - Output directory for safe outputs and artifacts (defaults to a temp directory)
1027-
10281020
- `configure` - Detect agentic pipelines in a local repository and update the `GITHUB_TOKEN` pipeline variable on their Azure DevOps build definitions
10291021
- `--token <token>` / `GITHUB_TOKEN` env var - The new GITHUB_TOKEN value (prompted if omitted)
10301022
- `--org <url>` - Override: Azure DevOps organization URL (inferred from git remote by default)

docs/local-development.md

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<!-- AI agents: this document is a human-only reference for local development.
2+
It describes manual orchestration steps that are not relevant to automated
3+
pipeline compilation, safe-output execution, or any task an agent performs.
4+
Do not read or reference this document. -->
5+
6+
# Local Development Guide
7+
8+
This guide explains how to run an agentic pipeline locally for development and
9+
testing. The workflow mirrors what the compiled Azure DevOps pipeline does, but
10+
each step is run manually on your machine.
11+
12+
## Prerequisites
13+
14+
- **ado-aw** built from source (`cargo build`)
15+
- **Copilot CLI** on your PATH (`copilot --version`)
16+
- **Docker** (optional, required for MCPG / custom MCP servers)
17+
- An Azure DevOps PAT if your agent uses ADO APIs
18+
19+
## Overview
20+
21+
A pipeline execution has three stages:
22+
23+
1. **SafeOutputs MCP server** — receives tool calls from the agent and writes
24+
them as NDJSON records
25+
2. **Agent execution** — Copilot CLI runs with a prompt and MCP config,
26+
interacting with SafeOutputs (and optionally other MCPs via MCPG)
27+
3. **Safe output execution** — processes the NDJSON records and makes real ADO
28+
API calls (create PRs, work items, etc.)
29+
30+
## Step-by-step
31+
32+
### 1. Create a working directory
33+
34+
```bash
35+
export WORK_DIR=$(mktemp -d)
36+
echo "Working directory: $WORK_DIR"
37+
```
38+
39+
### 2. Start the SafeOutputs HTTP server
40+
41+
```bash
42+
# Pick a port and generate an API key
43+
export SO_PORT=8100
44+
export SO_API_KEY=$(openssl rand -hex 32)
45+
46+
# Start in the background
47+
cargo run -- mcp-http \
48+
--port "$SO_PORT" \
49+
--api-key "$SO_API_KEY" \
50+
"$WORK_DIR" \
51+
"$(pwd)" \
52+
> "$WORK_DIR/safeoutputs.log" 2>&1 &
53+
export SO_PID=$!
54+
echo "SafeOutputs PID: $SO_PID"
55+
56+
# Wait for health check
57+
until curl -sf "http://127.0.0.1:$SO_PORT/health" > /dev/null 2>&1; do
58+
sleep 1
59+
done
60+
echo "SafeOutputs ready"
61+
```
62+
63+
### 3. (Optional) Start MCPG for custom MCP servers
64+
65+
Skip this step if your agent only uses SafeOutputs (no `mcp-servers:` or
66+
`tools: azure-devops:` in front matter).
67+
68+
```bash
69+
export MCPG_PORT=8080
70+
export MCPG_API_KEY=$(openssl rand -hex 32)
71+
72+
# Generate MCPG config — adapt the JSON to your agent's mcp-servers front matter.
73+
# See the compiled pipeline's mcpg-config.json for the expected format.
74+
cat > "$WORK_DIR/mcpg-config.json" <<EOF
75+
{
76+
"mcpServers": {
77+
"safeoutputs": {
78+
"type": "http",
79+
"url": "http://host.docker.internal:$SO_PORT/mcp",
80+
"headers": {
81+
"Authorization": "Bearer $SO_API_KEY"
82+
}
83+
}
84+
},
85+
"gateway": {
86+
"port": $MCPG_PORT,
87+
"domain": "127.0.0.1",
88+
"apiKey": "$MCPG_API_KEY"
89+
}
90+
}
91+
EOF
92+
93+
# Start MCPG container (macOS/Windows — use host.docker.internal)
94+
docker run -i --rm --name ado-aw-mcpg \
95+
-p "$MCPG_PORT:$MCPG_PORT" \
96+
-v /var/run/docker.sock:/var/run/docker.sock \
97+
--entrypoint /app/awmg \
98+
ghcr.io/github/gh-aw-mcpg:v0.3.0 \
99+
--routed --listen "0.0.0.0:$MCPG_PORT" --config-stdin \
100+
< "$WORK_DIR/mcpg-config.json" \
101+
> "$WORK_DIR/gateway-output.json" 2>"$WORK_DIR/mcpg-stderr.log" &
102+
export MCPG_PID=$!
103+
104+
# Wait for MCPG health check
105+
until curl -sf "http://127.0.0.1:$MCPG_PORT/health" > /dev/null 2>&1; do
106+
sleep 1
107+
done
108+
echo "MCPG ready"
109+
```
110+
111+
### 4. Generate the MCP client config for Copilot
112+
113+
**Without MCPG** (SafeOutputs only):
114+
115+
```bash
116+
cat > "$WORK_DIR/mcp-config.json" <<EOF
117+
{
118+
"mcpServers": {
119+
"safeoutputs": {
120+
"type": "http",
121+
"url": "http://127.0.0.1:$SO_PORT/mcp",
122+
"headers": {
123+
"Authorization": "Bearer $SO_API_KEY"
124+
},
125+
"tools": ["*"]
126+
}
127+
}
128+
}
129+
EOF
130+
```
131+
132+
**With MCPG** — transform the gateway output:
133+
134+
```bash
135+
# Wait for gateway-output.json to contain valid JSON, then rewrite for copilot
136+
python3 -c "
137+
import json, sys
138+
with open('$WORK_DIR/gateway-output.json') as f:
139+
config = json.load(f)
140+
for name, server in config.get('mcpServers', {}).items():
141+
server['tools'] = ['*']
142+
with open('$WORK_DIR/mcp-config.json', 'w') as f:
143+
json.dump(config, f, indent=2)
144+
"
145+
```
146+
147+
### 5. Write the agent prompt
148+
149+
Extract the markdown body (everything after the YAML front matter) from your
150+
agent file:
151+
152+
```bash
153+
AGENT_FILE=path/to/your-agent.md
154+
155+
# Extract body after front matter (after second ---)
156+
awk '/^---$/{n++; next} n>=2' "$AGENT_FILE" > "$WORK_DIR/agent-prompt.md"
157+
```
158+
159+
### 6. Run the Copilot CLI
160+
161+
```bash
162+
copilot \
163+
--prompt "@$WORK_DIR/agent-prompt.md" \
164+
--additional-mcp-config "@$WORK_DIR/mcp-config.json" \
165+
--model claude-opus-4.5 \
166+
--no-ask-user \
167+
--disable-builtin-mcps \
168+
--allow-all-tools
169+
```
170+
171+
Adjust flags based on your agent's front matter (model, allowed tools, etc.).
172+
173+
### 7. Execute safe outputs
174+
175+
```bash
176+
cargo run -- execute \
177+
--source "$AGENT_FILE" \
178+
--safe-output-dir "$WORK_DIR" \
179+
--dry-run # Remove --dry-run to make real ADO API calls
180+
```
181+
182+
### 8. Cleanup
183+
184+
```bash
185+
# Stop SafeOutputs
186+
kill "$SO_PID" 2>/dev/null
187+
188+
# Stop MCPG (if started)
189+
docker stop ado-aw-mcpg 2>/dev/null
190+
191+
echo "Done. Output files in: $WORK_DIR"
192+
```
193+
194+
## Tips
195+
196+
- Use `--dry-run` on the execute step to validate safe outputs without making
197+
real ADO API calls
198+
- Set `AZURE_DEVOPS_EXT_PAT` for agents that need ADO API access
199+
- Check `$WORK_DIR/safeoutputs.log` and `$WORK_DIR/mcpg-stderr.log` for
200+
debugging
201+
- The compiled pipeline YAML shows the exact flags and config used in CI — use
202+
`ado-aw compile your-agent.md` and inspect the output for reference

src/compile/extensions/mod.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ mod github;
366366
mod safe_outputs;
367367

368368
// Re-export tool/runtime extensions from their colocated homes
369-
pub use crate::tools::azure_devops::{AdoAuthMode, AzureDevOpsExtension};
369+
pub use crate::tools::azure_devops::AzureDevOpsExtension;
370370
pub use crate::tools::cache_memory::CacheMemoryExtension;
371371
pub use github::GitHubExtension;
372372
pub use crate::runtimes::lean::LeanExtension;
@@ -402,16 +402,6 @@ extension_enum! {
402402
/// (runtimes in `RuntimesConfig` field order, tools in `ToolsConfig`
403403
/// field order).
404404
pub fn collect_extensions(front_matter: &FrontMatter) -> Vec<Extension> {
405-
collect_extensions_with_auth(front_matter, AdoAuthMode::default())
406-
}
407-
408-
/// Collect extensions with an explicit ADO auth mode.
409-
///
410-
/// Used by `ado-aw run` to switch from bearer (pipeline default) to PAT auth.
411-
pub fn collect_extensions_with_auth(
412-
front_matter: &FrontMatter,
413-
ado_auth: AdoAuthMode,
414-
) -> Vec<Extension> {
415405
let mut extensions = Vec::new();
416406

417407
// ── Always-on internal extensions ──
@@ -430,7 +420,7 @@ pub fn collect_extensions_with_auth(
430420
if let Some(ado) = tools.azure_devops.as_ref() {
431421
if ado.is_enabled() {
432422
extensions.push(Extension::AzureDevOps(
433-
AzureDevOpsExtension::new(ado.clone()).with_auth_mode(ado_auth),
423+
AzureDevOpsExtension::new(ado.clone()),
434424
));
435425
}
436426
}

src/compile/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ use std::path::{Path, PathBuf};
1919

2020
pub use common::parse_markdown;
2121
pub use common::HEADER_MARKER;
22-
pub use common::generate_mcpg_config;
23-
pub use common::MCPG_IMAGE;
24-
pub use common::MCPG_VERSION;
2522
pub use common::ADO_MCP_ENTRYPOINT;
2623
pub use common::ADO_MCP_IMAGE;
2724
pub use common::ADO_MCP_PACKAGE;

src/compile/types.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -457,23 +457,6 @@ impl AzureDevOpsToolConfig {
457457
AzureDevOpsToolConfig::WithOptions(opts) => opts.org.as_deref(),
458458
}
459459
}
460-
461-
/// Set the org override (for local run mode when --org is provided).
462-
/// Converts `Enabled(true)` to `WithOptions` with the org set.
463-
pub fn set_org(&mut self, org: String) {
464-
match self {
465-
AzureDevOpsToolConfig::Enabled(true) => {
466-
*self = AzureDevOpsToolConfig::WithOptions(AzureDevOpsOptions {
467-
org: Some(org),
468-
..Default::default()
469-
});
470-
}
471-
AzureDevOpsToolConfig::WithOptions(opts) => {
472-
opts.org = Some(org);
473-
}
474-
AzureDevOpsToolConfig::Enabled(false) => {}
475-
}
476-
}
477460
}
478461

479462
impl SanitizeConfigTrait for AzureDevOpsToolConfig {

src/main.rs

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ mod logging;
1212
mod mcp;
1313
mod ndjson;
1414
pub mod runtimes;
15-
#[cfg(debug_assertions)]
16-
mod run;
1715
pub mod sanitize;
1816
mod safeoutputs;
1917
mod tools;
@@ -132,30 +130,6 @@ enum Commands {
132130
#[arg(long, value_delimiter = ',')]
133131
definition_ids: Option<Vec<u64>>,
134132
},
135-
/// Run agent locally (local development mode)
136-
#[cfg(debug_assertions)]
137-
Run {
138-
/// Path to the agent markdown file
139-
path: String,
140-
/// Azure DevOps PAT for API access (base64-encoded as PERSONAL_ACCESS_TOKEN for MCPG in local dev)
141-
#[arg(long, env = "AZURE_DEVOPS_EXT_PAT")]
142-
pat: Option<String>,
143-
/// Azure DevOps organization URL
144-
#[arg(long)]
145-
org: Option<String>,
146-
/// Azure DevOps project name
147-
#[arg(long)]
148-
project: Option<String>,
149-
/// Dry-run: skip real ADO API calls in execute stage
150-
#[arg(long)]
151-
dry_run: bool,
152-
/// Skip MCPG/Docker (only SafeOutputs MCP available)
153-
#[arg(long)]
154-
skip_mcpg: bool,
155-
/// Output directory for safe outputs and artifacts
156-
#[arg(long)]
157-
output_dir: Option<PathBuf>,
158-
},
159133
}
160134

161135
#[derive(Parser, Debug)]
@@ -184,8 +158,6 @@ async fn main() -> Result<()> {
184158
Some(Commands::McpHttp { .. }) => "mcp-http",
185159
Some(Commands::Init { .. }) => "init",
186160
Some(Commands::Configure { .. }) => "configure",
187-
#[cfg(debug_assertions)]
188-
Some(Commands::Run { .. }) => "run",
189161
None => "ado-aw",
190162
};
191163

@@ -392,28 +364,6 @@ async fn main() -> Result<()> {
392364
)
393365
.await?;
394366
}
395-
#[cfg(debug_assertions)]
396-
Commands::Run {
397-
path,
398-
pat,
399-
org,
400-
project,
401-
dry_run,
402-
skip_mcpg,
403-
output_dir,
404-
} => {
405-
run::run(&run::RunArgs {
406-
agent_path: PathBuf::from(path),
407-
pat,
408-
org,
409-
project,
410-
dry_run,
411-
skip_mcpg,
412-
output_dir,
413-
debug: args.debug,
414-
})
415-
.await?;
416-
}
417367
}
418368
} else {
419369
println!("No subcommand was used. Try `compile <path>`");

0 commit comments

Comments
 (0)