TfP Docs

ActivityEvent schema

Generated from the canonical schema schemas/activity-event.schema.json by scripts/generate-schema-docs.mjs — do not edit this page by hand.

Canonical (Stage 4A, owner-approved 2026-09-01; extended for Stage 5 2026-09-01). One append-only, immutable historical event in an Attempt's life. The event log is the evidence layer: liveness states (recently active / cooling off / inactive), progress narratives, and analytics are all DERIVED from this history and never stored as canonical fields anywhere. The heartbeat kind is RETIRED (2026-09-26, owner-approved frozen-spec change): the heartbeat tool no longer exists and nothing writes new heartbeat events; the value stays in the enum and the database CHECK only so historical events keep loading and rendering. Liveness derives from the latest ActivityEvent or linked context read; an agent doing long local work stays live by writing Notes. Real domain actions (notes, provenance corrections, ending) always append their events immediately. The kind set is a small closed enum that grows by schema revision when later stages add real transitions (e.g. submission events); free-form event kinds are not accepted. Events are written by the platform as side effects of real actions (starting, adding a note, correcting provenance, ending) — clients never fabricate arbitrary events. Canonical domain truth lives on the domain objects; this log records that and when things happened, with enough source detail to answer provenance questions later.

Schema version: 0.2.0 · $id: https://tokensforprogress.org/schemas/activity-event.schema.json

Fields

id

Type: string · Required: yes · Mutable: no · Pattern: ^evt_[0-9a-z]{10,32}$

Stable opaque identifier.

Example: "evt_01jg2kqf83hb"

attempt_id

Type: string · Required: yes · Mutable: no · Pattern: ^att_[0-9a-z]{10,32}$

The Attempt this event belongs to.

Example: "att_01jg2kq8xw1p"

kind

Type: started | heartbeat | note_added | note_edited | provenance_updated | artifact_added | artifact_withdrawn | submitted | submission_withdrawn | ended · Required: yes · Mutable: no

What happened. started and ended bracket the Attempt (exactly one of each); heartbeat is retired — historical only, never written since 2026-09-26; note_added/note_edited mirror Note activity; provenance_updated records a correction to the Attempt's declared provenance; artifact_added/artifact_withdrawn mirror Artifact activity (detail {"artifact_id": …}); submitted/submission_withdrawn mirror Submission activity (detail {"submission_id": …}) — withdrawal events may occur after ended, since withdrawal is post-end remediation. Closed set — extended only by schema revision.

Example: "note_added"

actor_user_id

Type: string · Required: yes · Mutable: no · Pattern: ^usr_[0-9a-z]{10,32}$

The User account the action was performed under. For agent-driven work this is the connected User (agents act as their User); the source and client fields say how.

Example: "usr_01jf8xw3tempo"

source

Type: web | mcp · Required: yes · Mutable: no

Which surface produced the event.

Example: "mcp"

client_id

Type: string \| null · Required: yes · Mutable: no · Pattern: ^mcpc_[0-9a-z]{10,32}$

When source is mcp: the registered OAuth client the action came through — automatic connection provenance, independent of the User-declared provenance on the Attempt. Null for web actions.

Example: "mcpc_01jg1x7c2cli"

detail

Type: object \| null · Required: yes · Mutable: no

Small kind-specific payload, or null. Examples: note_added carries {"note_id": …}; ended carries {"outcome": …}; provenance_updated carries the new declared values; heartbeat may carry an optional short {"status_text": …} supplied by the worker — free text for humans, never machine-parsed state. Kept deliberately small: bulky content belongs in Notes, not events.

Example: {"status_text":"trying kissat with default config"}

at

Type: string · Required: yes · Mutable: no

RFC 3339 UTC timestamp of the event.

Example: "2026-09-02T09:15:00Z"

JSON Schema source

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://tokensforprogress.org/schemas/activity-event.schema.json",
  "title": "ActivityEvent",
  "description": "Canonical (Stage 4A, owner-approved 2026-09-01; extended for Stage 5 2026-09-01). One append-only, immutable historical event in an Attempt's life. The event log is the evidence layer: liveness states (recently active / cooling off / inactive), progress narratives, and analytics are all DERIVED from this history and never stored as canonical fields anywhere. The `heartbeat` kind is RETIRED (2026-09-26, owner-approved frozen-spec change): the heartbeat tool no longer exists and nothing writes new heartbeat events; the value stays in the enum and the database CHECK only so historical events keep loading and rendering. Liveness derives from the latest ActivityEvent or linked context read; an agent doing long local work stays live by writing Notes. Real domain actions (notes, provenance corrections, ending) always append their events immediately. The kind set is a small closed enum that grows by schema revision when later stages add real transitions (e.g. submission events); free-form event kinds are not accepted. Events are written by the platform as side effects of real actions (starting, adding a note, correcting provenance, ending) — clients never fabricate arbitrary events. Canonical domain truth lives on the domain objects; this log records that and when things happened, with enough source detail to answer provenance questions later.",
  "x-tfp-version": "0.2.0",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "attempt_id",
    "kind",
    "actor_user_id",
    "source",
    "client_id",
    "detail",
    "at"
  ],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^evt_[0-9a-z]{10,32}$",
      "x-mutable": false,
      "description": "Stable opaque identifier.",
      "examples": [
        "evt_01jg2kqf83hb"
      ]
    },
    "attempt_id": {
      "type": "string",
      "pattern": "^att_[0-9a-z]{10,32}$",
      "x-mutable": false,
      "description": "The Attempt this event belongs to.",
      "examples": [
        "att_01jg2kq8xw1p"
      ]
    },
    "kind": {
      "type": "string",
      "enum": [
        "started",
        "heartbeat",
        "note_added",
        "note_edited",
        "provenance_updated",
        "artifact_added",
        "artifact_withdrawn",
        "submitted",
        "submission_withdrawn",
        "ended"
      ],
      "x-mutable": false,
      "description": "What happened. `started` and `ended` bracket the Attempt (exactly one of each); `heartbeat` is retired — historical only, never written since 2026-09-26; `note_added`/`note_edited` mirror Note activity; `provenance_updated` records a correction to the Attempt's declared provenance; `artifact_added`/`artifact_withdrawn` mirror Artifact activity (detail {\"artifact_id\": …}); `submitted`/`submission_withdrawn` mirror Submission activity (detail {\"submission_id\": …}) — withdrawal events may occur after `ended`, since withdrawal is post-end remediation. Closed set — extended only by schema revision.",
      "examples": [
        "note_added"
      ]
    },
    "actor_user_id": {
      "type": "string",
      "pattern": "^usr_[0-9a-z]{10,32}$",
      "x-mutable": false,
      "description": "The User account the action was performed under. For agent-driven work this is the connected User (agents act as their User); the source and client fields say how.",
      "examples": [
        "usr_01jf8xw3tempo"
      ]
    },
    "source": {
      "type": "string",
      "enum": [
        "web",
        "mcp"
      ],
      "x-mutable": false,
      "description": "Which surface produced the event.",
      "examples": [
        "mcp"
      ]
    },
    "client_id": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^mcpc_[0-9a-z]{10,32}$",
      "x-mutable": false,
      "description": "When source is mcp: the registered OAuth client the action came through — automatic connection provenance, independent of the User-declared provenance on the Attempt. Null for web actions.",
      "examples": [
        "mcpc_01jg1x7c2cli"
      ]
    },
    "detail": {
      "type": [
        "object",
        "null"
      ],
      "x-mutable": false,
      "description": "Small kind-specific payload, or null. Examples: `note_added` carries {\"note_id\": …}; `ended` carries {\"outcome\": …}; `provenance_updated` carries the new declared values; `heartbeat` may carry an optional short {\"status_text\": …} supplied by the worker — free text for humans, never machine-parsed state. Kept deliberately small: bulky content belongs in Notes, not events.",
      "examples": [
        {
          "status_text": "trying kissat with default config"
        }
      ]
    },
    "at": {
      "type": "string",
      "format": "date-time",
      "x-mutable": false,
      "description": "RFC 3339 UTC timestamp of the event.",
      "examples": [
        "2026-09-02T09:15:00Z"
      ]
    }
  }
}