AWTL: Turning Failure Logs into Next-Run Hints
메뉴

AI Harness

AWTL: Turning Failure Logs into Next-Run Hints

A trace structure that converts failures into reusable execution hints.

AWTL: Turning Failure Logs into Next-Run Hints hero image

A good harness doesn't end with lots of failure logs. We need to turn the failure into a hint that can be used in the next run.

In Part 1, Ctx2Skill was applied from the operational memory perspective of the development harness, and in Part 2, it was summarized that MemoryGraph should only accept proven compact rules. The topic of this article is the middle layer.AWTL, or Agent Work Trace Logging.

The purpose of AWTL is not log collection. The goal is to attribute failures to the action unit and inject only as many recurrence prevention hints as necessary before the next attempt.

Key takeaways

  • AWTL records agent actions in action/span/event units.
  • Connect failed judge events with source actions, artifacts, and memory reads.
  • The attribution results are compressed into failed turn cases.
  • Before the next attempt, only relevant cases are injected into the Failure Prevention Brief.
  • The replay scorecard filters out stale, risky, and blocked cases.
  • MemoryGraph promotion is only allowed if it passes replay or human approval.

full flow

AWTL starts with a trace but does not end with a trace.

The point here is not “Paste all logs to the next prompt”. Pick only the failures that are relevant to the task at hand and turn them into small, actionable hints.

Minimum unit of trace event

It is difficult to know the cause of failure by only looking at the final PR. Failure usually begins already in the middle of the action.

failure starting pointProblems with only looking at the final resultWhat AWTL must leave behind
Wrong file selectionI don't know why I modified the wrong filesearch/read action and selection basis
misjudgment of test commandOnly verification missing is visibleExecution command, exit code, stderr
Error log misunderstandingRe-edit direction is wrongConnect observation and next action
memory misapplicationDon't know if memory is the causeConnect memory_read event and action
verifier failedNot sure which artifact failedjudge_result and artifact_refs

Therefore, a trace must have at least the following layers:

Trace Sink: Creating a Replayable Event Stream

Trace Sink is a layer that receives the work left by the agent during execution line by line and saves it as an execution log that can be read again later. The important thing here is not “save a lot,” but “save so that it can be analyzed again with the same criteria later.”

The trace referred to in this article is closer to a JSONL file. One event is included in one line.

{"type":"action","spanId":"s-42","tool":"shell","command":"npm test"}{"type":"observation","spanId":"s-42","exitCode":1,"summary":"contract test failed"}{"type":"judge_result","spanId":"s-42","status":"failed","reason":"contract mismatch"}

You must have this file so that you can later recalculate “what observation came after what action and why the judge considered it a failure.” So Trace Sink's first responsibility is twofold.

responsibilityreason
Only use designated trace storage locationsMixing other project logs or temporary files will pollute the failure analysis.
Set the event format to one canonical form.If field names and structures change from run to run, replay and aggregation are impossible.

The code below is a boundary that prevents trace from being written to any directory.EXPECTED_TRACE_ROOTis the storage location allowed by the harness, and if any other path comes in, it will fail immediately.

function assertExpectedTraceRoot(traceRoot) {  const resolvedRoot = normalizeAbsolutePath(traceRoot);  const expectedRoot = normalizeAbsolutePath(EXPECTED_TRACE_ROOT);   if (resolvedRoot !== expectedRoot) {    throw new Error(`Invalid trace root: ${traceRoot}`);  }   return resolvedRoot;}

The second problem is a broken JSONL line. If the process dies mid-execution or file writing is interrupted, a single line may not be parsed as JSON. At this time, if you discard the entire trace, even normal events will be lost. Therefore, only broken lines are separated intoquarantinefiles, and normal lines are continued to be analyzed.

quarantined.push(quarantineLine(  quarantinePath,  rawLine,  reason,  canonicalPath,  index + 1,));

This design ensures three things:

guaranteemeaning
trace root fixedTrace artifacts are not outside the expected range.
corrupt line isolationA single line error won't ruin the entire run analysis.
Regenerate materialized viewDerived output can be recreated from the canonical trace.

Harness Capture: Connecting action and judge

If the judge failure remains simplyfailed, it is difficult to use to prevent recurrence. You need to connect which action created which artifact and which verifier saw that artifact and failed.

async function recordJudgeResult(details = {}) {  return emit("judge_result", {    judge_name: toText(details.judgeName, "phase-verifier"),    result: toText(details.result, "warn"),    lifecycle_event: "judge_result",    source_action_id: actionId,    artifact_refs: normalizeArtifactRefs(details.artifactRefs ?? [], repoRoot),    detail: toText(details.detail, ""),  });}

source_action_idandartifact_refsare the key. Without these two, failures are difficult to reproduce or prevent in subsequent runs.

Failure Attribution: Separating cause from promotability

Attribution connects failed judge events with source actions, artifacts, and memory reads. At the same time, we need to isolate whether this failure is subject to memory promotion.

return {  failureEvent,  failureTurnId,  failedArtifactRefs,  sourceActionIds,  verifierActionId,  touchedActionIds,  memoryReadNodeIds,  evidenceRefs,  rootCauseSummary,  verificationProbeCandidate,  classification,  failureTypeInfo,  attributionHeuristics,};

What is important is not the single root cause statement. This isclassification.

classificationbasic decision
agent_failureFailed turn case and memory candidate candidates
verification_failurereplay probe candidate
environment_blockerBlock memory promotion
flaky_blockerBlock before checking reproducibility
harness_blockerSeparated into harness modification backlog

Long-term memory is faulty if we do not distinguish between environmental and agent failures. For example, if e2e fails because the browser binary is missing, you should not have memory saying “e2e is omitted in this repository.”

Failed Turn Case: Compact case for next run

Attribution results should be compressed into compact cases rather than the entire raw trace.

{  "schema_version": 1,  "case_id": "case-demo-a",  "turn_id": "turn-3-1",  "failure_turn_id": "turn-3-1",  "failure_event_id": "judge-17",  "artifact_refs": ["artifact:build-output"],  "memory_read_node_ids": ["memory:contract-rule"],  "prevention_hint": "Before closeout, run the same verifier again for the changed artifact.",  "applicability": ["contract_change", "public_api"],  "evidence_refs": ["judge:contract-verifier"]}

Verification must also be rigorous.

if (caseValue.turn_id !== caseValue.failure_turn_id) {  errors.push("turn_id and failure_turn_id must match");} if (!Array.isArray(caseValue.artifact_refs) || caseValue.artifact_refs.length === 0) {  errors.push("artifact_refs must be a non-empty array of strings");}

The case affects the next attempt. Therefore, cases withoutartifact_refs,evidence_refs, andapplicabilityare difficult to use as recurrence prevention data.

Failure Prevention Brief: Inject Smallly

You should not add all failed cases before the next attempt. Only cases that match the current phase should be included.

const selectedCases = selectFailurePreventionCases(loaded.cases, context, options); if (selectedCases.length === 0) {  return {    status: "no-op",    section: "",  };}

The brief should be short.

Failure Prevention Brief- [high-confidence] Re-run the verifier that failed before Closeout as the target for the changed artifact.- [scope: public_api] Update contract artifact and verifier evidence together.

A good brief satisfies three conditions:

conditionreason
Relevant to current taskReduces unnecessary memory noise.
This is an executable statementChange the agent's next action.
have a basisTracked with failure logs and verifier evidence.

Replay Scorecard: Managing the effectiveness of failure memories.

Even a failed case that was once valid can become stale over time. The verifier may change, the code structure may change, or the failure may no longer be reproducible.

{  "schema_version": 1,  "record_id": "replay-demo-a",  "status": "passed",  "decision": "allow_brief_and_promotion",  "candidate_id": "memcand-demo-a",  "case_id": "case-demo-a",  "validated_by": "replay",  "last_validated_at": "example-timestamp",  "memory_graph_status": "candidate",  "replay_status": "passed",  "risk_level": "low",  "applies_to": ["public_api"],  "does_not_apply_to": ["internal_refactor"],  "evidence_refs": ["judge:contract-verifier"]}

Brief Check exclusion conditions with scorecard before injection.

export function isReplayScorecardExcluded(record = {}) {  const status = normalizeStatus(record.status ?? record.result ?? record.outcome);   return isReplayScorecardStaleOrRisky(record)    || ["blocked", "skipped", "unavailable", "denied"].includes(status);}

This filter prevents old failure memories from persisting in the prompt.

Memory promotion is the final step

The results from AWTL should not go directly into MemoryGraph. Promotion is the final step.

if (!approval.approved && !replayOk) {  reasons.push("replay or human approval is required before promotion");} if (isImportedOnlyCandidate(candidate)) {  reasons.push("imported-only or trace-only candidate is blocked");} const shouldWrite = options.writeMemoryGraph === true  && toText(options.autoPromote, "verified-only") === "verified-only";

The policy created by these three conditions is clear.

  • No promotion without replay or human approval.
  • Imported-only candidates are blocked.
  • Do not write to MemoryGraph without an explicit write flag.
  • Auto-promotion only allows verified-only.

Practical Checklist

Before attaching the AWTL to the actual harness, check the following items:

AWTL application checklist [ ] The event schema includes action, observation, judge_result, and artifact_ref.[ ] Trace event has run_id, attempt_id, and turn/span/action identifiers.[ ] Guarantees sortability with writer_seq or ingest_seq.[ ] The trace root is safely fixed and path traversal is blocked.[ ] Quarantine corrupt JSONL lines without ruining the entire trace.[ ] judge_result has source_action_id and artifact_refs.[ ] Include memory_read event in attribution.[ ] failure attribution classifies the failure class.[ ] environment/flaky/harness failure blocks default promotion.[ ] The failed turn case only has compact metadata.[ ] The prevention brief injects only cases that match the current phase.[ ] Exclude stale/risky/blocked cases from the replay scorecard.[ ] MemoryGraph promotion is limited to verified-only.

finish

The point of AWTL is not to keep good logs. It's about turning failure into a hint for the next move.

  1. failure log
  2. failure attribution
  3. failed turn case
  4. prevention brief
  5. replay scorecard
  6. verified-only memory promotion

Once this loop is created, the harness is not a simple executor. It becomes an operating system that observes execution, interprets failures, and promotes only verified knowledge to long-term memory.

댓글

GitHub 계정으로 로그인하면 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions를 통해 운영됩니다.

TOP