# MEMORY.md - Long-term memory (curated)

> 목적: 라군(석호)과 클로의 장기 컨텍스트를 축적하는 파일.
> - 너무 사소한 로그는 daily memory로.
> - 프로젝트 상태/취향/선호/결정사항 위주로 요약.

## 사람/호칭
- 라군(석호): 기본 대화 언어 한국어, 편한 친구 톤 선호.
  - Discord: choonarm3 (id: 686473647262531626)

## 프로젝트
- 라군(LLagoon3)의 프로젝트는 워크스페이스(`/home/lagoon3/.openclaw/workspace`)에 클론되어 있는 git repo들을 기준으로 확인/정리한다.
- 라군의 커리어/이력서/포트폴리오 정리 문서는 워크스페이스의 `career/` 폴더를 우선 참고한다.
  - 프로필 요약: `/home/lagoon3/.openclaw/workspace/career/profile.md`
  - 프로젝트 정리: `/home/lagoon3/.openclaw/workspace/career/projects.md`

## 환경/설정
- 기본 모델(새 세션 기본값): openai-codex/gpt-5.2
- 모델 전환 요청이 오면: 먼저 OpenClaw 설정 JSON(또는 관련 설정 파일)에서 해당 모델명이 실제로 정의되어 있는지 확인한 뒤, 그 중에서 요청한 이름과 매칭되는 모델로 세션/설정을 전환하려고 시도한다.
- **Codex OAuth 로그인 주의(중요):** `openclaw models auth login --provider openai-codex`는 현재 구조상(plugins registry 경로) `Unknown provider "openai-codex"`가 날 수 있다. Codex OAuth는 **`openclaw onboard --auth-choice openai-codex`** 플로우로 해결/재인증한다.
- **실무 룰(요약):** OAuth 기반 provider는 `onboard` 플로우를 우선(특히 Codex). API key/token 기반 provider는 `openclaw models auth login|paste-token|add` 계열을 사용.
- 웹 자동화 워크플로우: 사이트별 시나리오는 Notion `Web Automation Workflow` 페이지 아래에 정리하고, 브라우저 자동화 전에 우선 참고한다.
- 인프라 네트워크 구조: OpenClaw 호스트 `lagoon3`는 Oracle VPS `lagoon-oracle-vps`를 경유한 reverse SSH 구조를 사용한다. Oracle VPS SSH config는 `HostName 168.107.14.33`, `User ubuntu`, `Port 20222` 이다.
- reverse SSH는 이제 **core + extra 분리 구조**를 canonical로 사용한다.
  - core 서비스: `/etc/systemd/system/reverse-ssh-core.service`
  - core 스크립트: `/home/lagoon3/bin/reverse-ssh-core.sh`
  - extra template 서비스: `/etc/systemd/system/reverse-ssh-extra@.service`
  - extra 스크립트: `/home/lagoon3/bin/reverse-ssh-extra.sh`
- 핵심 reverse SSH 포트 의미:
  - core: VPS `127.0.0.1:18789` → `lagoon3` `localhost:18789` (OpenClaw gateway, VPS 내부 전용)
  - core: VPS `0.0.0.0:10022` → `lagoon3` `localhost:22` (외부에서 집 서버 SSH 진입용)
  - extra: VPS `127.0.0.1:13440` → `lagoon3` `localhost:7340` (Portfolio frontend)
  - extra: VPS `127.0.0.1:13441` → `lagoon3` `localhost:7341` (Portfolio backend)
- OpenClaw gateway는 `lagoon3`에서 기본적으로 `127.0.0.1:18789` 로만 리슨하고, Oracle VPS는 외부 노출 진입점 역할을 맡는다.
- 운영 교훈: reverse SSH 구조 변경 시에는 **기존 메인 터널을 내리기 전에 replacement core 서비스가 먼저 올라와 있고 `10022` 진입이 살아있는지 확인한 뒤** old 서비스를 정리한다.
- Notion: DB컬럼은 DB 생성 후, `PATCH /v1/data_sources/{data_source_id}`로 별도로 진행한다.
- **쿠팡 URL 메모(중요):** 하드코딩한 추정 URL(예: `https://www.coupang.com/mycoupang/orderlist` 등)은 301/변경으로 깨질 수 있으니, **항상 쿠팡 메인에서 UI 링크를 따라가며 실제 URL을 확인**한다.
  - 메인: `https://www.coupang.com/`
  - 장바구니: `https://cart.coupang.com/cartView.pang`
  - 마이쿠팡/주문목록(데스크톱 SSR): `https://mc.coupang.com/ssr/desktop/order/list`
  - 배송조회(개별 주문): `https://mc.coupang.com/ssr/desktop/shiptrack?orderId=...&shipmentBoxId=...&invoiceNumber=...`
- 웹 자동화 공통 규칙:
  - 기본 프로필은 `openclaw`만 사용한다. `user` 프로필은 existing-session attach 구조라 기본 운영 경로에서 제외한다.
  - 시작 전 `browser.status` / `browser.tabs`로 상태를 확인한다.
  - `targetId="current"` 같은 가짜 식별자는 쓰지 않고, `tabs/open`에서 받은 실제 `targetId`만 사용한다.
  - 타임아웃/연결 오류는 1회만 재시도하고, 계속 실패하면 `stop` → `start`로 복구 시도 후 그래도 안 되면 중단하고 상태를 공유한다.
  - 작업 종료 후 사용자가 “열어둬/유지해”라고 하지 않았다면 `browser.stop(profile="openclaw")`로 정리한다.
- 쿠팡 전용 규칙:
  - 쿠팡 관련 요청은 먼저 Notion `Web Automation Workflow > Coupang`을 본다.
  - 재구매/장바구니 요청은 Notion `Coupang — Reorder / Previous Purchase SOP`를 먼저 확인한다.
  - `openclaw` 관리 브라우저 프로필을 쓰면 쿠팡 로그인 폼에 저장된 로그인 정보가 이미 입력되어 있을 수 있다. 이 경우 로그인 페이지에서 바로 로그인 버튼 제출부터 확인한다.
  - 공통 브라우저 이슈(`DISPLAY`, `XAUTHORITY`, 재기동`)는 쿠팡 페이지가 아니라 Notion 상위 브라우저 운영 메모를 먼저 본다.
- 리마인더/알림/알람: 라군이 “알림 걸어줘/알람 설정해줘/리마인더”라고 말하면 전부 **리마인더(=알림)** 요청으로 해석한다.
- 리마인더/알림(중요): 시간 지정 리마인더 요청이 오면 OpenClaw cron `kind:"cron"`으로 예약한다. (타임존: Asia/Seoul)
  - **방식:** 거대한 ms 숫자 대신 인간 친화적인 **크론 표현식(`expr`)**을 사용한다.
  - **표현식 형식:** `"분 시 일 월 *"` (예: 2월 19일 16:30분 → `"30 16 19 2 *"`)
  - 기본 전달은 **Telegram DM**(현재 대화)로 고정: `channel:"telegram"`, `to:"8308098400"`
  - 반드시 `enabled:true`를 명시한다.
  - `sessionTarget:"main"` + `payload.kind:"systemEvent"`는 세션 내부 이벤트라 **Discord 푸시 보장 안 됨**.
  - 권장 패턴: `sessionTarget:"isolated"` + `payload.kind:"agentTurn"` + `deliver:true` + `wakeMode:"now"`
  - **중요(Silent 방지):** `agentTurn`의 `message` 본문에는 서브 에이전트가 침묵(`NO_REPLY`)하지 않고 사용자에게 실제 메시지를 생성하도록 **"사용자에게 다음 리마인더 내용을 그대로 전달해:"** 같은 명시적 지시어를 반드시 포함한다.
  - **템플릿(권장, DM으로 확실히 보내는 버전):**

    ```json5
    {
      action: "add",
      job: {
        name: "리마인더 <간단설명>",
        enabled: true,
        schedule: { 
          kind: "cron", 
          expr: "분 시 일 월 *", // Asia/Seoul 기준
          tz: "Asia/Seoul"
        },
        sessionTarget: "isolated",
        wakeMode: "now",
        payload: {
          kind: "agentTurn",
          deliver: true,
          channel: "telegram",
          to: "8308098400",
          message: "사용자에게 다음 리마인더 내용을 그대로 전달해: 리마인더: <내용>"
        }
      }
    }
    ```

    - (참고) 상대적 시간 알림(예: 10분 뒤)은 `kind:"every"`도 가능하지만, 가능하면 **원샷(`kind:"at"`) + `deleteAfterRun:true`** 로 만들어서 자동 정리되게 하는 편이 관리가 쉽다.
    - (문제 발생 시) 게이트웨이/크론이 "메시지 칠 때만" 도는 증상 → `openclaw gateway restart`로 복구 가능

- **중요(지연 방지):** 웹 브라우저 자동화(`browser`)나 복잡한 툴 작업 전에는 **반드시 `memory_search`를 수행**하여 노션 워크플로우나 기존 결정 사항을 먼저 확인한다.
- `daiso-mcp`는 `https://mcp.aka.page`를 `streamable-http`로 붙여야 OpenClaw에서 정상 동작한다.
- **침묵 방지 워크플로우 (중요):**
  - 답변 생성 중 텔레그램 네트워크 오류(`sendChatAction failed` 등)가 감지되거나, `thinking` 시간이 20초를 초과할 것으로 예상되면 즉시 중간 진행 상황을 짧게라도 전송하여 세션이 끊기지 않게 한다.
  - 만약 최종 답변 전송에 실패했다면, 1회에 한해 즉시 재시도를 수행한다.
- **Claude ACP 운영 원칙:**
  - Claude 작업은 우선 **ACP 런타임**으로 처리한다.
  - `sessions_spawn(runtime="acp", agentId="claude", ...)` 를 기본 호출 패턴으로 사용한다.
  - **중요:** Claude ACP harness id는 `claude`다. `claude-code`나 `claude-cli`로 넣으면 spawn 실패할 수 있다.
  - OpenClaw 설정에서 필요하면 `acp.defaultAgent = "claude"` 와 `acp.allowedAgents = ["claude", ...]` 를 사용한다.
  - 문서/설정상 개별 에이전트 지정은 `agents.list[].runtime.acp.agent = "claude"` 형태를 사용한다.

## 에러 처리/툴 호출 습관
- 동일한 툴 에러가 1–2회 이상 같은 이유로 반복되면 **같은 호출을 다시 하지 말고**:
  - (1) 에러 메시지를 확인하고, **즉시 라군에게 "오류 수정 중"임을 짧게 공유하여 침묵하지 않는다.** 그 후 필수 필드를 채운 새 payload를 구성하여,
  - (2) **수정된 호출을 1회만** 재시도한다.
  - (3) 여전히 같은 에러가 반복되면 **자동 시도를 중단하고, 현재까지 시도/에러 내용을 라군에게 보고**한 뒤 다음 지시를 기다린다.

## 자동화/스크립트 메모
- YouTube 링크 → 텍스트 파이프라인: `~/.local/bin/openclaw-youtube-transcribe.sh`
  - 자막(ko/en) 있으면 자막 우선 추출
  - 자막 없으면 `yt-dlp`로 오디오 다운로드 → `whisper-cli`(small)로 STT
  - yt-dlp는 Python 3.11 venv(`~/.local/share/yt311/bin/yt-dlp`)에서 최신 유지 (YouTube 변경 대응)
- 특정 작업을 한 번 수행하기 위해 만든 **일회성 스크립트**는 작업이 완료되면 워크스페이스에서 정리(삭제)한다. (Notion 관련 `prepare_notion.py` 같은 것 포함)
- 외부 API에 보낼 JSON은 가능한 한 파일로 분리(`notion_payload.json` 등)해서 생성·검증하고, 전송 시에는 `-d @file.json`처럼 **파일 내용을 그대로 body로 쓰는 패턴**을 우선한다.
- 웹 자동화 액션 메모:
  1) 액션 직전에 `snapshot --interactive`로 role ref를 확보한다.
  2) 액션은 role ref 기준으로 실행하고, 이동 뒤에는 ref를 다시 잡는다.
  3) `axNN`류 ref는 보조 정보로만 참고한다.
  4) 목표 요소는 먼저 화면에 실제로 보이게 만들고, stale ref면 즉시 snapshot을 다시 뜬다.
  5) ref가 불확실하면 `highlight`나 `snapshot(labels=true)`로 위치를 확인한다.
