Skip to content

[Feat] LangGraph 체크포인터 배선 — 대화 멀티턴 지원#66

Merged
haein45 merged 4 commits into
devfrom
feat/multiturn-checkpointer
May 29, 2026
Merged

[Feat] LangGraph 체크포인터 배선 — 대화 멀티턴 지원#66
haein45 merged 4 commits into
devfrom
feat/multiturn-checkpointer

Conversation

@haein45

@haein45 haein45 commented May 29, 2026

Copy link
Copy Markdown
Collaborator

📌 관련 이슈

🏷️ PR 타입

  • ✨ 기능 추가 (Feature)
  • 🐛 버그 수정 (Bug Fix)
  • ♻️ 리팩토링 (Refactoring)
  • 📝 문서 수정 (Documentation)
  • 🎨 스타일 변경 (Style)
  • ✅ 테스트 추가 (Test)

📝 작업 내용

  • common/checkpoint/saver.py 추가 — open_checkpointer(database_url): 값 있으면 AsyncPostgresSaver(최초 setup()), 없으면 InMemorySaver 폴백. postgresql+psycopg:// 같은 SQLAlchemy식 URL을 libpq 형식으로 정규화
  • builder.pybuild_graph(checkpointer) 추가해 compile(checkpointer=...)로 주입. 기존 get_graph()는 lazy 폴백 유지
  • main.py lifespan — checkpointer를 앱 수명 동안 열고 그래프를 주입 빌드
  • solve.pyainvoke(state, config={"configurable": {"thread_id": state.thread_id}}) 전달
  • 멀티턴이 안 되던 원인 2개(체크포인터 미배선 + thread_id config 미전달) 해소

📸 스크린샷

tests/common/checkpoint/test_saver.py::test_open_checkpointer_falls_back_to_inmemory_without_url PASSED [  7%]
tests/graph/test_multiturn.py::test_build_graph_attaches_checkpointer PASSED [ 15%]
tests/graph/test_multiturn.py::test_build_graph_without_checkpointer_is_none PASSED [ 23%]
tests/graph/test_multiturn.py::test_multiturn_accumulates_with_same_thread_id PASSED [ 30%]
tests/graph/test_multiturn.py::test_multiturn_isolated_by_thread_id PASSED [ 38%]
tests/graph/test_multiturn.py::test_no_checkpointer_does_not_persist PASSED [ 46%]
tests/app/test_solve_endpoint.py::test_missing_problem_returns_422 PASSED [ 53%]
tests/app/test_solve_endpoint.py::test_missing_user_id_returns_422 PASSED [ 61%]
tests/app/test_solve_endpoint.py::test_valid_request_returns_sse_stream PASSED [ 69%]
tests/app/test_solve_endpoint.py::test_thread_id_auto_generated PASSED   [ 76%]
tests/app/test_solve_endpoint.py::test_thread_id_passed_as_langgraph_config PASSED [ 84%]
tests/app/test_health.py::test_health_check PASSED                       [ 92%]
tests/app/test_health.py::test_app_lifespan_initializes_and_closes_daytona PASSED [100%]
======================== 13 passed, 1 warning in 0.83s =========================

✅ 체크리스트

  • 코드 리뷰를 받을 준비가 완료되었습니다
  • 테스트를 작성하고 모두 통과했습니다
  • 문서를 업데이트했습니다 (필요한 경우)
  • 코드 스타일 가이드를 준수했습니다
  • 셀프 리뷰를 완료했습니다

📎 기타 참고사항

  • database_url은 libpq 형식(postgresql://...) 권장. Supabase 콘솔의 connection string을 그대로 넣으면 됨.

Summary by CodeRabbit

  • 새로운 기능

    • 데이터베이스 기반 또는 메모리 백업 체크포인트로 세션 상태 지속성 추가
    • 체크포인트가 앱 수명 주기 동안 안전하게 열리고 닫힘
  • 개선 사항

    • 대화 히스토리가 사용자별로 네임스페이스되어 스레드 격리 보장
    • 크레딧 정산을 턴 단위로 변경하여 정산 정확성 향상
    • 앱 초기화 흐름과 리소스 관리 개선
  • 버그 수정 / 검증

    • user_id에 ':' 포함 시 요청을 거부하는 입력 검증 추가
  • 테스트

    • 멀티턴·체크포인터 동작 및 엔드포인트 유효성 검증 테스트 추가

Review Change Stack

- common/checkpoint/saver.py: database_url 있으면 AsyncPostgresSaver(setup 포함),
  없으면 InMemorySaver 폴백. SQLAlchemy식 conn string(+psycopg)도 libpq로 정규화
- builder.py: build_graph(checkpointer) 추가 — compile(checkpointer=...)로 주입.
  get_graph()는 lazy 폴백 유지
- main.py: lifespan에서 checkpointer를 long-lived로 열고 그래프 주입 빌드
- solve.py: ainvoke에 config={"configurable": {"thread_id": ...}} 전달
- 테스트: 팩토리 폴백, build_graph 체크포인터 부착, InMemorySaver 멀티턴
  누적/격리/대조군, /solve thread_id config 전달. test_health 라이프스팬은
  checkpointer 격리 추가

범위: 대화 멀티턴만. tool box 재현(도구 메시지 영속화)은 별도 이슈.
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20f6b22d-17bf-410a-a671-77cd5192dd33

📥 Commits

Reviewing files that changed from the base of the PR and between 7311c68 and 65d049b.

📒 Files selected for processing (2)
  • src/proovy_agent/app/schemas/solve.py
  • tests/app/test_solve_endpoint.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/app/test_solve_endpoint.py

📝 Walkthrough

걷기

LangGraph 체크포인터 배선을 통해 멀티턴 대화 지원을 구현합니다. Postgres(또는 메모리 폴백) 기반 상태 영속화, user_id 네임스페이싱된 thread_id config 전달, 턴 단위 크레딧 정산, 그리고 상태 누적을 검증하는 통합 테스트를 추가합니다.

변경 사항

멀티턴 체크포인터 배선

Layer / File(s) Summary
체크포인터 팩토리 및 인프라
src/proovy_agent/common/checkpoint/saver.py, tests/common/checkpoint/test_saver.py
비동기 컨텍스트 매니저 open_checkpointer()database_url 유무에 따라 AsyncPostgresSaver(초기 setup 포함) 또는 InMemorySaver로 분기합니다. PostgreSQL 연결 문자열을 libpq 형식(postgresql://)으로 정규화하고, msgpack 역직렬화를 allowlist로 제한합니다. 메모리 폴백 비활성화 시 RuntimeError로 fail-fast합니다. 팩토리, URL 정규화, 폴백 동작을 검증하는 테스트를 포함합니다.
그래프 빌드 함수 및 체크포인터 주입
src/proovy_agent/graph/builder.py, tests/graph/test_multiturn.py
build_graph(checkpointer) 함수를 추가하여 체크포인터를 받아 _build()로 전달하고, 컴파일 시 builder.compile(checkpointer=checkpointer)로 주입한 후 전역 _graph 캐시에 저장합니다. 테스트는 체크포인터 부착 확인 및 싱글톤 캐시 리셋 fixture를 포함합니다.
애플리케이션 라이프사이클 통합
src/proovy_agent/app/main.py, tests/app/test_health.py
lifespan 컨텍스트 매니저가 시작 시 open_checkpointer(settings.database_url, allow_memory_fallback=settings.debug)로 체크포인터를 열고, build_graph(checkpointer)로 그래프를 빌드한 후 yield합니다. 종료 시 finally 블록에서 Daytona 클라이언트와 컨텍스트를 정리합니다. 테스트는 더미 체크포인터를 monkeypatch하여 외부 의존성을 차단합니다.
멀티턴 상태 및 정산 로직
src/proovy_agent/graph/state.py, src/proovy_agent/graph/nodes/credit_settler.py
ProovyStateoperator.add 리듀서를 사용하는 settled_count 필드를 추가하여 턴별 정산 진행도를 추적합니다. credit_settler 노드는 settled_count 이후의 새 항목만 정산(turn_log/turn_cost)하고, 누적 합계(total_credit_cost)와 현재 턴 합계를 구분하여 반환합니다. SSE 이벤트는 현재 턴 값만 전송합니다.
Solve 엔드포인트 thread_id 네임스페이싱
src/proovy_agent/app/api/v1/solve.py, src/proovy_agent/app/schemas/solve.py, tests/app/test_solve_endpoint.py
/solve 엔드포인트가 state.user_idstate.thread_id를 조합하여 checkpoint_thread_id = "{user_id}:{thread_id}"를 생성하고, config={"configurable": {"thread_id": checkpoint_thread_id}}get_graph().ainvoke(state, config=config)에 전달합니다. user_id: 문자가 포함되는 것을 금지하는 유효성 검사(field_validator)가 추가되었습니다.
통합 테스트 — 멀티턴 동작 검증
tests/graph/test_multiturn.py
InMemorySaver를 사용한 미니 그래프로 같은 thread_id 재호출 시 메시지가 누적되고, 서로 다른 thread_id의 히스토리가 격리되며, 체크포인터가 없으면 상태가 유지되지 않음을 검증합니다. ProovyState/credit_settler와 함께 턴 단위 크레딧 정산이 누적 비용/정산 카운트/메시지 개수 기준으로 올바르게 갱신됨을 검증합니다.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant solve_endpoint
  participant get_graph
  participant CompiledGraph
  participant GraphAinvoke
  participant Checkpointer
  participant Database

  Client->>solve_endpoint: POST /api/v1/solve (state: user_id, thread_id)
  solve_endpoint->>solve_endpoint: checkpoint_thread_id = f"{user_id}:{thread_id}"
  solve_endpoint->>get_graph: get_graph()
  get_graph-->>CompiledGraph: CompiledStateGraph
  CompiledGraph->>GraphAinvoke: ainvoke(state, config={"configurable":{"thread_id":checkpoint_thread_id}})
  GraphAinvoke->>Checkpointer: load/save state using checkpoint_thread_id
  Checkpointer->>Database: DB persist / retrieve
  GraphAinvoke-->>solve_endpoint: SSE events
  solve_endpoint-->>Client: SSE stream
Loading

🎯 3 (Moderate) | ⏱️ ~25 minutes

관련 가능성 있는 PR

  • Team-Proovy/proovy-agent#43: /solve 엔드포인트 및 그래프 파이프라인 변경과 체크포인터 thread_id config 주입 방식을 직접 연계합니다.

제안 검토자

  • chowon442

🐰 체크포인트 배선으로 대화 기억해,
턴마다 쌓이는 히스토리 여행!
user_id로 네임스페이스하니,
멀티턴 멀티사용자 동시 진행,
한번 호출도 못 잊어 ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항(LangGraph 체크포인터 배선을 통한 멀티턴 대화 지원)을 명확하고 간결하게 요약합니다.
Description check ✅ Passed PR 설명이 템플릿의 필수 섹션(관련 이슈, PR 타입, 작업 내용, 체크리스트)을 모두 포함하며 충분히 상세합니다.
Linked Issues check ✅ Passed 모든 코딩 요구사항(체크포인터 팩토리, build_graph 추가, lifespan 통합, solve 설정 전달, 테스트)이 충실히 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 멀티턴 지원 목표와 직접 관련되어 있으며 범위 내에 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/multiturn-checkpointer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@haein45 haein45 self-assigned this May 29, 2026
@haein45 haein45 added the 혜인 label May 29, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요약

체크포인터 배선과 thread_id config 전달로 멀티턴의 핵심 누락은 잘 맞춰졌습니다. 다만 스레드 식별자가 user_id와 묶이지 않아 타 사용자 대화에 접근할 여지가 있고, 턴이 바뀔 때마다 그래프 전체가 재실행되며 credit_log가 누적될 수 있습니다. Postgres 체크포인트 역직렬화 경고도 추후 strict 모드에서 깨질 수 있습니다.


[문제 1]

  • 심각도: High
  • 범주: 보안
  • 근거: solve.py에서 클라이언트가 준 thread_id를 그대로 LangGraph configurable.thread_id로 쓰고, API 레이어에 user_id 소유권 검증이 없습니다. 체크포인트 키가 사용자와 무관한 thread_id만으로 결정되므로, UUID를 알거나 유출된 경우 다른 사용자의 대화 히스토리를 이어 받거나 메시지를 덧붙일 수 있습니다.
  • 수정안: (1) 서버에서 thread_id를 발급·저장할 때 user_id와 함께 매핑하고, 요청 시 소유권을 검증하거나, (2) LangGraph config에 thread_id=f"{user_id}:{thread_id}"처럼 사용자 네임스페이스를 포함합니다. BFF/게이트웨이에서 인증한다면 그 경계와 정책을 코드·문서에 명시하는 것이 좋습니다.

[문제 2]

  • 심각도: Medium
  • 범주: 정확성
  • 근거: 체크포인트가 있어도 ainvoke는 매 요청마다 START부터 전체 그래프를 다시 탑니다. ProovyState.credit_logoperator.add 리듀서라 이전 턴 항목이 유지되고, router/planner 등이 턴마다 다시 실행되며 항목이 추가됩니다. credit_settlersum(state.credit_log)스레드 누적 합을 매번보냅니다. PR 설명상 “대화 멀티턴” 범위라도, 같은 thread_id로 후속 질문 시 과금·정산이 의도와 다를 수 있습니다.
  • 수정안: 턴 단위 정산이 목표면 (a) 새 턴 시작 시 credit_log/total_credit_cost를 초기화하는 입력 전략, (b) credit_settler가 직전 턴 delta만 합산, (c) 또는 Command(resume=...) 등으로 재개 지점을 분리하는 방안을 검토하세요. 의도적 누적이면 API/SSE 스펙에 “스레드 누적 과금”임을 명시하는 것이 좋습니다.

[문제 3]

  • 심각도: Medium
  • 범주: 유지보수성
  • 근거: Postgres 체크포인트에 PlanStep 등 커스텀 Pydantic 타입을 저장·복원할 때 Deserializing unregistered type proovy_agent.graph.state.PlanStep 경고가 발생합니다. LangGraph는 LANGGRAPH_STRICT_MSGPACK=true 또는 향후 버전에서 미등록 타입 역직렬화를 차단할 수 있습니다.
  • 수정안: StateGraph.compile() 시 LangGraph 권장 방식으로 serde allowlist에 PlanStep/CreditEntry 모듈을 등록하거나, 체크포인트에 넣는 상태는 plain dict/TypedDict로 제한합니다. PR 머지 전 .env + Postgres로 한 번 restore smoke test를 권장합니다.

[문제 4]

  • 심각도: Medium
  • 범주: 성능 / 운영
  • 근거: database_url이 비어 있으면 InMemorySaver로 폴백합니다. Uvicorn 다중 워커/다중 인스턴스에서는 워커·인스턴스마다 메모리가 분리되어 같은 thread_id라도 요청이 다른 프로세스로 가면 멀티턴이 깨집니다. 프로덕션에서 URL 누락 시에도 기동은 되지만 데이터가 조용히 사라집니다.
  • 수정안: 프로덕션(debug=False 등)에서는 database_url 미설정 시 기동 실패(fail-fast)를 고려하거나, 폴백 시 ERROR 로그 + 메트릭 알람을 추가하세요. 수평 확장 환경에서는 Postgres 체크포인터가 필수임을 배포 문서에 명시하면 좋습니다.

[문제 5]

  • 심각도: Low
  • 범주: 정확성
  • 근거: _to_libpqpostgresql+psycopg://, postgresql+asyncpg:// 두 접두사만 치환합니다. postgres://, postgresql+psycopg2:// 등 다른 connection string 형식은 그대로 AsyncPostgresSaver에 전달되어 연결 실패할 수 있습니다.
  • 수정안: urllib.parse.urlparse로 scheme을 정규화하거나, 지원 형식 목록을 문서화하고 잘못된 URL에 대해 명확한 예외 메시지를 던지세요.

[문제 6]

  • 심각도: Low
  • 범주: 테스트
  • 근거: test_multiturn.py는 미니 messages 그래프로 체크포인터 메커니즘만 검증합니다. 실제 ProovyState + 전체 그래프에서 메시지 누적·plan/credit_log 병합·라우팅 재실행 동작은 자동 테스트가 없습니다 (PR 본문의 수동 e2e와 일치).
  • 수정안: LLM/샌드박스를 mock한 통합 테스트 1건(동일 thread_id 2회 호출 후 messages 길이·credit_log 정책 assert)을 추가하면 회귀 방지에 도움이 됩니다.
Open in Web View Automation 

Sent by Cursor Automation: Chowon Reviewer

Comment thread src/proovy_agent/app/api/v1/solve.py Outdated
Comment thread src/proovy_agent/common/checkpoint/saver.py
Comment thread src/proovy_agent/common/checkpoint/saver.py
haein45 added 2 commits May 29, 2026 21:13
- [보안] solve.py: 체크포인트 thread_id를 user_id로 네임스페이스
  (f"{user_id}:{thread_id}") — 타 사용자 thread_id 접근 차단. user_id
  인증은 상위 게이트웨이/BFF 책임으로 명시
- [운영] main.py + saver.py: 프로덕션(debug=False)에서 database_url 미설정 시
  InMemory 폴백 대신 fail-fast (allow_memory_fallback=settings.debug).
  조용한 멀티턴 소실 방지
- [유지보수] saver.py: AsyncPostgresSaver에 JsonPlusSerializer allowlist 주입
  (PlanStep/CreditEntry) — "unregistered type" 경고 및 향후 strict 모드 차단 대비
- [정확성] saver.py: _to_libpq를 urlsplit 기반으로 강화 — postgres://,
  +psycopg2 등 다양한 scheme 정규화
- 테스트: 네임스페이스 config, fail-fast, scheme 정규화 파라미터화 추가

미해결(결정 필요): credit_log 턴 누적 정산 정책, 전체 그래프 통합 테스트
- ProovyState에 settled_count(operator.add reducer) 추가. 전체 state 입력이
  매 턴 LastValue 필드를 기본값으로 덮어써도 reducer라 정산 경계가 살아남음
- credit_settler: credit_log[settled_count:]만 정산해 이번 턴 비용(turn_cost)만
  SSE/메시지로 보냄. total_credit_cost는 스레드 누적 합계 유지
- 통합 테스트: 실제 ProovyState + credit_settler + InMemorySaver로 동일
  thread_id 2회 호출 시 turn2가 누적(10)이 아닌 턴 비용(5)만 정산하는지 검증

리뷰 #2(과금 정확성) 반영. 결정: 턴 단위 정산.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/proovy_agent/app/main.py (1)

19-28: ⚡ Quick win

open_checkpointer 진입 실패 시 Daytona 클라이언트가 정리되지 않습니다.

init_daytona_client()async with open_checkpointer(...) 바깥에서 호출되지만, close_daytona_client()async with 내부 finally에 있습니다. 따라서 open_checkpointer 진입 단계에서 예외가 발생하면(프로덕션 fail-fast RuntimeError 또는 Postgres setup() 실패 등) finally가 실행되지 않아 이미 초기화된 Daytona 클라이언트가 정리되지 않고 누수됩니다. 기동 실패가 반복되면 샌드박스 리소스가 누적될 수 있습니다.

finally를 체크포인터 컨텍스트 바깥으로 끌어올려 Daytona 초기화/정리를 짝맞추는 것을 권장합니다.

♻️ 정리 순서 보장 리팩터
     await init_daytona_client()
-    async with open_checkpointer(
-        settings.database_url,
-        allow_memory_fallback=settings.debug,
-    ) as checkpointer:
-        build_graph(checkpointer)
-        try:
-            yield
-        finally:
-            await close_daytona_client()
+    try:
+        async with open_checkpointer(
+            settings.database_url,
+            allow_memory_fallback=settings.debug,
+        ) as checkpointer:
+            build_graph(checkpointer)
+            yield
+    finally:
+        await close_daytona_client()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/proovy_agent/app/main.py` around lines 19 - 28, The Daytona client is
initialized by init_daytona_client() before entering async with
open_checkpointer(...), but close_daytona_client() is placed inside that
context's finally block so it won't run if open_checkpointer() raises; move the
cleanup so initialization and cleanup are paired: call await
init_daytona_client() as before, then wrap the entire open_checkpointer(...)
block (including build_graph(checkpointer) and yield) in a try/finally that
calls await close_daytona_client() in the finally, ensuring
close_daytona_client() always runs even if open_checkpointer() fails; adjust the
scope of the try/finally around open_checkpointer and reference the existing
functions init_daytona_client, open_checkpointer, build_graph, and
close_daytona_client.
src/proovy_agent/common/checkpoint/saver.py (1)

56-61: (선택) 체크포인트 저장 데이터의 미사용 시 암호화 고려.

체크포인트에는 대화 messages 등 사용자 입력(잠재적 PII)이 영속화됩니다. 민감 데이터 보호가 요구되는 환경이라면 EncryptedSerializerserde를 래핑해 at-rest 암호화를 적용하는 방안을 검토할 수 있습니다. 본 PR 범위 밖일 수 있어 후속 과제로 남겨두셔도 됩니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/proovy_agent/common/checkpoint/saver.py` around lines 56 - 61, The
checkpoint saver persists user messages which may contain PII; wrap the serde
passed into AsyncPostgresSaver.from_conn_string with an at-rest encryption
wrapper (e.g., EncryptedSerializer) when encryption is enabled to ensure stored
checkpoints are encrypted. Modify the logic around
AsyncPostgresSaver.from_conn_string (and the _to_libpq/database_url usage) to
accept a wrapped serde: if an encryption flag/config is set, replace serde with
EncryptedSerializer(serde, key_provider) before calling
AsyncPostgresSaver.from_conn_string; otherwise pass the original serde. Ensure
the EncryptedSerializer is imported/available and that key management is plumbed
via configuration so AsyncPostgresSaver.setup() continues to work transparently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/proovy_agent/app/api/v1/solve.py`:
- Around line 44-50: The current checkpoint key built as checkpoint_thread_id =
f"{state.user_id}:{state.thread_id}" can collide if user_id contains ":"; update
this by either adding a validation on SolveRequest.user_id (in
src/proovy_agent/app/schemas/solve.py) to forbid ":" via a regex/validator, or
by encoding/hashing the combined key before use (replace the f-string creation
of checkpoint_thread_id in solve.py to use a safe transform such as
base64/url-safe encode or a deterministic hash of
f"{state.user_id}:{state.thread_id}"), and then pass that safe
checkpoint_thread_id into get_graph().ainvoke config; ensure references use
state.user_id and state.thread_id so the source of the values is clear.

---

Nitpick comments:
In `@src/proovy_agent/app/main.py`:
- Around line 19-28: The Daytona client is initialized by init_daytona_client()
before entering async with open_checkpointer(...), but close_daytona_client() is
placed inside that context's finally block so it won't run if
open_checkpointer() raises; move the cleanup so initialization and cleanup are
paired: call await init_daytona_client() as before, then wrap the entire
open_checkpointer(...) block (including build_graph(checkpointer) and yield) in
a try/finally that calls await close_daytona_client() in the finally, ensuring
close_daytona_client() always runs even if open_checkpointer() fails; adjust the
scope of the try/finally around open_checkpointer and reference the existing
functions init_daytona_client, open_checkpointer, build_graph, and
close_daytona_client.

In `@src/proovy_agent/common/checkpoint/saver.py`:
- Around line 56-61: The checkpoint saver persists user messages which may
contain PII; wrap the serde passed into AsyncPostgresSaver.from_conn_string with
an at-rest encryption wrapper (e.g., EncryptedSerializer) when encryption is
enabled to ensure stored checkpoints are encrypted. Modify the logic around
AsyncPostgresSaver.from_conn_string (and the _to_libpq/database_url usage) to
accept a wrapped serde: if an encryption flag/config is set, replace serde with
EncryptedSerializer(serde, key_provider) before calling
AsyncPostgresSaver.from_conn_string; otherwise pass the original serde. Ensure
the EncryptedSerializer is imported/available and that key management is plumbed
via configuration so AsyncPostgresSaver.setup() continues to work transparently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 010af3af-e510-46a5-838f-119af4350b46

📥 Commits

Reviewing files that changed from the base of the PR and between a23178c and 7311c68.

📒 Files selected for processing (10)
  • src/proovy_agent/app/api/v1/solve.py
  • src/proovy_agent/app/main.py
  • src/proovy_agent/common/checkpoint/saver.py
  • src/proovy_agent/graph/builder.py
  • src/proovy_agent/graph/nodes/credit_settler.py
  • src/proovy_agent/graph/state.py
  • tests/app/test_health.py
  • tests/app/test_solve_endpoint.py
  • tests/common/checkpoint/test_saver.py
  • tests/graph/test_multiturn.py

Comment thread src/proovy_agent/app/api/v1/solve.py
코드 리뷰 반영. 체크포인트 키 f"{user_id}:{thread_id}"에서 user_id가 ':'를
포함하면 (a:b, c)와 (a, b:c)가 동일 키로 충돌해 타 사용자 대화에 접근할 수 있다.
SolveRequest.user_id에 ':' 금지 validator를 추가해 첫 ':'가 항상 경계를 명확히
가르도록 한다 (thread_id는 ':' 포함해도 충돌 불가).
@haein45 haein45 requested review from chowon442 and gaeunee2 May 29, 2026 13:54

@gaeunee2 gaeunee2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추후에 DB 도커 방식으로 변경해주세오~

@haein45 haein45 merged commit 56c1b3b into dev May 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] LangGraph 체크포인터 배선 — 대화 멀티턴 지원 (Supabase Postgres)

2 participants