---
title: "MemoryGraph는 자동 저장소가 아니다"
slug: "ctx2skill-harness-02-memorygraph-promotion"
canonicalUrl: "https://moonshotnotes.com/posts/ctx2skill-harness-02-memorygraph-promotion/"
sourceUrl: "https://moonshotnotes.com/posts/ctx2skill-harness-02-memorygraph-promotion/"
markdownUrl: "https://moonshotnotes.com/agent/posts/ctx2skill-harness-02-memorygraph-promotion.md"
language: "ko"
category: "Workflow"
updatedAt: "2026-05-08"
agentTokenEstimate: 1796
---

# MemoryGraph는 자동 저장소가 아니다

AI 코딩 에이전트의 장기 기억을 raw trace가 아니라 replay와 human approval을 통과한 compact rule로 운영해야 하는 이유를 정리합니다.

## Agent metadata

- Source: https://moonshotnotes.com/posts/ctx2skill-harness-02-memorygraph-promotion/
- Markdown: https://moonshotnotes.com/agent/posts/ctx2skill-harness-02-memorygraph-promotion.md
- Language: ko
- Category: Workflow
- Tags: AI Agent, MemoryGraph, Developer Harness, Replay Gate, Ctx2Skill, AI Memory, Workflow
- Updated: 2026-05-08
- Estimated tokens: 1796

AI 에이전트에 memory를 붙이면 문제가 해결될 것처럼 보입니다. 하지만 개발 하네스에서는 반대로 시작해야 합니다. MemoryGraph의 핵심 가치는 무엇을 저장하느냐보다 **무엇을 저장하지 않느냐**에 있습니다.

1편에서는 Ctx2Skill의 context-to-skill 구조를 개발 하네스의 Repo Skill Memory 관점으로 적용해 봤습니다. 이번 글에서는 그 기억을 어디까지 MemoryGraph에 넣어야 하는지 다룹니다.

결론은 보수적입니다. MemoryGraph는 raw trace 저장소가 아닙니다. 실패 로그를 자동으로 밀어 넣는 곳도 아닙니다. MemoryGraph에는 replay 또는 human approval을 통과한 compact rule만 들어가야 합니다.

## 핵심 요약

- MemoryGraph는 source of truth가 아니라 recall acceleration layer입니다.
- 진실의 원천은 code, test, CI, contract, production behavior입니다.
- raw trace, imported evidence, 실패 로그를 그대로 장기 기억에 넣으면 다음 실행을 오염시킬 수 있습니다.
- 실패에서 나온 규칙은 먼저 memory candidate로 남기고, replay 또는 human approval을 거친 뒤 승격해야 합니다.
- memory fact에는 적용 범위, 제외 범위, 검증 근거, lifecycle metadata가 있어야 합니다.

## MemoryGraph를 어디에 두어야 하는가

개발 하네스의 지식 계층을 분리하면 역할이 명확해집니다.

| 계층 | 역할 | 저장해도 되는 것 | 저장하면 위험한 것 |
|---|---|---|---|
| Source of truth | 실제 판정 기준 | 코드, 테스트, CI, contract, docs | 추론 요약만 있는 규칙 |
| Raw trace | 실행 관측 | action, observation, judge event | 장기 기억으로 바로 쓰는 실패 로그 |
| Candidate | 승격 후보 | failure attribution, scope, evidence refs | 근거 없는 일반화 |
| MemoryGraph | 재사용 기억 | 검증된 compact rule | raw trace 전체, imported-only evidence |
| Prompt context | 현재 실행 힌트 | 관련성이 높은 작은 recall | 전체 memory dump |

MemoryGraph는 source of truth를 대체하지 않습니다. 에이전트가 올바른 source of truth로 빨리 가도록 돕는 색인에 가깝습니다.

```mermaid
%% MemoryGraph는 진실 원천 위에 놓인 가속 레이어이며, 쓰기 경계가 좁아야 한다.
flowchart TD
  Truth["Truth source<br/>code / tests / CI / docs"]
  Trace["Raw trace<br/>action / observation / judge"]
  Candidate["Memory candidate<br/>failure attribution"]
  Gate["Promotion gate<br/>replay or human approval"]
  Graph["MemoryGraph<br/>verified compact rule"]
  Recall["Small recall context<br/>next run"]

  Truth --> Trace
  Trace --> Candidate
  Candidate --> Gate
  Gate --> Graph
  Graph --> Recall
  Recall --> Truth
```

## 가장 위험한 실수: raw trace를 기억으로 착각하기

raw trace에는 유용한 단서가 많습니다. 동시에 노이즈도 많습니다.

- 환경 문제 때문에 실패한 명령
- flaky test의 일회성 실패
- 잘못된 가정으로 시작한 탐색
- 중간에 폐기된 계획
- 사용자가 거절한 접근
- 외부 runtime에서 import한 불완전한 로그

이런 내용을 그대로 장기 기억에 넣으면 에이전트는 다음 실행에서 잘못된 규칙을 따릅니다.

```text
나쁜 memory:
이 프로젝트에서는 e2e 테스트가 자주 실패하므로 closeout에서 생략한다.

좋은 candidate:
최근 run에서 e2e가 브라우저 바이너리 누락으로 실패했다.
failure_class는 environment_blocker이며, 코드 변경에 대한 memory promotion 대상이 아니다.
재시도 전에는 browser dependency availability를 먼저 확인한다.
```

좋은 후보는 실패를 숨기지 않습니다. 다만 실패를 일반 규칙으로 승격하기 전에 원인과 적용 범위를 분리합니다.

## 승격 파이프라인

MemoryGraph write는 마지막 단계입니다. 그 전에는 candidate와 gate가 있어야 합니다.

```mermaid
%% 실패 로그는 candidate와 gate를 거친 뒤에만 장기 기억이 된다.
flowchart LR
  Failure["Failed judge event"]
  Attribution["Failure attribution"]
  Candidate["Memory candidate"]
  Replay["Replay probe"]
  Human["Human approval"]
  Decision["Promotion decision"]
  Memory["MemoryGraph fact"]

  Failure --> Attribution --> Candidate
  Candidate --> Replay
  Candidate --> Human
  Replay --> Decision
  Human --> Decision
  Decision -->|approved| Memory
  Decision -->|blocked| Archive["Denied / archived candidate"]
```

이 파이프라인의 정책은 다음처럼 단순하게 유지하는 편이 좋습니다.

| 정책 | 이유 |
|---|---|
| replay 또는 human approval 없이는 승격하지 않는다 | 실패 원인 오판을 줄입니다. |
| imported-only candidate는 차단한다 | 외부 trace를 진실로 착각하지 않습니다. |
| explicit write flag 없이는 쓰지 않는다 | 분석과 장기 상태 변경을 분리합니다. |
| auto promotion은 verified-only만 허용한다 | 편의보다 기억 신뢰도를 우선합니다. |
| scope와 anti-scope가 없으면 보류한다 | 맞는 규칙을 틀린 상황에 적용하는 일을 막습니다. |

## memory candidate의 최소 구조

candidate는 문장 하나가 아니라 검증 가능한 운영 지식 후보여야 합니다.

```json
{
  "schema": "awtl.memory_candidate.v2",
  "candidate_id": "memcand-demo-a",
  "failure_class": "agent_failure",
  "failure_type": "contract_mismatch",
  "source_action_ids": ["action-12"],
  "root_cause_summary": "Public response shape changed without updating contract evidence.",
  "proposed_memory": "public response schema가 바뀌는 경우 contract artifact와 verifier evidence를 함께 갱신한다.",
  "scope": {
    "applies_to": ["public_api", "contract_change"],
    "does_not_apply_to": ["internal_refactor", "test_only_change"]
  },
  "evidence_refs": ["judge:contract-verifier", "artifact:contract-output"],
  "verification_probe_candidate": "public-contract-regression",
  "promotion_status": "candidate",
  "requires_human_review": true
}
```

이 구조에서 빠지면 안 되는 것은 네 가지입니다.

| 필드 | 없을 때 생기는 문제 |
|---|---|
| `failure_class` | 환경 실패를 agent failure로 오판합니다. |
| `source_action_ids` | 어떤 행동에서 규칙이 나왔는지 추적할 수 없습니다. |
| `scope` | 규칙이 너무 넓게 적용됩니다. |
| `evidence_refs` | 왜 이 기억이 생겼는지 검증할 수 없습니다. |

## 실패 class를 먼저 나누기

모든 실패가 memory promotion 대상은 아닙니다.

| failure class | 기본 처리 | 예시 |
|---|---|---|
| `agent_failure` | candidate 가능 | 파일을 잘못 수정했거나 contract 갱신을 누락함 |
| `verification_failure` | probe와 함께 candidate 가능 | 새 verifier가 실제 회귀를 잡음 |
| `environment_blocker` | promotion 차단 | 네트워크, 권한, 브라우저 바이너리 누락 |
| `flaky_blocker` | promotion 차단 | 재현되지 않는 일회성 테스트 실패 |
| `harness_blocker` | 하네스 수정 대상으로 분리 | verifier 자체 버그 |

이 분리가 없으면 memory가 빠르게 오염됩니다. 네트워크 장애 때문에 실패한 작업을 보고 “이 검증은 생략해도 된다”는 규칙을 저장하면, 다음 실행은 더 위험해집니다.

## fact lifecycle이 필요하다

한 번 맞았던 memory도 시간이 지나면 틀릴 수 있습니다. 따라서 MemoryGraph fact에는 lifecycle metadata가 있어야 합니다.

```json
{
  "origin": "awtl",
  "origin_candidate_id": "memcand-demo-a",
  "validated_by": "replay",
  "valid_for": ["contract_change", "public_api"],
  "not_valid_for": ["internal_refactor"],
  "last_validated_at": "example-timestamp",
  "verification_contract_version": "v1",
  "status": "active"
}
```

운영 상태는 최소한 다음 흐름을 가져야 합니다.

```mermaid
%% Memory fact는 영구 불변 지식이 아니라 검증 상태를 가진 운영 객체다.
stateDiagram-v2
  [*] --> candidate
  candidate --> active: replay 또는 human approval
  candidate --> denied: 근거 부족 또는 imported-only
  active --> challenged: verifier 변경 또는 회귀 의심
  challenged --> active: replay 통과
  challenged --> deprecated: replay 실패
  deprecated --> archived
  denied --> archived
```

## prompt에 넣는 memory도 작아야 한다

MemoryGraph에 검증된 fact만 넣어도, 매 실행에 전부 주입하면 다시 노이즈가 됩니다. 현재 작업과 관련된 작은 recall만 써야 합니다.

좋은 recall은 이런 형태입니다.

```text
Relevant project memory
- public API request/response schema 변경 시 contract artifact와 verifier evidence를 함께 갱신한다.
- 적용 범위: public_api, contract_change
- 제외 범위: internal_refactor, test_only_change
- 검증: contract verifier를 closeout 전에 실행한다.
```

나쁜 recall은 프로젝트 memory 전체를 그대로 붙이는 것입니다. 에이전트는 중요도와 적용 범위를 스스로 안정적으로 판정하지 못할 수 있습니다.

## 실전 체크리스트

승격 정책을 설계할 때는 다음 항목을 기준으로 점검합니다.

```text
MemoryGraph promotion 체크리스트

[ ] MemoryGraph를 raw trace 저장소로 쓰지 않는다.
[ ] phase 시작 전에는 read-only recall만 수행한다.
[ ] projectMemoryContext는 작고 관련성 높은 내용만 포함한다.
[ ] rules, developer instruction, hard rule과 중복되는 memory는 제거한다.
[ ] MemoryGraph 조회 실패와 workflow 실패를 분리한다.
[ ] MemoryGraph write는 explicit flag가 있을 때만 수행한다.
[ ] auto promotion은 verified-only만 허용한다.
[ ] replay evidence 또는 human approval 없는 candidate는 차단한다.
[ ] imported-only candidate는 장기 memory로 승격하지 않는다.
[ ] promotion decision은 replay scorecard에 남긴다.
[ ] fact lifecycle과 stale detection을 설계한다.
```

## 마무리

MemoryGraph를 “많이 저장하는 곳”으로 설계하면 에이전트는 더 똑똑해지는 것이 아니라 더 위험해질 수 있습니다. AI는 memory를 단순 참고가 아니라 작업 규칙처럼 따르는 경향이 있기 때문입니다.

그래서 원칙은 보수적이어야 합니다.

```text
read-only recall은 넓게 허용한다.
promotion은 좁게 허용한다.
write는 검증된 경우에만 허용한다.
```

다음 편에서는 이 구조의 원재료가 되는 AWTL을 다룹니다. 실패 로그를 어떻게 다음 실행의 힌트로 바꾸고, failed turn case와 replay scorecard로 운영할 수 있는지 살펴보겠습니다.
