§10 Contracts¶
Three cross-cutting promises jellycell makes to its users and agents. Each is expensive to break silently — so breaking them is a deliberate ceremony, documented below.
Bumping __version__ follows the versioning policy in
Releasing; every §10 break is a major
bump.
§10.1 — --json schemas¶
Every CLI command supports
--json. The shape of that JSON is a pydantic model withschema_version: int. Field renames, removals, and type changes are breaking.
Stable across patches + minors:
Existing fields keep their names.
Existing fields keep their types.
schema_versionstays the same.
Allowed additively in minors (no schema_version bump):
Adding an optional field with a default.
Adding a value to an enum (as long as it doesn’t narrow consumers).
Requires a major + schema_version bump:
Renaming a field.
Removing a field.
Changing a field’s type in a non-widening way (e.g.
str → int).Changing the shape of a nested object.
Owning models¶
Each command’s --json output has one pydantic model as the source of
truth. Adding schema_version: int = 1 is required.
Command |
Model |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(manifest file format) |
|
Regression¶
Every --json command’s shape is pinned by
tests/integration/test_json_schemas.py. Snapshots live alongside the
test under test_json_schemas/. Regenerating a snapshot must
accompany a CHANGELOG entry + version bump level appropriate to the
change (additive = minor, shape change = major).
Ceremony — when you change a --json model¶
Classify the change (additive vs. shape-change) per the rules above.
If breaking: increment
schema_versionin the pydantic model (1 → 2).Regenerate the snapshot:
uv run pytest tests/integration/test_json_schemas.py --force-regen
Bump
__version__insrc/jellycell/_version.pyto the appropriate level.Add a
CHANGELOG.mdentry under the new version.In the PR, note “Invariant touched: §10.1 (
<model>)” and the before/after shape.
§10.2 — Cache key algorithm¶
A cell’s cache key is a pure function of
(normalized source, sorted dep keys, env_hash, MINOR_VERSION). Any change to what goes in or how it’s combined forces every existing cache entry to invalidate cleanly.
Lives in src/jellycell/cache/hashing.py. The algorithm and
MINOR_VERSION are a single joint contract.
What’s in the key¶
Normalized source — line endings →
\n, trailing whitespace stripped per-line, leading/trailing blank lines removed. Cosmetic edits don’t invalidate.Sorted dep keys — the cache keys of the cells this one depends on (declared explicitly via
deps=tag, viajc.deps(...)AST-walked out of source, or via producer lookup onjc.load(...)). Order doesn’t matter; content does.env_hash— sha256 of the lockfile (uv.lock/poetry.lock) bytes if present, otherwise of the PEP-723dependencieslist.MINOR_VERSION— the counter insrc/jellycell/_version.pybaked into every key. Bumping it invalidates the entire cache in one move.
MINOR_VERSION is not semver¶
It’s a separate counter. Independent of __version__. Bump whenever:
cache/hashing.pychanges behavior (normalization, what goes in, how it’s combined).A pydantic schema baked into a manifest has a breaking change — a field renamed, removed, or type-changed in a non-widening way. Additive optional fields with defaults are backwards-compatible (old manifests parse fine; the new field defaults to
None/ its default value) and don’t require a bump.
Regression¶
tests/unit/test_hashing.py pins concrete key values for canonical
inputs. Any change to the algorithm fails the snapshot by design.
Ceremony — when you change the algorithm¶
Bump
MINOR_VERSIONin_version.pyby 1, and add a dated one-liner in the docstring explaining what changed.Regenerate the snapshot:
uv run pytest tests/unit/test_hashing.py --force-regen
Bump
__version__to the next major (1.X.Y → 2.0.0) — cache invalidation is breaking.Add a
CHANGELOG.mdentry under the new version, prominently noting the one-shot invalidation.Run
/spec-checkto confirm the ceremony before merging.
§10.3 — Agent guide content¶
The markdown emitted by
jellycell promptis the canonical agent guide. Agents rely on it as a stable bootstrap; changes that modify existing guidance break their flow.
Content lives in docs/agent-guide.md and is packaged + emitted via
src/jellycell/cli/commands/prompt.py.
What’s stable¶
Every documented rule keeps its meaning across patches.
The CLI command table is accurate.
The “idiomatic patterns” section doesn’t drop rules without ceremony.
Three-tier classification¶
Typo / clarification (no meaning change) → patch bump is fine.
Additive (new section for a new feature; existing sections unchanged) → minor bump.
Breaking (existing guidance removed, rewritten with different meaning, or changed in a way that would mislead an agent following the previous version) → major bump.
Regression¶
tests/unit/test_prompt_snapshot.py captures the full output. Any
content change fails the snapshot; regenerate intentionally and add a
CHANGELOG note.
Ceremony — when you change the agent guide¶
Classify the change (typo / additive / breaking).
Regenerate the snapshot:
uv run pytest tests/unit/test_prompt_snapshot.py --force-regen
Bump
__version__per the tier.Add a
CHANGELOG.mdentry.In the PR, note “Invariant touched: §10.3” and the classification.
Summary¶
§ |
Contract |
File |
Regression snapshot |
|---|---|---|---|
10.1 |
|
pydantic models with |
|
10.2 |
Cache key algorithm |
|
|
10.3 |
Agent guide content |
|
|
The /spec-check slash command and the spec-reviewer subagent both
read this page when auditing a diff.
See also¶
Architecture — the 8-layer boundary that the contracts sit on top of.
Releasing — the full versioning policy, including the patch/minor/major decision rule.