simpl-agent is a local-first AI orchestrator, and the constraint it was built around is a cheap one: its generalist text tiers resolve to Llama 3.1 8B and 70B (NVIDIA NIM by default), with local inference — Ollama, vLLM — supported alongside cloud providers. The registry declares three tiers, but the top two point at the same 70B, so in the shipped configuration there is no 405B-class generalist sitting above the work.
The premise is that a cheap model rarely fails from lack of intelligence. It fails from too much context and too vague a task. So the engineering budget goes into cutting the task down and shrinking what reaches the model rather than into buying a bigger one. What follows is how that is actually done — and, at the end, a straight split between the parts that run in the turn loop today and the parts that are built and tested but not yet called from it.
Routing is where a cheap setup is usually squandered — a model call spent deciding which model to call — so routing here barely uses one.
Intent classification is keyword-first across seven domains, scored in Korean and English. A model is consulted only when keyword confidence falls under 0.6, and then it is the 8B tier capped at max_tokens=10 — one word out. Complexity estimation is pure heuristics: length, sequential connectors, code fences, file mentions, turn count. The domain × complexity mapping is a table, not a prompt. And there is a fully synchronous path that routes with zero model calls.
What the router learns, it learns without a model either. Outcomes are recorded per intent fingerprint, and a mode's score is its matrix confidence + history at weight 0.40 + a critic at 0.10 + a cache-affinity bonus − a 0.55 penalty for a mode that has never once succeeded on that fingerprint. Switching modes needs 0.85 confidence: a hysteresis band, because thrashing between modes costs more than sitting in an imperfect one.
Above that sits a tier profiler that runs a task type across the tiers and records the minimum sufficient tier — the cheapest that clears a 0.85 quality bar under an LLM judge, where a costlier tier counts as overkill only if it fails twice over: under 0.02 quality gain and at least 5× the cost. Merely being more expensive is not enough to disqualify it. Named limit: this one is not wired into the turn loop yet, and with no judge client configured it falls back to stub constants — so it is a designed policy with tests behind it, not a measurement the running agent makes.
A mission becomes a plan of typed steps — seven kinds: GRILL, UNDERSTAND, DECOMPOSE, EXECUTE, VERIFY, REFLECT, FINALIZE. The kind is not decoration. It decides whether a step is delegated to a sub-agent (a DECOMPOSE always is; a high-risk EXECUTE with a compound description is too), and which persona it is handed to — UNDERSTAND to a researcher, VERIFY to a reviewer, or to a security auditor when the description mentions security.
No comments yet. Be the first to say something!
The shape expands with risk: LOW gets UNDERSTAND → EXECUTE → FINALIZE, MED adds DECOMPOSE and VERIFY, HIGH adds REFLECT, CRITICAL adds a second VERIFY pass. Risk is not the only driver — a goal that splits into several sub-goals earns a DECOMPOSE step on its own, and more than two earns a VERIFY — but the principle holds: cheap work stays cheap, and extra passes have to be bought.
Steps form a DAG with declared dependencies, cycle-checked by DFS at construction, and each walks a state machine that refuses illegal transitions — FAILED → RUNNING is legal, because that is a retry; COMPLETED → anything is not.
The guardrail that actually bites lands at the end of a step: if a VERIFY or EXECUTE step reports itself COMPLETED without having called a single tool, the run halts with an error. A cheap model's most expensive failure mode is announcing that it checked something it never ran, and that is the one place worth spending a hard stop instead of a politely worded prompt.
Finished steps hand off through a mailbox rather than one ever-growing context: a step packages its result as an addressed artifact for the steps that declared a dependency on it, and branch-local values can be tagged as bubbles that travel only down their own branch. Both exist for the same reason — a small model degrades fast as irrelevant context piles up, so context is delivered, not broadcast.
Planning is the honest exception to the cheap-model rule. The default planner is LLM-backed and hierarchical, and it reaches for a hosted flash model; the deterministic planner — same mission, same plan, zero tokens — sits underneath it as the fallback when LLM planning throws. This is the one place the project pays for a better model, and it was built to degrade to free rather than to broken.
The retrieval layer introduces no new store. It is a facade that normalises what already exists locally into a single contract across three tiers — working memory plus FTS5 full-text search over session transcripts, semantic facts, and multi-hop graph context.
It is lexical-first: FTS5 and token-overlap scoring carry the working tier, and the semantic tier ranks with a local, dependency-free trigram-hash embedding rather than a hosted embedding model. No network hop and no per-query embedding bill — the same reasoning as everywhere else here, applied to retrieval.
Every hit carries a citation URI — memory://working/<id>, session://<sid>/messages/<mid>, semantic://<id>, graph://context/<hash> — and the bundle reports citation coverage as a ratio, so "how much of this answer is anchored to something" is a number rather than an impression. The merge is deterministic: dedupe on tier + citation, then sort by score, tier rank, id. Same query, same bundle — which is what makes a retrieval regression visible instead of anecdotal.
If the agent picks your model tier and spends your money, that choice cannot be invisible. That is the entire argument for the interface.
So the cost store keeps live totals broken down by model and by mode, alongside a rolling window of the last 50 usage events. The tier policy is auditable while it runs, not reconstructable from a bill afterwards. Every routing decision publishes an event carrying the chosen mode, its confidence, the reason string, estimated cost, expected cache-hit ratio, and both the history and critic scores — and the frontend consumes it. The router's reasoning is a surface, not a log line.
The rest follows from being ambient rather than a destination: a translucent layered HUD — blurred surfaces, metric chips, panels that sit beside your work — instead of a chat page you visit. 25 Zustand stores hold state so the view keeps mirroring the backend through WebSocket drops rather than silently drifting. The agent topology is an editor — nodes renamed, roles and execution modes changed, edges added and removed — because a topology you can see but not correct is decoration. Human-in-the-loop sign-off, undo, and data-source badges are surfaces because gating, reversibility and provenance are only real if they are reachable. It ships as a Tauri desktop app, which is what local-first means once you follow it to the end.
The tiered registry, the keyword-first router with its zero-model path, typed mission planning with its delegation rules and its halt-on-unverified guardrail, and the cost and routing telemetry the UI renders — those run in the turn loop.
The tier profiler, the retrieval facade, skill exposure and the attention budget are library surface: implemented, unit-tested and exported, but not yet called from the turn loop. They are how the system is designed to spend a cheap model well; they are not yet what it does on every turn. Drawing that line costs less than being caught on the wrong side of it.
Scale: 981 Python modules and 921 test files in the engine, 69 tool modules, 371 skills; 32 components and 25 stores in the UI.
simpl-agent은 로컬 우선 AI 오케스트레이터이고, 만들 때 잡은 제약은 값싼 쪽입니다. 범용 텍스트 계층이 Llama 3.1 8B와 70B로 해결됩니다. 기본은 NVIDIA NIM이고 Ollama·vLLM 같은 로컬 추론도 클라우드와 나란히 지원합니다. 레지스트리는 세 계층을 선언하지만 상위 두 계층이 같은 70B를 가리켜서, 출고 설정에는 그 위에 놓이는 405B급 범용 모델이 없습니다. 전제는 이렇습니다. 값싼 모델이 실패하는 이유는 지능이 모자라서인 경우가 드물고, 컨텍스트가 너무 많고 과제가 너무 막연해서입니다. 그래서 예산은 더 큰 모델을 사는 데가 아니라 과제를 잘게 자르고 모델에 도달하는 양을 줄이는 데 들어갑니다. 아래는 그것을 실제로 어떻게 했는지이고, 마지막에 지금 턴 루프에서 도는 것과 아직 연결되지 않은 것을 갈라 두었습니다.
1. 무엇을 할지 정하는 데 최대한 적게 쓰기. 값싼 구성이 가장 많이 새는 곳이 라우팅입니다. 어떤 모델을 부를지 정하려고 모델을 부르는 지점이기 때문입니다. 그래서 여기서는 라우팅이 모델을 거의 쓰지 않습니다. 의도 분류는 7개 도메인에 대해 키워드 우선이고 한국어와 영어를 함께 채점합니다. 키워드 신뢰도가 0.6 아래로 떨어질 때만 모델을 부르며, 그때도 8B 계층에 max_tokens=10 상한을 걸어 한 단어만 받습니다. 복잡도 추정은 길이·순차 접속사·코드 펜스·파일 언급·턴 수를 보는 순수 휴리스틱입니다. 도메인 × 복잡도 대응은 프롬프트가 아니라 표입니다. 모델 호출이 0회인 완전 동기 경로도 있습니다. 라우터가 학습하는 방식에도 모델이 없습니다. 의도 지문별로 결과를 기록하고, 모드 점수는 매트릭스 신뢰도 + 이력 0.40 + 크리틱 0.10 + 캐시 친화 보너스 − 그 지문에서 한 번도 성공한 적 없는 모드에 대한 0.55 페널티로 계산됩니다. 모드를 바꾸려면 0.85가 필요한데, 모드 사이를 오가는 비용이 불완전한 모드에 머무는 비용보다 크기 때문에 둔 히스테리시스 구간입니다. 그 위에는 작업 유형을 계층별로 돌려 최소 충분 계층을 기록하는 프로파일러가 있습니다. LLM 저지 기준 0.85를 넘기는 가장 싼 계층을 고르고, 더 비싼 계층은 품질 이득 0.02 미만이면서 비용이 5배 이상일 때만 과잉으로 처리합니다. 단지 더 비싸다는 이유만으로는 탈락시키지 않습니다. 다만 이것은 아직 턴 루프에 연결되어 있지 않고, 저지 클라이언트가 없으면 스텁 상수로 대체됩니다. 그러니 돌아가는 에이전트가 매번 수행하는 측정이 아니라 테스트가 붙은 설계된 정책으로 읽어 주십시오.
2. 과제를 어떻게 쪼개는가. 미션은 타입이 있는 스텝들의 계획이 됩니다. GRILL·UNDERSTAND·DECOMPOSE·EXECUTE·VERIFY·REFLECT·FINALIZE 일곱 종류이고, 종류는 장식이 아닙니다. 그 스텝을 하위 에이전트에 위임할지(DECOMPOSE는 항상, 서술이 복합적인 고위험 EXECUTE도) 그리고 어떤 페르소나에 넘길지를 결정합니다. UNDERSTAND는 리서처, VERIFY는 리뷰어, 서술에 보안이 언급되면 보안 감사자로 갑니다. 계획의 형태는 위험도에 따라 커집니다. LOW는 UNDERSTAND → EXECUTE → FINALIZE, MED는 DECOMPOSE와 VERIFY 추가, HIGH는 REFLECT 추가, CRITICAL은 VERIFY를 한 번 더 붙입니다. 위험도만 형태를 정하는 것은 아닙니다. 목표가 여러 하위 목표로 갈리면 그 자체로 DECOMPOSE가 붙고 셋 이상이면 VERIFY가 붙습니다. 그래도 원칙은 유지됩니다. 싼 작업은 싸게 끝나고, 추가 패스는 값을 치러야 합니다. 스텝은 의존성을 선언한 DAG를 이루고 생성 시점에 DFS로 사이클을 검사하며, 각 스텝은 잘못된 전이를 거부하는 상태 기계를 지납니다. FAILED → RUNNING은 재시도라서 허용되고 COMPLETED → 무엇이든은 허용되지 않습니다. 실제로 무는 가드레일은 스텝 끝에 있습니다. VERIFY나 EXECUTE 스텝이 도구를 한 번도 부르지 않고 완료를 선언하면 실행이 에러로 정지합니다. 값싼 모델의 가장 비싼 실패는 실행하지도 않은 것을 확인했다고 말하는 것이고, 거기가 정중한 프롬프트 대신 하드 스톱을 쓸 값어치가 있는 유일한 지점입니다. 끝난 스텝은 계속 커지는 하나의 컨텍스트가 아니라 메일박스로 결과를 넘깁니다. 자기에게 의존을 선언한 스텝 앞으로 결과를 수신자 지정 아티팩트로 포장하고, 분기 안에서만 쓰는 값은 그 분기만 따라가는 버블로 태그할 수 있습니다. 둘 다 같은 이유에서 나왔습니다. 작은 모델은 무관한 컨텍스트가 쌓일수록 빠르게 나빠지므로, 컨텍스트는 뿌리는 것이 아니라 배달하는 것입니다. 값싼 모델 원칙의 정직한 예외는 플래닝입니다. 기본 플래너는 LLM 기반 계층적 플래너이고 호스팅된 플래시 모델을 부릅니다. 결정론적 플래너는 같은 미션에 같은 계획을 토큰 0으로 내놓으며 그 아래 폴백으로 앉아 있습니다. 이 프로젝트가 더 나은 모델에 값을 치르는 유일한 자리이고, 고장이 아니라 무료로 내려앉도록 만들었습니다.
3. RAG를 어떻게 적용했는가. 검색 계층은 새 저장소를 만들지 않습니다. 이미 로컬에 있는 것들을 세 계층에 걸친 하나의 계약으로 정규화하는 파사드입니다. 워킹 메모리와 세션 트랜스크립트에 대한 FTS5 전문 검색, 시맨틱 사실, 멀티홉 그래프 컨텍스트입니다. 기본은 어휘 검색 우선입니다. 워킹 계층은 FTS5와 토큰 중첩 점수가 담당하고, 시맨틱 계층은 호스팅 임베딩 모델이 아니라 의존성 없는 로컬 트라이그램 해시 임베딩으로 순위를 매깁니다. 네트워크 왕복도 질의당 임베딩 비용도 들지 않습니다. 다른 모든 선택과 같은 논리를 검색에 적용한 것입니다. 모든 히트는 인용 URI를 답니다. memory://working/<id>, session://<sid>/messages/<mid>, semantic://<id>, graph://context/<hash> 형태이고, 번들은 인용 커버리지를 비율로 보고합니다. "이 답의 얼마가 무언가에 근거하는가"가 인상이 아니라 숫자가 됩니다. 병합은 결정론적입니다. 계층과 인용으로 중복을 제거한 뒤 점수·계층 순위·id 순으로 정렬합니다. 같은 질의에 같은 번들이 나오고, 그래야 검색 품질 퇴행이 일화가 아니라 눈에 보이는 것이 됩니다.
4. 컨텍스트 경제. 스킬 노출은 371개 스킬 디렉터리와 447개 스킬 파일 중 한 턴에 최대 8개만 프롬프트에 올립니다. 순위는 결정론적이고(가중 자카드 중첩, 카테고리 일치, 플랫폼 일치, 감쇠 최근성, 고정 우선), 플랫폼이 맞지 않는 스킬은 즉시 탈락합니다. 400개짜리 목록을 시스템 프롬프트에 쏟아붓는 것이야말로 작은 모델이 잘못 고르게 만드는 방법입니다. 어텐션 예산은 상한 대비 사용량을 0.65에서 경고, 0.85에서 경보로 표시하되 올라갈 때만 이벤트를 냅니다. 스스로는 아무것도 압축하지 않고 보고만 하며, 압축 시점은 호출자가 정합니다. 궤적 압축은 7단계 스택입니다. 키 축약, 역할 약칭, 빈 값 생략, 부동소수 정밀도 축소, 객체 내 참조 중복 제거, 사고 체인 절단, 공백 없는 직렬화입니다. 여기는 솔직히 적습니다. 헤더의 40~50%는 측정값이 아니라 목표이고, 코드가 실제로 계산하는 비율은 토큰이 아니라 문자 수이며, 일곱 단계 중 셋은 구조상 손실이 있어서 빈 값 생략과 부동소수 반올림은 왕복이 되지 않습니다. 이것은 아카이브가 아니라 압축기이고, 모듈 자신의 독스트링이 지금 과대 포장되어 있습니다.
5. UI는 왜 이렇게 만들었는가. 에이전트가 모델 계층을 고르고 사용자의 돈을 쓴다면 그 선택이 보이지 않아서는 안 됩니다. 인터페이스의 근거는 전부 여기서 나옵니다. 그래서 비용 스토어는 모델별·모드별로 나눈 실시간 합계와 최근 50건의 사용 이벤트를 함께 들고 있습니다. 계층 정책을 청구서로 사후 복원하는 것이 아니라 돌아가는 동안 감사할 수 있습니다. 모든 라우팅 결정은 선택된 모드, 신뢰도, 이유 문자열, 예상 비용, 기대 캐시 적중률, 이력 점수와 크리틱 점수를 실은 이벤트를 발행하고 프런트엔드가 그것을 소비합니다. 라우터의 판단 근거가 로그 한 줄이 아니라 표면입니다. 나머지는 목적지가 아니라 곁에 머무는 성질에서 따라옵니다. 방문하는 채팅 페이지가 아니라, 흐릿한 표면과 지표 칩과 작업 옆에 놓이는 패널로 이루어진 반투명 레이어 HUD입니다. 25개 Zustand 스토어가 상태를 들고 있어 WebSocket이 끊겨도 화면이 조용히 어긋나지 않고 백엔드를 계속 반영합니다. 에이전트 토폴로지는 그림이 아니라 에디터입니다. 노드 이름을 바꾸고 역할과 실행 모드를 바꾸고 엣지를 넣고 뺄 수 있습니다. 보이지만 고칠 수 없는 토폴로지는 장식이기 때문입니다. 휴먼인더루프 승인, 되돌리기, 데이터 출처 배지가 표면인 이유도 같습니다. 게이팅과 가역성과 출처는 손이 닿아야 실재합니다. Tauri 데스크톱 앱으로 배포되는데, 로컬 우선을 끝까지 밀면 그렇게 됩니다.
지금 도는 것과 아직 아닌 것. 계층 레지스트리, 모델 호출 0회 경로를 가진 키워드 우선 라우터, 위임 규칙과 미검증 정지 가드레일을 갖춘 타입드 미션 플래닝, 그리고 UI가 그리는 비용·라우팅 텔레메트리는 턴 루프에서 실제로 돕니다. 반면 계층 프로파일러, 검색 파사드, 스킬 노출, 어텐션 예산은 라이브러리 표면입니다. 구현되고 단위 테스트가 붙고 내보내지긴 했지만 아직 턴 루프에서 호출되지 않습니다. 값싼 모델을 잘 쓰기 위한 설계이지 매 턴 실제로 하는 일은 아직 아닙니다. 그 선을 긋는 비용이 그 선의 반대편에서 들키는 비용보다 쌉니다.
규모는 엔진 쪽 Python 모듈 981개와 테스트 파일 921개, 도구 모듈 69개, 스킬 371개, UI 컴포넌트 32개와 스토어 25개입니다.
Live / 바로가기: agent.simplicity-is-art.com · Status / 상태: Beta (베타) · Pattern / 원칙: cheapest sufficient tier · local-first · Note / 참고: code closed, showcase public (코드 비공개, 쇼케이스 공개)