ctxpkg.org

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

FieldTypeDescription
schema_versioninteger2 for v2 packages
namestringPackage name; MAY use the scoped form @scope/name
versionstringSemantic Versioning 2.0.0
descriptionstringHuman-readable summary
created_atdatetimeISO 8601 creation timestamp
integrityobjectSHA-256 hashes and byte size (§8)
provenanceobjectProducing tool, tool version, project hash

3.2 Optional fields

FieldTypeDescription
conformance_levelinteger1, 2 or 3 (§5); default 1
scopestringNamespace prefix, e.g. @acme
signatureobjectEd25519 signature block (§9)
dependenciesarray | objectDirect dependencies. Canonical (written) form: a list of {"name", "version_req", "optional"} objects. Readers MUST also accept a map of scoped name → SemVer range
optional_dependenciesarray | objectSame 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
conflictsstring[]Packages that MUST NOT be co-installed
graph_summaryobjectNode/edge counts, type sets, mean activation, freshness
compatibilityobjectMin reader versions, agent hints, v1_fallback flag
marketplaceobjectCategories, badges, license — registry-facing metadata
layersstring[]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

FieldTypeReq.Description
idstringyesUnique within the package
typestringyesNode type (§4.2)
contentstringyesThe knowledge payload
activationnumbernoActivation energy 0.0–1.0; default 1.0
categorystringnoClassification category
sourcestringnoOrigin session or system
created_atdatetimenoCreation timestamp
decay_half_life_daysintegernoTemporal decay half-life
blob_refstringnoContent-addressable blob reference
file_path / line_start / line_end—noSource code anchor
confidencenumberno0.0–1.0
supersedesstringnoID of the node this one replaces

4.2 Node-type taxonomy (extensible registry)

CategoryTypes
Semanticfact pattern insight convention
Memorygotcha decision finding episode
Structurecode_symbol code_file code_module code_function code_class
Sessionsession task evidence procedure
Governancepolicy overlay profile slo
Eventbus_event handoff tool_call
CustomAny string — readers MUST preserve unknown types

4.3 Edge schema

FieldTypeReq.Description
from / tostringyesNode IDs within the package
typestringyesEdge type (§4.4)
weightnumbernoDefault 1.0
coactivationsintegernoHebbian co-activation counter
metadatastringnoFree-form annotation

4.4 Edge-type taxonomy (extensible registry)

CategoryTypes
Semanticsupports contradicts supersedes elaborates
Causaldecided_by caused_by followed_by tested_by
Structuralimports calls exports contains related_to
Dependencydepends_on extends conflicts_with
Hebbianco_activated (with weight + coactivations)

5Conformance levels#

Three levels enable progressive adoption. A reader claiming level N MUST also satisfy all lower levels.

LevelNameRequirementsTypical implementers
1Basic 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
2Graph Typed nodes + edges; SemVer dependency resolution; graph-merge composition with conflict detection (§6). Context engines, MCP servers
3Cognitive 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:

  1. Union — all nodes, identity by id; identical IDs from different packages reconcile to one node.
  2. Edge merge — duplicate edges (same from, to, type) merge; weights average (midpoint).
  3. Conflict detection — nodes connected by contradicts edges MUST surface as warnings; readers MUST NOT silently drop either side.
  4. Activation propagation — new cross-package edges MAY raise activation of connected nodes.
  5. Supersedes resolution — if node X supersedes Y, Y's activation MUST be set to 0.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 sourcev2 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/edgesnodes/edges with type mapping
sessionone 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#

StandardLayerRelation
MCPTool invocation (runtime)Complementary — .ctxpkg payloads travel over MCP unchanged
Agent SkillsProcedural instructionsComplementary — skills say how to act, CTXPKG says what is true
A2AAgent-to-agent messagingComplementary — packages are valid hand-off payloads
CTXPKGDeclarative knowledge interchangeThis specification