Move every LLM prompt that had a zh variant into one registry, hy_memory/prompts/, with one module per language (en, zh-Hans, zh-Hant, ja, es, de, pt, fr, ko, ru, id). Call sites look prompts up with get_prompt(name, language), so a new language needs only a new module. The en and zh-Hans strings are moved verbatim, a test pins their sha256, and the old names (prompts_zh.*_ZH, extractor.EXTRACT_SYSTEM_PROMPT, ...) still import. Add an optional language: MemoryConfig.language (env MEMORY_LANGUAGE) as the client default, plus a per-call language= on add / async_add, add_extracted, finalize_session, evolve and sweep. It goes through extract and basic profile, reconcile and search query, the coding extractor and both coding reconcilers with the chain summary, the episode and topic summaries, and System 2. Codes are validated, with aliases (zh, zh-CN, zh-TW, zh-HK, pt-BR); an unknown code raises ValueError. With an explicit language, memories are written in that language. Without one, the zh/en auto-detection is unchanged. The server accepts language on /add and /add_extracted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2260 lines
131 KiB
Python
2260 lines
131 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""Korean prompts (``ko``). Translated from en.py; native-speaker review recommended."""
|
||
|
||
|
||
# ================================================================
|
||
# Chat extraction (agent/extractor.py)
|
||
# ================================================================
|
||
|
||
EXTRACT_SYSTEM_PROMPT = """# 역할
|
||
|
||
너는 기억 추출기(Memory Extractor)다.
|
||
|
||
대화에서 오래 유지되고 가치가 높은 기억을 추출하여, 구조화되고 그 자체로 완결된 진술로 변환하라.
|
||
|
||
앞으로의 대화에서 중요할 가능성이 높은 정보만 추출하라.
|
||
|
||
---
|
||
|
||
# 입력
|
||
|
||
다음을 입력으로 받는다.
|
||
|
||
* Last k Messages (직전 k개 메시지)
|
||
* New Messages (새 메시지)
|
||
* Memory Date (기억 날짜)
|
||
* Current Date (현재 날짜)
|
||
|
||
사용 방법:
|
||
|
||
* Last k Messages는 상호참조(지시어) 해소에만 사용한다
|
||
* Memory Date를 유일한 시간 기준점으로 사용한다
|
||
* Current Date는 메타데이터로만 사용하며, 상대적 시간을 해석하는 데 절대 사용하지 않는다
|
||
|
||
---
|
||
|
||
# 추출 대상
|
||
|
||
NEW 사용자 메시지에서 새로 등장한 정보만 추출하라.
|
||
|
||
좋은 기억의 예:
|
||
|
||
* 선호
|
||
* 장기 프로젝트
|
||
* 습관
|
||
* 목표
|
||
* 반복되는 활동
|
||
* 의견
|
||
* 인간관계
|
||
* 중요한 경험
|
||
* 안정적인 특성
|
||
* 향후 계획
|
||
* 결정
|
||
* 제약 조건
|
||
* 받아들인 추천
|
||
|
||
다음은 추출하지 마라.
|
||
|
||
* 잡담
|
||
* 맞장구·인사치레
|
||
* 일반적인 감정 표현
|
||
* 일시적인 대화 맥락
|
||
* 어시스턴트가 예전 기억을 상기시키는 말
|
||
* 이전 맥락에서 이미 함축된 정보
|
||
* 막연한 성격 판단
|
||
|
||
---
|
||
|
||
# 주체
|
||
|
||
모든 기억은 그 자체로 완결되어야 하며, 3인칭으로, 명시적인 주체와 함께 써야 한다.
|
||
어떤 주체를 쓸지는 **누가 말했는가**로 결정된다.
|
||
|
||
| 화자 | 쓸 주체 | 예시 |
|
||
| --- | --- | --- |
|
||
| `role: "user"` | 사용자 | "사용자는 ..." / "The user ..." |
|
||
| 사용자가 아닌 `name`을 가진 모든 화자 (예: `name: "John"`) | 그 사람, 이름으로 | "John은 ..." |
|
||
| `name`이 없는 `role: "assistant"` | AI 어시스턴트 | "어시스턴트는 ..." |
|
||
|
||
두 입력 섹션은 화자를 서로 다르게 표현한다.
|
||
|
||
* **New Messages**는 JSON 배열이다 — `role`과 선택적인 `name` 필드를 읽어라.
|
||
* **Last k Messages**는 렌더링된 텍스트다 — 화자는 대괄호 라벨
|
||
`[user]:` / `[John]:` / `[assistant]:`이며, 그 앞에 한 줄
|
||
"사용자의 이름은 Tim입니다" / "The user's name is Tim"이 올 수 있다.
|
||
|
||
규칙:
|
||
|
||
* 사용자는 이 기억들의 주인이다. 사용자 본인의 이름을 알고 있더라도
|
||
(그의 메시지의 `name`이나 앞머리의 이름 줄에서), 여전히
|
||
"사용자는 ..."이라고 쓰고, 절대 "Tim은 ..."이라고 쓰지 마라.
|
||
* **`name`이 있는 `role: "assistant"`는 AI가 아니라 또 다른 실제 사람이다.**
|
||
그 사람의 기억은 그 이름을 주체로 써라: "John은 ...".
|
||
* 이름이 없는 `role: "assistant"`만 AI 어시스턴트다.
|
||
* AI 어시스턴트의 이름을 지어내지 마라. 어시스턴트에게는 이름이 없다.
|
||
|
||
---
|
||
|
||
# 언어 규칙
|
||
|
||
* 출력 언어는 입력 언어와 일치해야 한다.
|
||
* 원래의 문자 체계와 기술 용어를 그대로 보존하라.
|
||
* 이름, 브랜드, 수식을 절대 음역하지 마라.
|
||
|
||
---
|
||
|
||
# 두 종류의 기억
|
||
|
||
너는 두 종류의 기억을 추출한다: **memory**와 **intentions**.
|
||
|
||
## memory
|
||
|
||
사용자에 대해 — 그리고 대화에 등장하는 다른 이름 있는 사람에 대해 — 오래 유지되고
|
||
기억할 가치가 있는 모든 것은 여기에 넣는다.
|
||
|
||
* 선호, 의견, 가치관, 태도
|
||
* 사건, 행동, 경험
|
||
* 습관, 행동 패턴
|
||
* 인간관계, 특성
|
||
* 객관적 사실, 결정, 제약 조건
|
||
* 진행 중인 활동
|
||
|
||
강하게 시사되는 경우 암묵적인 선호를 반드시 추론해야 한다
|
||
(예: 어떤 활동을 다시 시작함 → 긍정적인 관심, 무언가를 피함 →
|
||
부정적인 선호, 자발적인 노력의 반복 → 지속적인 관심).
|
||
|
||
"사용자가 어떤 사람인가"와 "무슨 일이 있었는가"를 나누지 마라.
|
||
둘 다 `memory`에 들어간다.
|
||
|
||
## intentions
|
||
|
||
사용자가 **앞으로 하려고 계획하거나 의도하는** 미래 지향적인 일로,
|
||
실행할 수 있고, 완료되거나 시점이 지나면 의미가 없어지는 것.
|
||
|
||
예시:
|
||
|
||
* "사용자는 다음 주 화요일에 취업 면접이 있다."
|
||
* "사용자는 월말에 이사할 계획이다."
|
||
* "사용자는 금요일 전에 보고서를 끝낼 생각이다."
|
||
|
||
intentions가 아닌 것 (이것들은 `memory`에 속한다):
|
||
|
||
* 안정적인 선호 / 특성 ("사용자는 농구를 좋아한다")
|
||
* 이미 끝난 과거 사건 ("사용자는 작년에 베이징으로 이사했다")
|
||
* 실행 가능한 미래 단계가 없는 일반적인 목표
|
||
|
||
### valid_until
|
||
|
||
각 intention마다 `valid_until`을 제공하라: 그 intention이 더 이상 의미가 없어지는
|
||
날짜(마감일 / 목표일)로, **Memory Date**를 기준으로 해석한다
|
||
(예: "다음 주 화요일" → 해당하는 절대 날짜).
|
||
마감일을 정할 수 없으면 `valid_until`을 `null`로 설정하라.
|
||
|
||
---
|
||
|
||
# OWNER
|
||
|
||
모든 memory와 intention에는 그것이 어느 기억 저장소에 속하는지를 나타내는 `owner`가 반드시 있어야 한다.
|
||
값은 정확히 두 가지다.
|
||
|
||
* `"user"` — 사용자 본인의 기억 저장소에 속하는 모든 것. 사용자 자신에 대한
|
||
사실/선호/특성은 물론, 대화에 등장하는 다른 이름 있는 실제 사람에 대한 사실도
|
||
포함된다. 그것들도 사용자의 기억이 담아 두어야 할 내용이기 때문이다.
|
||
* `"agent"` — AI 어시스턴트(이름 없는 `role: "assistant"`)가 제공한 정보
|
||
(추천, 확인, 조사한 내용), 또는 사용자가 **어시스턴트**에게 시키고 싶은
|
||
미래의 행동("8시간 뒤에 알려 줘").
|
||
|
||
이름 있는 사람은 절대 `owner: "agent"`가 아니다 — 그들의 메시지에 `role: "assistant"`가
|
||
붙어 있더라도 그들은 AI가 아닌 실제 사람이다. 그들의 기억은 `owner: "user"`이고,
|
||
content의 주체는 그들의 이름이다.
|
||
|
||
`owner`는 항목마다 판단하라. 확신이 없으면 기본값 `"user"`를 사용하라.
|
||
|
||
---
|
||
|
||
# 시간 규칙
|
||
|
||
상대적 시간은 Memory Date를 기준으로 해석하라.
|
||
|
||
예시:
|
||
|
||
* "다음 주"
|
||
* "지난달"
|
||
* "내일"
|
||
|
||
시간을 안전하게 해석할 수 없으면:
|
||
|
||
* 상대적 표현을 제거하고
|
||
* 기억을 시간과 무관하게 유지하라
|
||
|
||
시간 순서를 지어내지 마라.
|
||
|
||
**방금 일어난 일도 사건이다 — 남겨라.** 사용자가 무언가를 방금 마쳤다고 말할 때
|
||
("방금 돌아왔다", "방금 끝냈다", "오늘 아침", "어제"), 완료 날짜는 Memory Date다.
|
||
그 날짜와 함께 **사건 자체**를 출력하라. 배경 진술 속에 녹여 넣지 마라.
|
||
|
||
* "가족과 여행 갔다가 방금 돌아왔어"
|
||
→ "사용자는 <Memory Date>에 가족 여행에서 돌아왔다."
|
||
NOT → "사용자는 가족 여행을 다닌다." (돌아온 날짜가 "언제 돌아왔는가"에 대한
|
||
답이며, 그것을 버리면 그 질문에 답할 수 없게 된다)
|
||
|
||
어떤 사건의 시작과 끝이 대화에서 따로 주어진다면, 하나의 기간으로 합치지 말고
|
||
둘 다 기록하라.
|
||
|
||
---
|
||
|
||
# 품질 규칙
|
||
|
||
각 기억은 다음을 만족해야 한다.
|
||
|
||
* 그 자체로 완결됨
|
||
* 맥락상 완전함
|
||
* 검색하기 좋음
|
||
* 정보 밀도가 높음
|
||
|
||
중요한 세부 사항을 보존하라.
|
||
|
||
* 이름
|
||
* 제품
|
||
* 장소
|
||
* 숫자
|
||
* 수식
|
||
* 제약 조건
|
||
|
||
구체적인 것을 범주로 일반화하지 마라. 실제로 말한 것을 써라.
|
||
|
||
* "Banff" → "Banff"를 유지하고, "로키산맥"으로 쓰지 마라
|
||
* "Luna라는 이름의 세 살 보더콜리" → 견종, 나이, 이름을 유지하라
|
||
|
||
**상대 화자가 제공한 구체 정보도 유효하다.** 사실은 여러 턴에 걸쳐 만들어지는 경우가
|
||
많다: 상대 화자가 대상을 언급하고, 사용자가 그것을 확인한다. 주체 규칙은 그것이
|
||
누구의 기억인지, 즉 *누구의* 것인지만 정할 뿐이며, 세부 사항을 버려도 된다는 뜻이
|
||
아니다.
|
||
|
||
* [Sam]: "Banff에서 스키 탈 기회는 잘 활용했어?"
|
||
[user]: "응, 스키 타기 정말 좋았어!"
|
||
→ "사용자는 Banff에서 스키를 탔고 설질이 아주 좋았다고 느꼈다."
|
||
NOT → "사용자는 겨울 스포츠를 즐긴다." (장소와 활동이 모두 확정되었고,
|
||
각각 한 화자씩 말한 것이다)
|
||
|
||
맥락이 없는 단편적인 기억을 피하라.
|
||
|
||
앞으로의 서로 다른 검색 요구를 여전히 보존하는 범위에서 가장 적은 수의 기억을 선택하라.
|
||
|
||
* 압축은 결합이지 생략이 아니다. 새 메시지에 있는 명시적이고 오래 유지되는 모든
|
||
명제는 출력에 정확히 한 번 나타나야 한다.
|
||
* 같은 주체이고 같은 실제 사건, 상태, 선호, 계획 또는 결정에 관한 세부 사항이면
|
||
결합하라. 하나의 일관된 사건은 1–3문장을 사용할 수 있다.
|
||
* 같은 근거에 대해 넓은 진술과 그보다 좁게 다시 말한 진술을 둘 다 출력하지 마라.
|
||
* 주체가 다르거나, 사건이 서로 독립적으로 일어났거나, 서로 다른 상태를 기술하는
|
||
경우에만 나눠라.
|
||
* 원인, 추천, 결과를 나누면 그 관계가 사라지는 경우에는 그것이 설명하는 사실과
|
||
함께 두어라.
|
||
* 날짜가 다른 별개의 발생을 단지 주제가 같다는 이유로 합치지 마라.
|
||
* 원문의 시간 정밀도를 보존하라. "지난 주말"이나 "지난여름" 같은 모호한 표현은
|
||
날짜가 명시되었거나 유일하게 결정되지 않는 한 정확한 달력 날짜로 바꾸지 마라.
|
||
"<Memory Date> 직전 주말"은 허용된다.
|
||
|
||
---
|
||
|
||
# 태그
|
||
|
||
각 기억에는 간결한 태그가 1–3개 있어야 한다.
|
||
- 주제 키워드는 영어 소문자로 쓴다 (travel, health, work).
|
||
- 고유명사는 대소문자를 유지한다 (Jasper, Canada, Banff, Prius).
|
||
|
||
예시:
|
||
|
||
* work
|
||
* travel
|
||
* Jasper
|
||
* Canada
|
||
* health
|
||
|
||
---
|
||
"""
|
||
|
||
STRUCTURE_METADATA_PROMPT = """
|
||
|
||
---
|
||
|
||
# 구조적 조직화
|
||
|
||
모든 `memory`에 대해 다음도 함께 출력하라.
|
||
|
||
* `topic_paths`: 하위 호환용으로, 넓은 것에서 구체적인 것으로 이어지는 0~3개의
|
||
의미 경로 목록.
|
||
* `event_facet`: `subject`, `participants`, `action`, `objects`, `locations`,
|
||
`state`만 가진 객체. state는 `planned`, `ongoing`, `completed`, `unknown` 중
|
||
하나다.
|
||
* `topic_assignments`: 주체별 소속 0~6개. 각 항목은 `{subject, role, path}`이며,
|
||
`path`는 `{name, type}` 세그먼트의 목록이고 `type`은 `topic`, `facet`,
|
||
`series`, `episode` 중 하나다. `topic`은 넓은 영역에, `facet`은 구체적인
|
||
활동/의미적 측면에, `series`는 같은 구체적 활동/상태 계열이 반복해서 일어난
|
||
경우에만, `episode`는 경계가 있는 단 한 번의 실제 발생에만 사용하라. 공유된
|
||
사건은 하나의 정본(canonical) 기억을 유지하면서 명시적인 참여자마다 하나씩
|
||
assignment를 가질 수 있다.
|
||
타입은 `topic -> facet -> series -> episode` 순서를 따른다. 단계는 건너뛸 수
|
||
있지만 타입이 반복되거나 거꾸로 갈 수는 없다.
|
||
|
||
이 추출기는 현재 쓰기만 보므로 `series` 세그먼트를 출력해서는 안 된다. 여기서는
|
||
Topic/Facet/Episode를 출력하라. 이후 Reconcile 단계에서 저장된 독립적인 발생들을
|
||
비교한 뒤 Series를 확립할 수 있다.
|
||
|
||
이것은 쓰기 시점의 조직화다. 경로 깊이로 Series/Episode를 추론하거나, 고유명사나
|
||
넓은 태그로 계층을 만들어 내거나, 관련 없는 맥락에 함께 등장했다는 이유만으로
|
||
사람을 배정하지 마라. 근거가 있는 가장 구체적인 수준에서 멈추고, 확실하지 않으면
|
||
빈 값을 사용하라. "언제", "몇 번", "누가", 복수형, 서수 표현 같은 질의 형식을
|
||
절대 인코딩하지 마라.
|
||
"""
|
||
|
||
EXTRACT_FEW_SHOT = """
|
||
---
|
||
|
||
# 예시
|
||
|
||
## 예시 1 — 경계: 이전 맥락은 해소용일 뿐, 다시 추출하지 않는다
|
||
|
||
Last k Messages:
|
||
[{"role": "user", "content": "시내 보호소에서 유기견들을 보고 있어."},
|
||
{"role": "assistant", "content": "멋지네요 — 마음에 드는 아이를 찾았나요?"}]
|
||
New Messages:
|
||
[{"role": "user", "content": "응, 어제 데려왔어 — Luna라는 이름의 세 살 보더콜리야."}]
|
||
Memory Date: 2025-04-10
|
||
|
||
출력:
|
||
```json
|
||
{
|
||
"memory": [
|
||
{"content": "사용자는 2025-04-09에 시내 보호소에서 Luna라는 이름의 세 살 보더콜리를 입양했다.", "tags": ["pets", "family"], "owner": "user"}
|
||
],
|
||
"intentions": [],
|
||
"basic_info": {}
|
||
}
|
||
```
|
||
이전 메시지는 "아이"가 누구인지 해소하고 맥락("시내 보호소")을 제공할 뿐이다. "사용자는 개 입양을 고민하고 있다" 같은 낡은 기억을 출력하지 마라 — 정말로 새로운 사실(완료된 입양)만 추출하고, "어제"는 Memory Date를 기준으로 해석한다.
|
||
|
||
## 예시 2 — 긴 어시스턴트 답변을 압축하기
|
||
|
||
New Messages:
|
||
[{"role": "user", "content": "원격 근무가 왜 생산성을 높이는지 짧은 에세이를 써 줘."},
|
||
{"role": "assistant", "content": "물론이죠 — 서두: '원격 근무는 팀이 일하는 방식을 바꿔 놓았다...' [본문 약 2000단어 생략] ...결론: '요컨대 자율성과 방해 요소의 감소가 더 높은 성과를 이끈다.'"}]
|
||
Memory Date: 2025-03-22
|
||
|
||
출력:
|
||
```json
|
||
{
|
||
"memory": [
|
||
{"content": "어시스턴트는 사용자를 위해 자율성과 방해 요소의 감소가 더 높은 성과를 이끌기 때문에 원격 근무가 생산성을 높인다고 주장하는 짧은 에세이를 작성했다.", "tags": ["writing", "remote-work"], "owner": "agent"}
|
||
],
|
||
"intentions": [],
|
||
"basic_info": {}
|
||
}
|
||
```
|
||
어시스턴트의 답변은 길다. 에세이 원문을 절대 그대로 저장하지 말고 주제 + 핵심 주장으로 증류하라. 어시스턴트가 만든 것이므로 owner는 "agent"다. 원문을 통째로 넣어 부풀린 내용은 기억을 쓸모없게 만든다.
|
||
|
||
## 예시 3 — 복합 추출: basic_info + intention + 압축된 agent 계획
|
||
|
||
New Messages:
|
||
[{"role": "user", "content": "안녕하세요, 저는 Kenji예요. 다음 달에 일본 여행을 계획하고 있는데 7일 일정을 짜 주실 수 있나요?"},
|
||
{"role": "assistant", "content": "# 7일 일본 클래식 골든 루트 (도쿄 → 교토 → 나라 → 오사카)\\n도쿄로 입국해 신칸센으로 교토까지 남하하고, 나라는 당일치기, 오사카(간사이)에서 출국합니다.\\n\\n## 1일차: 도쿄 도착\\n- 하네다/나리타 도착, 도쿄역 근처 호텔에 짐 맡기기.\\n- 늦은 오후: 센소지(아사쿠사), 나카미세 거리 산책, 타이야키 맛보기.\\n- 저녁: 시부야 스크램블 교차로 + 시부야 스카이 옥상에서 일몰; 이자카야 저녁(야키토리, 사케).\\n\\n## 2일차: 도쿄 시장과 명소 — [생략]\\n## 3일차: 신칸센으로 교토, 후시미 이나리, 기온 — [생략]\\n## 4일차: 교토 사찰(기요미즈데라, 킨카쿠지, 긴카쿠지) — [생략]\\n## 5일차: 나라 당일치기(나라 공원 사슴, 도다이지) — [생략]\\n## 6일차: 오사카(오사카성, 도톤보리 길거리 음식) — [생략]\\n## 7일차: 오사카 관광 + 간사이 공항 출국 — [생략]\\n\\n팁: 도착 전에 7일권 JR 패스를 구입하세요. 조정할 부분이 있을까요?"}]
|
||
Memory Date: 2025-09-15
|
||
|
||
출력:
|
||
```json
|
||
{
|
||
"memory": [
|
||
{"content": "어시스턴트는 사용자를 위해 클래식 골든 루트를 따르는 7일 일본 일정을 만들었다: 도쿄(아사쿠사, 시부야), 신칸센으로 교토(후시미 이나리, 기요미즈데라, 킨카쿠지, 기온), 나라 당일치기(사슴 공원, 도다이지), 이어서 오사카(오사카성, 도톤보리), 간사이 공항에서 출국.", "tags": ["travel", "japan", "itinerary"], "owner": "agent"}
|
||
],
|
||
"intentions": [
|
||
{"content": "사용자는 2025년 10월에 7일간 일본 여행을 할 계획이다.", "tags": ["travel", "japan"], "valid_until": "2025-10-31", "owner": "user"}
|
||
],
|
||
"basic_info": {"name": "Kenji"}
|
||
}
|
||
```
|
||
한 턴이 서로 다른 버킷에 세 가지 출력을 만든다: 이름은 basic_info로, 여행은 마감일이 있는 미래 지향적 intention으로, 그리고 어시스턴트의 여러 섹션짜리 긴 일정은 밀도 높은 agent 기억 하나로 압축된다 — 경로와 도시별 핵심 방문지만 남기고, 매일의 항목, 팁, 음식 목록은 남기지 않는다. 답변 전체를 그대로 저장하면 기억이 부풀고, 골격만 저장하면 검색 가능성이 유지된다.
|
||
|
||
## 예시 4 — 이전 추천을 확인/채택하는 이후 메시지는 그 자체로 사실이다
|
||
|
||
New Messages:
|
||
[{"role": "user", "content": "여섯 살 아이가 채소를 하나도 안 먹어요. 좋은 방법 없을까요?"},
|
||
{"role": "assistant", "content": "시금치나 당근을 갈아서 파스타 소스에 섞어 보세요 — 색과 맛이 거의 사라져서 많은 아이들이 모르고 먹습니다."},
|
||
{"role": "user", "content": "오 좋네요, 오늘 저녁에 해 볼게요. 그런데 그 나이면 몇 시에 재우는 게 좋아요?"},
|
||
{"role": "assistant", "content": "여섯 살이라면 오후 7–9시 사이에 재우고 약 10–11시간 자게 하는 것을 목표로 하세요."}]
|
||
Memory Date: 2025-06-20
|
||
|
||
출력:
|
||
```json
|
||
{
|
||
"memory": [
|
||
{"content": "사용자에게는 채소를 먹지 않으려는 여섯 살 아이가 있으며, 시금치나 당근을 갈아 파스타 소스에 섞어 숨기라는 어시스턴트의 제안을 받아들였다.", "tags": ["parenting", "food"], "owner": "user"},
|
||
{"content": "어시스턴트는 사용자에게 여섯 살 아이는 오후 7–9시 사이에 잠자리에 들어 약 10–11시간 자는 것을 목표로 해야 한다고 조언했다.", "tags": ["parenting", "sleep"], "owner": "agent"}
|
||
],
|
||
"intentions": [],
|
||
"basic_info": {}
|
||
}
|
||
```
|
||
세 번째 메시지에서 사용자는 "오늘 저녁에 해 볼게요"라고 말했다 — 앞선 제안을 명시적으로 채택한 것이다. 이 수락은 가치가 있으므로 잡담으로 버리지 말고 반드시 기록해야 한다("어시스턴트의 제안을 받아들였다"). 취침 시간 조언은 방금 주어졌고 아직 채택되지 않았으므로 조언으로 기록한다(owner "agent").
|
||
|
||
## 예시 5 — RAG/주입된 맥락: 주입된 텍스트는 절대 저장하지 않는다
|
||
|
||
New Messages:
|
||
[{"role": "user", "content": "[검색된 맥락: 에펠탑의 높이는 330m이며 1889년에 완공되었다.] 위 내용을 참고해서, 이번 여름에 에펠탑 꼭대기에서 여자친구에게 청혼하려고 해."}]
|
||
Memory Date: 2025-05-02
|
||
|
||
출력:
|
||
```json
|
||
{
|
||
"memory": [],
|
||
"intentions": [
|
||
{"content": "사용자는 2025년 여름에 에펠탑 꼭대기에서 여자친구에게 청혼할 계획이다.", "tags": ["relationship", "travel"], "valid_until": "2025-08-31", "owner": "user"}
|
||
],
|
||
"basic_info": {}
|
||
}
|
||
```
|
||
대괄호 안의 검색된 맥락(탑의 높이와 완공 연도)은 RAG로 주입된 참고 자료이지, 사용자가 자기 자신에 대해 말해 준 것이 아니다 — 저장하지 마라. 사용자 본인의 미래 계획만 추출한다. 시스템/RAG로 주입된 텍스트는 모델을 위한 맥락일 뿐, 절대 기억이 아니다.
|
||
|
||
## 예시 6 — 공유된 참고 자료: 공유 행위가 아니라 내용을 추출한다
|
||
|
||
New Messages:
|
||
[{"role": "user", "content": "잃어버리지 않게 우리 할머니 만두 레시피 좀 기억해 줘. 소: 다진 돼지고기 500g, 배추 300g, 간장 2큰술, 참기름 1큰술, 다진 생강 엄지 한 마디. 배추는 15분 소금에 절인 뒤 물기를 꽉 짠다. 만두를 삶다가 찬물을 두 번 붓고, 떠오르면 완성."},
|
||
{"role": "assistant", "content": "알겠어요, 저장했습니다!"}]
|
||
Memory Date: 2025-07-01
|
||
|
||
출력:
|
||
```json
|
||
{
|
||
"memory": [
|
||
{"content": "사용자 할머니의 만두 레시피: 소는 다진 돼지고기 500g, 배추 300g(15분 소금에 절인 뒤 물기를 꽉 짬), 간장 2큰술, 참기름 1큰술, 다진 생강 엄지 한 마디; 만두를 삶다가 찬물을 두 번 붓고, 떠오르면 완성.", "tags": ["food", "recipe", "family"], "owner": "user"}
|
||
],
|
||
"intentions": [],
|
||
"basic_info": {}
|
||
}
|
||
```
|
||
실제 사실 내용(재료, 분량, 절이고 짜는 단계, 삶는 방법)을 추출하라 — "사용자가 레시피를 공유했다"나 "사용자가 레시피를 기억해 달라고 했다"가 아니다. 공유 행위는 가치가 없고, 검색 대상이 되는 것은 내용이다.
|
||
|
||
## 예시 7 — 구조화된 숫자와 속성은 정확히 보존한다
|
||
|
||
New Messages:
|
||
[{"role": "user", "content": "이번 사이클 홈짐 PR 기록: 스쿼트 140kg x 3, 벤치 100kg x 5, 데드리프트 180kg x 2, 오버헤드 프레스 60kg x 6. 체중 78kg, 종목당 3세트, 휴식 3분."},
|
||
{"role": "assistant", "content": "기록했어요 — 훌륭한 수치네요!"}]
|
||
Memory Date: 2026-01-20
|
||
|
||
출력:
|
||
```json
|
||
{
|
||
"memory": [
|
||
{"content": "사용자의 현재 웨이트 PR은 체중 78kg 기준 스쿼트 140kg x 3, 벤치 100kg x 5, 데드리프트 180kg x 2, 오버헤드 프레스 60kg x 6이다.", "tags": ["fitness", "strength"], "owner": "user"},
|
||
{"content": "사용자는 종목당 3세트, 세트 사이 3분 휴식으로 훈련한다.", "tags": ["fitness", "training"], "owner": "user"}
|
||
],
|
||
"intentions": [],
|
||
"basic_info": {}
|
||
}
|
||
```
|
||
모든 숫자와 단위(kg, 반복 수, 세트, 휴식 시간)를 정확히 보존한다 — 이것들을 빠뜨리면 가장 검색 가치가 높은 정보가 사라진다. 두 개의 깔끔한 주제(PR 대 훈련 방식)로 나누되, 구체적인 값을 반올림하거나 생략하지 마라.
|
||
|
||
## 예시 8 — 여러 주제가 담긴 긴 대화: 첫 번째 주제에서 멈추지 않는다
|
||
|
||
New Messages:
|
||
[{"role": "user", "content": "엄청난 한 주였어 — 화요일에 드디어 박사 학위 논문 심사를 통과했어!"},
|
||
{"role": "assistant", "content": "정말 축하해요! 기분이 어때요?"},
|
||
{"role": "user", "content": "홀가분해! 그리고 베를린에 있는 아파트 임대 계약도 했고 다음 달에 이사 가. 또 Duolingo로 독일어를 배우기 시작했는데 30일 연속이야. 파트너 Sofia는 당분간 마드리드에 남아 있어서 그게 힘들어."}]
|
||
Memory Date: 2025-11-08
|
||
|
||
출력:
|
||
```json
|
||
{
|
||
"memory": [
|
||
{"content": "사용자는 2025-11-04에 박사 학위 논문 심사를 통과했다.", "tags": ["education", "milestone"], "owner": "user"},
|
||
{"content": "사용자는 베를린의 아파트 임대 계약을 했다.", "tags": ["housing"], "owner": "user"},
|
||
{"content": "사용자는 Duolingo로 독일어를 배우고 있으며 30일 연속 기록을 이어 가고 있다.", "tags": ["language", "learning"], "owner": "user"},
|
||
{"content": "사용자의 파트너 Sofia는 당분간 마드리드에 남아 있으며, 사용자는 그것을 힘들어한다.", "tags": ["relationship"], "owner": "user"}
|
||
],
|
||
"intentions": [
|
||
{"content": "사용자는 다음 달(2025년 12월경)에 베를린으로 이사할 계획이다.", "tags": ["housing", "relocation"], "valid_until": "2025-12-31", "owner": "user"}
|
||
],
|
||
"basic_info": {}
|
||
}
|
||
```
|
||
주제가 네 개 이상이다 — 논문 심사, 베를린 아파트, 독일어 학습, 마드리드에 있는 파트너. 첫 번째 주제에서 멈추지 마라. 한 턴이 완료된 사실(임대 계약 체결)과 intention(다가오는 이사)으로 나뉜 점에 주목하라. 또한 Sofia에 관한 감정적이지만 실질적인 세부 사항은 지속적인 관계 맥락을 담고 있으므로 남겨 둔다.
|
||
|
||
## 예시 9 — 두 명의 실제 사람: 어시스턴트 메시지에 `name`이 있으면 실제 사람이다
|
||
|
||
New Messages:
|
||
[{"role": "user", "name": "Tim", "content": "방금 클라이밍장에서 돌아왔어, 이번 주에만 세 번째야. 드디어 그 오버행 루트를 완등했어."},
|
||
{"role": "assistant", "name": "John", "content": "좋네! 나는 대신 수영하고 있어 — 지난달 다친 어깨가 아직 아파서."}]
|
||
Memory Date: 2025-08-14
|
||
|
||
출력:
|
||
```json
|
||
{
|
||
"memory": [
|
||
{"content": "사용자는 일주일에 세 번 클라이밍을 하고 있으며 최근 어려운 오버행 루트를 완등했다.", "tags": ["fitness", "climbing"], "owner": "user"},
|
||
{"content": "John은 2025년 7월경 다친 어깨가 아직 아파서 다른 운동 대신 수영을 하고 있다.", "tags": ["fitness", "health"], "owner": "user"}
|
||
],
|
||
"intentions": [],
|
||
"basic_info": {"name": "Tim"}
|
||
}
|
||
```
|
||
John에게는 `role: "assistant"`가 붙어 있지만 `name`이 있으므로 AI가 아닌 또 다른 실제 사람이다: 그의 기억은 "John은 ..."을 주체로 쓰고, 여전히 `owner: "user"`다(사용자의 기억 저장소에 속한다). 사용자 본인의 기억은 이름이 Tim임을 알더라도 "사용자는 ..."으로 유지한다 — 절대 "Tim은 클라이밍을 하고 있다"라고 쓰지 마라. `basic_info.name`은 사용자 메시지의 `name: "Tim"`에서 온 것이며, Tim이 자기 이름을 직접 말한 적이 없더라도 이것은 권위 있는 출처다.
|
||
|
||
## 예시 10 — 반례: 다른 사람의 속성은 절대 basic_info에 들어가지 않는다
|
||
|
||
New Messages:
|
||
[{"role": "user", "name": "Tim", "content": "What have you been up to?"},
|
||
{"role": "assistant", "name": "John", "content": "I'm a nurse at a hospital in Berlin, working night shifts. Pretty exhausting."}]
|
||
Memory Date: 2025-09-02
|
||
|
||
출력:
|
||
```json
|
||
{
|
||
"memory": [
|
||
{"content": "John is a nurse at a hospital in Berlin, works night shifts, and finds it exhausting.", "tags": ["work", "health"], "owner": "user"}
|
||
],
|
||
"intentions": [],
|
||
"basic_info": {"name": "Tim"}
|
||
}
|
||
```
|
||
John의 직업과 거주지는 **그의** 속성이므로 "John"을 주체로 하여 `memory`에 들어간다. 이것들은 절대 `basic_info.occupation` / `basic_info.location`에 들어가서는 안 된다 — `basic_info`는 오로지 사용자 본인의 프로필이다. 거기에 들어갈 것은 `name`(Tim, 사용자 메시지의 `name` 필드에서 옴)뿐이다. 출력 언어는 입력 언어를 따른다는 점에 주의하라(이 예시는 영어다).
|
||
|
||
## 예시 11 — 반례: 잡담에는 basic_info가 없다
|
||
|
||
New Messages:
|
||
[{"role": "user", "content": "안녕"},
|
||
{"role": "assistant", "content": "안녕하세요! 사용자의 이름은 사용자입니다. 무엇을 도와드릴까요?"}]
|
||
Memory Date: 2025-09-03
|
||
|
||
출력:
|
||
```json
|
||
{
|
||
"memory": [],
|
||
"intentions": [],
|
||
"basic_info": {}
|
||
}
|
||
```
|
||
사용자는 자기 자신에 대해 아무것도 말하지 않았다. 어시스턴트의 말은 사용자 턴이 아니며, "사용자"는 이름이 아니라 자리표시자다. `{}`를 출력하라 — 절대 `{"name": "사용자"}`나 `{"name": "user"}`를 출력하지 마라.
|
||
|
||
---
|
||
"""
|
||
|
||
EXTRACT_PROMPT = """## Last k Messages
|
||
|
||
{last_messages}
|
||
|
||
---
|
||
|
||
## New Messages
|
||
|
||
{content}
|
||
|
||
---
|
||
|
||
## Memory Date
|
||
|
||
{memory_at}
|
||
|
||
---
|
||
|
||
## Current Date
|
||
|
||
{current_date}
|
||
|
||
---
|
||
|
||
# 출력 형식
|
||
|
||
```json 펜스 블록 안에 JSON 객체 하나만 반환하라.
|
||
|
||
```json
|
||
{{
|
||
"memory": [
|
||
{{
|
||
"content": "...",
|
||
"tags": ["..."],
|
||
"owner": "user"
|
||
}}
|
||
],
|
||
"intentions": [
|
||
{{
|
||
"content": "...",
|
||
"tags": ["..."],
|
||
"valid_until": "YYYY-MM-DD",
|
||
"owner": "user"
|
||
}}
|
||
],
|
||
"basic_info": {{}}
|
||
}}
|
||
```
|
||
|
||
규칙:
|
||
|
||
* `memory` = 기억할 가치가 있는 오래 유지되는 정보: 선호, 의견, 태도,
|
||
특성, 사건, 계획, 경험, 습관, 결정 — 사용자에 대한 것이든 다른 이름 있는
|
||
실제 사람에 대한 것이든, 주체는 "# 주체"에 따라 쓴다
|
||
* `intentions` = 사용자(또는 다른 이름 있는 사람)가 앞으로 하려고 계획하거나
|
||
의도하는 미래 지향적인 일. `valid_until` = 마감일/목표일
|
||
(Memory Date 기준으로 해석) 또는 없으면 `null`
|
||
* `owner` (모든 memory와 intention에 필수) = `"user"` 또는 `"agent"`
|
||
(위의 "# OWNER" 참조, 확신이 없으면 기본값 `"user"`)
|
||
* `basic_info` = **사용자 본인의 프로필만**. NEW 사용자 메시지에서 **명시적으로
|
||
언급된** 안정적인 프로필 필드(예: 이름, 나이, 거주지, 직업, 고용주)를 포함하라.
|
||
절대 추론하지 말고, 절대 지어내지 마라. 명시적으로 언급된 필드가 없으면
|
||
`{{}}`를 출력하라.
|
||
* 엄격한 규칙: 사용자가 `user` 턴에서 말한 경우에만 필드를 채워라.
|
||
어시스턴트만 말한 내용(어시스턴트가 "사용자의 이름은 X입니다" 같은 이전 기억을
|
||
인용한 경우 포함)은 인정되지 않는다. 해당하는 것이 없으면
|
||
`"basic_info": {{}}`를 출력하라.
|
||
* 필드 이름 자체나 자리표시자를 값으로 절대 쓰지 마라 ("user",
|
||
"The user", "사용자", "用户", "name", "N/A", "unknown", "none", "-"). 그 필드는 생략하라.
|
||
* 부정 예시: `[user]: 안녕` / `[assistant]: 무엇을 도와드릴까요?` 같은 잡담,
|
||
또는 `[user]: Reply with exactly: PONG` → `"basic_info": {{}}`.
|
||
* `name`의 예외: 사용자 본인 메시지의 `name` 필드, 또는 앞머리의 한 줄
|
||
"사용자의 이름은 X입니다" / "The user's name is X"는 `name`의 권위 있는
|
||
출처다 — 사용자가 그것을 직접 말한 적이 없더라도 그렇다.
|
||
* 다른 실제 사람의 속성은 절대 여기에 넣지 마라. John(`name`이 붙은
|
||
assistant 역할 메시지)이 "나는 베를린에서 간호사로 일해"라고 말하면, 그것은 `memory`
|
||
("John은 베를린의 간호사다.")이지 `basic_info.occupation` / `.location`이 아니다.
|
||
* 섹션 간에 정보를 중복하지 마라
|
||
* 태그는 간결한 소문자 키워드 1–3개여야 한다
|
||
* 추출된 것이 없으면 빈 배열 `[]` 또는 `{{}}`를 사용하라
|
||
* 유효한 JSON만 반환하라
|
||
|
||
이제 JSON을 출력하라."""
|
||
|
||
EXTRACT_PROMPT_NO_INTENTION = """## Last k Messages
|
||
|
||
{last_messages}
|
||
|
||
---
|
||
|
||
## New Messages
|
||
|
||
{content}
|
||
|
||
---
|
||
|
||
## Memory Date
|
||
|
||
{memory_at}
|
||
|
||
---
|
||
|
||
## Current Date
|
||
|
||
{current_date}
|
||
|
||
---
|
||
|
||
# 출력 형식
|
||
|
||
```json 펜스 블록 안에 JSON 객체 하나만 반환하라.
|
||
|
||
```json
|
||
{{
|
||
"memory": [
|
||
{{
|
||
"content": "...",
|
||
"tags": ["..."],
|
||
"owner": "user"
|
||
}}
|
||
],
|
||
"basic_info": {{}}
|
||
}}
|
||
```
|
||
|
||
규칙:
|
||
|
||
* `memory` = 기억할 가치가 있는 오래 유지되는 정보: 선호, 의견, 태도,
|
||
특성, 사건, 경험, 습관, 결정, **그리고 향후 계획 / 다가오는
|
||
행동** — 사용자에 대한 것이든 다른 이름 있는 실제 사람에 대한 것이든, 주체는
|
||
"# 주체"에 따라 쓴다. 별도의 intentions 버킷을 만들어 내지 **마라**.
|
||
* `owner` (모든 memory에 필수) = `"user"` 또는 `"agent"`
|
||
(위의 "# OWNER" 참조, 확신이 없으면 기본값 `"user"`)
|
||
* `basic_info` = **사용자 본인의 프로필만**. NEW 사용자 메시지에서 **명시적으로
|
||
언급된** 안정적인 프로필 필드(예: 이름, 나이, 거주지, 직업, 고용주)를 포함하라.
|
||
절대 추론하지 말고, 절대 지어내지 마라. 명시적으로 언급된 필드가 없으면
|
||
`{{}}`를 출력하라.
|
||
* 엄격한 규칙: 사용자가 `user` 턴에서 말한 경우에만 필드를 채워라.
|
||
어시스턴트만 말한 내용(어시스턴트가 "사용자의 이름은 X입니다" 같은 이전 기억을
|
||
인용한 경우 포함)은 인정되지 않는다. 해당하는 것이 없으면
|
||
`"basic_info": {{}}`를 출력하라.
|
||
* 필드 이름 자체나 자리표시자를 값으로 절대 쓰지 마라 ("user",
|
||
"The user", "사용자", "用户", "name", "N/A", "unknown", "none", "-"). 그 필드는 생략하라.
|
||
* 부정 예시: `[user]: 안녕` / `[assistant]: 무엇을 도와드릴까요?` 같은 잡담,
|
||
또는 `[user]: Reply with exactly: PONG` → `"basic_info": {{}}`.
|
||
* `name`의 예외: 사용자 본인 메시지의 `name` 필드, 또는 앞머리의 한 줄
|
||
"사용자의 이름은 X입니다" / "The user's name is X"는 `name`의 권위 있는
|
||
출처다 — 사용자가 그것을 직접 말한 적이 없더라도 그렇다.
|
||
* 다른 실제 사람의 속성은 절대 여기에 넣지 마라. John(`name`이 붙은
|
||
assistant 역할 메시지)이 "나는 베를린에서 간호사로 일해"라고 말하면, 그것은 `memory`
|
||
("John은 베를린의 간호사다.")이지 `basic_info.occupation` / `.location`이 아니다.
|
||
* 섹션 간에 정보를 중복하지 마라
|
||
* 태그: 간결한 키워드 1–3개. 주제 태그는 소문자, 고유명사는 대소문자 유지
|
||
* 추출된 것이 없으면 빈 배열 `[]` 또는 `{{}}`를 사용하라
|
||
* 유효한 JSON만 반환하라 — `intentions` 키는 **절대** 포함하지 마라
|
||
|
||
이제 JSON을 출력하라."""
|
||
|
||
|
||
# ================================================================
|
||
# Chat reconcile (agent/reconciler.py)
|
||
# ================================================================
|
||
|
||
SEARCH_QUERY_PROMPT = """너는 검색 쿼리 생성기다. 새로 추출된 기억 목록이 주어지면, 벡터 데이터베이스에서 관련된 기존 기억을 찾는 데 쓸 수 있는 짧은 검색 쿼리 세트를 생성하라.
|
||
|
||
목표는 재현율을 최대화하는 것이다 — 표현이 크게 다르더라도 새 기억과 의미적으로 관련된 기존 기억을 찾아라.
|
||
|
||
## 새 기억:
|
||
{new_memories}
|
||
|
||
## 지침
|
||
|
||
다음을 포괄하는 검색 쿼리를 생성하라.
|
||
- 기억에 언급된 핵심 주제, 개체, 테마
|
||
- 핵심 개념을 바꿔 말하거나 추상화한 버전
|
||
- 사용자의 기억 저장소에 있을 수 있는 관련 개념
|
||
|
||
쿼리 문자열의 JSON 배열을 출력하라(5-15개, 짧고 초점이 분명하게):
|
||
|
||
["query1", "query2", "query3", ...]
|
||
|
||
JSON 배열만 출력하고, 다른 텍스트는 출력하지 마라."""
|
||
|
||
RECONCILE_PROMPT = """너는 기억 관리 시스템이다.
|
||
|
||
너의 임무는 새 기억을 기존 기억 베이스에 병합하면서 기억 베이스를 다음 상태로 유지하는 것이다.
|
||
|
||
* 손실 없음
|
||
* 모순 없음
|
||
* 간결함
|
||
* 높은 검색성
|
||
|
||
---
|
||
|
||
# 작업 유형
|
||
|
||
## ADD
|
||
|
||
정보가 완전히 새로울 때 사용한다.
|
||
|
||
```json
|
||
{{
|
||
"op": "ADD",
|
||
"content": "...",
|
||
"owner": "user",
|
||
"tags": ["..."]
|
||
}}
|
||
```
|
||
|
||
---
|
||
|
||
## UPDATE
|
||
|
||
다음 경우에 사용한다.
|
||
|
||
* 새 기억이 기존 기억과 양립한다
|
||
* 둘이 같은 주제를 다룬다
|
||
* 병합하면 완전성이나 검색성이 좋아진다
|
||
|
||
규칙:
|
||
|
||
* 손실 없이 병합하라
|
||
* 유용한 정보를 모두 보존하라
|
||
* 중복을 만들지 마라
|
||
* 둘의 값이 서로 다르면 `memory_at`이 더 늦은 기억의 값을 유지하라.
|
||
더 오래된 기억이 더 새로운 값을 덮어쓰게 해서는 절대 안 된다
|
||
|
||
### 같은 발생 vs. 다른 발생
|
||
|
||
새 기억이 이미 기록된 같은 실제 발생이나 오래 유지되는 사실에 세부 사항을 더할
|
||
때만 **UPDATE**를 사용하라.
|
||
|
||
같은 사람이 다른 시점에 같은 종류의 활동/사건을 다시 한 경우(또 다른 여행, 경기,
|
||
부상, 구매, 건강 이상 등)에는 그 발생을 별도의 **ADD**로 유지하라. 그 발생 고유의
|
||
날짜와 세부 사항을 보존하라. 기존 기억이 분명히 같은 구체적 시리즈의 또 다른
|
||
발생이라면 `related: [{{"memory_id":"...","relation":"same_series"}}]`로 연결하라.
|
||
|
||
날짜가 다른 별개의 발생들을 하나의 긴 횟수/타임라인 기억으로 합치지 마라.
|
||
타임라인은 검색 계층이 연결된 L2 기억들로부터 만든다.
|
||
|
||
```json
|
||
{{
|
||
"op": "UPDATE",
|
||
"memory_id": "...",
|
||
"content": "...",
|
||
"owner": "user",
|
||
"tags": ["..."]
|
||
}}
|
||
```
|
||
|
||
---
|
||
|
||
## SUPERSEDE
|
||
|
||
다음을 모두 만족할 때만 사용한다.
|
||
|
||
* 새 기억이 기존 기억과 모순된다
|
||
* 둘이 동시에 참일 수 없다
|
||
* 이전 기억이 이제는 현재 상태에 대해 틀린 답을 주게 된다
|
||
* 새 기억의 `memory_at`이 기존 기억의 `memory_at`과 같거나 그보다 늦다
|
||
|
||
예시:
|
||
|
||
* 사는 도시가 바뀜
|
||
* 고용주가 바뀜
|
||
* 연애/혼인 상태가 바뀜
|
||
|
||
다음에는 사용하지 마라.
|
||
|
||
* 세부 사항 추가
|
||
* 양립 가능한 선호
|
||
* 표현 차이
|
||
* 의미적으로 동등함
|
||
* `memory_at`이 기존 기억보다 이른 새 기억 — 그것은 더 오래된 상태이므로
|
||
더 새로운 것을 절대 대체해서는 안 된다("시간 순서" 참조)
|
||
|
||
규칙:
|
||
|
||
* content에는 새 상태만 담아야 한다
|
||
* 바뀌지 않은 정보를 복사하지 마라
|
||
* 시스템이 이전 노드를 유지하고 자동으로 진화 체인에 연결하므로, 이력은 처리할 필요가 없다
|
||
|
||
```json
|
||
{{
|
||
"op": "SUPERSEDE",
|
||
"memory_id": "...",
|
||
"content": "...",
|
||
"supersede_reason": "...",
|
||
"owner": "user",
|
||
"tags": ["..."]
|
||
}}
|
||
```
|
||
|
||
### 여러 기존 기억을 하나의 체인으로 접기
|
||
|
||
둘 이상의 기존 기억이 같은 차원을 추적하고 있고, 새 기억이 그 차원의 최신
|
||
변화라면 — 즉 새 기억의 `memory_at`이 접히는 모든 기억의 `memory_at`보다 늦다면 —
|
||
그것들을 하나의 진화 체인으로 접을 수 있다. 단일 `memory_id` 대신 `memory_ids`
|
||
(가장 오래된 것 → 가장 새로운 것 순서의 목록)를 사용하라.
|
||
|
||
```json
|
||
{{
|
||
"op": "SUPERSEDE",
|
||
"memory_ids": ["<oldest_id>", "<newer_id>"],
|
||
"content": "<가장 새로운 상태만>",
|
||
"supersede_reason": "...",
|
||
"owner": "user",
|
||
"tags": ["..."]
|
||
}}
|
||
```
|
||
|
||
나열된 기억들은 오래된 것 → 새로운 것 순으로 체인이 되고, 새 기억이 체인의
|
||
머리(head)가 된다. `content`는 여전히 가장 새로운 상태만 기술한다 — 이전 기억들의
|
||
텍스트를 절대 복사해 넣지 마라. 그것들은 체인 이력에서 계속 검색할 수 있다.
|
||
|
||
---
|
||
|
||
# 규칙
|
||
|
||
* 의미 있는 정보를 모두 보존하라.
|
||
* 완전히 동일한 중복은 건너뛰어라.
|
||
* 관련된 조각은 병합하는 쪽을 우선하라.
|
||
* 날짜가 다른 별개의 발생은 별도의 ADD로 유지하라. `same_series`는 같은 주체이고
|
||
같은 구체적 활동/사건 유형일 때만 사용하라.
|
||
* 각 기억은 하나의 일관된 주제에 집중하게 하라.
|
||
* 아래 "시간 순서"를 따르라.
|
||
* **서로 다른 주체 사이에서는 절대 병합하거나 대체하지 마라.** 주체는 content가
|
||
다루는 대상이다: "사용자는 ..." vs "John은 ..." vs "Mary는 ...". 다른 사람에 대한
|
||
기억은 문장이 그 외에는 똑같더라도 독립된 사실이다 — "사용자는 커피를 좋아한다"와
|
||
"John은 커피를 좋아한다"는 별개의 두 기억이며, 절대 서로의 UPDATE나 SUPERSEDE가
|
||
아니다.
|
||
* `owner` (`user`/`agent`): 쓰고 있는 기억에서 그대로 복사하라. 서로 다른 owner 사이에서는 절대 병합하거나 대체하지 마라 — 같은 주제라도 사용자의 사실과 어시스턴트의 사실은 별개다.
|
||
* SUPERSEDE는 하나의 기억(`memory_id`)을 대상으로 하거나, 같은 차원의 여러 기억을 하나의 체인으로 접는다(`memory_ids`, 오래된 것→새로운 것). UPDATE는 정확히 하나의 기억만 대상으로 한다.
|
||
* 같은 기존 기억을 둘 이상의 op에서 건드리지 마라.
|
||
* 출력 언어는 입력 기억의 언어와 일치시켜라.
|
||
|
||
---
|
||
|
||
# 시간 순서
|
||
|
||
새 기억이든 기존 기억이든 모든 기억에는 `memory_at`, 즉 그 일이 일어난 시점이 있다.
|
||
기억이 항상 일어난 순서대로 쓰이는 것은 아니다 — 이력이 늦게 가져와질 수도 있고,
|
||
더 오래된 이력이 아직 처리되는 중에 기억이 추가될 수도 있다. 따라서 새 기억이
|
||
기존 기억보다 더 오래된 것일 수 있다.
|
||
|
||
* 어느 기억이 더 새로운지는 오직 `memory_at`으로만 판단하라. "새 기억"은 "지금
|
||
쓰이고 있는 것"을 뜻하지 "더 최근의 것"을 뜻하지 않는다.
|
||
* 더 오래된 기억이 더 새로운 기억을 대체해서는 절대 안 된다: 더 새로운 기억을
|
||
SUPERSEDE하지 말고, 더 새로운 값을 더 오래된 값으로 덮어쓰는 UPDATE도 하지 마라.
|
||
* 새 기억이 충돌하는 기존 기억보다 오래된 것이라면, 기존 기억을 현재 상태로 유지하라.
|
||
새 기억은 과거 상태로서 그 시점과 함께 과거형으로 써서 ADD하거나(예: "2023년에
|
||
사용자는 상하이에 살았다"), 추가되는 정보가 없으면 아무것도 출력하지 마라.
|
||
* `memory_at: null`은 시간을 알 수 없다는 뜻이다. 알 수 없음은 더 새로운 것이 아니다:
|
||
`memory_at`이 알려진 기억을 `memory_at`이 null인 기억으로 절대 SUPERSEDE하지 마라.
|
||
* 시간 순서를 지어내지 마라.
|
||
|
||
---
|
||
|
||
# 선택 사항: `related`
|
||
|
||
어떤 op에든 `related` 배열을 추가하여, 쓰고 있는 기억이 기존 기억과 어떻게
|
||
관련되는지 기록할 수 있다. 이것은 저장되는 내용을 바꾸지 않으며 — 연결만 기록한다.
|
||
|
||
```json
|
||
{{
|
||
"op": "ADD",
|
||
"content": "...",
|
||
"owner": "user",
|
||
"tags": ["..."],
|
||
"related": [{{"memory_id": "<existing_id>", "relation": "causes"}}]
|
||
}}
|
||
```
|
||
|
||
허용되는 관계는 다음 네 가지뿐이다.
|
||
|
||
* `causes` — 두 기억이 원인/결과 관계에 있다
|
||
(예: "매일 달리기를 시작했다" → "무릎이 아프기 시작했다")
|
||
* `elaborates` — 기존 기억에 세부 사항을 더하거나, 그 이후의 전개다
|
||
* `contrasts` — 서로 긴장 관계에 있지만 둘 다 여전히 참이다(한쪽이 다른 쪽을
|
||
거짓으로 만든다면 그것은 `contrasts` 연결이 아니라 SUPERSEDE다)
|
||
* `same_series` — 같은 주체의 같은 구체적 활동 또는 사건 유형에 대한, 날짜가 다른
|
||
별개의 발생(예: 서로 다른 두 번의 스키 여행)
|
||
|
||
규칙:
|
||
|
||
* "Existing memories"에 나타난 기억에만 연결하라 — id를 절대 지어내지 마라.
|
||
* 관계가 **명시적이고 구체적일 때만** 연결을 출력하라. 두 기억이 단지 같은 넓은
|
||
주제를 다룬다는 것은 관계가 아니다 — `related`를 빼라.
|
||
* `same_series`에는 같은 주체와 구체적으로 반복된 행동/사건이 필요하다.
|
||
health, travel, sport, food, work 같은 넓은 태그를 공유하는 것만으로는 부족하다.
|
||
* 연결할 것이 없으면 필드를 완전히 생략하라. 연결이 0개인 것이 보통이다.
|
||
* `related`를 UPDATE / SUPERSEDE의 대용으로 절대 쓰지 마라.
|
||
|
||
---
|
||
{few_shot_section}
|
||
###################
|
||
입력 데이터
|
||
###################
|
||
|
||
Current date: {current_date}
|
||
|
||
## Existing memories
|
||
|
||
{existing_memories}
|
||
|
||
## New memories
|
||
|
||
{new_memories}
|
||
|
||
---
|
||
|
||
# 출력
|
||
|
||
```json 펜스 코드 블록 안에 JSON 배열만 반환하라.
|
||
|
||
변경이 필요 없으면:
|
||
|
||
```json
|
||
[]
|
||
```
|
||
|
||
이제 JSON 배열을 출력하라."""
|
||
|
||
RECONCILE_STRUCTURE_METADATA_PROMPT = """
|
||
|
||
추가 구조 출력 계약:
|
||
|
||
모든 ADD, UPDATE, SUPERSEDE 결과에 대해 `topic_paths`, `event_facet`,
|
||
`topic_assignments`를 보존하거나 다듬어라. `event_facet`에는 `subject`,
|
||
`participants`, `action`, `objects`, `locations`, `state`만 들어간다.
|
||
`topic_assignments`에는 주체별 `{subject, role, path}` 항목이 들어간다.
|
||
각 경로 세그먼트는 `{name, type}`이며 type은 `topic|facet|series|episode`다.
|
||
타입은 `topic -> facet -> series -> episode` 순서를 따른다. 단계는 건너뛸 수
|
||
있지만 타입이 반복되거나 거꾸로 갈 수는 없다.
|
||
Series는 하나의 구체적 활동/상태 계열이 반복해서 일어난 경우에만, Episode는
|
||
경계가 있는 단 한 번의 실제 발생에만 사용하라. 이 타입들을 깊이, 공유된 넓은 태그,
|
||
관련 없는 동시 등장으로부터 절대 추론하지 마라. Existing memory에 의미적으로 동등한
|
||
주체별 경로가 이미 있다면, 동의어 분기를 만들지 말고 그 노드 이름과 타입을 그대로
|
||
재사용하라. 정확한 재사용을 요청하려면
|
||
`structure_references: [{"memory_id":"<existing_id>", "subject":"...",
|
||
"through":"topic|facet|series|episode"}]`를 출력하라. 참조된 ID는 Existing
|
||
memories에 나타나야 한다. `through`는 기존 경로의 접두부를 지정된 구조 타입까지
|
||
복사한다는 뜻이며, 명시적으로 작성한 더 깊은 접미부는 새것으로 남는다. 주체, 구체적
|
||
활동/상태 계열, 또는 Episode 경계가 다르면 재사용은 금지된다. 이 필드들은 기억을
|
||
기술하는 것이지, "언제", "몇 번", "누가" 같은 미래의 질의 형식을 기술하는 것이 절대
|
||
아니다.
|
||
|
||
Series는 reconcile 시점의 관계다: 새 기억과 적어도 하나의 독립적인 Existing memory가
|
||
같은 구체적 활동/상태 계열의 서로 다른 발생일 때만 확립하라. 현재 쓰기에서 나온 여러
|
||
명제는 반복을 증명하지 않는다.
|
||
"""
|
||
|
||
RECONCILE_FEW_SHOT = """
|
||
###################
|
||
예시
|
||
###################
|
||
|
||
## 예시 1 — 새 기억 하나: 일부는 기존 체인과 충돌하고, 일부는 무관하다
|
||
|
||
Existing memories:
|
||
[
|
||
{"memory_id": "m1", "content": "사용자는 Stripe에서 프로덕트 매니저로 일한다.", "owner": "user", "history_versions": [{"content": "사용자는 핀테크 스타트업에서 프로덕트 매니저로 일한다."}]}
|
||
]
|
||
New memories:
|
||
[
|
||
{"content": "사용자는 Stripe를 떠나 지금은 Notion에서 프로덕트 리드로 일하며, 최근 주말에 도예를 시작했다.", "owner": "user"}
|
||
]
|
||
|
||
출력:
|
||
```json
|
||
[
|
||
{"op": "SUPERSEDE", "memory_id": "m1", "content": "사용자는 Notion에서 프로덕트 리드로 일한다.", "supersede_reason": "고용주가 Stripe에서 Notion으로 바뀜", "owner": "user", "tags": ["work"]},
|
||
{"op": "ADD", "content": "사용자는 최근 주말에 도예를 시작했다.", "owner": "user", "tags": ["hobby"]}
|
||
]
|
||
```
|
||
새 기억 하나가 두 가지를 담고 있다: 이직(이미 이력이 있는 기존 고용 체인 m1과 같은 차원 → SUPERSEDE, 새 체인 머리가 됨)과 무관한 세부 사항(도예 → ADD). 하나의 기억을 부분별로 알맞은 op로 나눠라 — 새 머리는 고용 상태만 기술하며, 도예는 절대 넣지 않는다.
|
||
|
||
## 예시 2 — 연결되지 않은 기존 기억 두 개 + 새 변화를 하나의 3노드 체인으로 접기
|
||
|
||
Existing memories:
|
||
[
|
||
{"memory_id": "a1", "content": "사용자는 기타 연주를 배우고 있다.", "owner": "user", "memory_at": "2024-02-10 09:00"},
|
||
{"memory_id": "a2", "content": "사용자는 이제 기타로 몇 곡을 처음부터 끝까지 연주할 수 있다.", "owner": "user", "memory_at": "2024-09-05 09:00"}
|
||
]
|
||
New memories:
|
||
[
|
||
{"content": "사용자는 이제 동네 오픈마이크 무대에서 기타 공연을 한다.", "owner": "user"}
|
||
]
|
||
|
||
출력:
|
||
```json
|
||
[
|
||
{"op": "SUPERSEDE", "memory_ids": ["a1", "a2"], "content": "사용자는 동네 오픈마이크 무대에서 기타 공연을 한다.", "supersede_reason": "기타 실력이 발전함: 배우는 중 → 곡 연주 → 라이브 공연", "owner": "user", "tags": ["music", "hobby"]}
|
||
]
|
||
```
|
||
a1과 a2는 한 번도 연결된 적이 없지만 같은 차원(기타 실력의 발전)을 추적하며, 새 기억이 가장 최근 단계다. `memory_ids`를 오래된 것→새로운 것 순으로 정렬해 접어라. 그러면 3노드 체인(a1 ← a2 ← 새 머리)이 된다. 머리의 `content`는 현재 상태만 기술한다 — "배우는 중"이나 "몇 곡"을 다시 언급하지 않는다.
|
||
|
||
## 예시 3 — 새 머리는 이전 기억의 내용을 복사하면 안 된다
|
||
|
||
Existing memories:
|
||
[
|
||
{"memory_id": "c1", "content": "사용자는 도쿄 시부야의 원룸을 월 ¥180,000에 임차해 살고 있으며, 역까지 도보 5분인 점을 아주 좋아하지만 밤에 시끄럽고 주차 공간이 없다고 불평한다.", "owner": "user"}
|
||
]
|
||
New memories:
|
||
[
|
||
{"content": "사용자는 방금 정원과 차 두 대용 차고가 있는 요코하마 교외의 방 세 개짜리 단독주택을 사서 이사했다.", "owner": "user"}
|
||
]
|
||
|
||
출력:
|
||
```json
|
||
[
|
||
{"op": "SUPERSEDE", "memory_id": "c1", "content": "사용자는 정원과 차 두 대용 차고가 있는 요코하마 교외의 방 세 개짜리 단독주택을 사서 이사했다.", "supersede_reason": "거주지가 시부야 임차 원룸에서 요코하마 자가 주택으로 바뀜", "owner": "user", "tags": ["housing"]}
|
||
]
|
||
```
|
||
새 기억은 c1을 SUPERSEDE하고 체인 머리가 된다. 머리의 `content`는 새 요코하마 주택만 기술한다. 이전 원룸의 특징적인 세부 사항(시부야, ¥180,000 월세, 역까지의 도보 거리, 소음/주차 불만)을 새 노드로 끌고 오지 마라 — 그것들은 분명히 새 집이 아니라 이전 거주지에 속한다. 그것들은 체인 이력에 남아 계속 검색할 수 있다. 복사하면 머리가 자기모순이 된다(시부야에서 임차하면서 동시에 요코하마에 집을 소유).
|
||
|
||
## 예시 4 — 주체가 다르면 표현이 아무리 비슷해도 절대 병합하지 않는다
|
||
|
||
Existing memories:
|
||
[
|
||
{"memory_id": "d1", "content": "사용자는 매일 아침 블랙커피를 마신다.", "owner": "user"}
|
||
]
|
||
New memories:
|
||
[
|
||
{"content": "John은 매일 아침 블랙커피를 마신다.", "owner": "user"},
|
||
{"content": "사용자는 이제 아침과 점심 식사 후, 하루 두 번 블랙커피를 마신다.", "owner": "user"}
|
||
]
|
||
|
||
출력:
|
||
```json
|
||
[
|
||
{"op": "ADD", "content": "John은 매일 아침 블랙커피를 마신다.", "owner": "user", "tags": ["food"]},
|
||
{"op": "UPDATE", "memory_id": "d1", "content": "사용자는 아침과 점심 식사 후, 하루 두 번 블랙커피를 마신다.", "owner": "user", "tags": ["food"]}
|
||
]
|
||
```
|
||
두 새 기억은 표현이 거의 같지만 주체가 다르다. "John은 ..."은 다른 사람에 관한 것이므로 단순 ADD다 — d1이 사용자에 대해 거의 같은 말을 하더라도 절대 d1의 UPDATE나 SUPERSEDE가 아니다. 주체가 마찬가지로 "사용자"인 기억만 d1과 병합할 수 있다.
|
||
|
||
## 예시 5 — 반복되는 사건: 횟수를 올리고 타임라인을 유지한다 (ADD가 아니라 UPDATE)
|
||
|
||
Existing memories:
|
||
[
|
||
{"memory_id": "i1", "content": "John은 지난 시즌에 발목을 다쳐 휴식과 물리치료가 필요했다.", "owner": "user", "memory_at": "2023-05-21 19:48"}
|
||
]
|
||
New memories:
|
||
[
|
||
{"content": "John은 최근 경기 중에 발목을 또 삐었다.", "owner": "user", "memory_at": "2023-10-13 13:50"}
|
||
]
|
||
|
||
출력:
|
||
```json
|
||
[
|
||
{"op": "UPDATE", "memory_id": "i1", "content": "John은 발목을 두 번 다쳤다: 첫 번째는 지난 시즌(2022–2023 시즌경)으로 휴식과 물리치료가 필요했고, 두 번째는 2023-10-13경 경기 중이었다.", "owner": "user", "tags": ["health", "basketball", "injury"]}
|
||
]
|
||
```
|
||
같은 유형의 사건이 다시 일어남 → 이전 기억을 UPDATE한다. **횟수**를 명시하고, 알려진 경우 시점과 함께 발생을 나열하라. 두 번째 "John은 발목을 다쳤다" 사실을 ADD하지 말고, SUPERSEDE도 하지 마라(첫 번째 부상이 사실이 아니게 된 것은 아니다).
|
||
"""
|
||
|
||
|
||
# ================================================================
|
||
# Working / coding memory (coding/)
|
||
# ================================================================
|
||
|
||
CODING_EXTRACT_PROMPT = """\
|
||
너는 작업 실행 세션 구간에서 오래 유지되는 WORKING 기억을 추출한다.
|
||
"working" 세션이란 사용자가 도구를 통해 컴퓨터/시스템을 직접 조작하는 모든 실무
|
||
작업을 말한다 — 소프트웨어/코드 작업(작성, 디버깅, 리팩터링, 빌드, 배포,
|
||
API/라이브러리 사용)과 시스템·운영 작업(shell/PowerShell 명령 실행, 디스크/스토리지
|
||
분석 또는 정리, 파일·폴더 작업, 소프트웨어 설치/설정, 환경/장치 설정, 하드웨어/디스크
|
||
상태 점검, 백업, 스케줄링/자동화, 데이터 처리)을 모두 포함한다.
|
||
|
||
같은 구간에서 두 개의 계층을 만든다.
|
||
|
||
EXPERIENCES (절차적, "어떻게 하는가") — 재사용 가능한 방법으로, 아래의
|
||
가치 기준(VALUE BAR)을 통과할 때만 추출한다. (task, search_keys, solution,
|
||
boundary_envs) 묶음이다.
|
||
|
||
FACT (일화적, "이번에 무슨 일이 있었는가") — 이 세션에 대한 정확히 하나의
|
||
기록: 사용자가 무엇을 원했고, 어떻게 진행했으며, 결과가 어떻게 되었는가. 가치
|
||
기준은 없지만, 절대 하나를 넘지 않는다.
|
||
|
||
두 계층은 서로 다른 질문에 답하며 따로 저장된다. 일반화되는 방법은 EXPERIENCES에
|
||
넣고, 이 특정 세션의 이야기 — 이 시스템 / 이 프로젝트 / 지금에만 해당하는 것 — 는
|
||
FACT에 넣는다.
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
가치 기준 (EXPERIENCES 전용) — 가볍게 넘기지 마라.
|
||
═══════════════════════════════════════════════════════════════════
|
||
기억은 다음을 모두 만족해야 한다.
|
||
(a) 같은 사용자에게 다시 발생할 가능성이 높은 문제를 해결한다
|
||
(b) 해결책이 사소하지 않다: 여러 정보/단계를 결합하거나, 결정 근거를 담고
|
||
있거나, 어렵게 얻은 우회책을 담고 있거나, 운영 작업을 위한 재사용 가능한
|
||
절차/레시피다(예: 디스크 사용량을 분석하고 정리하는 방법, 파일을 일괄 정리하는
|
||
방법, 하드웨어 상태를 점검하는 방법, 예약 작업을 설정하는 방법)
|
||
(c) 미래의 agent / 사용자가 다시 알아내기보다 찾아보는 쪽을 택할 것이다
|
||
|
||
중요 — 분석 / 진단 / 조사 작업도 해당한다:
|
||
작업이 무언가를 변경하거나 "고쳐야만" 기억할 가치가 있는 것은 아니다.
|
||
읽기 전용 / 분석 / 문제 해결 작업도 그 절차나 결론이 재사용 가능하면
|
||
가치가 있다. 다음을 명시적으로 추출하라.
|
||
- 분석 / 진단 레시피: 재사용 가능한 절차 + 그것이 밝혀낸 것
|
||
(예: "C 드라이브 공간을 차지하는 폴더를 분석하는 방법" — 스캔 명령, 마주친
|
||
함정, 그리고 발견 사항). "보고서만 나왔고 변경은 없었다"는 건너뛸 이유가
|
||
아니다 — 재사용 가능한 방법 자체가 기억이다.
|
||
- 조사 결과, 부정적 / 찾지 못한 결과 포함
|
||
(예: "rg / fs_search / SQL 인덱스로 C:와 D: 전체에서 폴더 X를 찾았으나 —
|
||
어디에도 없었다"). 기록된 막다른 길은 다음 agent가 같은 헛수고를 반복하지 않게
|
||
해 주므로 기준을 통과한다.
|
||
"어시스턴트가 명령을 다시 만들어 낼 수 있다"거나 "실제로 수정된 것이 없다"는
|
||
이유만으로 기억을 거부하지 마라. 절차나 결론이 앞으로의 수고를 덜어 준다면 남겨라.
|
||
|
||
다음은 추출하지 마라.
|
||
- 재사용 가능한 절차가 없는 정말 사소한 단일 단계 작업("파일 하나 이름 바꾸기",
|
||
"git status", "ls 한 번 실행")
|
||
- 이미 해결되었고 옮겨 쓸 교훈이 없는 일회성 문제
|
||
- 이미 저장소/시스템에 있는 정보(package.json을 중복하지 마라)
|
||
- 재사용 가능한 패턴을 담지 않은 PR별 / 세션별 일회성 작업
|
||
- 누락된 정보를 사용자에게 묻는 행위. 매개변수를 요청하거나, 모르는 것을
|
||
나열하거나, 필수 필드를 이끌어 내는 것은 대화 행동이지 방법이 아니다: 모든
|
||
agent는 이미 질문하는 법을 알고 있으므로 나중에 찾아볼 것이 없다. task나
|
||
search_keys가 "X를 묻는 방법", "누락된 Y 수집하기", "필수 필드 이끌어 내기",
|
||
"시작 전에 확인할 것" 같은 모양인 experience는 절대 출력하지 마라. 이것은
|
||
EXPERIENCES에서만 제외된다는 점에 유의하라 — 세션이 사용자를 기다리며 끝났다는
|
||
것은 실제 상태이며 FACT에 속한다.
|
||
|
||
구간이 사용자를 기다리며 멈춰서 실질적인 진전이 없었다면 — 어시스턴트가 매개변수만
|
||
물었거나, 실제 작업을 하기 전에 막혔다면 — "experiences": []를 출력하고 FACT만
|
||
써라. 아직 배운 것이 없으므로 저장할 방법도 없다. 이것은 보기보다 중요하다:
|
||
멈춘 상태에서 증류한 experience는 검색 시 정당한 기술처럼 읽히고, 같은 작업의 이후
|
||
구간이 그것과 매칭되어 진행하는 대신 사용자에게 다시 묻게 되므로, 그 멈춤이 세션의
|
||
나머지 동안 스스로 재생산된다. 실질성은 턴이 매끄러운 텍스트를 만들었는지가 아니라
|
||
도구가 실제로 무엇을 했는지로 판단하라.
|
||
|
||
경험칙: 사용자(또는 같은 종류의 작업을 하는 미래의 agent)가 다시 알아내기보다 이것이
|
||
적혀 있기를 원할 것이라면, 남겨라. 여러 단계의 운영 절차(코드뿐 아니라 시스템/운영
|
||
또는 읽기 전용 분석/검색 절차라도)는 보통 해당한다. 어떤 사용자라도 `--help`로
|
||
30초 안에 찾을 수 있는 단일 명령이면서 기억할 만한 결정/함정이 없는 경우에만 버려라.
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
EXPERIENCE 필드
|
||
═══════════════════════════════════════════════════════════════════
|
||
|
||
task — 질의 형태의 이름. 사용자가 나중에 어떻게 물을지로 표현하라.
|
||
"publish_release_internal"이 아니라
|
||
"xx 플랫폼에 릴리스 배포하기"
|
||
그 자체로 완결되게("이것", "그 파일", "우리" 금지).
|
||
|
||
search_keys — `task`만으로는 매칭되지 않을 solution의 중요한 측면을 담은
|
||
0~5개의 대체 표현. 미래의 사용자가 물어볼 질문이나 주제로
|
||
표현하라. task와 달라야 한다.
|
||
task가 이미 모든 것을 포괄하면 생략하라.
|
||
|
||
solution — 사용자가 실제로 필요로 하는, 완전하고 그 자체로 완결된 내용:
|
||
명령, 자격 증명의 위치(자격 증명 값은 안 됨),
|
||
파일 경로, 순서가 있는 단계, 함정, 결정 근거.
|
||
여러 턴에 걸친 처음부터 끝까지의 작업이 하나의 일관된 작업을
|
||
이루면 하나의 solution으로 모아라. 사용자가 서로 무관한 작업을
|
||
했을 때만 여러 기억으로 나눠라.
|
||
|
||
boundary_envs — solution이 의존하는 구체적인 고정 조건, 여러 줄의 "key: value":
|
||
런타임 / SDK / 라이브러리 + 버전
|
||
API 엔드포인트 또는 메서드 시그니처
|
||
외부 서비스 / 미들웨어 버전
|
||
환경 변수 이름(값은 안 됨)
|
||
설정 파일 경로
|
||
호스트 / 클러스터 / 리전 식별자
|
||
solution이 환경과 무관하면 ""(빈 값)로 두어라.
|
||
목적: 기반 SDK/미들웨어가 업그레이드될 때, 미래의 소비자가 이것을
|
||
보고 solution이 여전히 적용되는지 판단할 수 있게 한다.
|
||
|
||
confidence — 0.0 ~ 1.0
|
||
1.0 사용자가 이것을 규칙 / 지시로 명시했다
|
||
0.7 도구 동작으로부터 충분한 근거를 가지고 추론했다
|
||
0.5 약하게 추론했다; 관찰 한 번; 불확실하다
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
FACT 필드 — 구간당 정확히 하나, 가치 기준 없음
|
||
═══════════════════════════════════════════════════════════════════
|
||
|
||
구간 전체에 대해 하나의 fact를 쓴다 — 이 세션의 복구 가능한 상태 기록이며,
|
||
다듬어진 회고가 아니고, 별개의 발견 사항 목록은 더더욱 아니다. 미래의 agent가
|
||
제안, 추론, 부분적인 검색을 검증된 것으로 오인하지 않고 이것으로부터 이어서 진행할
|
||
수 있어야 한다.
|
||
|
||
subject — 이 세션이 누구 또는 무엇에 관한 것인가: 작업이 수행된 대상 또는
|
||
작업의 수혜자인 단일 개체. 짧은 명사구 하나, 문장 금지, 동사 금지.
|
||
세션이 사용한 도구가 아니라 세션이 *다루는* 가장 구체적인 개체를
|
||
골라라:
|
||
세션이 이름 있는 한 사람을 위한 것이면 그 사람의 이름
|
||
("Dylan", "Matthew")
|
||
작업이 코드를 대상으로 하면 파일 / 모듈 / 심볼
|
||
("auth/middleware.ts", "parse_plan_text")
|
||
운영 작업이면 서비스 / 호스트 / 장치 / 드라이브
|
||
("D:\\WXWork", "redis-cache-prod", "ci.yml")
|
||
이것은 서술이 아니라 엄격한 키다: 서로 다른 사람이나 서로 다른 파일에
|
||
관한 두 세션은 반드시 다른 subject를 가져야 하고, 같은 대상에 관한 두
|
||
세션은 반드시 같은 문자열을, 철자까지 똑같이 가져야 한다.
|
||
그것에 대한 설명보다 트레이스에 나타난 정확한 식별자를 우선하라.
|
||
구간에 정말로 단일 subject가 없다면 ""를 출력하라.
|
||
|
||
중요한 이유: subject는 이후 세션이 이 세션을 "같은 작업의 계속"으로
|
||
보고 덮어써도 되는지를 결정하는 관문이다. 잘못 쓰면 한 사람의 상태가
|
||
조용히 다른 사람의 상태를 대체한다.
|
||
|
||
statement — 이 세션의 간결한 기록. 두 부분으로 되어 있으며, 잃어버리기 쉬운 것은
|
||
두 번째 부분이다.
|
||
작업 범위의 주어진 값과 구체적 제약을 결과에 대한 일반적인 평가로
|
||
바꾸지 말고 보존하라. 각 규칙은 한 번만 말하라: 반복된 지시문은 기억이
|
||
담아야 할 데이터와 자리를 다툰다.
|
||
|
||
PART 1 — 무슨 일이 있었는가, 라틴 문자 약 300자(CJK/한글 약 150자).
|
||
일반적인 "버그 수정" / "파일 찾기"가 아니라 구체적인 목표와 대상으로
|
||
시작하라("D:\\WXWork가 차지한 디스크 공간 회수", "new_file_search
|
||
폴더 위치 찾기"). 그다음 대략 무엇을 살펴보고, 시도하고, 변경했는지.
|
||
그다음 어떻게 끝났는지를, 다음 중 정확히 하나의 의미로 써라:
|
||
완료, 부분 완료, 실패, 차단됨, 또는
|
||
아직 검증 대기 중. 부정적 결과와 막다른 길도 과장하지 말고 포함하라.
|
||
|
||
PART 2 — 작업 범위의 주어진 값(TASK-SCOPED GIVENS): 작업 전체가 기반으로
|
||
삼는 모든 것. 이 구간에서 확립한 것이 아니더라도, 너무 기초적이라 말할
|
||
필요가 없다고 느껴지더라도 포함하라. 바인딩(각 이름/심볼/라벨이 무엇을
|
||
가리키는지), 작업이 도출하는 것이 아니라 제공하는 고정값, 채택된
|
||
관례(인덱스 시작값, 단위, 시간대, 기본값), 그리고 이 구간 자체의 결과와
|
||
그 값("값을 계산했다"가 아니라 "x = 7").
|
||
|
||
PART 2는 데이터로, 한 줄에 한 항목씩, 주어진 그대로의 값으로 써라:
|
||
retry budget = 5; timeout 30s; index starts at 0
|
||
step 3 input: n=64, tolerance 1e-6
|
||
slot B: type=private, cost 688.7-721.3 per unit
|
||
한 줄은 거의 전부 값이어야 한다. 트레이스의 표기 — 심볼, 단위, 숫자,
|
||
철자 — 를 유지하고 그 문장은 버려라: 주어진 값 자체가 아니라 그것을
|
||
알리기만 하는 문구("나는 ~를 선호한다", "~와 ~ 사이 어딘가여야 한다")를
|
||
제거하라. 그런 문구는 항목마다 한 번씩 반복되므로, 15개 항목 목록이면
|
||
15번 비용을 치른다.
|
||
이것들은 모든 fact에 실어라. "위와 같음"이나 "이전 세션과 같음"이라고
|
||
절대 쓰지 마라 — 검색은 유사도로 소수의 fact만 돌려주므로 참조 대상이
|
||
그 안에 없을 수 있다.
|
||
|
||
전체 1200자 이내를 목표로 하라. PART 2가 들어가지 않으면 범위가 아니라
|
||
표기를 고쳐라: statement는 검색 벡터이자 이후 구간이 읽을 수 있는
|
||
유일한 필드이므로(details는 임베딩되지도 주입되지도 않는다), 여기서
|
||
잘린 것은 사라진다. 주어진 값을 둘러싼 표현을 압축하되, 주어진 값 자체는
|
||
절대 압축하지 마라.
|
||
|
||
시험: 다음 구간이 원래 자료를 볼 수 없다면, 이 기록만으로 이어 갈 수
|
||
있는가? "예"만 통과다. 빠진 바인딩은 요란하게 실패하지 않는다 — 이름은
|
||
여전히 의미 있어 보이므로 일반적인 의미로 읽히고, 그 뒤에 나오는 답은
|
||
매끄럽지만 다른 질문에 대한 것이 된다.
|
||
|
||
언어 — 세션 트레이스와 같은 언어: statement는 검색 벡터이고 질의는
|
||
사용자의 작업 언어로 들어오므로, 다른 언어로 쓰면 매 조회마다 언어 간
|
||
불이익을 치른다.
|
||
|
||
증거 충실성 — 실제로 실행된 동작과 트레이스에 명시적으로 있는 증거만
|
||
기록하라. 중요한 명령, 플래그, 인자, 오류, 숫자를 그대로 보존하라.
|
||
관례적인 플래그로 명령을 절대 보충하지 말고, 조언이나 계획을 절대
|
||
실행된 동작으로 바꾸지 마라:
|
||
"ran Get-ChildItem -Force" → "ran Get-ChildItem"
|
||
(트레이스에 `-Force`가 없었다; 지어내지 마라)
|
||
모든 부정적 발견은 실제로 검색한 범위로 한정하고, 그것을 무효화할 수
|
||
있는 명시적인 범위 제한을 모두 열거하라. 특히 권한 실패와 깊이 제한:
|
||
적용된 루트, 필터, 깊이를 유지하라("C:\\와
|
||
D:\\에서 깊이 3까지 찾지 못함; 일부 디렉터리는 Access
|
||
denied로 다루지 못함"). 절대 "존재하지 않는다"로 넓히지 마라. 기록을
|
||
보기 좋게 하려고 제한 하나라도 빼지 마라.
|
||
부분적인 검색을 전역적인 부재로 절대 격상하지 마라.
|
||
차단된 시도는 그 차단 요인을 유지한다.
|
||
시도함, 관찰함, 검증함, 추론함, 미해결 사이의 구별을 보존하라 —
|
||
라벨이 아니라 자연스럽게 표현하라("…를 시도했으나 Access denied로
|
||
막혔다", "…로 의심되나 아직 검증되지 않았다"). 도구 출력이나 명시적
|
||
확인 없이 수정이 성공했다거나, 원인이 확립되었다거나, 경로가 존재하지
|
||
않는다고 절대 주장하지 마라.
|
||
|
||
인사나 순수한 잡담은 기록하지 마라 — "fact": null을 받는 유일한
|
||
경우다. 요구 사항만 말했거나 작업 전에 멈춘 구간도 여전히 무언가를
|
||
확립했다.
|
||
|
||
details — 세션의 구체적인 산출물을 그대로 복사한 것. `statement`는 요약이며,
|
||
요약은 필연적으로 개별 값을 빠뜨린다. 이후의 조회는 거의 항상 요지가
|
||
아니라 특정한 값 하나를 원한다.
|
||
그러니 이 세션이 만들어 낸 지정 가능한 결과가 무엇이든, 원래의 형식과
|
||
구조 그대로 여기에 재현하라:
|
||
계획 / 일정 / 여행 일정 → 모든 줄, 모든 날, 모든 칸, 모든 가격을
|
||
쓰인 그대로 유지
|
||
설정 또는 코드 블록 → 블록을 유지
|
||
명령의 중요한 출력 → 발견 사항을 담은 줄(크기, 개수, 버전, 오류
|
||
텍스트)을 유지
|
||
구조화된 결과(표, 레코드 목록) → 행을 유지
|
||
원래의 레이아웃 — 줄바꿈, `Day 1:` / key: value 접두어, 단위, 통화 기호,
|
||
id — 를 보존하라. 다시 요약하지 말고, 순서를 바꾸지 말고, 숫자를
|
||
반올림하지 말고, 번역하지 마라.
|
||
|
||
트레이스에 있는 것만 복사하라. 빈칸을 절대 채우지 마라: 어떤 칸이
|
||
채워지지 않았다면 비워 두거나 그렇다고 말하라 — 지어낸 값은 빠진 값보다
|
||
나쁘다. 그것이 검색되어 사용되기 때문이다.
|
||
약 4000자로 제한하라. 산출물이 더 길면 구체적인 값을 담은 부분을 남기고
|
||
장식적인 서술은 버리되, 무엇을 버렸는지 적어라.
|
||
|
||
세션이 그런 산출물을 만들지 않았다면(아직 발견이 없는 순수 조사, 또는
|
||
사용자를 기다리며 멈춤) "".
|
||
|
||
이 필드는 검색 벡터의 일부가 아니다 — 찾는 일은 `statement`와
|
||
`subject`가 하고, `details`는 찾은 뒤에 값을 제공한다.
|
||
그러니 여기에 검색용 미끼를 쓰지 말고, 요약을 중복하지도 마라.
|
||
원본 그 자체를 써라.
|
||
|
||
files — 이 세션에서 실제로 조작하거나 조사한 0..N개의 절대 또는 저장소 기준
|
||
상대 파일 또는 디렉터리 경로. `C:\\`와 `D:\\` 같은 조사한 루트도
|
||
유지하라. 제안되거나 계획만 된 경로가 아니라 실제로 접근/검색한
|
||
경로만 포함하라. 해당하는 것이 없으면 [].
|
||
═══════════════════════════════════════════════════════════════════
|
||
호스트 컨텍스트 (SDK가 제공)
|
||
═══════════════════════════════════════════════════════════════════
|
||
- workspace_id 있음: {workspace_present}
|
||
- branch 있음: {branch_present}
|
||
|
||
workspace_id가 없으면 solution은 언급되지 않은 프로젝트 로컬 맥락에 의존할 수
|
||
없다 — 필요한 저장소 / 경로 / 서비스를 `solution`이나 `boundary_envs` 안에
|
||
명시하여 기억이 그것만으로 쓸 수 있게 하라.
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
대화
|
||
═══════════════════════════════════════════════════════════════════
|
||
{conversation_block}
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
출력 (엄격한 JSON만, markdown 금지)
|
||
═══════════════════════════════════════════════════════════════════
|
||
{{
|
||
"experiences": [
|
||
{{
|
||
"task": "...",
|
||
"search_keys": ["...", "..."],
|
||
"solution": "...",
|
||
"boundary_envs": "...",
|
||
"confidence": 0.9
|
||
}}
|
||
],
|
||
"fact": {{
|
||
"subject": "...",
|
||
"statement": "...",
|
||
"files": ["..."],
|
||
"details": "..."
|
||
}}
|
||
}}
|
||
|
||
기본은 추출이다. 구간이 실제 작업을 했다면 적어도 하나의 experience를 반드시
|
||
출력해야 한다 — 작업이 단순해 보이거나 한 번에 성공했더라도, 수행한 일의 재사용
|
||
가능한 핵심(방법 / 명령 / 하는 법)을 담아라.
|
||
세션 내용이 빈약하면 가장 가치 있는 experience 하나로 충분하다.
|
||
|
||
유일한 예외는 위의 멈춤 경우다: 구간이 누락된 정보를 사용자에게 묻느라 막혀서 실제
|
||
작업을 하지 않았다면 "experiences": []를 출력하고 fact만 써라. 이 지시를 충족하려고
|
||
experience를 만들어 내지 마라 — 지어낸 것("출발 도시를 묻는 방법")은 없는 것보다
|
||
나쁘다. 나중에 검색되어 그대로 따라지기 때문이다. 빈 목록은 오직 그 이유로만
|
||
허용되며, 실제로 작업이 이루어졌을 때의 지름길로는 절대 허용되지 않는다.
|
||
|
||
"fact"는 단일 객체이며 절대 목록이 아니다 — 세션당 하나의 기록이다.
|
||
|
||
fact는 항상 써라. fact의 기준은 "가치가 있었는가"가 아니라 "무엇이든 일어났는가"이며,
|
||
너에게 도달한 구간은 이미 그 기준을 통과했다.
|
||
여기에는 작업이라기보다 준비처럼 보이는 구간도 포함된다: 요구 사항, 매개변수, 또는
|
||
시작 상태를 말하는 사용자 메시지 하나뿐이고 도구는 실행되지 않은 경우, 그것이 바로
|
||
이후 세션이 스스로 재구성할 수 없는 자료다
|
||
— 요구 사항, 매개변수, 시작 상태를 상황과 함께 `statement`에 써라(간결한 표기로, 대화 원문을 그대로 베끼지 말 것).
|
||
`details`로 미루지 마라(`details`는 검색 벡터에 들어가지 않으며, 읽는 쪽에서도 더 이상 주입되지 않는다).
|
||
"fact": null은 내용이 전혀 없는 인사와 순수한 잡담에만 사용하라.
|
||
확신이 없으면 fact를 써라: 불필요한 기록의 비용은 한 행이지만, 빠뜨린 기록의 비용은
|
||
그것이 필요했으면서도 그것이 존재했는지 알 길이 없는 이후의 모든 세션이다.
|
||
"""
|
||
|
||
CODING_EXTRACT_PROMPT_CODING = """\
|
||
╔═══════════════════════════════════════════════════════════════════════════╗
|
||
║ 강한 제약 — 다른 무엇보다 먼저 읽어라. 이 프롬프트 뒤쪽에서 서술형을 ║
|
||
║ 원하는 것처럼 들리는 표현이 있어도 이 제약이 우선한다. ║
|
||
║ ║
|
||
║ 너는 어떤 사람이 무엇을 했는지에 대한 보고서를 쓰는 것이 아니다. 너는 ║
|
||
║ 미래의 AGENT가 행동하기 위해 필요한 것을 쓴다. 그 agent는 이미 자기 작업이 ║
|
||
║ 있고, 이 사용자가 무엇을 원했는지에는 관심이 없다. 그 agent가 알아야 하는 ║
|
||
║ 것은: 여기서 무엇이 결정되었고, 어느 코드 좌표에서였고, 무엇이 시도되어 ║
|
||
║ 기각되었고, 그것이 검증되었는지다. ║
|
||
║ ║
|
||
║ 구체적으로, 모든 `statement`와 모든 `solution`에서: ║
|
||
║ · 문장의 주어는 코드 또는 결정이다 — 절대 "사용자"도 "agent"도 아니다. ║
|
||
║ "사용자가 ~를 시도했다", "사용자가 ~를 원했다", "agent가 그다음 ║
|
||
║ ~를 확인했다"라고 쓰지 마라. ║
|
||
║ · "세션이 완료되었다", "오류를 기록했다", "아직 해결책을 찾지 못했다" ║
|
||
║ 같은 사무적인 문구로 끝내지 마라. 결정과 그 검증 상태로 끝내라. ║
|
||
║ 결정된 것이 없다면, 무엇이 배제되었는지와 아직 탐색되지 않은 다음 ║
|
||
║ 단서로 끝내라 — 그것이 재사용 가능한 부분이다. ║
|
||
║ · 구체적인 증상(오류 문자열, 실패한 명령, 잘못된 출력)을 유지하라 — ║
|
||
║ 미래의 검색이 매칭하는 대상이 그것이다. ║
|
||
║ ║
|
||
║ 나쁨: "사용자가 header_rows로 QTable을 RST로 쓰려다 TypeError를 만났다. ║
|
||
║ 세션은 오류를 기록한 상태로 완료되었다." ║
|
||
║ 좋음: "ascii.rst의 RST writer는 header_rows를 거부한다: `tbl.write(..., \\ ║
|
||
║ format='ascii.rst', header_rows=['name','unit'])`는 RST.__init__이 ║
|
||
║ 그것을 전달하지 않기 때문에 TypeError를 일으킨다. 이를 받아들이는 ║
|
||
║ ascii.fixed_width와 다르다. 수정은 작성되지 않음 — writer에 ║
|
||
║ fixed_width와 같은 header_rows 배선이 필요하다." ║
|
||
╚═══════════════════════════════════════════════════════════════════════════╝
|
||
|
||
너는 작업 실행 세션 구간에서 오래 유지되는 WORKING 기억을 추출한다.
|
||
"working" 세션이란 사용자가 도구를 통해 컴퓨터/시스템을 직접 조작하는 모든 실무
|
||
작업을 말한다 — 소프트웨어/코드 작업(작성, 디버깅, 리팩터링, 빌드, 배포,
|
||
API/라이브러리 사용)과 시스템·운영 작업(shell/PowerShell 명령 실행, 디스크/스토리지
|
||
분석 또는 정리, 파일·폴더 작업, 소프트웨어 설치/설정, 환경/장치 설정, 하드웨어/디스크
|
||
상태 점검, 백업, 스케줄링/자동화, 데이터 처리)을 모두 포함한다.
|
||
|
||
같은 구간에서 두 개의 계층을 만든다.
|
||
|
||
EXPERIENCES (절차적, "어떻게 하는가") — 재사용 가능한 방법으로, 아래의
|
||
가치 기준(VALUE BAR)을 통과할 때만 추출한다. (task, search_keys, solution,
|
||
boundary_envs) 묶음이다.
|
||
|
||
FACT (일화적, "이번에 무엇이 확립되었는가") — 이 세션에 대한 정확히 하나의
|
||
기록. 사용자가 무엇을 했는지에 대한 서술이 아니다. 다음 순서로 기록한다:
|
||
작업과 그 구체적인 증상(이것이 나중에 찾을 수 있게 만든다), 코드 좌표,
|
||
무엇으로 결정되었고 무엇이 기각되었는지, 그리고 어디까지 검증되었는지.
|
||
가치 기준은 없지만, 절대 하나를 넘지 않는다.
|
||
|
||
두 계층은 서로 다른 질문에 답하며 따로 저장된다. 일반화되는 방법은 EXPERIENCES에
|
||
넣고, 이 특정 세션의 이야기 — 이 시스템 / 이 프로젝트 / 지금에만 해당하는 것 — 는
|
||
FACT에 넣는다.
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
가치 기준 (EXPERIENCES 전용) — 가볍게 넘기지 마라.
|
||
═══════════════════════════════════════════════════════════════════
|
||
기억은 다음을 모두 만족해야 한다.
|
||
(a) 같은 사용자에게 다시 발생할 가능성이 높은 문제를 해결한다
|
||
(b) 해결책이 사소하지 않다: 여러 정보/단계를 결합하거나, 결정 근거를 담고
|
||
있거나, 어렵게 얻은 우회책을 담고 있거나, 운영 작업을 위한 재사용 가능한
|
||
절차/레시피다(예: 디스크 사용량을 분석하고 정리하는 방법, 파일을 일괄 정리하는
|
||
방법, 하드웨어 상태를 점검하는 방법, 예약 작업을 설정하는 방법)
|
||
(c) 미래의 agent / 사용자가 다시 알아내기보다 찾아보는 쪽을 택할 것이다
|
||
|
||
중요 — 분석 / 진단 / 조사 작업도 해당한다:
|
||
작업이 무언가를 변경하거나 "고쳐야만" 기억할 가치가 있는 것은 아니다.
|
||
읽기 전용 / 분석 / 문제 해결 작업도 그 절차나 결론이 재사용 가능하면
|
||
가치가 있다. 다음을 명시적으로 추출하라.
|
||
- 분석 / 진단 레시피: 재사용 가능한 절차 + 그것이 밝혀낸 것
|
||
(예: "C 드라이브 공간을 차지하는 폴더를 분석하는 방법" — 스캔 명령, 마주친
|
||
함정, 그리고 발견 사항). "보고서만 나왔고 변경은 없었다"는 건너뛸 이유가
|
||
아니다 — 재사용 가능한 방법 자체가 기억이다.
|
||
- 조사 결과, 부정적 / 찾지 못한 결과 포함
|
||
(예: "rg / fs_search / SQL 인덱스로 C:와 D: 전체에서 폴더 X를 찾았으나 —
|
||
어디에도 없었다"). 기록된 막다른 길은 다음 agent가 같은 헛수고를 반복하지 않게
|
||
해 주므로 기준을 통과한다.
|
||
"어시스턴트가 명령을 다시 만들어 낼 수 있다"거나 "실제로 수정된 것이 없다"는
|
||
이유만으로 기억을 거부하지 마라. 절차나 결론이 앞으로의 수고를 덜어 준다면 남겨라.
|
||
|
||
다음은 추출하지 마라.
|
||
- 재사용 가능한 절차가 없는 정말 사소한 단일 단계 작업("파일 하나 이름 바꾸기",
|
||
"git status", "ls 한 번 실행")
|
||
- 이미 해결되었고 옮겨 쓸 교훈이 없는 일회성 문제
|
||
- 이미 저장소/시스템에 있는 정보(package.json을 중복하지 마라)
|
||
- 재사용 가능한 패턴을 담지 않은 PR별 / 세션별 일회성 작업
|
||
|
||
경험칙: 사용자(또는 같은 종류의 작업을 하는 미래의 agent)가 다시 알아내기보다 이것이
|
||
적혀 있기를 원할 것이라면, 남겨라. 여러 단계의 운영 절차(코드뿐 아니라 시스템/운영
|
||
또는 읽기 전용 분석/검색 절차라도)는 보통 해당한다. 어떤 사용자라도 `--help`로
|
||
30초 안에 찾을 수 있는 단일 명령이면서 기억할 만한 결정/함정이 없는 경우에만 버려라.
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
코드 작업 — 미래의 agent가 다시 도출할 수 없으므로 반드시 담아야 하는 것
|
||
═══════════════════════════════════════════════════════════════════
|
||
구간이 저장소에 대한 소프트웨어 작업이라면, 재사용 가능한 방법은 그 절반일 뿐이다.
|
||
나머지 절반은 그 작업이 어디에 있고 어느 버전이 채택되었는지다. 그것들은 나중에
|
||
코드를 읽어서는 복원할 수 없다 — 코드는 현재 상태를 보여 주지만, 그것이 어느
|
||
브랜치에 있는지도, 이미 무엇이 기각되었는지도 보여 주지 않는다.
|
||
|
||
이 절은 더 긴 기록을 요구하지 않는다. 같은 예산을 더 가치 있는 토큰에 쓰라고
|
||
요구한다: 반복된 목표 서술, 어시스턴트가 "그다음 ~하기로 했다"는 식의 설명, 그저
|
||
그대로 옮기는 코드 붙여넣기를 버리고, 그 글자 수를 아래 네 항목에 써라. 브랜치를
|
||
빠뜨리면서 diff 30줄을 인용하는 기록은 나쁜 거래다.
|
||
|
||
1. 브랜치 / 작업 위치 — fact `statement`에 그대로. 구간이 `feat/x` / `fix/y`를
|
||
언급하거나 체크아웃했다면 그 토큰이 반드시 나타나야 한다. 잘못된 브랜치에서
|
||
이어 가는 agent는 조용히 버려지는 변경을 만들어 내므로, 이 한 토큰이 다른 모든
|
||
세부 사항보다 중요하다.
|
||
|
||
2. 어느 버전이 채택되었는가 — 사용자가 마음을 바꿨다면, 채택된 것을 명시하고
|
||
기각된 것을 한 절로 표시하라: "A로 결정; 중간안 B는 폐기". 둘 다 온전히 쓰지
|
||
말고 — 한 절로. 최신 것만 남기는 것은 잘못이다: 기각된 코드를 만난 agent가
|
||
그것을 알아보지 못하면 폐기된 변형으로 되돌리는 "수정"을 하게 된다.
|
||
`그건 취소하고 / 되돌리자 / 사실은 이렇게 / 아니, 그거 말고` 같은 표현이 이것을
|
||
표시한다: 뒤에 오는 것이 기록 대상이고, 앞에 오는 것은 대체된 것이다.
|
||
|
||
3. 붙여넣은 코드가 아니라 정확한 좌표 — 저장소 기준 상대 경로
|
||
(`qutebrowser/utils/debug.py`, 절대 그냥 `debug.py`가 아니다)와 손댄 함수 /
|
||
클래스. 시그니처가 바뀌었다면 새 시그니처를 적어라. 상수, 정규식, 리터럴이
|
||
결정되었다면 그것을 그대로 인용하라 — 다만 그것만 인용하고, 주변 블록은
|
||
인용하지 마라. 정확한 한 줄이 대충 쓴 한 단락보다 낫다.
|
||
|
||
4. 검증되었는가 vs. 그냥 작성되었는가 — 네 단어면 충분하다("테스트 실행, 통과" /
|
||
"git diff로만 확인, 테스트 미실행"). "수정이 들어갔다"와 "수정이 들어가고 테스트까지
|
||
했다"는 미래의 agent가 다음에 할 일을 매우 다르게 만든다.
|
||
|
||
안티패턴 — 목표만 요약하고 좌표를 빠뜨리기:
|
||
나쁨: "색상 파싱 로직을 더 깔끔하게 리팩터링했다."
|
||
좋음: "feat/q4에서 qutebrowser/config/configtypes.py의 QtColor._parse_value:
|
||
`mult = 360.0 if kind == 'h' else 255.0`로 결정; 모든 경우에 255.0을
|
||
쓰던 중간안은 폐기. 테스트 미실행."
|
||
(좋은 예가 대부분의 나쁜 예보다 더 짧다 — 다만 모든 글자를 다시 도출할 수 없는
|
||
것에 쓴다)
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
EXPERIENCE 필드
|
||
═══════════════════════════════════════════════════════════════════
|
||
|
||
task — 질의 형태의 이름. 사용자가 나중에 어떻게 물을지로 표현하라.
|
||
"publish_release_internal"이 아니라 "xx 플랫폼에 릴리스 배포하기"
|
||
그 자체로 완결되게("이것", "그 파일", "우리" 금지).
|
||
|
||
search_keys — `task`만으로는 매칭되지 않을 solution의 중요한 측면을 담은
|
||
0~5개의 대체 표현. 미래의 사용자가 물어볼 질문이나 주제로
|
||
표현하라. task와 달라야 한다.
|
||
task가 이미 모든 것을 포괄하면 생략하라.
|
||
|
||
solution — 사용자가 실제로 필요로 하는, 완전하고 그 자체로 완결된 내용:
|
||
명령, 자격 증명의 위치(자격 증명 값은 안 됨),
|
||
파일 경로, 순서가 있는 단계, 함정, 결정 근거.
|
||
여러 턴에 걸친 처음부터 끝까지의 작업이 하나의 일관된 작업을
|
||
이루면 하나의 solution으로 모아라. 사용자가 서로 무관한 작업을
|
||
했을 때만 여러 기억으로 나눠라.
|
||
|
||
boundary_envs — solution이 의존하는 구체적인 고정 조건, 여러 줄의 "key: value":
|
||
런타임 / SDK / 라이브러리 + 버전
|
||
API 엔드포인트 또는 메서드 시그니처
|
||
외부 서비스 / 미들웨어 버전
|
||
환경 변수 이름(값은 안 됨)
|
||
설정 파일 경로
|
||
호스트 / 클러스터 / 리전 식별자
|
||
solution이 환경과 무관하면 ""(빈 값)로 두어라.
|
||
목적: 기반 SDK/미들웨어가 업그레이드될 때, 미래의 소비자가 이것을
|
||
보고 solution이 여전히 적용되는지 판단할 수 있게 한다.
|
||
|
||
confidence — 0.0 ~ 1.0
|
||
1.0 사용자가 이것을 규칙 / 지시로 명시했다
|
||
0.7 도구 동작으로부터 충분한 근거를 가지고 추론했다
|
||
0.5 약하게 추론했다; 관찰 한 번; 불확실하다
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
FACT 필드 — 구간당 정확히 하나, 가치 기준 없음
|
||
═══════════════════════════════════════════════════════════════════
|
||
|
||
구간 전체에 대해 하나의 fact를 쓴다 — 이 세션의 복구 가능한 상태 기록이며,
|
||
다듬어진 회고가 아니고, 별개의 발견 사항 목록은 더더욱 아니다. 미래의 agent가
|
||
제안, 추론, 부분적인 검색을 검증된 것으로 오인하지 않고 이것으로부터 이어서 진행할
|
||
수 있어야 한다.
|
||
|
||
statement — 이 세션의 간결한 요약. 두 가지 일을 동시에 하며, 둘 다 해내야 한다:
|
||
검색 벡터(미래의 agent가 사용자가 문제를 표현하는 방식으로 쓴 새 이슈
|
||
보고로 검색해 찾아낸다)이자, 그 agent가 그다음 행동의 근거로 삼는
|
||
내용물이다. 그러므로:
|
||
|
||
작업과 증상으로 시작하라. 사용자가 쓸 말로.
|
||
이것이 찾을 수 있게 만든다 — 표면 형태를 문제가 보고되는 방식에
|
||
가깝게 유지하라. 일반적인 "버그 수정" / "파일 찾기"가 아니라 구체적인
|
||
목표와 대상을 명시하라("D:\\WXWork 아래 디스크 공간 회수",
|
||
"feat/q4에서 QtColor._parse_value의 hue 배수 조정", "new_file_search
|
||
폴더 위치 찾기"). 구체적인 증상도 포함하라: 오류 문자열, 실패한
|
||
명령, 잘못된 출력.
|
||
|
||
그다음 좌표와 결정을 적어라.
|
||
코드 작업이면 저장소 기준 상대 경로, 심볼, 브랜치. 무엇으로
|
||
결정되었는지 명시하고, 같은 절에서 무엇이 폐기되었는지 표시하라
|
||
("X로 결정; 중간안 Y는 폐기"). 이것이 코드를 읽어서는 다시 도출할 수
|
||
없는 부분이다 — 코드는 채택된 것만 보여 주고, Y가 시도되어
|
||
기각되었다는 사실은 결코 보여 주지 않는다. 나중에 기각된 변형을 만난
|
||
agent가 그것을 알아볼 수 있어야 하며, 그렇지 않으면 코드를 폐기된
|
||
버전으로 되돌리는 "수정"을 하게 된다.
|
||
|
||
증거 상태로 끝내라.
|
||
다음 중 정확히 하나: 완료, 부분 완료, 실패, 차단됨, 또는
|
||
아직 검증 대기 중 — 그리고 어떻게 확인했는지("테스트 실행, 통과" /
|
||
"git diff로만 확인, 테스트 미실행"). 부정적 결과와 막다른 길도
|
||
과장하지 말고 포함하라.
|
||
|
||
⚠️ 서술에서 멈추지 마라. "사용자가 X를 시도했고 오류 Y를 만났다. 아직
|
||
해결책은 없다"는 낭비된 기록이다: 검색은 잘 되지만 미래의 agent에게
|
||
행동할 근거를 주지 않는다. 세션이 정말로 어떤 결정에도 이르지 못했다면,
|
||
무엇이 배제되었고 아직 탐색되지 않은 다음 단서가 무엇인지 말하라 —
|
||
그것이 재사용 가능한 부분이다.
|
||
|
||
문장의 주어는 "사용자"가 아니라 코드와 결정이어야 한다. 2~4문장,
|
||
라벨 붙인 섹션이 아니라 흐르는 서술.
|
||
상한은 라틴 문자 약 400자(CJK/한글 약 200자).
|
||
|
||
증거 충실성 — 필수:
|
||
- 실제로 실행된 동작과 트레이스에 명시적으로 있는 증거만 기록하라.
|
||
중요한 명령, 플래그, 인자, 오류, 숫자를 그대로 보존하라. 관례적인
|
||
플래그로 명령을 절대 보충하지 말고, 조언/계획을 실행된 동작으로
|
||
바꾸지 마라.
|
||
- 시도함, 관찰함, 검증함, 추론함, 미해결 사이의 구별을 보존하라.
|
||
서술에 그런 라벨을 붙이지 말고, 그 의미를 자연스럽게 표현하라.
|
||
예: "…를 시도했으나 Access denied로 막혔다" 또는
|
||
"… 로 의심되나 아직 검증되지 않았다".
|
||
- 도구 출력이나 명시적 확인 없이 수정이 성공했다거나, 원인이
|
||
확립되었다거나, 파일/폴더가 존재하지 않는다거나, 테스트가
|
||
통과했다고 절대 주장하지 마라.
|
||
- 모든 부정적 발견은 실제로 검색한 범위로 한정하고, 그것을 무효화할
|
||
수 있는 명시적인 범위 제한을 모두 열거하라. 특히 권한 실패와 최대
|
||
검색 깊이:
|
||
"C:\\와 D:\\의 검색된 범위 안에서는 찾지 못함; 일부
|
||
디렉터리는 Access denied로 건너뛰었고, 검색에는 깊이 제한도
|
||
있었다". 요약을 보기 좋게 하려고 제한 하나라도 빼지 마라.
|
||
부분적인 검색을 전역적인 부재로 절대 격상하지 마라.
|
||
- 인과관계는 트레이스가 그것을 직접 검증할 때만 확실한 것으로
|
||
진술하라. 그렇지 않으면 유보 표현을 유지하라("의심됨" / "추론됨" /
|
||
"아직 검증되지 않음"). 잘못된 테스트 가정을 시스템의 근본 원인으로
|
||
다시 쓰지 마라.
|
||
|
||
매우 중요 — 이 텍스트가 바로 검색 벡터이며, 미래의 질의가 매칭되는
|
||
유일한 대상이다. 일반적인 요약은 쓸모없다. 모든 세션이 일반적인
|
||
요약처럼 보이기 때문이다: 그것은 모든 질의에 매칭되면서 어느 질의에도
|
||
답하지 못한다. 그러므로 이 세션을 찾을 수 있게 만드는 구체적인 닻을
|
||
반드시 명시하라 — 실제 파일 경로, 함수 / 심볼 이름, 브랜치 이름, 명령,
|
||
오류 문자열, 버전, 설정 키와 값, 호스트 / 서비스 이름.
|
||
누군가 나중에 그중 어느 하나를 검색하면, 돌아와야 하는 것이 바로 이
|
||
요약이어야 한다.
|
||
좋음: "CI에서 실패하는 `npm run build` 수정.
|
||
.github/workflows/ci.yml과 package.json을 읽고, node 18에서
|
||
webpack 5.74가 ERR_OSSL_EVP_UNSUPPORTED를 일으키는 것으로
|
||
추적. 워크플로의 node를 20으로 올리자 빌드 통과; node 18에서는
|
||
로컬에서 여전히 재현됨."
|
||
좋음: "feat/q0 브랜치, qutebrowser/utils/debug.py의 signal_name:
|
||
PyQt<5.11은 이름을 repr(sig)로만 노출하며, repr은 대괄호 형태와
|
||
괄호 형태 두 가지로 온다. fullmatch로 두 패턴을 순서대로
|
||
시도해 첫 매칭에서 중단하는 방식으로 결정; 단일 정규식 버전은
|
||
폐기. git diff로 확인, 테스트 미실행."
|
||
나쁨: "사용자가 빌드 버그 수정을 원했고, agent가 설정 파일 몇 개를
|
||
보고, 코드를 조금 바꿔서 고쳤다."
|
||
(경로도, 명령도, 오류 문자열도, 버전도 없다 — 검색할 거리가
|
||
전혀 없고, 모든 세션이 정확히 이렇게 읽힌다)
|
||
나쁨: "기능 브랜치에서 signal-name 파싱 호환성 문제를 고쳤다."
|
||
(어느 브랜치? 어느 파일? 어느 함수? 경쟁하던 두 버전 중 어느
|
||
것이 채택되었나? 일주일 뒤에 검색되어도 살아남는 것이 하나도
|
||
없다)
|
||
충실성 예시 (나쁨 → 좋음):
|
||
"ran Get-ChildItem -Force" → "ran Get-ChildItem"
|
||
(트레이스에 `-Force`가 없었다; 지어내지 마라)
|
||
"new_file_search가 존재하지 않음을 확인했다" →
|
||
"new_file_search는 C:\\와 D:\\의 검색된 범위 안에서는 찾지
|
||
못했다; 일부 디렉터리는 Access denied 또는 깊이 제한으로
|
||
건너뛰었으므로 조사는 일부만 끝난 상태다"
|
||
"동일한 페이로드가 잘못된 lock 오류를 일으킨다" →
|
||
"검증 스크립트는 마지막 writer가 항상 고정된 rank라고 가정했다.
|
||
그 가정은 성립하지 않으므로, lock 버그가 있는지는 아직 검증되지
|
||
않았다"
|
||
|
||
인사나 순수한 잡담은 기록하지 마라. 구간이 아무것도 확립하지
|
||
못했다면 "fact": null을 출력하라.
|
||
|
||
files — 이 세션에서 실제로 조작하거나 조사한 0..N개의 절대 또는 저장소 기준
|
||
상대 파일 또는 디렉터리 경로. `C:\\`와 `D:\\` 같은 조사한 루트도
|
||
유지하라. 제안되거나 계획만 된 경로가 아니라 실제로 접근/검색한
|
||
경로만 포함하라. 해당하는 것이 없으면 [].
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
호스트 컨텍스트 (SDK가 제공)
|
||
═══════════════════════════════════════════════════════════════════
|
||
- workspace_id 있음: {workspace_present}
|
||
- branch 있음: {branch_present}
|
||
|
||
workspace_id가 없으면 solution은 언급되지 않은 프로젝트 로컬 맥락에 의존할 수
|
||
없다 — 필요한 저장소 / 경로 / 서비스를 `solution`이나 `boundary_envs` 안에
|
||
명시하여 기억이 그것만으로 쓸 수 있게 하라.
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
대화
|
||
═══════════════════════════════════════════════════════════════════
|
||
{conversation_block}
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
출력 (엄격한 JSON만, markdown 금지)
|
||
═══════════════════════════════════════════════════════════════════
|
||
{{
|
||
"experiences": [
|
||
{{
|
||
"task": "...",
|
||
"search_keys": ["...", "..."],
|
||
"solution": "...",
|
||
"boundary_envs": "...",
|
||
"confidence": 0.9
|
||
}}
|
||
],
|
||
"fact": {{
|
||
"statement": "...",
|
||
"files": ["..."]
|
||
}}
|
||
}}
|
||
|
||
필수: 적어도 하나의 experience를 반드시 출력해야 한다. 빈 목록을 반환하지 마라.
|
||
작업이 단순해 보이거나 한 번에 성공했더라도, 수행한 일의 재사용 가능한 핵심(방법 /
|
||
명령 / 하는 법)을 담아라. 세션 내용이 빈약할 때는 가장 가치 있는 experience 하나만
|
||
있으면 되지만, "experiences" 배열을 비워서 출력하는 일은 절대 없어야 한다.
|
||
|
||
"fact"는 단일 객체이며 절대 목록이 아니다 — 세션당 하나의 기록이다.
|
||
"fact": null은 구간이 아무것도 확립하지 못한 순수한 잡담일 때만 사용하라.
|
||
"""
|
||
|
||
CODING_RECONCILE_PROMPT = """\
|
||
너는 새로운 코딩 기억 초안을 사용자의 기존 기억과 어떻게 병합할지 결정한다.
|
||
|
||
각 새 초안에 대해 다음 중 하나를 결정하라.
|
||
ADD — 같은 작업/측면을 기술하는 기존 기억이 없다
|
||
UPDATE — 기존 기억이 같은 작업을 기술하며, 새 초안이 그것을 대체한다
|
||
(solution이 바뀌었거나, 환경이 업그레이드되었을 수 있다)
|
||
SKIP — 기존 것과 중복이며 새로운 것이 없다
|
||
|
||
추가로, 새 내용이 기존 후보를 쓸모없게 만들거나 무효화할 때 기존 후보에 대해
|
||
DELETE op을 출력할 수 있다.
|
||
DELETE — 새 대화가 기존 기억을 명시적으로 대체/취소/무효화하며, UPDATE로는
|
||
고칠 수 없는 경우. 예시:
|
||
* 사용자가 "앞으로 X는 쓰지 마"라고 말했고, X가 (추가/갱신되는 기억이
|
||
아닌) 별개의 기억으로만 존재하는 경우
|
||
* 후보의 boundary_envs(서비스 / 엔드포인트 / SDK)가 사라졌고,
|
||
새 대화가 그 폐기를 확인해 주는 경우
|
||
|
||
규칙:
|
||
- "같은 작업"이란 기존 기억과 새 초안이 미래의 같은 사용자 질의에 대해 둘 다
|
||
유효한 답이 된다는 뜻이다.
|
||
- solution이 다르면 UPDATE를 우선하라(이력은 유지하지 않는다).
|
||
- task와 solution이 모두 거의 일치하면 SKIP.
|
||
- DELETE는 후보가 무효/폐기되었다는 명시적 증거가 있을 때만 하라.
|
||
후보가 "낡아 보인다"는 이유만으로 절대 DELETE하지 마라.
|
||
- DELETE가 생기면 출력 op의 개수가 초안 개수보다 많을 수 있다.
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
새 초안
|
||
═══════════════════════════════════════════════════════════════════
|
||
{drafts_block}
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
기존 후보 (초안별, task 유사도로 검색됨)
|
||
═══════════════════════════════════════════════════════════════════
|
||
{candidates_block}
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
출력 (엄격한 JSON만, markdown 금지)
|
||
═══════════════════════════════════════════════════════════════════
|
||
{{
|
||
"ops": [
|
||
{{"draft_idx": 0, "action": "ADD"}},
|
||
{{"draft_idx": 1, "action": "UPDATE", "target_experience_id": "exp_xxx", "reason": "..."}},
|
||
{{"draft_idx": 1, "action": "DELETE", "target_experience_id": "exp_yyy", "reason": "사용자가 Y를 명시적으로 폐기함"}},
|
||
{{"draft_idx": 2, "action": "SKIP", "reason": "중복"}}
|
||
]
|
||
}}
|
||
|
||
초안이 없으면 {{"ops": []}}를 출력하라.
|
||
"""
|
||
|
||
CODING_FACT_RECONCILE_PROMPT = """\
|
||
너는 세션 수준의 WORKING FACT를 관리한다. 각 statement는 하나의 트레이스/세션
|
||
전체를 요약한다: 사용자의 작업, 대략적인 접근 방식, 그리고 결과. 이것은 원자적인
|
||
주장이 아니다.
|
||
|
||
아래의 모든 새 세션 fact는 저장될 것이며, 그것은 너의 결정 사항이 아니다. 너의
|
||
유일한 임무는 작업의 연속성을 판별하는 것이다: 새 세션이 기존 세션 fact와 같은
|
||
사용자 작업을 계속 진행하고 있는가? 그렇다면 더 오래된 fact를 SUPERSEDE하여,
|
||
연결이 그 작업의 세션 간 진행 체인을 이루게 하라.
|
||
|
||
기존 fact를 대체됨으로 표시하는 것은 다음에 대한 강한 증거가 있을 때만 하라.
|
||
- subject가 일치한다: 두 기록이 같은 개체 — 같은 사람, 같은 파일/모듈, 같은
|
||
호스트/서비스 — 에 관한 것이다. subject가 같다는 것은 필요조건이지만
|
||
충분조건은 아니다. 그리고
|
||
- 사용자 목표가 단지 같은 주제/프로젝트에 속하는 정도가 아니라 실질적으로
|
||
같다. 그리고
|
||
- 새 트레이스가 기존 트레이스에 기록된 작업을 계속하거나, 재시도하거나,
|
||
확장하거나, 검증하거나, 끝낸다.
|
||
|
||
다음 경우에는 대체하지 마라.
|
||
- 두 기록이 서로 다른 subject를 가리킨다. 한 그룹의 두 사람, 한 모듈의 두 파일,
|
||
한 클러스터의 두 호스트는 연속이 아니라 병렬이다: 한 사람의 상태가 다른 사람의
|
||
더 새로운 버전이 아니다. subject가 (unspecified)로 표시된 경우에는 statement만
|
||
으로 판단하라.
|
||
- 두 세션이 단지 같은 파일, 심볼, 컴포넌트, 저장소, 오류, 기술을 언급하지만
|
||
서로 다른 사용자 목표를 추구한다.
|
||
- 한 작업이 다른 작업에 맥락이나 기법만 제공한다.
|
||
- 관계가 불확실하다. 보수적으로 판단하라: INSERT가 안전한 기본값이며, 무관한
|
||
세션을 병합하면 각자의 작업 이력이 파괴된다.
|
||
|
||
각 statement를 증거에 한정된 텍스트로 다루어라. 그 안의 추론을 확립된 원인으로
|
||
재해석하지 말고, 공유된 추측이나 오류를 사용자 목표가 같다는 증거로 쓰지 마라.
|
||
너의 판단은 오직 작업의 연속성에 관한 것이다. 어느 세션 기록도 "교정"하거나,
|
||
보완하거나, 꾸미지 마라.
|
||
|
||
예시:
|
||
기존: "사용자는 CI의 npm run build를 고쳐야 했다. Node 18에서의
|
||
ERR_OSSL_EVP_UNSUPPORTED로 추적했고, 워크플로는 아직 변경되지 않았다."
|
||
신규: "사용자는 같은 CI 수정을 계속했다. .github/workflows/ci.yml의 Node를
|
||
20으로 올리자 npm run build가 통과했다."
|
||
→ SUPERSEDE
|
||
|
||
기존: "사용자는 auth/middleware.ts의 JWT 만료 처리를 고쳐야 했다.
|
||
refresh 로직이 추가되었다."
|
||
신규: "사용자는 auth/middleware.ts에 성능 로깅을 추가해야 했다.
|
||
지연 시간 지표가 도입되었다."
|
||
→ 둘 다 유지
|
||
(같은 파일/프로젝트, 다른 사용자 목표)
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
새 세션 FACT
|
||
═══════════════════════════════════════════════════════════════════
|
||
{drafts_block}
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
기존 세션 FACT (새 fact별, statement 유사도로 검색됨)
|
||
═══════════════════════════════════════════════════════════════════
|
||
{candidates_block}
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
출력 (엄격한 JSON만, markdown 금지)
|
||
═══════════════════════════════════════════════════════════════════
|
||
{{
|
||
"ops": [
|
||
{{"draft_idx": 0, "supersedes": ["fact_xxx"], "reason": "같은 CI 수정 작업의 연속"}},
|
||
{{"draft_idx": 1, "supersedes": []}}
|
||
]
|
||
}}
|
||
|
||
같은 draft_idx의 후보 목록에 나타난 fact id만 사용하라.
|
||
어디에서도 대체가 일어나지 않으면 {{"ops": []}}를 출력하라.
|
||
"""
|
||
|
||
CODING_CHAIN_SUMMARY_PROMPT = """\
|
||
working 기억 저장소의 한 사용자 작업이 여러 세션에 걸쳐 진행되었다. 아래는 그
|
||
작업의 진행 체인에 있는 세션 요약들이며, 오래된 것이 먼저이고 마지막이 현재
|
||
세션이다.
|
||
|
||
같은 작업이 여러 세션에 걸쳐 현재 상태까지 어떻게 진행되었는지 요약하라.
|
||
모든 중요한 단계를 시간순으로 다루어라. 각 단계마다 가능한 경우 다음을 보존하라:
|
||
실제 동작 또는 변경, 그 직접적인 결과, 그리고 미해결 문제나 제약 조건.
|
||
마지막에 현재 상태를 명시적으로 적어라.
|
||
|
||
규칙:
|
||
- 이것은 압축일 뿐이다. 항목들이 말하는 것만 보고하라. 명령, 원인, 동기, 동작,
|
||
테스트 결과, 성공 상태, 전역적인 부정 결론을 절대 추가하거나 보완하지 마라.
|
||
- 증거 상태를 정확히 보존하라: 시도함은 완료가 아니고, 관찰함은 검증이 아니며,
|
||
추론함은 확립이 아니고, 미해결은 미해결로 남는다.
|
||
불확실성을 나타내는 표현과 검색/권한/범위 제한을 유지하라.
|
||
- 현재의 종료 상태를 충실하게 진술하라(완료, 부분 완료, 실패, 차단됨, 또는
|
||
아직 검증 대기 중). 그럴듯한 인과 설명이나 그것을 검증하지 않은 이후 항목에
|
||
근거해 절대 상태를 격상하지 마라.
|
||
- 항목들이 의심되는 원인과 이후의 결과를 언급할 때, 어떤 항목이 그 원인이
|
||
검증되었다고 직접 말하는 경우에만 인과적으로 연결하라.
|
||
- 구체적인 검색 닻을 보존하라: 파일 경로, 심볼, 명령, 오류 문자열, 버전,
|
||
설정 키/값, 그리고 진행에 중요한 수치.
|
||
- 2–3세대 체인에서는 진행 과정을 한 문장으로 과도하게 압축하지 마라. 유용하다면
|
||
간결한 2–4단락, 또는 분명한 작업 목표, 단계별 진행, 현재 상태를 담은 읽기 쉬운
|
||
서술을 선호하라.
|
||
- 전체 이력을 기계적으로 복사하지 마라. 중요한 동작, 직접적인 결과, 남은 과제는
|
||
유지하면서 반복과 가치 낮은 세부 사항은 생략하라.
|
||
- 단계 제목이나 날짜를 지어내지 마라. 항목들이 날짜를 의미 있게 만드는 경우에만
|
||
날짜를 언급하라. 맥락 없는 타임스탬프는 잡음이다.
|
||
- 항목들이 더 오래된 수정 이력이 생략되었다고 말한다면, 이전 이력이 생략되었음을
|
||
명시하라. 제공된 항목들이 전부인 것처럼 암시하지 마라.
|
||
- 항목들이 쓰인 것과 같은 언어로 써라.
|
||
- 머리말이나 전체를 감싸는 인용 부호 없이.
|
||
|
||
예시
|
||
항목:
|
||
(2026-07-20) 사용자는 CI의 npm run build를 고쳐야 했다. Node 18에서의
|
||
ERR_OSSL_EVP_UNSUPPORTED로 추적했고, 워크플로는 아직
|
||
변경되지 않았다.
|
||
(2026-07-24) 사용자는 그 CI 수정을 계속했다. .github/workflows/ci.yml의
|
||
Node를 20으로 올렸지만, 의존성 캐시에서 이전 오류가 여전히
|
||
재현되었다.
|
||
(현재) 사용자는 같은 수정을 계속 검증했다. npm 캐시를 지운 뒤
|
||
npm run build가 통과했다.
|
||
출력:
|
||
작업 목표는 CI의 npm run build를 고치는 것이다. 처음에는 Node 18에서의
|
||
ERR_OSSL_EVP_UNSUPPORTED로 추적되었고 워크플로는 아직 변경되지 않았다.
|
||
이어서 .github/workflows/ci.yml을 Node 20으로 올렸지만 의존성 캐시 때문에
|
||
이전 오류가 남아 있었다. 마지막으로 npm 캐시를 지운 뒤 npm run build가
|
||
통과했으므로, 이 빌드 수정은 현재 완료된 것으로 검증되었다.
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
항목
|
||
═══════════════════════════════════════════════════════════════════
|
||
{chain_block}
|
||
|
||
═══════════════════════════════════════════════════════════════════
|
||
출력 (요약 자체만, 그 외에는 아무것도 없이)
|
||
═══════════════════════════════════════════════════════════════════
|
||
"""
|
||
|
||
|
||
# ================================================================
|
||
# Episode / topic summaries
|
||
# ================================================================
|
||
|
||
EPISODE_SUMMARY_PROMPT = """너는 사용자와의 대화 중 하나의 에피소드를 요약하고 있다.
|
||
|
||
에피소드란 하나로 이어지는 논의 구간이다 — 이야기된 하나의 대상이며, 여러 번의
|
||
쓰기 세션에 걸쳐 있을 수도 있다.
|
||
|
||
## 이 에피소드 동안 수집된 기억 (오래된 것 → 새로운 것)
|
||
|
||
{memories}
|
||
|
||
---
|
||
|
||
이 에피소드에서 무슨 일이 있었는지 / 무엇이 논의되었는지를 밀도 높은 하나의 요약으로 써라.
|
||
|
||
규칙:
|
||
|
||
* 2–4문장, 3인칭, "사용자는 ..."으로 시작한다(기억이 다른 사람에 관한 것이면
|
||
그 이름 있는 사람으로 시작한다).
|
||
* 기억들을 하나의 일관된 서술로 엮어라 — 에피소드에는 보통 그것을 관통하는
|
||
줄기가 있다(계획, 문제, 외출, 결정).
|
||
* 구체적인 세부 사항을 보존하라: 이름, 장소, 숫자, 날짜.
|
||
* 기억에 없는 것을 절대 지어내지 마라.
|
||
* 기억을 하나씩 열거하지 마라. 종합하라.
|
||
* 출력 언어는 기억의 언어와 일치해야 한다.
|
||
|
||
요약 텍스트만 반환하라. 머리말, JSON, 인용 부호 없이."""
|
||
|
||
TOPIC_SUMMARY_PROMPT = """너는 하나의 주제에 대해 알려진 모든 것을 요약하고 있다.
|
||
|
||
## 주제
|
||
|
||
{topic_name}
|
||
|
||
## 이 주제에 속한 기억 (오래된 것 → 새로운 것)
|
||
|
||
{memories}
|
||
|
||
---
|
||
|
||
이 주제와 관련하여 사용자에 대해 알려진 것을 밀도 높은 하나의 요약으로 써라.
|
||
|
||
규칙:
|
||
|
||
* 2–4문장, 3인칭, "사용자는 ..."으로 시작한다(기억이 다른 사람에 관한 것이면
|
||
그 이름 있는 사람으로 시작한다).
|
||
* 주제를 전체적으로 다루어라 — 현재 상황을 진술하고, 변화가 중요한 경우에만
|
||
그것이 어떻게 전개되었는지 언급하라.
|
||
* 구체적인 세부 사항을 보존하라: 이름, 장소, 숫자, 날짜.
|
||
* 기억에 없는 것을 절대 지어내지 마라.
|
||
* 기억을 하나씩 열거하지 마라. 종합하라.
|
||
* 출력 언어는 기억의 언어와 일치해야 한다.
|
||
|
||
요약 텍스트만 반환하라. 머리말, JSON, 인용 부호 없이."""
|
||
|
||
|
||
# ================================================================
|
||
# System 2 evolve (pipelines/system2_agent.py)
|
||
# ================================================================
|
||
|
||
SYSTEM2_AGENT_PROMPT = """너는 인지 처리 Agent다. 너의 임무는 사용자의 raw 기억 데이터로부터 상위 차원의 인지 구조를 진화시키는 것이다.
|
||
|
||
## 기억 아키텍처
|
||
|
||
### VDB (벡터 데이터베이스) — 읽기 전용
|
||
| Layer | 설명 |
|
||
|-------|-------------|
|
||
| L0 BASIC_INFO | 사용자 속성(이름, 나이, 직업) |
|
||
| L1 RAW | 원본 대화 기록 |
|
||
| L2 FACT | 대화에서 추출된 원자적 사실 |
|
||
| L3 SUMMARY | 세션 요약 |
|
||
| L4 IDENTITY | 정체성 특성, 선호, 습관 |
|
||
| L5 KNOWLEDGE | 도메인 지식 |
|
||
|
||
### Graph (지식 그래프) — 너만이 쓰기를 한다
|
||
| Layer | 설명 |
|
||
|-------|-------------|
|
||
| L6 SCHEMA | 행동 패턴 — 도메인에 묶이고, 원자적이며, 생성된 뒤에는 불변 |
|
||
|
||
## L6 Schema란 무엇인가?
|
||
|
||
Schema는 장기 사용자 모델 안의 하나의 사건 간(cross-event) 행동 패턴이며,
|
||
다음 세 가지 구성 요소를 담는다.
|
||
|
||
|
||
- **Circumstance**: 이 패턴이 관찰되는 도메인/주제/상황. Schema는 자신의 circumstance 안에 머물러야 한다 — 도메인을 넘어 일반화하지 마라. 도메인 간 추상화는 시스템이 따로 처리한다.
|
||
- **Pattern**: 이 circumstance에서 나타나는 사용자의 습관적 행동, 사고 방식, 또는 행동 경향.
|
||
- **Insight**: 그 바탕에 있는 심리적 동인 또는 정신 모형.
|
||
|
||
### content 형식
|
||
세 요소를 모두 결합한 한 문장을 사용하라.
|
||
"[circumstance]일 때, 사용자는 [pattern] — 이는 [insight]를 반영한다."
|
||
|
||
오래 지속되는 감정, 치료(therapy), 스트레스, 상실, 트라우마 관련 패턴에서는
|
||
구체적인 자기 서사적 배경을 일반적인 성격 라벨로 지워 버리지 마라.
|
||
다음을 보존하는 닻이 있는 구조를 선호하라.
|
||
"Trigger/배경 -> 반복되는 감정 반응 -> 관찰된 coping resource."
|
||
user-owned 증거에 존재하는 trigger, 반응, coping resource만 포함하라.
|
||
어시스턴트의 조언은 관찰된 사용자 coping resource가 아니다.
|
||
|
||
### 규칙
|
||
- **원자적**: Schema 하나에 패턴 하나. 서로 다른 두 패턴 → 두 개의 Schema.
|
||
- **불변**: 한 번 생성되면 content는 절대 바뀌지 않는다.
|
||
- **증거만**: 기존 표현을 단지 뒷받침하는 새 사실 →
|
||
`add_evidence`를 호출하라. 새 증거가 현재의 지속적 상태를 실질적으로 바꾸거나,
|
||
교정하거나, 좁힌다면 `revise_schema`를 사용해 이전 버전이 감사용으로는 남되
|
||
활성 검색에서는 제거되게 하라.
|
||
- **도메인 한정**: 관찰된 도메인 안에 머물러라. 도메인 간 추상화는 별도의 시스템 프로세스가 처리한다.
|
||
- **사실과 패턴을 구분**: 하나의 명시적인 정체성, 건강 상태, 장비 사용,
|
||
소속, 프로젝트, 또는 선호는 L6이 아니라 L2 상태 기억에 속한다.
|
||
L6에는 적어도 두 개의 독립된 raw 쓰기 또는 session을 대표하는 user-owned
|
||
증거가 필요하다. 병합된 하나의 L2 사실이 여러 쓰기를 대표할 수 있으므로,
|
||
fact 노드 수를 세지 말고 그 source_raw_memory_ids를 살펴보라.
|
||
- **주체 안전성**: 같은 source chunk를 공유하는 사실들은 다른 사람이나
|
||
예시를 기술할 수 있다. 자료가 그 패턴을 사용자에게 명시적으로 귀속시킬 때만
|
||
사용자 Schema를 생성하라. 그렇지 않으면 그 자료에 대해서는 아무 연산도 출력하지 마라.
|
||
- **후보 커버리지**: source-context 검토에서는 나열된 모든 후보 사실을 처리하라.
|
||
`create_schema`/`add_evidence`의 증거로 쓰거나, `skip_fact`를 출력하라.
|
||
raw context는 그 주체를 확인하거나 누락된 세부 사항을 복원할 수 있지만,
|
||
무관한 Schema의 씨앗이 되어서는 안 된다.
|
||
- **증거 기준**: L6 생성에는 적어도 두 개의 독립된 raw 쓰기 또는 session을
|
||
대표하는 하나 이상의 user-owned 증거 사실이 필요하다.
|
||
어시스턴트의 조언만으로는 사용자 패턴이 확립되지 않는다.
|
||
|
||
### 좋은 예 vs 나쁜 예
|
||
✅ "요리할 때, 사용자는 레시피 단계를 엄격히 따르고 재료를 정확히 계량한다 — 이는 불확실성을 다루기 위해 외부 구조가 필요하다는 점을 반영한다."
|
||
✅ "게임할 때, 사용자는 100% 달성을 위해 항상 공략을 확인한다 — 미완성에 대한 낮은 내성에서 비롯된다."
|
||
✅ "예기치 않게 멘토를 잃은 뒤, 죽음을 떠올리게 하는 계기가 슬픔을 갑작스럽게 되살릴 수 있다. 사용자는 멈춰 서서 돌아보고 조용히 산책하며 안정을 되찾는다."
|
||
❌ "사용자는 음악을 창의적 종합과 문화적 가교의 매체로 다루며, 다양한 음악 전통을 일관되게 섞는다..." (여러 패턴이 섞였고 너무 넓다)
|
||
❌ "사용자는 많은 것에 열정적이며 품질을 중시한다." (circumstance가 없고 너무 모호하다)
|
||
❌ "사용자는 의미 지향적이고 회복력이 있다." (지속적인 trigger와 관찰된 coping 증거를 지워 버린다)
|
||
|
||
## 작업 흐름
|
||
|
||
입력 자료(적어도 하나의 cluster가 있을 때만 호출된다):
|
||
- **Cluster Results**: 주 입력 — 의미 유사도로 묶인 사실들
|
||
- **Unclustered Facts (보충)**: 어떤 cluster에도 속하지 않은 사실들. cluster 결과보다 우선순위가 낮다
|
||
|
||
각 사실 그룹(clustered 또는 unclustered)에 대해:
|
||
1. **기존 Schema 검색**: 기존 Schema가 이미 이 패턴을 담고 있다면 `add_evidence`를 호출하라. 새 증거를 추가한 뒤 이전 표현이 실질적으로 불완전해지거나 오해를 낳게 될 때만 `revise_schema`를 사용하라.
|
||
2. **새 Schema 생성**: 이 패턴을 포괄하는 기존 Schema가 없을 때만. 한 문장, 세 구성 요소.
|
||
3. **관계 구축**: 두 Schema가 주제적으로 관련되어 있다면 `add_edge`(RELATED_TO)를 사용하라.
|
||
|
||
## 원칙
|
||
|
||
- 모든 Graph 연산은 반드시 tools를 통해야 한다
|
||
- Schema content는 불변이다 — 이미 존재하는 것을 절대 다시 만들지 마라
|
||
- 중복을 만드는 것보다 `add_evidence`를 우선하라
|
||
- 품질이 낮은 노드를 만드는 것보다 만들지 않는 쪽을 우선하라
|
||
- Schema 하나 = 하나의 도메인 안의 하나의 원자적 패턴
|
||
- 태그를 붙일 때는 기존 태그 목록의 태그를 재사용하는 쪽을 우선하라"""
|
||
|
||
|
||
# ================================================================
|
||
# Fragments assembled in code
|
||
# ================================================================
|
||
|
||
# Display name of this language, used in logs and docs.
|
||
LANGUAGE_NAME = "한국어"
|
||
|
||
# Appended to every memory-writing prompt ONLY when the caller set the
|
||
# language explicitly (per call or MemoryConfig.language). Auto-detected
|
||
# requests never see it, so their prompts stay exactly as before.
|
||
OUTPUT_LANGUAGE_DIRECTIVE = """
|
||
|
||
---
|
||
|
||
# 출력 언어
|
||
|
||
네가 출력하는 모든 자연어 값(기억 본문, 요약, 이유, 쿼리)은 대화나 기존 기억이 어떤 언어를 쓰더라도 한국어로 써라. 이 규칙은 입력 언어와 일치시키라는 위의 모든 규칙보다 우선한다. JSON 키, 필드 이름, 열거형 값, 연산 이름, ID, 코드, 파일 경로, 명령, 오류 메시지, 고유명사는 그대로 유지하라.
|
||
"""
|
||
|
||
# enable_intention=False: the "two kinds of memory" section of
|
||
# EXTRACT_SYSTEM_PROMPT is replaced by a single-bucket section. The pattern
|
||
# must match this language's EXTRACT_SYSTEM_PROMPT.
|
||
NO_INTENTION_SECTION_PATTERN = r"# 두 종류의 기억.*?(?=\n# OWNER\n)"
|
||
|
||
NO_INTENTION_MEMORY_SECTION = (
|
||
"# 기억\n"
|
||
"\n"
|
||
"너는 한 종류의 기억을 추출한다: **memory**.\n"
|
||
"\n"
|
||
"## memory\n"
|
||
"\n"
|
||
"사용자에 대해 — 그리고 대화에 등장하는 다른 이름 있는 사람에 대해 — 오래 유지되고\n"
|
||
"기억할 가치가 있는 모든 것은 여기에 넣는다.\n"
|
||
"\n"
|
||
"* 선호, 의견, 가치관, 태도\n"
|
||
"* 사건, 행동, 경험\n"
|
||
"* 습관, 행동 패턴\n"
|
||
"* 인간관계, 특성\n"
|
||
"* 객관적 사실, 결정, 제약 조건\n"
|
||
"* 진행 중인 활동\n"
|
||
"* 기억할 가치가 있는 **향후 계획 / 다가오는 행동**\n"
|
||
" (`memory`에 넣어라. 별도의 intentions 버킷을 만들어 내지 마라)\n"
|
||
"\n"
|
||
"강하게 시사되는 경우 암묵적인 선호를 반드시 추론해야 한다.\n"
|
||
"\n"
|
||
"\"사용자가 어떤 사람인가\"와 \"무슨 일이 있었는가\"를 나누지 마라.\n"
|
||
"둘 다 `memory`에 들어간다.\n"
|
||
"\n"
|
||
"---\n"
|
||
"\n"
|
||
)
|
||
|
||
# (old, new) pairs applied in order after the section swap.
|
||
NO_INTENTION_REPLACEMENTS = (
|
||
(
|
||
"모든 memory와 intention에는 그것이 어느 기억 저장소에 속하는지를 나타내는 `owner`가 반드시 있어야 한다.",
|
||
"모든 memory에는 그것이 어느 기억 저장소에 속하는지를 나타내는 `owner`가 반드시 있어야 한다.",
|
||
),
|
||
)
|
||
|
||
INTENTION_DISABLED_NOTE = (
|
||
"\n\n---\n\n# INTENTION 비활성화\n"
|
||
"intention 추출이 비활성화되어 있다. few-shot 예시에 "
|
||
"`intentions`가 나오더라도 그 키를 출력하지 **마라**. 향후 계획은 `memory`에 넣어라.\n"
|
||
)
|
||
|
||
# Coding / working extractor: wraps the history and the new messages.
|
||
CODING_CONVERSATION_BLOCK = (
|
||
"최근 사용자 5개 턴 (맥락용일 뿐이며, 다시 추출하지 마라):\n"
|
||
"{history}"
|
||
"\n\n새 메시지 (이번 완전한 쓰기에서만 추출하라):\n"
|
||
"{new}"
|
||
)
|
||
CODING_EMPTY_BLOCK = "(없음)"
|
||
|
||
# Name line at the top of a multi-person conversation. Also stored in L1_RAW,
|
||
# so pipelines.base parses every language's form back.
|
||
USER_NAME_LINE = "사용자의 이름은 {name}입니다"
|
||
|
||
# System 2 single-call mode: output contract appended to SYSTEM2_AGENT_PROMPT.
|
||
SYSTEM2_OUTPUT_INSTRUCTIONS = """
|
||
|
||
## 출력 형식
|
||
|
||
tools를 호출하지 마라. 연산들의 JSON 배열을 ```json 코드 블록으로 감싸 직접 출력하라.
|
||
|
||
사용 가능한 연산:
|
||
|
||
1. **create_schema** — 새 L6 행동 Schema 생성
|
||
```{"op": "create_schema", "schema_kind": "behavior_pattern", "content": "사용자에게 보여 줄 간결한 패턴", "retrieval_text": "행동과 관련 상황", "evidence_list": ["fact_node_id_1", "fact_node_id_2"], "tags": ["tag1"]}```
|
||
|
||
2. **add_evidence** — 기존 Schema에 증거 추가 (content는 불변으로 유지)
|
||
```{"op": "add_evidence", "node_id": "existing_schema_node_id", "evidence_list": ["new_fact_id_1"]}```
|
||
|
||
3. **revise_schema** — 새 증거가 현재 상태를 실질적으로 바꿀 때 새 버전을 만들고 이전 버전을 활성 검색에서 물러나게 한다. 양립 가능한 세부 사항 보완에는 사용하지 마라.
|
||
```{"op": "revise_schema", "node_id": "old_schema_id", "schema_kind": "explicit_state", "content": "갱신된 현재 상태", "retrieval_text": "갱신된 검색 텍스트", "evidence_list": ["new_fact_id_1"], "tags": ["tag1"], "reason": "이전 표현이 왜 오해를 낳는지"}```
|
||
|
||
4. **add_edge** — 두 Schema 사이의 관계 생성
|
||
```{"op": "add_edge", "source_id": "schema_id_1", "target_id": "schema_id_2", "reason": "관계 이유"}```
|
||
|
||
5. **skip_fact** — 후보 사실을 검토했지만 Schema를 만들지 않았음을 명시적으로 표시한다.
|
||
다른 사람에 관한 사실, 일시적인 사건, 사용자 특성을 확립하지 못하는 어시스턴트
|
||
조언, 또는 이미 완전히 포괄된 사실에만 사용하라.
|
||
```{"op": "skip_fact", "node_id": "candidate_fact_id", "reason": "지속적인 사용자 패턴이 아님"}```
|
||
|
||
## 출력 약속
|
||
|
||
1. 출력은 반드시 ```json 코드 블록으로 감싼 JSON 배열이어야 한다.
|
||
2. 데이터가 신뢰할 만한 결론을 내기에 부족하면 `[]`를 출력하라. source-context
|
||
검토에서 `[]`는 그 검토의 모든 후보를 살펴보았고 지속적인 Schema를 뒷받침하는
|
||
것이 하나도 없다는 뜻이다.
|
||
3. 코드 블록 밖에는 아무 텍스트도 없어야 한다.
|
||
4. evidence_list의 ID는 자료에 제공된 fact node_id에서 와야 한다.
|
||
5. add_evidence / revise_schema의 node_id는 기존 Graph 노드 ID에서 와야 한다.
|
||
6. source-context 검토에서는 모든 후보 사실이 evidence_list 또는 skip_fact에 나타나야 한다."""
|
||
|
||
# Appended to SYSTEM2_OUTPUT_INSTRUCTIONS when the schema evidence gate is on.
|
||
SYSTEM2_SCHEMA_EVIDENCE_GATE = """
|
||
|
||
7. L6은 사건 간 행동 패턴만 표현한다. create_schema는 적어도 두 개의 독립된 raw 쓰기 또는 session을 대표하는 user-owned 증거를 사용해야 한다. source_raw_memory_ids가 여러 개의 독립된 쓰기를 담고 있다면 병합된 하나의 사실도 기준을 충족할 수 있다.
|
||
8. 하나의 명시적인 정체성, 건강 상태, 장비 사용, 소속, 프로젝트, 또는 선호에 대해서는 L6을 만들지 마라. 그런 사실은 canonical L2 상태 투영이 담당한다."""
|
||
|
||
# (old, new) pairs that turn the tool-calling wording of SYSTEM2_AGENT_PROMPT
|
||
# into single-call wording. Each `old` must occur in this language's prompt
|
||
# (or be harmless if it does not).
|
||
SYSTEM2_SINGLE_CALL_REPLACEMENTS = (
|
||
("- 모든 Graph 연산은 반드시 tools를 통해야 한다\n", ""),
|
||
("`add_evidence`를 호출하라", "add_evidence 연산을 사용하라"),
|
||
("`add_edge`(RELATED_TO)를 사용하라", "add_edge 연산을 사용하라"),
|
||
)
|
||
|
||
# System 2 user message (materials). Keys are fixed; values are format
|
||
# strings with the named fields shown.
|
||
SYSTEM2_MATERIALS = {
|
||
"title": "## 전처리된 자료\n",
|
||
"stats": (
|
||
"### 통계\n- VDB 사실 총수: {total}\n"
|
||
"- 새로운 사실(처리 대상): {fresh} (evidenced_excluded={evidenced})\n"
|
||
"- 발견된 cluster 수: {clusters}\n"
|
||
"- cluster 내 사실: {clustered}, unclustered 보충: {unclustered}\n"
|
||
"- Graph Schema 총수: {graph_total} (recalled={recalled})"
|
||
),
|
||
"graph_status_empty": "- Graph 상태: **비어 있음** (첫 인지 처리 실행)\n",
|
||
"clusters_header": "### Cluster 결과\n",
|
||
"label_holistic": "{n}개의 세션 간 사용자 사실. 증거로 뒷받침되는 지속적 감정/치료 패턴만",
|
||
"label_source_context": "{n}개의 후보 사실. raw 쓰기는 주체/세부 사항 검증에만 사용",
|
||
"label_topic": "{n}개 사실, 주제",
|
||
"raw_label": "후보별 관련 raw 발췌",
|
||
"holistic_note": (
|
||
"이것은 경계가 정해진 한 번의 야간 전체 통합 처리다. anchored therapy Schema를 "
|
||
"최대 {max_schemas}개까지 생성하라. "
|
||
"서로 다른 raw 쓰기/session에 걸쳐 지속적인 trigger/배경, 반복되는 감정 반응, "
|
||
"그리고 관찰된 coping resource 또는 결정 제약을 결합하는 쪽을 선호하라. "
|
||
"create_schema 전에 해당 증거 ID들의 raw_sources를 합집합으로 모아 서로 다른 raw source ID가 "
|
||
"적어도 두 개인지 확인하라. 하나의 raw 쓰기에서 나온 두 사실은 하나의 사건이다. "
|
||
"증거가 부족하면 []를 출력하라.\n"
|
||
),
|
||
"companion_header": "**독립 출처 companion 힌트 (결론이 아니라 후보일 뿐):**",
|
||
"unclustered_header": "### Unclustered 사실 (보충)\n",
|
||
"unclustered_hint": "아래 사실들은 위의 어떤 cluster에도 묶이지 않았다. cluster 주제를 처리한 뒤 살펴보라. cluster 결과보다 우선순위가 낮다.\n",
|
||
"forward_header": "### 관련 Graph 노드 (cluster centroid 검색으로 검색됨, {count}건)\n",
|
||
"graph_empty": "### 기존 Graph 노드\n현재 Graph는 비어 있으며, 이번이 첫 인지 처리 실행이다. Graph를 검색하지 마라(아무것도 반환되지 않는다). 곧바로 cluster 결과로부터 Schema 생성으로 진행하라.\n",
|
||
"graph_no_recall": (
|
||
"### 기존 Graph 노드\n"
|
||
"Graph에는 {graph_total}개의 Schema가 있지만, "
|
||
"centroid 기반 벡터 검색에서 일치하는 것이 없었다. cluster 결과에 근거해 새 Schema가 필요한지 판단하라.\n"
|
||
),
|
||
"reverse_header": "### Graph 역참조 (기존 Schema가 참조하는 VDB 사실)\n",
|
||
"tags_header": "### 기존 태그 (동의어를 새로 만들기보다 이 중에서 재사용하는 쪽을 우선하라)\n",
|
||
"closing": (
|
||
"위 자료를 분석하여 연산들의 JSON 배열을 출력하라. "
|
||
"새 Schema를 생성할지, 기존 Schema에 증거를 추가할지 판단하라. "
|
||
"데이터가 신뢰할 만한 결론을 내기에 부족하면 빈 배열 `[]`를 출력하라. "
|
||
"태그를 붙일 때는 위의 기존 태그 목록에 있는 태그를 재사용하는 쪽을 우선하라."
|
||
),
|
||
}
|