이 레포가 무엇을 하는 물건인가.
보통의 스마트홈 앱은 사람이 "거실 불 켜"라고 일일이 명령해야 움직인다. Anima는 거꾸로 묻는다 — "네가 가진 기기가 뭐야? 내가 알아서 쓸게." 로컬 네트워크를 스캔해 기기를 자동 발견하고, 기기 종류별 도메인 지식(Skill)을 불러와, 환경·사용자 의도·과거 기억을 종합해 LLM Brain이 행동 계획을 세운다.
예를 들어 습도가 35% 아래로 떨어지면, Anima는 단순히 "가습기 켜기"가 아니라 계절·에어컨과의 상호작용·사용자가 학습된 선호 습도까지 고려해 "가습기를 어느 모드로, 언제까지"를 결정한다. 결정은 LLM이 마음대로 하는 게 아니라 Skill이 정의한 경계·기기 능력·안전 규칙 안에서만 이루어진다.
Anima는 하드웨어 지능화 Agent Runtime이다. (1) 기기를 발견·등록하고 상태를 유지하며 어댑터로 실제 하드웨어를 제어한다, (2) 명시적 선호와 반복 행동에서 장기 기억(Memory)을 학습한다, (3) 환경·의도·기억·스킬 지식을 읽어 행동을 계획하는 LLM Brain을 쓴다, (4) 기기 종류별 도메인 지식을 Skill로 패키징한다, (5) 관찰·디버그·제어·확장을 위한 Dashboard·REST API·CLI를 제공한다. 현재는 샤오미 Mi Home / MIoT 기기를 지원하며, 더 많은 프로토콜이 추가될 예정이다.
ip + token으로 일반 기기를 제어한다.트렌딩 이유 · Home Assistant 등 기존 스마트홈 대비 장점.
스마트홈 소프트웨어 자체는 새롭지 않다 — Home Assistant가 이미 거대한 생태계를 가지고 있다. 그런데도 Anima가 주목받는 이유는, 기존 스마트홈이 "규칙(automation)을 사람이 직접 짜는 도구"에 머물러 있는 지점에서 한 발 더 나아가 "LLM이 도메인 지식과 기억으로 스스로 판단하는 에이전트"를 지향하기 때문이다. 2025~2026년 "Agent + 물리 세계(physical AI)" 흐름을 스마트홈에 정조준한 셈이다.
| 비교 항목 | 기존 스마트홈 (예: HA 자동화) | Anima |
|---|---|---|
| 의사결정 | 사람이 if-then 규칙을 직접 작성 | LLM Brain이 환경·기억·스킬로 계획 수립 |
| 기기 추가 | 통합(integration)·엔티티 설정 수작업 | 로컬 스캔 + 샤오미 QR 로그인 자동 발견 |
| 기기 지능 | 대체로 켜기/끄기 + 단순 조건 | 기기별 Skill(쾌적 모델·재실 인지·기기 협조) |
| 개인화 | 사용자가 수동으로 시나리오 작성 | 증거 기반 장기 기억으로 선호 자동 학습 |
| 확장 방식 | 플러그인 코드 작성 | SKILL.md + 프롬프트 파일 추가(코드 최소화) |
| 안전 경계 | 규칙이 곧 경계 | Skill 경계·기기 능력·안전 규칙으로 LLM 자유도 제한 |
| 로컬 우선 | 대부분 로컬 | 코어가 로컬 실행, 가능한 한 로컬 네트워크 제어 |
한쪽 극단은 사용자가 "습도 40% 미만 && 겨울 && 자정 이전이면 가습기 강모드" 같은 규칙을 수십 개씩 손으로 짜는 것이다. 상황이 조금만 바뀌어도 규칙이 깨지고 유지보수가 폭발한다. 반대 극단은 "LLM에게 모든 기기 제어권을 주고 알아서 하라"는 것인데, 이는 환각·과잉 행동·안전 문제(문 잠금·전원 등 고위험 기기)를 부른다.
Anima는 Skill이라는 도메인 지식 캡슐로 LLM의 판단 범위를 좁힌다. 각 Skill은 "어떤 기기에, 언제 행동하고, 언제 아무것도 안 하며(no-op), 어떤 구조화된 액션을 낼 수 있는가"를 정의한다. Brain은 그 경계·기기 능력·안전 규칙 안에서만 계획을 세운다. 거기에 증거 기반 기억으로 사용자 습관을 누적 학습해, 규칙을 손으로 짜지 않아도 시간이 갈수록 개인화된다. "사람이 규칙을 쓰는 시대"에서 "에이전트가 지식으로 판단하는 시대"로의 다리다.
백엔드(코어)·프론트엔드(대시보드)·인프라(MQTT/도커) 각각.
Anima의 코어는 "Thin Core(얇은 코어) + MQTT 기기 레이어" 설계로, 단일 asyncio 프로세스 안에서 API·Brain·메모리·스케줄러·이벤트버스가 함께 돈다. 기기 제어는 본질적으로 I/O 바운드(네트워크·기기 응답 대기)라 동기 스레드 대신 비동기로 짜였다.
| 요소 | 역할 |
|---|---|
| Python 3.11~3.13 | 코어 전체 언어. async/await·타입힌트 적극 사용. |
FastAPI + uvicorn | REST API 서버(기본 :8080). /api/chat·/api/devices·Swagger 자동 문서. |
LangGraph | Brain의 두뇌. Planner/Executor 흐름을 StateGraph로 구성. |
openai + langchain-openai | OpenAI 호환 LLM 백엔드(OpenAI·DeepSeek·Doubao·Ollama 등). |
pydantic / pydantic-settings | 데이터 모델(Device·SkillActionSpec 등)·환경설정 검증. |
aiomqtt / amqtt | 비동기 MQTT 클라이언트 / 순수 파이썬 MQTT 브로커(로컬 dev). |
python-miio (extra) | 샤오미 MIoT 로컬 기기 제어 라이브러리(--extra miot). |
pyyaml · httpx · rich | 스킬 메타·어댑터 설정 파싱 / HTTP / 터미널 출력. |
| 요소 | 역할 |
|---|---|
| React + TypeScript | 대시보드 UI(기본 :3000). 기기 카드·환경 패널·채팅 바·메모리 디버그. |
| Vite | 프론트 빌드/HMR. vite.config.ts로 백엔드 프록시. |
| 주요 컴포넌트 | DeviceList·EnvironmentPanel·ChatBar·DecisionLog·MemoryPanel·SkillsPanel·VoiceOrb. |
| i18n | LanguageProvider + dictionary.ts로 영어/중국어 다국어. |
useApi.ts 훅 | 백엔드 REST API 호출 래퍼(기기·채팅·설정·스캔·메모리). |
| Tailwind CSS v4 | 유틸리티-퍼스트 CSS 프레임워크. @tailwindcss/vite 플러그인으로 Vite와 통합(vite.config.ts). dashboard/index.css에서 import. |
| 요소 | 역할 |
|---|---|
| pnpm 모노레포 | pnpm-workspace.yaml로 루트+dashboard 관리. pnpm dev가 브로커+백엔드+프론트 동시 기동. |
uv (astral) | 파이썬 의존성 관리·락(uv.lock). postinstall이 uv sync 호출. |
| Docker Compose | mqtt(eclipse-mosquitto:2) + core(python:3.13-slim) 2개 서비스. ./data·./skills 볼륨 마운트. |
| MQTT 브로커 | 로컬 dev는 amqtt(순수 파이썬, amqtt.yaml), 도커는 mosquitto(:1883). |
| 데이터 영속화 | data/memory/에 history.json·learned.json·preferences.md 등을 파일로 저장(도커 볼륨). |
| 품질 도구 | ruff(린트) · pytest+pytest-asyncio · pre-commit · Playwright(E2E). |
신호 입력 → Brain 계획 → Skill 결정 → Adapter 실행 → 검증 → 학습.
Anima의 흐름은 사용자 요청 · 기기 발견 · 센서 업데이트 · 예약 작업 · 기기 액션이라는 신호로 시작된다. 신호가 Anima Core에 들어오면, Brain이 기기 상태 + Memory + Skill 컨텍스트를 합쳐 이해·계획한다. 실행 단계에서 Skill이 결정을 구조화된 액션으로 바꾸고, Adapter가 그 액션을 구체적 하드웨어 프로토콜(MIoT 등)에 매핑해 실제 제어와 피드백 기록을 완성한다. 그 결과는 다시 History/Memory로 흘러 다음 결정을 개선한다.
Anima에서 Skill은 단순 함수나 프롬프트 한 줄이 아니다. 기기 도메인 지식을 담은 폴더 단위 패키지다. 구조는 다음과 같다.
skills/system/humidifier/
├── SKILL.md # 메타데이터: 지원 기기·동작 규칙(프런트매터 YAML)
├── references/
│ ├── knowledge.md # 도메인 지식(쾌적 습도 범위·계절 요인·에어컨 상호작용)
│ ├── decide.md # 단일 결정 프롬프트
│ └── learn.md # 장기 학습 프롬프트
└── scripts/
└── actions.py # 구조화 액션 실행 진입점
SKILL.md의 프런트매터에는 device_types(이 스킬이 맡는 기기 종류 목록)·version이 들어가고, 본문에는 "언제 행동하고 언제 none(아무것도 안 함)을 반환하는가" 같은 동작 규칙이 적힌다. SkillLoader가 부팅 때 모든 스킬 폴더를 스캔해 device_type → Skill 매핑 캐시를 만든다.
기본 제공 시스템 스킬은 다음과 같고, skills/custom/에 자신만의 스킬을 추가할 수 있다.
| Skill | 역할 |
|---|---|
light | 조명 제어 — 밝기·색온도·서카디언(생체리듬) 조명. 급격한 변화보다 부드러운 전환 선호. |
humidifier | 습도 쾌적 범위·계절 요인·에어컨 상호작용. |
air_conditioner | 온도 제어 — 쾌적성과 에너지 균형. |
air_purifier | 공기질·정화 모드·수면 시 정숙 전략. |
speaker | 오디오 재생·정지·정숙 시간(quiet-hour) 보호. |
coordinator | 여러 기기 간 협조(cross-device coordination). |
device_discovery | 기기 발견·샤오미 QR 로그인·기기 활성화. |
skill_creator | 자연어 요구사항에서 커스텀 스킬을 자동 생성. |
Brain을 "새로 온 만능 집사"라고 하자. 집사가 가습기·에어컨·조명을 다 직접 외워서 다루면 실수투성이가 된다. 대신 각 기기마다 "이 기기는 이렇게 다루세요"라고 적힌 매뉴얼(Skill)을 책장(Skill Bank)에 꽂아 둔다. 집사는 상황에 맞는 매뉴얼만 펼쳐 보고 행동한다. 새 기기가 오면? 코드를 고치는 게 아니라 매뉴얼 한 권을 책장에 더 꽂으면 된다. skill_creator는 그 매뉴얼을 자동으로 써 주는 사서다.
매 요청마다 모든 이력을 LLM에 욱여넣으면 토큰이 폭발한다. Anima는 기억을 L1(핵심 정체성) · L2(기억 디렉토리) · L3(상세) 3계층으로 나눠, "필요할 때만 깊이 읽는다."
핵심은 "Planner는 기억이 무엇이 있는지(목차)만 먼저 알고, 실제 기기 Skill을 실행할 때 비로소 상세를 로드"한다는 점이다. 또한 기억은 그냥 저장되지 않고 증거(evidence)와 상태(status)를 갖는다.
| 필드 | 의미 |
|---|---|
claim_type | 명시적 선호 · 암묵적 선호 · 루틴 · 기기 별칭 · 제약 · 가정 컨텍스트 등으로 구분. |
positive_evidence / negative_evidence | 그 기억을 뒷받침/반박하는 증거 출처를 기록. |
status | candidate(후보) · confirmed(확정) · rejected(기각) · stale(노후). |
| 기본 정책 | confirmed 기억만 기본적으로 Skill 결정에 들어간다. |
learned.json | 기기 종류별 학습 프로필(정규화된 선호). |
history.json | 상호작용마다 event_id 생성 → 백그라운드에서 안정적 장기 기억으로 추출. |
Adapter는 Anima의 구조화된 액션을 실제 기기 프로토콜 호출로 변환한다. 인터페이스가 매우 작아서(BaseAdapter 추상클래스) 새 프로토콜을 붙이기 쉽다.
class BaseAdapter(abc.ABC):
@abc.abstractmethod
async def discover(self) -> list[Device]:
"""네트워크를 스캔해 발견한 기기 목록 반환"""
@abc.abstractmethod
async def subscribe(self, device: Device) -> None:
"""기기 상태 변화 모니터링 시작"""
@abc.abstractmethod
async def execute(self, device_id, action, params) -> ActionResult:
"""기기에 제어 명령 실행"""
현재는 miot/(샤오미)와 virtual/(테스트용 가상 기기) 어댑터가 있다. Matter·Home Assistant·BLE·HTTP API 등 새 프로토콜은 이 세 메서드만 구현하면 붙는다. 단, 일반 MIoT 명령 실행은 여전히 기기의 현재 로컬 IP+토큰에 의존하며, 샤오미 클라우드 로그인은 주로 발견·토큰 획득용이다(모든 기기가 클라우드 원격 제어를 지원하는 것은 아님).
코어·어댑터·스킬·대시보드가 명확히 분리된 모노레포.
① skills/system/light/SKILL.md로 "스킬이 어떻게 생겼나" 감 잡기 → ② core/brain/skill_loader.py로 스킬이 어떻게 로드·매핑되는지 → ③ core/brain/engine.py의 BrainCycleState·LangGraph 흐름으로 Planner→Executor 파악 → ④ core/memory/store.py로 3계층 기억 구현 → ⑤ adapters/base.py + adapters/miot/adapter.py로 하드웨어 추상화. 코어 통찰의 90%가 이 다섯 곳에 있다.
하드웨어 Agent OS 아키텍처에서 무엇을 배우나.
Anima는 "LLM을 한 번 호출하고 끝"이 아니라, 상태를 가진 그래프(StateGraph)로 다단계 추론"을 짜는 살아 있는 예제다. engine.py의 BrainCycleState(TypedDict)를 보면 devices·environment_state·lightweight_skills·planner_output·plan_items·execution_results가 그래프 노드를 거치며 채워진다. "컨텍스트 구성 → 계획 → 실행 → 검증"을 노드로 분리하면 각 단계를 따로 테스트·교체할 수 있다.
가장 배울 점은 기기 정책을 Brain이나 Adapter에 하드코딩하지 않고 SKILL.md + 프롬프트 파일로 외부화한 설계다. 새 기기 = 폴더 하나 추가. 이는 Anthropic의 "Agent Skills" 패턴(SKILL.md + references + scripts)을 스마트홈에 적용한 것으로, "변하는 부분(도메인 지식)은 데이터/문서로, 안 변하는 부분(로딩·실행 엔진)만 코드로"의 모범 사례다.
skills/custom/_template/을 복사해 "오후 10시 이후 거실 조명을 30%로" 같은 나만의 스킬을 SKILL.md + decide.md로 작성하고, SkillLoader가 인식하는지 확인해 보라.L1/L2/L3 설계는 "LLM 컨텍스트는 비싸고 유한하다"는 현실을 정면으로 다룬다. 핵심 트릭은 get_planner_context()가 "기억의 목차(L2)만" 주고, get_skill_context(device_type)가 실제 실행 직전에야 상세(L3)를 로드하는 지연 로딩(lazy loading)이다. RAG·에이전트 메모리를 설계할 때 그대로 쓸 수 있는 패턴이다.
기억에 positive/negative_evidence와 candidate/confirmed/rejected/stale 상태를 둔 것은 "한 번 본 걸 곧장 사실로 믿지 않는다"는 자세다. 확정(confirmed)된 기억만 결정에 쓰는 보수성은, 환각·과잉 일반화를 막는 에이전트 설계의 핵심 원칙이다. 추천 시스템·사용자 모델링에도 동일하게 응용된다.
BaseAdapter가 discover/subscribe/execute 단 3개 메서드인 것은 "플러그인 인터페이스는 작을수록 확장하기 쉽다"는 교훈이다. 인터페이스가 비대해지면 새 어댑터를 붙이는 비용이 커진다. 좋은 추상화 경계를 찾는 연습 대상으로 훌륭하다.
virtual/adapter.py를 읽고, 그것을 본떠 "콘솔에 출력만 하는" 더미 어댑터를 만들어 코어가 가짜 기기를 발견·제어하게 해 보라.설치는 가볍지만, 실제 기기 제어에는 별도 조건이 붙는다.
| 항목 | 요구사항 |
|---|---|
| 런타임(공통) | Node.js >= 18 · pnpm >= 8 · Python >= 3.11 · uv. pnpm install이 uv sync로 파이썬 의존성까지 설치. |
| 기동 | pnpm dev 한 번으로 MQTT 브로커 + 백엔드(:8080) + 프론트(:3000) 동시 실행. |
| LLM | OpenAI 호환 API 키(ANIMA_LLM_API_KEY). OpenAI·DeepSeek·Doubao·Ollama 호환 엔드포인트 가능(ANIMA_LLM_BASE_URL). |
| MIoT 기기 제어(선택) | uv sync --extra dev --extra miot --python 3.13로 python-miio 설치 필요. |
| 네트워크 | 제어할 기기와 같은 로컬 네트워크. 일반 MIoT 기기는 도달 가능한 로컬 IP + 토큰 필요. |
| 샤오미 연동 | Mi Home 앱으로 QR 로그인 → 클라우드가 기기 토큰 반환(발견·토큰 획득용). 토큰 미반환 기기는 수동 토큰 입력으로 활성화. |
| 배포(선택) | Docker Compose(mqtt + core). ./data 볼륨으로 메모리 영속화. network_mode: host로 로컬 기기 접근. |
Anima는 실존 하드웨어에 진짜 액션을 실행한다. 공식 README도 못 박는다 — Dashboard·API를 공용 인터넷에 직접 노출하지 말 것. 샤오미 토큰·LLM API 키 같은 민감 정보는 신뢰된 환경에만 저장한다. 사무실·공용 공간·공유 주택에서 기기를 제어하기 전에는 명확한 권한 경계를 먼저 세워야 한다. 문 잠금·보안 장치·전원처럼 고위험 기기에 대해서는 자동화를 보수적으로 유지하라. (현재 Anima는 초기 단계로, 다중 사용자·권한 모델·완성된 보안 정책은 아직 진화 중이다.)
난이도별 5단계 — 가상 어댑터를 쓰면 실제 기기 없이도 대부분 가능.
실제 샤오미 기기 없이, pnpm install → .env에 LLM 키 입력 → pnpm dev로 코어를 띄운다. Dashboard(:3000)와 Swagger(:8080/docs)를 열어 /api/devices·/api/environment 응답을 직접 확인하고, virtual 어댑터의 가상 기기로 채팅 명령을 보내 본다.
skills/system/light/SKILL.md와 그 references/decide.md를 읽고, "Brain이 조명 결정을 내릴 때 어떤 입력(시간·현재 상태·선호)을 받아 어떤 구조화 액션(set_brightness 등)을 내는지"를 한 문단으로 정리한다. "현재 조명이 이미 적절하면 none 반환"이라는 규칙의 의미도 설명해 본다.
skills/custom/_template/을 복사해 나만의 스킬(예: "재실 감지가 없으면 공기청정기 정숙 모드")을 SKILL.md 프런트매터(device_types·version) + knowledge/decide 프롬프트로 작성한다. 코어를 재기동해 SkillLoader가 스킬을 인식하고 SkillsPanel에 뜨는지 확인한다.
adapters/base.py의 BaseAdapter를 상속해, discover()가 가짜 기기 2~3개를 반환하고 execute()가 콘솔에 명령을 출력만 하는 "콘솔 어댑터"를 만든다. virtual/adapter.py를 참고하라. 코어가 이 가짜 기기를 발견·제어 루프에 태우는 과정을 로그로 추적한다.
여러 번 채팅 명령을 내린 뒤 data/memory/의 history.json·learned.json·추출된 기억을 들여다보며, candidate→confirmed 승격이 어떤 증거로 일어나는지 추적한다. 그다음 core/memory/extractor.py의 추출 로직을 읽고, 자신만의 claim_type(예: "정숙 시간 선호")을 추가해 본다.
6주 코스 — 비동기 백엔드부터 에이전트 메모리·하드웨어 통합까지.
| 주차 | 주제 | 실습 · 참고 |
|---|---|---|
| 1주차 | FastAPI + asyncio 기초 — async 라우트·이벤트 루프·동시성 | FastAPI 공식 문서 · core/api/routes.py·core/main.py 정독 |
| 2주차 | LangGraph — StateGraph·노드·조건 분기로 에이전트 짜기 | LangGraph 튜토리얼 · core/brain/engine.py의 BrainCycleState 분석 |
| 3주차 | Agent Skills 패턴 — SKILL.md + 프롬프트로 지식 외부화 | skills/ 구조·skill_loader.py · 커스텀 스킬 작성(과제 3) |
| 4주차 | 에이전트 메모리 — 계층형·증거 기반·지연 로딩 | core/memory/ 전체 · L1/L2/L3 컨텍스트 함수 추적 |
| 5주차 | 하드웨어 통합 — 어댑터 추상화·MQTT·MIoT 프로토콜 | adapters/base.py·adapters/miot/ · MQTT/python-miio 학습 |
| 6주차 | 풀스택 운영 — pnpm 모노레포·Docker·React 대시보드·E2E | docker-compose.yml · dashboard/ · Playwright 테스트 |
본문·소스에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| Agent OS | 여러 기기·기억·의사결정을 한 런타임에서 조율하는 "에이전트 운영체제". Anima의 정체성. |
| Brain | Anima의 의사결정 중추. LangGraph로 Planner/Executor 흐름을 구성. |
| Skill | 기기 종류별 도메인 지식 패키지(SKILL.md + references + scripts). 기기 지능의 최소 단위. |
| SKILL.md | 스킬 메타데이터·동작 규칙 파일. 프런트매터에 device_types·version. |
| Adapter | 구조화 액션 ↔ 실제 하드웨어 프로토콜 변환층. discover/subscribe/execute 3메서드. |
| Memory (L1/L2/L3) | 핵심 정체성 / 기억 목차 / 상세의 3계층 장기 기억. 지연 로딩으로 토큰 절약. |
| claim_type | 기억의 유형: 명시/암묵 선호·루틴·기기 별칭·제약·가정 컨텍스트. |
| candidate / confirmed | 기억의 상태. 증거로 candidate→confirmed 승격, 확정된 것만 기본 결정에 사용. |
| LangGraph | 상태(State)를 가진 그래프로 다단계 LLM 에이전트를 짜는 프레임워크. |
| MIoT / Mi Home | 샤오미 IoT 프로토콜·앱. Anima의 첫 하드웨어 어댑터 대상. |
| MQTT | IoT용 경량 pub-sub 메시지 프로토콜. 브로커(amqtt/mosquitto)를 통해 통신. |
| Thin Core | "얇은 코어 + MQTT 기기 레이어" 설계 — 코어를 단일 asyncio 프로세스로 작게 유지. |
| uv | astral의 빠른 파이썬 패키지/락 관리 도구. pnpm postinstall이 호출. |
| brain tick | 스케줄러가 주기적으로 Brain을 깨워 능동적 환경 점검·자동화 결정을 시키는 것. |