ActivityEvent schema
Generated from the canonical schema
schemas/activity-event.schema.jsonbyscripts/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"
]
}
}
}