Make Failure a First-Class Result: A DAL Feature Deep-Dive

A failed agent run is only useful when the system can say what failed, what remains safe, and what should happen next. Treating failure as a structured result turns postmortems from archaeology into an operating interface.

This article explains one concrete improvement: representing agent outcomes as explicit, drainable result records rather than relying on prose logs. It follows the feature from the problem through its mechanism and ends with practical guidance for deciding when to use it.

The problem: a log line is not a recovery plan

Agent workflows often cross several boundaries: a model produces a decision, a tool reads or writes a file, a scheduler retries the task, and a review gate decides whether an external effect is allowed. When something goes wrong, each boundary may leave a different trace. One component reports an exception, another reports a timeout, and the operator is left to infer whether the work was lost, duplicated, or merely waiting for approval.

That ambiguity is expensive. A retry can repeat a side effect. A human can approve work that is already obsolete. A queue can appear empty even though a partially completed task needs attention.

The underlying mistake is treating failure as the absence of success. For an orchestrated agent, failure is data. It needs an identity, a scope, a reason, and a disposition.

The mechanism: return an outcome, not just a message

A useful DAL improvement is an explicit outcome value for every meaningful unit of work. The value can be represented as a map or record with a stable shape:

``dal { status: "failed", task_id: task_id, stage: "write_draft", retryable: false, reason: "destination_not_writable", completed: ["research", "outline"], pending: ["write_draft"], evidence: ["COO-FILESYSTEM/research/brief.md"], next_step: "repair destination, then resume from write_draft" } ``

The exact syntax can vary with the surrounding DAL program. The important contract is that the result is machine-readable and preserves the boundary between facts and recommendations.

status answers the first operational question. It should distinguish at least success, failure, waiting for review, and skipped work. stage prevents a generic error from hiding how far the workflow progressed. retryable stops every failure from becoming an automatic retry. completed and pending make partial progress visible. evidence points to artifacts an operator can inspect. next_step gives a concise handoff without pretending that the system has completed work it has not done.

This is more than a prettier error message. It creates a contract between the worker, the scheduler, and the human reviewer.

Why the checkpoint belongs in the language layer

A shell wrapper can inspect exit codes, but an exit code cannot express that research succeeded while publication is waiting for approval. A log parser can search for phrases, but phrases drift as prompts and implementations change. A DAL-level result travels with the computation and can be consumed by the next step without reconstructing state from ambient text.

The pattern is especially valuable at effect boundaries. Before a file move, email send, or social post, the program can return waiting_review with the proposed effect and its evidence. The caller can persist that result, show it in a queue, and resume only after approval. If the operation fails, the same record can say whether resumption is safe.

That separation keeps the workflow calm: computation may continue until a gate, while external effects remain explicit and reviewable.

A worked flow: from postmortem to resumable run

Imagine a daily article job that researches a topic, writes a draft, and leaves publication for a later process. The old implementation might emit three log messages and then stop when the destination path is unavailable. The operator has to inspect timestamps and guess whether research should be repeated.

With explicit outcomes, each stage returns its result to the coordinator. Research returns success plus its source artifacts. Drafting consumes those artifacts and returns success with the draft path. The final publication stage returns waiting_review rather than attempting to publish. If writing fails, the coordinator persists failed, identifies write_draft, records the completed research artifact, and marks the failure non-retryable until the path is repaired.

After repair, the scheduler does not rerun the entire job blindly. It resumes at the named stage, validates the evidence it was given, and produces a new outcome. The postmortem is now already encoded in the run record: the failure reason, the safe recovery boundary, and the proof available for review.

This also improves deduplication. A retry can carry the original task_id and stage identifier, allowing the system to distinguish a legitimate resume from a second independent request.

When to use it—and when not to

Use structured outcomes when a workflow has more than one stage, can partially complete, crosses a trust or side-effect boundary, or may be retried by a scheduler. These are the cases where ambiguity creates operational risk.

Use a simpler return value for a small, local computation with no persistence and no external effect. Adding a full outcome record to a pure helper can obscure rather than clarify the code.

Start with a narrow schema. Do not attempt to encode the entire execution trace in every result. Keep detailed diagnostics in logs or evidence files, and place stable, actionable facts in the outcome. Version the schema if consumers will persist records across deployments.

Most importantly, do not make next_step an authority bypass. It is a handoff, not permission. A result that says “send email” must still pass the email route and its policy checks; a result that says “publish” must still respect the publication workflow.

Conclusion: make the next safe action obvious

Failure postmortems become operationally useful when the system records them at the moment of failure. An explicit DAL outcome gives every run a durable identity, a precise stopping point, a retry policy, and evidence for the next decision.

To adopt the pattern, choose one multi-stage job and define a small result schema. Return it from each stage, persist the final record, and teach the scheduler to resume only at declared safe boundaries. Add review states before external effects, then test the unpleasant cases: partial completion, duplicate retries, missing destinations, and approval delays.

The goal is not to eliminate failure. It is to ensure that when failure occurs, the queue can stop cleanly and the operator can see the next safe move without reconstructing the story from scattered logs.