Runtime v2 Client Wiring
플러그인 런타임이 이제 모든 RuntimeContext
에 기존 v1 BackendClient 와 함께
BackendV2Client 를 함께 주입합니다. 두
클라이언트가 같은 컨텍스트 객체 위에 놓이므로 — ctx.client (v1) 와
ctx.v2_client (v2) — caller 는 SDK 전체를 한꺼번에 cut-over 하지 않고도
호출 단위로 v2 엔드포인트로 점진 마이그레이션할 수 있습니다.
26H1Patch)이 변경은 synapse-backend 의 v2 export-readiness 작업
(/v2/tasks/bulk-fetch/, /v2/assignments/bulk-fetch/,
/v2/ground-truths/bulk-data/, /v2/ground-truth-events/bulk-data/) 의
SDK 측 짝입니다. 해당 bulk 엔드포인트 대응 BackendV2Client 리소스
메서드는 후속 SDK PR (SDK-2 / SDK-3 / SDK-4) 에서 추가되며,
ctx.v2_client.* 로 caller 를 옮기는 작업은 SDK-6 / SDK-7 / SDK-8 에서
이뤄집니다.
shim 대신 두 번째 클라이언트 필드를 두는 이유
BackendV2Client 는 BackendClient 의 drop-in replacement 가 아닙니다.
페이지네이션 형태, 헤더 이름, 메서드 네임스페이스, 응답 모델이 모두 다릅니다
(전체 비교는 BackendV2Client — Migration from v1
참고). v1 표면 뒤로 v2 클라이언트를 숨기는 shim 은 v2 만의 동작 (cursor
페이지네이션, request_id 캡처, typed Pydantic 모델) 을 잃거나 v1 caller 를
조용히 깨뜨립니다.
대신 SDK 는 additive 경로를 선택했습니다 — 두 클라이언트를 모두 주입하고, 각 caller 가 어느 쪽을 쓸지 직접 선택합니다. SemVer cut-over 가 없고, 외부 plugin 작성자 입장에서 v1 표면은 그대로입니다.
# RuntimeContext (synapse_sdk/plugins/context/__init__.py)
@dataclass
class RuntimeContext:
logger: BaseLogger
env: PluginEnvironment
job_id: str | None = None
client: BackendClient | None = None # v1 — 변경 없음
v2_client: BackendV2Client | None = None # v2 — 신규 (SYN-6919, additive)
agent_client: AgentClient | None = None
checkpoint: dict[str, Any] | None = None
language: str | None = None
Factory — create_backend_v2_client()
synapse_sdk/utils/auth.py 의 신규 factory 는 오래된 create_backend_client()
과 동일한 형태입니다. v1 factory 와 동일한 공유 env 들을 우선 사용하고
v2 전용 fallback 을 추가합니다.
from synapse_sdk.utils.auth import create_backend_v2_client
v2_client = create_backend_v2_client()
# BackendV2Client 또는 None (env / config 어디에도 토큰 없음).
환경 변수 우선순위
Factory 는 단일 env 해상 패스를 사용합니다 — 두 클라이언트가 같은 SSOT 를
참조하므로 synapse login 한 번 (또는 plugin 런타임이 주입하는 env 블록
하나) 으로 둘 다 부트스트랩됩니다.
| 우선순위 | 변수 | 용도 | v1 과 공유? |
|---|---|---|---|
| 1 | SYNAPSE_HOST | Backend base URL (/v2 suffix 없음) | 예 |
| 1 | SYNAPSE_ACCESS_TOKEN | syn_* 액세스 토큰 | 예 |
| 2 | SYNAPSE_PLUGIN_RUN_USER_TOKEN | 플러그인 런타임이 주입하는 DRF 토큰 | 예 (v1 은 authorization_token 으로, v2 는 drf_token 으로 바인딩) |
| 2 | SYNAPSE_PLUGIN_RUN_TENANT | 플러그인 런타임이 주입하는 테넌트 코드 | 예 |
| 3 | SYNAPSE_BACKEND_V2_ACCESS_TOKEN | v2 전용 access 토큰 fallback | 아니오 |
| 3 | SYNAPSE_BACKEND_V2_DRF_TOKEN | v2 전용 DRF 토큰 fallback | 아니오 |
| 3 | SYNAPSE_BACKEND_V2_TENANT | v2 전용 tenant fallback | 아니오 |
SYNAPSE_ACCESS_TOKEN, SYNAPSE_PLUGIN_RUN_USER_TOKEN,
SYNAPSE_BACKEND_V2_ACCESS_TOKEN, SYNAPSE_BACKEND_V2_DRF_TOKEN 중
아무것도 set 되어 있지 않으면 factory 는 None 을 리턴합니다. v1
factory 와 같은 정책이므로 자격증명이 없는 개발 환경도 계속 실행 가능합니다.
tenant_token_prefix=True 를 강제하는 이유
이미 배포된 모든 plugin-runtime 은 v1 스타일 SYNAPSE-Tenant: Token <code>
헤더를 가정합니다. 런타임 안에서 v2-native raw 값 (SYNAPSE-Tenant: <code>)
으로 전환하면 진행 중인 plugin run 의 tenant routing 을 조용히 깰 수
있습니다. 그래서 factory 는 tenant_token_prefix=True 를 무조건 전달합니다.
v2-native raw 값이 필요한 standalone caller 는 BackendV2Client 를 직접
생성하세요. 헤더별 동작은
BackendV2Client — Authentication
을 참고하세요.
Wiring 위치 — 5 instantiation site
RuntimeContext 는 정확히 다섯 곳에서 생성됩니다. 다섯 곳 모두 이제
create_backend_client() 옆에서 create_backend_v2_client() 를 호출하고
결과를 v2_client= 로 전달합니다.
| Site | 파일 | Trigger |
|---|---|---|
| Entrypoint | synapse_sdk/plugins/entrypoint.py | 서브프로세스 main() — plugin 런타임 컨테이너가 exec 하는 entrypoint |
| LocalExecutor | synapse_sdk/plugins/executors/local.py | In-process synapse plugin run --mode local |
| Ray Task | synapse_sdk/plugins/executors/ray/task.py | Ray Actor 기반 --mode task |
| Ray Job | synapse_sdk/plugins/executors/ray/job.py | Ray Jobs API 기반 --mode job |
| Ray Pipeline | synapse_sdk/plugins/executors/ray/pipeline.py | Ray 멀티 액션 파이프라인 |
Factory 는 RuntimeContext 빌드당 한 번 호출됩니다. Ray 기반 executor
의 경우 호출이 actor / job 프로세스 내부 에서 일어나므로 driver 가
아니라 런타임이 해당 워커에 주입한 env 블록을 사용합니다.
Caller 패턴 — 클래스 기반 액션
외부 plugin 작성자는 self.v2_client (클래스 기반 액션) 또는
ctx.v2_client (함수 기반 / step 기반 액션) 로 v2 클라이언트에 접근합니다.
표면은 BackendV2Client 와
완전히 동일합니다 — SDK 측 wrapper 가 없습니다.
from synapse_sdk.plugins.action import BaseAction
from synapse_sdk.plugins.enums import PluginCategory
class MyExportAction(BaseAction):
category = PluginCategory.EXPORT
def run(self):
# v1 경로 — 기존 동작과 동일
if self.client:
projects = self.client.list_projects()
# v2 경로 — 자격증명 없는 환경에선 None 가드 필요
if self.v2_client is not None:
page = self.v2_client.tasks.list(project=42, per_page=50)
for task in page.results:
...
# backend log lookup 용 request_id correlation
with self.v2_client.capture_request_ids():
self.v2_client.assignments.list(project=42, list_all=True)
self.logger.info(
'export-listing',
request_id=self.v2_client.last_request_id,
)
None 가드 규칙
ctx.client 와 ctx.v2_client 는 모두 Optional 타입이며 개발 환경에서
None 일 수 있습니다. Factory 는 env / ~/.synapse/config.json 어디서든
자격증명을 해상할 수 있을 때만 클라이언트를 리턴합니다. 운영 환경에선
런타임이 자격증명을 주입하지만, 자격증명 없는 셸에서 synapse plugin run --mode local 을 호출하면 두 필드가 모두 None 인 컨텍스트가 만들어집니다.
플러그인 코드는 v2 호출을 if ctx.v2_client is not None: (또는
if self.v2_client is not None:) 으로 가드해야 합니다.
공존 정책 — additive, cut-over 없음
| 항목 | v1 (self.client) | v2 (self.v2_client) |
|---|---|---|
| 필드 가용성 | 변경 없음 — SYN-6919 이전과 동일 | 신규, 기본값 None |
| Wire 되는 시점 | SYNAPSE_ACCESS_TOKEN, SYNAPSE_PLUGIN_RUN_USER_TOKEN 중 하나라도 set 된 경우 | SYNAPSE_ACCESS_TOKEN, SYNAPSE_PLUGIN_RUN_USER_TOKEN, SYNAPSE_BACKEND_V2_ACCESS_TOKEN, SYNAPSE_BACKEND_V2_DRF_TOKEN 중 하나라도 set 된 경우 |
| Tenant 헤더 | SYNAPSE-Tenant: Token <code> | SYNAPSE-Tenant: Token <code> (런타임이 v1 호환 prefix 강제, 위 factory 노트 참고) |
| 페이지네이션 | Offset (page_size=, 응답에 count) | Cursor (per_page=, count 없음) |
| 응답 형태 | Raw dict | Typed Pydantic 모델 |
| 마이그레이션 속도 | n/a | 호출 단위, per-category 정책으로 게이팅 (SYN-7082) |
SemVer 영향은 minor (additive). v2_client 를 참조하지 않는 외부
plugin 은 행동 변화가 0 입니다.
category 별 v2 선택
v2_client 가 wiring 되어 있다고 해서 caller 가 자동으로 v2 로
라우팅되지는 않습니다. in-tree caller 는
use_v2(ctx, *, category=None)
(synapse_sdk/plugins/actions/_v2_switch.py — SSOT) 를 통해 v1/v2 를
선택하며, 이 함수는 action category 별 정책 테이블을 참조합니다:
_V2_CATEGORY_POLICY = {PluginCategory.EXPORT: True} # 기본 v1; EXPORT → v2
Override 우선순위 (최상위 → 최하위):
SYNAPSE_FORCE_V1_EXPORTtruthy → 무조건 v1 (runtime 롤백).ctx.v2_client없음 /None→ v1._V2_CATEGORY_POLICYlookup → 기본 v1,EXPORT→ v2.
따라서 현재는 export handler 만 v2 를 타고, to_task fetch 와 dataset
download 는 v2_client 가 wiring 되어 있어도 v1 에 머뭅니다. 전체 라우팅
테이블은
category 별 v2 opt-in
을 참고하세요.
임계 초과 export 의 경우 SDK 는 v2_client.plugin_exports.create(...),
v2_client.async_jobs.stream_progress(job_id) (SSE),
v2_client.async_jobs.retrieve(job_id) 를 통해 처리를 backend async-job 에
위임합니다 — 따라서 v2_client 가 wiring 되어 있어야 위임 게이트가 발동할 수
있습니다. Export Actions — 서버사이드 위임
을 참고하세요.
마이그레이션 로드맵
SDK-1 에 들어간 wiring 은 caller 측 마이그레이션이 들어오기 전까지
의도적으로 inert 합니다 — 현재 in-tree action 중 ctx.v2_client 를
참조하는 것은 없습니다. 세 개의 후속 SDK PR 이 기존 v1 호출 사이트를
v2 표면으로 변환합니다.
| 후속 PR | 범위 |
|---|---|
| SDK-2 / SDK-3 / SDK-4 | bulk 엔드포인트용 v2 리소스 메서드 추가 (tasks.bulk_fetch, assignments.bulk_fetch, ground_truths.bulk_data, ground_truth_events.bulk_data). |
| SDK-6 | export action target handler 를 self.client.list_tasks → self.v2_client.tasks.bulk_fetch 로 마이그레이션. |
| SDK-7 | assignment / review export 경로 마이그레이션. |
| SDK-8 | ground-truth dataset export 경로 마이그레이션. |
각 후속 PR 은 독립적이며 v1 경로는 그대로 두므로 외부 plugin 은 계속 그대로
동작합니다. in-tree caller 가 모두 v2 로 이동하면 SDK 는 ctx.client 의
deprecation 여부를 재검토할 것입니다 — 그 결정은 SYN-6919 의 범위 밖입니다.
SDK-6/7/8 caller 는 모두 머지되었지만, 이후 SYN-7082 가 v2 선택을
per-category 정책 뒤로 게이팅했습니다 (위
category 별 v2 선택 참고). 현재 순효과:
export handler 는 v2 로 동작하고, to_task fetch 와 dataset download
caller 는 v1 으로 좁혀졌습니다 (이들의 v2 분기는 dead code 로 소스에 남아
있으며, category 를 _V2_CATEGORY_POLICY 에 추가하면 재활성화됩니다).
category 를 v2 로 옮기는 일은 이제 새 SDK-N cut-over PR 이 아니라 정책 테이블
편집입니다.
직접 작성한 plugin 테스트하기
tests/plugins/context/test_v2_client_wiring.py 는 5개 instantiation
site 모두에 대한 wiring 체크를 parametrize 합니다. 새 executor 가
RuntimeContext 를 빌드한다면 그 목록에 추가하세요 — v2_client= 인자
누락이 런타임이 아닌 테스트 단계에서 잡힙니다.
외부 plugin 테스트의 경우 factory 를 monkey-patch 하세요.
import synapse_sdk.utils.auth as auth_module
from synapse_sdk.clients.backend_v2 import BackendV2Client
def test_action_uses_v2_client(monkeypatch):
fake = BackendV2Client('https://api.test.synapse.sh', access_token='syn_x')
monkeypatch.setattr(auth_module, 'create_backend_v2_client', lambda: fake)
# ... LocalExecutor 빌드 / entrypoint 호출 / 등.
See also
- BackendV2Client — 이 페이지가 wire 하는 기반 v2 클라이언트
- RuntimeContext — 이제
v2_client를 갖는 dataclass - Local Execution —
LocalExecutor레퍼런스 - Ray Execution — Ray task / job / pipeline executor
- BackendClient (v1) — 계속 공존하는 기존 v1 클라이언트