{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://hxframework.org/psf/psf-v0.schema.json",
  "title": "PSF — Portable Session Format, v0.1 draft",
  "description": "A tool-agnostic interchange format for AI work-session records: one session per document, verbatim turns, tool calls, redaction markers, and provenance. Custody should never mean lock-in — any system holding the second codebase should be able to emit and ingest this shape.",
  "type": "object",
  "required": ["psf", "session", "turns", "provenance"],
  "properties": {
    "psf": {
      "description": "Format version, e.g. \"0.1\".",
      "type": "string"
    },
    "session": {
      "type": "object",
      "required": ["id", "startedAt"],
      "properties": {
        "id": { "type": "string", "description": "Stable session identifier, unique within the source tool." },
        "startedAt": { "type": "string", "format": "date-time" },
        "endedAt": { "type": "string", "format": "date-time" },
        "title": { "type": "string" },
        "workspace": {
          "type": "object",
          "description": "Where the work happened. The repository field is the scope boundary that makes a record a WORK record.",
          "properties": {
            "repository": { "type": "string", "description": "Canonical repo URL or identifier." },
            "branch": { "type": "string" },
            "path": { "type": "string", "description": "Working directory relative to the repository root." }
          }
        },
        "agent": {
          "type": "object",
          "properties": {
            "name": { "type": "string", "description": "Tool/agent name, e.g. \"claude-code\"." },
            "version": { "type": "string" },
            "model": { "type": "string" }
          }
        },
        "author": {
          "type": "object",
          "description": "Pseudonymous by default: a stable opaque id, resolvable only inside the custody system.",
          "properties": {
            "id": { "type": "string" },
            "display": { "type": "string" }
          }
        }
      }
    },
    "turns": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["role", "at"],
        "properties": {
          "role": { "enum": ["user", "assistant", "system", "tool"] },
          "at": { "type": "string", "format": "date-time" },
          "content": { "type": "string", "description": "Verbatim text. Omitted when redacted." },
          "redacted": {
            "type": "object",
            "description": "Present when content was removed. The record keeps the shape of the conversation even where the substance is withheld.",
            "required": ["reason"],
            "properties": {
              "reason": { "enum": ["secret", "pii", "policy", "author-request"] },
              "note": { "type": "string" }
            }
          },
          "toolCalls": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["name"],
              "properties": {
                "name": { "type": "string" },
                "input": { "description": "Tool input, JSON. May be redacted to null." },
                "output": { "description": "Tool output, JSON or string. May be redacted to null." },
                "redacted": { "type": "boolean" }
              }
            }
          }
        }
      }
    },
    "artifacts": {
      "type": "array",
      "description": "Links from the session to the receipts it produced — commits, pull requests, documents.",
      "items": {
        "type": "object",
        "required": ["kind", "ref"],
        "properties": {
          "kind": { "enum": ["commit", "pull-request", "issue", "document", "other"] },
          "ref": { "type": "string" }
        }
      }
    },
    "provenance": {
      "type": "object",
      "required": ["source", "exportedAt"],
      "properties": {
        "source": { "type": "string", "description": "The system that emitted this document." },
        "exportedAt": { "type": "string", "format": "date-time" },
        "contentHash": { "type": "string", "description": "Hash of the canonicalized turns array, so re-exports can be compared." }
      }
    }
  }
}
