개요
OpenArms를 떠받치는 다섯 개념입니다. 모두 한 가지를 향합니다 — 사람이 날 내부를 보지 않고도 일의 상태를 이해하고 판단할 수 있게 하는 것.
CLI AI 에이전트와 사람의 협업 도구
에이전트가 실제로 일을 받는 전경 터미널 세션과, 사람이 지켜보는 표면을 항상 일치시킵니다. 전경 세션이 권위이고, 표면은 그 권위를 비추는 얇은 창입니다.
프로바이더 중립 조율
역할과 프로바이더를 분리합니다.
PM 역할 → 프로바이더 중립 판단·수명주기·보드·정리 정책
워커 역할 → 프로바이더 중립 증거·결과 제출
프로바이더 → 그 역할을 현재 맡은 교체 가능한 CLI·메신저 구현Hermes와 AGY는 현재 증명용 프로바이더 한 쌍일 뿐, 계약 모델은 에이전트 중립입니다. 다른 CLI 에이전트도 어댑터만 붙으면 같은 역할을 맡습니다.
칸반 표면
칸반은 단순한 할 일 목록이 아니라 의사결정 도출 보드입니다. 카드는 결정이고, 수명주기 레인(접수→진행→검증→완료→보관)을 흐르며, 날 내부 id나 토큰을 표면에 드러내지 않습니다.
Vector-free LLM-wiki 기억
기억은 두 종류입니다. 작업·일화 기억은 각 스레드의 살아있는 현재 상태이고, 장기·의미 기억은 큐레이션된 위키와 그래프입니다. 검색은 그래프 탐색 + BM25 어휘 일치로 하며, 클라우드 벡터 임베딩을 쓰지 않습니다. 삭제 대신 supersede로 과거를 보존합니다.
프로젝트별 경계와 커뮤니티-노드
기억은 프로젝트 루트 절대경로로 칸을 나눠 무관한 프로젝트의 검색·군집(community detection)이 섞이지 않게 합니다. 결정 그래프는 커뮤니티-노드로 군집되어 “대륙 지도”로 시각화됩니다(파생 뷰, 식별자 아님).
서비스와 도구 — 선택 근거·구성·배포판 훅
위 개념을 실제로 돌리는 구체 스택입니다. 각 도구마다 왜 골랐는지, 어떻게 구성하는지,
배포판(릴리스)에서 훅을 어떻게 연동하는지를 짧은 예시와 함께 적습니다. 권위는 항상
레포의 pyproject.toml / requirements-dev.txt / config/*.example.json이고, 이 문서는
그 위의 사용자용 설명입니다.
한눈에 보는 역할 분담
역할(durable) 현재 프로바이더(replaceable) 구성 자리
------------------- ---------------------------- -------------------------
PM (판단·보드·정리) Hermes CLI fleet 매니페스트 role:"pm"
워커 (증거·결과) AGY(Antigravity) CLI 매니페스트 role:"worker"
전경 실행석 [tmux](https://github.com/tmux/tmux) 세션/페인 execution_seat backend
메시징 인그레스 Telegram ([telethon](https://docs.telethon.dev/), [MTProto](https://core.telegram.org/mtproto)) provider setup 디스크립터
파이썬 런타임 격리 [venv](https://docs.python.org/3/library/venv.html) (+ [uv](https://docs.astral.sh/uv/) 권장) .venv / pyproject 의존성핵심 불변식: 코드 어디에도 hermes == PM / antigravity == worker로 박힌 곳이 없습니다.
(provider, role) 짝은 오직 매니페스트(config/fleet-topology.live.json)의 role 필드에만
삽니다. 역할 교체는 그 필드 한 줄을 바꾸고 복원 스크립트를 다시 도는 일입니다.
tmux — 전경 실행석(execution seat) 백엔드
왜 이걸 선택했는지
OpenArms의 권위는 “에이전트가 실제로 답을 만드는 전경 CLI 세션”입니다. 그 세션을 사람이
보지 않을 때도 살아 있게 붙잡아 두고, 입력을 넣고, 출력을 읽을 수 있어야 합니다. 대화형 CLI
(REPL·TUI·에이전트 셸)는 진짜 터미널(pty)을 요구해서 일반 bash 파이프로는 몰 수 없습니다.
tmux는 이걸 정확히 해결합니다 — detached 세션이 백그라운드에서 pty를 들고 살아 있고,
send-keys로 입력을 주입하고 capture-pane으로 화면을 읽습니다(tmux man page,
Terminal Guide).
대안 대비:
screen— 비슷하지만 스크립트 친화 API가 약하고 페인 캡처가 거칠다.expect/pexpect — pty는 잡지만 사람이 중간에 붙어서(attach) 직접 보는 협업 표면이 없다. OpenArms는 “사람이 지켜보는 전경”이 제품 정체성이라 attach 가능성이 필수.- 순수 SSH/터미널 멀티플렉싱 없음 — 재부팅·detach 후 세션이 사라진다.
다만 tmux는 제품 의존성이 아니라 현재 구현의 backend입니다. 0.0.2.x 이후 방향은
execution_seat를 tmux/pty/ssh/windows-terminal/provider-native로 추상화해 tmux를 정체성으로
굳히지 않는 것입니다. Windows native는 비주력(tmux가 기본 환경이 아님)이고, WSL/Linux/macOS/
Mac VM이 선호 환경입니다.
어떻게 구성하는지
전경 토폴로지는 손으로 패치하지 않고 매니페스트에서 재현합니다. tmux 바이너리와 소켓은
매니페스트의 tmux 블록에 둡니다(config/fleet-topology.example.json 참조).
// config/fleet-topology.live.json (gitignore됨, per-host)
{
"schema_version": "openarms.fleet_topology.v1",
"tmux": { "bin": "/opt/homebrew/bin/tmux", "socket": "openarms" },
"roles": [
{ "role": "pm", "provider_id": "hermes", "session_name": "hermes-pm",
"launch_cmd": "hermes", "listener_cmd": "python3 scripts/...",
"registry_path": "state/hermes-pm-thread-router-live.json" }
]
}세션을 직접 띄울 때의 표준 패턴(외부 베스트 프랙티스와 동일):
# 1) detached 세션 생성 + CLI 기동
tmux -S openarms new-session -d -s hermes-pm 'hermes'
# 2) 초기화 대기 — new-session 직후 capture는 빈 화면이라 100~500ms 둔다
# (foreground 로직은 sleep 대신 idle-marker 폴링으로 settle을 판단)
# 3) 입력 주입
tmux -S openarms send-keys -t hermes-pm '작업 시작해줘' Enter
# 4) 화면 읽기 (진단/증거용 — 라이브 응답 권위는 아님)
tmux -S openarms capture-pane -p -J -t hermes-pm -S -200레포 안에서 이 호출은 scripts/fleet_restore.py의 TmuxClient가 감싸며, 모든 tmux 호출은
[tmux.bin, "-S", tmux.socket]를 prefix로 붙입니다. 안전 규율이 코드에 박혀 있습니다:
--dry-run이 기본. 실제로 손대려면--apply를 줘야 한다.- 세션은 있으면 건드리지 않고(noop), 없을 때만
new-session -d+send-keys로 만든다. 살아서 attach된 세션을 절대kill-session하지 않는다(attach가 끊기면 “[Process completed]“로 런처가 고아가 됨). 유일한 파괴적 op는 페인의 프로세스가 죽었을 때의respawn-pane -k뿐.
배포판에서 훅을 어떻게 쓰는지
릴리스에서 tmux는 두 곳에 훅됩니다.
-
재부팅 후 복원 훅 — 호스트(예: Mac VM)에서
scripts/fleet_restore.py가 매니페스트의launch_cmd/listener_cmd를 tmux로 재생합니다. 매니페스트 해석 순서는--manifest>$OPENARMS_FLEET_TOPOLOGY>config/fleet-topology.live.json.python scripts/fleet_restore.py # DRY-RUN: 계획만 출력 python scripts/fleet_restore.py --apply # 실제 생성/respawn/listener 재기동파서는
launch_cmd/listener_cmd에gateway가 들어가면 하드 거부합니다(은퇴한 게이트웨이 부활 방지). -
전경 신호 어댑터 훅 —
ProviderAdapter의submit()/capture()가 tmux send/capture로 기본 구현되어, 공유 엔진이 프로바이더를 몰라도 입력을 넣고 idle/busy를 읽습니다. 단,tmux capture-pane텍스트는 **진단·증거 등급(D)**일 뿐 라이브 Telegram 응답 권위가 될 수 없습니다(전경 게이트웨이 권위 규칙). 설치·운영 자리 구성은 빠른 시작·설치를 보세요.
Telegram + telethon — 메시징 인그레스(MTProto)
왜 이걸 선택했는지
OpenArms는 사람이 “어디서든” 일의 상태를 보고 판단을 내릴 표면이 필요하고, 그 첫 어댑터가 Telegram입니다. 핵심 선택은 HTTP Bot API가 아니라 MTProto 클라이언트(telethon)를 쓴다는 점입니다(Telethon 문서, mtcute 가이드):
- 봇 계정뿐 아니라 유저 계정도 다룬다. 포럼 토픽(스레드) 열거·삭제 같은 소유자급 작업은 봇 토큰만으로는 안 되고 유저 세션이 필요하다. 토픽 정리(over-delete 방지) 운영이 여기 의존.
- 폴링/웹훅 없이 서버에 직접 연결 — HTTP·JSON 오버헤드가 적고 실시간 이벤트가 자연스럽다.
- 파일 한도 — Bot API는 다운로드 20MiB/업로드 50MiB 제한, telethon(MTProto)은 최대 2GiB. 미디어 첨부 운영에 여유.
- 공개 API에 갇히지 않음 —
messages.getMessages등 Bot API가 막아둔 호출까지 쓴다.
Bot API의 장점(간단함·웹훅·쉬운 포매팅)은 인정하지만, OpenArms가 요구하는 토픽 수명주기와
소유자 권한 때문에 MTProto가 권위 경로입니다. 그래서 telethon은 core 필수 의존성입니다
(pyproject.toml: telethon>=1.24,<2; 라이브 VM은 1.43.x). 1.24 floor는 Python 3.9를 아직
지원하는 보수적 하한입니다.
중요한 구분: 봇 토큰은 워커 정체성이 아닙니다. 한 토큰 = 하나의 Telegram-facing 서비스 정체성이고, 그 뒤에 여러 OpenArms 워크플로 슬롯과 여러 프로바이더 리스가 붙습니다.
one bot token -> one Telegram ingress identity -> many workflow slots -> many provider leasesdurable 정체성은 slot_id/workflow_id/provider_id/role_lease_id로 관리하고,
OA(AGY)1 같은 라벨은 표시용일 뿐입니다.
어떻게 구성하는지
설치 위저드 core는 특정 프로바이더로 분기하지 않습니다. 프로바이더 중립 setup-dispatch
(openarms/messaging/provider_setup.py)가 디스크립터 + 레지스트리를 정의하고, Telegram은
그저 첫 등록 프로바이더입니다.
# 코어 CLI는 provider-generic — telegram은 위치 인자일 뿐
openarms setup telegram이 명령은 get_provider_setup("telegram")으로 디스크립터를 찾아 필드 위저드를 한 바퀴 돌고,
값을 owner-local 비밀 파일에 씁니다. 비밀(봇 토큰·chat destination)은 레포에 커밋 금지이며
.env.example을 참조합니다. 공개 디스크/스키마 계약은 비밀이 아니라 필드 선언만 담습니다:
- 스키마:
schemas/messaging/messaging_provider_setup_v1.schema.json - Telegram 인스턴스:
config/providers/telegram/setup_manifest.json(8개 필드 선언, PUBLIC-SAFE)
테스트(tests/test_setup_onboarding_cli.py)가 매니페스트 필드와 TELEGRAM_SETUP_FIELDS가
순서까지 정확히 일치하는지(anti-drift) 검증합니다. 새 메시징 프로바이더는 core 수정 없이
디스크립터를 import 시점에 register_provider_setup(...)로 등록하면 openarms setup <id>가
바로 동작합니다.
배포판에서 훅을 어떻게 쓰는지
라이브에서 telethon은 listener 프로세스로 훅됩니다. fleet 복원 훅이 각 역할의 listener_cmd를
독립 프로세스로 (재)기동합니다 — nohup … < /dev/null & 형태이고, pkill -f는 그 역할의
정확한 .py 스크립트만 겨냥해 전경 페인을 절대 죽이지 않습니다.
배포 후 워커(AGY)의 토픽 도구는 telethon이 아니라 stdlib Bot API POST로 가는 가벼운 경로를 쓰되, 봇 토큰·chat id는 항상 런타임에 AGY env 키에서 resolve합니다(하드코딩 금지). PM(Hermes) 쪽은 MTProto 유저 세션으로 토픽을 열거·삭제하는데, 이는 명시적 id 리스트만 지우는 ledger 경로로만 허용됩니다(blanket sweep 금지). 인그레스→응답 라운드트립 권위는 전경 receipt 게이트를 통과해야 하며, 자세한 운영은 메시징 플랫폼을 보세요.
Hermes — 현재 PM 프로바이더 (전경 게이트웨이 훅)
왜 이걸 선택했는지
PM 역할(판단·조율·칸반/제어 상태 소유)을 처음 증명한 CLI가 Hermes입니다. 고른 결정적 이유는
Hermes가 공식 hook/event API를 노출한다는 점입니다 — 즉 폴링(pane 캡처)에만 의존하지 않고,
“입력 수락”과 “출력 완료”를 push 이벤트로 받을 수 있습니다. 이게 등급 A 영수증
(Hermes official hook/event for accepted input and completed output)을 가능케 해서, 라이브
Telegram 응답을 안전하게 게이트할 수 있습니다. tmux 스크롤백 같은 등급 D 증거로는 PM 답을
낼 수 없다는 게 OpenArms의 불변식이라, hook을 가진 프로바이더가 첫 증명 대상이 된 것입니다.
단, Hermes == PM은 어디에도 박혀 있지 않습니다. Hermes는 “PM-capable 구현 하나의 현재
라벨”일 뿐이고, 매니페스트에서 role만 바꾸면 워커로 강등되거나 다른 CLI가 PM이 됩니다.
Hermes는 외부 CLI라 OpenArms가 설치·패치하지 않습니다.
어떻게 구성하는지
OpenArms는 Hermes에 얇은 사이드카 플러그인(integrations/hermes/openarms_contract_bridge/)
으로 붙습니다. 플러그인은 Hermes가 주는 ctx에 자신을 등록합니다. 등록은 방어적이라, 해당
훅을 Hermes가 지원할 때만 붙습니다(_register_if_supported는 register_hook이 없으면 조용히
건너뜀):
# integrations/hermes/openarms_contract_bridge/__init__.py (register 엔트리)
def register(ctx):
_register_if_supported(ctx, HERMES_HOOK_PRE_GATEWAY_DISPATCH, pre_gateway_dispatch)
_register_if_supported(ctx, HERMES_HOOK_TRANSFORM_LLM_OUTPUT, transform_llm_output)
if _register_foreground_hooks_enabled():
_register_if_supported(ctx, HERMES_HOOK_CLI_INPUT_BEFORE_SUBMIT, cli_input_before_submit)
_register_if_supported(ctx, HERMES_HOOK_CLI_RESPONSE_AFTER_RENDER, cli_response_after_render)
registered = _register_thread_tools(ctx) # ctx.register_tool로 네이티브 스레드 도구 노출훅 이름은 상수로 고정되어 있습니다:
pre_gateway_dispatch 라우트 전 결정 — OpenArms가 가로채 own routing/lease/receipt 적용
transform_llm_output 출력 변환 — role 헤더 부착·공개안전 포매팅
cli_input_before_submit 전경 입력 미러
cli_response_after_render 전경 응답 push(등급 A 영수증 소스)pre_gateway_dispatch는 OpenArms가 라우팅을 책임지면 skip을 돌려줘서 Hermes 답이 두 번
나가지 않게 합니다(authority split 방지). 또 인바운드 메시지가 도착한 토픽 id를
_CURRENT_INBOUND_THREAD_ID에 잡아두어 스레드 도구가 번호 없이 “이 쓰레드”를 해석합니다.
배포판에서 훅을 어떻게 쓰는지
배포판에서 이 브리지는 Hermes의 플러그인 로더가 register(ctx)를 호출하면 자동 연동됩니다.
전경 훅(입력/응답)은 환경 게이트(_register_foreground_hooks_enabled())로 켜지므로, 릴리스
프로파일에서 전경 미러를 끄고 싶으면 그 게이트만 닫으면 됩니다. 스레드 생성/삭제/이름변경은
브리지 전체가 완성되길 기다리지 않고 ctx.register_tool로 스코프된 도구로 노출되어, PM이
소유·스코프·프로파일·프로바이더 역량·표면 증거에서 실행 또는 거부를 판단합니다(영구 거부
템플릿 금지). 사이드카 불변식: 브리지는 답을 운반할 뿐 PM 답을 발명하지 않습니다.
AGY(Antigravity) — 현재 워커 프로바이더 (MCP 도구 훅)
왜 이걸 선택했는지
워커 역할(스코프된 일을 받아 증거·결과 제출)을 처음 증명한 CLI가 AGY입니다. Hermes와 다른 연동 모양을 일부러 골랐습니다 — AGY는 hook push 대신 MCP(Model Context Protocol) 도구를 네이티브로 부르는 경로가 자연스럽습니다. 이 둘을 한 쌍으로 쓰면 “push 훅을 가진 프로바이더”와 “MCP/pane 폴링 프로바이더”가 같은 공유 엔진을 똑같이 구동한다는 boundary를 실제로 증명합니다 (어댑터 플러그인 아키텍처 결정 #1: hook은 optional 강화일 뿐).
어떻게 구성하는지
AGY 어댑터는 의존성 0의 stdio JSON-RPC MCP 서버입니다
(integrations/antigravity/adapter/openarms_worker_threads_mcp.py). 서드파티 없이 stdlib +
프로바이더 중립 openarms.telegram_topic_owner_tools만 씁니다. 컴파일된 agy CLI가
~/.gemini/config/mcp_config.json을 통해 이 서버를 로드하고 도구를 네이티브 호출합니다.
봇 토큰·chat id는 런타임에 AGY env 키에서 resolve합니다(하드코딩 금지).
노출 도구:
openarms_thread_ping 도달성 프로브(마스킹된 토큰+chat)
openarms_thread_list 워커 토픽 열거(inventory → registry fallback)
openarms_thread_delete 단일 포럼 토픽 삭제 — confirm_permanent 플래그로 가드
openarms_thread_delete_batch 복수 id 일괄 삭제(snapshot-once·cap·registry soft-prune)
openarms_thread_cleanup_scan id-only 열거 별칭(AGY DM은 토픽 내용 읽기 불가)AGY의 사설 DM은 is_forum=false라 MTProto 토픽 디렉터리가 0을 돌려줍니다. 그래서 생성된
토픽의 유일한 durable 기록은 라우터 레지스트리의 누적 dynamic_bindings이고, 경로는
OPENARMS_AGY_THREAD_REGISTRY(기본 state/agy-pm-thread-router-live.json)에서 resolve합니다.
없거나 비면 graceful하게 [](리스너가 바인딩을 채울 때까지 0 보고).
배포판에서 훅을 어떻게 쓰는지
배포판 연동은 mcp_config.json에 서버 엔트리를 한 줄 거는 일입니다. 라이브 설정의 스크립트
엔트리포인트가 계속 풀리도록, 옛 경로 scripts/openarms_worker_threads_mcp.py에 thin shim이
남아 있습니다. Telegram을 건드리지 않고 env resolve와 tools/list 모양만 검증하려면:
python3 integrations/antigravity/adapter/openarms_worker_threads_mcp.py --selftest매니페스트에서 mcp_config는 워커 전용 필드입니다. 역할을 맞바꾸면 mcp_config를 새
워커 엔트리로 옮기고, 새 PM이 된 쪽에서는 떼어냅니다. 워커 답은 active 프로파일이 명시적으로
직접 대화를 켜고 워커 레인이 자체 responder inventory + receipt 게이트를 갖추지 않는 한 PM
판단을 우회해 직접 라우팅되지 않습니다. 워커 연동 전반은 연동을 보세요.
venv (+ uv) — 파이썬 런타임 격리
왜 이걸 선택했는지
OpenArms는 멀티-호스트(Windows 개발 + WSL + Mac VM 라이브)에서 같은 의존성으로 재현 가능한
파이썬 환경이 필요합니다. 기본 도구는 stdlib venv입니다 — 추가 설치 없이 어디서나 있고,
pyproject.toml의 의존성과 곧장 맞물립니다. 권위는 코드 메타데이터이지 손으로 만든 환경이
아닙니다.
빠른 반복·결정적 lockfile이 필요할 땐 uv(Astral의 Rust 구현)를 권장합니다. uv는 환경 생성이
venv보다 대략 200배, 설치·해석이 pip보다 10~100배 빠르고, uv.lock으로 정확한 버전을 고정해
호스트 간 일관성을 보장합니다(Real Python: uv vs pip,
Astral uv 문서). 같은 pyproject.toml을 읽으므로
도입 비용이 낮습니다. 주의: uv/poetry가 관리하는 환경에 pip install을 직접 박으면 resolver와
lock을 건너뛰어 다음 uv sync가 변경을 덮어씁니다 — 반드시 프로젝트 도구(uv add)로 갑니다.
테스트 러너는 **WSL/Linux/macOS의 python3**입니다. Windows의 hermes-agent venv
(C:\Users\<user>\AppData\Local\hermes\hermes-agent\venv\Scripts\python.exe)는 지원 러너가
아닙니다 — 거기엔 pytest/telethon이 없어 수집 자체가 안 됩니다.
어떻게 구성하는지
# 표준(stdlib) 경로
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]" # editable + dev extras
PYTHONIOENCODING=utf-8 python3 -m pytest -q # 한글 리터럴 출력 깨짐 방지
# uv 경로(권장, 더 빠르고 lock 가능)
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"의존성은 3계층입니다(권위는 pyproject.toml/requirements-dev.txt):
- 필수 — 없으면 스위트가 안 돈다:
pytest,telethon,[jsonschema](https://json-schema.org/). - 권장 — 없으면 실패가 아니라 SKIP되어 커버리지가 조용히 빠진다:
[networkx](https://networkx.org/)(wiki_chain 그래프;importorskip로 게이트),pyyaml(node_contract YAML-safe 렌더). - 선택 — 없어도 통과, 분기만 추가:
leidenalg+python-igraph(Leiden 군집), 래스터라이저resvg_py/cairosvg(그래프 PNG 단계).
설치한 그 인터프리터로 import-only 점검을 돌립니다(네트워크·수집 없음):
python3 scripts/dev_preflight.py # 필수 다 있으면 exit 0, 하나라도 없으면 비-0배포판에서 훅을 어떻게 쓰는지
릴리스에서 venv는 CI 게이트 훅으로 연동됩니다. CI는 .[dev]를 설치(pip install -e ".[dev]")하고 매트릭스(windows-latest, ubuntu-latest; macOS는 아직 약함)에서 스위트를
돕니다. 콘솔 엔트리포인트는 pyproject.toml의 [project.scripts]가 openarms = "openarms.cli:main"으로 박아, 설치된 환경에서 openarms doctor/openarms setup …이
python -m openarms와 동일하게 동작합니다. 라이브 VM은 PYTHONPATH=<repo root>로
python3 scripts/...를 직접 도는 모양도 보존되어, 패키지 discovery가 openarms*와 PEP 420
namespace scripts*를 둘 다 포함합니다. 설치 절차 전체는 빠른 시작·설치에 있습니다.
서비스 경계 요약
권위(authority) 이 도구가 제공 이 도구가 절대 하지 않음
--------------------- ----------------------------- -----------------------------
tmux detached pty 좌석·입력·캡처 라이브 응답 권위(D등급 증거뿐)
telethon/Telegram MTProto 인그레스·토픽·미디어 워커 정체성(토큰≠워커)
Hermes(PM) 판단·보드·라우팅 결정 답을 발명(사이드카는 운반만)
AGY(워커) 증거·결과·스코프 스레드 도구 PM 판단 우회(프로파일 게이트 없이는)
venv/uv 재현 가능한 격리·CI 게이트 프로바이더 CLI 설치/패치모든 (provider, role) 결합은 매니페스트 한 곳에만 있습니다. 그래서 위 어떤 프로바이더든 어댑터/매니페스트만 바꾸면 교체됩니다 — 이게 프로바이더 중립 조율의 실제 모습입니다.