세션 관리

OpenClaw은 에이전트당 하나의 직접 대화 세션을 기본으로 취급합니다. 직접 대화는 agent:<agentId>:<mainKey> (기본값 main)로 합쳐지고, 그룹/채널 대화는 자체 키를 갖습니다. session.mainKey가 적용됩니다.

session.dmScope를 사용하여 다이렉트 메시지의 그룹화 방식을 제어합니다:

  • main (기본값): 모든 DM이 연속성을 위해 메인 세션을 공유합니다.
  • per-peer: 채널에 상관없이 발신자 ID별로 격리합니다.
  • per-channel-peer: 채널 + 발신자별로 격리합니다 (다중 사용자 인박스에 권장).
  • per-account-channel-peer: 계정 + 채널 + 발신자별로 격리합니다 (다중 계정 인박스에 권장). session.identityLinks를 사용하여 프로바이더 접두사가 붙은 피어 ID를 정규 ID에 매핑하면, per-peer, per-channel-peer, per-account-channel-peer 사용 시 동일인이 채널 간에 DM 세션을 공유할 수 있습니다.

보안 DM 모드 (다중 사용자 설정에 권장)

보안 경고: 에이전트가 여러 사람으로부터 DM을 받을 수 있다면 보안 DM 모드 활성화를 강력히 고려해야 합니다. 활성화하지 않으면 모든 사용자가 동일한 대화 컨텍스트를 공유하여 사용자 간에 개인 정보가 유출될 수 있습니다.

기본 설정의 문제 예시:

  • Alice (<SENDER_A>)가 에이전트에게 개인적인 주제(예: 진료 예약)에 대해 메시지를 보냄
  • Bob (<SENDER_B>)이 에이전트에게 “우리 무슨 얘기하고 있었지?”라고 메시지를 보냄
  • 두 DM이 같은 세션을 공유하기 때문에 모델이 Alice의 이전 컨텍스트를 사용하여 Bob에게 답할 수 있음

해결 방법: dmScope를 설정하여 사용자별로 세션을 격리하세요:

// ~/.openclaw/openclaw.json
{
  session: {
    // 보안 DM 모드: 채널 + 발신자별로 DM 컨텍스트 격리.
    dmScope: "per-channel-peer",
  },
}

활성화해야 하는 경우:

  • 둘 이상의 발신자에 대해 페어링 승인이 있는 경우
  • 여러 항목이 있는 DM 허용 목록을 사용하는 경우
  • dmPolicy: "open"을 설정한 경우
  • 여러 전화번호 또는 계정이 에이전트에게 메시지를 보낼 수 있는 경우

참고:

  • 기본값은 연속성을 위한 dmScope: "main"(모든 DM이 메인 세션 공유)입니다. 단일 사용자 설정에 적합합니다.
  • 로컬 CLI 온보딩은 미설정 시 기본적으로 session.dmScope: "per-channel-peer"를 기록합니다(기존 명시적 값은 유지됨).
  • 동일 채널의 다중 계정 인박스에는 per-account-channel-peer를 권장합니다.
  • 같은 사람이 여러 채널로 연락하는 경우 session.identityLinks를 사용하여 DM 세션을 하나의 정규 ID로 통합하세요.
  • openclaw security audit로 DM 설정을 확인할 수 있습니다(보안 참고).

게이트웨이가 신뢰할 수 있는 단일 출처

모든 세션 상태는 게이트웨이(“마스터” OpenClaw)가 소유합니다. UI 클라이언트(macOS 앱, WebChat 등)는 로컬 파일을 읽는 대신 게이트웨이에서 세션 목록과 토큰 수를 조회해야 합니다.

  • 원격 모드에서는 관심 대상인 세션 저장소가 Mac이 아닌 원격 게이트웨이 호스트에 있습니다.
  • UI에 표시되는 토큰 수는 게이트웨이 저장소 필드(inputTokens, outputTokens, totalTokens, contextTokens)에서 가져옵니다. 클라이언트는 JSONL 대화 기록을 파싱하여 합계를 “수정”하지 않습니다.

상태 저장 위치

  • 게이트웨이 호스트:
    • 저장소 파일: ~/.openclaw/agents/<agentId>/sessions/sessions.json (에이전트별).
  • 대화 기록: ~/.openclaw/agents/<agentId>/sessions/<SessionId>.jsonl (Telegram 토픽 세션은 .../<SessionId>-topic-<threadId>.jsonl 사용).
  • 저장소는 sessionKey -> { sessionId, updatedAt, ... } 맵입니다. 항목 삭제는 안전하며 다음 메시지에서 재생성됩니다.
  • 그룹 항목에는 UI에서 세션에 레이블을 지정하기 위한 displayName, channel, subject, room, space가 포함될 수 있습니다.
  • 세션 항목에는 UI가 세션의 출처를 설명할 수 있도록 origin 메타데이터(레이블 + 라우팅 힌트)가 포함됩니다.
  • OpenClaw은 레거시 Pi/Tau 세션 폴더를 읽지 않습니다.

유지보수

OpenClaw은 sessions.json과 대화 기록 아티팩트를 시간이 지나도 제한된 크기로 유지하기 위해 세션 저장소 유지보수를 적용합니다.

기본값

  • session.maintenance.mode: warn
  • session.maintenance.pruneAfter: 30d
  • session.maintenance.maxEntries: 500
  • session.maintenance.rotateBytes: 10mb
  • session.maintenance.resetArchiveRetention: pruneAfter를 기본값으로 사용 (30d)
  • session.maintenance.maxDiskBytes: 미설정 (비활성화)
  • session.maintenance.highWaterBytes: 예산 관리가 활성화된 경우 maxDiskBytes80%를 기본값으로 사용

작동 방식

유지보수는 세션 저장소 쓰기 시 실행되며, openclaw sessions cleanup으로 수동 트리거할 수도 있습니다.

  • mode: "warn": 제거 대상을 보고하지만 항목/대화 기록을 변경하지 않습니다.
  • mode: "enforce": 다음 순서로 정리를 적용합니다:
    1. pruneAfter보다 오래된 만료 항목 제거
    2. 항목 수를 maxEntries로 제한 (오래된 것부터)
    3. 더 이상 참조되지 않는 제거된 항목의 대화 기록 파일 보관
    4. 보존 정책에 따라 오래된 *.deleted.<timestamp>*.reset.<timestamp> 보관 파일 삭제
    5. sessions.jsonrotateBytes를 초과하면 로테이션
    6. maxDiskBytes가 설정된 경우 highWaterBytes를 향해 디스크 예산 적용 (가장 오래된 아티팩트부터, 그 다음 가장 오래된 세션)

대규모 저장소의 성능 주의사항

대용량 세션 저장소는 고트래픽 환경에서 흔합니다. 유지보수 작업은 쓰기 경로에서 수행되므로 매우 큰 저장소는 쓰기 지연을 증가시킬 수 있습니다.

비용 증가 요인:

  • session.maintenance.maxEntries 값이 매우 높은 경우
  • 만료된 항목을 유지하는 긴 pruneAfter 기간
  • ~/.openclaw/agents/<agentId>/sessions/에 많은 대화 기록/보관 아티팩트가 있는 경우
  • 합리적인 프루닝/제한 없이 디스크 예산(maxDiskBytes)을 활성화한 경우

권장 사항:

  • 프로덕션에서는 mode: "enforce"를 사용하여 증가를 자동으로 제한
  • 시간과 수 제한(pruneAfter + maxEntries) 모두 설정(하나만이 아닌)
  • 대규모 배포에서는 maxDiskBytes + highWaterBytes로 하드 상한 설정
  • highWaterBytesmaxDiskBytes보다 의미 있게 낮게 유지 (기본값 80%)
  • 설정 변경 후 적용 전 openclaw sessions cleanup --dry-run --json을 실행하여 예상 영향 확인
  • 자주 활성화되는 세션의 경우 수동 정리 시 --active-key 전달

사용자 정의 예시

보수적인 적용 정책:

{
  session: {
    maintenance: {
      mode: "enforce",
      pruneAfter: "45d",
      maxEntries: 800,
      rotateBytes: "20mb",
      resetArchiveRetention: "14d",
    },
  },
}

세션 디렉터리에 하드 디스크 예산 활성화:

{
  session: {
    maintenance: {
      mode: "enforce",
      maxDiskBytes: "1gb",
      highWaterBytes: "800mb",
    },
  },
}

대규모 설치 최적화 (예시):

{
  session: {
    maintenance: {
      mode: "enforce",
      pruneAfter: "14d",
      maxEntries: 2000,
      rotateBytes: "25mb",
      maxDiskBytes: "2gb",
      highWaterBytes: "1.6gb",
    },
  },
}

CLI에서 유지보수 미리보기 또는 강제 실행:

openclaw sessions cleanup --dry-run
openclaw sessions cleanup --enforce

세션 프루닝

OpenClaw은 기본적으로 LLM 호출 직전에 인메모리 컨텍스트에서 오래된 도구 결과를 트리밍합니다. JSONL 히스토리를 수정하지 않습니다. /concepts/session-pruning을 참고하세요.

압축 전 메모리 플러시

세션이 자동 압축에 가까워지면 OpenClaw은 모델에게 지속적인 메모를 디스크에 기록하도록 알려주는 무음 메모리 플러시 턴을 실행할 수 있습니다. 워크스페이스가 쓰기 가능한 경우에만 실행됩니다. 메모리압축을 참고하세요.

전송 방식 → 세션 키 매핑

  • 직접 대화는 session.dmScope를 따릅니다(기본값 main).
    • main: agent:<agentId>:<mainKey> (기기/채널 간 연속성).
      • 여러 전화번호와 채널이 동일한 에이전트 메인 키에 매핑될 수 있으며, 하나의 대화로 전달되는 전송 방식으로 작동합니다.
    • per-peer: agent:<agentId>:direct:<peerId>.
    • per-channel-peer: agent:<agentId>:<channel>:direct:<peerId>.
    • per-account-channel-peer: agent:<agentId>:<channel>:<accountId>:direct:<peerId> (accountId 기본값 default).
    • session.identityLinks가 프로바이더 접두사가 붙은 피어 ID(예: telegram:123)와 일치하면 정규 키가 <peerId>를 대체하여 동일인이 채널 간에 세션을 공유합니다.
  • 그룹 대화는 상태를 격리합니다: agent:<agentId>:<channel>:group:<id> (룸/채널은 agent:<agentId>:<channel>:channel:<id> 사용).
    • Telegram 포럼 토픽은 격리를 위해 그룹 ID에 :topic:<threadId>를 추가합니다.
    • 레거시 group:<id> 키는 마이그레이션을 위해 여전히 인식됩니다.
  • 수신 컨텍스트는 여전히 group:<id>를 사용할 수 있으며, 채널은 Provider에서 추론되어 정규 agent:<agentId>:<channel>:group:<id> 형식으로 정규화됩니다.
  • 기타 소스:
    • 크론 작업: cron:<job.id>
    • 웹훅: hook:<uuid> (훅에 의해 명시적으로 설정되지 않는 한)
    • 노드 실행: node-<nodeId>

수명 주기

  • 리셋 정책: 세션은 만료될 때까지 재사용되며, 만료 여부는 다음 수신 메시지에서 평가됩니다.
  • 일일 리셋: 기본값은 게이트웨이 호스트의 현지 시간 오전 4시입니다. 세션의 마지막 업데이트가 가장 최근 일일 리셋 시간보다 이전이면 만료됩니다.
  • 유휴 리셋 (선택): idleMinutes로 슬라이딩 유휴 윈도우를 추가합니다. 일일 리셋과 유휴 리셋이 모두 구성된 경우 먼저 만료되는 쪽이 새 세션을 강제합니다.
  • 레거시 유휴 전용: session.reset/resetByType 설정 없이 session.idleMinutes만 설정하면 하위 호환성을 위해 유휴 전용 모드를 유지합니다.
  • 유형별 오버라이드 (선택): resetByType을 사용하여 direct, group, thread 세션의 정책을 오버라이드할 수 있습니다(thread = Slack/Discord 스레드, Telegram 토픽, 커넥터가 제공하는 Matrix 스레드).
  • 채널별 오버라이드 (선택): resetByChannel은 채널에 대한 리셋 정책을 오버라이드합니다(해당 채널의 모든 세션 유형에 적용되며 reset/resetByType보다 우선).
  • 리셋 트리거: 정확히 /new 또는 /reset(및 resetTriggers의 추가 항목)은 새 세션 ID를 시작하고 나머지 메시지를 전달합니다. /new <model>은 모델 별칭, provider/model, 또는 프로바이더 이름(퍼지 매치)을 받아 새 세션 모델을 설정합니다. /new 또는 /reset만 보내면 OpenClaw이 리셋 확인을 위한 짧은 “인사” 턴을 실행합니다.
  • 수동 리셋: 저장소에서 특정 키를 삭제하거나 JSONL 대화 기록을 제거하면 다음 메시지에서 재생성됩니다.
  • 격리된 크론 작업은 항상 실행마다 새 sessionId를 생성합니다(유휴 재사용 없음).

전송 정책 (선택)

개별 ID를 나열하지 않고 특정 세션 유형에 대한 전달을 차단합니다.

{
  session: {
    sendPolicy: {
      rules: [
        { action: "deny", match: { channel: "discord", chatType: "group" } },
        { action: "deny", match: { keyPrefix: "cron:" } },
        // 원시 세션 키(`agent:<id>:` 접두사 포함)를 매칭합니다.
        { action: "deny", match: { rawKeyPrefix: "agent:main:discord:" } },
      ],
      default: "allow",
    },
  },
}

런타임 오버라이드 (소유자 전용):

  • /send on → 이 세션에서 허용
  • /send off → 이 세션에서 거부
  • /send inherit → 오버라이드 해제 후 설정 규칙 사용 독립 메시지로 보내야 등록됩니다.

구성 (선택적 이름 변경 예시)

// ~/.openclaw/openclaw.json
{
  session: {
    scope: "per-sender", // 그룹 키 별도 유지
    dmScope: "main", // DM 연속성 (공유 인박스에는 per-channel-peer/per-account-channel-peer 설정)
    identityLinks: {
      alice: ["telegram:123456789", "discord:987654321012345678"],
    },
    reset: {
      // 기본값: mode=daily, atHour=4 (게이트웨이 호스트 현지 시간).
      // idleMinutes도 설정하면 먼저 만료되는 쪽이 적용됩니다.
      mode: "daily",
      atHour: 4,
      idleMinutes: 120,
    },
    resetByType: {
      thread: { mode: "daily", atHour: 4 },
      direct: { mode: "idle", idleMinutes: 240 },
      group: { mode: "idle", idleMinutes: 120 },
    },
    resetByChannel: {
      discord: { mode: "idle", idleMinutes: 10080 },
    },
    resetTriggers: ["/new", "/reset"],
    store: "~/.openclaw/agents/{agentId}/sessions/sessions.json",
    mainKey: "main",
  },
}

확인

  • openclaw status — 저장소 경로와 최근 세션을 표시합니다.
  • openclaw sessions --json — 모든 항목을 덤프합니다(--active <minutes>로 필터링).
  • openclaw gateway call sessions.list --params '{}' — 실행 중인 게이트웨이에서 세션을 가져옵니다(원격 게이트웨이 접근 시 --url/--token 사용).
  • 대화에서 독립 메시지로 /status를 보내면 에이전트 도달 가능 여부, 세션 컨텍스트 사용량, 현재 사고/빠른/상세 토글, WhatsApp 웹 자격 증명의 마지막 갱신 시간(재연결 필요 여부 확인에 도움)을 확인할 수 있습니다.
  • /context list 또는 /context detail을 보내면 시스템 프롬프트와 주입된 워크스페이스 파일의 내용(및 가장 큰 컨텍스트 기여자)을 확인할 수 있습니다.
  • /stop을 보내거나 (stop, stop action, stop run, stop openclaw 같은 독립 중단 구문) 현재 실행을 중단하고 해당 세션의 큐에 있는 후속 작업을 지우고 거기서 스폰된 서브 에이전트 실행도 중지합니다(응답에 중지된 수가 포함됨).
  • 독립 메시지로 /compact (선택적 지시사항)를 보내면 오래된 컨텍스트를 요약하고 윈도우 공간을 확보합니다. /concepts/compaction을 참고하세요.
  • JSONL 대화 기록을 직접 열어 전체 턴을 검토할 수 있습니다.

  • 기본 키는 1:1 트래픽 전용으로 유지하고, 그룹은 자체 키를 사용하도록 하세요.
  • 자동 정리 시 전체 저장소 대신 개별 키를 삭제하여 다른 곳의 컨텍스트를 보존하세요.

세션 출처 메타데이터

각 세션 항목은 출처를 origin에 기록합니다(최선 노력):

  • label: 사람이 읽을 수 있는 레이블 (대화 레이블 + 그룹 주제/채널에서 해석)
  • provider: 정규화된 채널 ID (확장 포함)
  • from/to: 수신 엔벨로프의 원시 라우팅 ID
  • accountId: 프로바이더 계정 ID (다중 계정 시)
  • threadId: 채널이 지원하는 경우 스레드/토픽 ID 출처 필드는 다이렉트 메시지, 채널, 그룹에 대해 채워집니다. 커넥터가 전달 라우팅만 업데이트하는 경우(예: DM 메인 세션을 최신 상태로 유지하기 위해)에도 세션이 설명 메타데이터를 유지하도록 수신 컨텍스트를 제공해야 합니다. 확장에서는 수신 컨텍스트에 ConversationLabel, GroupSubject, GroupChannel, GroupSpace, SenderName을 보내고 recordSessionMetaFromInbound를 호출(또는 updateLastRoute에 같은 컨텍스트를 전달)하여 이를 수행할 수 있습니다.