Kaneo fork — 에이전트가 일한 기록을 사람이 읽을 수 있게

플랫폼Live2026-09 ~ 운영 중첫 배포까지 2일

STACKHono · Drizzle · PostgreSQL · Better Auth · MCP(streamable HTTP) · React · k3d · ArgoCD

개정 이력

  1. 출처에 본문이 참조한 오픈소스의 GitHub 저장소 링크를 추가했다.
  2. 헤더와 카드에 걸린 시간 배지를 추가했다.
  3. 작업량만 보여주던 숫자(테스트·커밋·파일 수 등)를 빼고, 문제의 크기와 결과를 보여주는 숫자만 남겼다.
  4. 문단형으로 전면 재작성. 설계 문서·인수인계 기록·커밋 본문을 근거로 판단과 운영 사고를 채우고 아키텍처 그림을 추가했다.
  5. 첫 공개. Feature 탭과 요구사항 문서 화면.

시작

나는 Claude 와 Codex 를 번갈아 쓰며, 여러 세션으로 같은 프로젝트를 이어서 진행한다. 세션이 바뀔 때마다 맥락이 흩어졌다. 6월부터는 계획과 진행 상태를 Linear 에 모았지만 세 가지가 풀리지 않았다. API 응답이 커서 세션을 시작할 때마다 토큰을 많이 썼고, 화면은 에이전트가 붙인 정보로 과밀해졌고, AI 가 끝없이 덧붙인 기록은 사람이 읽을 수 없었다. 돈을 더 낸다고 풀리는 문제가 아니었다.

2026년 9월 1일 설계 문서를 쓰고 오픈소스 칸반 Kaneo 를 포크했다. 처음부터 만들지 않은 이유는 필요한 바탕이 이미 있어서다. OAuth 와 원격 MCP, 인증, 워크스페이스와 역할, 알림, 실시간 갱신, 첨부, 컨테이너 배포까지 Kaneo 가 갖추고 있었다. 내가 만들 것은 에이전트가 일한 기록을 남기고, 사람이 그걸 읽고 승인하는 층뿐이었다. 다음 날부터 모든 계획의 정본을 Linear 에서 이 인스턴스로 옮겼다.

무엇을 만들었나

사람은 프로젝트마다 여섯 탭을 본다. 개요에는 마지막 인수인계와 진행 중인 작업이, 타임라인에는 세션별 작업 기록이 트리로 있다. Feature 탭에는 기능마다 요구사항 → 설계 → 태스크가 묶여 있다. 태스크 탭은 원래 Kaneo 의 보드이고, 지식 탭은 확정된 결정과 용어, 문서 탭은 산출물 보관함이다. 사람도 타임라인에 직접 기록을 쓴다.

에이전트는 MCP 툴로 같은 데이터를 다룬다. 세션을 시작할 때 브리프 한 번으로 프로젝트 상황을 받고, 일하면서 타임라인에 기록을 남기고, 태스크를 잡을 때 lease 를 걸어 다른 세션과 겹치지 않게 한다. 요구사항과 설계 문서를 올리고 태스크에 연결하지만, 승인은 하지 못한다.

Kaneo Feature 탭 — 기능별 요구사항·설계·태스크 진행 요약
Feature 탭. 기능마다 요구사항·설계 승인 상태와 연결된 태스크를 한 줄로 본다.
Kaneo 요구사항 문서 — 사용자 이야기 절과 조건·결과 문장
요구사항 문서. 절은 사용자 이야기, 번호 줄은 "조건 → 시스템은 → 결과" 한 문장이다.

스택 — upstream 을 그대로 두는 포크

스택은 Kaneo 그대로다. API 는 Hono, ORM 은 Drizzle, 인증은 Better Auth, 웹은 React 와 TanStack Router 다. 포크가 upstream 을 계속 따라갈 수 있어야 해서 규칙을 하나 정했다. 새 코드는 새 폴더에 두고, 기존 파일에는 등록 한 줄만 추가한다.

마이그레이션이 이 규칙을 가장 먼저 시험했다. Drizzle 의 테이블 필터로 upstream 과 같은 폴더를 쓰려 했더니, 생성된 SQL 에 upstream 테이블까지 섞여 들어왔다. 필터가 생성 단계에는 적용되지 않았다. 그래서 에이전트 층의 마이그레이션을 별도 폴더와 별도 추적 테이블로 완전히 분리했다. upstream 의 마이그레이션 기록은 한 줄도 건드리지 않는다.

아키텍처 — 한 컨테이너, 같은 집

운영은 fika·약국통과 같은 Mac Studio 의 k3d 클러스터다. Kaneo 이미지는 웹과 API 를 한 컨테이너에 담아서 공용 배포 차트로 그대로 뜬다. 데이터는 클러스터 밖 PostgreSQL 에 두고, 첨부와 산출물은 MinIO 에 presigned URL 로 올린다.

이미지만 다른 경로로 만든다. sandbox 모노레포가 아니라 포크 저장소에서 버전을 입력해 워크플로를 수동 실행하고, 2.22.0-agent.N 형식으로 태그를 붙인다. upstream 의 릴리스 파이프라인이 semver 만 받기 때문에 upstream 버전 뒤에 포크 번호를 붙였다.

Kaneo fork 아키텍처 — 에이전트 세션과 브라우저 요청이 nginx 와 traefik 을 거쳐 web+API 한 컨테이너로 가고, PostgreSQL 과 MinIO 는 클러스터 밖에 있다. 이미지는 포크 저장소의 워크플로가 만들어 platform 레포 태그 커밋으로 배포한다.
에이전트는 MCP 로, 사람은 웹으로 같은 컨테이너에 들어온다. 이미지는 포크 저장소에서 수동 빌드하고 platform 레포 태그 커밋으로 배포한다.

응답 크기는 툴이 정한다

포크하기 전에 upstream MCP 부터 쟀다. 태스크 20개짜리 프로젝트에서 태스크 목록 한 번이 약 6,200토큰이었고, 세션을 시작하는 데만 6,400토큰이 들었다. API 응답을 가공 없이 넘겼기 때문이다. 프롬프트에 “필요한 것만 봐라” 라고 적어도 막을 수 없었다. 그래서 에이전트 층의 모든 툴에 응답 모양의 상한을 걸었다. 세션 시작 브리프는 약 2KB, 목록은 제목만, 타임라인 조회는 요약만 준다. 응답 크기가 데이터 양이 아니라 툴의 속성이 됐다.

툴 정의 자체도 컨텍스트를 먹는다. 하네스마다 비용 모델이 달라서, Claude Code 는 필요할 때 정의를 불러오고 Codex 는 전체 스키마를 늘 싣는다. 툴 개수 상한은 비용을 대표하지 못해서, 정의를 직렬화한 바이트 합계에 예산을 걸고 테스트로 강제했다. 툴이 늘면 예산을 넘는지부터 보고, 넘으면 설명을 줄이거나 예산을 다시 산정한다.

승인은 사람만, 기록은 지우지 않는다

에이전트는 요구사항과 설계를 올리지만 승인하지 못한다. 승인 MCP 툴을 아예 만들지 않았고, REST 로도 API 키 호출자는 403 을 받는다. 사람이 웹 세션에서 누르는 것만 승인이다. 흐름은 느려지지만, 에이전트가 쓴 요구사항을 에이전트가 통과시키는 구조는 원천적으로 없다.

타임라인 기록은 삭제하지 않는다. 숨길 수는 있지만 행은 남는다. 드물게 다시 찾는 기록이 가장 값비싸다고 봤기 때문이다. 오래 읽히지 않은 기록을 지우는 규칙은 결국 가장 비싼 기록부터 지운다. 반대로 아직 확정되지 않은 지식 항목 제안은 지울 수 있게 했다.

처음에는 사람이 쓰는 면과 AI 가 쓰는 면을 나누려 했다. Linear 를 읽을 수 없게 된 원인이 사람과 AI 가 한곳에 쓰기 때문이라고 봤다. 9월 3일 다시 따져 보니 원인은 작성자가 섞인 게 아니라 형식이 없다는 것이었다. 면을 나누는 설계를 버리고, 형식이 정해진 타임라인 하나를 사람과 AI 가 같이 쓰게 했다.

요구사항을 문서 하나로

9월 13일, 요구사항과 설계를 Kaneo 의 1급 엔티티로 올린 기능을 배포하고 바로 실제 작업에 써 봤다. 요구사항 탭, 설계 탭, 태스크 탭이 따로 있어 한 기능을 따라가기 불편했다. 같은 날 기능 단위로 요구사항·설계·태스크를 묶는 Feature 탭으로 다시 설계해 agent.23 으로 올렸다.

요구사항도 항목 표가 아니라 문서 하나를 정본으로 바꿨다. 절은 사용자 이야기이고, 번호 줄은 “조건 → 시스템은 → 결과” 한 문장이다. 항목과 키는 문서에서 뽑아내고, 문서에서 사라진 키는 폐기로 처리한다. 영어 키워드를 쓰는 요구사항 형식 대신 한국어 문장 형식을 쓴 것도 실제로 읽어 보고 정했다. 문서를 고치면 연결된 설계와 태스크에 “갱신 필요” 표시가 붙고, 확인은 사람이 한다.

운영하며 겪은 일

첫 배포 다음 날 결함 4건. 9월 2일, 기록 조회가 워크스페이스 범위를 확인하지 않아 다른 워크스페이스의 기록이 보일 수 있었다. lease 재획득이 거부되고, 용어 별칭의 대소문자가 정규화되지 않고, 밀리초 단위 커서 때문에 같은 시각의 기록이 누락되는 문제도 함께 나왔다. 같은 세션에서 모두 고치고 별도 리뷰를 받았다. 커서는 시각 대신 기록 id 기반으로 계약을 바꿨다.

upstream 의 버그. Claude Code 가 MCP 클라이언트를 동적으로 등록할 때 400 이 났다. upstream 등록 엔드포인트가 grant_types·response_types 를 배열로 받지 못했다. 포크에서 고쳐 agent.7 로 배포했다.

운영 데이터를 비운 날. 9월 13일, 요구사항 → 설계 → 태스크 흐름을 깨끗하게 실측하려고 백업을 뜬 뒤 fika 요구사항만 남기고 기존 프로젝트·태스크·기록을 모두 지웠다. 쌓인 과거 기록이 새 흐름을 시험하는 데 오히려 방해가 된다고 판단했다.

돌아보며

포크는 맞는 선택이었다. 인증·MCP·배포를 만들지 않고 첫날부터 에이전트 층에만 시간을 썼다. 새 폴더, 등록 한 줄, 마이그레이션 분리라는 규칙을 첫 주에 정해 둔 덕에 upstream 을 따라갈 길도 남아 있다.

사람과 AI 의 면 분리, 요구사항 탭 구조는 둘 다 실제로 써 본 다음 날 뒤집었다. 문서로 먼저 설계하고 나중에 써 보는 순서의 비용이었다.

출처

  • ~/Playground/kaneo 커밋 본문(2026-09-01 ~ 09-13), docs/agent-layer/DESIGN.md(문제 정의·응답 실측·툴 예산·면 분리 개정), docs/agent-layer/HANDOFF.md(배포·운영 이력)
  • platform apps/kaneo/prod.yaml, 하네스 공용 규칙 문서(Linear → Kaneo 전환)
  • 참조한 오픈소스(GitHub): Kaneo(upstream) · Hono · Drizzle ORM · Better Auth · Model Context Protocol · React · PostgreSQL · MinIO · k3d · Argo CD · Traefik.
  • 비밀값과 운영 호스트 식별정보는 싣지 않는다.