Content
The record itself: the meta-model brief (get_schema tells a client which item types, attributes, relationships and states exist, so read it before authoring), search and reads over items and documents, relationship traversal, and authoring. Every write lands as a proposal a named human reviews.
get_schema
Scope: read
Return the meta-model brief for the current project: item type categories, every item type with its attributes (definition and per-type assignment, including formula expressions), every relationship type with its labels, flags and allowed endpoints, every lifecycle with its states and transitions (signature, separation of duties, endorsement slots, presentation), and the role catalog with each role's id. These slugs and ids are what config actions name — read them here, never guess them; a ROLE: principal takes the role's id, not its name. Optional fields appear only when set: an absent flag is false, an absent value is unset. The permission cells are the separate scope 'permission-rules' (project administrators only, not included in 'all'), filterable by principal, verb, itemType, state and transition; each rule's ruleId is what PERMISSION_RULE_EDIT and PERMISSION_RULE_DELETE take.
Input schema
{
"type": "object",
"properties": {
"scope": {
"type": "string",
"enum": [
"all",
"item-types",
"relationship-types",
"lifecycles",
"roles",
"permission-rules"
],
"description": "Which slice of the schema to return. Default 'all' (every slice except permission-rules).",
"default": "all"
},
"principal": {
"type": "string",
"description": "permission-rules only: keep rules granted to this principal token, e.g. 'ROLE:quality_manager'."
},
"verb": {
"type": "string",
"description": "permission-rules only: keep rules for this verb, e.g. 'transition.fire'."
},
"itemType": {
"type": "string",
"description": "permission-rules only: keep rules covering this item type slug (unscoped rules included)."
},
"state": {
"type": "string",
"description": "permission-rules only: keep rules covering this state slug (unscoped rules included)."
},
"transition": {
"type": "string",
"description": "permission-rules only: keep rules covering this transition slug (unscoped rules included)."
}
},
"required": []
}search_items
Scope: read
Full-text search over items in the current project. Matches whole words (with stemming) across an item's number, title and description, and separately across the text of its LiveDoc documents. Supports "quoted phrases", OR, and -excluded terms. Returns `matches` (the item's own metadata matched) and `documentMatches` (the body of one of its documents matched), each ranked, each with a snippet showing the match. Use this when the user describes an item by content rather than knowing its number (e.g. 'find requirements about calibration').
Input schema
{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "What to search for. Whole words, stemmed and case-insensitive; \"quoted\" for a phrase, OR between alternatives, -term to exclude."
},
"itemType": {
"type": "string",
"description": "Optional item type id to restrict the search"
},
"limit": {
"type": "integer",
"description": "Maximum matches to return per list. Defaults to 20, max 100.",
"default": 20
}
},
"required": [
"query"
]
}list_items
Scope: read
List items in the current project, optionally filtered by item type. Returns summary rows (number, item type, title, lifecycle state). Use this to find items by category or to scan what exists in a section of the project. For large projects, supply itemType to narrow the result.
Input schema
{
"type": "object",
"properties": {
"itemType": {
"type": "string",
"description": "Optional item type id (e.g. 'design-input'). If omitted, lists items of every type."
},
"limit": {
"type": "integer",
"description": "Maximum number of items to return. Defaults to 50, max 200.",
"default": 50
}
},
"required": []
}get_item
Scope: read
Fetch a single item by its number (e.g. UN-007) or UUID. Returns id, number, item type, lifecycle state, seq, and attributes as the item's page shows them, calculated values included. calculatedAttributes names each calculated field with its formula kind and whether its value is frozen (non-DRAFT) — calculated fields cannot be edited; change their inputs instead. A calculated field missing from attributes has no value (an input is unset or the formula failed). provenance says who produced this version, as USER, AI or SYSTEM, from which AI proposal or item action, and its changeReason {type, payload}. Pass history:true for every version newest first with the same record (historyLimit, default 20), or asOfSeq to read the version in effect at that seq — a past version shows its stored values, and a past DRAFT has no calculated values. pendingProposalCount counts changes proposed for this item and not yet accepted — list them with list_pending_proposals.
Input schema
{
"type": "object",
"properties": {
"numberOrId": {
"type": "string",
"description": "Item number (e.g. UN-007) or UUID"
},
"asOfSeq": {
"type": "integer",
"description": "Read the version in effect at this seq on the project timeline (the latest at or below it)."
},
"history": {
"type": "boolean",
"description": "Include the version history, newest first. Default false."
},
"historyLimit": {
"type": "integer",
"description": "At most this many versions in the history. Default 20, max 200."
}
},
"required": [
"numberOrId"
]
}read_livedoc
Scope: read
Read an item's LiveDoc (rich-text document). For a structural edit (LIVEDOC_SECTION_INSERT / LIVEDOC_SECTION_REPLACE), work in two steps: first call with format='outline' to get the structure and each block's `blockId` (small, never truncates), then — when you need a section's actual text — call again with blockIds=['…'] or sectionByHeadingBlockId='…' to fetch just those blocks in full. Use format='plain-text' (default) to read the body, or format='json' for the whole raw document (may truncate on a large doc).
Input schema
{
"type": "object",
"properties": {
"itemNumberOrId": {
"type": "string",
"description": "Item number (e.g. SOP-001) or UUID"
},
"format": {
"type": "string",
"enum": [
"plain-text",
"outline",
"json"
],
"description": "'plain-text' (default) for readable body text; 'outline' for a compact block index with blockIds; 'json' for the whole raw ProseMirror document.",
"default": "plain-text"
},
"blockIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Fetch the FULL content of only these top-level blocks (blockIds from a prior format='outline' call). Small and never truncates. Overrides format."
},
"sectionByHeadingBlockId": {
"type": "string",
"description": "Fetch a heading block plus its body — every block until the next heading of equal/higher level. Use the heading's blockId from format='outline'."
},
"maxChars": {
"type": "integer",
"description": "Plain-text only: max characters to return. Defaults to 16000, max 32000.",
"default": 16000
}
},
"required": [
"itemNumberOrId"
]
}get_relationships
Scope: read
Return one-hop relationships (graph edges) for a given item — items it derives from, items that derive from it, or both. Each result includes the connected item's number, type, and the relationship type. Use this to navigate traceability (e.g. 'what verifies DI-005?', 'what user needs does this come from?'). Membership in a document is a link too: an item placed in a document links to it through the document-owned relationship type declared for the pair, else through the built-in 'embedded-in'. The documents that embed an item are on its downstream side, a document's members on its upstream side, each row carrying the section, the outline number and whether the document's record is current ('placed' false is a member the body no longer places). Those links are written by the document body and cannot be proposed as links — embed the item in the document instead. On an ordered relationship type the downstream item owns the sequence of its upstream links: those rows carry 'position' and a 1-based 'rank' (a downstream row carries its position in the other item's sequence, without a rank); reorder with RELATIONSHIP_SET. For multi-hop walks use `traverse` instead.
Input schema
{
"type": "object",
"properties": {
"itemNumberOrId": {
"type": "string",
"description": "Item number (e.g. UN-007) or UUID"
},
"direction": {
"type": "string",
"enum": [
"upstream",
"downstream",
"lateral",
"all"
],
"description": "Which side of the relationships to fetch. Default 'all'.",
"default": "all"
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"description": "Optional list of relationship type slugs to filter by ('embedded-in' is the built-in containment type)"
}
},
"required": [
"itemNumberOrId"
]
}traverse
Scope: read
Multi-hop graph walk from a starting item, following a sequence of relationship types. Returns the path tree (item at each level + the relationship that connected each pair) and a flat list of leaf items reached. Use this for traceability chains (e.g. 'from UN-007, follow derives-from then verified-by to find tests'). Membership in a document is a link too (ADR-043): a hop over a document-owned relationship type, or over the built-in 'embedded-in', reaches downstream the documents that embed the item and upstream a document's members in body order, each child carrying its section, outline number and whether the document's record of it is current. On an ordered relationship type, walking upstream from the downstream item (the owner of the sequence — a run's test cases) lists the members in order with a 1-based 'rank'; every edge of an ordered type carries its 'position'. Max 5 hops.
Input schema
{
"type": "object",
"properties": {
"startItemNumberOrId": {
"type": "string",
"description": "Item number or UUID to start from"
},
"relationshipTypes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Ordered list of relationship type slugs to follow (one per hop)"
},
"direction": {
"type": "string",
"enum": [
"upstream",
"downstream",
"lateral"
],
"description": "Which side of each relationship to follow. Default 'downstream'.",
"default": "downstream"
}
},
"required": [
"startItemNumberOrId",
"relationshipTypes"
]
}propose_item_actions
Scope: propose
Propose a batch of specific, typed changes yourself — without a predefined AI Action — when no AI Action fits the user's request. Nothing is written to the graph until the user accepts the batch in the review surface. Prefer `run_ai_action` when a listed AI Action matches (it carries a curated prompt and assembled context, so its output is higher-quality and consistent). Use `propose_item_actions` for the long tail: one-off edits, ad-hoc relationships, a quick observation — changes you can specify precisely from the conversation. You may call this several times in one turn — every call adds to the SAME batch (one review, one atomic accept). To link to an item you're creating in this batch, give the `ITEM_CREATE` a `localRef` (or use the one returned to you) and reference it from a later `RELATIONSHIP_ADD` via `{ "kind": "actionRef", "localRef": "…" }`. Returns `{batchId, stagedTotal, localRefs, reviewLink}` — always include the reviewLink in your reply, as a markdown link, so the user can open the review in one click. Allowed action types: ITEM_CREATE, ATTRIBUTE_EDIT, ITEM_DELETE, ITEM_REVISION_ABANDON, ITEM_STATE_TRANSITION, RELATIONSHIP_ADD, RELATIONSHIP_REMOVE, RELATIONSHIP_SET, RELATIONSHIP_SUSPECT_CLEAR, OBSERVATION_ADD, LIVEDOC_EDIT, LIVEDOC_SECTION_INSERT, LIVEDOC_SECTION_REPLACE, LIVEDOC_SECTION_DELETE, LIVEDOC_CONTENT_APPEND, LIVEDOC_ITEM_REFERENCE_INSERT, LIVEDOC_ITEM_EMBED_INSERT, QUERY_TABLE_INSERT. To draft or refine a document body, prefer the section-sized LiveDoc ops — LIVEDOC_CONTENT_APPEND to build a document up section by section (one call per section), LIVEDOC_SECTION_INSERT / LIVEDOC_SECTION_REPLACE to add or revise a section anchored by a block's blockId, and LIVEDOC_SECTION_DELETE to remove the block at a blockId. These have small payloads; avoid LIVEDOC_EDIT (which re-sends the entire document) unless you truly need a full rewrite. To place an item IN a document, propose LIVEDOC_ITEM_EMBED_INSERT on the document (an embed block, anchored by a blockId or appended). Containment is derived from the document body and is never a relationship: do not propose a RELATIONSHIP_ADD of type 'embedded-in' — it is refused. To create an item and place it in one go, give the ITEM_CREATE a localRef and name it in the embed's `localRef`. ITEM_DELETE **deletes** an item — it leaves every list, search result and traceability view, and no user can undo it. Only propose it for a DRAFT that was never released, i.e. something that should not exist at all. To retire a RELEASED item, propose ITEM_STATE_TRANSITION to the obsolete state instead; that keeps it visible, which is what a withdrawn controlled document requires. If a DRAFT sits above a RELEASED version, neither applies — say so rather than guessing, because deleting it would remove the released version too. ## Payload schema per type ### ITEM_CREATE ``` "payload": { "itemTypeId": "<item-type-slug>", // required — one of the allowed item types below "attributes": { "<attribute-slug>": <value> } // optional — keys are attribute slugs (formats below) } ``` Do NOT include `projectId` or `number` — the acceptance path injects the project context and generates a collision-free number automatically. ### ATTRIBUTE_EDIT ``` "payload": { "attributes": { "<attribute-slug>": <value> } // keys are attribute slugs; see the allowed attributes below } ``` Set `subjectItemNumber` to the item being edited. ### ITEM_DELETE ``` "payload": { "reason": "duplicate of DI-014" } ``` Set `subjectItemNumber` to the item to delete. **This deletes the item.** It disappears from every list, search result and traceability view, and there is no undo a user can reach. Only for a DRAFT that was never released — something that should not exist at all, like a duplicate or an item created by mistake. It is not how you retire something. Three different outcomes, and the wrong one is not recoverable: | The item… | Use | |---|---| | is a DRAFT and was never released | `ITEM_DELETE` — deletes it | | is RELEASED and is being withdrawn | `ITEM_STATE_TRANSITION` to the obsolete state — stays visible | | is a DRAFT above a RELEASED version, and the edits are unwanted | `ITEM_REVISION_ABANDON` — restores the release | If you are not certain the item was never released, do not propose this — check with `get_item` first, or propose the obsolete transition and let the reviewer decide. ### ITEM_REVISION_ABANDON ``` "payload": { "reason": "changes superseded by ECO-114" } ``` Set `subjectItemNumber` to the revision DRAFT to abandon. Throws away a revision draft and restores the RELEASED version it was cut from — the inverse of a revise. Use this when an item was released, someone revised it, and those edits are no longer wanted. The released revision comes back exactly as it was, including its document body, and downstream links the draft made suspect are re-verified. Only for a DRAFT that has a RELEASED version underneath it. If the item was never released there is no revision to abandon and `ITEM_DELETE` deletes it instead. ### ITEM_STATE_TRANSITION ``` "payload": { "transitionId": "submit", "comment": "ready for review", "revision": "A", // only for RELEASED-targeting transitions on a revisionable type "signatureId": "<uuid>" // required if the transition marks requiresSignature } ``` Set `subjectItemNumber` to the item being transitioned. Only item types under revision control (controlled artifacts — parts, specifications, SOPs, controlled documents) take a `revision`; software-domain items and one-off working records are tracked by version alone. Omit it when unsure — the transition is rejected with a clear error if one was required. ### RELATIONSHIP_ADD ``` "payload": { "relationshipTypeId": "<relationship-type-slug>", // one of the allowed types below "upstream": { "kind": "itemNumber", "value": "UN-007" }, "downstream": { "kind": "actionRef", "localRef": "NEW_1" } // OR itemNumber / itemId } ``` **REQUIRED: set `subjectItemNumber` to the downstream (owner) item** — a `RELATIONSHIP_ADD` with no owner is rejected. `upstream`/`downstream` must match the relationship type's allowed direction — the "Relationships — allowed types & directions" section below lists each allowed pair as `upstream → downstream`; put the upstream item in `upstream` and the downstream item in `downstream`. A reversed pair is rejected. The virtual type `embedded-in` (an item's containment in a document) is read-only and is refused here — to put an item in a document, propose LIVEDOC_ITEM_EMBED_INSERT on the document instead. ### RELATIONSHIP_REMOVE ``` "payload": { "relationshipId": "<uuid>", "rationale": "no longer required" } ``` ### RELATIONSHIP_SET ``` "payload": { "relationshipTypeId": "<relationship-type-slug>", "direction": "downstream", // where the TARGETS sit, relative to the subject "targets": [ { "kind": "itemNumber", "value": "HRM-004" } ] // OR itemId / actionRef; [] clears them all } ``` **REQUIRED: set `subjectItemNumber` to the item whose links you are replacing.** After this runs, the subject's links of this type in this direction are EXACTLY `targets` — links you omit are removed, links already present are left untouched. Use this instead of a `RELATIONSHIP_REMOVE` plus a `RELATIONSHIP_ADD` whenever you are *changing* which item something points at: it is one decision, one review card, and it cannot leave the item briefly unlinked. Use `RELATIONSHIP_ADD` when you are only adding to a set you do not otherwise know. `direction: "downstream"` means the links are `subject → target`; `"upstream"` means `target → subject`. Both must match the relationship type's allowed pair — see "Relationships — allowed types & directions" below. On an **ordered** relationship type (get_schema: `ordered: true`) the order of `targets` IS the order: the downstream item owns the sequence of its upstream links (a run's test cases, a container's members). To reorder, send `direction: "upstream"` with the owner as subject and the complete member list in the new order — members already linked stay linked and only move. Read the current order with get_relationships (`rank` on the owner's upstream rows). A document-owned type (`membership: "DOCUMENT"`) is ordered by the document body instead; move its embed there. ### RELATIONSHIP_SUSPECT_CLEAR ``` "payload": { "relationshipId": "<uuid>", "reason": "verified against DI-014 v3" } ``` ### OBSERVATION_ADD ``` "payload": { "severity": "MAJOR", // CRITICAL | MAJOR | MINOR | OBSERVATION "category": "Completeness", "body": "Acceptance criteria are absent…", "recommendation": "Add measurable pass/fail criteria referencing IEC 62304 §5.6", "anchor": { // OPTIONAL — omit for an item-level observation "attributeDefinitionId": "doc-livedoc-content", "selector": { "kind": "text", "blockId": "<block id from the doc>", "prefix": "<a few chars before>", "suffix": "<a few chars after>" }, "exact": "<the exact text you are commenting on, copied verbatim>" } } ``` Set `subjectItemNumber` to the item the observation is about. Include `anchor` only to pin the observation to a specific passage of that item's LiveDoc: copy the `blockId` and `exact` text verbatim from the document so the range can be re-located; add short `prefix`/`suffix` context when the quoted text repeats. ### LIVEDOC_EDIT ``` "payload": { "attributeId": "doc-livedoc-content", // the LiveDoc field id (use this unless told otherwise) "content": { "type": "doc", "content": [ … ] } // the FULL revised ProseMirror document } ``` Set `subjectItemNumber` to the item whose document you are editing. Rules: - `content` MUST be the ENTIRE revised document (`{"type":"doc","content":[…]}`), never a fragment, a diff, or plain text. Base it on the current document provided to you in context. - **Preserve every unchanged block's existing `blockId` attribute verbatim** — copy it onto the block in your output. Only brand-new blocks may omit `blockId`. This is what lets the reviewer see a precise redline (unchanged sections stay unchanged) instead of a wholesale rewrite. - Use only node/mark types already present in the current document. ### LIVEDOC_SECTION_INSERT ``` "payload": { "attributeId": "doc-livedoc-content", "anchorBlockId": "<blockId of an existing block to anchor to>", "position": "after", // or "before" — default "after" "content": [ {"type":"heading","attrs":{"level":2},"content":[…]}, {"type":"paragraph","content":[…]} ] } ``` Set `subjectItemNumber` to the item whose document you are editing. `content` is one or more ProseMirror blocks (a section — small, NOT the whole document). Anchor by a block's `blockId` from the current document in context; newly-inserted blocks may omit `blockId`. ### LIVEDOC_SECTION_REPLACE ``` "payload": { "attributeId": "doc-livedoc-content", "anchorBlockId": "<blockId of the block to replace>", "content": [ {"type":"paragraph","content":[…]} ] } ``` Replaces the single addressed block with `content`. Set `subjectItemNumber`. ### LIVEDOC_SECTION_DELETE ``` "payload": { "attributeId": "doc-livedoc-content", "anchorBlockId": "<blockId of the block to delete>" } ``` Deletes the single addressed block (no `content`). Anchor by a block's `blockId` from the current document in context. Set `subjectItemNumber`. ### LIVEDOC_CONTENT_APPEND ``` "payload": { "attributeId": "doc-livedoc-content", "content": [ {"type":"heading","attrs":{"level":2},"content":[…]}, {"type":"paragraph","content":[…]} ] } ``` Appends blocks to the end of the document — the natural op for drafting a document section by section (call it once per section) on an initially-empty LiveDoc. Set `subjectItemNumber`. ### LIVEDOC_ITEM_REFERENCE_INSERT ``` "payload": { "attributeId": "doc-livedoc-content", "itemNumber": "DI-014", // the item to link to "anchorBlockId": "<blockId>", // optional — appended at end if omitted "position": "after" // optional } ``` Inserts an inline linked reference to another item. Set `subjectItemNumber` to the document's item. ### LIVEDOC_ITEM_EMBED_INSERT ``` "payload": { "attributeId": "doc-livedoc-content", "itemNumber": "DI-014", // the item to embed — OR "localRef": "NEW_1" for an item created in this batch "anchorBlockId": "<blockId>", // optional — appended at end if omitted "position": "after" // optional } ``` Embeds the item in the document as a block, which is how an item becomes part of a document (its section and outline number follow from where the block sits). Set `subjectItemNumber` to the document's item. An item is embedded once per document; a second embed is refused. Containment is derived from the body and is never a relationship — never propose a RELATIONSHIP_ADD of type 'embedded-in'. ### QUERY_TABLE_INSERT ``` "payload": { "attributeId": "doc-livedoc-content", "anchorBlockId": "<blockId>", // optional — appended at end if omitted "position": "after", "content": { "type": "queryTable", "attrs": { … } } // the queryTable block node } ``` Inserts a live query-table block. Set `subjectItemNumber`.
Input schema
{
"type": "object",
"properties": {
"actions": {
"type": "array",
"description": "One or more ItemAction descriptors to stage into the current batch.",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "One of: ITEM_CREATE, ATTRIBUTE_EDIT, ITEM_DELETE, ITEM_REVISION_ABANDON, ITEM_STATE_TRANSITION, RELATIONSHIP_ADD, RELATIONSHIP_REMOVE, RELATIONSHIP_SET, RELATIONSHIP_SUSPECT_CLEAR, OBSERVATION_ADD, LIVEDOC_EDIT, LIVEDOC_SECTION_INSERT, LIVEDOC_SECTION_REPLACE, LIVEDOC_SECTION_DELETE, LIVEDOC_CONTENT_APPEND, LIVEDOC_ITEM_REFERENCE_INSERT, LIVEDOC_ITEM_EMBED_INSERT, QUERY_TABLE_INSERT"
},
"payload": {
"type": "object",
"description": "Fields per the type's payload schema (see tool description)."
},
"subjectItemNumber": {
"type": "string",
"description": "Item number the action targets (e.g. 'DI-014'); omit for ITEM_CREATE."
},
"localRef": {
"type": "string",
"description": "Optional handle (e.g. 'NEW_1') so later actions/calls can reference an item this action creates."
}
},
"required": [
"type"
]
}
}
},
"required": [
"actions"
]
}list_pending_proposals
Scope: read
List PENDING item proposals — changes proposed by people, AI Actions or agents that a human has not yet accepted or dismissed. Give itemNumberOrId for one item's (every version of it, and relationship proposals naming it from either end), or omit it for the whole branch, grouped by batch. Each proposal carries its type, subject, payload, author (USER or AI, and the chat it came from), when it was proposed, the seq it was authored against, and whether it refines an earlier proposal. Check this before proposing, so you do not propose a change that is already waiting; nothing here is in effect until accepted. Each batch carries its reviewLink — include it as a markdown link when pointing the user at a review. Pending configuration proposals are listed by list_config_change_requests.
Input schema
{
"type": "object",
"properties": {
"itemNumberOrId": {
"type": "string",
"description": "Only this item's pending proposals. Omit for the whole branch."
},
"limit": {
"type": "integer",
"description": "At most this many proposals. Default 100, max 500."
}
},
"required": []
}