개요
OpenArms의 기본 운용은 한 가지 원칙 위에 섭니다. 전경 터미널 세션이 권위입니다. 텔레그램 답장·칸반 카드·로그·게이트웨이 영수증만으로는 충분하지 않고, 에이전트가 실제로 일을 받고 답하는 전경 세션이 사실의 기준입니다.
이 장은 그 전경 세션을 떠받치는 스택을 하나씩 설명합니다. OpenArms는 provider-neutral
엔진(역할은 pm·worker, 둘 다 교체 가능)이고, 그 위에 교체 가능한 provider
어댑터(Hermes·AGY 등 외부 CLI)를 얹습니다. 따라서 “왜 이 도구인지 / 어떻게 구성하는지 /
릴리스에서 훅을 어떻게 거는지”를 도구별로 분리해 둡니다.
PM 운용 루프
사용자 의도
→ PM이 현재 보이는 표면들(로컬·[스레드](https://core.telegram.org/api/threads)·워커·라우트·전경·증거·정리 상태)을 판단
→ PM이 워커 계약 레인을 배정하거나 재사용
→ 워커가 공식 전경 CLI로 계약을 받음
→ 워커 증거가 같은 스레드로 되돌아옴
→ PM이 수용·재작업·보류·격리·정리 중 판단
→ 칸반·CLI 보드가 날 내부 없이 그 수명주기를 반영PM은 어떤 워커가 어떤 계약 아래 있는지, 왜 막혔는지, 다음 판단은 누구 몫인지, 어떤 증거가 주장을 강하게 만드는지를 사람 말로 유지합니다.
스택 한눈에
| 계층 | 도구 | 역할 | 제품 의존성인가 |
|---|---|---|---|
| 메시징 provider | Telegram + telethon (MTProto) | 사람 ↔ 시스템 입출구 | 현재 first adapter |
| 실행석(seat) | tmux | 전경 CLI를 붙잡아 두는 좌석 | 제품 정체성 아님, 현재 backend |
| provider CLI | Hermes(PM 역할) / AGY(worker 역할) | 실제로 일을 받는 에이전트 | 외부 CLI, OpenArms가 설치/패치 안 함 |
| 패키지 환경 | venv / uv + pip install -e | 엔진·테스트·리스너 실행 환경 | 필수 |
| 그래프 substrate | networkx (+ 선택 leidenalg) | wiki_chain 결정 노드 그래프 | 권장/선택 |
아래에서 각 도구를 (1) 선택 근거, (2) 구성 방법, (3) 릴리스 훅 연동 순서로 깊게 봅니다.
Telegram + telethon (MTProto) — 메시징 provider
왜 telethon(MTProto)인가
OpenArms의 사람 쪽 입출구는 텔레그램입니다. 텔레그램에 붙는 길은 두 가지인데, OpenArms는
HTTP Bot API가 아니라 telethon의 MTProto 경로를 1차로 택했습니다(필수 의존성으로 고정,
pyproject.toml/requirements-dev.txt가 권위).
대안 대비 장점:
- 다중 세션 동시성. Bot API는 같은 봇 인스턴스를 둘 띄우면 옛 인스턴스가 업데이트를 더는 못 받습니다. MTProto는 여러 세션을 동시에 살려 둘 수 있어, PM과 다수 worker가 각자 토큰으로 같은 채팅에 공존하는 “고용(employment)” 구조와 맞습니다.
- 소유자(user) 세션 기능. 봇 토큰으로는 할 수 없는 작업 — 특히 텔레그램 토픽(스레드) 생성/삭제 같은 owner 권한 — 을 같은 라이브러리에서 user 세션으로 처리할 수 있습니다. OpenArms의 레인 토픽 수명주기 관리가 여기에 의존합니다.
- 직결·대용량. TCP 직결이라 HTTP/JSON 오버헤드가 작고, 첨부 업/다운로드 한도가 Bot API(다운 20MiB·업 50MiB)보다 훨씬 큽니다(최대 2GiB). 워커가 증거로 큰 산출물을 돌려보낼 때 안전합니다.
단, 봇 토큰·chat destination은 owner-local 시크릿입니다. 레포에 절대 커밋하지 않고
.env.example만 추적합니다. 라이브 인스턴스의 토큰은~/.openarms/credentials/telegram.env같은 호스트-로컬 파일에서 읽습니다.
어떻게 구성하는가
- 자격 부트스트랩.
python3 openarms/paths.py bootstrap이~/.openarms(state·logs· credentials)를 만듭니다. 봇 토큰은~/.openarms/credentials/telegram.env에chmod 600으로 넣고, 셸에서set -a; . telegram.env; set +a로 export합니다. - 인바운드 경로. 가벼운 케이스는
getUpdateslong-poll로 새 메시지만 끌어옵니다. 봇을 재시작할 때 옛 백로그가 다시 주입되지 않도록 offset을 현재 max update_id+1로 초기화한 뒤 리스너를 띄웁니다. - owner 작업 경로. 토픽 생성/삭제처럼 user 권한이 필요한 작업은 telethon user 세션을 씁니다. 삭제는 항상 안전 절차 — 열거 → 보고 → 명시적 id 목록만 ledger 경유 삭제 — 를 따르고, blanket sweep은 금지합니다.
# offset 초기화 후 인바운드 리스너 기동 (setup_pm_seat.sh / run_pm_injector.sh 패턴)
export OPENARMS_HOME="$HOME/.openarms"
set -a; . "$OPENARMS_HOME/credentials/telegram.env"; set +a
# getUpdates?offset=-1 로 백로그 끝을 읽어 offset=max(update_id)+1 저장 후 리스너 nohup 기동릴리스에서 훅 연동
배포판에서 telethon은 두 자리로 들어갑니다.
- 엔진 의존성 훅: Hermes provider 브리지(
plugin.yaml)가 `pip_dependencies:- telethon>=1.36`을 선언합니다. Hermes의 플러그인 로더가 브리지를 적재할 때 telethon을 함께 보장하므로, 릴리스 측에서 별도 수동 설치가 필요 없습니다.
- 인바운드 주입 훅: 리스너는 텔레그램 답장을 스크랩하지 않고, 인바운드 텍스트를 그대로 전경 CLI 입력으로 흘려보냅니다(아래 tmux 절). 즉 telethon은 “사람 → seat” 한 방향 파이프이고, “seat → 사람”은 CLI provider 쪽 훅이 책임집니다.
설치/의존성 계층의 권위 문서는 빠른 시작·설치를, 메시징 provider 경계는 메시징 플랫폼을 보세요.
tmux — 실행석(execution seat)
왜 tmux인가
provider CLI(Hermes/AGY/Claude)는 사람이 붙어 있지 않아도 살아 있는 전경 세션이 있어야 합니다. 그 세션을 붙잡아 두는 좌석이 현재는 tmux입니다.
- 분리(detach)된 채로 살아 있음.
tmux new-session -d로 사람 없이 백그라운드 세션을 만들고, 호스트가 재부팅돼도 같은 구조를 다시 세울 수 있습니다. - 붙지 않고 입력 주입.
tmux send-keys -t <pane>로 세션에 붙지 않은 채 한 줄을 넣을 수 있습니다 — 이게 “텔레그램 메시지를 CLI 입력으로 흘리는” 자동화의 핵심입니다. - 검사 가능한 표면. pane 내용을 캡처해 전경이 실제로 무엇을 받았는지 확인할 수 있어, “전경이 권위”라는 원칙을 기계적으로 검증할 수 있습니다.
중요: tmux는 제품 정체성이 아니라 현재 구현의 backend입니다. 0.0.2.x 이후 방향은
execution_seat를 tmux/pty/ssh/windows-terminal/provider-native로 추상화하는 것입니다. 그래서 Windows native는 비주력이고(tmux가 기본 환경이 아님), WSL/Linux/macOS/Mac VM이 선호 환경입니다.
어떻게 구성하는가
좌석은 “세션·pane·실행 명령·리스너 명령”의 묶음이고, 라이브 구조는 손으로 패치하지 않고
config manifest(config/fleet-topology.live.json, gitignore)에서 복원합니다. (provider,
role) 결합은 코드가 아니라 manifest에만 있습니다.
# 부팅 후 전경 토폴로지 복원 (scripts/fleet_restore.py)
python scripts/fleet_restore.py # DRY-RUN 기본: 계획만 출력, 아무것도 안 건드림
python scripts/fleet_restore.py --apply # 실제 생성/respawn/재시작안전 규율(좌석을 깨뜨리지 않기 위한 핵심):
- dry-run이 기본.
--apply없이는 절대 손대지 않습니다. - 살아 attach된 세션은 손대지 않습니다(noop). 스크립트는 라이브 세션을 절대
kill-session하지 않습니다(kill하면 attach가 끊겨 launcher가 고아가 됩니다). 유일한 파괴적 tmux 연산은 pane 프로세스가 죽어 있을 때만 쓰는respawn-pane -k입니다. - 리스너는 정확한
.py만 겨냥.pkill -f는 해당 역할의 정확한 스크립트만 끄고, 전경 pane은 절대 건드리지 않습니다.
릴리스에서 훅 연동
배포판에서 tmux는 provider 브리지가 입력을 주입하는 좌석으로 노출됩니다.
- Hermes 브리지
plugin.yaml은requires_env에OPENARMS_TMUX_BIN을 둡니다 — 릴리스가 어떤 tmux 바이너리/소켓을 좌석으로 쓸지 환경으로 못박습니다. - 인바운드 주입 훅의 실체는 두 줄짜리 send-keys입니다(텍스트와 Enter를 분리해, 본문이 키로 해석되지 않게 함):
# inject_listener.py — 텔레그램 인바운드를 PM seat pane으로 주입
subprocess.run([TMUX, "send-keys", "-t", PANE, "-l", text], check=False) # 본문은 literal
subprocess.run([TMUX, "send-keys", "-t", PANE, "Enter"], check=False) # 그다음 EnterOPENARMS_PM_PANE(기본 fleet:openarms-pm.0)·OPENARMS_TMUX로 어느 세션:pane에 넣을지
지정합니다. 좌석/복원의 전체 규약은 레퍼런스에 정리돼 있습니다.
Hermes / AGY — provider CLI 어댑터
왜 이 분리인가 (role ≠ provider)
OpenArms는 한 provider를 한 역할에 못박지 않습니다. 역할은 pm·worker이고 교체
가능하며, 코드 어디에도 hermes == PM / antigravity == worker 가정이 없습니다. 라이브
testbed의 현재 바인딩은 인스턴스일 뿐 제품 기본값이 아닙니다.
- Hermes = 현재 PM. 중립 PM 리스너(스레드 라우터)를 구동합니다.
- AGY(Antigravity) = 현재 worker. worker 인그레스/이그레스/트랜스크립트 경로를 구동합니다.
장점: PM이던 provider가 내일 worker가 될 수 있고(그 반대도), manifest의 role 필드만 바꾸면
됩니다. 제품 기본형은 단일 PM(PM1)이고, PM이 worker를 여럿 “고용”하는 다중 토큰 구조가 라이브
구성입니다. 이 중립 엔진이 OpenArms를 특정 벤더에 묶이지 않게 합니다.
어떻게 구성하는가
- 고용 교체. manifest(
config/fleet-topology.live.json)에서role필드를 바꿉니다.mcp_config는 worker 전용이라 worker가 되는 쪽 엔트리로 옮깁니다. 그리고fleet_restore.py를--dry-run먼저, 그다음--apply로 새 구조를 올립니다. - provider별 자산은
integrations/<provider>/에 격리. 페르소나·플러그인·스킬·config 템플릿·훅이 거기 있고, 중립 엔진(openarms/)은 역할·라우팅·계약 용어만 씁니다. 중립 코어에 한 provider의 pane 관용구나 깨지기 쉬운 키워드 휴리스틱을 절대 하드코딩하지 않습니다. - provider CLI 자체는 외부. OpenArms는 Hermes/AGY/Claude를 설치하거나 패치하지 않습니다.
바인딩은
OPENARMS_HERMES_*/OPENARMS_AGY_*환경키(점차 중립OPENARMS_*역할키로 이행)로 주입합니다.
릴리스에서 훅 연동
두 provider는 코어를 패치하지 않고 각자의 공식 플러그인 훅 층으로 OpenArms를 끼웁니다.
Hermes — 공식 플러그인 브리지(integrations/hermes/openarms_contract_bridge/).
plugin.yaml이 네 개의 훅을 선언하고, Hermes의 실제 플러그인 로더가 이를 적재합니다(코어
패치 없음, 사이드카 모니터 없음):
# integrations/hermes/openarms_contract_bridge/plugin.yaml
hooks:
- pre_gateway_dispatch # 디스패치 전 라우팅 결정 주입
- transform_llm_output # LLM 출력 변형(증거/계약 형식 맞춤)
- cli_input_before_submit # CLI 제출 직전 입력 미러/가공
- cli_response_after_render # 렌더 후 응답 미러(seg → 사람)
requires_env:
- OPENARMS_ROOT
- OPENARMS_HERMES_ROUTE_TARGET
- OPENARMS_HERMES_ROUTE_EXECUTOR
- OPENARMS_TMUX_BIN브리지는 레포 밖(예: ~/.hermes/hermes-agent/plugins/...)에 설치돼도 OPENARMS_ROOT와
잘 알려진 체크아웃 위치를 차례로 시도해 openarms 패키지를 import할 수 있게 자기-부트스트랩
합니다 — 그래서 env 없이 respawn돼도 openarms_thread_* 네이티브 도구가 사라지지 않습니다.
AGY — Antigravity-native 플러그인 번들(integrations/antigravity/openarms_provider/).
plugin.json·commands/·skills/와 함께 hooks/openarms.json이 worker 바인딩을 선언합니다:
// integrations/antigravity/openarms_provider/hooks/openarms.json
{
"openarms_provider_bridge": {
"role": "worker",
"provider": "antigravity",
"authority": "official_antigravity_cli_surface",
"attachment_policy": "use_native_attachment_surface_before_fallback"
}
}핵심 규약 두 가지: 권위는 공식 AGY CLI 표면이고, 첨부는 네이티브 첨부 표면을 폴백보다 먼저 씁니다. 즉 worker 증거는 CLI의 진짜 출구로 나가야 권위로 인정됩니다.
provider 어댑터 아키텍처 전반은 연동을, 핵심 역할 개념은 핵심 개념을 참고하세요.
venv / uv — 패키지·테스트 환경
왜 격리 환경인가, 왜 uv를 곁들이나
엔진·테스트·리스너는 telethon·jsonschema·pytest 같은 고정 의존성을 깔끔히 깔아야 하고, provider별 다른 인터프리터와 섞이면 안 됩니다(예: Windows의 hermes-agent venv에는 pytest/telethon이 없어 테스트 수집 자체가 안 됨). 그래서 레포 루트에서 격리된 venv로 돌리는 게 권위입니다.
- venv: 파이썬 내장이라 아무 추가 설치 없이 어디서나 동일하게 동작 — 기준선입니다.
- uv: Rust로 작성된 설치/해석기로 pip 대비 수십~수백 배 빠르고, 환경 생성·활성화·락파일을
자동 관리합니다. CI나 잦은 재설치에서 시간을 크게 줄여 줍니다. pip 호환 인터페이스라 기존
pip install -e .[dev]워크플로를 그대로 두면서 속도만 얻을 수 있습니다.
어떻게 구성하는가
# 기준선 (venv)
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pytest -q
# 더 빠르게 (uv, pip 호환)
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"테스트는 **WSL/Linux/macOS의 python3**로 레포 루트에서 돌립니다. 한글 리터럴이 깨지지 않게
PYTHONIOENCODING=utf-8을 둡니다:
PYTHONIOENCODING=utf-8 python3 -m pytest -q의존성 3계층(권위는 pyproject.toml/requirements-dev.txt):
- 필수 — 없으면 스위트가 안 돔:
pytest,telethon,jsonschema. - 권장 — 없으면 실패가 아니라 SKIP되어 커버리지가 조용히 빠짐:
networkx(wiki_chain 그래프 substrate),pyyaml(node_contract 권위 렌더 경로). - 선택 — 없어도 통과, 분기만 켜짐:
leidenalg+python-igraph(Leiden 분기), 래스터라이저resvg_py또는cairosvg(graph_network PNG 단계).
릴리스에서 훅 연동
- preflight 훅. 테스트를 돌릴 바로 그 인터프리터로
python3 scripts/dev_preflight.py를 실행하면 import-only로 PASS/MISSING을 찍습니다(설치/네트워크 없음). 필수 의존성이 다 있으면 exit 0, 하나라도 없으면 비-0 — 릴리스 게이트에 그대로 걸 수 있습니다. - plugin 의존성 훅. provider 브리지의
plugin.yaml이pip_dependencies로 telethon을 선언하므로, 플러그인 로더가 적재 시점에 환경을 보강합니다 — venv/uv로 만든 환경 위에 provider 계층이 자기 의존성을 얹는 구조입니다.
마이크로 버전 증거 슬라이스
작업은 v0.0.0.x 마이크로 버전으로 나아갑니다. 한 슬라이스는 커밋 크기의 증거 단위이며,
증거 강도가 오르지 않거나 같은 블로커가 반복되면 멈추거나 보류합니다.
전형적인 한 슬라이스의 표면 흐름:
- PM이 텔레그램으로 의도를 받음 — telethon 인바운드가
send-keys로 PM seat에 주입. - PM이 worker 레인을 배정 — manifest의 (provider, role)과 계약 환경키로 결정.
- worker(AGY)가 공식 CLI 표면에서 계약을 받아 증거를 같은 스레드로 반환 — 네이티브 첨부 우선.
- PM이 수용/재작업/보류/격리/정리 판단 — 칸반·CLI 보드가 그 수명주기를 반영.
dev_preflight.py→pytest -q로 슬라이스의 증거를 고정, 부팅 후엔fleet_restore.py로 토폴로지를 그대로 복원.