Solution Architecture project — a bounded support workflow
Solution Architecture project — a bounded support workflow
Difficulty: intermediate. Time: 70–100 minutes. Cost: none. This project uses synthetic inputs and local Python; no model, cloud resource or paid API is needed. Prerequisites: the three Solution Architecture lectures, their quizzes, a text editor and Python 3.
Objectives: CCARP-U1.T1.LO1–LO2, CCARP-U1.T2.LO3–LO5, and CCARP-U1.T3.LO6. Deliver a business decision, executable control checks and an evaluation record. Every identifier, limit and measured value below is a house exercise input, not a vendor benchmark or production assurance claim.
Stage 1 — Define the task and its boundaries
A support service must explain a published policy, inspect an order record and propose an action that passes the supplied business checks. Its policy FAQ already works using retrieval and an answer. The new order workflow must reject invalid records before either downstream check uses them.
The house acceptance limits for the candidate comparison are:
| Criterion | Required result |
|---|---|
| Correctly completed fixture tasks | At least 19 of the same 20 tasks |
| End-to-end p95 | At most 1.5 seconds using the nearest-rank definition below |
| Mean execution cost per submitted task | At most $0.04 including the specified retries |
Create requirements.md with the outcome, these three limits, the measurement boundary and the acceptance owner. Explain why draft volume or agent count cannot substitute for the outcome. The small fixture teaches a decision method; explicitly record that it is insufficient to estimate production reliability.
Create architecture.md with a diagram and an equivalent sequence in words:
- Receive the order input and extract required fields.
- Validate
order_idandamountbefore downstream use. - If invalid, allow at most one repair using the reported field errors; then revalidate against the same rules and remaining allowance.
- If still invalid, stop and escalate with evidence. Do not perform the downstream checks or action.
- If valid, run two independent checks: amount is at most 100, and order ID does not begin with
HOLD-. - Join both results. A proposed action is allowed only when both checks pass.
The amount threshold and ID prefix are synthetic rules. Choose who runs the model/tool loop: your Client SDK implementation, an Agent SDK library in your process, or a hosted managed runtime. Explain the operational boundary and where the application-owned gate remains. You will simulate the gate locally rather than call any of those services.
Compare two house framework proposals in architecture.md. Proposal F1 supplies a loop with less integration code but exposes only the final result; its wrapper provides no intermediate prompts, tool results or gate decisions. Proposal F2 needs an additional adapter but exposes those observations at each required boundary. Reviewers must be able to explain why a failed record reached a consumer. Select a proposal, state the implementation-complexity trade-off and name the observation whose absence would block acceptance. These are supplied exercise capabilities, not claims about a named commercial framework.
Stage 2 — Execute the gate and bound a delegation
Implement workflow.py with a function process(record, repair=None) that returns a decision and an event list. Use these event names so the checks are inspectable: validate, repair, amount_check, hold_check, allow, deny, escalate. A repair function can be invoked only once, and its output must be revalidated.
The validity rules are: order_id is a nonempty string; amount is a finite nonnegative number. A boolean is not an amount, despite Python allowing booleans in some numeric operations. The action requires amount <= 100 and an order ID that does not start with HOLD-. Invalid data must never reach either check.
Use these cases:
| Case | Input / repair | Expected outcome |
|---|---|---|
| Valid | {"order_id":"ORD-1","amount":40} | Allow after both checks |
| Held | {"order_id":"HOLD-2","amount":40} | Deny |
| Over limit | {"order_id":"ORD-3","amount":120} | Deny |
| Missing ID | {"amount":40}, no repair | Escalate; no downstream checks |
| Repair succeeds | Missing ID; repair supplies ORD-4 and 40 | One repair, revalidate, then allow |
| Repair remains invalid | Missing ID; repair returns {"amount":40} | One repair, then escalate |
| Invalid numeric type | {"order_id":"ORD-5","amount":true} | Escalate |
Record the actual event traces in evaluation.md. In particular, verify that no amount_check or hold_check occurs before a valid record exists. It is acceptable to simulate the two independent checks sequentially in this local exercise, but the diagram must show that both depend on validation and both feed the final join.
Create worker-contract.md for an optional policy investigation. Initial tasks concern products A, B and C. New evidence establishes that A/B are aliases and introduces successor D, with a separate support policy. C is still unresolved. Define revised scopes and a return containing entity, period, finding, sources and unresolved questions. Preserve C rather than silently replacing it with D.
For this investigation, enforce these house controls in the design record: at most two active workers, twelve tool-call admissions across the entire run and no new admission after ninety seconds. A per-worker turn cap does not replace those shared limits. Explain how retries and follow-ups spend the same counters. If the budget is exhausted, stop admitting new work, request cancellation where supported, retain the supported partial findings and disclose the remaining gap. Do not label a partial investigation complete.
Stage 3 — Compare quality, latency and cost
Save the following as compare.py and run python3 compare.py. The twenty timings for each candidate are synthetic end-to-end measurements, already including the stated retry behavior. The success counts describe those same twenty submitted tasks. For this small exercise, p95 uses the nearest-rank element at index ceil(0.95 * n) - 1 after sorting; do not substitute a mean.
from math import ceil
candidates = {
"retrieval": {
"successes": 18,
"seconds": [0.80 + 0.01 * i for i in range(20)],
"initial_cost": 0.018, "retry_tasks": 0, "retry_cost": 0.018,
},
"chain": {
"successes": 19,
"seconds": [1.40 + 0.015 * i for i in range(20)],
"initial_cost": 0.025, "retry_tasks": 0, "retry_cost": 0.025,
},
"hybrid": {
"successes": 19,
"seconds": [1.00 + 0.02 * i for i in range(20)],
"initial_cost": 0.032, "retry_tasks": 4, "retry_cost": 0.032,
},
}
for name, candidate in candidates.items():
n = len(candidate["seconds"])
p95 = sorted(candidate["seconds"])[ceil(0.95 * n) - 1]
cost = candidate["initial_cost"] + (
candidate["retry_tasks"] / n * candidate["retry_cost"]
)
accepted = candidate["successes"] >= 19 and p95 <= 1.5 and cost <= 0.04
print(f"{name}: success={candidate['successes']}/{n}, "
f"p95={p95:.3f}s, mean_cost=${cost:.4f}, accepted={accepted}")Append the printed results to evaluation.md. Create decision.md naming the accepted candidate and explaining why each alternative fails the same contract. Distinguish execution cost per submitted task from cost per accepted task; the specified $0.04 ceiling uses the former. Explain how more retries or a changed request mix would trigger reevaluation. Include an owner, a rollback criterion and the verified configuration to restore.
Acceptance checks
- All six objectives appear in the artifacts as an applied decision, not just an objective ID.
- The diagram and event traces preserve extraction, validation, bounded feedback, both downstream checks and the final join.
- A second invalid record escalates after exactly one repair, and the repair counter survives revalidation.
- The worker contract retains C, merges A/B, scopes D, carries sources and states the shared-budget exhaustion response.
- The comparison uses the same twenty-task denominator, the specified nearest-rank p95 and the weighted retry cost.
- The decision applies all three criteria, records rejected alternatives and labels the small synthetic fixture's evidence limits.
Reference solution
The following implementation illustrates the control semantics for Stage 2. It simulates application rules, not an SDK API or a real financial operation.
import math
def valid(record):
if not isinstance(record, dict):
return False
identity = record.get("order_id")
amount = record.get("amount")
return (isinstance(identity, str) and bool(identity.strip())
and type(amount) in (int, float)
and math.isfinite(amount) and amount >= 0)
def process(record, repair=None):
events = ["validate"]
if not valid(record) and repair is not None:
events.append("repair")
record = repair(record)
events.append("validate")
if not valid(record):
return "escalate", events + ["escalate"]
events.append("amount_check")
amount_allowed = record["amount"] <= 100
events.append("hold_check")
hold_allowed = not record["order_id"].startswith("HOLD-")
allowed = amount_allowed and hold_allowed
decision = "allow" if allowed else "deny"
return decision, events + [decision]
assert process({"order_id": "ORD-1", "amount": 40})[0] == "allow"
assert process({"order_id": "HOLD-2", "amount": 40})[0] == "deny"
assert process({"order_id": "ORD-3", "amount": 120})[0] == "deny"
assert process({"amount": 40}) == ("escalate", ["validate", "escalate"])
assert process({"amount": 40}, lambda _: {"order_id": "ORD-4", "amount": 40}) == (
"allow", ["validate", "repair", "validate", "amount_check", "hold_check", "allow"])
assert process({"amount": 40}, lambda _: {"amount": 40}) == (
"escalate", ["validate", "repair", "validate", "escalate"])
assert process({"order_id": "ORD-5", "amount": True})[0] == "escalate"Stage 3 prints: retrieval 18/20, p95 0.980 s, cost $0.0180, rejected; chain 19/20, p95 1.670 s, cost $0.0250, rejected; hybrid 19/20, p95 1.360 s, cost $0.0384, accepted. Retrieval misses the outcome floor; chaining misses the latency ceiling. Hybrid passes every supplied limit. Its four retries add 4/20 × $0.032 = $0.0064 to the initial $0.032 mean. None of these numbers forecasts a real vendor workload.
A defensible abstraction decision selects F2 under the supplied inspection requirement, while recording its extra adapter work. F1's smaller integration surface does not compensate for missing evidence of which gate allowed the invalid record to continue. A changed F1 proposal could be reevaluated if it exposed the required boundary records. This compares implementation complexity against inspectability instead of assuming that every abstraction is either sufficient or harmful.
A defensible worker plan merges the A/B identity task, keeps unresolved C and adds D within the remaining shared budget. The lead reconciles returned evidence; reaching the budget produces supported partial findings and a disclosed gap. It never converts an uninvestigated product into a supported conclusion.
Cleanup and submission
Keep the six Markdown artifacts plus workflow.py and compare.py as your submission. Remove only temporary synthetic files you created for this exercise. No remote resource or credential should have been created. Revisit any missed quiz decisions using the corresponding event trace or comparison row.
Sources
Agent SDK overview supports loop/runtime ownership. Prompting best practices supports inspectable chaining and independent delegated work. Subagents documents context boundaries and turn limits. Define success criteria and build evaluations supports multidimensional evaluation. All scenario data, business rules, code, budgets and acceptance thresholds are house-authored.