--- type: Specification title: Nytka — project context specification description: Project context for AI agents — a file format and method for project knowledge that both humans and agents can maintain status: draft version: "0.1" generated: { by: "human:mike + claude-opus-5", at: 2026-07-27 } stale_after: 2027-07-27 --- # Nytka v0.1 **Nytka is project context for AI agents: a directory layout, a frontmatter vocabulary and three operations that keep a project's tasks, decisions, research, data and current state accurate while agents write most of it.** This file is normative and self-contained. Linking an agent to this one document gives it everything it needs to work on, or start, a nytka project. Everything else in the nytka repo is tooling or examples. --- ## 1. The problem Project knowledge decays in a specific way. It does not become obviously wrong — it becomes *plausibly* wrong. A plan says the CMS uses one localisation model; the build used another; nobody wrote it down; six months later an agent reads the plan and generates code against a model that never shipped. Agents make this worse in both directions. They produce knowledge fast, and they consume it uncritically. A generated claim and a human-verified fact look identical in markdown. Nytka's premise: **you cannot prevent knowledge from going stale, so make staleness detectable.** Every claim carries who made it, when, against what, and when to stop trusting it. --- ## 2. Principles **P1 — Split by lifetime, not by topic.** Durable knowledge (years: purpose, decisions, procedures) lives in git files. Operational state (days: ticket status, transitions, comments) lives in a tracker. Git is bad at churn and has no timeline; trackers are bad at review and portability. Do not put both in one file. **P2 — One writable source per fact.** Every fact has exactly one place it can be edited. Generated views are read-only and say so. Sync runs in one direction. Two writable copies of a status field is the most common way a project starts lying to itself. **P3 — The live system is the truth.** Files that describe an external system (a CMS, an ad account, a deployed site) are dated observations, not facts. Re-verify before acting. A file may never overwrite a live system on the grounds that the file is "the spec". **P4 — Undated is unverified.** Any claim about external state carries a date and a source. No date means the claim has no standing, regardless of how confident the prose sounds. **P5 — Superseded knowledge stays visible.** Never edit an accepted decision to change its meaning. Write a new one, mark the old `superseded`, link them both ways. The wrong old decision is how you discover *why* the new one exists. **P6 — Temporary rules are labelled as temporary.** Unresolved questions get a working rule and a trigger for settling them. A stopgap presented as architecture is worse than an open question. **P7 — Load the level you need.** Portfolio, project, task. Never load a whole project package to do one task. **P8 — Be liberal in what you accept.** Unknown frontmatter keys, unknown types, broken cross-links and missing index files are not errors. A consumer must not reject a bundle over them. This lets the format evolve without coordinated upgrades. *(Convention adopted from Google's Open Knowledge Format.)* --- ## 3. Directory layout ``` project-root/ ├── project.yaml identity, components, tracker mode REQUIRED ├── AGENTS.md canonical agent instructions REQUIRED ├── CLAUDE.md pointer to AGENTS.md (and peers) optional ├── README.md human entry point recommended ├── overview.md stable context: purpose, audience, scope recommended ├── current-state.md what is happening now — kept short recommended ├── inventory.md every folder, repo and system, incl. messy ones ├── unresolved.md open questions + working rule + trigger │ ├── tasks/ task registry and/or tracker snapshot ├── decisions/ numbered, immutable, supersede-only ├── procedures/ repeatable workflows ├── research/ distilled conclusions with expiry ├── datasets/ registry of collected data (metadata committed, payloads not) ├── references/ external system identifiers, no credentials ├── artifacts/ non-markdown files the project owns, input or output ├── history/ compressed timeline, superseded decisions, failed approaches └── private/ gitignored: contacts, account IDs, owner answers ``` Only `project.yaml` and `AGENTS.md` are required. Everything else appears when real work needs it. An empty `research/` is better than a `research/` full of speculative filler. ### `artifacts/` Holds the files the project owns that cannot carry frontmatter — inputs it consumes (logos, vendor PDFs, exported diagrams) as much as outputs it produced. Payloads are committed here, unlike `datasets/`, because these files are small, durable and canonical rather than large expiring evidence. Provenance lives in `artifacts/index.json` alongside them, one entry per file: `id`, `file`, `kind`, `summary`, `addedAt`, `source`, `status` (`current` · `superseded` · `draft`). A copy whose canonical original lives elsewhere prefixes `source` with `vendored:` — that names the direction of truth, which is what stops a copy being edited in place and quietly forking. **A file with no entry is legal and means unknown provenance.** Dropping a file in and registering it afterwards is the intended path; requiring the entry first would make the cheapest option leaving the file somewhere else entirely. A record of a change an agent made to an external system is an output and belongs here, carrying the before state, the after state, and the identifier needed to reverse it. --- ## 4. `project.yaml` Declares what exists. Not a history and not a plan. ```yaml schemaVersion: "0.1" id: acme-web # stable slug, never changes name: ACME Corp Website type: client # client | own-business | side-project | internal status: active # active | paused | archived purpose: description: One or two sentences on what this is primaryOutcome: The single measurable thing success means owners: projectOwner: Mike businessOwner: private-reference # never a real name in a shareable file components: # every codebase and system, each its own repo frontend: path: ../acme-astro repo: github.com/org/acme-astro framework: astro-5 hosting: netlify status: live capabilities: [seo, content, reporting] tasks: tracker: file # file | github-projects | linear | jira storage: registry # registry (one file) | files (one per task) registry: tasks/tasks.yaml snapshot: tasks/snapshot.md # generated, read-only, gitignored idPrefixes: [WEB, SEO, CONT] ``` `components` is the field that earns its keep. Most real projects span several repos plus hosted systems; without this, an agent infers the layout from whatever it stumbles across. --- ## 5. Frontmatter vocabulary Every markdown file except `README.md` and pure pointers carries YAML frontmatter. One field is required: `type`. *This vocabulary is a deliberate subset of Google's Open Knowledge Format (OKF), so nytka documents stay readable by OKF-aware tooling. Nytka adds `confidence` and `supersedes`.* ```yaml --- type: Decision # REQUIRED. free string; see common types below title: Use a headless CMS for content description: One line, used in listings status: stable # draft | stable | deprecated | superseded generated: { by: claude-opus-5, at: 2026-07-27 } verified: - { by: "human:owner", at: 2026-07-27, against: cms } - { by: "process:lint", at: 2026-07-28 } confidence: stated # stated | inferred | ambiguous stale_after: 2027-04-19 # omit if it does not expire sources: - { id: vendor-docs, resource: https://…, last_modified: 2026-04-19 } supersedes: 0004 superseded_by: null tags: [cms, content] --- ``` ### Actor convention | Form | Meaning | |---|---| | `human:` | a person | | `process:` | an automated process | | `/` or a bare model id | an agent | **Trust tier is derived, never written:** | Condition | Tier | |---|---| | no `verified` entry | **unverified** | | `verified` by agents/processes only | **machine-confirmed** | | at least one `human:` verifier | **human-reviewed** | This distinction is the point of the whole vocabulary. An agent-generated claim and a fact the client confirmed on the phone must not look the same in a file. ### `confidence` | Value | Meaning | |---|---| | `stated` | asserted by a primary source — a law, an API response, the owner | | `inferred` | derived by reasoning; plausible, unconfirmed | | `ambiguous` | sources conflict, or the claim is known to be shaky | Default to `inferred` when writing something you worked out rather than read. A demand calendar you reasoned from a business model is `inferred`; a statutory deadline is `stated`. ### `stale_after` An absolute date. On or after it, the document is stale and must be re-verified before use — not "probably still fine". Required for anything describing a moving external system. --- ## 6. Decisions One file per decision: `decisions/NNNN-kebab-title.md`. Numbers are never reused or renumbered. ```md --- type: Decision title: Postgres for the primary datastore status: stable verified: [{ by: "human:owner", at: 2026-04-19 }] supersedes: null superseded_by: null --- ## Decision what was chosen, imperative ## Reason why — including the evidence ## Consequences what this now forces or forbids ``` **Superseding:** create the new record with `supersedes: NNNN`; set the old one's `status: superseded` and `superseded_by`. Change nothing else in the old file. Both stay in the repo forever. A decision belongs here if it constrains future work. Casual opinions and one-off task judgments do not. --- ## 7. Procedures A workflow that has been done twice and went wrong the same way both times. Each states: **Trigger · Inputs · Steps · Approval points · Failure conditions · Done when** The failure-conditions table is the part that carries the value — it is where the scar tissue lives. A procedure without one is a tutorial, not a procedure. --- ## 8. Tasks Nytka does not mandate a tracker. `project.yaml → tasks.tracker` declares it, and `AGENTS.md` documents the workflow for the declared mode. **`tracker: file`** — a registry file is authoritative. Cheap, offline, diffable, portable to clients without tooling. Poor at history; cannot span repos. **`tracker: github-projects`** (or linear/jira) — issues own status. Free event timeline, spans repos. Requires network; costs API calls to read. **When using an external tracker, P2 is binding:** issues own status, the repo holds a *generated read-only snapshot* so agents get cheap offline context, and sync runs tracker → file only. Never the reverse. Minimum task fields: `id`, `title`, `status`, `priority`, `owner`, `blockedBy`, `context`, `created`, `updated`, `acceptanceCriteria`. `acceptanceCriteria` is not decorative. Without it, "done" is a judgment call, and tasks get closed against what a draft said rather than what the live system shows. A good criterion is checkable by someone who was not in the conversation. ### Lifecycle | Status | Means | Leaves when | |---|---|---| | `proposed` | Someone thinks this should happen. No commitment. | a human accepts it → `ready`, or declines it → `cancelled` | | `ready` | Accepted, owned, specified. Nothing is in the way. | work starts → `in_progress` | | `in_progress` | Being worked now. | a dependency stops it → `blocked`; work finishes → `review` | | `blocked` | Cannot proceed. `blockedBy` is non-empty and unresolved. | the last blocker closes → `ready` or `in_progress` | | `review` | Work finished, `acceptanceCriteria` not yet checked. | every criterion checked → `done`; any criterion failed → `in_progress` | | `done` | Every criterion checked, with evidence. | never | | `cancelled` | Will not be done. `reason` required. | never | `cancelled` is reachable from any open status, not only from `proposed`. `done` is not: work that turned out to be unnecessary was cancelled, and recording it as completed inflates the one number a backlog is asked for. The lifecycle is what a tracker *represents*, not what it redefines. A tracker with no equivalent of `proposed` maps it onto its own backlog state and `AGENTS.md` records the mapping. **A task may not leave `proposed` without a `human:` actor recorded in `acceptedBy`.** Agents may create tasks freely and may move none of them out of `proposed`. This is §5's trust distinction applied to work rather than to claims. Without it an agent can append twenty plausible tasks to a registry and they read exactly like the ones the owner chose. `acceptedBy` is its own field rather than a reuse of `verified` because the two answer different questions — `verified` records who checked a claim, `acceptedBy` records who committed to the work. Merged, every human-accepted task would derive as **human-reviewed** under §5, asserting that someone had checked something nobody has checked. **`blocked` and an unresolved `blockedBy` imply each other.** A task whose blockers are still open but whose status says `ready` claims to be startable and is not; one that says `blocked` with an empty `blockedBy` names nothing to wait for. Either way it falls out of both halves of "what can I start, and what is stuck?" — which is the whole question a backlog is kept to answer. `review` is what stops "done" from being self-assessed: with no state between finishing and closing, the person who did the work is also the one declaring it met, and that is the judgment call `acceptanceCriteria` exists to remove. It is also the state four values cannot express. In one real project three tasks reached "written, checked, not yet released" on the same day; a four-value vocabulary had to record all three as `in_progress`, so the board asserted three tasks were being actively worked when none was. ### Fields by state Every task carries the minimum fields in every state. These are additional, and each is required to **enter** the state named: | State | Also required | |---|---| | `proposed` | `proposedBy` — an actor (§5). `owner` may be empty; a proposal has no champion yet. | | every state after `proposed` | `acceptedBy` — a `human:` actor — and a non-empty `owner` | | `blocked` | a non-empty `blockedBy` | | `done` | `completionSummary` and `evidence` | | `cancelled` | `reason`. Without it a backlog forgets what it already rejected and proposes it again. | `workLog` and `artifacts` are optional everywhere and append-only. A `workLog` entry rewritten to agree with how things turned out is worth nothing. ### Existing tasks `todo` is a **documented alias for `ready`** and is read as `ready`. §13 forbids rejecting a project over it, and no checker may raise it above `info`. `ready` is the canonical spelling because the other six statuses name the state a task is in rather than instruct the reader. The alias is kept rather than merely tolerated. A task-management skill written independently, for a different kind of project, by someone not working from this spec, arrived at exactly `todo | in_progress | blocked | done`. Convergence from an unrelated direction is the strongest evidence available that those four are the natural base set, and overruling it to gain one word would cost every existing registry a rewrite on the day this was published. **The required fields bind at the transition, not retroactively.** A task closed before its project adopted this lifecycle is conforming as it stands and stays that way: the transition that would have demanded `completionSummary` and `evidence` already happened and cannot be re-run. Do not backfill them. `evidence` reconstructed months later from memory is an undated claim about work nobody re-checked, which is what P4 exists to reject, and the empty field is the more useful record — it marks exactly which tasks predate the rule. A checker that learns these rules introduces them the way §10 describes, at `info` or `warn`, promoted only after being right in practice. A rule that makes every existing registry non-conforming on the day it is published is how a spec teaches people to ignore it. --- ## 9. Datasets Collected data is registered, not pasted. `datasets/index.json` is committed; payloads live in `datasets/payloads/` and are gitignored. A payload is registered and queried by script, never read into an agent's context — the registry entry is what a reader consults. ```json { "id": "keyword-audit-2026-04", "source": "", "collectedAt": "2026-04-19", "validUntil": "2026-10-19", "rawPath": "reports/keyword-audit.json", "sizeBytes": 227000000, "summary": "Keyword volumes across the mapped URLs, both locales.", "status": "current", "warning": "~227 MB. Never load into context. Query with a script." } ``` `status`: `current` (safe to use) · `historical` (a record of what happened, not of what is true) · `stale` (superseded, kept for provenance). **A dataset is evidence; a research item is knowledge.** Query the payload with a script, write the conclusion into `research/`, and never load a payload into an agent's context. --- ## 10. The three operations Adapted from Andrej Karpathy's LLM-maintained wiki pattern. The insight: the hard part of a knowledge base is not reading or thinking, it is bookkeeping — which is exactly what agents are good at and humans abandon. ### Ingest New material — a chat log, a research dump, a client call — is distilled into the package. It is a repeatable operation, not a one-time migration. 1. Read the source. Classify each piece: stable context → `overview.md`; a binding choice → `decisions/`; a repeated workflow → `procedures/`; a finding → `research/`; collected data → `datasets/`; current state → `current-state.md`; an open question → `unresolved.md`. 2. Discard: greetings, tool narration, abandoned brainstorming, superseded assumptions, duplicate answers, raw data stored elsewhere. 3. Every new claim gets `generated`, `confidence` and — if it describes an external system — `stale_after`. 4. **Contradictions are not merged.** If new material conflicts with an existing file, that is a decision to make or an owner question to ask, not a wording problem to smooth over. 5. Run lint. Raw sources are immutable. Distil from them; never edit them to match your conclusions. ### Query Load the narrowest level that answers the question. | Level | Load | |---|---| | **Portfolio** | `project.yaml` + `current-state.md` | | **Project** | the above + `overview.md` + `decisions/` + the relevant procedure | | **Task** | the task record + the decisions and procedures it names + any dataset it points at | `research/`, `history/`, `datasets/` payloads and archives are never auto-scanned. The Task row is the one that is mechanised: `nytka context ` assembles it and prints what it deliberately left unopened. It loads what a task names, never what a task might have meant — query stays link-following, not retrieval. ### Lint A health check, run at session start and after any ingest. It reports: | Check | Catches | |---|---| | `stale_after` in the past | claims nobody re-verified | | unverified claims about external state | generated guesses treated as fact | | decision graph consistency | a `supersedes` with no matching `superseded_by` | | dangling links | references to files that no longer exist | | missing `type` | files outside the vocabulary | | `current-state.md` age | a "current" state that stopped being current | | orphaned documents | nothing links to them | | template placeholders in prose | a package that scaffolded and was never filled in | Lint is the highest-value operation and the one most projects skip. Nearly every knowledge failure in a real project is something a lint pass would have flagged: a superseded decision never marked, a count that drifted from the live system, a string that got "corrected" to a wrong value in seventeen files. Nytka ships `tools/nytka-lint.mjs` — zero dependencies, `node tools/nytka-lint.mjs `. Installed, the same checks are `nytka lint `. The placeholder check is scoped to prose: angle brackets inside a code span or fence are how this spec and the templates document their own formats, and are never findings. That distinction is load-bearing — a check that flagged both would be muted, and a muted check still reads as coverage. It reports at `warn`, and so does every new check. **A check enters at `info` or `warn` and is promoted to `error` only after it has been right in practice** — right on inputs that do not share an origin, with no false positive outstanding against the check as it currently stands. A check that errors wrongly on day one teaches people to ignore the tool, and fixing the check does not un-teach it. Two right answers on two packages scaffolded from one template are one observation, not two. --- ## 11. `AGENTS.md` One canonical instruction file per project. Several agent tools read `AGENTS.md` at the project root directly. For the ones that do not, a tool-specific file (`CLAUDE.md`, `.cursor/rules/`, `.github/copilot-instructions.md`) points at it — never copies it. Copies drift, and then agents disagree with each other about how the project works. Check what a tool actually reads before writing a pointer for it. A pointer file that nothing loads is worse than no pointer: it looks like wiring, so nobody tests whether the wiring works. Which tools need one is a dated observation, not a property of the format. It must contain: 1. **Reading order** — and explicitly what *not* to auto-scan 2. **The single most important project rule**, stated first and bluntly 3. **Safety rules** — what must never be modified without confirmation 4. **Task-state workflow** for the declared tracker mode 5. **Evidence rules** — the format for dated claims 6. **When to create a decision** — and when not to 7. **Secrets policy** 8. **Scope** — what does not belong in this repo Keep it under roughly 150 lines. An instruction file nobody finishes is an instruction file nobody follows. --- ## 12. Starting a project **New project:** copy `templates/project/`, fill `project.yaml` and `AGENTS.md`, delete the directories you do not need yet, `git init`, run lint. **Existing project:** see `procedures/adopt-existing.md`. The short version — inventory what exists before writing anything; distil rather than copy; verify every inherited claim against the live system before carrying it forward; keep the old folder as a read-only archive rather than migrating it. Do not port a stale decision into a fresh package without checking it. Adoption is the moment you find out what was already wrong — that is most of its value. --- ## 13. Conformance A conforming nytka project: 1. has `project.yaml` with `schemaVersion`, `id`, `name`, `status` 2. has `AGENTS.md` 3. gives every markdown file (except `README.md` and pointer files) frontmatter with a non-empty `type` A conforming consumer **must not** reject a project for unknown keys, unknown `type` values, unknown task `status` values, broken cross-links, missing optional files, or frontmatter fields it does not recognise. --- ## 14. Prior art Nytka is an assembly, and the parts are better documented in their sources: - **Open Knowledge Format** (Google Cloud) — the provenance vocabulary: `type`, `generated`, `verified`, `status`, `stale_after`, `sources`, the actor convention, derived trust tiers, and the liberal-conformance rule. Nytka uses a subset and adds `confidence` and the supersede pair. OKF's Attested Computations are out of scope here but are the right model for any project where an agent reports numbers it computed. - **Karpathy's LLM wiki** — the three-layer split (immutable sources / agent-owned wiki / schema file) and the ingest–query–lint operations. - **ADR practice** (Michael Nygard) — numbered, immutable, supersede-only decisions. Deliberately **not** adopted: knowledge graphs derived from source code. They answer structural questions about code, while the questions that decay in a project are about the world outside it — prices, regulations, what the client approved. A derived code graph is a dataset with a short shelf life, not project knowledge.