Skip to content

fix(bridge/memory): close audience-routed pending jobs as superseded after a mode switch (#2333) - #2410

Merged
seoseo-ai merged 2 commits into
mainfrom
fix/2333-superseded-route-pending
Oct 20, 2026
Merged

seoseo-ai merged 2 commits into
mainfrom
fix/2333-superseded-route-pending

Conversation

@seoseo-ai

Copy link
Copy Markdown
Collaborator

Closes #2333

문제

메모리 모드가 audience에서 off/curated로 바뀌면, audience 시절에 라우팅된 local_sink_status=pending 잡을 local sink가 끝내 가져가지 않습니다(distill_journal.py:645). 저널은 pending으로 굳고, 관측기는 이 잡들을 "미연결 backlog"로 계속 보고합니다.

결정: 전역 편입 대신 종결

audience 라우팅된 잡에는 오너가 아닌 audience의 내용이 들어 있을 수 있습니다. 반면 node-global 메모리는 오너 전용입니다. 그래서 전역 sink로 편입하지 않고 명시적으로 종결합니다. 이슈는 두 방식을 모두 허용합니다.

변경

  • 롤백 안전 표현: 새 enum 값 없이 local_sink_status=terminal_failed와 error_code="local_sink_superseded_route"로 표현합니다. 처음 시도한 superseded_route 새 상태는 이전 빌드의 list_jobs()·recover_stale_running()이 ValueError로 멈추게 만들었고, 이를 재현한 뒤 폐기했습니다.
  • 전환 조건: 라우팅된 PENDING/RETRYABLE_FAILED이고 audience_routes=False, global_route=True일 때만 전환합니다. 바뀌는 것은 status·error_code·updated_at뿐이며 어떤 메모리 저장소에도 쓰지 않습니다. 다시 실행해도 결과는 같습니다.
  • 변경 없음: global_route=False이면 지금처럼 pending을 유지합니다. #2296의 UNROUTABLE 편입도 그대로입니다.
  • audience 모드 재활성화: 표식이 있고 audience route와 extraction output을 가진 잡만 다시 claim할 수 있습니다. attempts는 보존합니다. 일반 terminal_failed는 재개하지 않습니다.
  • Wiki-sink 단계: 이 단계도 error_code를 쓰는데, 다섯 군데의 쓰기 경로 모두 표식을 지우지 않도록 수정했습니다.
  • retention: 다른 terminal 상태와 같이 정리하되, audience route가 있는 동안에는 보존합니다.
  • scripts/ccc-memory-check.sh: 표식이 있는 레코드는 local_status_counts.superseded_route로 따로 셉니다. degraded나 pending으로 세지 않습니다.
  • docs/memory.md를 갱신하고 changelog 조각 2개(bridge, harness)를 추가했습니다.

검증 (로컬, CI 고정 의존성 venv)

  • test_superseded_record_stays_readable_after_a_rollback: base 22d24780의 journal 모듈을 git show로 불러와, 새 코드가 쓴 레코드를 list_jobs·recover_stale_running·claim·prune으로 정상 처리하는지 확인합니다.
  • distill 관련 8개 파일 154 passed, matrix writeback·skill worker 42 passed, session_provider(distill/memory) 21 passed
  • 수정 전 코드(심볼만 추가한 상태)에서는 새 테스트 10건이 실패합니다.
  • ccc-memory.test.sh 193/0
  • ruff 0.17.0, mypy(173 files), shellcheck, canon scan, changelog check → 통과

참고

  • 이슈가 언급한 observe-node.py는 이 레포에 없습니다. 레포 안에서 같은 저널을 읽는 ccc-memory-check.sh를 갱신했습니다.
  • 배포 후에는 오래 남아 있던 pending 잡들(이슈의 실례)이 첫 sweep에서 종결됩니다. 내용은 이미 다른 경로로 nunchi에 들어가 있어서 이중으로 쓰이지 않습니다. 배포나 재시작 없이 소스만 바꾼 PR입니다.

🤖 Generated with Claude Code

…after a mode switch (#2333)

After a node switches CCC_BRIDGE_MEMORY_MODE from audience-scoped to
off/curated, audience-routed distill jobs left at local_sink_status
pending (or retryable_failed) were never claimed again: claim_local_sink
returned None forever and observers kept counting them as unmatched
backlog. #2296 only adopts route-free UNROUTABLE work.

These jobs are deliberately NOT adopted into the node-global sink:
audience-scoped content may belong to a non-owner audience, and the
global memory is the owner's own (privacy boundary). Instead:

- claim_local_sink with a global route and no audience routes closes a
  routed PENDING/RETRYABLE_FAILED job as superseded: local_sink_status
  TERMINAL_FAILED plus error_code "local_sink_superseded_route" (status,
  error_code, updated_at only; no lease, no attempt, nothing written).
  global_route=False keeps the historical None/pending contract.
- Rollback-safe: no new status value. Pre-#2333 list_jobs() and
  recover_stale_running() raise on an unknown status, which would stall
  the whole local-sink loop after a self-update rollback; the chosen
  representation parses there as plain terminal work (tested against the
  22d2478 sources).
- Wiki-sink writes share error_code; they now keep the marker.
- With audience_routes, exactly these marked jobs are claimable again
  like PENDING (attempt counter preserved), so re-enabling audience mode
  writes them to their original audience once. The local-sink loop offers
  them only to a worker with audience routes (new
  CodexDistillLocalSinkWorker.audience_routes_enabled); genuine terminal
  failures are never resumed.
- Retention: terminal like DONE/UNROUTABLE; the sweep passes
  keep_superseded_route while audience routes exist.
- ccc-memory-check.sh projects the marked records as local status
  superseded_route, neither pending nor degraded.
- docs/memory.md documents the behavior; changelog fragments added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jinon86
jinon86 previously approved these changes Oct 20, 2026

@jinon86 jinon86 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved after exact-head validation and fresh operator authorization using the relay-held jinon86 credential.

@jinon86 jinon86 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved after exact-head validation and fresh operator authorization using the relay-held jinon86 credential.

@seoseo-ai
seoseo-ai added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit bfecad2 Oct 20, 2026
14 checks passed
@seoseo-ai
seoseo-ai deleted the fix/2333-superseded-route-pending branch October 20, 2026 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

distill local sink: 모드가 off/curated로 바뀐 뒤 audience 라우팅된 PENDING 잡을 영원히 집지 않음 (distill_journal.py:645, #2130 D8)

2 participants