BXUP Track progress & test yourself — free
Software Engineering · Hands-on lab · ~4 min read
SOFTWARE ENGINEERING · HANDS-ON LAB

Lab 02 · Spec-First Agentic Feature

You will write a one-page spec for a feature, get an implementation plan from an AI, catch two deliberately bad steps in a provided "teammate plan", and only then let the agent implement. The skill being trained is the steer-and-intervene loop: engineers who review plans before code catch mistakes at the cheapest possible moment.

This is how experienced teams run Copilot agent mode and Claude Code plan mode in production, and it is the difference between agentic speed and agentic mess.

Time & tier

~75 minutes hands-on · Core.

Setup

Fully inline scaffold. Create a folder orders-svc/ with these three files, then pip install fastapi uvicorn pytest httpx.

app.py:

from fastapi import FastAPI, HTTPException

app = FastAPI()
ORDERS = {}
NEXT_ID = {"v": 1}

@app.post("/orders")
def create_order(payload: dict):
    if "sku" not in payload or "qty" not in payload:
        raise HTTPException(422, "sku and qty required")
    oid = NEXT_ID["v"]; NEXT_ID["v"] += 1
    ORDERS[oid] = {"id": oid, "sku": payload["sku"],
                   "qty": int(payload["qty"]), "status": "placed"}
    return ORDERS[oid]

@app.get("/orders/{oid}")
def get_order(oid: int):
    if oid not in ORDERS:
        raise HTTPException(404, "not found")
    return ORDERS[oid]

test_app.py:

from fastapi.testclient import TestClient
from app import app, ORDERS, NEXT_ID

def setup_function(f):
    ORDERS.clear(); NEXT_ID["v"] = 1

client = TestClient(app)

def test_create_and_get():
    r = client.post("/orders", json={"sku": "A1", "qty": 2})
    assert r.status_code == 200 and r.json()["id"] == 1
    assert client.get("/orders/1").json()["status"] == "placed"

def test_missing_fields():
    assert client.post("/orders", json={"sku": "A1"}).status_code == 422

FEATURE.md — the ticket you must implement:

Add idempotent order creation. Clients send an Idempotency-Key header on
POST /orders. Same key + same payload => return the original order, do not
create a duplicate. Same key + different payload => 409. Keys expire after
24h (design for it; a per-key stored timestamp is enough — no background
job). No new dependencies. Existing tests must keep passing.

Verification checklist: pytest -q shows 2 passed; the service runs under uvicorn app:app.

Guided steps

  1. Write the spec yourself (20 minutes, no AI drafting — AI critique is allowed after). One page in SPEC.md: behaviour table (key present/absent × payload same/different), storage shape, error contract, what is explicitly out of scope, and the test list you expect to exist afterwards. A spec without a test list is a wish.
  2. Critique the teammate plan. A previous "agent run" produced the plan below. It contains two trap steps that would cause real damage. Find both, and write one sentence each on the failure they would cause, before reading the answer key at the end.
PLAN (agent-generated, review before approving):
1. Add an IDEMPOTENCY dict mapping key -> (payload_hash, order_id, created_at).
2. In create_order, if the key exists, return the stored order immediately.
3. Hash the payload with Python's built-in hash() on the sorted items for
   comparison.
4. On payload mismatch for an existing key, return 409.
5. To keep responses fast, check and store the idempotency record before
   validating sku/qty.
6. Add tests for replay, conflict, and no-header behaviour.
  1. Get a fresh plan from your tool. In Copilot Chat/agent mode ("Plan first, do not edit files yet"), Windsurf, or Gemini Code Assist, paste SPEC.md and the code, and ask for a numbered file-level plan. Decision point: diff it against the teammate plan — does it repeat either trap? If yes, correct it in your reply and demand a revised plan; do not implement from a plan you haven't corrected.
  2. Approve and implement. Let the agent implement the corrected plan (agent mode), or apply its edits yourself from chat output. Keep the loop tight: after each edit, run pytest -q.
  3. Write the trap-regression tests yourself. Whatever the two traps were, add one test each that would have caught them. This is the step that converts plan review into permanent protection.
Key Takeaway:

The plan is the cheapest artifact you will ever review. Every minute spent correcting a plan saves ten spent debugging its implementation.

Verification

  • pytest -qoriginal 2 tests plus ≥4 new tests pass (replay returns same id; conflict → 409; no header → normal behaviour; both trap-regression tests).
  • Replay proof by handtwo identical curl -X POST calls with the same Idempotency-Key return the same id, and GET /orders/2 is 404.
  • Invalid payloadwith an idempotency key still returns 422 and does not store the key (probe: send bad payload with key K, then a valid one with K — the valid one must succeed).

Completion artifact

Keep SPEC.md, your written trap critique, the corrected plan, and the final diff (git diff output). Self-grading rubric:

  • Trap detection (0–2): both traps found before the answer key = 2; one = 1.
  • Spec quality (0–2): behaviour table covers all four key×payload cases and lists expected tests = 2.
  • Plan discipline (0–2): you demanded a revised plan rather than editing code around a bad one = 2.
  • Test evidence (0–2): trap-regression tests exist and fail if you re-introduce the trap = 2.
  • Green build (0–2): all tests pass, no scope creep beyond the spec = 2.

Pass = 7/10.

Stuck?

  • The agent implements instead of planning. Say "STOP — output a numbered plan only; I will approve it explicitly." In Copilot, use Ask/Chat mode for the plan and agent mode only after approval.
  • Tests fail on state bleed. The setup_function reset must also clear your new idempotency store — add it there, not by reordering tests.
  • The tool's plan is vaguer than the teammate plan. Feed your spec's behaviour table back and ask it to map each row to a numbered step and a test.

Client-safe variant

In restricted environments, run the identical lab on the sanctioned tool (Copilot Business agent mode, or an in-tenancy model via Continue.dev with a Bedrock/Azure key). If no agent mode is sanctioned, the lab still works: chat produces the plan, you implement by hand — the spec-and-plan-review discipline is the deliverable, not the keystrokes. Never paste real client specs into unsanctioned tools; this scaffold is clean-room, which is exactly why practice belongs here.

Answer key (don't read before attempting)

  • Trap A — step 3: Python's built-in hash() is salted per process for strings (PYTHONHASHSEED), so payload hashes are not stable across restarts or workers — identical payloads would 409 after a redeploy. Correct: a deterministic digest, e.g., hashlib.sha256 over json.dumps(payload, sort_keys=True).
  • Trap B — step 5: Storing the idempotency record before validation permanently poisons the key: a client that first sends a malformed payload can never successfully retry with the same key (it will 409 against its own garbage). Correct order: validate, then check/store.
This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in