Make task state, oversight, and operations agree
How to study this lesson
Read one section, trace the worked example, and try the knowledge check. Bookmark saves a shortcut; notes appear in My notebook. Mark lesson complete is your own assessment and does not mark its lab passed.
By the end, you can
- Map durable task states to useful user-facing feedback.
- Handle replay, cancellation, and approval without contradictory side effects.
- Trace a task across components while minimizing sensitive logs.
The interface should expose decisions the user can make
A long-running agent task needs more than a spinner. Show whether it is waiting to start, actively working, waiting for needed input, ready for review, completed, failed, or canceled. Explain the next action in user language, such as "Choose which incident to review" or "Review the proposed export." Internal protocol names and transport details belong in diagnostic views unless they help the user make a meaningful decision.
The displayed state should come from an authoritative application record. A browser that loses its streaming connection must be able to reload the task's current state and artifacts. Do not infer completion merely because tokens stopped arriving. A remote timeout, dropped connection, or interrupted approval can leave work unfinished. Distinguish a partial draft from a completed result, and preserve user edits separately so a delayed background event cannot overwrite them without a clear policy.
Events need identity and order
Distributed systems can deliver duplicate, delayed, or out-of-order updates. Assign application event identifiers and a monotonic sequence or revision within the relevant task stream. A reducer can deduplicate identical events, order them, and reject impossible transitions. These are application design choices; they should be mapped explicitly onto the guarantees of the chosen protocol and transport rather than assumed to come for free.
Suppose an interface receives events working at revision two, completed at revision four, and then an old working event at revision three. Blindly applying arrival order makes the task appear to resume after completion. An authoritative revision check prevents that regression. Similarly, counting a retried cost event twice can exhaust a budget incorrectly. Bind accounting to uniquely identified operations and define how corrections or refunds are represented instead of relying on the display's last visible number.
Approval is a bound decision, not a conversational mood
When a consequential action requires approval, present the concrete proposed operation: destination, resource, scope, relevant fields, and expected effect. Store the user's decision through a trusted application path and bind it to that exact proposal with an expiry. If the proposal changes materially, the old decision must not authorize a different action. Model-generated text claiming that approval already exists is not sufficient evidence.
Cancellation also needs a contract. The interface can acknowledge that a stop request was received while the system works to cancel child tasks and release reservations. If an external side effect already completed, cancellation cannot pretend to undo it. Report the actual outcome and any available compensating step. Test races between approval, cancellation, retries, and completion. Idempotency keys can prevent duplicate execution of a repeated approved operation when the underlying service supports the required semantics.
Observability should reconstruct the causal path
OpenTelemetry traces organize work into spans with context that can connect operations across services. In the capstone, trace the parent task, retrieval operation, specialist request, artifact validation, and any action execution. Record durations, outcome categories, retries, and bounded resource use. Trace context helps connect events but must not replace authentication or authorization context.
Avoid collecting full prompts and tool payloads by default when aggregate fields or protected references are sufficient. A debugging system can become an accidental secondary store of tenant data. Define which fields are logged, who can read them, and how long they remain available. Use synthetic fixtures for public screenshots and portfolio traces. A good failure trace explains why a task failed without requiring the reviewer to inspect unrelated private content.
Release behavior should be testable under failure
Test the interface and backend together on lost streams, repeated submissions, denied retrieval, unavailable peers, exhausted budgets, and stale approvals. The user should see a coherent result with a useful next step, while the backend preserves the same invariant. For an invented retry budget of two attempts, show a final failure after the second failed call rather than retrying forever behind a reassuring status message.
Define rollback, health checks, and incident ownership for the capstone even if its first deployment is small. A health endpoint that only returns HTTP 200 does not verify that retrieval credentials or the specialist dependency work. Use bounded readiness checks appropriate to the service and keep them from causing expensive or mutating operations. Operational maturity is visible when the project handles its known failure paths predictably and provides evidence that the user-facing state matches the durable task record.
Work through the code
This application-view reducer ignores stale revisions and preserves terminal states. It assumes updates were validated upstream and is not a protocol implementation. The lab expands this idea into a stricter event reducer with validation, duplicate detection, and accounting.
LABELS = {"queued": "Waiting to start", "working": "Reviewing evidence",
"awaiting_input": "Your input is needed", "completed": "Review complete",
"failed": "Review could not finish", "canceled": "Review canceled"}
def apply_update(view, update):
if update["revision"] <= view["revision"]:
return dict(view)
if view["state"] in {"completed", "failed", "canceled"}:
return dict(view)
return {"revision": update["revision"], "state": update["state"]}
view = {"revision": 1, "state": "queued"}
updates = [{"revision": 2, "state": "working"},
{"revision": 4, "state": "completed"},
{"revision": 3, "state": "working"}]
for update in updates:
view = apply_update(view, update)
print(f"revision {view['revision']}: {LABELS[view['state']]}")
assert view == {"revision": 4, "state": "completed"}
revision 2: Reviewing evidence revision 4: Review complete revision 4: Review complete
Run Python snippets locally with the prerequisites named above. The module coding lab runs directly in your browser.
Pause and reason
A user approves exporting 10 records to an internal destination. A retry proposes 1,000 records to a different destination while keeping the same task ID. Should the existing approval apply? Define a safer binding.
Check your understanding
A completed task receives an older working event after a temporary network delay. What should the application do?
Your notes
Explain the mechanism in your own words. Add a failure you want to test.