테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

에이전트 메모리와 드리밍(dreaming)

AI 에이전트가 세션을 넘어 배우고, 잠든 사이에 스스로 기억을 다듬게 하는 컨텍스트 엔지니어링 이야기다.

목차 (6)
  1. 왜 컨텍스트인가
  2. 컨텍스트 엔지니어링의 변천 (최근 1년)
  3. 운영에서 필요한 가드레일
  4. 드리밍(dreaming): 세션 밖에서 배우기
  5. 확장 단계
  6. 직접 구현: Codex·Claude Code와 launchd로 로컬 드리밍 만들기
드리밍: 기록 수집, 분석 에이전트, 오케스트레이터, 변경 제안, 사람 승인을 거쳐 메모리가 고쳐지고 다음 세션이 읽는다기록 수집대화·도구 호출분석 에이전트들나눠 읽기오케스트레이터자주 나온 문제변경 제안근거·빈도 첨부사람 승인받거나 거절메모리(문서)가 고쳐지고, 다음 세션이 읽는다세션 밖에서 주기적으로 (예: 밤마다)모델 가중치는 그대로 — 바뀌는 것은 건네는 컨텍스트다
드리밍 흐름: 세션 밖에서 기록을 모아 분석하고, 근거를 붙인 변경을 사람이 승인하면 다음 세션이 그 메모리를 읽는다.

먼저 짚을 점: “밤새 학습한다”는 말은 모델의 가중치(weights)가 바뀐다는 뜻이 아니다. 에이전트가 남긴 기록을 읽고 정리해 다음 세션이 읽을 메모리(문서)를 고치는 것이다. 모델은 그대로이고, 모델에게 건네는 컨텍스트가 좋아진다.

왜 컨텍스트인가

모델이 똑똑해진다고 실제로 배포된 에이전트가 똑똑해지지는 않는다. 에이전트에게는 조직·코드베이스·사용자 선호처럼 그곳에서만 통하는 지식이 필요하고, 발표자는 이 컨텍스트가 모델 지능을 곱해 준다고 말한다. 컨텍스트가 없을 때 생기는 문제는 세 가지다.

  • 코드베이스를 어떻게 다니는지, 사용자가 무엇을 좋아하는지 모른다.
  • 같은 일을 반복해도 나아지지 않는다.
  • 지난 실수에서 배우지 못한다.

컨텍스트 엔지니어링의 변천 (최근 1년)

단계 방식 좋은 점 한계
CLAUDE.md 세션 시작 때 마크다운 파일 하나를 통째로 넣는다 단순하고 효과가 크다 파일이 커질수록 컨텍스트 창을 압박한다
메모리 도구 에이전트가 세션 중에 직접 기억을 읽고·쓰고·고친다 (in-band) 에이전트가 스스로 기억을 관리한다 본 작업과 기억 관리 사이에서 주의가 갈린다
Skills 짧은 요약(front matter)만 보이다가 필요할 때 전체를 불러온다 점진적 공개(progressive disclosure)로 컨텍스트가 붓지 않는다 절차형 지식에 알맞다
파일 시스템 메모리 마크다운 파일 묶음을 bash·grep 같은 표준 도구로 찾아 읽는다 사람이 읽기 쉽고, 자율적이며, 점진적 공개까지 된다 현재의 모범 사례로 소개된다

발표의 한 줄 요약: “Do the simple thing that works. Markdown is great for reading memories.”

운영에서 필요한 가드레일

  • 버전 관리 — 변경마다 계기가 된 세션(transcript), 바꾼 주체(에이전트·사람), 전후 상태의 해시를 남긴다. 되돌리기와 감사 기록이 된다.
  • 동시성 — 여러 에이전트가 같은 메모리를 쓸 때 해시로 충돌을 막는다.
  • 권한 계층 — 조직 전체 지식(읽기 전용) → 팀·분야 메모리 → 에이전트 개인 작업 공간(읽기·쓰기). 민감한 변경은 사람이 검토한다.
  • 이식성 — 깔끔한 API로 만들어 한 도구에 갇히지 않게 한다.

해시 기반 커밋 절차

  1. 지금 메모리 상태의 해시를 잰다.
  2. 고칠 내용을 작성한다.
  3. 커밋 직전에 해시를 다시 잰다.
  4. 같으면 커밋한다.
  5. 다르면 다른 누군가가 먼저 고친 것이므로 다시 읽고, 다시 작성하고, 다시 시도한다.

효과로는 반복 작업 정확도 향상, 토큰·지연 감소(한 번에 끝내는 비율 증가), 개발자 시간 절약이 소개된다.

드리밍(dreaming): 세션 밖에서 배우기

세션 안에서 기억을 고치는 방식(in-band)은 에이전트의 주의를 나누고, 자기 세션만 볼 수 있어 여러 세션에 걸친 문제를 놓친다. 드리밍은 이것을 세션 밖(out-of-band)의 비동기 배치 작업으로 옮긴다. 따로 자원을 받고, 모든 세션의 기록을 본다. 에이전트의 답만이 아니라 도구 호출과 메타데이터까지 살핀다.

흐름

  1. 권한을 지키며 메모리와 에이전트 기록(transcript)을 모은다.
  2. 여러 분석 에이전트가 기록을 나눠 읽는다.
  3. 실패·빈틈·설정 오류 같은 패턴을 찾는다.
  4. 오케스트레이터가 결과를 모아 널리 퍼진 문제를 고른다.
  5. 근거와 빈도 통계를 붙여 메모리 변경을 제안한다.
  6. 사람이 변경마다 승인하거나 거절한다.

발표 속 예시

  • 모든 학생이 지리 문제 하나를 틀린다 → 커리큘럼에 그 주제가 빠진 것을 찾아 추가한다.
  • 수학 답이 모두 도(degree)가 아니라 라디안으로 나온다 → 도구 설정 오류를 찾아 고치자고 제안한다.
  • 전체 에이전트가 대시(—)를 지나치게 쓴다 → 조직 전체 컨텍스트를 바꾸자고 제안한다.

드리밍에 드는 추가 토큰은 한 번에 끝내는 비율이 오르고 전체 토큰 사용이 줄면서 상쇄된다고 한다. 아직 아주 새로운 연구 분야이고, 코딩뿐 아니라 발표 자료·고객 응대·분야 전문 지식에도 쓰인다.

확장 단계

단계 할 일
단순 CLAUDE.md(또는 마크다운 메모리), Skills, 에이전트의 자율 메모리
규모 버전 관리, 해시 기반 동시성, 권한 계층 추가
최적화 주기적인 드리밍으로 전체 에이전트가 함께 배우게 한다

직접 구현: Codex·Claude Code와 launchd로 로컬 드리밍 만들기

아래 구현은 2026년 10월의 Codex CLI, Claude Code, macOS launchd를 기준으로 한다. runner 하나와 skill 하나를 두 에이전트가 함께 쓰고, 맨 위의 agent 값만 codex나 claude로 바꾼다.

두 도구 모두 예약 기능이 따로 있지만 이 일에는 맞지 않는다.

방법 동작 이 일에 맞지 않는 이유
Codex 로컬 scheduled task 앱이 정해진 시각에 task를 실행한다 컴퓨터와 앱이 켜져 있어야 한다
Codex 웹 task · Claude Code 클라우드 예약 클라우드에서 정해진 시각에 실행한다 컴퓨터가 꺼져 있어도 돌지만 로컬 파일과 session 기록을 읽을 수 없다
Claude Code /loop 열려 있는 session 안에서 일정 간격으로 반복한다 터미널을 닫으면 멈춘다
launchd + 비대화형 실행 macOS가 runner를 깨우고 runner가 codex exec나 claude -p를 부른다 로컬 session 기록과 여러 Git 저장소를 읽을 수 있다

정확히 02:30에만 예약하면 그때 컴퓨터가 꺼져 있던 날은 건너뛴다. 그래서 로그인할 때와 한 시간마다 runner를 깨운다.

  • runner는 02:30 전이거나 오늘 이미 성공했다면 바로 끝난다.
  • 실패하면 상태를 갱신하지 않아 다음 시간에 다시 시도한다.
  • 며칠 동안 컴퓨터가 꺼져 있었다면, 다음 로그인 뒤 마지막 성공 시점부터 지금까지를 한 번에 분석한다.

파일 구성

~/.dreaming/
├── run-nightly-dreaming.sh      # runner (codex·claude 공용)
├── skills/nightly-dreaming/
│   └── SKILL.md                 # 관찰 범위와 금지 작업
├── logs/
├── reports/
└── state/last-success

~/.codex/skills/nightly-dreaming   → ~/.dreaming/skills/nightly-dreaming  (심볼릭 링크)
~/.claude/skills/nightly-dreaming  → ~/.dreaming/skills/nightly-dreaming  (심볼릭 링크)

~/Library/LaunchAgents/io.example.nightly-dreaming.plist

역할은 셋으로 나뉜다.

  • skill은 관찰 범위와 금지 작업을 정한다.
  • runner는 실행 시점과 성공 상태를 관리한다.
  • LaunchAgent는 runner를 깨우기만 한다.

skill은 두 도구가 함께 따르는 Agent Skills 형식(SKILL.md + front matter)이라 파일 하나를 링크로 공유한다.

mkdir -p "${HOME}/.dreaming/skills/nightly-dreaming" "${HOME}/.codex/skills" "${HOME}/.claude/skills"
ln -s "${HOME}/.dreaming/skills/nightly-dreaming" "${HOME}/.codex/skills/nightly-dreaming"
ln -s "${HOME}/.dreaming/skills/nightly-dreaming" "${HOME}/.claude/skills/nightly-dreaming"

한쪽 에이전트만 쓴다면 그쪽 링크만 만든다.

읽기 전용 skill

~/.dreaming/skills/nightly-dreaming/SKILL.md를 만든다.

---
name: nightly-dreaming
description: Analyze recent agent sessions and Git activity to propose evidence-backed memory and documentation improvements. Do not apply proposals automatically.
---

# Nightly Dreaming

최근 작업에서 다음 세션에 재사용할 교훈을 찾는다. 모델이 학습했다고 표현하지 않는다.
결과는 사람이 검토할 컨텍스트 개선 제안이다.

## Source scope

- 마지막 성공 시점부터 현재까지의 에이전트 session 기록과 workspace의 Git 활동을 조사한다.
  - Codex: ~/.codex/sessions/
  - Claude Code: ~/.claude/projects/
- 마지막 성공 시점이 없을 때만 최근 24시간을 조사한다.
- secret, credential, 환경 변수 값과 Git에서 제외된 파일 내용은 읽거나 출력하지 않는다.

## Selection criteria

- 사용자가 명시적으로 바로잡은 행동이나 규칙
- 두 번 이상 반복된 실패, 우회 또는 재작업
- 여러 프로젝트에 영향을 주는 계약이나 작업 순서
- 재발하면 데이터 손실, 배포 오류 또는 큰 시간 낭비를 일으키는 사건

기존 문서와 skill을 검색해 중복 제안을 만들지 않는다.

## Output

한국어 Markdown 보고서만 반환한다.

- 분석 시작·종료 시각과 조사 범위
- 작업 흐름과 확인된 교훈
- 관찰, 원인, 재발 방지, 근거와 확신도
- 문서 또는 skill의 최소 변경 제안
- 근거가 부족해 보류한 항목

## Hard boundaries

- 파일, Git 상태, schedule, 외부 시스템을 변경하지 않는다.
- build, test, formatter처럼 생성물을 남길 수 있는 명령을 실행하지 않는다.
- commit, push 또는 PR을 만들지 않는다.
- 제안을 자동으로 적용하지 않는다.

지연 실행 runner

~/.dreaming/run-nightly-dreaming.sh를 만든다. 다음 세 가지 실행 방식을 지원한다.

  • 기본 예약 실행
  • 설정 검사 --check
  • 예약 조건을 무시하는 수동 실행 --force

맨 위 세 줄을 실제 환경에 맞게 바꾼다.

  • agent: 쓸 에이전트(codex 또는 claude)
  • workspace_directory: 분석할 workspace의 절대 경로
  • agent_executable: 그 에이전트 실행 파일의 절대 경로(command -v codex나 command -v claude로 확인)
#!/bin/sh

set -u

readonly agent='codex'
readonly workspace_directory='/absolute/path/to/workspace'
readonly agent_executable='/absolute/path/to/codex'

readonly dreaming_directory="${HOME}/.dreaming"
readonly report_directory="${dreaming_directory}/reports"
readonly log_directory="${dreaming_directory}/logs"
readonly state_directory="${dreaming_directory}/state"
readonly last_success_path="${state_directory}/last-success"

umask 077

validate_runtime() {
    case "${agent}" in
        codex | claude)
            ;;
        *)
            printf 'Unknown agent: %s (use codex or claude)\n' "${agent}" >&2
            return 1
            ;;
    esac

    if [ ! -x "${agent_executable}" ]; then
        printf 'Agent executable is unavailable: %s\n' "${agent_executable}" >&2
        return 1
    fi

    if [ ! -d "${workspace_directory}/.git" ]; then
        printf 'Workspace is unavailable: %s\n' "${workspace_directory}" >&2
        return 1
    fi

    mkdir -p "${report_directory}" "${log_directory}" "${state_directory}"
    chmod 700 \
        "${dreaming_directory}" \
        "${report_directory}" \
        "${log_directory}" \
        "${state_directory}"
}

is_before_daily_start() {
    current_hour=$(date '+%H')
    current_minute=$(date '+%M')
    current_hour=${current_hour#0}
    current_minute=${current_minute#0}

    [ "${current_hour}" -lt 2 ] || \
        { [ "${current_hour}" -eq 2 ] && [ "${current_minute}" -lt 30 ]; }
}

read_last_success() {
    last_success=''

    if [ -f "${last_success_path}" ]; then
        IFS= read -r last_success < "${last_success_path}" || true
    fi
}

should_run_today() {
    if [ "${force_run}" = 'true' ]; then
        return 0
    fi

    if is_before_daily_start; then
        return 1
    fi

    current_date=$(date '+%Y-%m-%d')
    last_success_date=${last_success%%T*}
    [ -z "${last_success}" ] || [ "${last_success_date}" != "${current_date}" ]
}

# 에이전트마다 읽기 전용 비대화형 실행 방법이 다르다. 둘 다 최종 답만 첫 인자의 파일에 남긴다.
run_agent() {
    agent_output_path=$1
    agent_prompt=$2

    case "${agent}" in
        codex)
            "${agent_executable}" exec \
                --cd "${workspace_directory}" \
                --sandbox read-only \
                --ephemeral \
                --color never \
                --output-last-message "${agent_output_path}" \
                "${agent_prompt}" \
                > /dev/null
            ;;
        claude)
            cd "${workspace_directory}" || return 1
            "${agent_executable}" -p \
                --tools Read Grep Glob \
                --strict-mcp-config \
                --permission-mode dontAsk \
                --add-dir "${HOME}/.claude/projects" "${log_directory}" \
                --no-session-persistence \
                --output-format text \
                "${agent_prompt}" \
                > "${agent_output_path}"
            ;;
    esac
}

force_run='false'

case "${1:-}" in
    '')
        ;;
    --check)
        validate_runtime || exit 1
        printf 'Nightly dreaming runtime is ready (%s).\n' "${agent}"
        exit 0
        ;;
    --force)
        force_run='true'
        ;;
    *)
        printf 'Usage: %s [--check|--force]\n' "$0" >&2
        exit 64
        ;;
esac

validate_runtime || exit 1
read_last_success

if ! should_run_today; then
    exit 0
fi

readonly run_timestamp=$(date '+%Y-%m-%dT%H-%M-%S')
readonly report_path="${report_directory}/${run_timestamp}.md"
readonly temporary_report_path="${report_directory}/.${run_timestamp}.tmp"
readonly log_path="${log_directory}/${run_timestamp}.log"

if [ -n "${last_success}" ]; then
    analysis_window="from ${last_success} through the current time"
    git_since=${last_success}
else
    analysis_window='for the last 24 hours'
    git_since='24 hours ago'
fi

# Claude에게는 Bash를 주지 않으므로, 분석 구간의 Git 기록을 runner가 미리 파일로 뽑아 둔다
readonly git_activity_path="${log_directory}/${run_timestamp}.git.txt"
git -C "${workspace_directory}" log --all --since="${git_since}" --date=iso --stat \
    > "${git_activity_path}" 2>&1
chmod 600 "${git_activity_path}"

if [ "${agent}" = 'codex' ]; then
    skill_reference='$nightly-dreaming'
else
    skill_reference='the nightly-dreaming skill'
fi

readonly prompt="Use ${skill_reference} to analyze work ${analysis_window}. Git activity for this window is in ${git_activity_path}. Return the Korean Markdown report only. Do not modify files, Git state, schedules, external systems, or memory."

cleanup() {
    rm -f "${temporary_report_path}"
}

trap cleanup EXIT HUP INT TERM

if ! run_agent "${temporary_report_path}" "${prompt}" 2> "${log_path}"; then
    printf 'Nightly dreaming failed. See %s\n' "${log_path}" >&2
    exit 1
fi

if [ ! -s "${temporary_report_path}" ]; then
    printf 'Nightly dreaming produced an empty report.\n' >&2
    exit 1
fi

mv "${temporary_report_path}" "${report_path}"
completion_timestamp=$(date '+%Y-%m-%dT%H:%M:%S%z')
temporary_state_path="${state_directory}/.last-success.$$"
printf '%s\n' "${completion_timestamp}" > "${temporary_state_path}"
chmod 600 "${temporary_state_path}"
mv "${temporary_state_path}" "${last_success_path}"
chmod 600 "${report_path}" "${log_path}"
trap - EXIT HUP INT TERM

printf 'Nightly dreaming report: %s\n' "${report_path}"

두 에이전트 모두 대화형 화면 없이 실행하는 공식 경로를 쓴다. 읽기 전용으로 묶는 방법은 서로 다르다.

Codex (codex exec) Claude Code (claude -p)
읽기 전용 --sandbox read-only: OS 샌드박스가 쓰기를 막는다 --tools Read Grep Glob으로 쓰기·실행 도구를 아예 주지 않고, --strict-mcp-config로 MCP 도구도 뺀다. --permission-mode dontAsk는 묻는 창 없이 거절한다
Git 기록 샌드박스 안에서 git log를 직접 실행한다 Bash가 없으므로 runner가 미리 뽑은 logs/*.git.txt를 읽는다
session 기록 읽기 샌드박스가 읽기는 허락한다 --add-dir로 ~/.claude/projects와 logs 폴더를 읽을 수 있게 연다
이번 실행을 기록에 남기지 않기 --ephemeral --no-session-persistence
최종 답 받기 --output-last-message 파일 표준 출력(--output-format text)
skill 부르기 프롬프트에 $nightly-dreaming 프롬프트에 skill 이름. description을 보고 불러온다

Claude Code에서 --allowedTools로 Bash(git log *)만 허락하는 방법은 쓰지 않는다. 이 목록은 사용자 설정(~/.claude/settings.json)의 허용 규칙과 합쳐져서, 설정에 Bash(*) 같은 넓은 규칙이 있으면 파일 쓰기까지 통과한다. 실제로 그런 설정에서 echo hi > probe.txt가 실행됐다. 도구 자체를 빼면 설정과 상관없이 쓸 수 없다.

이번 실행을 기록에 남기지 않는 이유가 있다. 남기면 다음 날 드리밍이 자기 보고서 작업까지 분석 대상으로 읽는다. 모델은 최종 답만 임시 파일에 쓰고, runner는 성공한 뒤에만 보고서와 상태를 옮긴다.

LaunchAgent

~/Library/LaunchAgents/io.example.nightly-dreaming.plist를 만든다. launchd는 ProgramArguments의 $HOME이나 ~를 확장하지 않는다. 그래서 모든 __HOME__을 실제 home의 절대 경로로 바꾼다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>io.example.nightly-dreaming</string>
    <key>ProgramArguments</key>
    <array>
        <string>__HOME__/.dreaming/run-nightly-dreaming.sh</string>
    </array>
    <key>EnvironmentVariables</key>
    <dict>
        <key>HOME</key>
        <string>__HOME__</string>
        <key>PATH</key>
        <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>
    <key>RunAtLoad</key>
    <true/>
    <key>StartInterval</key>
    <integer>3600</integer>
    <key>ProcessType</key>
    <string>Background</string>
    <key>LowPriorityIO</key>
    <true/>
    <key>Nice</key>
    <integer>10</integer>
    <key>StandardOutPath</key>
    <string>__HOME__/.dreaming/logs/launchd.stdout.log</string>
    <key>StandardErrorPath</key>
    <string>__HOME__/.dreaming/logs/launchd.stderr.log</string>
</dict>
</plist>

검증하고 등록한다.

chmod 700 "${HOME}/.dreaming/run-nightly-dreaming.sh"
"${HOME}/.dreaming/run-nightly-dreaming.sh" --check
plutil -lint "${HOME}/Library/LaunchAgents/io.example.nightly-dreaming.plist"
launchctl bootstrap \
    "gui/$(id -u)" \
    "${HOME}/Library/LaunchAgents/io.example.nightly-dreaming.plist"
launchctl enable "gui/$(id -u)/io.example.nightly-dreaming"
launchctl print "gui/$(id -u)/io.example.nightly-dreaming"

설정을 바꿨다면 launchctl bootout 후 다시 bootstrap한다. 바로 시험할 때만 --force를 쓴다. Claude Code는 처음 claude -p를 돌리기 전에 터미널에서 한 번 로그인해 두어야 launchd 실행에서도 인증이 된다.

macOS가 아니라면 같은 runner를 그대로 두고 깨우는 쪽만 바꾼다.

  • Linux: systemd user timer(Persistent=true면 꺼져 있던 동안 놓친 실행을 켠 뒤 한 번 돌린다)
  • Windows: 작업 스케줄러의 “예약된 시간을 놓친 경우 가능한 빨리 실행”

운영 안전장치

  • 보고서에는 근거와 제안만 남긴다. 문서·skill·Git을 자동 수정하거나 commit하지 않는다.
  • .env, token, credential, 개인 정보는 읽거나 보고서에 싣지 않는다.
  • 마지막 성공 시각은 성공한 뒤에만 바꾼다. 실패를 성공으로 기록하면 분석 구간이 사라진다.
  • 보고서와 로그는 umask 077과 mode 600으로 보호한다.
  • 아침에 사람이 근거와 중복 여부를 확인하고, 필요한 제안만 별도 작업으로 반영한다.