Specification · Normative
CTXPKG v2
Status: Draft · 2026-10-19 · supersedes v1 · schema: /schemas/context-package-v2.schema.json
Abstract. .ctxpkg v2 is an open, graph-native package
format for portable AI-agent context. It defines a JSON container with a manifest
(identity, integrity, provenance, signature) and a typed knowledge graph
(nodes, edges, activation), three conformance levels for progressive adoption,
and deterministic graph-merge composition.
1Conventions#
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY in this document are to be interpreted as described in RFC 2119. JSON terminology follows RFC 8259. All timestamps are ISO 8601 strings in UTC. All hashes are lowercase hex SHA-256 unless stated otherwise.
2File format#
A .ctxpkg file MUST be a single UTF-8 JSON
document with exactly two top-level members:
{
"manifest": { … }, // identity, integrity, provenance, signature
"content": { … } // the knowledge graph and auxiliary content
}
Future minor versions MAY introduce a ZIP-based container
(manifest, graph and content-addressable blobs as separate entries). Readers
SHOULD detect the container by leading bytes:
{ for JSON, PK for ZIP.
3Manifest#
3.1 Required fields
| Field | Type | Description |
|---|---|---|
schema_version | integer | 2 for v2 packages |
name | string | Package name; MAY use the scoped form @scope/name |
version | string | Semantic Versioning 2.0.0 |
description | string | Human-readable summary |
created_at | datetime | ISO 8601 creation timestamp |
integrity | object | SHA-256 hashes and byte size (§8) |
provenance | object | Producing tool, tool version, project hash |
3.2 Optional fields
| Field | Type | Description |
|---|---|---|
conformance_level | integer | 1, 2 or 3 (§5); default 1 |
scope | string | Namespace prefix, e.g. @acme |
signature | object | Ed25519 signature block (§9) |
dependencies | array | object | Direct dependencies. Canonical (written) form: a list of {"name", "version_req", "optional"} objects. Readers MUST also accept a map of scoped name → SemVer range |
optional_dependencies | array | object | Same shapes; every entry is optional (as is a list entry with "optional": true). Readers MUST attempt them; a failure MUST NOT abort install and MUST be reported |
conflicts | string[] | Packages that MUST NOT be co-installed |
graph_summary | object | Node/edge counts, type sets, mean activation, freshness |
compatibility | object | Min reader versions, agent hints, v1_fallback flag |
marketplace | object | Categories, badges, license — registry-facing metadata |
layers | string[] | Legacy v1 layer names for backward compatibility |
4Knowledge graph#
The graph is stored at content.context_graph and
MUST declare "format": "ctxpkg-graph-v2":
{
"format": "ctxpkg-graph-v2",
"nodes": [ … ],
"edges": [ … ]
} 4.1 Node schema
| Field | Type | Req. | Description |
|---|---|---|---|
id | string | yes | Unique within the package |
type | string | yes | Node type (§4.2) |
content | string | yes | The knowledge payload |
activation | number | no | Activation energy 0.0–1.0; default 1.0 |
category | string | no | Classification category |
source | string | no | Origin session or system |
created_at | datetime | no | Creation timestamp |
decay_half_life_days | integer | no | Temporal decay half-life |
blob_ref | string | no | Content-addressable blob reference |
file_path / line_start / line_end | — | no | Source code anchor |
confidence | number | no | 0.0–1.0 |
supersedes | string | no | ID of the node this one replaces |
4.2 Node-type taxonomy (extensible registry)
| Category | Types |
|---|---|
| Semantic | fact pattern insight convention |
| Memory | gotcha decision finding episode |
| Structure | code_symbol code_file code_module code_function code_class |
| Session | session task evidence procedure |
| Governance | policy overlay profile slo |
| Event | bus_event handoff tool_call |
| Custom | Any string — readers MUST preserve unknown types |
4.3 Edge schema
| Field | Type | Req. | Description |
|---|---|---|---|
from / to | string | yes | Node IDs within the package |
type | string | yes | Edge type (§4.4) |
weight | number | no | Default 1.0 |
coactivations | integer | no | Hebbian co-activation counter |
metadata | string | no | Free-form annotation |
4.4 Edge-type taxonomy (extensible registry)
| Category | Types |
|---|---|
| Semantic | supports contradicts supersedes elaborates |
| Causal | decided_by caused_by followed_by tested_by |
| Structural | imports calls exports contains related_to |
| Dependency | depends_on extends conflicts_with |
| Hebbian | co_activated (with weight + coactivations) |
5Conformance levels#
Three levels enable progressive adoption. A reader claiming level N MUST also satisfy all lower levels.
| Level | Name | Requirements | Typical implementers |
|---|---|---|---|
| 1 | Basic | Parse manifest + flat node list (id, type, content); verify integrity; merge nodes into agent context. Edges MAY be ignored. | Editors, CLIs, any JSON-capable tool |
| 2 | Graph | Typed nodes + edges; SemVer dependency resolution; graph-merge composition with conflict detection (§6). | Context engines, MCP servers |
| 3 | Cognitive | Activation energy honored under token budgets; Hebbian weights and co-activation counters; temporal decay; full state round-trip. | Reference implementation (lean-ctx) |
See Conformance for claim rules and the test checklist.
6Composition (graph merge)#
When multiple packages are installed, a Level 2+ reader MUST compose them deterministically:
- Union — all nodes, identity by
id; identical IDs from different packages reconcile to one node. - Edge merge — duplicate edges (same
from,to,type) merge; weights average (midpoint). - Conflict detection — nodes connected by
contradictsedges MUST surface as warnings; readers MUST NOT silently drop either side. - Activation propagation — new cross-package edges MAY raise activation of connected nodes.
- Supersedes resolution — if node X
supersedesY, Y's activation MUST be set to0.0(deactivated, not deleted).
7Scopes & namespaces#
Scoped names take the form @scope/name. Scope and name
MUST match [a-z0-9][a-z0-9_-]* (scope additionally
allows no leading underscore). Names without @ are unscoped and
SHOULD be reserved for local development. Registries
MUST enforce scope ownership for publishing.
Names are case-sensitive and lowercase only: readers and registries
MUST reject a name or scope
containing uppercase letters rather than normalize it. A registry
MAY apply a stricter publishing policy (for example
length limits or no underscores) and MUST state it.
8Integrity#
content_hash = hex( SHA256(content_json_bytes) )
sha256 = hex( SHA256(utf8("{name}:{version}:{content_hash}")) )
Both digests are encoded as lowercase hexadecimal (64 characters); readers
MUST reject uppercase hex. byte_size is the
length in bytes of content_json_bytes.
content_json_bytes are the content member serialized as
compact JSON: no insignificant whitespace, object members in the order
defined by the schema, absent optional members omitted
entirely (never null).
Readers MUST verify against the document text:
take the exact character range of the content member as it appears in
the file, remove insignificant whitespace outside string literals, hash the
resulting UTF-8 bytes, and reject the package on any mismatch. Readers
MUST NOT verify by re-serializing parsed values — number
formatting is not preserved across languages (a writer's 1.0 would
re-serialize as 1 in JavaScript and break the hash).
The declared integrity values are claims. Every verifier — reader,
registry, conformance tool — MUST recompute
content_hash, byte_size and sha256 from the
bytes it received and reject the package when any of them differs; it
MUST NOT check a signature against a declared digest it has
not recomputed.
9Signing#
Signatures use Ed25519 (RFC 8032). The signed message is the 64 ASCII characters of the lowercase-hex digest — not the raw 32 bytes:
message = hex( SHA256(utf8("ctxpkg-sign-v1:{name}:{version}:{integrity.sha256}")) )
signature.value = hex( Ed25519-sign(message_ascii_bytes) )
The manifest's signature object carries algorithm
("ed25519"), the signer's public_key (32 bytes,
lowercase hex, 64 characters) and the signature value (64 bytes,
lowercase hex, 128 characters). Verifiers MUST check the
signature against the recomputed integrity hash — signing therefore covers
the full content transitively.
9.1 Manifest signature
value covers name, version and content, but not the other manifest
fields (visibility, dependencies, scope,
provenance, compatibility, marketplace, …).
Writers SHOULD therefore also sign the whole manifest with
the same key and store it as signature.manifest_value (64 bytes,
lowercase hex):
manifest_message = hex( SHA256( utf8("ctxpkg-sign-manifest-v1:") ‖ canonical_json(manifest − signature) ) )
signature.manifest_value = hex( Ed25519-sign(manifest_message_ascii_bytes) ) manifest − signature is the manifest member as written in the
document with its signature member removed (whatever it holds).
canonical_json writes it without whitespace; object members sorted by
the UTF-8 bytes of their names, recursively; array order kept; of repeated member
names the last one counts; strings with only ", \ and
control characters escaped (\b \f \n \r \t, others
\u00xx in lowercase hex); true, false,
null as is.
A number written without fraction or exponent that fits a signed or unsigned
64-bit integer is written as is. Any other number is the IEEE-754 double nearest to
its text, written with its shortest round-trip digits. With n digits,
k the decimal exponent of the last digit and kk = n +
k: when 0 ≤ k and kk ≤ 16, the digits, k zeros
and .0 (1.0, 100.0); when 0 < kk
≤ 16, the digits with the point after the first kk (12.5);
when −5 < kk ≤ 0,
0., −kk zeros and the digits (0.25,
0.00001); otherwise exponent form d[.ddd]e followed by
the signed exponent kk − 1 (1e-6, 1.5e+300,
1e+21). Zero is 0.0 or -0.0. A manifest with
a lone surrogate in a string or a number outside the double range cannot be
verified.
When manifest_value is present, verifiers MUST
verify it as well as value, and reject the package when either fails.
A package whose signature has no manifest_value is
identity-only: its value may verify, but every manifest field
besides name, version and integrity may have been changed after signing. Verifiers
MUST report which coverage a passing signature has
(manifest or identity_only) and
MUST NOT present an identity-only signature as covering the
manifest. Registries SHOULD require
signatures for publishing and MUST record the public key
they verified against and the coverage they found. The
test vectors pin this construction:
valid-level2-signed.ctxpkg (identity-only) and
valid-level2-manifest-signed.ctxpkg (manifest) must verify,
invalid-signature.ctxpkg and
invalid-manifest-signature.ctxpkg must not.
10v1 compatibility#
v1 packages (five static layers) map losslessly into a v2 graph without edges:
| v1 source | v2 mapping |
|---|---|
knowledge.facts[] | nodes of type fact, activation 1.0 |
knowledge.patterns[] | nodes of type pattern, activation 1.0 |
gotchas[] | nodes of type gotcha, activation 1.0 |
graph.nodes/edges | nodes/edges with type mapping |
session | one node of type session with blob_ref |
A v2 package with compatibility.v1_fallback: true declares it is
readable as v1 (edges and activation ignored).
11Security considerations#
- Context injection. Package content steers agent behavior. Readers MUST verify integrity and SHOULD verify signatures before loading any node.
- Secret leakage. Writers SHOULD scan content for credential-shaped strings before export. Registries SHOULD reject publishes containing high-confidence secrets (the public registry rejects with HTTP 422).
- Size limits. Readers SHOULD enforce size ceilings before parsing untrusted files.
- Unknown types. Unknown node/edge types MUST be preserved on round-trip but MAY be excluded from context assembly.
12Ecosystem positioning#
| Standard | Layer | Relation |
|---|---|---|
| MCP | Tool invocation (runtime) | Complementary — .ctxpkg payloads travel over MCP unchanged |
| Agent Skills | Procedural instructions | Complementary — skills say how to act, CTXPKG says what is true |
| A2A | Agent-to-agent messaging | Complementary — packages are valid hand-off payloads |
| CTXPKG | Declarative knowledge interchange | This specification |