AGENTS.md — kpubdata-builder¶
POLICY.md is the single canonical source for project-management and review policy. Epic, Issue, Priority, Review Level, Verification and Release rules come from there. This file keeps only what is specific to this repository — build commands and directory rules. POLICY.md wins any conflict.
Mission¶
Implement KPubData Builder: the orchestration and artifact-pipeline layer that
runs on top of kpubdata.
Ground rules¶
- Do not reimplement
kpubdata's provider logic here. - Keep build specs declarative.
- Prefer deterministic behaviour over anything that looks like magic.
- Keep exporters pluggable.
- Every build produces a manifest.
- Validation fails fast and says why.
Language policy¶
kpubdata ADR 0003 is canonical. The evidence (measurements across ten Korean OSS projects) and the rejected alternatives are there.
Titles are English; bodies are free. Titles show up in lists, searches and release notes.
| Area | Language |
|---|---|
| Code identifiers, comments, docstrings | English |
| Commit messages | English |
| PR titles | English (Conventional Commits) — a squash merge turns it into a commit |
| CHANGELOG and release notes | English |
Governance documents (AGENTS.md, CONTRIBUTING.md) |
English |
Implementation contracts (PROVIDER_ADAPTER_CONTRACT.md, API_SPEC.md) |
English |
Design rationale (VALIDATION.md, ARCHITECTURE.md, ADRs) |
Korean |
| README | Korean first, with an English section in the same file |
| Issue titles | English |
| Issue bodies | Korean or English |
| PR bodies and review comments | Korean or English |
| Korean-domain documents (활용신청, 공공누리 procedures) | Korean |
| User-visible string literals | Out of scope — runtime behaviour, decided separately |
Operating rules:
- Answer an issue in the language it was written in.
- Write
good first issuein English, or in both. - Do not let English block a contribution. If a title is hard to write in English, open it in Korean and say so — triage and review will sort it out.
Comments and docstrings are gated, not merely requested¶
The rule above went unenforced long enough to accumulate 3,878 Korean comments and
docstrings across 255 files. scripts/check_korean_comments.py is a ratchet: it
freezes the current per-file count and fails only when a count grows, or when a
file absent from the baseline has any. Write new code in English; the existing
debt is paid down separately (#710).
확인은 기계가 한다¶
POLICY 18.2 and VERIFICATION.md are canonical. Three rules carry most of the weight:
- A sentence with a number in it comes from a command. Run it in the same breath and paste the output. A figure recalled from memory is not a figure.
- Sweep with
git ls-files, not with paths you chose. Ask the repository what it has. A hand-written path list is how__tests__/got missed. - A rule without a gate is a wish. When you add a rule, add the command that checks it, wire it into CI, and write the test that shows it failing. Without the third, nobody knows the gate works.
- An absent check is not a failure — it is a stop. A required status check no
workflow produces leaves every pull request BLOCKED for ever, because GitHub waits
for it rather than reporting it. The way past is
--admin, which skips every other check too. Require the one aggregateCI gatejob, never a matrix-suffixed name, and runscripts/check_required_checks.py(in kpubdata) after touching a matrix.
Existing debt is frozen with a ratchet — the baseline holds today's per-file count and the check fails only when a count grows. Fixing everything first means starting nothing.
Say "done" with the command's output. If tests failed, paste the failure. If a step was skipped, say it was skipped.
Labels — what an agent applies¶
POLICY.md sections 2.1, 2.1.1 and 2.1.2 are the label reference. This file deliberately does not copy the table: a second copy goes stale, and the first draft of this section already dropped the Severity axis that POLICY defines.
What is specific to agents:
- A new issue carries at least one
epic:*. Its title starts with a Conventional Commits type —fix(localdata): empty wrapper becomes a phantom row— and thetype:*label follows from the title (POLICY 2.1.3). Never settype:*by hand, and change the title rather than the label when the type was wrong. - Pull request titles use the same types; the
PR titlecheck fails otherwise. The allowed list lives in kpubdata'sscripts/conventional_title.py, and the rules in kpubdata's POLICY 2.1.3 — the one place all three repositories read. Merges are squash-only, so the PR title becomes the commit title onmain. Do not put an issue number in a PR title; writeCloses #Nin the body. - Leave Priority off when there is no evidence for it. POLICY 8 requires
Impact:,Blocks:andEvidence:for High and above, and a rating without evidence is a wrong rating. - Do not prefix a title with
GOV-01:orWH-03:. Those are serial numbers from a backlog document, not the issue's name. The type is the only prefix. - A pull request labelled
review:R3cannot merge until someone other than its author, with write access, approves it: the requiredR3 reviewcheck fails until then (kpubdata POLICY 14.1). The author's own approval, a bot's, and one followed by a request for changes do not count. Ask for the review; do not remove the label.
What an agent does not do:
- Promote to
priority:highorpriority:critical— that is a person's judgement (POLICY 8, 14). - Create a label that POLICY's table does not list. Adding one goes through
epic:governance. - Lower a
review:*level. - Create an Epic issue. Epic is a label (POLICY 4.1).
- Substitute
P0/P1/P2mechanically forpriority:*. POLICY 8 requires a re-rating from zero, so that a wrong priority does not survive under a new name.
Branch rules¶
- The default branch is
main. Never push tomaindirectly. Branch protection now enforces this, so a direct push is refused rather than merely discouraged. - Always work on a feature branch and open a PR.
- Branch names:
feat/issue-<number>-<short-description>,fix/issue-<number>-<short-description>,docs/<short-description>. - Never force-push to
main. Never deletemain. - Do not rename or delete a branch you did not create.
- If a git operation is not obviously safe, ask instead of guessing.
Releases¶
Cadence and order live in kpubdata's compatibility.md §5.1; who may do what lives in POLICY 14. This section keeps only what applies to an agent.
- Builder and Studio release once a month (kpubdata#685): only in the week holding
the month's last Thursday, in KST — Builder by Wednesday, Studio by Thursday.
release.ymlruns kpubdata'srelease-windowgate (policy: monthly) first and refuses anything else. A critical patch (a security fix or a release-blocking defect) may go out at any time and must name its issue:critical_patch+critical_issueon a dispatch, or aCritical-Patch: #Nline in the release pull request's body. - Release week is a freeze. From the Monday of that week until kpubdata-studio is
released, open only release pull requests against
main: version, CHANGELOG, dependency pin, compatibility documents, or a fix for a failing release gate. Other work waits on its branch. A kpubdata pin raise may merge at any time. - kpubdata is not on this train. It releases on demand, at most once every seven
days. Never recommend a release outside these rules to unblock work: build against
kpubdata
mainin the early-warning job, and raise the pin when kpubdata releases. - Prepare, do not release. An agent may tidy the CHANGELOG's Unreleased section,
run a release workflow with
dry_run, and draft the version and pin pull requests. Pushing a tag, creating a GitHub Release, approving the PyPI environment and changing what a release contains are a person's (POLICY 14). - Propose the bump from the CHANGELOG, with the reason. In 0.x, a breaking change or a new feature is minor; fixes alone are patch.
- Write what a release changes under
## [Unreleased]inCHANGELOG.md, as you merge it. The prepare job dates that section and the release job publishes it as the notes. An empty[Unreleased]stops the release — Studio included, when it only follows Builder's version: say so in a line. - Builder and Studio share one version (ADR 0004). They ship as one application, so a release that only changed one of them still raises both, and neither skips a month alone.
- Target Release is a month (
2026-10), not a version.
Build order¶
- The spec model
- Medallion pipeline orchestration
- Source execution through
kpubdata - The tabular engine (Polars today, moving to DuckDB — ADR 0021) and Silver validation
- The artifact model and Gold packaging
- The Markdown exporter
- The HuggingFace layout exporter
- Stage-aware publish hooks
Test expectations¶
- Unit tests for spec validation
- Stage-aware tests for Bronze/Silver/Gold promotion
- Golden tests for Markdown output
- Manifest contract tests
- Fixture-based source execution tests
How this project fits together¶
kpubdata fetches; this repository turns what it fetched into published
artifacts. Collection, validation and packaging each happen in a named stage, and
every build leaves a manifest describing what came out.
Vocabulary¶
| Term | Meaning |
|---|---|
| BuildSpec | Declares what to collect, how to shape it and where it goes |
| Bronze | First stage: raw collection results and the source snapshot |
| Silver | Middle stage: tabular conversion, validation, statistics, preview |
| Gold | Final internal stage: partitioning and an export-ready package |
| Artifact | Something a build produced (a file, usually) |
| Manifest | The specification of what a build produced — version, timestamps, digests |
| Tabular engine | The one canonical tabular engine. Polars today; the migration to DuckDB is in progress (ADR 0021, #864–#877) and ends with DuckDB as the only engine in src/. The legacy publish path (scripts/pipeline/) keeps Polars until it is retired (ADR 0018, ADR 0021 D1) |
| Exporter | Converts data into a format (Markdown, JSONL, Parquet, HuggingFace) |
| Publisher | Uploads a finished artifact somewhere (GitHub, HF Hub) |
| Golden Test | Compares current output against a stored known-good file |
Pipeline flow¶
graph LR
BS[BuildSpec] --> B[Bronze]
B --> S[Silver\nPolars]
S --> G[Gold]
G --> EX[Export]
EX --> M[Manifest]
subgraph "what each does"
BS -.-> |YAML| BS
B -.-> |kpubdata / raw snapshot| B
S -.-> |tabular conversion, validation| S
G -.-> |packaging| G
EX -.-> |formatting| EX
M -.-> |metadata| M
end
During the DuckDB migration (ADR 0021) Silver runs on Polars until the Silver step (#869) lands; the diagram shows the stage, not the engine.
[BuildSpec] -> [Bronze: raw collection] -> [Silver: tabular conversion and validation]
-> [Gold: packaging] -> [Export: formatting] -> [Manifest: metadata]
Agent coding rules¶
Prompts that work¶
- "Add a
CSVExporter. Followexporters/base.pyand implementExportModel." - "Add filter conditions to the
BuildSpecmodel."
Forbidden¶
- Duplicating
kpubdatalogic. Parsing belongs there. Here you only handle what came back. - Unclear output paths. Where a file is written is always explicit.
- A build without a manifest. Every build output includes
manifest.json. - New Polars code under
src/. ADR 0021 moves the canonical tabular engine to DuckDB. New tabular code goes throughtabular/duckdb_runtime.py(connections, resource limits, temp directories),tabular/sql.py(identifier quoting and parameter binding — never interpolate user data into SQL) andtabular/dtypes.py(Builder-owned dtype names). Changes to existing Polars code stay minimal until the step that replaces it;tests/parity/holds the baseline every step must match. - Leaking kpubdata's vocabulary into the contract. Builder owns the wire
vocabulary (
service/vocabulary.py, #831); map kpubdata values explicitly and use only kpubdata's public API (scripts/check_kpubdata_imports.py).
Before handing work back¶
- [ ] Does
uv run ruff check .pass? - [ ] Are the Bronze/Silver/Gold responsibilities kept distinct in both the code and the docs?
- [ ] Does the new exporter have unit tests?
- [ ] If the change needs a stage-aware test or a fixture, is it there?
- [ ] Does a golden test confirm the output is what you intended?
Directory layout¶
graph TD
ROOT[src/kpubdata_builder/] --> PL[pipeline/]
ROOT --> ST[stages/]
ROOT --> TB[tabular/]
ROOT --> E[exporters/]
ROOT --> P[publishers/]
ROOT --> S[spec.py]
ROOT --> M[manifest.py]
PL --> ORCH[orchestrator.py]
ST --> BR[bronze/]
ST --> SI[silver/]
ST --> GO[gold/]
TB --> PO[duckdb_* · polars_*]
E --> ME[markdown.py]
E --> JE[jsonl.py]
E --> PE[parquet.py]
P --> HP[huggingface.py]
src/kpubdata_builder/
├── pipeline/ # medallion stage flow control
├── stages/ # bronze, silver, gold implementations
├── tabular/ # tabular processing — Polars today, DuckDB runtime alongside (ADR 0021)
├── exporters/ # format conversion (Markdown, JSONL, Parquet)
├── publishers/ # artifact upload (HF, GitHub)
├── service/ # HTTP service mode (app.py, http.py, auth.py)
├── store/ # BuildIndex — derived SQLite index (ADR 0003)
├── warehouse/ # table catalog: immutable snapshots, CAS pointer (#699)
├── spec/ # BuildSpec definition and validation
└── manifest/ # manifest generation
Which file to change¶
- A new output format: add a file under
exporters/. - Uploading somewhere new: add logic under
publishers/. - Changing stage promotion rules: review
pipeline/andstages/together.
Adding an exporter¶
flowchart TD
Step1[1. subclass BaseExporter] --> Step2[2. implement export]
Step2 --> Step3[3. declare the format name]
Step3 --> Step4[4. add unit tests]
Step4 --> Step5[5. confirm the golden test]
- Subclass
BaseExporterfromexporters/base.py. - Implement
export(self, artifacts: List[Artifact]) -> List[Path]. - Declare the supported format name as a class variable.
- Add tests to
tests/unit/test_exporters.py.
What a golden test is¶
When the output is text, such as Markdown, a golden test compares it line for line against a stored known-good file. It catches a formatting change that no assertion would notice.
Related documents¶
In this repository¶
| Document | What it covers |
|---|---|
| CONTRIBUTING.md | How to contribute |
| ARCHITECTURE.md | System architecture |
| DOMAIN_MODEL.md | Domain model |
| EXPORT_MODEL.md | Export model |
| API_CONTRACT.md | API contract |
| PRD.md | Product requirements |
| ROADMAP.md | Roadmap |
| CREDENTIAL_SURFACE.md | Every place a user key can persist |
| SECURITY.md | Security policy and known limits |
KPubData product family¶
| Repository | Document | What it covers |
|---|---|---|
| kpubdata | AGENTS.md | Core agent guide |
| kpubdata-studio | AGENTS.md | Studio agent guide |