changedInput schema / anyOf
Previous value: -[
- {
- "additionalProperties": false,
- "description": "action=\"update\": Update editable fields on any node; each field is validated against what's valid for the node's type, so type-mismatched fields (e.g. category on a task) are rejected. A task's status may be set to open or backlog here, but claim, completion, and cancellation are REJECTED — use task(action=\"claim\") / task(action=\"complete\") so the claim and completion (with session + agent identity) are recorded atomically, or destructive(action=\"cancel_task\") so the destructive transition records a reason. Archiving a component is also rejected; use destructive(action=\"archive_component\") so active children cannot be orphaned. `parent_component_id` reparents across components in the same project. Surgical edits: pass `edits` (literal old_string→new_string replacements applied server-side) instead of `body` to fix a substring without resending the whole body — they're mutually exclusive, and a non-matching old_string errors rather than no-ops. Optional `expected_version` gives optimistic concurrency.",
- "properties": {
- "action": {
- "const": "update",
- "type": "string"
- },
- "anchor_files": {
- "anyOf": [
- {
- "anyOf": [
- {
- "not": {}
- },
- {
- "items": {
- "type": "string"
- },
- "type": "array"
- }
- ],
- "description": "Repo-relative file paths this note is about (e.g. 'src/auth/session.ts'). When someone later edits a file named here, this node surfaces to them. Set it on a lesson/risk/gap scoped to specific code so it resurfaces exactly when that code is touched."
- },
- {
- "type": "null"
- }
- ],
- "description": "(update only) knowledge or task nodes — repo-relative file paths this node is about; surfaces it when one is later edited (for a task: don't step on toes). Pass [] or null to clear."
- },
- "anchor_symbols": {
- "anyOf": [
- {
- "anyOf": [
- {
- "not": {}
- },
- {
- "items": {
- "type": "string"
- },
- "type": "array"
- }
- ],
- "description": "Symbol names this note is about — function / class / constant / env var (e.g. 'refreshToken', 'SESSION_TTL'). Same purpose as anchor_files: a precise hook so the note resurfaces when that symbol's code is touched."
- },
- {
- "type": "null"
- }
- ],
- "description": "(update only) knowledge or task nodes — symbol names this node is about (function / class / constant / env var). Pass [] or null to clear."
- },
- "assignee_user_id": {
- "anyOf": [
- {
- "format": "uuid",
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "description": "(update only) task nodes only — user uuid (not a shareable id)"
- },
- "body": {
- "description": "(update only) New markdown body (full replace) — mutually exclusive with edits. Write it in plain style: one idea per sentence, active voice, lead with the condition, one term per concept, exact technical names.",
- "type": [
- "string",
- "null"
- ]
- },
- "category": {
- "anyOf": [
- {
- "enum": [
- "decision",
- "architecture",
- "goal",
- "risk",
- "gap",
- "fyi",
- "opportunity",
- "priority",
- "lesson"
- ],
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "description": "(update only) knowledge-type nodes only"
- },
- "due_at": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "description": "(update only) knowledge nodes only — ISO-8601 deadline. Pass null to clear. Any active knowledge with a past due_at surfaces in stats.overdue_commitments."
- },
- "edits": {
- "description": "(update only) Literal string-replacements applied to the body server-side, in order. Mutually exclusive with `body`. Pair with an exact search: use a matching line as old_string.",
- "items": {
- "additionalProperties": false,
- "properties": {
- "new_string": {
- "description": "Replacement text.",
- "type": "string"
- },
- "old_string": {
- "description": "Exact substring to find in the current body.",
- "minLength": 1,
- "type": "string"
- },
- "replace_all": {
- "description": "Replace every occurrence (default: require a unique match).",
- "type": "boolean"
- }
- },
- "required": [
- "old_string",
- "new_string"
- ],
- "type": "object"
- },
- "minItems": 1,
- "type": "array"
- },
- "expected_version": {
- "description": "(update only) Optimistic concurrency: reject the write if the node's current version differs (it changed since you read it).",
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "expires_at": {
- "anyOf": [
- {
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "description": "(update only) knowledge nodes only — ISO-8601 expiry (date TTL). Pass null to clear. When it passes, the next health-check run with fix archives the node. For ephemeral `fyi`/temporary-note knowledge; decision/architecture retire by supersession instead."
- },
- "hidden": {
- "description": "(update only) Set true to HIDE this node from every retrieval surface (search, recall, lists) — the one explicit invisibility. It stays fetchable by its shareable id (shown with a HIDDEN banner), so hiding is never a delete. Use for a genuine mistake, expired ephemera, or a secret that was pasted in by accident. Set false to un-hide. On the hosted assistant this pauses for human approval.",
- "type": "boolean"
- },
- "node_id": {
- "description": "REQUIRED for update — shareable id of the node to edit (TASK-N/KNOW-N/COMP-N/DOC-N/NODE-N).",
- "type": "string"
- },
- "parent_component_id": {
- "anyOf": [
- {
- "description": "Shareable id: TASK-N (task), KNOW-N (knowledge), COMP-N (component), DOC-N (document), or NODE-N (any).",
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "description": "(update only) shareable id of the new parent component. Accepted for component, knowledge, task, and document nodes. null = move to project root (documents and components only). Same-project enforcement is implicit — short ids resolve only within the active project."
- },
- "priority": {
- "anyOf": [
- {
- "enum": [
- "P0",
- "P1",
- "P2",
- "P3"
- ],
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "description": "(update only) task nodes only — priority P0 (most urgent) … P3. Pass null to clear."
- },
- "privacy_bypass": {
- "description": "true writes despite a privacy flag on a public project. Only meaningful after a flag; the user decides whether the content can be public.",
- "type": "boolean"
- },
- "privacy_false_positive": {
- "description": "With privacy_bypass: true reports the privacy flag as wrong.",
- "type": "boolean"
- },
- "privacy_judgement_id": {
- "description": "With privacy_bypass: the judgement_id the privacy flag returned.",
- "type": "string"
- },
- "privacy_note": {
- "description": "With privacy_bypass: an optional short reason, kept with the bypass.",
- "maxLength": 500,
- "type": "string"
- },
- "project_slug": {
- "description": "Project slug, e.g. \"acme-app\" — list(what=\"projects\") returns yours.",
- "minLength": 1,
- "type": "string"
- },
- "source": {
- "anyOf": [
- {
- "maxLength": 500,
- "minLength": 1,
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "description": "(update only) knowledge nodes only — free-form provenance label. Pass null to clear."
- },
- "source_type": {
- "description": "(update only) knowledge nodes only — provenance / trust signal (human, claude, import).",
- "enum": [
- "human",
- "claude",
- "import"
- ],
- "type": "string"
- },
- "source_url": {
- "anyOf": [
- {
- "format": "uri",
- "type": "string"
- },
- {
- "type": "null"
- }
- ],
- "description": "(update only) knowledge nodes only — canonical URL for the source. Pass null to clear."
- },
- "status": {
- "description": "(update only) A task may be set to open or backlog here. Claim, completion, and cancellation must use the task lifecycle actions (claim, complete, cancel) so lifecycle records and agent identity remain atomic.",
- "enum": [
- "active",
- "archived",
- "backlog",
- "open",
- "in_progress",
- "done",
- "cancelled"
- ],
- "type": "string"
- },
- "title": {
- "description": "(update only) New title.",
- "maxLength": 300,
- "minLength": 1,
- "type": "string"
- },
- "topics": {
- "description": "(update only) knowledge or task nodes only",
- "items": {
- "type": "string"
- },
- "type": "array"
- }
- },
- "required": [
- "action",
- "project_slug",
- "node_id"
- ],
- "type": "object"
- },
- {
- "additionalProperties": false,
- "description": "action=\"move\": Move an EXISTING node between scopes — into a workspace (hiding it from trunk readers) or back to trunk (shared truth). Re-scopes by id; reversible, never deletes. Distinct from destructive(action=\"cherry_pick_workspace\") (which promotes to trunk WITH provenance) — This is a plain re-assignment in any direction.",
- "properties": {
- "action": {
- "const": "move",
- "type": "string"
- },
- "node_id": {
- "description": "REQUIRED for move — the node to move, a short-id (KNOW-N / TASK-N / DOC-N / COMP-N).",
- "type": "string"
- },
- "project_slug": {
- "description": "Project slug, e.g. \"acme-app\" — list(what=\"projects\") returns yours.",
- "minLength": 1,
- "type": "string"
- },
- "to": {
- "description": "REQUIRED for move — destination scope: 'trunk' for shared truth, or a workspace NAME (e.g. \"Billing\") or uuid to scope it there.",
- "minLength": 1,
- "type": "string"
- }
- },
- "required": [
- "action",
- "project_slug",
- "node_id",
- "to"
- ],
- "type": "object"
- },
- {
- "additionalProperties": false,
- "description": "action=\"link\": Create a typed semantic link between two nodes (src → dst). Idempotent: re-linking the same (src, dst, relation) returns the existing edge, no error.\n\nSome relations change the graph: `supersedes` retires dst (it stays findable, but comes back marked SUPERSEDED and ranked below live knowledge), and `expires-on` retires src later. Every relation except `relates-to` is a claim and needs a `reason`; `supersedes` also needs `evidence`. If you cannot state the reason, use `relates-to`.\n\n## What each link means — and what it does\n\nAn edge is not decoration. Some of these change the graph.\n\n| Relation | Direction | Use it when | What it actually does |\n|---|---|---|---|\n| `relates-to` | A → B | Someone who pulls up B should know A exists. The weakest signal — use it when nothing typed fits. | Nothing but a pointer. Both nodes stay active. |\n| `blocks` | child → parent | A must land before B can be completed or become true. Also the **subtask** relation. | Surfaces A as a subtask of B and drives B's progress. |\n| `fulfills` | task → knowledge | This task delivers or acts on that decision, goal, or risk. | Records that the work discharged the commitment. |\n| `caused-by` | A → B | B is the root cause of A. | Builds the causal chain a provenance walk follows. |\n| `contradicts` | A → B | Two facts conflict and you cannot tell which is true. | Flags it for a human. **Both stay active** — this is the honest \"I don't know\" edge. |\n| `supersedes` | new → old | The old node has become **wrong to act on** — an agent following it would now do the wrong thing. **Never** for something that is merely older: history that is still true must stay. | **Retires the old node.** It stays in the graph and stays findable, but it comes back marked `SUPERSEDED` and ranked below live knowledge — so nothing acts on it as current. |\n| `expires-on` | knowledge → task | This knowledge is only true until that task closes. | Retires the knowledge when the task completes — same marked-not-deleted treatment. |\n\n`mentions` is not linkable by hand — write the id into a body and it is created for you.",
- "properties": {
- "action": {
- "const": "link",
- "type": "string"
- },
- "dst_id": {
- "description": "REQUIRED for link — destination node shareable id.",
- "type": "string"
- },
- "evidence": {
- "description": "The VERBATIM line from the source that proves the claim — REQUIRED for `supersedes`. It must show the old state actually ENDED, not merely that something newer exists. \"We also added a read replica\" is additive and supersedes nothing; \"we've moved off DynamoDB entirely\" is a termination. If you cannot quote a termination, the correct relation is relates-to.",
- "maxLength": 4000,
- "minLength": 1,
- "type": "string"
- },
- "privacy_bypass": {
- "description": "true writes despite a privacy flag on a public project. Only meaningful after a flag; the user decides whether the content can be public.",
- "type": "boolean"
- },
- "privacy_false_positive": {
- "description": "With privacy_bypass: true reports the privacy flag as wrong.",
- "type": "boolean"
- },
- "privacy_judgement_id": {
- "description": "With privacy_bypass: the judgement_id the privacy flag returned.",
- "type": "string"
- },
- "privacy_note": {
- "description": "With privacy_bypass: an optional short reason, kept with the bypass.",
- "maxLength": 500,
- "type": "string"
- },
- "project_slug": {
- "description": "Project slug, e.g. \"acme-app\" — list(what=\"projects\") returns yours.",
- "minLength": 1,
- "type": "string"
- },
- "reason": {
- "description": "WHY this edge exists — REQUIRED for supersedes / contradicts / caused-by / blocks / fulfills. State the claim in your own words: for `supersedes`, name the standing state that ENDED (\"we moved the event store off DynamoDB to Postgres on Jul 13\"); for `caused-by`, name the causal mechanism. Not required for relates-to (the 'nothing typed fits' default) — if you cannot state a reason, that is the signal to use relates-to instead of forcing a typed edge.",
- "maxLength": 2000,
- "minLength": 1,
- "type": "string"
- },
- "relation": {
- "description": "REQUIRED for link — edge type, read src → dst. relates-to: a pointer, no claim. blocks: src (child) must land before dst (parent) can finish; the subtask edge. fulfills: src task delivers dst. caused-by: dst is src's root cause. contradicts: both stay active for a human to resolve. supersedes: retires dst as wrong to act on, not merely older. expires-on: src knowledge retires when dst task closes.",
- "enum": [
- "relates-to",
- "blocks",
- "fulfills",
- "contradicts",
- "supersedes",
- "caused-by",
- "expires-on"
- ],
- "type": "string"
- },
- "src_id": {
- "description": "REQUIRED for link — source node shareable id.",
- "type": "string"
- }
- },
- "required": [
- "action",
- "project_slug",
- "src_id",
- "dst_id",
- "relation"
- ],
- "type": "object"
- }
-]New value: +[
+ {
+ "additionalProperties": false,
+ "description": "action=\"update\": Update editable fields on any node; each field is validated against what's valid for the node's type, so type-mismatched fields (e.g. category on a task) are rejected. A task's status may be set to open or backlog here, but claim, completion, and cancellation are REJECTED — use task(action=\"claim\") / task(action=\"complete\") so the claim and completion (with session + agent identity) are recorded atomically, or destructive(action=\"cancel_task\") so the destructive transition records a reason. Archiving a component is also rejected; use destructive(action=\"archive_component\") so active children cannot be orphaned. `parent_component_id` reparents across components in the same project. Surgical edits: pass `edits` (literal old_string→new_string replacements applied server-side) instead of `body` to fix a substring without resending the whole body — they're mutually exclusive, and a non-matching old_string errors rather than no-ops. Optional `expected_version` gives optimistic concurrency.",
+ "properties": {
+ "action": {
+ "const": "update",
+ "type": "string"
+ },
+ "anchor_files": {
+ "anyOf": [
+ {
+ "anyOf": [
+ {
+ "not": {}
+ },
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ ],
+ "description": "Repo-relative file paths this note is about (e.g. 'src/auth/session.ts'). When someone later edits a file named here, this node surfaces to them. Set it on a lesson/risk/gap scoped to specific code so it resurfaces exactly when that code is touched."
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "(update only) knowledge or task nodes — repo-relative file paths this node is about; surfaces it when one is later edited (for a task: don't step on toes). Pass [] or null to clear."
+ },
+ "anchor_symbols": {
+ "anyOf": [
+ {
+ "anyOf": [
+ {
+ "not": {}
+ },
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ ],
+ "description": "Symbol names this note is about — function / class / constant / env var (e.g. 'refreshToken', 'SESSION_TTL'). Same purpose as anchor_files: a precise hook so the note resurfaces when that symbol's code is touched."
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "(update only) knowledge or task nodes — symbol names this node is about (function / class / constant / env var). Pass [] or null to clear."
+ },
+ "assignee_user_id": {
+ "anyOf": [
+ {
+ "format": "uuid",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "(update only) task nodes only — user uuid (not a shareable id)"
+ },
+ "body": {
+ "description": "(update only) New markdown body (full replace) — mutually exclusive with edits. Write it in plain style: one idea per sentence, active voice, lead with the condition, one term per concept, exact technical names.",
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "category": {
+ "anyOf": [
+ {
+ "enum": [
+ "decision",
+ "architecture",
+ "goal",
+ "risk",
+ "gap",
+ "fyi",
+ "opportunity",
+ "priority",
+ "lesson"
+ ],
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "(update only) knowledge-type nodes only"
+ },
+ "due_at": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "(update only) knowledge nodes only — ISO-8601 deadline. Pass null to clear. Any active knowledge with a past due_at surfaces in stats.overdue_commitments."
+ },
+ "edits": {
+ "description": "(update only) Literal string-replacements applied to the body server-side, in order. Mutually exclusive with `body`. Pair with an exact search: use a matching line as old_string.",
+ "items": {
+ "additionalProperties": false,
+ "properties": {
+ "new_string": {
+ "description": "Replacement text.",
+ "type": "string"
+ },
+ "old_string": {
+ "description": "Exact substring to find in the current body.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "replace_all": {
+ "description": "Replace every occurrence (default: require a unique match).",
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "old_string",
+ "new_string"
+ ],
+ "type": "object"
+ },
+ "minItems": 1,
+ "type": "array"
+ },
+ "expected_version": {
+ "description": "(update only) Optimistic concurrency: reject the write if the node's current version differs (it changed since you read it).",
+ "exclusiveMinimum": 0,
+ "type": "integer"
+ },
+ "expires_at": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "(update only) knowledge nodes only — ISO-8601 expiry (date TTL). Pass null to clear. When it passes, the next health-check run with fix archives the node. For ephemeral `fyi`/temporary-note knowledge; decision/architecture retire by supersession instead."
+ },
+ "hidden": {
+ "description": "(update only) Set true to HIDE this node from every retrieval surface (search, recall, lists) — the one explicit invisibility. It stays fetchable by its shareable id (shown with a HIDDEN banner), so hiding is never a delete. Use for a genuine mistake, expired ephemera, or a secret that was pasted in by accident. Set false to un-hide. On the hosted assistant this pauses for human approval.",
+ "type": "boolean"
+ },
+ "node_id": {
+ "description": "REQUIRED for update — shareable id of the node to edit (TASK-N/KNOW-N/COMP-N/DOC-N/NODE-N).",
+ "type": "string"
+ },
+ "parent_component_id": {
+ "anyOf": [
+ {
+ "description": "Shareable id: TASK-N (task), KNOW-N (knowledge), COMP-N (component), DOC-N (document), or NODE-N (any).",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "(update only) shareable id of the new parent component. Accepted for component, knowledge, task, and document nodes. null = move to project root (documents and components only). Same-project enforcement is implicit — short ids resolve only within the active project."
+ },
+ "priority": {
+ "anyOf": [
+ {
+ "enum": [
+ "P0",
+ "P1",
+ "P2",
+ "P3"
+ ],
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "(update only) task nodes only — priority P0 (most urgent) … P3. Pass null to clear."
+ },
+ "privacy_bypass": {
+ "description": "true writes despite a privacy flag. Only meaningful after a flag; the user decides whether the content can be written.",
+ "type": "boolean"
+ },
+ "privacy_false_positive": {
+ "description": "With privacy_bypass: true reports the privacy flag as wrong.",
+ "type": "boolean"
+ },
+ "privacy_judgement_id": {
+ "description": "With privacy_bypass: the judgement_id the privacy flag returned.",
+ "type": "string"
+ },
+ "privacy_note": {
+ "description": "With privacy_bypass: an optional short reason, kept with the bypass.",
+ "maxLength": 500,
+ "type": "string"
+ },
+ "project_slug": {
+ "description": "Project slug, e.g. \"acme-app\" — list(what=\"projects\") returns yours.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "source": {
+ "anyOf": [
+ {
+ "maxLength": 500,
+ "minLength": 1,
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "(update only) knowledge nodes only — free-form provenance label. Pass null to clear."
+ },
+ "source_type": {
+ "description": "(update only) knowledge nodes only — provenance / trust signal (human, claude, import).",
+ "enum": [
+ "human",
+ "claude",
+ "import"
+ ],
+ "type": "string"
+ },
+ "source_url": {
+ "anyOf": [
+ {
+ "format": "uri",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "(update only) knowledge nodes only — canonical URL for the source. Pass null to clear."
+ },
+ "status": {
+ "description": "(update only) A task may be set to open or backlog here. Claim, completion, and cancellation must use the task lifecycle actions (claim, complete, cancel) so lifecycle records and agent identity remain atomic.",
+ "enum": [
+ "active",
+ "archived",
+ "backlog",
+ "open",
+ "in_progress",
+ "done",
+ "cancelled"
+ ],
+ "type": "string"
+ },
+ "title": {
+ "description": "(update only) New title.",
+ "maxLength": 300,
+ "minLength": 1,
+ "type": "string"
+ },
+ "topics": {
+ "description": "(update only) knowledge or task nodes only",
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ },
+ "required": [
+ "action",
+ "project_slug",
+ "node_id"
+ ],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "description": "action=\"move\": Move an EXISTING node between scopes — into a workspace (hiding it from trunk readers) or back to trunk (shared truth). Re-scopes by id; reversible, never deletes. Distinct from destructive(action=\"cherry_pick_workspace\") (which promotes to trunk WITH provenance) — This is a plain re-assignment in any direction.",
+ "properties": {
+ "action": {
+ "const": "move",
+ "type": "string"
+ },
+ "node_id": {
+ "description": "REQUIRED for move — the node to move, a short-id (KNOW-N / TASK-N / DOC-N / COMP-N).",
+ "type": "string"
+ },
+ "project_slug": {
+ "description": "Project slug, e.g. \"acme-app\" — list(what=\"projects\") returns yours.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "to": {
+ "description": "REQUIRED for move — destination scope: 'trunk' for shared truth, or a workspace NAME (e.g. \"Billing\") or uuid to scope it there.",
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "required": [
+ "action",
+ "project_slug",
+ "node_id",
+ "to"
+ ],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "description": "action=\"link\": Create a typed semantic link between two nodes (src → dst). Idempotent: re-linking the same (src, dst, relation) returns the existing edge, no error.\n\nSome relations change the graph: `supersedes` retires dst (it stays findable, but comes back marked SUPERSEDED and ranked below live knowledge), and `expires-on` retires src later. Every relation except `relates-to` is a claim and needs a `reason`; `supersedes` also needs `evidence`. If you cannot state the reason, use `relates-to`.\n\n## What each link means — and what it does\n\nAn edge is not decoration. Some of these change the graph.\n\n| Relation | Direction | Use it when | What it actually does |\n|---|---|---|---|\n| `relates-to` | A → B | Someone who pulls up B should know A exists. The weakest signal — use it when nothing typed fits. | Nothing but a pointer. Both nodes stay active. |\n| `blocks` | child → parent | A must land before B can be completed or become true. Also the **subtask** relation. | Surfaces A as a subtask of B and drives B's progress. |\n| `fulfills` | task → knowledge | This task delivers or acts on that decision, goal, or risk. | Records that the work discharged the commitment. |\n| `caused-by` | A → B | B is the root cause of A. | Builds the causal chain a provenance walk follows. |\n| `contradicts` | A → B | Two facts conflict and you cannot tell which is true. | Flags it for a human. **Both stay active** — this is the honest \"I don't know\" edge. |\n| `supersedes` | new → old | The old node has become **wrong to act on** — an agent following it would now do the wrong thing. **Never** for something that is merely older: history that is still true must stay. | **Retires the old node.** It stays in the graph and stays findable, but it comes back marked `SUPERSEDED` and ranked below live knowledge — so nothing acts on it as current. |\n| `expires-on` | knowledge → task | This knowledge is only true until that task closes. | Retires the knowledge when the task completes — same marked-not-deleted treatment. |\n\n`mentions` is not linkable by hand — write the id into a body and it is created for you.",
+ "properties": {
+ "action": {
+ "const": "link",
+ "type": "string"
+ },
+ "dst_id": {
+ "description": "REQUIRED for link — destination node shareable id.",
+ "type": "string"
+ },
+ "evidence": {
+ "description": "The VERBATIM line from the source that proves the claim — REQUIRED for `supersedes`. It must show the old state actually ENDED, not merely that something newer exists. \"We also added a read replica\" is additive and supersedes nothing; \"we've moved off DynamoDB entirely\" is a termination. If you cannot quote a termination, the correct relation is relates-to.",
+ "maxLength": 4000,
+ "minLength": 1,
+ "type": "string"
+ },
+ "privacy_bypass": {
+ "description": "true writes despite a privacy flag. Only meaningful after a flag; the user decides whether the content can be written.",
+ "type": "boolean"
+ },
+ "privacy_false_positive": {
+ "description": "With privacy_bypass: true reports the privacy flag as wrong.",
+ "type": "boolean"
+ },
+ "privacy_judgement_id": {
+ "description": "With privacy_bypass: the judgement_id the privacy flag returned.",
+ "type": "string"
+ },
+ "privacy_note": {
+ "description": "With privacy_bypass: an optional short reason, kept with the bypass.",
+ "maxLength": 500,
+ "type": "string"
+ },
+ "project_slug": {
+ "description": "Project slug, e.g. \"acme-app\" — list(what=\"projects\") returns yours.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "reason": {
+ "description": "WHY this edge exists — REQUIRED for supersedes / contradicts / caused-by / blocks / fulfills. State the claim in your own words: for `supersedes`, name the standing state that ENDED (\"we moved the event store off DynamoDB to Postgres on Jul 13\"); for `caused-by`, name the causal mechanism. Not required for relates-to (the 'nothing typed fits' default) — if you cannot state a reason, that is the signal to use relates-to instead of forcing a typed edge.",
+ "maxLength": 2000,
+ "minLength": 1,
+ "type": "string"
+ },
+ "relation": {
+ "description": "REQUIRED for link — edge type, read src → dst. relates-to: a pointer, no claim. blocks: src (child) must land before dst (parent) can finish; the subtask edge. fulfills: src task delivers dst. caused-by: dst is src's root cause. contradicts: both stay active for a human to resolve. supersedes: retires dst as wrong to act on, not merely older. expires-on: src knowledge retires when dst task closes.",
+ "enum": [
+ "relates-to",
+ "blocks",
+ "fulfills",
+ "contradicts",
+ "supersedes",
+ "caused-by",
+ "expires-on"
+ ],
+ "type": "string"
+ },
+ "src_id": {
+ "description": "REQUIRED for link — source node shareable id.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "action",
+ "project_slug",
+ "src_id",
+ "dst_id",
+ "relation"
+ ],
+ "type": "object"
+ }
+]