De-slop: remove every em-dash + banned words across all modules + capstone (#94)
Sync course wiki / sync-wiki (push) Successful in 4s
Sync course wiki / sync-wiki (push) Successful in 4s
Co-authored-by: claude <claude@jpaul.io> Co-committed-by: claude <claude@jpaul.io>
This commit was merged in pull request #94.
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
"""Module 25 lab — an autonomous-but-supervised agent orchestrator.
|
||||
"""Module 25 lab: an autonomous-but-supervised agent orchestrator.
|
||||
|
||||
This is the smallest honest version of the two patterns in the module:
|
||||
|
||||
* issue-to-pr — read an issue, let an agent implement it, run the gate, produce a PR PROPOSAL.
|
||||
* self-heal — run the gate; on failure, feed the failure back to the agent for a fix,
|
||||
* issue-to-pr : read an issue, let an agent implement it, run the gate, produce a PR PROPOSAL.
|
||||
* self-heal : run the gate; on failure, feed the failure back to the agent for a fix,
|
||||
bounded by a retry cap; produce a PR PROPOSAL.
|
||||
|
||||
The load-bearing idea is in one place and you should be able to point at it: the agent NEVER merges.
|
||||
Every path ends at `propose_pr()` — a branch, a commit, and the command *you* would run to open the
|
||||
Every path ends at `propose_pr()`: a branch, a commit, and the command *you* would run to open the
|
||||
PR. The CI/review/security gates (Modules 14/15/10) and recovery (Module 12) are what supervise it,
|
||||
not a human watching it type.
|
||||
|
||||
Run it two ways:
|
||||
|
||||
1. Simulated (no agent needed, fully deterministic) — see the machinery and the gates:
|
||||
1. Simulated (no agent needed, fully deterministic); see the machinery and the gates:
|
||||
python agent_runner.py issue-to-pr issue-delete-command.md --simulate good
|
||||
python agent_runner.py issue-to-pr issue-delete-command.md --simulate bad
|
||||
python agent_runner.py self-heal --simulate bad
|
||||
@@ -21,9 +21,9 @@ Run it two ways:
|
||||
|
||||
Simulation works on a SELF-CONTAINED demo target (agent_demo.py + test_agent_demo.py) so it is
|
||||
deterministic and never corrupts your real tasks-app files. The gate it runs (ruff + pytest) is
|
||||
the real one — the same checks Module 14's CI runs.
|
||||
the real one, the same checks Module 14's CI runs.
|
||||
|
||||
2. Real agent — drives your own agentic tool against the actual issue. Point AGENT_CMD at your
|
||||
2. Real agent: drives your own agentic tool against the actual issue. Point AGENT_CMD at your
|
||||
tool's non-interactive / one-shot mode, then drop --simulate:
|
||||
export AGENT_CMD='your-agent-cli --print --prompt-file {prompt_file}'
|
||||
python agent_runner.py issue-to-pr issue-delete-command.md
|
||||
@@ -52,7 +52,7 @@ CONFIG_CANDIDATES = ["AGENTS.md", ".agent/instructions.md", "agent-config.md"]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
# The gate — the same lint + test checks Module 14 runs in CI, run locally so they're reproducible.
|
||||
# The gate: the same lint + test checks Module 14 runs in CI, run locally so they're reproducible.
|
||||
# This is the structural supervision. It does not care whether a human or an agent wrote the change.
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
def run_gate() -> tuple[bool, str]:
|
||||
@@ -65,7 +65,7 @@ def run_gate() -> tuple[bool, str]:
|
||||
try:
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||
except FileNotFoundError:
|
||||
out.append(f" ! {cmd[0]} not installed — `pip install pytest ruff`. Treating as a gate FAIL.")
|
||||
out.append(f" ! {cmd[0]} not installed; run `pip install pytest ruff`. Treating as a gate FAIL.")
|
||||
ok = False
|
||||
continue
|
||||
out.append(proc.stdout.rstrip())
|
||||
@@ -78,7 +78,7 @@ def run_gate() -> tuple[bool, str]:
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
# The agent — real (your tool) or simulated (deterministic, for the lab).
|
||||
# The agent: real (your tool) or simulated (deterministic, for the lab).
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
def find_config() -> Path | None:
|
||||
env = os.environ.get("AGENT_CONFIG")
|
||||
@@ -93,14 +93,14 @@ def find_config() -> Path | None:
|
||||
def build_prompt(task: str, *, issue_path: Path | None = None, failure: str | None = None) -> str:
|
||||
"""Assemble the agent's brief: standing config (Module 5) + the specific task (issue or failure)."""
|
||||
parts = ["You are working in a Git repository on the current branch. Make the change directly in",
|
||||
"the files. Do not commit, push, or merge — just edit. Follow the project's conventions."]
|
||||
"the files. Do not commit, push, or merge; just edit. Follow the project's conventions."]
|
||||
config = find_config()
|
||||
if config:
|
||||
parts += ["", f"# Project conventions (from {config})", config.read_text()]
|
||||
if issue_path:
|
||||
parts += ["", "# Task (issue to implement)", issue_path.read_text()]
|
||||
if failure:
|
||||
parts += ["", "# A CI check just failed. Fix the CODE so it passes — do not weaken or delete",
|
||||
parts += ["", "# A CI check just failed. Fix the CODE so it passes; do not weaken or delete",
|
||||
"# the test to make it pass. Here is the failing output:", "```", failure, "```"]
|
||||
return "\n".join(parts)
|
||||
|
||||
@@ -134,21 +134,21 @@ def simulate_implement(variant: str) -> None:
|
||||
)
|
||||
if variant == "good":
|
||||
DEMO_SRC.write_text("def discount(price, pct):\n return price - price * pct / 100\n")
|
||||
else: # 'bad' — plausible but wrong: treats the percent as a flat amount.
|
||||
else: # 'bad': plausible but wrong, treats the percent as a flat amount.
|
||||
DEMO_SRC.write_text("def discount(price, pct):\n return price - pct\n")
|
||||
|
||||
|
||||
def simulate_fix(variant: str, attempt: int) -> None:
|
||||
if variant == "stuck":
|
||||
# The "agent" keeps producing plausible, still-wrong fixes — the loop must give up, not run forever.
|
||||
# The "agent" keeps producing plausible, still-wrong fixes, so the loop must give up, not run forever.
|
||||
DEMO_SRC.write_text(f"def discount(price, pct):\n return price - pct - {attempt}\n")
|
||||
else: # 'bad' — converges on the second attempt with the correct formula.
|
||||
else: # 'bad': converges on the second attempt with the correct formula.
|
||||
DEMO_SRC.write_text("def discount(price, pct):\n return price - price * pct / 100\n")
|
||||
|
||||
|
||||
def simulate_cleanup() -> None:
|
||||
"""Discard the simulator's demo artifacts. These are UNTRACKED new files, so `git restore`
|
||||
(which only touches tracked files) can't remove them — the simulator cleans up after itself."""
|
||||
(which only touches tracked files) can't remove them, so the simulator cleans up after itself."""
|
||||
for path in (DEMO_SRC, DEMO_TEST):
|
||||
path.unlink(missing_ok=True)
|
||||
|
||||
@@ -163,7 +163,7 @@ def in_git_repo() -> bool:
|
||||
|
||||
def ensure_branch(name: str) -> None:
|
||||
"""Create and switch to the agent's working branch. The orchestrator owns this git step the same
|
||||
way agent-job.yml's runner does (`git switch -c`) — you direct the automation and then verify the
|
||||
way agent-job.yml's runner does (`git switch -c`): you direct the automation and then verify the
|
||||
branch (`git branch`), instead of typing `git checkout` by hand. No-op outside a Git repo."""
|
||||
if not in_git_repo():
|
||||
return
|
||||
@@ -175,7 +175,7 @@ def ensure_branch(name: str) -> None:
|
||||
|
||||
def propose_pr(message: str) -> None:
|
||||
print("\n" + "=" * 80)
|
||||
print("GATE PASSED. Proposing a PR — NOT merging. A human reviews the diff (Module 10).")
|
||||
print("GATE PASSED. Proposing a PR, NOT merging. A human reviews the diff (Module 10).")
|
||||
print("=" * 80)
|
||||
if in_git_repo():
|
||||
subprocess.run(["git", "add", "-A"])
|
||||
@@ -188,7 +188,7 @@ def propose_pr(message: str) -> None:
|
||||
print(f" git push -u origin {branch}")
|
||||
print(" # ...and open a pull request on your forge. CI + security gates run there.")
|
||||
else:
|
||||
print("\n(Not a Git repo — skipping commit. In your tasks-app this would commit to the branch.)")
|
||||
print("\n(Not a Git repo, so skipping commit. In your tasks-app this would commit to the branch.)")
|
||||
print("\nThe agent stops here. It cannot merge. That is the whole safety model.")
|
||||
|
||||
|
||||
@@ -249,14 +249,14 @@ def cmd_self_heal(simulate: str | None) -> int:
|
||||
print(gate_output)
|
||||
if attempt > RETRY_CAP - 1:
|
||||
break
|
||||
print(f"\n[self-heal] gate red — attempt {attempt}/{RETRY_CAP - 1}: asking the agent for a fix.")
|
||||
print(f"\n[self-heal] gate red, attempt {attempt}/{RETRY_CAP - 1}: asking the agent for a fix.")
|
||||
if simulate:
|
||||
simulate_fix(simulate, attempt)
|
||||
else:
|
||||
run_real_agent(build_prompt("fix", failure=gate_output))
|
||||
|
||||
print("\n" + "=" * 80)
|
||||
print(f"SELF-HEAL GAVE UP after {RETRY_CAP - 1} attempts. Handing off to a human — NOT looping forever.")
|
||||
print(f"SELF-HEAL GAVE UP after {RETRY_CAP - 1} attempts. Handing off to a human, NOT looping forever.")
|
||||
print("This cap is what stops an agent burning a runner bill chasing a flaky or impossible fix.")
|
||||
print("=" * 80)
|
||||
return 2
|
||||
|
||||
Reference in New Issue
Block a user