Agent Lease
The grant envelope the kernel returns for an accepted Agent Lease Manifest. The lease id is the only key that opens anything: the tool gate, the dispatcher, and later the egress proxy and process supervisor all key off it. manifest is the GRANTED manifest after narrowing; declared_hash is the SHA-256 of the declared manifest's canonical bytes, so the narrowing diff can be reconstructed from the two. signature is HMAC-SHA256, lower-case hex, over the canonical bytes of every field except signature; key_fingerprint names the key without revealing it. A lease with a parent is a child whose grant is a subset of the parent's; that containment is the registry's rule, not this schema's, because a schema cannot see the parent.
- Schema
agent-lease.schema.json, served as JSON at its$id: https://cognitive-delivery.github.io/contract/1.x/agent-lease.schema.json- Dialect
http://json-schema.org/draft-07/schema#- Root
- object, open (additional properties are carried)
required:schema_version,lease_id,manifest,declared_hash,issued_at,expires_at,issuer_session_id,key_fingerprint,signature - Stability
- none stated at the root; every declared property carries its own
- Properties
- 49 declared: 10 under the root, 39 in definitions
Properties
Every declared property under the root, in the schema's own order. [] is an array's items, .* the shape of every unnamed member, #name a definition. Objects carry additional properties unless a row says otherwise.
| Path | Type | Required | Description | Constraints | Stability |
|---|---|---|---|---|---|
schema_version | string | yes | Contract version this record was written against, as major.minor. One rule across every schema (schema set 1.2): a reader compares the major only, and a minor it has not met is additive by the contract's own rule. | pattern ^\d+\.\d+$ | stable |
lease_id | string | yes | The grant's identity. Lower-case UUID, the same shape as a capability token id. | pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ | stable |
manifest | object #grantedManifest | yes | The GRANTED manifest: the same shape as the Agent Lease Manifest the agent declared, after narrowing. This is a dereferenced copy of agent-lease-manifest.schema.json, inlined because the contract keeps every schema self-contained (a reader compiles each file on its own, with no cross-file references). A test in the consumer asserts the copy is identical to the source. | stable | |
declared_hash | string | yes | SHA-256, lower-case hex, of the canonical bytes of the manifest as DECLARED, before narrowing. | pattern ^[0-9a-f]{64}$ | stable |
parent_lease_id | string | The lease this one was issued under. Absent for a root lease. | pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ | stable | |
issued_at | string | yes | When the lease was issued. One form only, because this field is inside the signed bytes: UTC with millisecond precision and a Z suffix (2026-10-05T06:00:00.000Z), which is what Date.prototype.toISOString() writes. An offset form would hash differently for the same instant, so two implementations could disagree about one signature. SPEC §6 rule L1: expires_at is after issued_at, checked by the runner because a schema cannot compare two fields. | pattern ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$ | stable |
expires_at | string | yes | When the lease stops opening anything, whatever its recorded status; fixed at issue. One form only, because this field is inside the signed bytes: UTC with millisecond precision and a Z suffix (2026-10-05T06:00:00.000Z), which is what Date.prototype.toISOString() writes. An offset form would hash differently for the same instant, so two implementations could disagree about one signature. SPEC §6 rule L1: expires_at is after issued_at, checked by the runner because a schema cannot compare two fields. | pattern ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$ | stable |
issuer_session_id | string | yes | The kernel session that issued the lease. | min length 1 | stable |
key_fingerprint | string | yes | Fingerprint of the signing key. Never the key. | min length 1 | stable |
signature | string | yes | HMAC-SHA256, lower-case hex, over the canonical bytes of every field except this one. | pattern ^[0-9a-f]{64}$ | stable |
Definitions
The named shapes this schema refers to as #name. A row above that links here is not expanded in place; its constraints are the definition's.
#grantedManifest
An inlined copy of agent-lease-manifest.schema.json (its reference), carried so the schema is self-contained and held identical to its source by npm test.
| Path | Type | Required | Description | Constraints | Stability |
|---|---|---|---|---|---|
#grantedManifestan inlined copy of agent-lease-manifest.schema.json, held identical to its source | object | The GRANTED manifest: the same shape as the Agent Lease Manifest the agent declared, after narrowing. This is a dereferenced copy of agent-lease-manifest.schema.json, inlined because the contract keeps every schema self-contained (a reader compiles each file on its own, with no cross-file references). A test in the consumer asserts the copy is identical to the source. | |||
#grantedManifest.schema_version | string | yes | Contract version this record was written against, as major.minor. One rule across every schema (schema set 1.2): a reader compares the major only, and a minor it has not met is additive by the contract's own rule. | pattern ^\d+\.\d+$ | stable |
#grantedManifest.agent | object | yes | Who is asking. name is a label for the cockpit; kind says how the harness runs it; runtime_agent is the existing closed agent vocabulary so a lease keys to the same identity every audit event already carries. | stable | |
#grantedManifest.agent.name | string | yes | Display label for the cockpit, at most 120 characters: the worker role, the session, the plugin. Not an identity, and never a person's name, email address or git identity; this value is recorded in an audit journal that is retained indefinitely. | min length 1 max length 120 | stable |
#grantedManifest.agent.kind | string | yes | How the harness runs this agent. native: inside the kernel's own process. delegated-cli: a spawned coding CLI. plugin: loaded into the host. external: presented over a bridge from outside the harness. | one of "native", "delegated-cli", "plugin", "external" | stable |
#grantedManifest.agent.runtime_agent | string | yes | The closed agent identity vocabulary. unknown is a first-class value, never an error: the kernel looked and could not tell. cdf-native (schema set 1.0, additive) is the harness's own native turn loop: the kernel drives a model provider directly, with no vendor CLI or SDK. | one of "codex-cli", "claude-code", "gemini-code-assist", "github-copilot", "cdf-native", "unknown" | stable |
#grantedManifest.agent.provider | string | The organisation or product providing the agent, when it differs from the runtime. | stable | ||
#grantedManifest.agent.model | object | The model behind the agent, for attribution. Never a prompt, never a key. | stable | ||
#grantedManifest.agent.model.vendor | string | yes | min length 1 | stable | |
#grantedManifest.agent.model.family | string | yes | min length 1 | stable | |
#grantedManifest.agent.model.version | string | stable | |||
#grantedManifest.agent.harness_version | string | Version of the harness that generated or presented this manifest. | stable | ||
#grantedManifest.parent_lease_id | string | The lease this manifest is presented under, when it is a child. A root manifest has none and is narrowed against the workspace policy instead. | pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ | stable | |
#grantedManifest.intent | object | yes | Why the agent is running. purpose is required and non-empty: a manifest with no stated purpose is refused. | stable | |
#grantedManifest.intent.spec_slug | string | The governed spec this run serves, when there is one. | stable | ||
#grantedManifest.intent.task_id | string | The task within that spec, when there is one. | stable | ||
#grantedManifest.intent.purpose | string | yes | One sentence of intent, at most 500 characters. Recorded in the audit journal, so no prompt and no content. | min length 1 max length 500 | stable |
#grantedManifest.allow | object | yes | What the agent asks to be allowed. Every list is required and may be empty; an empty tools list in a GRANTED manifest is a lease that opens nothing, which is why the kernel refuses rather than grants it. Granted lists are always a subset of the parent's. | stable | |
#grantedManifest.allow.tools | array | yes | Governed tool names the agent may call. An empty list in a GRANTED manifest is refused (SPEC §5.2 R2). | stable | |
#grantedManifest.allow.tools[] | string | A governed tool name the agent may call. At most 128 characters of letters, digits, _, ., :, / and -, so a governed cdf_ name and a <server>/<tool> pair both fit. * is refused: a lease that names every tool has not named one, and SPEC §5.2 R2 exists because a grant must say what it opens. Whitespace is refused. | pattern ^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,127}$min length 1 | ||
#grantedManifest.allow.read_paths | array | yes | Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading /, any .. segment, a leading ~, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root. | stable | |
#grantedManifest.allow.read_paths[] | string | min length 1 must not match ^/, (^|/)\.\.(/|$), ^~, ^[A-Za-z]:, \\ | |||
#grantedManifest.allow.write_paths | array | yes | Workspace-relative POSIX globs the agent may change. Refused here, without lookahead: a leading /, any .. segment, a leading ~, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root, and a change outside the granted set is a scope violation that revokes the lease. | stable | |
#grantedManifest.allow.write_paths[] | string | min length 1 must not match ^/, (^|/)\.\.(/|$), ^~, ^[A-Za-z]:, \\ | |||
#grantedManifest.allow.hosts | array | yes | Hosts the agent may reach. *.example.com matches api.example.com and not example.com; matching a bare parent domain requires listing it. Enforced by the egress proxy once it keys off the lease. | stable | |
#grantedManifest.allow.hosts[] | string | A host the agent may reach. A lower-case DNS name of one or more labels, an IPv4 literal or localhost (both are DNS-name shaped), with an optional single leading *. label, or the bare *. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs. | pattern ^(\*|(\*\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$min length 1 max length 253 | ||
#grantedManifest.allow.commands | array | yes | Executables the agent may run, by basename. | stable | |
#grantedManifest.allow.commands[] | string | An executable the agent may run. The basename of the executable, at most 128 characters: letters, digits, ., _, +, -. No path separator (identity is the resolved executable's basename, not where it was found), no whitespace (an argument is not part of the name) and no shell operator. | pattern ^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$min length 1 | ||
#grantedManifest.allow.tool_args | object | DECLARED argument constraints per tool (schema set 1.2, development stability): tool name to a JSON Schema the agent proposes for its own calls to that tool, after Progent's argument-schema policies. A declaration, not a grant: in 1.x an issuer MAY omit it from the granted manifest and MUST NOT treat it as granted authority, because the reference gate does not yet evaluate argument schemas and a narrowing rule without an enforcing gate is a claim the corpus cannot test. The vector tool-args-dropped shows the reference dropping it. It becomes a rule when a gate enforces it. | keys must match ^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,127}$additional properties: see #grantedManifest.allow.tool_args.* | development a declaration an issuer MAY omit from the grant (SPEC 4.4); becomes a rule when a gate enforces it | |
#grantedManifest.allow.tool_args.* | object | A JSON Schema. Not validated as one here: draft-07 cannot validate a schema as data without a meta-schema $ref, which the self-contained rule forbids. | |||
#grantedManifest.deny | object | yes | What the agent must not do even if allow would otherwise permit it. Granted deny lists are the UNION of the declared and the parent's, so a child can never shed a parent's refusal. | stable | |
#grantedManifest.deny.commands | array | yes | Executable names refused outright, for example sudo, su, docker, ssh, curl, wget, launchctl, systemctl. | stable | |
#grantedManifest.deny.commands[] | string | An executable refused outright. The basename of the executable, at most 128 characters: letters, digits, ., _, +, -. No path separator (identity is the resolved executable's basename, not where it was found), no whitespace (an argument is not part of the name) and no shell operator. | pattern ^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$min length 1 | ||
#grantedManifest.deny.paths | array | yes | POSIX globs the agent must not touch: credentials, governance evidence, the lease journal itself. A denial MAY be home-relative (~/.ssh/**), because refusing a path outside the workspace is meaningful even though no allow could grant it; a leading /, any .. segment and any backslash are refused. | stable | |
#grantedManifest.deny.paths[] | string | min length 1 must not match ^/, (^|/)\.\.(/|$), \\ | |||
#grantedManifest.deny.hosts | array | yes | Hostnames refused outright. | stable | |
#grantedManifest.deny.hosts[] | string | A host refused outright. A lower-case DNS name of one or more labels, an IPv4 literal or localhost (both are DNS-name shaped), with an optional single leading *. label, or the bare *. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs. | pattern ^(\*|(\*\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$min length 1 max length 253 | ||
#grantedManifest.budget | object | yes | Ceilings. Every field is optional and non-negative; an absent field means the parent's remaining budget applies. A child's granted budget is clamped to its parent's remaining, depth is decremented per generation and fan_out is decremented on the parent as each child is issued. | stable | |
#grantedManifest.budget.steps | integer | Maximum tool calls or turns. | min 0 max 9007199254740991 | stable | |
#grantedManifest.budget.wall_clock_ms | integer | Maximum lifetime in milliseconds; the lease expires when it is spent. | min 0 max 9007199254740991 | stable | |
#grantedManifest.budget.tokens | integer | Maximum model tokens, input plus output. | min 0 max 9007199254740991 | stable | |
#grantedManifest.budget.cost_usd | number | Maximum spend in US dollars. | min 0 | stable | |
#grantedManifest.budget.depth | integer | How many generations of child lease may be issued beneath this one. Zero means no children. At most 16: the reference root policy allows 4, and a declaration above the ceiling is nonsense rather than something to clamp. | min 0 max 16 | stable | |
#grantedManifest.budget.fan_out | integer | How many child leases may be live under this one at once. At most 256: the reference root policy allows 8. | min 0 max 256 | stable | |
#grantedManifest.approvals | array | yes | Action names that need a human before the agent may perform them, under the same identifier rule as tool names. Required and may be empty. | stable | |
#grantedManifest.approvals[] | string | An action that needs a human first; the same identifier rule as a tool name. At most 128 characters of letters, digits, _, ., :, / and -, so a governed cdf_ name and a <server>/<tool> pair both fit. * is refused: a lease that names every tool has not named one, and SPEC §5.2 R2 exists because a grant must say what it opens. Whitespace is refused. | pattern ^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,127}$min length 1 | ||
#grantedManifest.attestation | object | Who vouches for the declaration. Optional: an agent presenting its own manifest has nothing to sign with, and the bridge or harness signs on its behalf. | stable | ||
#grantedManifest.attestation.issuer | string | yes | Which party produced the declaration. | one of "agent", "bridge", "harness", "plugin" | stable |
#grantedManifest.attestation.signature | string | Lower-case hex signature over the canonical bytes of the manifest, when the issuer can sign: 64 hex characters, which is HMAC-SHA256 like the lease's own signature. An odd-length or longer value is not a signature this contract defines. | pattern ^[0-9a-f]{64}$ | stable | |
#grantedManifest.attestation.key_fingerprint | string | Fingerprint of the key that produced signature. Never the key. | min length 1 | stable |