
채팅이 cron 작업, 병렬 배치, 편집 가능한 .pptx로 변환되는 로컬 우선(Local-first) AI 데스크톱을 구축했습니다
요약
채팅 인터페이스를 통해 cron 스케줄링, 병렬 작업 처리, 편집 가능한 .pptx 파일 생성을 지원하는 로컬 우선(Local-first) AI 데스크톱 앱 'Praxia Desktop'을 소개합니다. 사용자의 데이터를 로컬에 저장하며 다양한 LLM 제공업체를 직접 연결해 사용할 수 있는 오픈 소스 프로젝트입니다.
핵심 포인트
- 자연어 명령을 통한 POSIX cron 기반 작업 자동 스케줄링 지원
- 다수의 파일에 대해 동시에 작업을 수행하는 병렬 에이전트 실행 기능
- 마크다운이 아닌 실제 편집 가능한 .pptx 파일 생성 기능
- 사용자 데이터와 채팅 기록을 로컬에 저장하는 프라이버시 중심 설계
- OpenAI, Anthropic, Ollama 등 다양한 LLM 모델 선택 가능
요약 (TL;DR)
Praxia Desktop은 제가 다음과 같은 상황들에 지쳤을 때 원했던 바로 그것입니다:
- ChatGPT에 "매주 월요일에 이거 해줘"라고 붙여넣고 나서, 정작 매주 월요일에 실행하는 것을 잊어버리는 상황
- 단일 PDF 채팅 워크플로우를 확장할 수 있는 방법이 없어, 50개의 PDF에 대해 동일한 프롬프트를 수동으로 실행해야 하는 상황
- LLM에게 "덱(deck) 하나 만들어줘"라고 요청했는데, 이해관계자에게 전달할 실제 편집 가능한 PowerPoint가 필요함에도 마크다운(Markdown) 결과물만 받는 상황
그래서 저는 채팅이 유일한 인터페이스인 데스크톱 앱을 만들었습니다. 그리고 그 이면의 에이전트는 다음과 같은 역할을 수행합니다:
- 스스로 스케줄링 (Schedules itself) — "매주 월요일 오전 9시에 Documents/ 폴더의 차이점(diffs)을 요약해줘"라고 말하면 POSIX cron 작업이 생성됩니다.
- 병렬로 확장 (Fans out in parallel) — "이 50개의 PDF 각각에 대해 실행 항목(action items)을 추출해줘"라고 말하면 50개의 에이전트가 실시간 진행 상황과 함께 동시에 실행됩니다.
- 네이티브 편집 가능 파일 작성 (Writes native, editable files) — "3분기 회고 덱을 초안 작성해줘"라고 말하면 실제
.pptx파일이 워크스페이스에 생성됩니다 (스크린샷이나 마크다운이 아닌, 차트와 핵심 요약 등을 모두 포함).
모든 것은 **로컬(locally)**에서 실행됩니다. 여러분의 파일과 채팅 기록은 제가 제어하는 서버가 아닌 여러분의 Praxia 폴더에 저장됩니다. LLM 호출은 여러분의 기기에서 여러분이 선택한 제공업체(OpenAI, Anthropic, Azure, Gemini 또는 자체 Ollama / LM Studio 인스턴스)로 직접 전달됩니다. 저는 여러분의 프롬프트를 절대 볼 수 없습니다.
무료이며, 오픈 소스(Apache 2.0)이고, Windows 10/11 x64를 지원합니다.
이 포스트의 나머지 부분에서는 이 세 가지 기능 각각에 대해 상세히 살펴봅니다. 즉, 그것이 어떻게 생겼는지, 왜 중요한지, 어떻게 구축되었는지, 그리고 구현 측면에서 흥미로운 점은 무엇인지 설명합니다.
기능 1: "이 작업을 매주 실행하도록 예약해줘"
Cron은 위대한 컴퓨팅 기본 요소(primitives) 중 하나이지만, 시스템 관리자(sysadmins) 외에는 기본적으로 아무도 직접 사용하지 않습니다. 인터페이스가 매우 까다롭기 때문입니다. 그 사고 모델("분/시/일/월/요일을 나타내는 공백으로 구분된 5개의 필드, 이들 모두는 * 또는 1-5 또는 */15가 될 수 있음...")은 제가 항상 crontab -e를 통해 다시 유도해내야 하는 것입니다.
제가 원했던 것은 이랬습니다: 내가 말하면, 그것이 예약되는 것.
Praxia에서는 다음과 같이 작동합니다:
나: 매주 월요일 오전 9시에, 지난 월요일 이후로
Documents/team-notes/에서 변경된 내용을 요약해서
workspace/weekly-team-summary.md에 작성해줘
상대편의 에이전트(agent)는 이를 읽고 다음과 같이 추론합니다:
- 이것은 반복적인 작업이다 (일회성 작업이 아님)
- 일정은 POSIX cron 기준으로
0 9 * * 1이다 - 작업은 감시 중인 폴더에 대한 차이점(diff) 분석 + 워크스페이스(workspace)로의 쓰기 작업을 포함한다
- 쓰기 작업은 일정 생성 시점이 아니라 실행 시점에 사용자의 승인 게이트(approval gate)가 필요하다
일정이 등록됩니다. 이 일정은 Schedules 탭에 저장됩니다:
cron 표현식, 프롬프트(prompt), 다음 실행 시간, 마지막 실행 결과 등을 검사할 수 있습니다. 일정을 일시 중지하거나, cron을 수정하거나, 프롬프트를 수정하거나, 삭제할 수 있습니다. 일정을 등록한 것과 동일한 에이전트가 이를 수정할 수도 있습니다 — "사실, 그걸 매 평일 아침으로 바꿔줘."라고 말이죠.
이것은 단순히 "기능으로서의 자연어 cron 파싱"이 아닙니다. 그것은 추가적인 단계가 붙은 정규 표현식(regex)일 뿐입니다. 이것을 유용하게 만드는 것은, 일정이 실행되는 시점의 **실행 컨텍스트 (execution context)**가 새로운 채팅에서 얻는 것과 동일하다는 점입니다. 즉, 동일한 메모리, 동일한 커넥터(connectors), 동일한 승인 게이트(approval-gate) 의미론, 동일한 프로바이더(provider) 설정이 적용됩니다.
이것이 중요한 이유: 많은 LLM 도구들이 워크플로우를 _설명(describe)_할 수는 있습니다. 하지만 모든 실제 부수 효과(side effects, 파일 쓰기, API 호출 등)가 적절히 제어되고 관찰 가능한 상태로, 일정을 따라 워크플로우를 _실행(execute)_할 수 있는 도구는 거의 없습니다.
두 번째 기능: 채팅을 50개의 파일로 팬아웃 (Fan out)
제가 LLM을 무시할 수 없는 규모로 처음 사용해 보려 했던 때는 50개의 후보 이력서를 분류해야 했을 때였습니다. ChatGPT는 이력서 한 장을 처리하는 데는 훌륭했습니다. 하지만 50장을 처리하기 위해 저에게 주어진 선택지는 다음과 같았습니다:
- 탭 50개 열기 (유감스럽게도 제가 처음에 실제로 했던 방식입니다)
- 파이썬(Python) 스크립트 작성하기 (정답이지만, 마감 기한이 촉박했습니다)
- 병렬 에이전트 실행을 지원하는 SaaS 사용하기 (유료이며, 특정 서비스에 종속됩니다)
Praxia의 해답은 다음과 같습니다:
me: Documents/candidates/ 폴더 내의 각 PDF에서 강점, 약점,
기술 스택(tech stack), 그리고 추천 팀을 추출해줘.
각 후보자의 분석 내용을 workspace/candidates/<name>.md에 저장하고
비교 매트릭스(comparison matrix)를 작성해줘.
...
에이전트의 동작:
- 폴더 내의 PDF 목록을 나열합니다.
- N개의 병렬 서브 에이전트(sub-agents)를 생성합니다 (여기서 N은 사용자가 설정한 LLM 제공업체의 속도 제한(rate limits) — OpenAI의 TPM/RPM, Anthropic의 분당 예산 등 — 을 준수합니다).
- Batches 탭에서 이들을 동시에 모니터링합니다:
- 결과를 수집하고, 후보자별 분석 내용을 작성하며, 비교 매트릭스를 구축합니다.
- 실패 사례를 개별적으로 표시하여, 성공한 47개를 다시 실행할 필요 없이 타임아웃(timed out)된 3개만 재시도할 수 있게 합니다.
사용자 중심의 UX는 "에이전트에게 무언가를 요청하고 그것이 실행되는 것을 지켜보는 것"입니다. 에이전트 중심의 UX는 입력 항목 리스트와 서브 프롬프트 템플릿(sub-prompt template)을 받는 fan_out 도구입니다.
중요한 이유: 병렬 실행(parallel execution)은 "문서 하나에 유용한 수준"과 "실제 업무량에 유용한 수준" 사이의 격차를 메워줍니다. 일단 이를 갖추게 되면, 밀린 문자열 번역, 고객 지원 티켓 분류, 쌓여 있는 송장에서 필드 추출, 루브릭(rubric)에 따른 N개의 제안서 비교 등 어디에서나 활용 사례를 찾을 수 있습니다.
제대로 구현하기 어려운 이유: 속도 제한(rate-limit) 인지 문제입니다. Praxia는 활성화된 LLM 제공업체의 공개된 제한 사항을 기반으로 동시성(concurrency)을 구성하므로, GPT-4o-mini의 TPM 상한선이 Claude Sonnet과 다르다는 점이나, 사용자의 Ollama 인스턴스가 로컬 GPU에서 병목 현상이 발생한다는 점을 일일이 기억할 필요가 없습니다.
세 번째 기능: 채팅으로부터 생성되는 네이티브 편집 가능 .pptx
이 기능은 제가 가장 자랑스럽게 생각하는 부분이며, 기술적으로 가장 흥미로운 부분입니다.
LLM이 "슬라이드를 생성"할 때, 일반적으로 얻게 되는 것은 Markdown, HTML 미리보기, 스크린샷, 혹은 기껏해야 Reveal.js 페이지입니다. PowerPoint에서 열고 편집할 수 있는 실제 .pptx 파일을 얻는 경우는 거의 없습니다.
Praxia는 진짜를 수행합니다:
나: Documents/sales/ 폴더를 바탕으로 3분기 회고 데크(deck)를 초안 작성해줘. 차트 3개, 요약 슬라이드 1개, 다음 조치(next-actions) 슬라이드 1개로 구성해줘. 기업 색상은 네이비와 화이트야.
내부적으로 일어나는 과정:
-
계획 (Plan) — LLM이 개요를 작성합니다: 5개의 슬라이드, 각 슬라이드에는 제목, 콘텐츠 유형 (불렛 포인트 / 차트 / 텍스트), 그리고 인덱싱된 sales 폴더의 출처 인용(source citations)이 포함됩니다.
-
코드 생성 (Code-gen) — LLM이 python-pptx를 사용하여 데크를 구성하는 Python 코드를 작성합니다. 변환되는 Markdown이 아닙니다. 실제 python-pptx 호출을 사용합니다:
shapes.add_chart(),shapes.add_text_box(),text_frame.paragraphs[0].font.color.rgb = RGBColor(0x1f, 0x2a, 0x5e). -
렌더링 (Render) — 번들링된 Python 사이드카(sidecar)가 해당 코드를 실행합니다.
.pptx파일이 디스크에 생성됩니다. -
비전 검토 (Vision review) — 데크가 PNG(슬라이드당 하나)로 변환되어 비전 기능이 있는 LLM (GPT-4o, Claude 3.5 Sonnet 등)으로 전송되며, 다음과 같은 프롬프트가 함께 전달됩니다: "이 슬라이드들을 확인하세요. 제목을 읽을 수 있나요? 텍스트 박스가 적절한가요? 색상이 일관된가요? 차트가 어긋나 있지는 않나요?"
-
반복 (Iterate) — 비전 검토 단계에서 문제(예: "3번 슬라이드 제목이 넘침", "2번 차트의 레이블이 겹침")가 발견되면, LLM이 문제가 된 부분을 다시 생성하고 4단계를 다시 실행합니다. 보통 한두 번의 반복이면 충분합니다.
-
승인 (Approve) — Praxia는 최종 데크를 승인 대화 상자에 표시합니다. '적용(Apply)'을 클릭하면
.pptx파일이 작업 공간(workspace) 폴더에 저장됩니다.
PowerPoint에서 열어 정상적으로 편집할 수 있는 결과물입니다. 텍스트 상자는 실제 텍스트 상자입니다. 차트는 실제 차트입니다 (붙여넣은 이미지가 아니라 임베디드 데이터에 의해 구동됩니다). 비전 패스 (vision pass)가 불일치를 명시적으로 확인하기 때문에 색상 구성도 일관됩니다.
이것이 어려운 이유: LLM은 공간 추론 (spatial reasoning)에 취약합니다. LLM은 10인치 너비의 슬라이드에서 text_box(left=Inches(5), top=Inches(3), width=Inches(8), …)를 즐겁게 생성하지만, 상자가 가장자리 밖으로 벗어난다는 사실을 알아차리지 못합니다. 비전 리뷰 루프 (vision-review loop)가 이를 잡아냅니다. LLM은 코드만으로는 방금 생성한 슬라이드를 "볼" 수 없지만, 렌더링된 PNG는 "볼" 수 있습니다. 이 두 번째 패스가 루프를 완성합니다.
이것이 중요한 이유: "슬라이드에 대한 Markdown 개요를 가지고 있다"와 "CFO에게 보낼 수 있는 다듬어진 PowerPoint를 가지고 있다"의 차이는 대략 한 시간 정도의 번거로운 작업이며, 이는 역사적으로 AI 도구들이 해결하지 못했던 부분입니다. 이 앱이 그 간극을 메웁니다.
앱의 형태
세 개의 탭:
- Chat (채팅) — 에이전트와 대화하는 곳
- Documents (문서) — Praxia가 감시하고 인덱싱하는 폴더 (RAG 방식의 검색, 완전 로컬)
- Workspace (워크스페이스) — Praxia가 사용자를 대신해 파일을 작성하는 곳이며, 모든 작업은 승인 대화 상자에 의해 제어됩니다.
디스크를 건드리는 모든 작업 (파일 쓰기, 파일 삭제, 파일 덮어쓰기)은 작업당 승인 대화 상자를 거칩니다. 에이전트는 절대 조용히(silently) 쓰지 않습니다. 만약 기존 파일을 덮어쓰려고 하면, 사용자는 차이점(diff)을 확인하고 결정합니다.
이것은 에이전트형 데스크톱 앱 (agentic desktop app)을 구축할 때 가장 어려운 부분 중 하나이며, 제가 클라우드 우선 (cloud-first) AI 도구들과 가장 강력하게 의견을 달리하는 지점이기도 합니다. 신뢰는 '아니오'라고 말할 수 있는 능력에서 나옵니다. 모델이 가끔 잘못된 파일 경로를 환각 (hallucinate)하더라도, 모델이 무엇을 하려는지 알려주는 대화 상자가 있다면 괜찮습니다. 하지만 동일한 환각률을 가진 모델이 디스크에 조용히 파일을 쓴다면 그것은 재앙입니다.
진정한 로컬 우선 (Local-first)
당신의 문서와 채팅 기록은 ~/Praxia/(또는 당신이 지정한 위치)에 저장됩니다. 저는 당신의 데이터를 보유하는 백엔드를 운영하지 않습니다. 채팅 메시지를 보내면, Praxia는 당신이 설정한 LLM 제공업체 — OpenAI, Anthropic, Azure, Gemini, Ollama, LM Studio — 중 하나로 메시지를 라우팅하며, 해당 제공업체는 당신의 기기에서 직접 요청을 받게 됩니다.
만약 엄격한 온디바이스 (on-device) 동작을 원한다면: Ollama 또는 LM Studio를 제공업체로 설정하세요. 어떠한 HTTPS 호출도 당신의 기기를 떠나지 않습니다. 에이전트 루프 (agent loop), 검색 (retrieval), 스케줄링 (scheduling), 배치 팬아웃 (batch fan-out), 그리고 .pptx 렌더링이 모두 로컬에서 수행됩니다.
이는 텔레메트리 (telemetry)나 모델 레지스트리 확인을 위해 여전히 외부 서버와 통신하는 대부분의 "로컬 AI" 데스크톱 앱과는 다른 입장입니다. Praxia는 그 어느 것도 하지 않습니다. 유일한 외부 트래픽은 당신이 선택한 LLM 제공업체로 향하는 것뿐이며, 만약 로컬 제공업체를 선택했다면 외부 트래픽은 전혀 발생하지 않습니다.
내부의 흥미로운 부분들
아키텍처 (이 부분은 기술적인 내용을 다루겠습니다):
Tauri 2 shell (Rust + Svelte 4 + WebView2)
↓ spawn / localhost HTTP
PyInstaller-frozen Python sidecar
...
셸 (shell)은 Tauri 2 앱입니다. 에이전트 백엔드는 PyInstaller를 통해 단일 praxia-server.exe로 번들링된 Python FastAPI 서버이며, 앱 실행 시 자식 프로세스로 시작됩니다. 이들은 localhost HTTP를 통해 통신합니다.
이러한 구조를 선택한 이유:
- 동일한 Python 코드 경로가 CLI 사용자를 위한
pip install praxia로도, 데스크톱 사이드카 (sidecar)로도 실행됩니다. 하나의 코드베이스로 두 가지 배포 채널을 가집니다. - WebView2는 Electron의 Chromium보다 훨씬 가볍습니다.
- Rust 셸은 시스템 통합 부분(파일 대화 상자, OAuth 콜백, OS 알림 등)을 처리하며, 이 영역은 Tauri 2의 플러그인 생태계가 이미 적절한 추상화를 제공하고 있습니다.
짚고 넘어갈 만한 몇 가지 구체적인 사항들입니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기


