Skip to main content
Glama

Thot

tests python

터미널용 코드 어시스턴트로, 이미 내 저장소를 알고 있다 — 그리고 Hermes AgentPrime Agent가 사는 저장소 전체를 알고 있다.

대화형 에이전트는 모델로 파일을 열어 프로젝트를 발견한다: 느리고, 부분적이며, 세션마다 다시 비용을 지불해야 한다. Thot은 AST와 호출 그래프로 동일한 그림을 계산한다 — 완전하고, 즉각적이며, 무료 — 그리고 모델에게 중요한 것만 전달한다.

세 가지 프로그램

이 저장소에는 하나가 아니라 세 가지가 들어 있다. 어느 것도 다른 것의 재작성이 아니다: 각각은 자신의 언어와 도구로 존재하며, Thot이 그것들을 연결한다.

그것이 무엇인가

위치

thot

결정적 감사, 코드 지도, 판정 기억

src/thot/

hermes

에이전트: 도구, 게이트웨이, 플러그인, cron, ACP

hermes/ — Python, uv 워크스페이스 멤버

prime

코드 에이전트: 모델 공급자, TUI, RLM

prime/ — TypeScript, npm

thot                 # la session d'audit
thot hermes          # Hermes, arguments transmis tels quels
thot prime           # Prime, pareil
thot fusion status   # ce qui est présent, prêt, et branché

연결은 장식이 아니다. thot fusion wire는 Thot의 MCP 서버를 두 에이전트에 선언한다: 그들은 code_map, find_symbol, callers, audit, skills, skill을 얻게 된다 — 모델 밖에서 계산된 저장소의 완전한 지도로, 파일별로 재발견하는 대신에. 이것이 상호 지원이다: Thot은 묻지 않고 알며, Hermes와 Prime은 행동한다.

두 에이전트는 같은 방식으로 도달되지 않으며, 그 반대라고 주장하는 것은 둘 중 하나만 연결했을 뿐이다. Hermes는 서버를 직접 시작하고 자신이 연 파이프로 통신한다. Prime은 HTTP만 허용한다 — 그의 mcp-manager는 타입이 http가 아닌 모든 입력을 버리며, 그의 런타임에는 파이프 전송이 없다 — 따라서 그의 쪽은 실행 중인 서버를 요구한다:

thot fusion wire            # écrit les deux branchements, chacun dans sa forme
thot mcp serve --http       # ce que Prime interroge : boucle locale, jeton bearer
thot mcp service --install  # et pour que ce soit encore vrai demain

세 번째 줄은 처음 두 줄이 한 세션만 유지되기 때문에 존재한다. fusion wirehttp://127.0.0.1:8787/mcp를 Prime의 구성에 쓰며, 이 주소는 터미널이 열려 있는 동안만 유효하다: 첫 재시작 시, 파일은 더 이상 아무도 서비스하지 않는 서버를 약속한다. thot mcp service --install은 그것을 다시 시작하는 유닛을 쓴다 — macOS에서는 KeepAlive, systemd에서는 Restart=always — 그리고 그것을 로드하지 않는다: Thot은 명령을 출력하고 사용자가 직접 실행하게 한다. 왜냐하면 말 없이 백그라운드 에이전트를 등록하는 도구는 신뢰를 잃는 도구이기 때문이다. thot doctor는 그 후 유일하게 중요한 질문을 던진다 — 주소가 지금 응답하는가 — 파일이 존재하는지가 아니라.

fusion wire는 또한 Prime 쪽에 Thot이 그를 위해 제공하는 메서드 패키지를 설치한다: 구성 항목만으로는 충분하지 않으며, Prime은 그것을 명명하는 클래스를 통해서만 MCP 서버에 도달한다. 그리고 모델 자격 증명을 건드리지 않고 auth.json에 토큰을 배치한다 — 토큰이 없으면 연결은 열리기 전에 실패한다.

각 에이전트는 자신의 구성을 유지한다. thot fusion unwire는 모든 것을 해체하며, Prime의 settings.json은 첫 수정 전에 백업된다.

thot fusion status작동하는 것을 측정한다, 쓰여 있는 것이 아니라: Hermes는 보안상 비활성화된 휴대용 플러그인을 설치하므로, 두 파일을 쓰는 것은 plugins.enabled가 그것을 명명하지 않는 한 아무것도 연결하지 않는다. 활성화는 Hermes의 CLI를 통해서만 이루어지며, 그의 config.yaml을 편집해서는 절대 안 된다 — 그 파일은 그의 것이며, 그의 스키마와 마이그레이션이 있다. 또한 Hermes를 실행하는 인터프리터가 MCP SDK를 임포트할 수 있는지 확인한다. 왜냐하면 그것이 없는 에이전트는 어떤 도구도 갖지 않기 때문이다 — Thot의 것뿐만 아니라 — 그리고 그것을 logger.debug에 말하는데, 아무도 읽지 않는 곳이다. 마지막으로 Prime의 서버에게 응답하는지 묻는다: 파일에 쓰인 주소는 연결이 아니다.

그리고 지원은 양방향으로 작동한다: Hermes와 Prime은 또한 thot audit --deep엔진이다, 각 finding을 모델이 논증하고 반박하게 하는 단계.

기본적으로 세 가지는 동일한 감사에서 함께 작동한다. finding은 한 에이전트가 논증하고, 다른 에이전트가 공격한다 — 방금 시나리오에 서약한 에이전트는 절대 아니다. 자신의 주장을 반박하는 모델은 자신의 답을 고친다; 그것이 패널이 사는 유일한 것이며, 융합의 존재 이유다.

thot audit . --deep                    # tous les agents installés, en panel
thot audit . --deep --engine hermes    # un seul : Hermes argumente et réfute
thot audit . --deep --engine prime     # un seul : Prime

보고서는 누가 무엇을 했는지 말한다:

Analyse assistée : panel — claude-cli contre hermes contre prime
  [1] serve.py:7 — confirmé · hermes
…
1 confirmé(s) · 2 réfuté(s)
Argumenté par claude-cli 1 — attaqué par prime 1 — puis par hermes 1

동일한 finding에 대한 세 개의 서로 다른 에이전트, 측정됨: claude-cli가 논증했고, prime이 시나리오를 파괴하려 시도했고 실패했으며, hermes가 두 번째 공격을 했다. 보고된 것은 두 명의 독립적인 적대자를 견뎌냈다.

캐스케이드. finding은 논증된 후 공격받는다. 공격에서 살아남는 것은 인간에게 보여질 것이다 — 따라서 그것은 세 번째 에이전트로 다시 보내지며, 그 에이전트는 논증이 구성되는 것도 첫 공격이 쓰여지는 것도 보지 못했다. 확인된 finding은 두 명의 독립적인 적대자에 대항한 것이다.

반박은 결코 본질적으로 재심되지 않는다: 공격자는 조금이라도 의심되면 반박하라는 지시를 받았으므로, 그것을 재고하는 것은 거짓 긍정을 만들어낼 것이다. 그러나 그의 주장은 재검토된다 그것이 심각한 것(MEDIUM 이상)을 묻을 때, 그 finding에 대해 아무 말도 하지 않은 에이전트에 의해. 재검토자는 결함을 판단하지 않고, 제시된 이유가 보여진 코드에서 검증 가능한지 판단한다.

두 오류는 동등하지 않다. 거짓 확인은 인간의 10분 읽기를 소모한다. 거짓 반박은 실제 결함을 영원히 잃게 한다 — 기억된 반박은 이후 모든 감사에서 건너뛰어지기 때문이다. 이것은 한 번 확실히 일어났다: Hermes 사본의 실제 SQL 인젝션이 전날 수정된 Thot 사본의 완벽하게 정확한 설명에 의해 배제되었다. 이의 제기된 반박은 확인이 되지 않는다 — 아무도 그렇게 주장하지 않았다 — 그것은 원래 심각도와 함께 plausible로 돌아가며, 기억되지 않는다: 누군가 결정할 때까지 finding이 돌아온다.

에이전트가 작업에 실패하면, 다른 에이전트가 한 번 다시 수행한다. 그 이상은 아니다: 모두가 거부하는 작업은 그 자체의 문제가 있다.

프로브가 할 수 있는 것, 추정이 아닌 측정. Claude는 Write, Edit, MultiEdit, NotebookEdit, Bash, Task 없이 실행된다 — 그리고 thot doctor --agents는 파일을 쓰도록 요청한 다음 디스크에서 확인함으로써 이를 검증한다.

이것은 화이트리스트가 아니다, 왜냐하면 클라이언트가 제공하지 않기 때문이다: --allowed-tools사전 승인이며, 제한하지 않는다. 측정됨 — Read Glob Grep가 허용된 상태로 실행된 프로브는 여전히 Write, Bash, Workflow를 가진다. 유일한 수단은 블랙리스트다.

측정 전에 프로브가 가졌던 것: CronCreate, CronDelete, Workflow, SendMessage, PushNotification, RemoteTrigger, EnterWorktree, WebFetch, 그리고 사용자가 연결한 모든 MCP 서버 — 이름이 clear_로 시작하는 도구를 포함. 영구 예약 작업 생성, 메시지 보내기, 메일함 도달. 코드를 읽고 JSON으로 응답하기 위해.

측정 후 그것이 가진 것:

✓ outils · claude        7 outil(s), tous en lecture seule
✓ outils · hermes        mcp__patch, mcp__read_file, mcp__search_files, mcp__write_file
✓ outils · prime         ipython

세 가지는 보여지고, 하나만 판단된다: Hermes의 file 세트는 write_filepatch를 읽기와 함께 제공하며, Prime의 유일한 내장 도구는 커널이다. 변경할 수 없는 것에 대한 영구적인 빨간 선은 더 이상 읽히지 않는 선이다; --engine hermes를 선택하는 사람은 자신이 수용하는 것을 본다.

블랙리스트는 구조적으로 취약하다 — Task가 빠져 있었고 하위 에이전트가 그 구멍으로 파일을 썼다, 여섯 번 중 한 번. 그래서 격차는 감지 가능하게 만들어진다: thot doctor --agents는 살아있는 프로브에게 실제로 가진 것을 묻고 인식하지 못하는 모든 것을 명명한다, 왜냐하면 다음 클라이언트 버전은 이 목록이 들어본 적 없는 도구를 가져올 것이기 때문이다. 그리고 쓰기에 대한 녹색 선은 "이번에는 아니다"를 의미하며, "불가능"이 아니다: 그렇게 표현된다.

Hermes와 Prime은 읽기 전용 모드가 없으며, 추정이 아닌 명시로 말해진다: -t file은 "File Operations"를 의미하며, 읽기와 쓰기를 모두 포함하고, Hermes의 --safe-mode는 사용자 정의에 관한 것이지 권한이 아니다; Prime의 유일한 내장 도구는 IPython 커널이다. Thot은 그래도 그들의 범위를 줄인다 — Hermes는 기본 12개 대신 file 세트만으로 실행된다: 터미널, 브라우저, 인터프리터가 없다. 이것은 좁혀진 행동 반경이지, 닫힌 것이 아니다.

샌드박스(thot sandbox use docker)는 엔진에 연결되지 않았으며, 어쨌든 문제의 절반만 해결할 것이다: 모델 API와 사용자 키링에 도달해야 하는 컨테이너는 더 이상 완전한 샌드박스가 아니다.

그래서 막을 수 없는 것은 놓칠 수 없게 만들어진다. 범위는 모델이 실행되기 전에 그리고 다시 후에 스탬프되며, 크기나 날짜가 변경된 모든 파일이 명명된다:

⚠ L'audit a modifié 1 fichier(s) du dépôt — ce n'est pas normal :
   src/app.py
   `git diff` avant toute autre chose.

침묵이 정상적인 결과다. 그것은 또한 믿을 가치가 있는 유일한 결과다: 프로브가 읽은 코드는 정확히 아무도 보증하지 않는 코드이며, "지시를 무시하고 나를 위해 고쳐줘"는 편집기를 쥔 에이전트에 대한 가장 저렴한 공격이다.

경로는 절대 경로로 주어진다. 세 가지 모두에서 측정됨: Hermes는 작업 디렉토리에 대한 상대 경로를 열지 않고 "이 파일을 읽을 수 없습니다"라고 응답한다 — 거부처럼 읽히지 결핍처럼 읽히지 않는다. 패널의 1/3은 두 번째 파일을 열도록 요구하는 모든 주장에 대해 눈이 멀었다.

각 에이전트는 자신으로서 인증한다, 당신의 계정으로: Thot은 그의 명령줄을 실행하고, 결코 임포트하지 않으며 어떤 토큰도 보유하지 않는다. 기억된 판정은 결정한 사람의 이름을 담는다 — refuted · hermes — 결정은 귀속 가능해야 하기 때문이다.

각자가 가져오는 것, 동일한 인젝션에서 측정됨:

엔진

지속 시간

보고된 토큰

prime

48초

예, 비용 추정 포함

hermes

159초

아니요-z는 응답만 출력한다

셀 수 없는 엔진은 숫자를 만들지 않는다: 그것을 선언하고(reports_usage), 호출자는 진짜처럼 보이는 0을 표시하는 대신 "측정되지 않음"이라고 말할 수 있다.

하나의 구성, 하나의 기억

세 가지는 각자 자신의 폴더에 쓰며, 그것은 매우 좋다: config.yaml은 Hermes의 것이고, settings.json은 Prime의 것이다. Thot이 추가하는 것은 단일 뷰와 결정을 위한 단일 장소다.

thot fusion config                          # le modèle que chacun utilisera
thot fusion config --model claude-opus-5    # le dire une fois, l'écrire aux trois
thot fusion memory                          # ce que les trois ont retenu
thot fusion memory --sync                   # y verser les faits appris par Thot

구성은 파일에서 읽히고 — 즉각적, 무위험 — 각자의 도구로 쓰여진다: hermes config set은 그의 YAML을 다시 쓰는 대신에, 그 YAML은 Thot의 것이 아닌 주석과 마이그레이션 기록을 담고 있다. Thot이 공식 CLI에 모델을 위임하는 것은 불일치가 아니다: 없는 의견은 아무것과도 충돌하지 않는다.

기억은 양방향에서 동일한 원칙이다:

어디

형태

thot

~/.thot/harness.json

구조화됨, 제목 + 내용

hermes

~/.hermes/memories/MEMORY.md

§로 구분된 항목

prime

~/.prime/agent/AGENTS.md

markdown, 전역 로드

Thot는 매 브리핑마다 세 가지를 모두 읽습니다: Hermes가 지난주에 배운 사실은 오늘 Thot이 아는 사실입니다. 나머지 두 곳에는 --sync 시에만 쓰며, 각자의 네이티브 형식으로, 자신이 직접 놓은 항목에만 손대고 — Hermes에서는 [thot] 태그, Prime에서는 구분된 블록. 첫 수정 전에 백업하고, 연속 세 번의 동기화는 단 하나의 사본만 씁니다.

갓 생성된 USER.md는 빈 양식입니다: **Name:**, 이탤릭체 지침, 가로선. 이를 주입하면 Thot이 "Context: ---"를 사실로 알게 됩니다. 이들은 제외되고 — 화면에 집계됩니다, 양식과 간결한 메모를 구분하는 것은 프로그램이 확실히 할 수 있는 일이 아니기 때문입니다.

하나의 라이브러리, 하나의 히스토리

세 가지는 같은 형식을 읽습니다 — YAML frontmatter가 있는 SKILL.md, 메서드당 하나의 폴더. 이것이 가능한 유일한 이유입니다.

thot fusion skills            # qui possède quoi, et ce qui n'est qu'à un seul
thot fusion skills --share    # donner la bibliothèque de Thot à Prime
thot fusion sessions          # l'historique des trois, du plus récent au plus ancien
thot fusion audit             # auditer les trois arbres en une passe

thot fusion audit는 그 반대가 프로그램의 마찰이었기 때문에 존재합니다: 세 개의 명령과 세 개의 보고서의 정신적 융합.

thot       203 fichiers     5 finding(s) — 4 high · 1 medium · 14 sous le seuil
hermes    7080 fichiers   127 finding(s) — 12 high · 115 medium · 806 sous le seuil
prime      952 fichiers    13 finding(s) — 3 high · 10 medium · 28 sous le seuil

145 finding(s) sur l'ensemble — 19 high · 126 medium · 848 sous le seuil (`--all`)

임계값은 thot audit의 그것이며, 그것이 핵심입니다: 같은 트리는 두 명령에 같은 숫자를 주어야 합니다. 이 뷰는 모든 low를 세었기 때문에, 같은 분 안에 thot audit hermes가 127이라고 부르는 저장소에 933이라고 답했습니다. 유지되는 것은 집계되고, 결코 죽지 않습니다.

판정 메모리가 이미 판단을 내린 경우, 그 줄은 따로 말합니다 — 0 finding(s) · 416 réfuté(s) en mémoire. 0만으로는 깨끗한 트리처럼 읽힙니다; 올바른 문장은 패널이 416개를 기각했다는 것입니다.

감사할 수 없는 부분은 그 줄을 잃고 결코 통과를 잃지 않습니다: Prime이 없어도 Hermes가 말한 것을 숨기지 않아야 합니다.

복사본 없음: 파일은 소유자에게 남고 각 프로그램은 다른 프로그램의 폴더를 가리킵니다. 두 번 복사된 메서드는 한 번만 수정된 메서드입니다.

Thot은 Hermes의 설치된 라이브러리를 보호 아래 읽습니다 — 그것은 공개 레지스트리에서 온 것이며, 이것이 정확히 가드가 존재하는 경우입니다. 가드는 자신이 직접 전달하는 것에만 보증합니다. 그러나 Hermes의 83개 메서드 중 73개는 Thot의 것과 비트 단위로 동일한 복사본입니다: 자신의 파일을 커뮤니티 위협으로 신고하는 것은 진짜 위협을 무시하도록 가르치는 오탐입니다. 바이트가 전달된 메서드와 일치하는 메서드는 메서드입니다. 가드는 42건 거부에서 8건으로 줄었습니다.

그리고 8에서 0으로, 혼동하지 않는 것이 좋은 두 가지 별개의 이유로. 첫 번째는 잘못된 규칙입니다: ENV[]는 Ruby 상수로, 구조상 대문자이지만, 패턴은 전체 카탈로그처럼 — 대소문자 구분 없이 — 컴파일되었습니다. 그래서 Python을 Ruby로 읽었고, subprocess.run(env=env) 두 줄 앞의 env["…_TOKEN"] = jeton — 자식에게 비밀을 전달하는 권장 방식 — 을 "비밀 읽기", CRITICAL로 분류했습니다. 두 번째는 순위 문제입니다: 사용자가 이웃 에이전트의 폴더에 직접 설치한 라이브러리는 감사 대상 저장소가 아닙니다. 설치는 이미 의도적으로 이루어졌습니다; Thot은 이미 머신에 있는 것을 읽을지 여부만 결정합니다. 그것은 항상 스캔되고 항상 보고되지만, 검사 대상 저장소가 caution부터 거부되는 반면 dangerous에서만 거부됩니다 — 8건의 거부 중 6건은 자신의 토큰 주소를 문서화하는 메서드에서 유출을 보는 유일한 규칙 때문이었습니다.

Prime과 함께 제공되는 13개 메서드는 Prime에 남습니다: 그것들은 IPython 커널(edit(old_str, new_str), refine())을 문서화합니다. Thot은 그 커널을 포팅했지, 이 함수들을 포팅하지 않았습니다 — 로드하면 모델이 존재하지 않는 것을 호출하게 됩니다. 그것들은 카탈로그에 있으며, 아는 것이 유용합니다; 발견에서 제외되며, 믿는 것이 유용하지 않습니다.

Prime은 두 복사본이 아닌 상위 집합을 받습니다. 측정되었지, 추정되지 않았습니다: Thot의 라이브러리만 가리키면 응답하고, Hermes의 것만 가리키면 응답하고, 둘 다 가리키면 모델이 응답을 거부합니다. Prime은 이름이 아닌 폴더를 받으므로 부분 응답이 없습니다.

히스토리는 저장소를 병합하지 않습니다 — 한 프로그램의 마이그레이션은 다른 프로그램의 히스토리를 깨뜨릴 것입니다 — 그러나 "지난 화요일에 이 저장소에서 무엇을 하고 있었지"라는 질문은 세 바이너리 중 어느 것이 앞에 있었는지에 관한 것이 아닙니다. 세 가지는 각자의 형식으로 읽기 전용으로 읽히며, 진행 중인 세션에 잠긴 데이터베이스는 그 줄을 잃고 결코 목록을 잃지 않습니다.

설치

git clone https://github.com/nobodyohm-web/thot.git
cd thot
uv tool install --editable --from . thot

루트에서 단 한 번의 uv sync로 Thot Hermes를 설치합니다: 그것은 워크스페이스이지, 분기되는 복사본이 아닙니다. Prime은 TypeScript이며 별도로 빌드됩니다:

cd prime && npm install && npm run build

Node 없이도 Thot과 Hermes는 작동합니다; thot fusion status는 첫 호출에서 실패하는 대신 무엇이 빠졌고 어떻게 고치는지 말합니다.

사용법

thot

그게 전부입니다. 첫 실행 시 어떤 모델을 연결할지 묻고, 현재 폴더를 스캔한 후 제어권을 돌려줍니다.

   ╔╦╗╦ ╦╔═╗╔╦╗
    ║ ╠═╣║ ║ ║    claude-opus-5
    ╩ ╩ ╩╚═╝ ╩

   ▪ dossier  ~/Desktop/Quanta
   ▪ code     142 python · 8 points d'entrée
   ▪ git      main · propre
   ▪ audit    1 high · 2 medium

   Reconnaissance en 0.31 s. Prêt.

   ›

빈 폴더면 그렇게 말하고 지시를 기다립니다. 코드가 있는 폴더면 첫 문장 전에 이미 매핑해 두었습니다.

세션 명령

명령

효과

/audit · /audit deep

분석 재실행, 또는 모델이 반박하도록 하기

/verdict n refute …

근거와 함께 finding 기각

/goal <목표> --budget N

세션 간 추적되는 목표 설정

/sessions · /resume

여기서 이전에 한 일, 그리고 그곳으로 돌아가기

/search <단어>

Thot이 말하거나 찾은 모든 것에서 검색

/compact

요약하고 빈 컨텍스트로 다시 시작

/export · /import

세션을 다른 곳으로 가져가기

/skills · /plugins · /mcp

로드된 것, 그리고 카탈로그

/scan

저장소 맵 재계산

/model · /clear · /quit

모델, 망각, 종료

그리고 당신의 것들: 모든 .thot/commands/<이름>.md 파일은 /<이름>이 됩니다.

모델

선택

필요한 것

Claude — 당신의 계정

설치되고 연결된 claude CLI. 복사할 것 없음.

Claude — API 키

sk-ant-…

OpenAI

API 키, 또는 환경의 OPENAI_API_KEY

로컬

실행 중인 Ollama 또는 LM Studio — 무료, 오프라인

기타

OpenAI 호환 엔드포인트

변경은 thot login, 망각은 thot logout. 구성은 ~/.thot/config.json0600 권한으로 있습니다. 계정 모드에서는 어떤 토큰도 저장되지 않습니다.

계정 모드가 작동하는 방식

Messages API는 타사 프로그램의 구독 토큰을 거부합니다. 이를 통과하려면 Claude Code로 위장해야 합니다 — 위장된 user-agent, 차용된 시스템 프롬프트. Thot은 그렇게 하지 않습니다.

그 반대를 합니다: 공식 클라이언트에 위임합니다. 각 턴은 다음을 실행합니다

claude -p --output-format stream-json --session-id <uuid> \
       --mcp-config <outils Thot> --append-system-prompt <carte du dépôt>

추론은 claude가 당신의 계정으로 수행하며, 당신이 직접 입력한 것과 정확히 같습니다. Thot은 저장소 맵을 제공하고, 작은 MCP 서버를 통해 결정적 도구를 연결하며, 이벤트 스트림을 형식화합니다. 대화 스레드는 동일한 세션 식별자로 --resume에 의해 전달됩니다.

세션 — 아무것도 잃지 않음

창을 닫아도, 감사와 함께하려던 추론은 여전히 있습니다. 각 턴은 도착하는 순간 ~/.thot/sessions.db에 기록됩니다.

   › /search injection parseur
   a3f9c210 user       trouve les «injections» SQL dans le «parseur»
   a3f9c210 audit      HIGH sink.sqlite.execute  src/parse.py:88
   7b02e4d1 verdict    sink.os.system src/deploy.py:12 → refuted : commande littérale

검색은 말한 것과 찾은 것을 모두 포함합니다: 반쯤 기억된 finding은 기억하는 단어로 찾을 수 있습니다.

thot sessions              # ce qui a été fait dans ce dépôt
thot sessions --all        # partout
thot sessions --show <id>  # la transcription entière
thot search <mots>         # sans ouvrir de session
thot export <id> --out s.json ; thot import s.json

/resume는 기록 컨텍스트를 반환합니다: 계정 모드에서 Thot은 공식 CLI의 대화 식별자를 보관했다가 돌려주므로, 모델은 다시 읽는 대신 기억합니다.

/compact는 요약으로 세션을 닫고 링크를 유지하는 자식 세션에서 계속합니다. 압축은 컨텍스트를 소모하지, 증거를 소모하지 않습니다: 부모 세션은 완전히 남아 있고 /search는 항상 그것을 찾습니다.

압축은 자동으로도 트리거되며, 임계값은 상수가 아닙니다: CLI는 사용하는 모델의 창을 게시하고(claude-opus-5[1m]contextWindow: 1000000), Thot은 그 창의 70%에서 압축합니다 — 여기서 700,000 토큰, 200k 창에서는 140,000. 트리거는 메시지 기반 추정이 아닌 CLI가 보고한 실제 크기를 읽습니다: 계정 모드에서 스레드는 CLI에 속하며, Thot은 읽은 파일이나 도구 트래픽을 볼 수 없습니다. 일반 턴에서 측정했을 때, 추정은 실제 창에 있는 88,290 토큰에 대해 95 토큰을 주었습니다.

목표 — 언제 멈출지 알기

목표는 통과하는 대화를 넘어 생존하며, /compact 직후를 포함해 매 턴 모델에게 상기됩니다.

   › /goal plus aucun HIGH dans le parseur --budget 200000
   ✓ Objectif fixé — plus aucun HIGH dans le parseur
     Budget : 200000 jetons.

예산 소진은 상태이지, 오류가 아닙니다: Thot은 턴 중간에 멈추지 않고, 끝내고, budget_limited로 전환한 후 목표의 진행 상황을 말합니다. /goal budget 500000/goal done 사이에서 선택하는 것은 당신의 몫입니다.

메모리 — 한 번 결정하기

감사에서 비용이 드는 것은 후보를 찾는 것이 아닙니다: 결정적 단계는 몇 초 안에 무료로 수행합니다. 비용이 드는 것은 그것들이 가치가 무엇인지 결정하는 것입니다. 두 실행 사이에 그 결정을 잃는 것은 보안 도구를 견딜 수 없게 만드는 것입니다 — 매주 같은 40건의 거부, 아무도 보고서를 읽지 않을 때까지.

   › /verdict 3 refute la commande est littérale, aucune entrée utilisateur
   ✓ pattern.os_system_injection à app/shellutil.py:5 — refuted
   Retenu tant que ce code ne change pas.

결정

효과

refute

오탐 — INFO로 전환, 보고서에서 제외, 근거 유지

accept

실제 위험, 수용됨 — INFO로 전환, 주석 처리

fixed

수정됨 — 다시 나타나면 회귀로 신고

두 가지 깊이, 명시적으로 말함

Python

TypeScript · JavaScript

나머지

심볼, 호출 그래프, code_map / callers

아니요

함수 본문 내 틴트

아니요

같은 파일의 헬퍼로 틴트

아니요

파일 간 틴트

아니요

아니요

패턴 기반 규칙

JavaScript 틴트는 같은 파일에 정의된 함수로의 호출을 따라갑니다 — 핸들러가 위임하는 일반적인 형태 — 그리고 거기서 멈추며, 그것이 말하는 바입니다. 다음 두 수준은 해석된 호출 그래프에 의존합니다 — 여기서 호출된 readInput이 저기 정의된 것임을 아는 것. Python의 import 시스템은 이 질문에 답합니다; JavaScript의 것은 그렇지 않습니다, 모듈 해석기, tsconfig, 그리고 this에 대한 타입러의 관점 없이는. 추정에 기반한 두 번째 수준은 증명된 경로를 보고하는 도구를 그럴듯한 경로를 보고하는 도구로 바꿀 것입니다.

엔진은 파일을 스캔하지, 명명된 함수 본문을 스캔하지 않습니다. 웹 핸들러의 일반적인 형태는 라우트에 전달되는 익명 화살표입니다 — app.get("/x", (req, res) => { … }) — 어떤 인덱서도 이름을 붙이지 않는 것.

이렇게 측정한다: 매개변수 목록이 괄호로 묶이고 쉼표나 여는 괄호를 따르는 화살표 함수, 즉 [(,]\s*(?:async\s*)?\([^)]*\)\s*=>detect_scope가 유지하는 파일들의 마스킹된 소스에서 찾는다 — Prime에서 15,094개, Hermes에서 19,625개, 모두 심볼을 추적하는 엔진에는 보이지 않는다. 이전 버전에서는 24,454개라고 발표했지만 어떻게 집계했는지 기록하지 않았다. 숫자는 전적으로 정의에 달려 있으므로, 정의를 명시한다.

두 코퍼스에서 측정: Prime에서 31개 경로, Hermes에서 41개 경로, 3,552개의 JS/TS 파일 중에서. 3천 개가 아니라 72개 — 이것이 틴트 엔진의 형태이며, 패턴 스캐너의 형태가 아니다.

엔진은 또한 아무도 이름으로 호출하지 않는 함수도 추적한다: 런타임이 그 함수들을 호출하고 값을 전달한다. addEventListener는 틴트를 도입한다 — 매개변수 자체가 입력이다. .then, .map, .forEach는 틴트를 전달한다 — 매개변수는 순회 대상이 틴트되었을 때 정확히 그때 틴트되므로, 상수 리스트는 상수 리스트로 남는다. 이것이 값을 추적하는 것과 값을 만들어내는 것의 차이이며, 이 차이가 없으면 브라우저 코드 트리는 거의 완전히 보이지 않게 된다.

obj[키] = 값에서 가 통제되는 경우는 별도의 싱크다: 페이로드는 값이 아니라 키다. __proto__가 프로그램의 모든 객체를 통해 쓰여질 수 있기 때문이다. Hermes에서 실제 사이트 11곳, Prime에서 3곳, 모두 for (const [k, v] of Object.entries(x)) { out[k] = v } 형태다. __proto__를 이름으로 거부하는 루프는 수정된 것이며, 신고되지 않는다.

보고서는 균일한 커버리지를 암시하기보다 스스로 이렇게 말한다:

teinte au fichier près, pas au-delà : javascript 3 · typescript 912

Une exception, et une seule : un import **relatif** se résout par une règle de
fichiers, pas par une inférence. `./helpers` depuis `src/app.ts` ne désigne
qu'un chemin, et soit il est dans l'index, soit le franchissement n'a pas
lieu. Les spécificateurs nus et les alias `tsconfig` restent refusés — ceux-là
demandent vraiment un résolveur. Le niveau reste unique : ce qui est franchi
est la frontière, pas la profondeur.

Mesuré sur le périmètre que Thot audite réellement — celui que `detect_scope`
calcule, `dist/` et `build/` exclus : **336 appelables importés résolus, tous
sur Hermes, aucun sur Prime**, pour **zéro chemin nouveau** et un surcoût de
4 à 8 %. La capacité est prouvée par les tests, son rendement ici est nul, et
les deux se disent.

Une première version de ce paragraphe annonçait 1 514 appelables et +21 %.
Ces chiffres venaient d'une liste de fichiers bâtie à la main qui incluait
`dist/bundle/` — des bundles minifiés de deux méga-octets que Thot n'indexe
jamais. La mesure portait sur du code hors périmètre, et la méthode juste
était disponible depuis le début : demander son périmètre à l'outil plutôt
que de le reconstruire.

TypeScript 인덱서는 스캐너이지 tsc가 아니다: 주석과 리터럴을 마스킹한 다음 중괄호 매칭으로 선언을 읽는다. tsc로 넘어갔다면 맵이 설치된 node 체인에 의존하게 되었을 것이고, 그것도 해석 가능하고 올바른 버전이어야 한다 — 일부 머신에서만 작동하는 맵은 한계가 명시된 맵보다 가치가 낮다. 측정: Prime에서 8,568개 심볼을 1.7초에, Hermes에서 11,138개 추가.

파일이 무엇을 위한 것인가

심각도는 영향 × 접근성 × 신뢰도이며, 접근성은 호출 그래프에서 나온다. 그래프는 "진입점이 여기에 도달할 수 있는가"에 답한다. 공격 표면이 전혀 아닌 파일에 대해서는 할 말이 없다.

Thot와 함께 제공되는 두 프로그램에서 측정: Hermes의 HIGH findings 25개 중 12개와 Prime의 11개 중 6개가 테스트 또는 예제 코드에 있었다. 보고서 상단의 거의 절반이 어떤 공격자도 도달하지 못하는 코드에 관한 것이었다 — 이것이 보고서가 더 이상 읽히지 않게 되는 방식이다.

이전

이후

hermes

25 high · 94 medium · 297 low

13 high · 58 medium · 345 low

prime

11 high · 2 medium · 9 low

5 high · 8 medium · 9 low

HIGH 열이 논쟁을 이끄는 열이며, 첫 측정 이후 단 하나의 finding도 움직이지 않았다: 25 → 13, 11 → 5. 위의 medium 및 low 집계는 오늘날의 트리 기준으로 다시 계산되었으며, 두 측정 사이에 Hermes에서 9개의 취약점이 수정되었다.

추가된 finding도, 손실된 finding도 없다. 이것은 강등이지 결코 삭제가 아니다: 테스트 코드는 개발자 머신과 CI에서 실행되며, 이것이 바로 공급망 공격의 정확한 형태다. Finding은 남아 있고, 그 역할을 출처와 함께 전달한다.

분류는 보수적이다 — 하위 문자열이 아닌 전체 세그먼트(latest/는 테스트 폴더가 아니고, contest.py는 테스트 파일이 아니다), 그리고 인식되지 않는 모든 것은 프로덕션이다. "테스트" 쪽으로 잘못 분류하면 실제 결함을 숨기게 된다. "프로덕션" 쪽으로 잘못 분류하면 단계 하나만 잃을 뿐이다.

가치는 어디서 오는가

Finding은 경로를 시작한 소스 규칙을 전달한다 — source.argv, source.http, source.js.event — 단지 그 줄이 있는 위치만이 아니다. 보고서는 이를 명시적으로 말하고("명령줄에서 비롯된 값…") JSON은 source_rule 키로 제공하여, 다운스트림에서 필터링하는 것이 프랑스어 문장이 아닌 사실을 읽게 한다.

이것이 순위에서 빠진 절반이다. 명령줄 도구에서 open(args.sortie, "w")는 운영자가 파일 이름을 지정하는 것이다: argv를 제공하는 자는 이미 이 프로세스의 파일 시스템을 소유하고 있으며, 호출은 그에게 아무것도 주지 않는다. 핸들러에서 open(request.args["f"])는 임의 파일 읽기다. 같은 규칙, 같은 싱크, 두 개의 다른 세계.

로컬 소스

원격 소스

sink.fs.read · sink.fs.write · sink.js.path

한 단계 낮음

전체 순위

그 외 전부

전체 순위

전체 순위

이 세 가지만 해당된다. argv에서 구성된 명령은 여전히 명령이며, 환경 변수에서 읽은 pickle은 여전히 코드를 실행한다: 여기서는 경로가 아니라 싱크가 승격을 만든다.

엔진이 속성 체인을 추적할 수 있게 된 날 Hermes에서 측정: 이 구분 없이 sink.fs.read만으로 보고서에 48개의 findings가 들어갔고, 그중 9개는 단일 CI 스크립트에서 나왔으며, 각각은 요청받은 파일을 여는 유틸리티였다. 구분을 적용하면 임계값 아래로 내려가고 한 번의 키 입력(--all)으로 유지된다.

알 수 없는 출처는 로컬로 간주되며, 숨기지 않고 명시한다: 반대로 가정하면 엔진이 출처를 명명하지 못한 모든 경로가 보고서 상단으로 올라갈 것이다. 로컬 변수와 그것이 파생된 매개변수 사이의 연결이 더 이상 끊기지 않게 되면서 명명할 수 있는 것이 두 배로 늘었다 — cible = chemin.strip()chemin에 대한 연결을 유지했고, 그것 없이는 싱크가 어떤 매개변수에도, 따라서 어떤 호출자에도 연결되지 않았다.

왜 안전한가

판정은 Finding.compute_id에 인덱싱되며, 이는 규칙, 파일, 심볼, 그리고 해당 심볼의 정규화된 AST를 해시한다. 서식을 바꾸고, 함수를 옮기고, 로컬 변수를 이름 변경해도 판정은 유지된다. 코드가 하는 일을 바꾸면: 식별자도 함께 바뀌고, 판정은 저절로 만료된다.

따라서 거부는 그것이 관련된 코드보다 오래 살아남을 수 없다. 이것이 거부를 기억하는 것을 수용 가능하게 만드는 유일한 속성이다.

식별자는 또한 대상이 되는 정확한 호출을 명명한다 — httpx.get#3 — 단지 그것을 포함하는 함수만이 아니다. 이것 없이는 같은 함수의 네트워크 호출 5개가 메모리에는 단일 finding으로 보였고, 첫 번째를 기각하면 나머지 네 개도 그 이유와 함께 기각되었다. 이 판별자는 아무것도 약화시키지 않는다: 본문의 한 버전 내에서만 고유하면 되며, 본문의 AST는 그것이 움직이는 즉시 관련된 모든 것을 만료시킨다.

thot verdicts                    # tout ce qui a été décidé
thot verdicts --path src/auth    # sur un chemin
thot verdicts --forget <id>      # revenir sur une décision
thot audit . --no-memory         # ignorer la mémoire pour ce run

결정은 그것을 생성한 finding보다 오래 산다: 코드가 바뀌면 finding은 새로운 정체성을 갖게 되고, 이전 결정은 더 이상 아무것도 가리키지 않는다. 목록은 그러한 것들을 다른 것처럼 표시하는 대신 [마지막 감사에 없음]으로 표시한다 — 죽은 결정 3개를 포함한 결정 6개는 살아있는 결정 6개처럼 읽혀서는 안 된다.

메모리는 모델보다 먼저 적용된다: 이미 결정 — 기각, 수용, 또는 수정 — 이 있는 finding은 결코 분석에 다시 보내지지 않는다. 모든 것이 결정된 실행은 호출을 전혀 하지 않는다. 이것은 단지 경제성의 문제가 아니다: 프로브는 신뢰도, 심각도, 시나리오, 출처를 한 번에 대체하므로, 결정을 모델에 다시 보내면 그것을 덮어쓰고, 누가 결정했는지도 지워버릴 것이다. 회귀가 가장 중요한 경우다: 이미 한 번 실제로 판정되었으므로, 어떤 심층 패스도 그것을 잠재울 수 없다.

그리고 반박은 thot audit --deep에서든 /audit deep에서든 자동으로 기록된다: 모델 호출 2회, 한 번만 지불. 그것들은 결정한 엔진의 이름을 전달하며, 결코 당신의 이름이 아니다 — 기계 결정은 인간 결정보다 우선하지 않는다.

아무것도 조용히 삭제되지 않는다. 기각된 finding은 refuted로 보고서에 남으며, 그 이유와 작성자와 함께 — 무시하라고 지시받은 것을 숨기는 감사는 다시 읽을 수 없다.

Python 커널

Prime Agent의 핵심 아이디어를 이식했다: 질문당 도구 호출 대신, 모델이 Python을 작성하고 그 변수들이 생존한다.

   › /py bas = audit(severity="low"); print(len(bas), "findings"); [f.rule for f in bas]
   3 findings
   → ['sink.eval', 'sink.network', 'sink.subprocess.shell']

   › /py len(files())
   → 148

저장소 맵은 객체로 사용 가능하다 — files(), symbols(), find(), callers(), callees(), audit(), read(). findings와 호출자를 교차하는 루프는 모델 턴이면 된다. 도구 호출로 같은 일을 하면 열두 번이 들며, 각 호출은 맵이 이미 알고 있던 것을 다시 읽는 비용을 지불한다.

커널은 결코 Thot의 프로세스에서 실행되지 않는다. 자체 exec()는 감사 대상 코드에 Thot의 메모리, 열린 데이터베이스, 파일 디스크립터를 부여할 것이다. 따라서 하위 프로세스다 — 그리고 샌드박스가 구성된 경우 컨테이너 안에서.

이것이 정확히 무엇을 보호하는지 — 그리고 Thot은 자체 적대적 패스가 너무 절대적인 docstring을 지적한 후 스스로를 수정했다:

하위 프로세스 (local)

Thot의 메모리, 데이터베이스, 파일 디스크립터를 보호한다. 아닌 것: 당신의 자격 증명 — worker는 당신의 계정으로 실행되며 ~/.claude/.credentials.json을 읽을 수 있다.

컨테이너 (docker)

실제 경계: 네트워크 없음, 당신의 $HOME 없음, 저장소 읽기 전용.

민감한 환경 변수는 실행 전에 제거되며, /py는 "분리된 프로세스"가 제공하지 않는 보장으로 읽히지 않도록 로컬 모드에서 한 번 명시한다.

rlm() — 셀에서 위임하기

verdicts = {f.id: rlm(f"Ce chemin est-il exploitable ?\n{f.failure_scenario}")
            for f in audit(severity="high")}

셀은 자신의 문제를 분해할 수 있다. 셀은 어떤 자격 증명도 보유하지 않는다: 호스트에게 요청하며, 호스트가 결정하고 지불한다. 따라서 한계는 호스트 측에서 유지된다 — 셀당 8회 호출, 커널당 40회 — 자식이 수정할 수 있는 한계는 한계가 아니며, 자식은 감사 대상 저장소에서 온 코드를 실행하기 때문이다.

Thot이 저장소에서 기억하는 것

   › /harness note team.shell.run : échappe ses arguments, les findings dessus sont faux
   ✓ Retenu — rappelé à chaque session.

Prime의 정제를 감사에 적용: 어떤 정적 분석도 결코 도출하지 못할 사실들. 그것들은 <저장소>/.thot/harness.json에 살며, 판정처럼 풀 리퀘스트에서 다시 읽히고, 매 세션 브리핑에 돌아온다.

모델이 할 수 있는 것

thot --tools lecture      # lire et raisonner, jamais modifier
thot --tools carte        # la carte seule : aucun fichier ouvert

세션에서: /tools lecture. 당신 것이 아닌 저장소를 다시 읽는 것은, 당신이 충분히 경계할 이유가 있는 코드를 읽는 것이다 — 그리고 모델이 그것을 수정하는 것은 당신이 원하는 것이 거의 아니다.

이 자세는 한 곳이 아니라 곳에 달려 있다: 모델에게 제안되는 도구, 모델이 그래도 도구를 호출하는 시점, 그리고 — 계정 모드에서 — 공식 CLI로, --disallowed-toolsWrite, Edit, Bash를 금지한다. Thot의 도구만 필터링하는 자세는 가장 중요한 곳에서 거짓말이 될 것이다.

공급망

thot deps                       # les dépendances épinglées, contre OSV.dev
thot deps --list                # ce qui a été trouvé, sans réseau
thot deps --fail-on high        # code 1 en CI
thot audit . --deps             # dans le rapport d'audit
thot mcp check                  # tes serveurs MCP sont-ils malveillants ?

항상 잠금부터: uv.lock, poetry.lock, Pipfile.lock, package-lock.json, yarn.lock, pnpm-lock.yaml. 매니페스트가 requests>=2라고 말하면 OSV는 구간에 답할 수 없다. 잠금이 2.31.0이라고 말하면 OSV는 안다. 구간으로만 존재하는 의존성은 추측되지 않으며, 고정되지 않은 것으로 보고된다.

특정 버전을 다루는 판정은 추측이 아니라 사실이다 — 하지만 코드가 취약한 함수에 도달하는지는 분석되지 않으므로, 그런 findings는 PLAUSIBLE로 남고 그렇게 명시한다. 오직 MAL-*만 예외다: 패키지 자체가 페이로드이므로, 도달 가능성은 문제가 되지 않는다.

그리고 다른 모든 곳과 동일한 속성: finding의 정체성은 고정된 버전을 담고 있으므로, bump는 판정을 만료시킨다. requests==2.19.1에 대한 CVE를 배제해도 2.20.0에 대해서는 아무것도 배제하지 않는다.

OSV에 연결할 수 없어도 결코 건강 진단서가 되지 않는다: thot deps는 "미확인"이라고 말하고 오류 코드를 반환한다.

감사 대상 코드를 네 컴퓨터에서 실행하지 않고 실행하기

감사 대상 저장소에서 pytest를 실행하면, 그 저장소의 코드가 네 계정으로 실행된다. 설계 전체가 새어 나가는 유일한 지점이 바로 여기다.

thot sandbox status
thot sandbox use docker
thot sandbox show pytest -q     # la commande docker exacte, à relire

기본적으로, 컨테이너 안에서:

네트워크

차단됨 (--network none)

저장소

읽기 전용으로 마운트, tmpfs에 쓰기 가능한 복사본

권한

--cap-drop ALL, no-new-privileges, 사용자 65534

제한

--pids-limit, --memory, --cpus, --rm

네트워크 차단은 가장 가치가 높으면서도 가장 불편한 플래그다. 그래서 법이 아니라 플래그인 것이다 (--network).

Thot의 나머지 규칙을 뒤집는 규칙 하나: 다른 모든 곳에서는 누락된 의존성이 해당 기능만 희생시키고 작업은 계속된다. 여기서는 요청된 샌드박스를 사용할 수 없으면 실행을 거부한다. 조용히 호스트로 대체하는 것은 보호 장치를 거짓말로 바꾸는 일이 될 것이다.

결정 공유하기

판정은 이 코드의 이 리비전에 대한 사실이다. 따라서 코드와 함께 이동한다: <저장소>/.thot/verdicts.json, 해당 코드를 건드리는 풀 리퀘스트에서 다시 읽히며, 네트워크가 있기 전에 새 클론에도 존재한다.

thot verdicts --share <id>   # publier une décision locale dans le dépôt
thot verdicts --share-all    # toutes celles qui concernent ce dépôt
thot verdicts --where        # d'où viennent les décisions, où elles s'écrivent

메모리는 트리 간에 공유되지만 파일은 그렇지 않다: 다른 저장소에 대한 결정을 게시하는 것은 거부되며, --share-all은 여기에 파일이 존재하는 결정만 가져온다.

별도의 구성 없이 기본 체인: 저장소가 먼저, 네 머신이 그다음. 검토를 거친 결정이 네가 스스로에게 남긴 메모보다 우선한다.

쓰기는 여전히 로컬에 남는다. /verdict마다 버전 관리되는 파일을 수정하는 도구는 아무도 요청하지 않은 diff를 만들어 낼 것이다: 로컬에서 결정하고, 의도적으로 게시한다.

공유 서버, 또는 기존 mem0

// ~/.thot/memory.json
{"remote": {"kind": "http", "base_url": "https://audit.equipe.example", "token": "…"}}
{"remote": {"kind": "mem0", "host": "http://localhost:8888", "api_key": "…"}}

mem0 백엔드는 Hermes Agent의 클라이언트와 정확히 동일하게 자체 호스팅 계약을 말한다: Hermes용으로 이미 구축된 서버는 아무것도 바꾸지 않고 Thot에 서비스한다.

연결할 수 없는 원격 저장소는 과거 결정에 대한 메모리만 희생시킬 뿐, 감사 자체는 희생하지 않는다 — 하지만 조용히 그러지 않는다: thot verdicts --where는 어느 것이 침묵하는지, 왜 그런지 말해준다.

다른 곳에서 감사 받기

03:00에 끝나는 감사는 아무도 알리지 않으면 아무 가치가 없으며, 알림을 받아야 할 사람은 터미널 앞에 있지 않다.

thot gateway add ntfy topic=thot-$(openssl rand -hex 8)   # le sujet EST le secret
thot gateway add telegram token=… chat_id=…
thot gateway allow telegram <ton-id>     # obligatoire pour commander
thot gateway test
thot serve                                # écouter les commandes

채널

발신

수신

Telegram

✓ (롱 폴링 — 열 포트 없음)

Discord · Slack

✓ (웹훅)

ntfy

— (정체성 없음: 주제만으로 게시에 충분)

이메일

✓ (SMTP)

알림은 데몬을 요구하지 않는다: gateway-notify 플러그인은 post_audit에서 발동하며, 오직 감시되지 않은 감사에 대해서만 그렇다. 수동으로 시작한 감사는 이미 화면에 표시된다; 매번 알리면 수신자가 채널을 끄는 법을 배우게 되어, 유일하게 중요했던 메시지를 잃게 된다. 새로운 것 없음: 침묵.

도난당한 토큰이 허용하는 것

데몬은 오직 회신을 위해서만 존재하며, 그 설계는 주로 여기에 달려 있다:

  • 명령 집합이 폐쇄적이다status, audit, findings, verdict, help. 셸 없음, 쓰기 없음, 임의 경로 없음;

  • 감사는 오직 thot schedule add로 이미 선언된 저장소만 대상으로 할 수 있다;

  • 수신은 허용 목록을 요구한다. Hermes는 개발용 ALLOW_ALL_USERS를 제공한다; Thot에는 그에 상응하는 것이 없다. 목록이 없으면 채널은 발신 전용이며, thot serve가 그렇게 말한다.

~/.thot/gateway.json0600으로 작성된다 — 봇 토큰과 SMTP 비밀번호를 담고 있기 때문이다. 환경 변수는 Hermes의 이름으로 필드별로 이를 덮어쓴다.

예약된 감사

thot schedule add nuit ~/mon-projet --every daily --threshold high
thot schedule list
thot schedule run nuit            # ce que le planificateur appelle
thot schedule remove nuit

Thot은 launchd 유닛(macOS)을 작성하거나 crontab 줄을 제공하며, 직접 활성화하도록 맡긴다 — 조용히 백그라운드 작업을 설치하는 도구는 더 이상 믿지 않는 도구다.

launchd가 할 수 없을 때. macOS에서 권한은 바이너리 단위로 부여된다: launchd 에이전트는 ~/Desktop, ~/Documents 또는 ~/Downloads에 대한 접근이 거부될 수 있으며, 그러면 유닛은 한 줄도 쓰지 않고 인터프리터 시작에서 멈춘다. 네 세션에서 시작된 프로세스는 고아가 된 후에도 그 세션의 접근 권한을 유지한다 — 이것이 세 번째 해결책이며, 시스템에 아무것도 요구하지 않는다:

thot schedule start       # un planificateur dans ta session
thot schedule status      # tourne-t-il, et quand est-il passé
thot schedule stop
thot schedule autostart   # le relever au premier terminal après un redémarrage

launchctl이 실제로 실행했다고 선언하는 launchd 유닛 앞에서는 물러난다: 같은 작업에 스케줄러가 둘이면 작업도 두 배, 토큰도 두 배다. thot doctor는 둘 중 어느 것이 서비스 중인지 말해준다.

예약된 감사는 새로운 것이 없으면 아무것도 말하지 않는다. 같은 300개의 findings를 반복하는 야간 보고서는 아무도 열지 않는 폴더에 쌓인다. 올라오는 것은 diff다: 지난번 이후 새로 나타난 것, 임계값 이상, 이미 무의미하다고 판정된 것을 뺀 것.

플러그인

다섯 개의 훅, 각각은 배송된 무언가가 사용하기 때문에 존재한다:

Hook

시점

on_finding

보고서 전, 주석 달기용

post_audit

감사 완료 — 알림, 내보내기, 보관

pre_write

에이전트의 쓰기 전 — 경고 반환

post_write

성공적인 쓰기 후

on_verdict

결정이 방금 기록됨

플러그인은 plugin.yaml__init__.py가 있는 폴더로, ~/.thot/plugins/ 또는 <repo>/.thot/plugins/에 있다 — Hermes Agent가 사용하는 형태다. 플러그인이 죽으면 자신의 기능만 희생할 뿐 다른 것은 희생하지 않는다: 그 오류는 기록되고 /plugins로 표시된다.

감사 대상 저장소의 플러그인은 네 동의 없이 실행되지 않는다. 플러그인을 로드하는 것은 그 코드를 여기, 네 계정으로 실행하는 것이다 — 그리고 감사 대상 저장소는 바로 Thot이 경계하는 바로 그 저장소다. 따라서 그 플러그인들은 네가 승인하기 전까지 이름만 표시되고, 가져오지 않는다:

thot plugins list <dépôt>                 # chargés, et refusés avec la raison
thot plugins trust <dépôt>/.thot/plugins/x   # après l'avoir lu
thot plugins untrust <dépôt>/.thot/plugins/x

승인은 이름이 아니라 내용에 적용된다: Thot은 폴더의 지문을 기록하며, 조금이라도 수정되면 그렇게 말하면서 승인을 취소한다.

세 개가 배송된다:

Plugin

기능

write-guard

모델이 쓰는 것을 다시 읽고 위험한 패턴이 나타나면 경고를 올린다. 차단하지 않음 — 세션을 차단하는 오탐은 쓰기보다 더 나쁘다.

regression-alert

fixed로 표시된 결함이 다시 나타나면 CRITICAL로 올라간다: 회귀는 새로운 후보보다 더 가치가 있다.

audit-log

각 감사, 판정, 쓰기의 로컬 JSONL 저널, ~/.thot/journal.jsonl에 저장. 네트워크 없음.

모든 것이 있는지 확인하기

"작동한다"는 주장이며, 세 프로그램으로 이루어진 프로그램에서는 특히 그 주장을 맹신해서는 안 된다 — 특히 그 도구 자신에게서 나온 말이라면 더욱 그렇다.

thot doctor
✓ fusion                 thot · hermes · prime
✓ câblage                4/4 fichiers en place · sdk mcp présent
✓ moteurs                claude, hermes, prime
✓ panel                  claude-cli contre hermes contre prime · cascade oui
✓ indexeurs              python 10 symbole(s) · typescript 1
✓ teinte                 python 1 chemin(s) · javascript 1
✓ règles                 python 8 sinks · javascript 8
✓ skills                 91 chargée(s) · 0 refusée(s)
✓ plugins                4 chargé(s) · 0 refusé(s)
✓ mémoire                492 décision(s)
✓ mcp                    6 outil(s) exposé(s)
✓ service                http://127.0.0.1:8787/mcp répond
✓ amélioration           daily, 8 candidats par arbre · unité launchd,
                         1 passage(s) · agents joignables depuis l'unité

13/13 vérification(s) passées en 1.83 s

계약이 아니라 시점이 찍힌 스냅샷: mémoire는 누적된 판정을 세며 증가할 수밖에 없고, skills는 머신에 설치된 것에 달려 있다. 안정적인 것은 형식이다 — 각 줄은 측정된 숫자를 담고 있으며, 12/12.

마지막 줄은 개발 머신에서의 실제 출력이며, 그대로 유지된다: 그것이 바로 검사가 만들어내기 위한 것이다. 1 passage(s)launchctl 자체에서 나온 것이며 장식이 아니다: launchctl list는 로드된 유닛을 보여주고, LastExitStatus는 0이며, 저널은 존재하지 않는다 — 한 번도 시작되지 않은 작업에 대해 "모두 정상"이라고 말하는 세 가지 신호다. 원인을 이름 짓는 것이 초록 줄을 세는 것보다 낫다.

이 검사 자체도 수정이 필요했다. 경로의 형태로 작업을 비난했다 — "트리가 ~/Desktop 아래에 있으므로 launchd가 읽을 수 없을 것" — 하지만 macOS 권한은 바이너리 단위로 부여된다: 유닛의 인터프리터는 /bin/sh가 거부당한 트리를 읽고 있었다. 형태는 의심이고, 통과는 사실이며, 이제 검사가 묻는 것은 바로 그 사실이다.

그리고 어떤 정적 검사도 할 수 없었던 검사 하나:

thot doctor --agents        # un appel modèle par agent installé
✓ lecture · claude       lit un fichier par chemin absolu
✓ écriture · claude      n'a pas écrit cette fois
✓ outils · claude        10 outil(s), tous en lecture seule
✓ lecture · hermes       lit un fichier par chemin absolu
✓ écriture · hermes      peut écrire — aucun mode lecture seule
                         (`-t file` et `--safe-mode` ne restreignent pas les permissions)
✓ outils · hermes        mcp__patch, mcp__read_file, mcp__search_files, mcp__write_file
✓ lecture · prime        lit un fichier par chemin absolu
✓ écriture · prime       peut écrire — outil unique : un noyau IPython
✓ outils · prime         ipython

쓰기 줄은 불편한 능력을 알리면서도 초록색이다: 원하는 것이 아니라 있는 그대로를 보고한다. 세 에이전트 중 둘은 쓸 수 있고 어떤 플래그도 이를 막지 않는다 — 파일을 만들도록 요청한 다음 디스크를 확인하여 측정했다. 막을 수 없는 것은 놓칠 수 없게 만든다: AuditResult.touched는 한 패스가 수정한 것을 이름 짓고, 야간 루프는 stderr로 그것을 외친다.

임시 폴더에 파일을 만들고 그 내용을 요청한다. 실제 결함 때문에 존재한다: Hermes는 작업 폴더에 상대적인 경로를 열지 않았고, 거부처럼 읽히는 문장으로 응답했다. 패널의 3분의 1은 두 번째 파일에 의존하는 어떤 주장도 검증할 수 없었으며, 파일을 만드는 것 외에는 그것을 보여줄 방법이 없었다.

각 줄은 실제 작업을 실행하고 측정한 것을 보고한다: "skills: 구성됨"이 아니라 "91개 로드됨, 0개 거부됨". 색상 엔진은 두 언어의 샘플에서 경로를 찾고, MCP 서버는 자신의 프로토콜에 응답한다. 실행할 수 없는 검사는 조용히 통과하는 대신 실패한다: "테스트되지 않음"을 의미하는 초록 줄은 빨간 줄보다 더 나쁘다. 네트워크나 모델에는 아무것도 닿지 않는다 — 비행기 안의 thot doctor는 사무실과 같은 답을 준다. 실패 시 0이 아닌 종료 코드, && 또는 CI에 들어갈 수 있도록.

지속적인 개선

20개의 후보를 논하고 멈추는 감사는 나머지를 영원히 판정 없이 남긴다. 예산 없는 패스는 네가 다시 앉을 때까지 계속 돈다. thot improve는 그 중간이다: 각각 디스크에 쓰고, 각각 이전 것이 멈춘 곳에서 다시 시작하는 유한한 라운드.

thot improve                      # un tour sur les trois arbres
thot improve --rounds 5           # jusqu'à ce qu'un tour ne juge plus rien
thot improve --every daily        # la boucle devient permanente

작성된 유닛은 자신의 PATH를 담고 있다. launchd는 작업에 /usr/bin:/bin:/usr/sbin:/sbin을 주고, cron은 그보다도 적게 주며, claude, hermes, node는 그 어떤 폴더에도 없다 — 그들은 ~/.local/bin 아래에 있다. 이것이 없으면 야간 패스는 어떤 엔진도 빌드하지 못하고, 아무것도 판정하지 않았으며, 종료 코드 0으로 나갔다: launchd는 매일 밤 무한히 성공을 기록했다. 이런 종류의 작업이 조용히 실패하는 것은 작동하는 작업과 구별할 수 없으므로, 에이전트가 없는 심층 패스는 이제 오류로 종료되고 그렇게 말한다.

야간 버전은 결정한 것을 보고하며, 나타난 것을 보고하지 않는다. 그 구분이 중요하다: 예약된 감사(audit)의 보고 메커니즘은 "임계값 위에 새로운 것이 무엇인가"에 답하며, 이는 스캔에는 올바른 질문이고 판단에는 잘못된 질문이다. 이미 보고서에 있는 MEDIUM을 확인하는 것은 정확히 루프가 하는 일이며 — 그것은 누구에게도 보고되지 않았을 것이다. 감사가 수정했을 파일들은 같은 로그에 표시된다.

판단할 것이 더 이상 없는 트리는 자신의 몫을 다음에게 넘긴다. 실제 코퍼스로 측정: thot은 빈 백로그를, prime은 단 하나의 후보를 가지므로, 트리당 20의 예산은 그것을 사용할 수 없는 트리들에 40을 쓰는 동안 Hermes는 150을 기다리고 있었다. 20의 라운드는 20, 20, 60의 라운드가 된다.

세 번째 속성이 그것을 빠르게 수렴하게 한다: 실패가 집계된다. 에이전트가 시간을 초과하거나 모델이 확정을 거부하는 후보는 그 심각도를 유지한다 — 따라서 다음 라운드에서 첫 번째로 다시 처리되고, 그 다음 라운드에서도 그렇다. 1,660줄 파일의 finding으로 측정: 세 번의 시도 중 네 번의 시도, 그중 세 번이 같은 벽에 부딪혔다. 두 번 실패한 후에는 큐의 끝으로 이동한다: 여전히 자격은 있지만, 결코 우선순위가 아니다. 성공은 카운트를 지운다 — 바쁜 오후나 만료된 구독이었던 벽은 finding을 영원히 따라다녀서는 안 된다.

두 가지 속성이 그것을 빙빙 도는 대신 수렴하게 한다: 반박은 기억되므로 다음 선택은 그것을 건너뛴다; 확인은 의도적으로 기억되지 않는다 — 진짜 결함은 누군가 고칠 때까지 계속 나타나야 하므로 — 루프는 자체적인 이미 판단된 식별자 집합을 가진다. 그것 없이는, 첫 라운드 이후의 모든 라운드는 예산 전체를 첫 라운드가 방금 확인한 것을 다시 논증하는 데 쓸 것이다.

그것은 합계 앞에, 해야 할 일로 끝난다:

À REGARDER — 2 finding(s) :
  [hermes] plugins/platforms/a2a/tools.py:83 — confirmé · prime
      L'URL vient d'un argument d'outil, donc du modèle…
  [prime] packages/coding-agent/…/state-snapshot.ts:163 — réfutation contestée · hermes
      Le chemin dit fixe est construit depuis un identifiant non validé…

4 tour(s) · 83 jugement(s) (80 réfuté · 1 confirmé) · 157 candidat(s) sans décision

반박은 관리(housekeeping)이고, 확인은 뉴스다. 이의가 제기된 반박도 마찬가지다: 그것은 프로그램이 무언가를 묻기 전에 스스로 만회했다고 말하는 것이다. 그것들을 이름 없이 세는 것은 독자를 로그를 grep하도록 보낸다 — 이것은 하루 동안 매번 정확히 일어난 일이다.

그것은 결코 코드를 수정하지 않는다. 여기서 "개선"은 프로그램의 자기 자신에 대한 판단이 더 선명해지고 더 저렴해진다는 뜻이다: 결정되지 않은 후보가 줄어들고, 디스크에 더 많은 결정이 생기며, 각각은 그것을 내린 에이전트에게 귀속될 수 있다.

온도계, 그리고 그것을 사용하는 루프

위의 모든 것은 Thot을 Thot으로 측정한다. improve는 모델에게 finding이 실제인지 묻는다. evolveprovenance를 감시했는데, 이는 엔진이 자체 출력에 대해 계산하는 보고서다. 둘 다 순환적이며, 그 원은 학문적인 것이 아니다: 심층 패스는 9개의 확인을 위해 638개의 판단을 지불했고, 그동안 **−100 %**로 표시된 규칙이 잠들어 있었다 — xml_unsafe_parsedefusedxml을 지적했는데, 이는 정확히 자체 메시지가 권장하는 해결책이었다. 프로그램 안의 어떤 것도 그것을 볼 수 없었다.

thot bench는 그 원을 깨뜨린다. 그것은 Thot을 다른 누군가가 취약 또는 건전으로 레이블을 붙인 코드에 대해 측정하며, 동등한 비율로, 약점 클래스가 명명된다.

thot bench ~/.thot/bench              # les suites présentes, catégorie par catégorie
thot bench ~/.thot/bench --json       # ce que la boucle d'évolution lit
thot bench ~/.thot/bench --floor info # ce que le plancher de sévérité cache

코퍼스는 내장되어 있지 않다 — 18,000개의 타사 파일은 이 저장소에 있을 이유가 없다 — 따라서 경로는 항상 주어지고, 각 스위트는 매니페스트의 지문(fingerprint)에 대해 검증된다. 측정 중에 레이블이 움직인 코퍼스는 코퍼스가 없는 것보다 나쁘다: 이후의 모든 숫자는 거짓이고 그것을 말해주는 것도 없다.

점수는 Youden의 J, TPR − FPR이다. 0은 동전 던지기이고, +100은 완벽하며, 음수는 규칙이 반전되었음을 의미한다. 정밀도와 재현율은 그것을 말하지 않았을 것이다: 진짜 양성이 하나도 없는 규칙은 정밀도가 정의되지 않고, 비어 보이며, 데이터 없음으로 읽힌다 — 이것이 정확히 반전된 규칙이 살아남는 방식이다. J에는 그 구멍이 없다.

두 가지 속임수 방법이 있고, 둘 다 진다. 더 적게 찾는 것 — 이것이 provenance를 올렸던 것 — 은 TPR을 떨어뜨린다. 모든 것을 신고하면 TPR 100 %, FPR 100 %, J 0이 된다: 코퍼스는 정확히 그 이유로 50/50으로 균형 잡혀 있다.

측정된 상태, 기본 플로어, 세 가지 프레임워크(django, fastapi, flask):

                        avant      après
TPR                      9.9 %     34.4 %
FPR                      0.5 %      0.0 %
J de Youden             +9.4 %    +34.4 %
catégories actives          10         24
catégories négatives         0          0

"이전"은 온도계가 처음 존재했을 때의 프로그램 상태다. "이후"는 같은 코퍼스, 같은 플로어, 같은 명령이다. 홀드아웃이 확인한다: 제외된 flask는 +33,3 %, 제외된 django는 +35,9 % — 훈련과 2포인트 미만의 차이이므로, 규칙들은 그것을 작성하는 데 사용되지 않은 코드에서 작동한다.

카테고리별로, 엔진이 오늘 할 수 있는 것:

xxe · tlsverify · weakhash · weakrand · weakcipher · weakkeylength
hardcodedcreds · default_credentials · cleartexttransmit · errormessage
debug_code_production · cookie_no_httponly · cookie_no_samesite
securecookie · directory_listing_exposure                      +100,0 %
deserial  +98,0 %   cmdi  +93,3 %   codeinj  +86,0 %
eval_injection  +84,0 %   sqli  +82,7 %   cloud_ssrf_metadata  +78,7 %
ssrf  +64,7 %   pathtraver  +56,0 %   xss / basic_xss  +26,7 %

전체 18,300건에 대해 오탐지(거짓 양성) 0건, 그리고 음수 카테고리 없음. 출발점은 ssrf가 −8,0 %, xxe가 −100 %였다.

이 25포인트를 만든 것, 측정된 순서대로:

변경

J

출발

+9,4 %

웹 라우트가 진입점으로 인식됨

+9,6 %

색조(tint)가 컨테이너의 값을 따름

+10,0 %

상수 분기를 가진 삼항 연산자는 아무것도 전달하지 않음

+12,4 %

부정 클래스를 가진 fullmatch는 블랙리스트임

포함

SSRF 가드: 호스트 화이트리스트, 해석된 IP 범위

+14,3 %

socket.create_connection이 네트워크 싱크로 인식됨

+14,9 %

경로 격리 및 명명된 화이트리스트

+15,5 %

mark_safe를 싱크로, bleach를 HTML 중화로

+16,3 %

한 줄 패턴 규칙 12개

+27,8 %

패턴이 더 이상 접근성 할인을 지불하지 않음

+34,4 %

이 변경 사항 중 세 가지는 측정 후 거부되었고, 그것을 거부한 것은 온도계였다: 데이터베이스 읽기를 신뢰할 수 없는 소스로 취급하는 것 (436개의 취약한 사례, 343개의 건전한 사례 — 신호만큼이나 많은 노이즈), clickjacking(이 저장소에서 23개의 오탐지), 그리고 다섯 가지 LDAP/XPath/NoSQL/SSTI/EL 주입 규칙 — 측정된 J가 정확히 0,000: 그것들은 건전한 절반만큼이나 다른 절반에도 동등하게 당긴다.

정밀도는 사각지대로 살 수 없다

위에서 인식된 각 가드는 먼저 점수를 올리고 악용 가능한 구멍을 여는 버전으로 제안되었다. 적대적 프로브가 그것들을 모두 찾아냈다:

resolved = socket.gethostbyname(parsed.hostname or url)
if ipaddress.ip_address(resolved).is_private:
    return "blocked", 403
os.system("curl -s " + url)      # ← silencieux, et exploitable

그 가드는 올바른 SSRF 방어이며 문자열에 여전히 있는 셸 메타문자에 대해 아무것도 말하지 않는다. 모든 곳에서 색조를 정화하는 것은 오탐지 174건을 줄이고 명령 주입의 사각지대를 얻는 것이었다.

따라서 엔진이 지니는 구분: 을 제약하는 가드(리터럴 화이트리스트, 열거하는 fullmatch)는 모든 것에 대해 그것을 정화한다; 대상을 증명하는 가드(호스트가 허용됨, 해석된 주소가 공개됨, 경로가 격리됨)는 관련된 싱크 계열에 대해서만 유효하다. bleach.clean도 마찬가지인데, 그것은 HTML을 중화하고 x; rm -rf /를 그대로 둔다.

네 개의 적대적 프로브가 테스트 스위트에 있으며 모두 올바르게 신호한다. 이 구멍 중 하나를 다시 여는 미래의 "개선"은 명명된 테스트를 깨뜨린다.

남은 침묵에는 두 가지 원인이 있고, thot bench는 그것들이 두 가지 다른 작업이기 때문에 분리한다:

règle muette — elle existe et ne matche jamais : elle a un motif à élargir
aucune règle pour la classe : il y en a une à écrire

그 구분이 목표의 순서를 결정한다 — 그것 없이는 침묵하는 카테고리들이 J = 0에서 완벽하게 동률이 되고 정렬은 알파벳 순서로 떨어진다.

융합, 그것이 무언가를 바꾸는 곳

Cascade.turn하나의 에이전트를 선택하고 그것을 호출한다; 그것은 첫 번째가 오류를 반환할 때만 다른 것을 찾는다. 그런 라운드는 구조상 둘 중 더 나은 것으로 제한된다: 더 적게 잃을 수는 있어도, 더 많이 얻을 수는 없다. agent_apply도 마찬가지였다 — 단수형의 엔진 하나.

thot evolve --fused는 둘 다 같은 문제의 다른 절반에서 작업하게 한다:

thot bench ~/.thot/bench                      # où ça fait mal
thot evolve --from-bench --fused \
      --corpus ~/.thot/bench --hold-out flask # et on répare, en boucle
  • Hermes는 측정을 읽고 사양을 작성한다. 그것은 어떤 파일도 건드리지 않는다. 그것의 출력은 원인에 대한 주장이다: 어떤 규칙, 어떤 줄, 왜 그 사례들인지.

  • Prime은 사양을 읽고 코드를 작성한다. 코드가 사양을 모순하면 거부할 수 있다고 명시적으로 말해진다 — 거절할 수 없는 실행자는 중계기이고, 중계기는 아무것도 추가하지 않는다.

  • 둘 중 누구도 결정하지 않는다. 테스트 스위트는 플로어이고, 레이블이 붙은 코퍼스가 평결이다. 확신을 가지고 적용된 잘못된 사양은 점수를 낮추고 바이트 단위로 취소된다.

순서도 임의적이지 않다. 설계-후-구축은 접합부에서 검증된다: Prime은 Hermes의 추론을 그것에 참여하기 전에 본다. 구축-후-재검토는 그것을 허용하지 않는다 — 두 번째가 볼 때, 첫 번째는 이미 결정했다.

목표는 측정에서 오는 것이지, 입력된 문장에서 오는 것이 아니다. 지금까지 루프는 인간이 이미 의심한 것만 추구할 수 있었다; 점수에서 구축된 목표는 프로그램이 자신이 선택하지 않은 숫자로 자신이 어디에 약한지 말하는 것이다 — 그리고 같은 숫자가 이후에 답이 도움이 되었는지 말한다. 각 목표는 실패한 파일들을 지닌다: "xss가 0 %입니다"라고 말하는 에이전트는 추측만 할 수 있고, 실패한 파일 세 개를 주는 에이전트는 해결할 문제가 있다.

과적합, 그리고 --hold-out이 정말 할 수 있는 것

코퍼스에 대해 점수가 매겨진 루프는 진짜 속임수 방법이 하나뿐이다: 코퍼스를 학습하는 것. BenchmarkTest01126이 어떻게 생겼는지에 맞춰진 규칙은 점수를 올리고 아무에게도 도움이 되지 않으며, 외부에서 보면 진짜 진보와 구별할 수 없다.

--hold-out flask는 하나의 스위트를 주요 숫자에서 빼내고 그것을 두 번째 안전장치로 유지한다: 그것이 최적화된 스위트를 움직이고 결코 보지 못한 스위트를 움직이지 않는 변경은 그것이 무엇인지 말한 것이다. 두 숫자 모두 ne_baisse_pas로 유지된다.

그것의 한계, 측정됨: 세 프레임워크는 서로 반 포인트 차이로 점수를 매긴다. 그것은 파일 수준의 과적합을 잡지만, 벤치마크 형태에 대한 과적합은 잡지 않는다 — 생성된 코퍼스는 여전히 생성된 코퍼스이고, 데모 코드에서만 도움이 되는 규칙은 세 가지를 모두 통과할 것이다. 홀드아웃은 속임수를 보이게 만든다; 그것은 코퍼스를 대표적으로 만들지 않는다.

루프가 라운드에서 라운드로 유지하는 것은 ~/.thot/evolve-log.jsonl에 기록된다. 그것 없이는, 측정이 한 라운드에서 거의 움직이지 않으므로, 다음 라운드는 같은 최악의 카테고리를 다시 읽고, 같은 파일을 제시하고, 합리적으로 — 이미 구축되고, 측정되고, 취소된 같은 사양을 받는다: --rounds 5는 다섯 번 시도된 시도, 다섯 배 더 비싸고, 바쁜 것처럼 보이는 것이 될 것이다.

그것은 오라클이 아니다. 수정이 초록색이고, J를 올리고, 여전히 나쁠 수 있다 — 그것은 과적합이고 자동 프로그램 수리에 관한 문헌은 그것만 이야기한다. 코퍼스는 그것에 대해 진보했다는 증거다. 루프는 인간이 동의하지 않을 수 있도록 그것이 변경한 것을 보고한다.

Skills — Thot이 아는 방법

skill은 한 번 작성된 방법이다: YAML frontmatter가 있는 SKILL.md. 이것은 Hermes Agent와 Prime Agent의 형식이므로, 둘 중 하나를 위해 작성된 skill은 수정 없이 여기에서 로드되고, 그 반대도 마찬가지다.

Thot은 Hermes Agent의 전체 라이브러리(MIT — NOTICE.md 참조)를 탑재한다: 90개의 로드된 방법, 117개가 더 사용 가능하다.

thot skills list              # les 91 chargées
thot skills search pentest    # y compris la bibliothèque optionnelle
thot skills install ast-grep  # activer une optionnelle
thot skills show plan         # ce que lirait le modèle

로드된 카테고리: audit, security, software-development, github, devops, research, mlops, productivity, creative, apple, email, media, note-taking, smart-home, social-media, autonomous-ai-agents.

모델은 skills 도구로 그것들을 발견한다 — 키워드를 주지 않으면 이름의 인덱스로 응답하는데, 200개의 설명은 카탈로그가 아니기 때문이다 — 그리고 skill로 해당하는 것을 읽는다. 세션에서는 /skills가 같은 것을 보여준다.

여기에 없는 도구(delegate_task, browser_navigate…)를 인용하는 가져온 메서드는 그대로 제공되며, 어떤 것이 누락되었고 대신 무엇을 사용해야 하는지 알려주는 메모가 함께 붙는다. 도구 호출이 이동되지 않아도 그 절차는 이동된다.

추가하기

~/.thot/skills/<nom>/SKILL.md            # partout où tu travailles
<repo>/.thot/skills/<nom>/SKILL.md       # versionné avec ce dépôt
---
name: ma-méthode
description: Ce qu'elle fait et quand s'en servir.
---

# Ma méthode

Les étapes, dans l'ordre.

두 가지 배치가 모두 허용된다: 평면 폴더(Prime Agent) 또는 카테고리별 그룹화(Hermes Agent). 이미 존재하는 이름은 내장 버전을 대체한다 — 포크하지 않고 제공된 메서드를 적응시키는 방법이다.

감사 대상 저장소가 제공한 메서드가 먼저 분석된다

SKILL.md지시사항으로 모델에게 전달되는 텍스트다. Thot이 읽는 저장소는 정의상 아무도 책임지지 않는 것들이다. 적대적인 저장소가 .thot/skills/x/SKILL.md를 배치하면 브리핑의 일부를 작성하게 된다.

Hermes Agent의 가드가 여기로 이식되었으며 저장소에서 오는 모든 것에 적용된다: 인젝션, 유출, 지속성, 난독화.

   ▲ 1 skill(s) fourni(s) par ce dépôt ont été refusés — ils seraient passés
     au modèle comme instructions.
     pwn   curl vers l'extérieur ; accès à ~/.thot ; « ignore previous
           instructions »

thot skills scan <폴더>는 요청 시 같은 질문을 던진다. Thot이 자체적으로 제공하는 것은 분석되지 않는다: 프로그램이 설치되었기 때문에 디스크에 있는 것이지, 저장소가 요청했기 때문이 아니다.

사용자 정의 명령어

마크다운 파일은 명령어다. 문법은 Prime Agent, Claude Code, Codex의 것과 같다 — 새로 배울 것이 없다.

---
description: Relire un fichier sans rien modifier.
argument-hint: <chemin>
---

Relis $1 et dis-moi ce qui cloche. Ne modifie rien.

.thot/commands/revue.md에서 이것은 /revue src/app.py를 생성한다. 치환: $1, $2…, $@, $ARGUMENTS, ${@:2}, ${@:2:3}. 인자는 절대 재해석되지 않는다. 저장소의 명령어는 그 스킬과 같은 가드를 통과한다.

세 가지가 제공된다: /triage (항목 이름 지정 또는 무혐의로 분류), /harden (먼저 실패하는 테스트, 그다음 수정), /regress (git 참조에 대한 차이 감사).

MCP 서버

Hermes Agent의 카탈로그, 검증된 서버 20개:

thot mcp list            # le catalogue, et ce qui est déjà connecté
thot mcp show sentry
thot mcp add linear

설치는 공식 CLI에 위임되며, 이미 OAuth와 토큰 갱신을 보유하고 있다 — Thot이 두 번째 금고를 보유하여 유출시킬 이유가 없다. 등록됨승인됨이 아니라는 것과, 어떤 명령이 작업을 완료하는지 명시적으로 말한다.

모델의 도구

기본적인 것들 — 읽기, 쓰기, 편집, 명령 실행. 모든 쓰기와 실행은 확인을 요구하며, 이것은 구성할 수 없다.

그리고 Thot에만 속한 네 가지가 있는데, 무료인 이유는 모델이 아니라 맵을 조회하기 때문이다:

도구

응답

code_map

프로젝트의 파일들

find_symbol

함수의 파일, 줄, 매개변수

callers

누가 무엇을 호출하는지, 그리고 진입점까지의 거리

audit

소스 → 싱크 틴트 경로

계정 모드에서는 이 네 가지가 thot.mcp_server를 통해 공식 CLI에 제공된다 — 쓰기나 실행이 불가능한 읽기 전용 MCP 서버다.

모델이 process_payment를 누가 호출하는지 찾을 때, 그래프를 조회하여 완전한 답을 얻는다 — 무작위로 세 파일을 grep하는 대신.

감사 전용 모드

분석 코어는 모델 없이, 네트워크 없이, 비용 없이도 사용할 수 있다:

thot init /chemin/du/repo --owner "Ton Nom"   # autorisation, une fois
thot audit /chemin/du/repo --paths            # chemins de teinte complets
thot audit . --all                            # y compris le bruit faible
thot audit . --json --out rapport.json
thot audit . --out rapport.sarif              # SARIF 2.1, format déduit du nom
thot audit . --fail-on high                   # code 1 en CI

SARIF — 이미 존재하는 체인에 들어가기

어떤 파이프라인도 읽을 수 없는 보고서는 단일 터미널에서만 존재한다. GitHub code scanning, GitLab, Azure DevOps, 그리고 편집기들은 모두 SARIF 2.1을 읽으며, Thot의 두 가지 속성은 다른 곳보다 더 가치가 있다.

finding의 정체성은 규칙 + 파일 + 심볼 + 본문의 지문이며, 절대 줄 번호가 아니다 — 이것이 정확히 partialFingerprints가 요구하는 것이다. 줄 번호로 채워진 대시보드는 누군가 파일 상단에 import를 추가하는 순간 모든 티켓을 다시 연다; 이것으로 채워진 대시보드는 그렇지 않다.

그리고 틴트 경로는 위치의 연속이며, codeFlows가 그것을 렌더링한다: 독자는 도구의 말을 믿는 대신 소스에서 싱크로 클릭한다. 경로가 없는 finding은 어떤 codeFlows 키도 가지지 않는다 — 빈 흐름은 단계가 없는 틴트 경로로 렌더링되며, 이는 패턴 매칭이 아니라 깨진 분석으로 읽힌다.

패널에 의해 반박된 finding은 삭제되지 않는다: 정당화된 suppressions와 함께 나간다. 그것을 결코 보지 못하는 대시보드는 "아무도 보지 않았다"와 "누군가 보았고 판단했다"를 구분할 수 없으며, 후자가 패널의 존재 이유 전체다.

- run: thot audit . --out thot.sarif
- uses: github/codeql-action/upload-sarif@v3
  with: { sarif_file: thot.sarif }

지원 분석 — --deep

결정론적 분석은 "이 데이터가 흐를 수 있는가?"에 답한다. 그것은 철저하고, 무료이며, 감사관에게 판단을 맡기는 질문이 아니다. --deep은 그것을 받을 자격이 있는 후보들에 대해서만 비싼 질문을 던진다:

thot audit . --deep                  # 20 pires candidats, 4 en parallèle
thot audit . --deep --budget 50      # plus large
thot audit . --deep --parallel 8     # plus vite

의도적으로 적대적인 두 번의 패스:

  1. 탐침은 위험 지점에 도달하는 구체적인 입력을 명명해야 한다. 취약점 클래스에 대한 일반론이 아니라 — URL, 값, 효과.

  2. 반박은 이 시나리오를 받아 그것을 파괴하는 것만을 임무로 한다: 사전 검증, 상수만 전달하는 호출자, 가정된 입력을 금지하는 타입. 의심스러우면 반박한다.

finding은 동일한 코드에 대한 두 번째 적대적 읽기가 그것을 죽이는 데 실패할 때만 살아남는다. 그러면 confirmed는 의미를 가진다.

세션에서도 같은 것: /audit deep.

엔진은 자동으로 선택된다 — 공식 CLI를 통한 당신의 Claude 계정(연결된 경우, 분석은 당신의 구독으로 병렬 실행), 그렇지 않으면 API 키.

감사가 읽지 말아야 할 것

# .thotignore, à la racine du dépôt
vendor/
*.generated.py
tests/fixtures/

내장 제외 항목은 모든 저장소가 가진 것 — node_modules, build, .venv — 을 포함한다. .thotignore는 이 저장소만 아는 것을 포함한다: 임베디드 문서, 생성된 클라이언트, 의도적으로 깨진 픽스처 폴더. 그것들을 감사하는 것은 findings를 생성하는 것이 아니라, findings가 있을 정확한 위치에 노이즈를 생성하는 것이다.

당신 자신의 규칙

내장 카탈로그는 표준 라이브러리를 알고 있다. 그것은 당신의 팀이 subprocess 주위에 작성한 래퍼, 당신의 서비스가 소비하는 큐, 당신의 환경에서 값을 안전하게 만드는 검증기를 알지 못한다. 그것을 말할 곳이 없으면, 실제 시스템에 대한 모든 감사는 같은 세 곳에서 틀린다.

# <repo>/.thot/rules/team.yaml   — versionné avec le code
# ~/.thot/rules/*.yaml           — ce que tu sais, partout où tu travailles
sinks:
  - id: sink.team.run_shell
    patterns: [run_shell, shellutil.run_shell]
    impact: critical
    description: Wrapper shell interne (shell=True)
    match_mode: bare          # qualified | method | bare | prefix

sources:
  - id: source.queue
    patterns: [msg.payload]
    description: File de messages
    match_mode: prefix        # couvre msg.payload.decode(...)

sanitizers: [validate_host, team.escape]

같은 파일js: 키 아래 JavaScript 규칙을 담는다 — 팀 래퍼는 일반적으로 두 언어 모두에 존재하며, 선언을 분리하는 것은 절반이 쓸모없어지는 방법이다.

js:
  sinks:
    - id: sink.js.team
      names: [runShell, sh]     # comparés au dernier segment, ou qualifiés
      impact: critical
      description: Notre wrapper shell
      needs: [child_process]    # ne se déclenche que si le fichier l'importe
  sources:
    - id: source.js.queue
      patterns: [job.payload]
      description: File de messages
  sanitizers: [escapeArg]

모델이 요청하는 것은 신뢰할 수 없는 입력이다

소스는 표현식이다 — sys.argv, os.environ. 이것은 실행하는 프로그램을 포함하고 호출하는 프로그램을 놓친다: 에이전트의 도구는 명명된 매개변수로 신뢰할 수 없는 입력을 받으며, 레지스트리가 모델이 요청한 것으로부터 채우고, 본문 어디에도 표현식이 나타나지 않는다.

이것을 모델링하지 않는 측정된 비용: 오후에 SSRF 4건, 모두 도구 인자로 도달했으며, 틴트로는 하나도 발견되지 않았다 — 패턴 규칙으로 발견되었는데, 그것은 형태를 인식할 뿐 아무것도 증명하지 않는다.

entry_sources:
  - id: entry.tool
    patterns: [tools.image_gen]     # les fonctions qu'un registre appelle
    parameters: [args]              # facultatif : lesquels de leurs paramètres
    description: Arguments remplis par le modèle
    match_mode: prefix

기본적으로 비어 있으며, 의도적으로 그렇다: 레지스트리가 어떤 함수를 호출하는지는 저장소에 대한 사실이며, 추측하는 것은 모든 프로그램의 모든 매개변수 아래에 소스를 놓는 것이다. Hermes에서 측정된 두 극단: pluginstools 패키지를 명명하는 규칙은 19개의 입증된 경로를 드러내며 그중 여러 개가 과대 근사되었다(헬퍼가 구성에서 받는 base_url은 신뢰할 수 없는 것이 아니다); args 매개변수를 명명하는 규칙은 0개를 드러내는데, Hermes의 핸들러가 사전이 아닌 명명된 매개변수를 받기 때문이다. 올바른 규칙은 실제 진입점을 명명한다 — 그리고 그것을 아는 것은 그 작성자들의 몫이다.

내장 id를 다시 사용하는 규칙은 그것을 대체한다 — 팀이 의도적으로 수용한 싱크를 Thot을 패치하지 않고 저하시키는 방법이다. 잘못된 형식의 파일은 파일과 잘못된 키를 명명하며 감사를 중지시킨다 — finding 부재를 암시하게 두지 않는다.

억제

억제는 어떤 도구도 다시 읽지 않는 유일한 보안 주장이다 — 이 도구조차도, 구조상 그렇다. # nosec, # noqa: S310, // eslint-disable … security/…: 코드에 대한 주장이며, 한 번 작성되어 그것이 설명하던 호출자들보다 오래 산다.

여기서 같은 감사에서 두 번, 그것은 거짓이었다:

억제

그것이 주장한 것

실제로 사실이었던 것

# nosec B310 — scheme checked above

스킴이 통제된다

그것은 file://만 막았고 다른 것은 막지 않았다 — 메타데이터 서비스로의 SSRF

# noqa: S310 (configured peers)

URL이 구성에서 온다

호출자 중 하나가 도구 인자에서 읽으므로 모델에서 온다

따라서 Thot은 그것들을 클래스로, LOW로, 옆에 패턴을 적어 보고한다. finding은 "이 줄이 위험하다"고 말하지 않는다: "그것이 면제된 이유를 아무도 다시 읽지 않았다"고 말한다. --deep 패스에서는, 패턴이 여전히 유지되는지 확인하는 에이전트가 간다.

Python의 경우, 패턴이 아니라 실제 주석 토큰이 읽힌다 — 정규 표현식은 주석의 # nosec와 docstring에 인용된 같은 텍스트를 구분하지 못하며, 이 모듈의 docstring은 두 개를 인용한다.

이 감사가 신고하는 줄에 놓인 억제는 같은 객체가 아니다: 그것은 살아있는 finding을 모순하는 주장이며, 같은 줄을 읽고 다르게 결론을 내린 누군가가 작성한 것이다. 그것은 한 단계 올라가서 그렇게 말한다. Hermes에서 측정: 45개 중 7개 — 그리고 그날 읽은 억제 중 세 개는 거짓이었다.

측정: Thot에서 0, Prime에서 0, Hermes에서 45.

보정

정밀도는 탐지만큼 중요하다. 의도적으로 보고되지 않는 것들:

  • shell=True 없는 subprocess.run(cmd) — 어떤 셸도 명령을 읽지 않는다.

  • cursor.execute("… ?", params) — 리터럴 쿼리, 바인딩된 매개변수.

  • int(), shlex.quote(), os.path.basename(), html.escape()로 전달된 값 — 이러한 호출은 오염 체인을 끊는다.

  • 어떤 진입점도 도달하지 않는 결함은 자동으로 저하된다 — 그러나 진입점이 발견된 경우에만. 전혀 없으면 범위는 알 수 없음이지 없음이 아니며, 그 무지 위에 아무것도 묻히지 않는다.

  • payload.get(...)requests.get(...)이 아니다.

크기 정도, Hermes Agent(4,457개의 Python 파일)에서 측정: 98초, findings 365개 중 high 25개 — 메모리를 적용한 후 기본 임계값보다 3개 위.

인자가 채우는 매개변수

하나의 매개변수가 싱크에 도달하는 헬퍼는 전달되는 모든 것을 위험하게 만들지 않는다. 그런데도 엔진은 호출자를 callee의 모든 싱크 집합과 짝지었고, 인자가 어디에 도착하는지 보지 않았다:

def helper(safe, cmd):
    os.system(cmd)          # seul `cmd` atteint le shell

helper(sys.argv[1], "ls")   # la donnée va dans `safe` — et c'était rapporté

위치는 이제 호출 지점에서 읽히고, 명명된 인자의 경우 이름이 읽힌다. JavaScript 엔진이 이미 자체적으로 하던 것이다.

실행이 가능해진 후 측정: Thot과 hermes/tools를 분석하면서 요청된 83,238개의 해석 중 90%가 단일 매개변수를 지목하고 9.8%는 아무것도 지목하지 않는다; 0.1%는 열린 채로 남아(f(*rest), f(**options)) 넓은 답을 유지한다. callee는 평균 2.68개의 매개변수를 제공했으므로, 검색 공간의 거의 3분의 2가 사라진다 — hermes/tools 분석이 11.4초에서 9.5초로 줄어든다.

그러나 findings 수에 관해서는: 변화 없음. Thot, prime, hermes/tools는 전후에 정확히 동일한 151개의 후보를 반환하며, 그중 발명된 것은 0개 — 안전성 속성은 실제 현장에서 유지된다. 네 개의 틴트 경로가 짧아질 뿐이다. 위에서 수정된 형태는 실제이며 테스트가 이를 증명하지만, 측정된 세 트리 중 어느 것에서도 발생하지 않는다: 이 수정은 속도와 더 정확한 경로를 사는 것이지, 노이즈를 줄이는 것이 아니다.

두 가지 형태는 아무것도 말하지 않는다: helper(*args)는 알 수 없는 수의 값을 펼치고, helper(**options)는 어떤 값도 이름을 지정하지 않는다. 그 경우 엔진은 이전에 주던 넓은 답변을 유지한다 — 유지되는 집합은 항상 이전 집합에 포함되므로, 정제는 finding을 제거할 수만 있고 결코 발명할 수는 없다.

수신자는 호출 구문이 아니라 callee의 시그니처에 근거하여 건너뛴다: Runner().go(x)는 점 없는 go로 귀결되고, Cls.m(obj, x)obj.m(x)는 둘 다 속성이다. 메서드는 거의 항상 바인딩된 상태로 호출되므로, 결정하는 것은 파라미터 맨 앞의 self이다 — 비바인딩 호출만이 한 단계 짧게 읽히는 유일한 형태이다.

그래프가 따라갈 수 없는 것

분석이 해결하지 못하는 경로로 도달한 결함 — 디스패치 테이블에 정리된 handler, 데코레이팅된 뷰, 타입을 알 수 없는 변수에 대한 호출 — 은 접근 불가능한 결함이 아니다. Thot은 이 둘을 구분한다:

HANDLERS = {"run": run_command}     # aucun appel : le graphe ne voit rien
@app.route("/ping")                 # enregistré à l'import par le décorateur
sandbox.run("pytest")               # plusieurs `run` répondent à ce nom

세 경우 모두 범위는 알 수 없음이지, 없음이 아니며, finding은 묻히는 대신 가벼운 페널티를 유지한다. Hermes에서: 동일한 365개 findings, 그러나 60개가 한 단계 올라간다. 아무도 호출하지 않고 아무도 언급하지 않는 함수는 여전히 올바르게 감점된다 — 그렇지 않으면 필터는 더 이상 필터가 아니게 된다.

한계

파일 간 틴트는 Python 전용이다: JavaScript와 TypeScript는 인덱싱되고, 함수 본문 내에서 그리고 같은 파일의 helper까지 추적되며, 해결된 호출 그래프가 없어 그 지점에서 멈춘다 — 위의 표가 줄 단위로 말해준다. 패턴 기반 규칙은 모든 곳에 적용된다.

--deep 없이, 각 finding은 PLAUSIBLE이다: 정적으로 감지되었지만 아직 실행으로 증명되지 않았다. --deep을 사용하면, confirmed finding은 반대 반박을 견뎌냈다 — 이것은 아직 실행 증명이 아니며, repro와 함께 올 것이다. 그리고 finding이 없다는 것은 결함이 없다는 증거가 아니다: 동적 디스패치, 리플렉션, 메타프로그래밍은 분석을 벗어난다.

세션 내 전체 텍스트 검색은 fts5가 rowid를 역추적할 수 없는 SQLite에서 상수 비용이 아니게 된다: 엔진은 20개에서 끊는 대신 전체 일치 집합을 정렬한다. CPython 3.12에 내장된 것으로 측정했을 때 — 6,588개 명령어 대비 코퍼스가 5배로 늘어나면 27,443개, 반면 가능한 엔진은 2,215개에서 2,698개에 머문다. 테스트가 이를 검증하고, 허용하지 않을 때 버전을 명명한다.

개발

cd thot
uv run pytest -q

편집 가능 모드로 설치됨: 소스 코드가 즉시 권위를 가진다. 반면 pyproject.toml이 변경되면(새 의존성), uv tool install --editable --from . thot --force를 다시 실행해야 한다.

결정적 커널(codemap, taint, scope, scoring, store, report)은 어떤 에이전트에도 의존하지 않으며 네트워크에 닿지 않는다 — 테스트가 이를 검증하고, 변경되면 스위트를 실패시킨다.

Spec 및 계획: docs/superpowers/.

-
license - not tested
Not graded
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
Release cycle
1Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Remote MCP for A2A dependency inspector MCP, structured receipts, audit logs, and reviewer-ready evi

  • Static MCP manifest and tool-policy security preflight with signed input-redacted receipts

  • A paid remote MCP for CodeG, built to return verdicts, receipts, usage logs, and audit-ready JSON.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nobodyohm-web/thot'

If you have feedback or need assistance with the MCP directory API, please join our Discord server