16 KiB
Agent Runtime Contract
계약 메타
- id:
iop.agent-runtime - boundary:
inner - status: active
- 원본 경로:
packages/go/agentruntime/types.gopackages/go/agentruntime/failure.gopackages/go/agentruntime/emitter.gopackages/go/agentruntime/session.gopackages/go/agentruntime/status.gopackages/go/agentruntime/registry.gopackages/go/agentconfig/packages/go/agentprovider/cli/packages/go/agentprovider/catalog/packages/go/agentguard/packages/go/agenttask/configs/iop-agent.providers.yamlapps/node/internal/node/runtime_bridge.go
읽는 조건
- Node와 독립 host가 공통 provider run/stream/resume/cancel/status 계약을 소비할 때
Provider,ExecutionSpec,RuntimeEvent,SessionMode,Failure,Registry를 변경할 때- CLI provider process, logical session, emitter, terminal, status/quota 파서를 변경할 때
- agent provider catalog YAML, provider/model/profile ID, discovery/readiness와 profile factory를 변경할 때
- unattended AgentTask의 canonical workspace grant, task isolation descriptor, admission permit과 provider invocation gate를 변경할 때
AgentTaskManager, manual start/auto-resume, explicit dependency, isolated dispatch, official review와 serial integration orchestration을 변경할 때- Node의 protobuf 요청/이벤트와 공통 runtime 사이 변환을 변경할 때
범위와 비범위
이 계약은 Node와 독립 agent host가 공유하는 host-neutral provider 실행 및 Agent Task orchestration 경계다. 공통 package는 provider lifecycle, 실행 요청, stream event, logical session, cancel, status/quota projection, typed failure와 registry lifecycle을 소유한다. agent 전용 catalog는 외부 CLI provider/model/profile의 공식 ID와 비밀정보 없는 실행·probe 선언, readiness와 공통 provider factory를 소유한다. agentguard는 unattended AgentTask provider 호출 직전의 canonical workspace와 capability admission을 소유한다. agenttask.Manager는 durable manual start intent부터 dependency-ready dispatch, submission/review, follow-up과 ordinal integration까지의 상태 전이를 단일 구현으로 소유한다.
Edge-Node protobuf field와 ordering 원문은 iop.edge-node-runtime-wire가 소유한다. 기존 Edge resource provider pool과 models[]는 iop.edge-config-runtime-refresh가 소유하며 agent catalog와 이름이 비슷해도 schema와 의미를 섞지 않는다. 실제 workspace overlay 생성·change-set apply/rollback backend와 standalone iop-agent process lifecycle은 이 계약의 비범위다. AgentTaskManager는 이 backend들의 strict port와 호출 순서만 소유한다. Admission은 이미 생성된 isolation descriptor를 검증할 뿐 overlay/worktree/clone을 만들지 않는다.
최소 호출과 이벤트 형태
- host는
Provider.Capabilities(ctx)로 target과 concurrency capability를 읽고Provider.Execute(ctx, ExecutionSpec, EventSink)로 실행한다. ExecutionSpec은run_id,adapter,target,session_id,session_mode, background, workspace, policy, input, timeout, metadata를 운반한다.SessionModeCreateIfMissing은 새 logical session 생성을 허용하고SessionModeRequireExisting은 기존 session이 없으면 실패해야 한다.- provider는 start, delta/reasoning_delta, complete/error/cancelled
RuntimeEvent를 순서대로 보낸다. complete/error/cancelled 중 하나만 terminal이며 terminal 이후 event는 host에 노출하지 않는다. - run cancel은 실행 context 취소와
ErrRunCancelled로 수렴한다. logical session 종료는 optionalSessionTerminator경계로 분리한다. - 조회/제어는 실행 stream과 섞지 않고 optional
CommandHandler가CommandRequest/CommandResponse로 처리한다. usage status는AgentUsageStatus로 정규화한다.
AgentTaskManager 명령과 durable 상태
- 공통 concrete 구현은
packages/go/agenttask.Manager하나다. host는AgentTaskManager의StartProject,Reconcile,StopProjectlifecycle만 호출하고 Node나 독립 CLI에 state machine을 복제하지 않는다. StartProject는command_id, project/workspace/Milestone identity와 workflow/config/grant revision을 atomic CAS state에 manualStartIntent로 기록한다. 같은 command와 같은 immutable 입력은 idempotent이고, 같은 command를 다른 입력으로 재사용하면 오류다.Reconcile은WorkflowAdapter.RegisteredProjects와 project별 snapshot을 관측하되StartIntent가 없는 ready Milestone을 실행하지 않는다. 수동 시작된 project만 진행하며 시작 기록이 있는 interrupted state는auto_resume_interrupted생략 시true, 명시false이면 stopped로 유지한다.- durable identity는 project, workspace, Milestone, work unit, attempt, artifact, change set, workflow/config/grant/isolation revision과 dispatch/integration ordinal을 분리한다. corrupt 또는 drift한 identity를 빈 상태나 현재 설정으로 재선택하지 않고 typed task/project blocker로 남긴다.
StateStore는 revision compare-and-swap을 제공해야 한다. manager는 project invocation lease와 workspace integration lease를 durable state에 claim하고 live 다른 owner가 있으면 중복 호출하지 않는다.- work state는
observed → ready → preparing → dispatching → submitted → reviewing → pending_integration → integrating → completed를 기준으로 하며,blocked,stopped,terminal_deferred를 명시 terminal branch로 쓴다. 정의되지 않은 전이는 거부한다. Event와 모든 external port idempotency key는 length-prefixed injective canonical tuple로 구성하여 raw delimiter 충돌을 방지하고, command/workflow revision/change-set ID·revision/integration attempt 등의 logical discriminator를 보존하여 replay 시 동일event_id로 수렴해야 한다. sink는 같은event_idreplay를 idempotent하게 처리해야 한다.
Dependency, isolated dispatch와 review/integration
- readiness gate는 workflow snapshot의
ExplicitPredecessors만 사용한다. task 번호, directory 순서, write-set 비중첩·중첩·unknown은 dependency를 만들지 않는다. predecessor reference가 없거나 둘 이상이면 각각 typed missing/ambiguous blocker다. Selector는 immutable config revision의 provider/model/profile과 capacity를 반환한다.Scheduler는 provider/profile capacity와 work-attempt ticket을 결합하며 cancel/release가 capacity를 정확히 반환하도록 한다.- 실행 전에
IsolationBackend.Prepare가 task별overlay | worktree | clonedescriptor와 exact grant/profile revision을 반환해야 한다. backend 미설정, identity mismatch, admission 차단은 provider invocation 0회이며 canonical workspace direct-write fallback은 없다. - manager는
agentguard.Admit의 opaque Permit을 invocation 직전에agentguard.Invoke로 재검증하고 그 canonical task view만ProviderInvoker에 전달한다. - provider submission이 complete이고 project/work/attempt/artifact identity가 일치한 뒤에만
Reviewer를 호출한다. PASS는 exact artifact의 immutable change set을 integration queue에 넣고 WARN/FAIL rework는 같은 dispatch ordinal의 새 attempt로 진행하며 USER_REVIEW는 해당 task만 terminal-deferred로 둔다. - integration은 최초 dispatch ordinal 순서로 한 번에 하나씩
Integrator를 호출한다. 모든 external port call은 stable idempotency key를 받아 crash 후 replay가 같은 결과로 수렴해야 한다. conflict, unmanaged drift, validation/apply 오류는 partial completion 없이 retained change set과 blocker를 반환하며 뒤 independent ordinal은 계속 진행한다. - project-local workflow, admission, invocation, review와 integration blocker는 다른 project나 independent sibling 진행을 중단하지 않는다.
Workspace guardrail admission
WorkspaceGrant는 project/workspace identity, canonical base root, immutable grant revision과 worktree가 사용할 수 있는 exact external Git metadata root allowance를 가진다.IsolationDescriptor는 immutable isolation/base revision,overlay | worktree | clonemode, canonical base/task/working root, task 내부 writable roots와 실제 writable-root confinement 여부를 가진다. task root와 canonical base가 같으면 admission을 거부한다.ProviderProfile은 provider/model/profile identity와 immutable revision,unattended,approval_bypass,writable_root_confinementcapability를 가진다. 세 capability 중 하나라도 없으면 provider process를 호출하지 않는다.- canonicalization은 absolute·clean·existing directory, symlink resolution, component-aware containment와 task root 및 effective working repository의 실제
.git/gitdir/commondir를 확인한다. task root 밖 Git metadata는 grant에 exact root로 등록된 경우만 허용한다. - 성공한 admission은 process-local opaque
Permit에 grant/isolation/profile revision, pinned base revision, canonical roots와 filesystem identity를 봉인한다. invocation 직전에 현재 입력과 filesystem identity를 다시 검증하며 stale, forged, replacement identity는 provider invocation 0회로 차단한다. - unattended AgentTask caller는
catalog.NewAdmittedProfileProvider가 반환하는 facade의Admit/Execute만 사용한다. facade는 caller가 제공한 rawExecutionSpec.Workspace를 사용하지 않고 Permit의 canonical working directory로 덮어쓴다. AdmissionResult는permitted | blocked, typedBlocker, raw path를 포함하지 않는 actionableNotification을 반환한다. 차단은 task/project-local result이며 다른 project provider를 stop하지 않는다. interactive approval fallback은 없다.- 기존 Node Edge-wire provider와 명시적인 authenticated smoke가 쓰는
ProfileProvider.Execute는 기존 실행 호환 경계다. AgentTask unattended 호출에서 이 compatibility 경로를 admission 우회로 사용하지 않는다.
Agent provider catalog와 readiness
configs/iop-agent.providers.yaml은version,providers[],models[],profiles[]의 비밀정보 없는 repo-owned 선언이다. 각 배열의id는 배열 안에서 유일한 stable ID이며 profile은 정확히 하나의 provider와 그 provider가 소유한 model을 참조한다.- provider는 CLI
command, bounded version/authentication probe, optional model target probe와 지원 capability를 선언한다. model probe를 생략하면 검증된 static model target 선언이 기준이며, probe를 선언하면 출력의 exact line과 target을 비교한다. - profile은 common CLI runtime args/resume args/mode/output format과 capability를 선언한다.
{{model}}은 factory가 provider-native model target으로 치환하고 catalog 원본은 변경하지 않는다.writable_root_confinement는 task isolation owner와 결합해 provider process의 writable root를 제한할 수 있는 profile만 선언한다. - loader는 YAML unknown field, multiple document, duplicate ID, dangling/cross-provider reference, invalid capability/mode/regex/timeout과 secret-like environment key를 거부하고 provider/model/profile을 ID 순서로 정규화한다.
- discovery는 PATH binary lookup, bounded version/authentication/model probe를 수행하고 공식 provider/model/profile ID와 함께
ready,missing_binary,unauthenticated,unsupported_model,probe_error중 하나를 반환한다. - 실행 불가 readiness는 각각
ErrBinaryMissing,ErrAuthenticationRequired,ErrModelUnsupported,ErrProbeFailed로errors.Is가능한ReadinessError를 반환한다. provider raw output, credential/token/header와 account identity는 redaction 후 bounded diagnostic에만 남긴다. - profile factory는 동일 ID의
ready결과만 받아 하나의 공통 CLI provider를 생성한다. runtime target은 profile ID이며 run/resume/cancel/status event·result metadata에provider_id,model_id,profile_id를 보존한다. - status는 predecessor 공통 CLI status API를 호출해 구조화 usage/quota를 얻고 discovery snapshot의 ID, readiness와 version을
AgentUsageStatus.Metadata에 병합한다. provider가 별도 status surface를 제공하지 못하면 직전에 검증한 readiness snapshot을status_probe=readiness_fallback으로 명시해 반환하며 ready로 새로 추정하지 않는다.
Typed failure codec
Failure은 stableFailureCode, 사용자/운영 진단message,retryable, 비민감 metadata를 가진다.- durable boundary는
EncodeFailure/DecodeFailure의 versioned JSON envelope를 사용한다. - 알 수 없는 미래 code는 실패를 버리지 않고
unknown으로 정규화하며 원래 code를 metadata에 보존한다. ErrRunCancelled와context.Canceled는cancelled,context.DeadlineExceeded는 retryabledeadline_exceeded다.- provider별 raw output, credential, token과 private endpoint를 failure metadata에 넣지 않는다.
- readiness error는 실행
Failurecodec과 별도 preflight 타입이다. readiness를 실행 실패처럼 codec에 강제로 넣지 않는다.
Node bridge 호환 규칙
- Node만 protobuf를 import하고
runtime_bridge.go에서RunRequest를 공통RunRequest로, 공통RuntimeEvent를 기존RunEvent로 변환한다. RunEvent.type, delta/message/error, usage, metadata, timestamp, session/background/node identity의 기존 wire 의미를 유지한다.- typed failure가 있어도 기존 Node wire
error에는 사람 읽기 가능한 message를 유지한다. protobuf 확장 없이 codec payload를 기존 field에 강제로 넣지 않는다. - config refresh registry swap, in-flight snapshot, admission ticket release 뒤 terminal flush ordering은 공통 package 이동으로 바뀌지 않는다.
금지 사항
packages/go/agentruntime과packages/go/agentprovider에서apps/*/internal또는 protobuf package를 import하지 않는다.- Node와 독립 host에 CLI process/session/emitter/status/failure 구현을 복사하지 않는다.
- unattended AgentTask에서 raw
ProfileProvider.Execute를 직접 호출하거나 invalid/stale Permit을 interactive fallback으로 우회하지 않는다. - canonical base, task root 밖 writable root, grant에 없는 worktree Git metadata root를 Permit에 포함하지 않는다.
- agent provider catalog를 기존 Edge provider-pool
NodeProviderConf/ModelCatalogEntryschema와 합치거나 서로의 ID 의미로 해석하지 않는다. - tracked catalog에 raw token, credential, authorization header, password 또는 secret-bearing environment 값을 넣지 않는다.
- discovery timeout/cancel을 ready로 간주하거나 unknown provider/model/profile을 fallback target으로 선택하지 않는다.
- readiness ID와 factory profile ID가 다르거나 ready가 아닌 profile로 provider를 생성하지 않는다.
- provider-specific session/conversation id를 공통 execution identity로 승격하지 않는다.
- terminal event를 둘 이상 내보내거나 terminal 뒤 delta를 노출하지 않는다.
- cancel과 terminate-session을 같은 lifecycle action으로 취급하지 않는다.
- 기존 Edge-Node wire를 공통 runtime 타입과 같게 만들기 위해 proto 의미를 변경하지 않는다.
- manual
StartIntent가 없는 ready project를 daemon start나 filesystem scan만으로 dispatch하지 않는다. - explicit predecessor 외 번호, 경로, write-set overlap/unknown에서 암묵 dependency를 만들지 않는다.
IsolationBackend,ProviderInvoker,Reviewer,Integrator가 없거나 실패했을 때 canonical workspace 직접 실행, review 생략, blind integration으로 fallback하지 않는다.- artifact/change-set/revision identity mismatch를 성공으로 정규화하거나 새 identity로 조용히 재발급하지 않는다.
- worker 완료 순서로 integration ordinal을 바꾸거나 terminal-deferred task 하나로 뒤 independent queue를 멈추지 않는다.
변경 시 확인할 코드/테스트
packages/go/agentruntime/*_test.gopackages/go/agentconfig/*_test.gopackages/go/agentprovider/catalog/*_test.gopackages/go/agentguard/*_test.gopackages/go/agenttask/*_test.gopackages/go/agentprovider/cli/*_test.gopackages/go/agentprovider/cli/status/*_test.goapps/node/internal/node/*_test.goapps/node/internal/adapters/config_set_test.goapps/node/internal/router/router_test.goapps/node/internal/bootstrap/module_test.gocmd/iop-provider-smoke/main.goconfigs/iop-agent.providers.yamlagent-contract/inner/edge-node-runtime-wire.md