diff --git a/docs/docs.json b/docs/docs.json
index 955c7ba..df6db55 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -95,7 +95,8 @@
"pages": [
"reference/events",
"reference/state",
- "reference/summary"
+ "reference/summary",
+ "reference/final"
]
},
{
diff --git a/docs/explanation/artifacts.mdx b/docs/explanation/artifacts.mdx
index e27739f..dd98ffc 100644
--- a/docs/explanation/artifacts.mdx
+++ b/docs/explanation/artifacts.mdx
@@ -14,6 +14,7 @@ Every Noēsis episode produces a set of structured artifacts that capture the co
events.jsonl # cognitive event timeline with lineage
summary.json # metrics and KPIs (insight.metrics)
state.json # plan, beliefs, memory, outcomes
+ final.json # terminal seal marker
manifest.json # SHA-256 catalog + optional HMAC
learn.jsonl # learning signals (optional)
prompts.jsonl # prompt provenance (opt-in, ADR-005)
@@ -334,6 +335,49 @@ noesis events ep_abc123 --phase act
noesis events ep_abc123 -j | jq '.[] | select(.phase == "plan")'
```
+## final.json
+
+`final.json` is the terminal seal marker for an episode. If it exists, Noēsis treats the run as finalized and blocks lifecycle mutations such as checkpoint or resume.
+
+### Schema
+
+Current final schema version: `final/2.0.0` ([JSON schema](/schema/final/2.0.0.json)).
+
+```json
+{
+ "schema_version": "final/2.0.0",
+ "episode_id": "ep_2024_abc123_s0",
+ "process_id": "proc_abc123",
+ "run_index": 1,
+ "finalized_at": "2024-01-15T10:30:05Z",
+ "outcome": "success",
+ "verification_status": "unverified"
+}
+```
+
+| Field | Description |
+| --- | --- |
+| `schema_version` | Final marker schema (`final/2.0.0`) |
+| `episode_id` | Episode/run ID being sealed |
+| `process_id` | Process lineage ID for the run |
+| `run_index` | 1-based run number within the process |
+| `finalized_at` | ISO 8601 timestamp for finalization |
+| `outcome` | One of `success`, `failed`, `vetoed`, `cancelled`, `interrupted`, `error` |
+| `verification_status` | One of `verified`, `unverified`, `not_applicable` |
+
+### Sealing contract
+
+Terminal runs seal in a fixed order:
+
+1. Write `final.json`.
+2. Write `manifest.json` with a checksum entry for `final.json`.
+
+If manifest writing fails, Noēsis removes `final.json` so the run is not left in a falsely sealed state. A second seal attempt raises an immutability error and leaves existing artifacts unchanged.
+
+
+Pause-on-veto runs (`governance_pause_on_veto=true`) stay unsealed: they emit lifecycle evidence such as `run.interrupt` and `run.checkpoint`, but do not write `final.json` or `manifest.json` until continuation or explicit termination.
+
+
## manifest.json
The manifest provides integrity verification for all artifacts with SHA-256 hashes and optional signatures.
@@ -348,7 +392,8 @@ The manifest provides integrity verification for all artifacts with SHA-256 hash
"files": [
{"name": "summary.json", "sha256": "sha256:abc123...", "size_bytes": 1234, "kind": "summary"},
{"name": "state.json", "sha256": "sha256:def456...", "size_bytes": 5678, "kind": "state"},
- {"name": "events.jsonl", "sha256": "sha256:ghi789...", "size_bytes": 9012, "kind": "events"}
+ {"name": "events.jsonl", "sha256": "sha256:ghi789...", "size_bytes": 9012, "kind": "events"},
+ {"name": "final.json", "sha256": "sha256:jkl012...", "size_bytes": 234, "kind": "attachment"}
],
"signature": {
"alg": "hs256",
@@ -365,6 +410,8 @@ The manifest provides integrity verification for all artifacts with SHA-256 hash
| `files` | Array of files with `name`, `sha256`, `size_bytes`, `kind` (`summary`, `state`, `events`, `learn`, `attachment`, `custom`) |
| `signature` | Optional signature block with algorithm, key ID, value, timestamp |
+For sealed terminal runs, verifiers should expect both `final.json` and `manifest.json`. `manifest.json` is not the seal marker by itself; `final.json` is.
+
### Artifact immutability guarantees
All artifacts are written atomically using this pattern:
@@ -389,16 +436,16 @@ def verify_manifest(episode_dir: Path) -> bool:
with open(manifest_path) as f:
manifest = json.load(f)
- for filename, info in manifest["files"].items():
- file_path = episode_dir / filename
+ for info in manifest["files"]:
+ file_path = episode_dir / info["name"]
# Check size
- if file_path.stat().st_size != info["size"]:
+ if file_path.stat().st_size != info["size_bytes"]:
return False
# Check hash
with open(file_path, "rb") as f:
- actual_hash = hashlib.sha256(f.read()).hexdigest()
+ actual_hash = "sha256:" + hashlib.sha256(f.read()).hexdigest()
if actual_hash != info["sha256"]:
return False
@@ -517,7 +564,7 @@ store.vacuum() # Remove episodes older than 14 days
-**Use manifests for compliance.** The SHA-256 checksums in manifest.json provide tamper evidence for audits.
+**Use manifests with final markers for compliance.** `final.json` marks a terminal seal; the SHA-256 checksums in `manifest.json` provide tamper evidence for the sealed bundle.
## Next steps
@@ -535,4 +582,7 @@ store.vacuum() # Remove episodes older than 14 days
Complete summary schema documentation.
+
+ Final marker schema and sealing semantics.
+
diff --git a/docs/explanation/core-concepts.mdx b/docs/explanation/core-concepts.mdx
index 87b40e9..f2bbd05 100644
--- a/docs/explanation/core-concepts.mdx
+++ b/docs/explanation/core-concepts.mdx
@@ -151,6 +151,7 @@ Every episode produces structured **artifacts** that capture the full cognitive
summary.json # metrics and outcomes
state.json # cognitive state
events.jsonl # timeline
+ final.json # terminal seal marker
manifest.json # integrity checksums
learn.jsonl # learning signals (optional)
```
@@ -256,11 +257,14 @@ The manifest provides integrity verification:
"created_at": "2024-01-15T10:30:05Z",
"files": [
{"name": "summary.json", "sha256": "sha256:abc123...", "size_bytes": 1234, "kind": "summary"},
- {"name": "events.jsonl", "sha256": "sha256:def456...", "size_bytes": 5678, "kind": "events"}
+ {"name": "events.jsonl", "sha256": "sha256:def456...", "size_bytes": 5678, "kind": "events"},
+ {"name": "final.json", "sha256": "sha256:ghi789...", "size_bytes": 234, "kind": "attachment"}
]
}
```
+Terminal runs also write `final.json` before `manifest.json`. The final marker is the seal; the manifest records checksums for the sealed bundle, including `final.json`.
+
## Adapters
**Adapters** connect Noēsis to your existing agent runtimes. They're simply callables that Noēsis wraps with cognition:
@@ -344,6 +348,7 @@ flowchart TD
A --> SUM[summary.json]
A --> ST[state.json]
A --> EV[events.jsonl]
+ A --> FIN[final.json]
```
1. A **task** creates an **episode**
diff --git a/docs/guides/adopting-noesis.mdx b/docs/guides/adopting-noesis.mdx
index 36c5dab..d6f2e4f 100644
--- a/docs/guides/adopting-noesis.mdx
+++ b/docs/guides/adopting-noesis.mdx
@@ -12,7 +12,7 @@ It's practical and opinionated: what to do, what to avoid, and how to fit Noēsi
## 1) What Noēsis actually adds
- **Episodes**: every run is a cognitive episode with a stable ID.
-- **Artifacts**: `events.jsonl`, `summary.json`, `state.json`, `manifest.json`, optional `prompts.jsonl`.
+- **Artifacts**: `events.jsonl`, `summary.json`, `state.json`, `final.json`, `manifest.json`, optional `prompts.jsonl`.
- **Cognitive phases**: `observe → intuition → interpret → plan → direction → governance → act → reflect → learn → terminate → insight → memory`.
- **Governance & direction**: policies that hint, intervene, or veto, with explicit flags in summary + events.
- **Determinism controls**: seeds, deterministic clocks/IDs, replay support.
@@ -46,6 +46,7 @@ You don’t need a perfect “episode store” on day one—pick **one** place t
events.jsonl
summary.json
state.json
+ final.json
manifest.json
```
diff --git a/docs/index.mdx b/docs/index.mdx
index 32ea5be..212182f 100644
--- a/docs/index.mdx
+++ b/docs/index.mdx
@@ -94,6 +94,7 @@ Every run produces a clean artifact structure:
summary.json # metrics and outcomes
state.json # current plan and episode state
events.jsonl # timeline with causal IDs
+ final.json # terminal seal marker
manifest.json # SHA-256 + size ledger for tamper evidence
learn.jsonl # optional learning payloads
```
diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx
index b48ed36..b73b1b4 100644
--- a/docs/quickstart.mdx
+++ b/docs/quickstart.mdx
@@ -106,6 +106,7 @@ Every episode creates a structured artifact directory (written under `.noesis/ep
summary.json # metrics + rollups
state.json # final state snapshot
events.jsonl # append-only timeline
+ final.json # terminal seal marker
manifest.json # SHA-256 + size ledger
learn.jsonl # optional learning payloads
_episodes/ # optional episode index (best-effort)
diff --git a/docs/reference/final.mdx b/docs/reference/final.mdx
new file mode 100644
index 0000000..f38ed53
--- /dev/null
+++ b/docs/reference/final.mdx
@@ -0,0 +1,144 @@
+---
+title: "Final schema"
+description: "Complete reference for the Noesis final.json seal marker."
+---
+
+The `final.json` artifact marks an episode run as terminal and sealed. It is small by design: consumers can check one file to decide whether a run may accept lifecycle mutations, then use `manifest.json` to verify the sealed bundle.
+
+Current final schema version: `final/2.0.0` ([JSON schema](/schema/final/2.0.0.json)).
+
+## Schema overview
+
+```json
+{
+ "schema_version": "final/2.0.0",
+ "episode_id": "ep_2024_abc123_s0",
+ "process_id": "proc_abc123",
+ "run_index": 1,
+ "finalized_at": "2024-01-15T10:30:05Z",
+ "outcome": "success",
+ "verification_status": "unverified"
+}
+```
+
+## Root fields
+
+
+Final marker schema. Currently `"final/2.0.0"`.
+
+
+
+Episode/run ID being finalized.
+
+
+
+Stable process lineage ID for the run.
+
+
+
+1-based run number within the process lineage.
+
+
+
+ISO 8601 timestamp when the final marker was written.
+
+
+
+Terminal outcome class.
+
+| Value | Meaning |
+| --- | --- |
+| `success` | The run reached a successful terminal state |
+| `failed` | Verification completed and the goal was not achieved |
+| `vetoed` | Governance enforced a veto before side effects completed |
+| `cancelled` | The run was cancelled |
+| `interrupted` | The run ended as interrupted |
+| `error` | The run ended due to an execution or runtime error |
+
+
+
+Verification result class.
+
+| Value | Meaning |
+| --- | --- |
+| `verified` | Verification assertions ran and passed or failed deterministically |
+| `unverified` | The run succeeded without verification assertions |
+| `not_applicable` | Verification does not apply, such as veto or error outcomes |
+
+
+## Sealing contract
+
+Noesis treats `final.json` as the canonical seal marker. Once it exists, lifecycle writes and resume attempts fail with `RunSealedError`. The artifact immutability guard also blocks later writes, except for the single `manifest.json` seal write that completes finalization.
+
+Terminal sealing is ordered:
+
+1. Write `final.json`.
+2. Write `manifest.json`, including a checksum entry for `final.json`.
+
+If manifest writing fails, Noesis removes `final.json` so the episode is not left in a falsely sealed state. Double-seal attempts raise `ImmutabilityError` and do not mutate the existing final marker or manifest.
+
+## Lifecycle constraints
+
+Terminal runs include both `final.json` and `manifest.json`. This covers successful runs, failed verification, errors, and enforced vetoes that terminate the episode.
+
+Paused runs are intentionally unsealed. For example, `governance_pause_on_veto=true` emits `run.interrupt` and `run.checkpoint` lifecycle events, but does not write `final.json` or `manifest.json` until the run continues or terminates.
+
+## Examples
+
+### Successful unverified run
+
+```json
+{
+ "schema_version": "final/2.0.0",
+ "episode_id": "ep_2024_success_s0",
+ "process_id": "proc_success",
+ "run_index": 1,
+ "finalized_at": "2024-01-15T10:30:05Z",
+ "outcome": "success",
+ "verification_status": "unverified"
+}
+```
+
+### Enforced veto
+
+```json
+{
+ "schema_version": "final/2.0.0",
+ "episode_id": "ep_2024_veto_s0",
+ "process_id": "proc_veto",
+ "run_index": 1,
+ "finalized_at": "2024-01-15T10:30:05Z",
+ "outcome": "vetoed",
+ "verification_status": "not_applicable"
+}
+```
+
+## Reading final markers
+
+### File access
+
+```bash
+EP_DIR=$(noesis view ep_abc123 --json | jq -r '.episode_dir')
+jq '.outcome, .verification_status' "$EP_DIR/final.json"
+```
+
+### Sealed check
+
+```python
+from pathlib import Path
+
+
+def is_sealed(episode_dir: Path) -> bool:
+ return (episode_dir / "final.json").exists()
+```
+
+## Next steps
+
+
+
+ Learn how final markers and manifests seal evidence bundles.
+
+
+ See lifecycle links for terminal and non-terminal runs.
+
+