Workspace IndexKnowledge Notes › gadak — Jira cached to one SQLite file, and the empty result an agent reads as "nothing exists"

#117Product2026-09-21chatdeep

gadak — Jira cached to one SQLite file, and the empty result an agent reads as "nothing exists"

midagedev shipped gadak, a local cache for Jira and Confluence: you pick the projects and spaces worth keeping, it syncs them into a single SQLite file on your machine, and search and issue reads happen there instead of over the REST API. The origin story is an agent problem, not a UI problem — the author was digging through stale issues with Claude Code, watched it take forever, and got cut off by a rate limit mid-task. On a real Atlassian Cloud project of 3,296 issues the author measured text search at 543 ms against the API and 41 ms against the cache, and open-issues-by-epic at 4,761 ms against 22 ms — the second one 214×, because the REST side has to pull eight pages and add them up while the cache answers it as one GROUP BY. Writes are not cached: they go to Jira first and only land locally once Jira accepts them. A Claude Code skill and an MCP server put the same cache in front of an agent, and the skill carries a warning worth more than the speed numbers.

For Jayverse this is the cleanest external example of a pattern alice already runs by hand — keep the corpus local so the agent never pays a network round trip to read it — and the skill's warning is a rule the Auditor should carry: Jira translates status and priority names into the account's language, so an agent querying status = "In Progress" on a Korean account gets no error and zero rows, and reads that answer as "no such issue exists". Version 0.24, one maintainer, Apache-2.0, no telemetry.

Why

The interesting claim here is not that a cache is faster than an API. It is where the agent breaks without one.

An agent searching Jira over the REST API pays a round trip per query, and the queries it wants to ask are the expensive ones — text across issues and comments, counts grouped by epic, anything that needs more than one page. The agent does not know it is being expensive. It fans out, the site paginates, and the budget the user actually cares about — wall-clock time, and the rate limit — is spent on plumbing rather than on the question. The author's version of this was concrete: a dig through old issues that ran long and then died on a rate limit.

The fix is unglamorous. Sync the subset you care about into local storage, answer reads from there, and leave writes on the network where they belong. What makes it worth a note is the second-order effect on the agent: once reads are free, the agent can afford to look before it acts, and the cost of a wrong guess drops to nothing.

How it works

One SQLite file, scoped by you

gadak init asks for the site, the account email, an API token, and the projects to cache; --spaces adds Confluence, and without it the wiki is never touched. It pulls only what your account can already see. Atlassian Cloud takes one API token for both Jira and Confluence on the same site; Jira Server and Data Center connect with gadak init --server and a personal access token, and because there is no Confluence Server client yet, Server workspaces are issues only.

Everything lands in one SQLite file on the machine. The first full sync took 3.7 minutes on the author's 3,296-issue site; after that the cache is stale by however often you sync. You can delete it and sync again.

Reads are local, writes are not

Writes go to Jira first. If Jira accepts, the cache updates; if Jira rejects, the write fails where you made it. Nothing is queued locally to be flushed later — which means there is no offline-edit reconciliation problem to get wrong, and no divergence between what you see and what the origin holds.

Four operations still ask Jira directly: viewing an attachment, gadak issue --editmeta, gadak fields, and gadak api. Everything else reads the file. There is no telemetry, no analytics, and no gadak account; the token sits in ~/.gadak/config.json at mode 0600 and goes only to your own site.

What the numbers say, and what they don't

QueryJira REST APIgadak
Text search543 ms41 ms13×
Open issues by epic (GROUP BY)4,761 ms22 ms214×

Medians, measured 2026-08-26 on a real Atlassian Cloud work project of 3,296 issues. The epic row is the honest one: the REST figure is eight pages fetched and summed, and the gadak figure includes the time to start the CLI process. The 214× is therefore a statement about pagination, not about SQLite being magic — grouped counts are exactly the shape the API is worst at and a local database is best at.

The agent surface

gadak skill install writes a Claude Code skill; gadak mcp install claude-desktop registers an MCP server. From either, the agent searches the cache, creates issues, and moves status through the gadak CLI. Comments and issues an agent creates are attributed to the agent's name, so the audit trail does not quietly credit the human.

The trap the skill writes down

Jira translates status and priority names into the account's display language. Query for an English status name on a Korean-language account and Jira does not error — it returns an empty result set. An agent reads an empty result as an answer, not as a failure, and reports that no such issue exists. The skill ships with this written down precisely because a model will not infer it.

This is the transferable lesson. For a human an empty list is ambiguous and invites a second look; for an agent it is a confident negative. Any tool an agent drives needs to distinguish no match from wrong vocabulary, and if the underlying API will not make that distinction, the skill has to.

What stays in Jira

Sprint planning, Jira's own dashboards and notifications, admin work, and anything that cannot tolerate a minute of staleness. The cache is a read path, not a replacement. The author says so on the page, which is the right instinct — the failure mode of this category of tool is claiming to be the system of record.

Where it lands in Jayverse

  • The pattern alice already runs. Knowledge Notes is a local corpus that an agent reads with no network call, which is why drafting against it is fast. gadak is that same bet applied to a system jay does not control, and it is the proof that the pattern survives contact with a corporate API.
  • A rule for the Auditor. "An empty result is not a negative answer" generalizes past Jira. Any agent rule set that checks for absence — no matching record, no prior approval, no counterexample — needs to prove the query was well formed before it trusts the empty set.
  • Cache scope is disclosure scope. The page is explicit that gadak itself sends nothing anywhere, but an agent that reads the cache sends what it read to its own model. That is the same calculus as any agent-readable corpus, and it means the --projects list is a privacy decision, not a convenience one.

Verified and unverified

On the page, checkable: version 0.24, Apache-2.0, single maintainer, install paths (brew install --cask midagedev/tap/gadak, the CLI-only cask, Microsoft Store on Windows, gadak serve on Linux), and the benchmark method with dates and issue counts stated.

Not verified here: the benchmark numbers are the author's own, from one site on one day, and no third party has reproduced them. The iPhone app is beta and TestFlight-only for internal testers, with no public download and store submission still pending a review path that does not require a reviewer onto the author's network. Support for Linear and the built-in tracker is claimed cell by cell in the project's own table; nothing here checks it.

Key expressions

Words and phrases from this page worth keeping, with the Korean meaning and the sentence they come from.

Expression뜻 · 쓰이는 자리
local cache로컬 캐시(원본을 내 기기에 복사해 두고 거기서 읽는 것) · gadak의 전체 구조. "a local cache for Jira and Confluence"
round trip왕복(요청이 서버에 갔다 오는 한 번의 비용) · 네트워크 지연을 말할 때. "pays a round trip per query"
rate limit레이트 리밋(일정 시간 안에 허용되는 요청 수 상한) · API가 끊기는 지점. "got cut off by a rate limit mid-task"
pagination페이지네이션(결과를 여러 페이지로 쪼개 주는 방식) · 집계가 느려지는 진짜 이유. "a statement about pagination"
GROUP BY그룹 바이(기준 컬럼으로 묶어 집계하는 SQL 절) · 에픽별 집계 쿼리. "answers it as one GROUP BY"
median중앙값(값을 줄 세웠을 때 한가운데 값; 평균보다 이상치에 강함) · 벤치마크 보고 단위. "Medians, measured 2026-08-26"
system of record기록의 원본(진실로 취급되는 시스템) · 캐시가 넘보면 안 되는 자리. "claiming to be the system of record"
stale오래된·낡은(원본보다 뒤처진 상태) · 캐시의 본질적 한계. "the cache is stale by however often you sync"
write path / read path쓰기 경로 / 읽기 경로 · 캐시 설계의 핵심 구분. "The cache is a read path, not a replacement"
queued locally로컬에 쌓아 두다(나중에 보내려고 대기시키다) · 오프라인 편집 방식. "Nothing is queued locally to be flushed later"
reconciliation정합성 맞추기(어긋난 두 상태를 다시 일치시키는 일) · 오프라인 쓰기의 대표 난제. "no offline-edit reconciliation problem"
attributed to~의 이름으로 기록되다 · 감사 추적에서. "attributed to the agent's name"
audit trail감사 기록(누가 무엇을 언제 했는지 남는 흔적) · 에이전트 작업의 책임 소재. "the audit trail does not quietly credit the human"
empty result set빈 결과 집합(행이 0개인 정상 응답) · 오류와 구별해야 하는 것. "it returns an empty result set"
confident negative확신에 찬 부정(근거 없이 "없다"라고 단정하는 답) · 에이전트의 실패 방식. "for an agent it is a confident negative"
transferable lesson옮겨 쓸 수 있는 교훈(다른 맥락에도 적용되는 배움) · 사례에서 규칙을 뽑을 때. "This is the transferable lesson"
well formed제대로 만들어진(문법·어휘가 유효한) · 쿼리를 신뢰하기 전 조건. "prove the query was well formed"
disclosure scope공개 범위(밖으로 나갈 수 있는 정보의 경계) · 프라이버시 판단. "Cache scope is disclosure scope"
second-order effect2차 효과(직접 결과가 다시 낳는 결과) · 설계 변화의 파급. "the second-order effect on the agent"
unglamorous화려하지 않은·수수한 · 해법이 단순할 때 쓰는 칭찬. "The fix is unglamorous"

← All Knowledge Notes · Workspace Index · Top ↑

gadak — Jira를 SQLite 파일 하나에 캐시하다, 그리고 에이전트가 "없다"로 읽어 버리는 빈 결과

midagedev가 Jira와 Confluence용 로컬 캐시 gadak을 공개했다. 남겨 둘 프로젝트와 스페이스만 고르면 내 컴퓨터 안의 SQLite 파일 하나로 동기화하고, 검색과 이슈 조회는 REST API 대신 그 파일에서 한다. 출발점은 UI 문제가 아니라 에이전트 문제였다. 만든 사람이 Claude Code로 묵은 이슈들을 뒤지다가 한참 걸리는 것을 보았고, 결국 작업 도중 rate limit에 걸려 중단됐다. 이슈 3,296건짜리 실제 Atlassian Cloud 프로젝트에서 측정한 값은 텍스트 검색이 API 543 ms 대 캐시 41 ms, 에픽별 열린 이슈가 4,761 ms 대 22 ms였다. 두 번째가 214배인 이유는 REST 쪽은 응답 8페이지를 받아 합산해야 하는데 캐시 쪽은 GROUP BY 한 번으로 끝나기 때문이다. 쓰기는 캐시하지 않는다. Jira로 먼저 가고, Jira가 받아들여야 로컬에 반영된다. Claude Code 스킬과 MCP 서버가 같은 캐시를 에이전트 앞에 놓아 주는데, 그 스킬에 적힌 경고 하나가 속도 수치보다 값어치가 있다.

Jayverse 입장에서 이것은 alice가 이미 손으로 굴리고 있는 패턴 — 말뭉치를 로컬에 두어 에이전트가 읽을 때 네트워크 왕복을 치르지 않게 한다 — 의 가장 깔끔한 외부 사례이고, 스킬의 경고는 Auditor가 규칙으로 가져가야 할 내용이다. Jira는 상태와 우선순위 이름을 계정 표시 언어로 번역한다. 그래서 한국어 계정에서 에이전트가 status = "In Progress"로 물으면 오류도 없이 0행이 돌아오고, 에이전트는 그 답을 "그런 이슈는 없다"로 읽는다. 버전 0.24, 만드는 사람 한 명, Apache-2.0, 텔레메트리 없음.

Why

여기서 흥미로운 주장은 캐시가 API보다 빠르다는 것이 아니다. 캐시가 없을 때 에이전트가 어디서 무너지는가다.

REST API로 Jira를 뒤지는 에이전트는 쿼리마다 왕복 비용을 치르는데, 에이전트가 던지고 싶어 하는 쿼리가 하필 비싼 것들이다. 이슈 본문과 댓글을 가로지르는 텍스트 검색, 에픽별로 묶은 집계, 한 페이지를 넘어가는 모든 것. 에이전트는 자기가 비싸게 굴고 있다는 사실을 모른다. 넓게 퍼뜨려 묻고, 서버는 페이지를 쪼개고, 정작 사용자가 신경 쓰는 예산 — 벽시계 시간과 rate limit — 은 질문이 아니라 배관에 쓰인다. 만든 사람이 겪은 형태가 바로 그것이다. 오래된 이슈를 뒤지는 작업이 길어지다가 rate limit에서 죽었다.

해법은 화려하지 않다. 필요한 부분집합만 로컬로 동기화하고, 읽기는 거기서 답하고, 쓰기는 원래 있어야 할 네트워크 쪽에 남겨 둔다. 이 글감이 되는 지점은 에이전트에 미치는 2차 효과다. 읽기가 공짜가 되는 순간 에이전트는 행동하기 전에 확인할 여유가 생기고, 잘못 짚었을 때의 비용이 0으로 떨어진다.

How it works

SQLite 파일 하나, 범위는 내가 정한다

gadak init이 사이트 주소, 계정 이메일, API 토큰, 캐시할 프로젝트를 차례로 묻는다. --spaces를 붙이면 Confluence까지 가져오고, 붙이기 전에는 위키를 건드리지 않는다. 내 계정이 이미 볼 수 있는 만큼만 가져온다. Atlassian Cloud는 토큰 하나로 같은 사이트의 Jira와 Confluence를 함께 받고, Jira Server와 Data Center는 gadak init --server와 개인 액세스 토큰으로 붙는다. Confluence Server용 클라이언트가 아직 없어서 Server 워크스페이스는 이슈만 가져온다.

모든 것은 그 컴퓨터 안의 SQLite 파일 하나에 남는다. 첫 전체 동기화는 만든 사람의 3,296건짜리 사이트에서 3.7분 걸렸고, 그 뒤로 캐시는 동기화 주기만큼 늦는다. 지웠다가 다시 동기화해도 된다.

읽기는 로컬, 쓰기는 아니다

쓰기는 Jira로 먼저 간다. Jira가 받아들이면 캐시를 갱신하고, 거절하면 그 자리에서 실패한다. 나중에 보내려고 로컬에 쌓아 두는 것이 없다 — 그래서 오프라인 편집을 다시 맞추는 문제 자체가 생기지 않고, 내가 보는 것과 원본이 가진 것이 어긋나지 않는다.

여전히 Jira에 직접 묻는 동작은 네 가지다. 첨부파일 보기, gadak issue --editmeta, gadak fields, gadak api. 나머지는 전부 파일에서 읽는다. 텔레메트리도, 분석 도구도, gadak 계정도 없다. 토큰은 ~/.gadak/config.json에 0600으로 있고 내 사이트에만 간다.

수치가 말하는 것과 말하지 않는 것

쿼리Jira REST APIgadak
텍스트 검색543 ms41 ms13배
에픽별 열린 이슈 (GROUP BY)4,761 ms22 ms214배

2026-08-26에 이슈 3,296건짜리 실제 Atlassian Cloud 업무 프로젝트에서 잰 중앙값이다. 에픽 행이 정직한 쪽이다. REST 값은 8페이지를 받아 합산한 것이고, gadak 값에는 CLI 프로세스가 뜨는 시간까지 들어 있다. 따라서 214배는 SQLite가 마법이라는 뜻이 아니라 페이지네이션에 대한 이야기다. 묶어서 세는 쿼리는 API가 가장 못하고 로컬 DB가 가장 잘하는 모양이다.

에이전트가 붙는 자리

gadak skill install은 Claude Code용 스킬을 깔고, gadak mcp install claude-desktop은 MCP 서버를 등록한다. 어느 쪽이든 에이전트는 gadak CLI를 통해 캐시를 검색하고, 이슈를 만들고, 상태를 옮긴다. 에이전트가 남긴 댓글과 만든 이슈에는 에이전트 이름이 붙어서, 감사 기록이 사람의 공으로 슬쩍 넘어가지 않는다.

스킬에 적어 둔 함정

Jira는 상태와 우선순위 이름을 계정의 표시 언어로 번역한다. 한국어 계정에서 영어 상태 이름으로 물으면 Jira는 오류를 내지 않는다 — 빈 결과를 돌려준다. 에이전트는 빈 결과를 실패가 아니라 답으로 읽고, 그런 이슈는 없다고 보고한다. 모델이 스스로 알아낼 수 없는 내용이기 때문에 스킬에 그대로 적혀 있다.

여기서 옮겨 쓸 수 있는 교훈이 나온다. 사람에게 빈 목록은 애매해서 한 번 더 보게 만들지만, 에이전트에게는 확신에 찬 부정이다. 에이전트가 모는 도구라면 *일치하는 것이 없음*과 *어휘가 틀렸음*을 구분할 수 있어야 하고, 밑에 깔린 API가 그 구분을 해 주지 않는다면 스킬이 대신 해야 한다.

Jira에 남는 일

스프린트 계획, Jira 자신의 대시보드와 알림, 관리자 작업, 1분의 지연도 견딜 수 없는 일. 캐시는 읽기 경로이지 대체재가 아니다. 만든 사람이 그렇게 써 두었고, 그게 옳은 감각이다. 이 부류 도구의 실패 방식은 자기가 기록의 원본인 척하는 것이다.

Jayverse에서의 위치

  • alice가 이미 굴리는 패턴. Knowledge Notes는 에이전트가 네트워크 호출 없이 읽는 로컬 말뭉치이고, 그래서 그 위에서 글을 쓰는 일이 빠르다. gadak은 jay가 통제하지 못하는 시스템에 같은 베팅을 적용한 것이고, 그 패턴이 회사 API와 부딪혀도 살아남는다는 증거다.
  • Auditor용 규칙 하나. "빈 결과는 부정의 답이 아니다"는 Jira를 넘어 일반화된다. 부재를 확인하는 규칙 — 일치하는 기록 없음, 사전 승인 없음, 반례 없음 — 은 빈 집합을 믿기 전에 쿼리가 제대로 만들어졌음을 먼저 증명해야 한다.
  • 캐시 범위가 곧 공개 범위. gadak 자신은 아무 데도 보내지 않는다고 페이지에 분명히 적혀 있지만, 캐시를 읽은 에이전트는 읽은 내용을 자기 모델로 보낸다. 에이전트가 읽는 모든 말뭉치와 같은 계산이고, --projects 목록은 편의가 아니라 프라이버시 결정이라는 뜻이다.

확인된 것과 확인되지 않은 것

페이지에 적혀 있고 확인 가능한 것: 버전 0.24, Apache-2.0, 단독 개발, 설치 경로(brew install --cask midagedev/tap/gadak, CLI 전용 cask, Windows는 Microsoft Store, Linux는 gadak serve), 그리고 날짜와 이슈 건수가 명시된 벤치마크 방법.

여기서 확인하지 않은 것: 벤치마크 수치는 만든 사람이 한 사이트에서 하루에 잰 자기 값이고, 제3자가 재현한 적이 없다. 아이폰 앱은 베타이고 내부 테스터용 TestFlight로만 가며, 공개 다운로드는 없고 스토어 등록은 심사자가 개발자의 네트워크에 들어오지 않고도 앱을 볼 수 있는 경로가 먼저다. Linear와 내장 트래커 지원은 프로젝트 자체 표에 셀 단위로 적혀 있을 뿐, 여기서 검증하지 않았다.

핵심 표현

이 페이지의 영어 본문에서 배울 만한 단어와 표현, 뜻과 나온 자리.

Expression뜻 · 쓰이는 자리
local cache로컬 캐시(원본을 내 기기에 복사해 두고 거기서 읽는 것) · gadak의 전체 구조. "a local cache for Jira and Confluence"
round trip왕복(요청이 서버에 갔다 오는 한 번의 비용) · 네트워크 지연을 말할 때. "pays a round trip per query"
rate limit레이트 리밋(일정 시간 안에 허용되는 요청 수 상한) · API가 끊기는 지점. "got cut off by a rate limit mid-task"
pagination페이지네이션(결과를 여러 페이지로 쪼개 주는 방식) · 집계가 느려지는 진짜 이유. "a statement about pagination"
GROUP BY그룹 바이(기준 컬럼으로 묶어 집계하는 SQL 절) · 에픽별 집계 쿼리. "answers it as one GROUP BY"
median중앙값(값을 줄 세웠을 때 한가운데 값; 평균보다 이상치에 강함) · 벤치마크 보고 단위. "Medians, measured 2026-08-26"
system of record기록의 원본(진실로 취급되는 시스템) · 캐시가 넘보면 안 되는 자리. "claiming to be the system of record"
stale오래된·낡은(원본보다 뒤처진 상태) · 캐시의 본질적 한계. "the cache is stale by however often you sync"
write path / read path쓰기 경로 / 읽기 경로 · 캐시 설계의 핵심 구분. "The cache is a read path, not a replacement"
queued locally로컬에 쌓아 두다(나중에 보내려고 대기시키다) · 오프라인 편집 방식. "Nothing is queued locally to be flushed later"
reconciliation정합성 맞추기(어긋난 두 상태를 다시 일치시키는 일) · 오프라인 쓰기의 대표 난제. "no offline-edit reconciliation problem"
attributed to~의 이름으로 기록되다 · 감사 추적에서. "attributed to the agent's name"
audit trail감사 기록(누가 무엇을 언제 했는지 남는 흔적) · 에이전트 작업의 책임 소재. "the audit trail does not quietly credit the human"
empty result set빈 결과 집합(행이 0개인 정상 응답) · 오류와 구별해야 하는 것. "it returns an empty result set"
confident negative확신에 찬 부정(근거 없이 "없다"라고 단정하는 답) · 에이전트의 실패 방식. "for an agent it is a confident negative"
transferable lesson옮겨 쓸 수 있는 교훈(다른 맥락에도 적용되는 배움) · 사례에서 규칙을 뽑을 때. "This is the transferable lesson"
well formed제대로 만들어진(문법·어휘가 유효한) · 쿼리를 신뢰하기 전 조건. "prove the query was well formed"
disclosure scope공개 범위(밖으로 나갈 수 있는 정보의 경계) · 프라이버시 판단. "Cache scope is disclosure scope"
second-order effect2차 효과(직접 결과가 다시 낳는 결과) · 설계 변화의 파급. "the second-order effect on the agent"
unglamorous화려하지 않은·수수한 · 해법이 단순할 때 쓰는 칭찬. "The fix is unglamorous"

← 전체 기술 노트 · 워크스페이스 인덱스 · 맨 위 ↑