DeepSeek Harness Remote
DeepSeek Harness Remote 可将手机或另一台电脑与你运行 DeepSeek Harness 的电脑安全连接,随时随地继续你的工作,方便随时查看进度并继续工作。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Own your custom agent like "JARVIS"
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗개인 AI 비서 에이전트. 브라우저 Web HUD 와 그 HUD 를 창 하나로 띄우는 데스크톱 앱(Electron)으로 쓰며, 멀티프로바이더 LLM, 멀티 에이전트 오케스트레이션, 장기 메모리에 더해, 요청에만 답하지 않고 스스로 사고하고 먼저 행동하는 proactive agent 기능을 지원한다.
문서 사이트: https://ash-hun.github.io/jarvis-agent/ (website/ 의 Docusaurus, main 에 머지되면 자동 배포). 로컬은 cd website && npm install && npm start. 소개, 구현 철학, 기능별 동작 흐름, 이용 방법이 있다.
모션그래픽은 assets/hud-motion/ 의 Remotion 프로젝트에서 나온다. 떠 있는 HUD 에서 capture.py(화면)와 capture_video.py(구체 녹화)로 소재를 찍고 npm run render.
요구사항: Python 3.11+, uv
git clone https://github.com/ash-hun/jarvis-agent.git
cd jarvis-agent
uv sync --extra web --extra scheduler
cp .env.example .env
# .env에서 최소 JARVIS_LLM__API_KEY 설정
# .env.example 은 세션과 메모리가 postgres 다. Docker 없이 띄우려면 jsonl, in_memory 로
jarvis # Web HUD 백엔드 실행 (http://localhost:7777)
jarvis --host 0.0.0.0 --port 8080 # 바인딩은 플래그로. 환경변수 없음
jarvis -a karen # 그 에이전트의 기본 세션으로 연다
jarvis --list-sessions # 저장된 세션 목록
데스크톱 앱. desktop/ 의 Electron 셸이 HUD 를 창 하나로 띄운다. 백엔드는 아래 Docker web 프로파일 그대로이고, 떠 있지 않으면 셸이 docker compose --profile web up -d 로 띄운다. 창을 닫으면 트레이에 남아 먼저 건네는 말과 리마인더를 OS 알림으로 받고, 전역 단축키(기본 Cmd/Ctrl+Shift+J)로 창을 불러 듣기를 연다. 단축키, 자동 실행, 백엔드 주소는 HUD 의 SET 화면에서 고친다.
cd desktop
npm install # electron 버전은 package-lock.json 이 고정
npm start
npm test # 셸 설정 검증과 백엔드 기동 로직
cp .env.example .env # .env 설정
# Web HUD 전체 스택 (nginx + backend + postgres)
docker compose --profile web up
# 종료
docker compose --profile web down
| 프로파일 | 포함 서비스 | 접속 |
|---|---|---|
web | nginx + jarvis-backend + postgres | http://localhost:7777 |
7777 은 루프백(127.0.0.1)에만 열린다. 같은 네트워크의 다른 기기나 집 밖에서는 tailscale serve --bg 7777 로 들어온다. 공개 포트(tailscale funnel)는 쓰지 않는다. 에이전트가 bash, write_file 도구를 갖고 있어서다.
| 프로바이더 | JARVIS_LLM__PROVIDER 값 | API 키 필요 |
|---|---|---|
| Anthropic (Claude) | anthropic | ✅ |
| OpenAI (GPT) | openai | ✅ |
| Google (Gemini) | gemini | ✅ (uv sync --extra gemini) |
| Ollama (로컬) | ollama | ❌ |
| vLLM (로컬) | vllm | ❌ |
# Anthropic (기본)
JARVIS_LLM__PROVIDER=anthropic
JARVIS_LLM__MODEL=claude-sonnet-4-5
JARVIS_LLM__API_KEY=sk-ant-...
# 로컬 모델 (API 키 불필요)
JARVIS_LLM__PROVIDER=ollama
JARVIS_LLM__BASE_URL=http://localhost:11434
JARVIS_LLM__MODEL=gemma4:e4b
JARVIS 와 같은 층에 에이전트를 더 둔다(team/<id>.yaml). 정보 수집 담당 KAREN 은 LH, SH 청약 공고를 조회해 KST 기준 마감(D-n)을 붙이고, 공고의 분양가나 보증금으로 월, 연간 원리금 상환액과 이자를 계산한다(lh_notice, sh_notice, housing_loan). 보유 현금과 월 여유자금으로 공고마다 대출 경우의 수를 펼쳐 추천 1순위를 고르고(housing_budget), "조건에 맞는 추천 공고만 알려줘" 같은 목표를 맡기면 매시 진행해 1순위가 바뀔 때만 알린다(/goal). JARVIS 에게 물으면 ask_agent 로 KAREN 에게 맡기고, 둘이 함께 일하는 동안 HUD 구체가 두 에이전트의 색으로 섞인다. 자세한 건 문서 사이트의 "최상위 에이전트 팀", "청약 공고와 대출 계산".
| 계층 | 설명 |
|---|---|
| 작업 기억 (STM) | 세션 범위. 최근 3턴만 모델에 넣고, 회상 항목 되뇌기를 센다 |
| 장기 기억 (LTM) | PostgreSQL 영속화. 사용자 프로필은 사용자의 모든 에이전트가, 일화와 절차는 쓴 에이전트와 정책에 적힌 에이전트만 읽는다 |
| Episodic | 턴이 끝날 때마다 그 턴(발화와 답)을 일화로 저장. 3턴 밖의 대화는 여기서 회상 |
JARVIS_MEMORY__BACKEND=postgres # in_memory | postgres
jarvis -s <session_id> # 기존 세션 재개
jarvis --list-sessions # 세션 목록
한국어 시각 표현으로 작업 예약:
/schedule 내일 오전 9시에 날씨 알려줘
/schedule 30분 뒤에 배포 로그 확인
/schedule 2026.12.01 오전 9시까지 AI 뉴스 요약해줘
cron 식으로 도는 작업은 ~/.jarvis/schedules/<name>.yaml 에 schedule: "0 9 * * *" 로 적는다.
정한 시각에 정한 일을 하는 일정과 달리, 목표는 에이전트가 차례마다 다음 한 단계를 스스로 정하고 새로 알릴 것만 대화로 건넨다. 에이전트 정의의 boundaries(ask, never)가 혼자 할 때 쓸 수 없는 도구를 정한다.
/goal 조건 및 가장 좋은 원리금 납부액을 추천하는 공고만 알려줘
/goal # 맡은 목표 목록
/goal stop 원리금 # 문장 일부로 멈춘다
요청에만 응답하던 reactive agent를 스스로 사고하고 먼저 행동하는 proactive agent로 확장한다. 핵심은 매 요청마다 메인 응답과 분리된 사고 호출(inner thought)로 구조화된 판단을 만들고, 그 판단으로 먼저 말할지, 뒤에서 처리할지, 침묵할지를 정하는 것이다.
| 계층 | 설명 |
|---|---|
| Inner Thought | 매 요청 시 메인 응답과 분리된 별도 LLM 호출로 구조화된 사고(ThoughtFrame) 생성 → 컨텍스트 증강 + 사고 로그·이벤트 노출 (Web 은 💭 한 줄로 접혀 있고 클릭하면 해석 전문) |
| Dual-Channel | 오래 걸릴 작업이면 filler("잠깐만요, 확인할게요")를 즉시 발화(voice는 TTS)하고 메인 루프는 백그라운드 수행 — 대기 체감 감소 |
| Proactivity Daemon | 요청 없이 주기(기본 120초)/idle 시점에 성찰해, 게이팅을 통과하면 먼저 말 검. 건네는 말은 사고 프레임의 utterance 이고 비어 있으면 침묵한다(해석문을 대신 내보내지 않는다). 저위험 읽기 작업은 자율로 미리 처리(이중 안전 게이트) |
| Thought Router | router decision model 이 요청마다 사고가 필요한지(think) 아닌지(direct) 정한다. 어느 쪽이든 바로 답하고, think 일 때만 사고를 응답과 함께 돌려 그 결과를 다음 턴에 넣는다. 끄면(기본) 아래 REQUEST_TIMING 대로 매 요청 사고한다 |
| Open-loop 추적 | 대화에서 흘린 약속·미완 작업을 매 요청마다 사고와 따로 도는 AGENDA 판단이 감지·저장 → 이후 성찰 시 상기/처리 (/openloop). 성찰은 기존 약속을 볼 뿐 새로 저장하지 않는다. 대화에서 끝나거나 취소되면 AGENDA 판단이 닫고(done), 기한이 지나면 expired 로 닫힌다. 바꿔 말한 같은 약속은 기한만 갱신한다 |
| 피드백 학습 | Proactive Dialogue의 수락/무시를 학습해 발화 임계치를 자동 조정. 묻지 않고, 먼저 건넨 말 뒤 사용자의 첫 말을 받아들임, 거절, 딴 얘기로 분류해 배운다 |
| User Model · 저널 | 누적 기억을 증류한 사용자 모델(/usermodel)을 사고에 주입, 자기 성찰 저널(/journal)을 남겨 회상에 반영 |
# Inner Thought + Dual-Channel: 기본 ON (false로 끔)
JARVIS_THOUGHT__ENABLED=true
JARVIS_PROACTIVE__ENABLED=true
# 별도(저렴) 사고 모델 분리 — 메인=anthropic / 사고=ollama 등. 로컬 Ollama 는 gemma4:e2b-it-qat 로 고정한다.
# 사고 프레임은 JSON 이라 0.8b 급은 형식을 못 지킨다
JARVIS_THOUGHT__MODEL=gemma4:e2b-it-qat # 메인이 anthropic 이면 claude-haiku-4-5 같은 값
# 자율 성찰 daemon: 기본 ON — 요청 없이 먼저 말 검 (false로 끔)
JARVIS_PROACTIVE__DAEMON_ENABLED=true
JARVIS_PROACTIVE__DAEMON_TRIGGER=interval # interval | idle | both
JARVIS_PROACTIVE__DAEMON_INTERVAL_SEC=120 # 로컬과 배포 모두 같은 값
Thought Router 는 Ollama 가 아니라 별도 서버로 띄운다. router 의 판정 head 를 Ollama 가 읽지 못해서다. LLMClick 체크아웃에서 uv run --frozen python <jarvis>/scripts/router_server.py 로 11435 포트에 띄우고 아래를 켠다. 서버가 없거나 1초 안에 답하지 않으면 think 로 둔다.
JARVIS_THOUGHT__ROUTER_MODEL=router
JARVIS_THOUGHT__ROUTER_BASE_URL=http://host.docker.internal:11435 # Docker 밖에서 돌리면 http://localhost:11435
호스트 Ollama 하나로 메인과 사고를 같이 돌리면 성찰 중 사용자 요청이 줄을 선다. brew 서비스면 launchctl setenv OLLAMA_NUM_PARALLEL 2 && brew services restart ollama 로 동시 처리 수를 늘린다(재부팅하면 다시 설정).
먼저 건네는 말이 나오지 않는 이유는 백엔드 로그 [Thought] proactive silent: <이유> 에 남는다. 대표적인 이유는 awaiting reply(먼저 건넨 말 뒤 사용자가 아직 아무 답도 하지 않음), cooldown(300초), confidence … < 임계치, empty utterance(모델이 건넬 말을 안 채움), nothing to tell(챙길 약속도 새 외부 이벤트도 없음)이다. 전체 목록은 문서 사이트 FAQ 의 "먼저 건네는 말이 오지 않을 때" 에 있다. 답을 기다리는 동안은 성찰도 건너뛴다.
| 피드백 | 동작 |
|---|---|
네, 좋아, 고마워 로 답 (Web, 음성) | Proactive Dialogue "도움됨" |
아니오, 됐어, 무시 로 답 (Web, 음성) | Proactive Dialogue "무시" |
Web 에서 짧은 답은 피드백으로만 쓰이고 LLM 으로 가지 않는다. "응 그걸로 해줘" 처럼 키워드가 아닌 40자 이하 답은 사고 모델이 수락/무시/다른 말로 분류한다(사고가 켜져 있을 때). 다른 말이면 보통 메시지로 흘러가고 질문은 닫힌다. 음성 승인 답(음성으로 시작한 요청이나 말로 이어 가는 대화 중)도 같은 분류기를 쓰되, 허용이 분명할 때만 허용한다.
사고 방식은 프롬프트 파일로 직접 조율할 수 있다:
~/.jarvis/inner_thought.md(요청),~/.jarvis/inner_thought_reflection.md(성찰). 직접 쓴 템플릿에도utterance키는 있어야 한다. 없으면 daemon 이 항상 침묵한다.
같은 종류의 작업(처음 부른 도구가 같은 턴)이 3번 성공하면 통합(세션 종료, 유휴) 때 절차 초안으로 증류된다(~/.jarvis/procedures/<name>.md). 사용자가 승인하면 그 절차는 LLM 도구 procedure_<name> 과 슬래시 스킬 /<name> 이 되어, 에이전트가 같은 종류의 요청에 스스로 꺼내 쓴다.
/procedures # 초안·승인 목록
/procedures show run-tests # 본문 보기
/procedures approve run-tests # 승인 → 도구 + 스킬 등록
/procedures reject run-tests # 삭제
/procedures learn [session_id] # 그 세션의 도구 3회 이상 구간을 지금 바로 학습
edm:<에이전트>:<작업 종류>:<세대> 다. 성공이 3번 쌓일 때마다 한 세대이고, 같은 세대는 다시 정제하지 않으므로 통합을 다시 돌려도 파일이 늘지 않는다./procedures learn 의 자연 키는 <session_id>#<메시지 인덱스> 다. 실패한 도구 호출 뒤에 같은 도구가 성공했으면 회복으로 보고 성공한 경로만 배운다. 회복하지 못한 오류가 있거나 도구 호출이 JARVIS_PROCEDURES__MIN_TOOL_CALLS(기본 3) 미만인 구간은 건너뛴다.plan)이 있으면 절차 도구가 단계를 차례로 실행한다. 단계마다 세션의 권한, 승인 검사를 그대로 거친다. 계획이 없거나 입력이 빠졌으면 레시피를 돌려주고 에이전트가 따른다.bash, write_file, edit_file 은 실행 전 승인을 묻는다(JARVIS_SECURITY__REQUIRE_APPROVAL_FOR). 위험 명령 정규식 설정은 도구 호출 경로에서 검사되지 않으므로 안전장치는 이 승인과 규칙이다. 승인 화면에서 항상 허용(ALWAYS)을 고르면 제안된 패턴이 그 에이전트의 규칙 파일 ~/.jarvis/approvals/<agent_id>.yaml(JARVIS 는 main.yaml)에 남고, 그 패턴은 다시 묻지 않는다. 규칙은 사용자가 그 버튼을 눌렀을 때만 생긴다. 모든 에이전트에 적용할 규칙은 공용 파일 ~/.jarvis/approvals.yaml 에 손으로 적는다.
| 도구 | 제안 패턴 | 대조 방식 |
|---|---|---|
bash | 명령의 앞 두 단어 (git status) | 토큰 접두. git status --short 는 통과, git stash 는 다시 묻는다 |
write_file, edit_file | 경로의 디렉토리 (src/app/*) | glob |
| 그 외 | * | 도구 전체 |
# ~/.jarvis/approvals.yaml (공용) 또는 ~/.jarvis/approvals/<agent_id>.yaml. 손으로 고쳐도 된다
rules:
- {tool: bash, pattern: uv run}
- {tool: write_file, pattern: "docs/*"}
열린 화면이 없어도 스케줄 결과와 Proactive Dialogue가 닿도록 슬랙 incoming webhook 이나 디스코드 채널 webhook 으로 보낸다. 둘 다 설정하면 둘 다 보낸다. 브라우저와 데스크톱 앱 모두 같은 채널을 쓴다.
JARVIS_NOTIFY__SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
JARVIS_NOTIFY__DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/<id>/<token> # 채널 설정 > 연동 > 웹후크
JARVIS_NOTIFY__AVATAR_BASE_URL=https://raw.githubusercontent.com/ash-hun/jarvis-agent/main/assets/avatars # 디스코드에서 에이전트마다 이름과 구체 아바타로 보낸다
JARVIS_NOTIFY__KINDS=["reminder","proactive","spoken"] # reminder=스케줄 결과, proactive=먼저 거는 말, spoken=Web HUD 가 소리 내어 읽은 말
디스코드 봇을 더하면 내가 음성 채널에 있을 때 봇이 그 채널에 들어가 알림을 읽고 나간다. 음성 채널에 없거나 읽기에 실패하면 위 webhook 글로 보낸다. 300자를 넘는 알림은 앞부분만 읽고 전문은 글로도 남긴다. spoken 은 화면이 이미 읽은 말이라 글로만 간다.
uv sync --extra discord # discord.py[voice] + 한국어 TTS(supertonic). 재생에 ffmpeg 필요
JARVIS_NOTIFY__DISCORD_BOT_TOKEN=... # Developer Portal > Bot > Reset Token. 초대 권한 Connect, Speak
JARVIS_NOTIFY__DISCORD_GUILD_ID=123456789012345678 # 서버 id
JARVIS_NOTIFY__DISCORD_USER_ID=123456789012345678 # 내 사용자 id
JARVIS_NOTIFY__DISCORD_VOICE_KINDS=["reminder","proactive"]
모바일 앱(jarvis_agent_app)은 같은 출처의 /m/ 에서 서빙되는 홈 화면 웹앱이고, 앱이 꺼져 있어도 Web Push 로 알림을 받는다. 앱이 PUT /api/push/subscriptions 로 구독하면 위 알림 종류가 구독 전부에 간다. 구독은 Postgres(push_subscriptions)에 두므로 jsonl 세션 저장소에서는 꺼진다. 앱 빌드 결과는 JARVIS_APP_DIST(기본 ../jarvis_agent_app/dist)를 nginx 가 마운트한다.
npx web-push generate-vapid-keys # 한 번만. 바꾸면 기존 구독이 무효
JARVIS_NOTIFY__WEBPUSH_VAPID_PUBLIC_KEY=B...
JARVIS_NOTIFY__WEBPUSH_VAPID_PRIVATE_KEY=...
JARVIS_NOTIFY__WEBPUSH_SUBJECT=mailto:[email protected]
슬랙, 디스코드 webhook 은 중복 제거 키가 없어 재시도하지 않는다(실패는 로그). 자체 앱으로 바꿀 때는
core/notify에 채널 하나를 더하면 된다.
docker compose --profile web up -d 뒤 http://localhost:7777. 정적 HTML 과 ES 모듈(src/jarvis/ui/web/static/app/)이고, nginx 가 그대로 서빙한다.
| 영역 | 내용 |
|---|---|
| 왼쪽 열 | AGENDA(스케줄, open loop ✓/✕), ACTIVITY |
| 헤더 | 가운데에 연결 점과 에이전트 이름, 그 아래 날짜와 시각. 이 묶음은 리액터 구체의 가로 중심에 맞춘다. 오른쪽은 화면 탭 HUD, FLOW, SET. 연결 점은 연결되면 깜빡이고 끊기면 빨갛게 멈춘다 |
| 가운데 | 리액터. 상태(대기, 생각, 실행, 듣기, 말하기)가 구체와 링에 보인다. 클릭하면 듣기 시작, 읽는 중이면 읽기 멈춤. 에이전트가 둘 이상이면 양옆 화살표나 구체를 좌우로 밀어 에이전트를 바꾼다 |
| 오른쪽 열 | SESSIONS, CONVERSATION. 헤더를 클릭하면 접히고 아래 카드가 자리를 채운다 |
| SET 화면 | 왼쪽에 SYSTEM(모델, 토큰, 비용, 도구 목록)과 DESKTOP, 오른쪽에 SETTINGS, 아래에 EFFECTIVE CONFIG. 화면 탭을 바꾸면 카드가 밀려 나가고 들어온다 |
대화에는 네 종류가 섞인다. 💭 는 접힌 한 줄(decision, 확신도)이고 클릭하면 해석 전문. 먼저 건넨 말과 대기 안내는 따로 표시하지 않고 에이전트의 보통 답처럼 보이고 읽힌다. 전송 직후 첫 이벤트까지는 THINKING 자리표시가 뜬다. 응답과 세션 복원 메시지는 마크다운으로 렌더된다. 승인이 필요한 도구는 모달(ALLOW, ALWAYS, DENY)로 묻고, 120초 동안 답이 없으면 시간 초과로 거부한다. 음성으로 시작한 요청이나 말로 이어 가는 대화 중이면 모달 대신 아래쪽 띠를 띄우고 음성으로 묻는다.
음성에는 모드 전환이 없다. 타이핑한 요청은 화면으로만 답하고, 말로 시작한 요청(구체 클릭, "Jarvis" 웨이크워드, 데스크톱 전역 단축키)은 답을 읽은 뒤 다시 들으며 대화를 이어 간다. 들었는데 말이 없거나, 타이핑해서 보내거나, 읽기를 멈추면 대화가 끝난다. /focus <작업> 은 Focus 모드를 켠다. Focus 는 양옆 카드를 띠로 접고 구체 둘레에 레퍼런스 조사, 작업 기록, 지시 분석, 진행 현황 네 영역을 펼친다. 각 영역은 작업 동안 살아 있는 sub-agent 가 채운다. 세션을 다른 에이전트로 바꾸면 화면 색이 약 0.9초에 걸쳐 섞인다.
정적 파일은 서비스 워커가 네트워크 우선으로 받고 끊겼을 때만 캐시를 쓴다. 버전 문자열이 없으므로 파일을 고치면 새로고침만으로 반영된다. 860px 이하에서는 왼쪽 열이 드로어가 된다. 수동 확인 절차는 scripts/e2e/hud-scenario.md, 자동 e2e 는 scripts/e2e/hud_flow.py.
브라우저 HUD에서 구체를 클릭하면 음성 대화가 가능하다. STT/TTS 처리 위치는 설정으로 결정된다.
| 설정 | browser (기본) | 서버 처리 |
|---|---|---|
| STT | Web Speech API (브라우저 인식) | whisper — 브라우저가 녹음 → 백엔드 faster-whisper 전사 |
| TTS | Web Speech API (브라우저 합성) | supertonic — 백엔드 ONNX 합성 → WAV 스트림 |
JARVIS_VOICE__STT_PROVIDER=whisper # browser | whisper (한국어 고정)
JARVIS_VOICE__TTS_PROVIDER=supertonic # browser | supertonic
# 서버 STT/TTS 사용 시 추가 의존성
uv sync --extra voice
읽기 전에 서버가 마크다운 기호를 지운다. 읽는 중에 Esc 를 누르거나 구체를 클릭하면 멈춘다. 영어로만 된 문장은 JARVIS_VOICE__ENGLISH_TTS_PROVIDER 로 따로 읽게 할 수 있다. piper 는 Piper 음성 모델, qwen 은 Mac 의 Qwen3-TTS 합성 서버(services/qwen_tts)가 읽고, Web HUD 는 소리를 조각으로 나오는 대로 받아 첫 조각부터 읽는다.
JARVIS 전용 음성 모델(TTS, 읽기 문구 9개, "Jarvis" 웨이크워드)은 레포의 models/ 에 두고 쓴다. 비상업 라이선스라 .gitignore 에 있어 올라가지 않는다. Docker 는 ./models 를 /app/models 로 마운트하고 JARVIS_VOICE__PIPER_MODEL, JARVIS_VOICE__PHRASES_DIR, JARVIS_VOICE__WAKE_MODEL_DIR 로 켠다. 채우는 법은 문서의 Voice Interface 에 있다.
서버 STT 모드에서는 connected 메시지의 voice.stt_mode=server를 받아, 브라우저가 MediaRecorder로 녹음한 오디오를 voice_stt_submit으로 전송하고 백엔드가 voice_stt_result로 전사 결과를 돌려준다.
브라우저 인식(Web Speech API)이 없거나 실패하면(Electron 은 Google 키가 없어 network 로 끝난다) JARVIS_VOICE__STT_FALLBACK(기본 whisper, voice extra 필요)으로 녹음을 서버에서 받아 적는다. 모델은 처음 받아 적을 때 불러온다.
입력은 앞의 세 단계를 차례로 지나고, 도구 호출은 Sandbox 가 실행 여부(승인 불필요, 기억된 규칙, 사용자에게 질문)를 정한다. PolicyEngine 은 지금 모두 허용(AllowAllPolicy)이다.
InputValidator → Authenticator → PolicyEngine → (도구 호출마다) Sandbox
Web HUD 접근 키. JARVIS_SECURITY__API_KEYS 에 키 → 사용자(이름, roles)를 등록하면 /ws 와 /api/* 가 그 키를 요구한다. 키가 곧 식별자이므로 길고 무작위인 값을 쓴다. 브라우저는 처음 연결이 거부되면 키를 한 번 묻고 저장한다. 비워두면 web 인증이 꺼진다.
JARVIS_SECURITY__API_KEYS='{"<long-random-key>": {"name": "jh", "roles": ["admin"]}}'
| 도구 | 주요 파라미터 | 설명 |
|---|---|---|
bash | command, timeout | 셸 명령 실행. 실행 전 승인을 묻는다(REQUIRE_APPROVAL_FOR) |
read_file | path, offset, limit | 파일 읽기. 라인 번호 기반 범위 지정 가능 |
write_file | path, content, encoding | 파일 쓰기. 부모 디렉토리 자동 생성, 기존 파일 덮어쓰기. 실행 전 승인 |
edit_file | path, old_string, new_string, replace_all | 파일 내 문자열 정밀 교체. 매칭 실패 시 에러 반환. 실행 전 승인 |
glob | pattern, path, limit | 글롭 패턴으로 파일 탐색 (**/*.py). 최근 수정순 정렬 |
grep | pattern, path, glob, case_insensitive, max_results | 파일 내용 정규식 검색. path:line: text 형식 반환, 바이너리 자동 스킵 |
list_dir | path, all | 디렉토리 나열. 타입(dir/file)·크기 표시 |
web_search | query, max_results | 웹 검색. Brave API 키 설정 시 Brave, 미설정 시 DuckDuckGo fallback |
web_fetch | url, max_length | URL 본문을 가져와 텍스트로 변환 (HTML 태그 제거) |
이 밖에 remember_task_pattern, recall_episodes(기억)는 늘 붙고, 기능이 켜지면 schedule, goal(스케줄러), orchestrate(서브 에이전트 정의가 있을 때), ask_agent(최상위 에이전트가 둘 이상일 때), MCP 서버의 도구가 붙는다. 청약 도구(lh_notice, sh_notice, housing_loan, housing_budget, housing_profile, housing_watch)는 에이전트 정의의 tools 에 적은 에이전트만 쓴다.
| 커맨드 | 별칭 | 설명 |
|---|---|---|
/test | /t, /pytest, /ut | 유닛 테스트 실행 및 결과 리포트 (레포 skills/test.md, JARVIS_PLUGIN__SKILLS_DIR=./skills 일 때) |
/schedule | 작업 예약 (<시각>에 <내용> 한 번 | <YYYY.MM.DD 시각>까지 <내용> 마감까지 반복) | |
/goal | 목표 맡기기 (<문장> | 목록 | stop <문장 일부>) | |
/housing | 주거 프로필 보기와 고치기 (set 현금=7000만 월여유자금=150만 | clear) | |
/remember | 장기 기억 저장/검색/삭제 (list | search <키워드> | forget <id> | conflicts | reembed) | |
/patterns | 지금 에이전트의 작업 패턴 (forget <이름>) | |
/sessions | 저장된 세션 목록 표시 | |
/branch | [session_id] 세션을 특정 시점에서 분기 | |
/tools | 현재 사용 가능한 도구 목록 표시 | |
/procedures | 학습된 절차 (approve <name> | reject <name> | show <name> | learn [session_id]) | |
/compact | 명령을 친 세션을 요약해 장기 기억에 저장 | |
/export | 현재 세션 대화를 파일로 내보내기 (md | json, ~/.jarvis/exports/) | |
/cost | 현재 세션 토큰 사용량과 추정 비용 리포트 (모델별 집계) | |
/openloop | 추적 중인 약속/미완 작업 (done <id> | dismiss <id>). proactive | |
/usermodel | 증류된 사용자 모델 표시 / build로 갱신. proactive | |
/journal | 자기 성찰 저널 표시 / write로 작성. proactive | |
/orchestrate |
/test # 전체 테스트
/test -k "test_policy" # 키워드 필터
/test tests/unit/test_session_jarvis_config.py
전체 목록과 등록 조건은 문서 사이트의 "공용 커맨드" 에 있다.
src/jarvis/model_layer/providers/ 아래에 my_provider.py 생성Provider 를 상속해 provider_name 과 AsyncIterator[StreamEvent] 를 내는 stream() 메서드 구현. 오류는 예외 대신 ErrorEvent 로 낸다model_registry.py 의 build_default_registry() 에 등록# src/jarvis/model_layer/providers/my_provider.py
from collections.abc import AsyncIterator
from jarvis.model_layer.model_types import (
LLMContext, ModelConfig, StopEvent, StreamEvent, StreamOptions, TextDeltaEvent, Usage,
)
from jarvis.model_layer.providers.provider_base import Provider
class MyProvider(Provider):
provider_name = "my-provider"
async def stream(
self, model: ModelConfig, context: LLMContext, options: StreamOptions | None = None
) -> AsyncIterator[StreamEvent]:
# SDK 호출 → StreamEvent yield
yield TextDeltaEvent(delta="Hello")
yield StopEvent(reason="stop", usage=Usage())
# src/jarvis/model_layer/model_registry.py 의 build_default_registry()
registry.register(MyProvider())
JARVIS_LLM__PROVIDER=my-provider
~/.jarvis/tools/(JARVIS_PLUGIN__TOOLS_DIR)에 파일을 놓고 재시작하면 등록된다. 같은 이름의 내장 도구를 덮어쓴다.
방식 1, Python 파일:
# ~/.jarvis/tools/my_tool.py
TOOL_NAME = "my-tool"
TOOL_DESCRIPTION = "유용한 작업 수행"
TOOL_PARAMETERS = {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
}
async def execute(tool_call_id: str, params: dict, **kwargs) -> dict:
return {"content": f"결과: {params['query']}"}
방식 2, YAML 선언과 같은 이름의 .py: YAML 이 이름, 설명, 파라미터를 정하고 같은 이름의 .py 에 있는 execute() 가 실행한다. .py 가 없으면 등록되지 않는다.
# ~/.jarvis/tools/my_tool.yaml
name: my-tool
description: "유용한 작업 수행"
execution_mode: parallel # parallel | sequential
parameters:
type: object
properties:
query: {type: string}
required: [query]
MCP 서버의 툴을 자동으로 발견·등록한다. stdio(로컬 프로세스)와 http(SSE) transport를 지원한다.
# config / .env 로 주입되는 mcp_servers 항목
{
"name": "filesystem",
"transport": "stdio",
"command": ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/data"],
"timeout": 30.0 # 연결/호출 타임아웃(초) — 응답 없는 서버가 startup을 막지 않음
}
{
"name": "remote-api",
"transport": "http",
"url": "https://mcp.example.com/sse",
"auth_token": "sk-...", # Authorization: Bearer 헤더로 변환
"headers": {"X-Org": "acme"}, # 커스텀 헤더 (auth_token보다 우선)
"timeout": 30.0
}
timeout(기본 30초) 내 응답하지 않으면 skip된다.uv sync --extra mcp (mcp 패키지) 필요. 미설치 시 자동 skip.~/.jarvis/skills/(JARVIS_PLUGIN__SKILLS_DIR)에 Markdown 파일을 놓고 재시작한다.
---
name: summarize
description: 텍스트를 요약합니다
aliases: [sum, tldr]
---
다음 텍스트를 간결하게 요약해 주세요:
{{args}}
사용: /summarize <텍스트> 또는 /sum <텍스트>. 본문의 {{args}}(또는 {{input}})가 명령 뒤 글로 바뀌어 LLM 에 간다. 같은 필드를 가진 .yaml(본문은 content 키)도 읽는다.
플러그인은 에이전트 실행 흐름에 훅을 걸 수 있다.
# ~/.jarvis/plugins/my_plugin.py
class MyPlugin:
name = "my-plugin"
version = "1.0.0"
async def before_agent_start(self, prompt, context): ...
async def before_tool_call(self, tool_name: str, args: dict, auth):
return None # {"block": True, "reason": "..."} 을 돌려주면 호출을 막는다
async def after_tool_call(self, tool_name: str, args: dict, result: dict): ...
async def on_message_end(self, message):
return None # 메시지를 돌려주면 응답이 그것으로 바뀐다
async def transform_context(self, messages: list):
return messages # 프로토콜에만 있고 아직 호출되지 않는다
JARVIS_PLUGIN__PLUGINS_DIR (기본: ~/.jarvis/plugins/) 를 기동 때 읽는다. 파일을 더했으면 재시작. name 과 version 을 가진 첫 클래스를 플러그인으로 쓴다. 상속하지 않으면 훅 다섯 개를 다 두어야 빈 훅 오류 로그가 안 남는다. jarvis.core.plugins.plugins_protocol.JarvisPlugin 을 상속하면 쓰지 않는 훅은 비워 둬도 된다.
YAML 파일로 에이전트를 선언하고 ~/.jarvis/agents/ 에 배치한다.
단일 에이전트 선언 (researcher.yaml):
name: researcher
description: "웹 검색과 문서 분석을 담당하는 에이전트"
tools:
- web_search
- read_file
system_prompt: |
당신은 리서처 에이전트입니다.
사용자의 질문에 답하기 위해 웹을 검색하고 문서를 분석합니다.
model_override: null # null이면 전역 설정 사용
tools_only: false # true 면 tools 에 적은 것만(비면 도구 없음). false 면 빈 tools 는 메인 도구 전체
여러 에이전트를 한 파일에 (list 형식):
# ~/.jarvis/agents/team.yaml
- name: coder
description: "코드 작성 전담 에이전트"
tools: [bash, read_file]
system_prompt: "당신은 Python 전문 코더입니다."
- name: reviewer
description: "코드 리뷰 전담 에이전트"
tools: [read_file]
system_prompt: "당신은 시니어 코드 리뷰어입니다. 보안과 성능을 중점으로 리뷰합니다."
에이전트 정의의 메모리와 레벨:
- name: planner
description: "작업을 단계로 나누는 기획자"
capabilities: [계획]
level: 0 # hierarchical 패턴에서 먼저 도는 레벨
memory:
stm: per_parent_session # per_parent_session | per_task | per_agent
- name: writer
description: "보고서 작성"
capabilities: [작성, 보고서]
level: 1
서브 에이전트는 위임한 부모의 사용자 범위를 상속한다. 사용자의 /remember 사실은 보이고 부모의 일화와 절차는 안 보이며, 부모는 위임을 일화로 기억한다.
오케스트레이션 호출 두 가지:
/orchestrate hierarchical 분기 보고서 작성 # 사용자가 패턴을 지정
/orchestrate blackboard --agents a,b --promote 시장 분석
/orchestrate list # 패턴과 에이전트 목록
메인 에이전트도 orchestrate 도구로 같은 일을 한다. 작업이 여러 전문가나 여러 단계를 요구한다고 판단하면 LLM 이 패턴과 참여 에이전트를 고른다. 패턴은 supervisor(감독자 LLM 이 위임 결정), hierarchical(레벨 순, 앞 결과가 뒤로 전달), market(capabilities 가 맞는 하나), blackboard(전원 병렬, 결과 취합). 쉼표로 이으면 파이프라인이다.
에이전트 간 결과는 작업 범위의 작업 기억으로 전달되어 다음 에이전트의 프롬프트에 [작업 공유 메모리] 블록으로 들어간다. --promote 는 블랙보드 결과를 부모 에이전트의 장기 기억 일화로 남긴다.
이벤트 계층 추적:
각 에이전트 이벤트는 agent_name을 포함한다. 중첩 에이전트 호출 시 부모-자식 관계를 UI에서 시각화한다.
# AgentStartEvent, AgentEndEvent 모두 agent_name 포함
AgentStartEvent(session_id="...", agent_name="researcher")
AgentEndEvent(session_id="...", agent_name="researcher", turns=3)
# ── LLM ─────────────────────────────────────────────────────────────────────
JARVIS_LLM__PROVIDER=anthropic # anthropic | openai | gemini | ollama | vllm
JARVIS_LLM__MODEL=claude-sonnet-4-5
JARVIS_LLM__API_KEY=sk-ant-...
JARVIS_LLM__BASE_URL= # Ollama, vLLM, OpenAI 호환 주소. OpenAI 는 비우면 기본 주소
JARVIS_LLM__MAX_TOKENS=4096
JARVIS_LLM__TEMPERATURE=0.7
JARVIS_LLM__TIMEOUT=120 # 초
# ── 메모리 ───────────────────────────────────────────────────────────────────
JARVIS_MEMORY__BACKEND=postgres # in_memory | postgres
JARVIS_MEMORY__STM_TURNS=3 # 모델에 넣는 최근 턴 수. 그 밖은 턴마다 쓴 일화에서 회상
JARVIS_MEMORY__CONSOLIDATE_ON_IDLE=true # 조용할 때 일화에서 사실 추출 (daemon_idle_sec 넘게 입력이 없을 때, 트리거 종류 무관)
JARVIS_MEMORY__EMBEDDING_PROVIDER= # 의미 유사도 검색과 약속 중복 판정: openai | ollama | vllm (비우면 키워드만, Postgres 는 pgvector 필요)
JARVIS_MEMORY__EMBEDDING_MODEL= # 비우면 provider 기본값. 한국어는 ollama 의 bge-m3 (DIMENSIONS=1024)
# ── 세션 영속성 ──────────────────────────────────────────────────────────────
JARVIS_SESSION__PERSISTENCE_BACKEND=jsonl # jsonl | postgres
JARVIS_SESSION__SESSIONS_DIR=~/.jarvis/sessions
JARVIS_SESSION__POSTGRES_DSN=postgresql://user:pw@host:5432/db
# ── 보안 ─────────────────────────────────────────────────────────────────────
JARVIS_SECURITY__API_KEYS= # Web HUD 접근 키 → 사용자 JSON. 비우면 web 인증 없음
JARVIS_SECURITY__APPROVALS_PATH=~/.jarvis/approvals.yaml # "항상 허용" 규칙 파일
# ── 절차 학습 / 외부 알림 ────────────────────────────────────────────────────
JARVIS_PROCEDURES__ENABLED=true
JARVIS_PROCEDURES__DIR=~/.jarvis/procedures
JARVIS_PROCEDURES__MIN_TOOL_CALLS=3
JARVIS_NOTIFY__SLACK_WEBHOOK_URL= # 비우면 그 채널로 보내지 않는다
JARVIS_NOTIFY__DISCORD_WEBHOOK_URL= # https://discord.com/api/webhooks/<id>/<token>
JARVIS_NOTIFY__AVATAR_BASE_URL= # <주소>/<에이전트 id>.png 를 디스코드 아바타로. 비우면 webhook 기본 그림
JARVIS_NOTIFY__KINDS=["reminder","proactive","spoken"]
JARVIS_NOTIFY__DISCORD_BOT_TOKEN= # 디스코드 봇. 토큰, 서버 id, 사용자 id 가 다 있어야 음성 채널에서 읽는다
JARVIS_NOTIFY__DISCORD_GUILD_ID=
JARVIS_NOTIFY__DISCORD_USER_ID=
JARVIS_NOTIFY__DISCORD_VOICE_KINDS=["reminder","proactive"]
JARVIS_NOTIFY__DISCORD_TTS_PROVIDER=supertonic
JARVIS_NOTIFY__WEBPUSH_VAPID_PUBLIC_KEY= # 앱(iOS 홈 화면 웹앱) Web Push. npx web-push generate-vapid-keys 로 한 번 만든다
JARVIS_NOTIFY__WEBPUSH_VAPID_PRIVATE_KEY= # 키를 바꾸면 기존 구독이 전부 무효
JARVIS_NOTIFY__WEBPUSH_SUBJECT= # mailto:<주소>. 셋 다 있고 세션 저장소가 Postgres 일 때만 켠다
# ── 플러그인 ─────────────────────────────────────────────────────────────────
JARVIS_PLUGIN__TOOLS_DIR=~/.jarvis/tools
JARVIS_PLUGIN__SKILLS_DIR=~/.jarvis/skills
JARVIS_PLUGIN__PLUGINS_DIR=~/.jarvis/plugins
JARVIS_AGENTS__AGENTS_DIR=~/.jarvis/agents
# 위 디렉토리는 기동 때 읽는다. 파일 감시(WATCH_FILES)는 아직 아무것도 감시하지 않는다
# ── Web HUD ──────────────────────────────────────────────────────────────────
# 호스트와 포트는 환경변수가 아니라 `jarvis --host 0.0.0.0 --port 7777` 플래그다
# ── Proactive: Inner Thought (Tier 0) ────────────────────────────────────────
JARVIS_THOUGHT__ENABLED=true # 기본 ON
JARVIS_THOUGHT__MODEL= # 비우면 메인 모델, 지정 시 별도 사고 모델
JARVIS_THOUGHT__PROVIDER= # 사고 모델만 다른 provider로 분리(예: ollama)
JARVIS_THOUGHT__PROMPT_PATH=~/.jarvis/inner_thought.md
JARVIS_THOUGHT__REFLECTION_PROMPT_PATH=~/.jarvis/inner_thought_reflection.md
JARVIS_THOUGHT__LOG_SINKS=["ltm"] # ltm | stm (복수 가능)
JARVIS_THOUGHT__COMPOSER=default # default | interpretation_only
JARVIS_THOUGHT__REQUEST_TIMING=race # before | race | after. 로컬 Ollama(한 GPU)는 after 가 가장 빠르다
JARVIS_THOUGHT__WAIT_SEC=3.0 # race 에서 사고를 기다리는 상한. 넘기면 응답 먼저, 사고는 기록만
JARVIS_THOUGHT__ROUTER_MODEL= # 지정하면 REQUEST_TIMING 대신 router 가 요청마다 사고 여부를 정한다
JARVIS_THOUGHT__ROUTER_BASE_URL=http://localhost:11435
JARVIS_THOUGHT__ROUTER_THRESHOLD=0.2 # P(think) 가 이 이상이면 think
JARVIS_THOUGHT__ROUTER_TIMEOUT_SEC=1.0 # 넘기면 think 로 둔다
JARVIS_THOUGHT__HISTORY_TURNS=8 # 사고 프롬프트의 최근 대화 턴 수
JARVIS_THOUGHT__HISTORY_CHARS=240 # 턴당 글자 상한
# ── Proactive: Dual-Channel + Daemon (Tier 1~2) ──────────────────────────────
JARVIS_PROACTIVE__ENABLED=true # 기본 ON (요청 시 filler+백그라운드)
JARVIS_PROACTIVE__CONFIDENCE_THRESHOLD=0.85
# JARVIS_PROACTIVE__DECISION_MODEL=tev1 # Ollama decision model 로 "먼저 말할까" 를 정한다
# JARVIS_PROACTIVE__DECISION_THRESHOLD=0.6 # decision model 을 쓸 때의 임계치
JARVIS_PROACTIVE__COOLDOWN_SEC=300
JARVIS_PROACTIVE__IGNORE_AFTER_SEC=600 # 먼저 건넨 말 뒤 답이 없으면 이만큼 지나 약한 무시로 학습
JARVIS_PROACTIVE__DAEMON_ENABLED=true # 자율 성찰 루프 (기본 on)
JARVIS_PROACTIVE__DAEMON_TRIGGER=interval # interval | idle | both
JARVIS_PROACTIVE__DAEMON_INTERVAL_SEC=120
JARVIS_PROACTIVE__DAEMON_IDLE_SEC=180
# ── Proactive: Open-loop / User Model (Tier 2~3) ─────────────────────────────
JARVIS_OPENLOOP__BACKEND=jsonl # jsonl | in_memory | postgres
JARVIS_OPENLOOP__PATH=~/.jarvis/openloops.jsonl
JARVIS_OPENLOOP__EXPIRE_AFTER_SEC=21600 # 기한 뒤 이만큼 지나면 expired 로 닫음(6시간)
JARVIS_OPENLOOP__MAX_AGE_SEC=604800 # 기한 없는 약속의 수명(7일). 0 이면 닫지 않음
JARVIS_USER_MODEL__ENABLED=true
JARVIS_USER_MODEL__BACKEND=jsonl # jsonl | in_memory
JARVIS_USER_MODEL__PATH=~/.jarvis/user_model.json
# ── 로그 ─────────────────────────────────────────────────────────────────────
# 프로세스 환경변수에서 직접 읽는다. 로컬 실행은 .env 가 아니라 export 로 준다(Docker 는 env_file 이 넣는다)
JARVIS_LOG_LEVEL=INFO
LOG_FORMAT=console # console | json
음성, Focus, 브랜드, 외부 이벤트 등 나머지 설정은 문서 사이트의 "서비스 설정 값" 에 있다.
uv sync # 의존성 설치 (dev 포함)
uv run pytest tests/unit # 단위 테스트 (외부 API 는 mock)
uv run pytest tests/integ # 통합 테스트 (Postgres 경로는 testcontainers 로 Docker 필요)
uv run pytest -k "test_policy" # 특정 테스트
uv run ruff check src tests # 린트
uv run python scripts/sort_imports.py --check src tests scripts # import 순서 (CLAUDE.md 규칙)
uv run mypy # 타입 체크. 경로를 주지 않는다(pyproject 의 packages 로 본다)
uv run python -m jarvis.memory.eval # 메모리 기능별 평가 (LLM 없음)
uv run python -m jarvis.core.proactive.eval -v # proactive 게이팅 평가 (정책만, LLM 없음)
uv run python -m jarvis.core.proactive.eval --engine llm # 상황 사례까지 (사고 모델 호출)
uv run python scripts/e2e/launch_hud.py & # 스크립트 프로바이더로 HUD 를 7799 에 띄우고
uv run --with playwright python scripts/e2e/hud_flow.py # 승인 기억·절차 학습 흐름을 브라우저로 검증
proactive 평가는 src/jarvis/core/proactive/eval/datasets/ 의 사례로 "이 상황에서 먼저 말해야 했나"를 잰다. policy.jsonl 은 사고 프레임을 직접 주어 게이팅(응답 대기, 임계치, 쿨다운, 위험도 분리)만 검사하고, situation.jsonl 은 상황을 주어 사고 엔진까지 거친다. 사례마다 정책을 새로 만들고 시계를 고정하므로 같은 입력은 같은 결과를 낸다.
tests/
├── conftest.py
├── fixtures/ 테스트 도우미(faux_provider 등), 청약 게시판 HTML, 벤치마크 샘플
├── unit/
│ └── test_{기능}_{모듈}.py 기능 하나의 모듈 하나 (예: test_policy_sandbox.py, test_memory_memory_service.py)
└── integ/
└── test_{기능}.py 기능 단위 흐름 (예: test_approval.py, test_session.py)
Postgres 통합 테스트 (Docker 필요):
uv sync # testcontainers, asyncpg 포함
uv run pytest tests/integ/test_session.py tests/integ/test_memory.py tests/integ/test_openloop.py -v
All Rights Reserved. 이 저장소는 열람과 참고 목적으로만 공개한다. 사용, 복제, 수정, 배포, AI 학습 데이터로의 사용은 허용하지 않는다. 머지된 기여자만 저작권자의 서면 허락을 받아 허락받은 범위 안에서 쓸 수 있다. 자세한 조건은 LICENSE 를 본다.
서브 에이전트를 패턴으로 조율 (<pattern> [--agents a,b] [--promote] <작업> | list) |
/cleanup | 옛 서브 에이전트 세션, 옛 STM 테이블, 시험 흔적 보기 (agents | stm | tests 뒤에 confirm 으로 삭제) |
/focus | Focus 모드 (<작업> | stop | 현황). Web HUD 전용 |
/help | /? | 사용 가능한 명령 목록 표시 |