Skip to main content
Glama

Log a code change

log_change

Record codebase changes with reasoning to preserve decision history. Log renames, rejections, and reverts so future agents see what was tried and why.

Instructions

Record a change to a codebase entity.

Call this immediately after making any meaningful change. The event is written to the local SQLite store and returned with its assigned id and timestamp. If the reasoning fails the quality validator (empty, too short, or a generic placeholder), or the entity_path doesn't match the usual shape for its entity_type, the result includes a warnings array — the event is still stored.

Renames: pass the new path in entity_path, set change_type="rename", and pass the old path in rename_from. Selvedge then writes two events — a rename on the old path and a create on the new path with metadata.renamed_from set — so the entity's history follows it. Example:

log_change(
    entity_path="src/auth/session.py::login",   # new path
    change_type="rename",
    rename_from="src/auth.py::login",            # old path
    entity_type="function",
    reasoning="Split auth.py into an auth/ package; login moved.",
)

Rejections: when you consider an approach and decide against it WITHOUT writing the change, record the verdict with change_type="reject" — the abandoned path is a first-class event, and the next agent's prior_attempts query finds it as a high-confidence ("exact") row instead of re-deriving the dead end. Name what was rejected AND what was chosen instead, and record the condition that would invalidate the verdict. Example:

log_change(
    entity_path="users.card_pan",
    change_type="reject",
    entity_type="column",
    reasoning="Rejected storing raw card PANs on the user row — "
              "went with provider tokens instead; PANs in our own "
              "DB put us in PCI scope.",
    stale_when="payment provider changed",
    expires_when="entity:deps/stripe:changes",
)

Use change_type="revert" for the sibling case — the change WAS written and then rolled back (clearer than a plain remove).

Superseding a reverted decision: when a reverted change becomes correct again (the constraint that killed it no longer holds), do NOT delete or edit history — log with change_type="supersede" and the reason. The new event links the prior revert (auto-resolved when supersedes is empty) and every read surface then reports the trail tried → reverted → re-opened. Never re-apply a reverted change without superseding it first.

On validation failure (invalid change_type, missing entity_path, rename_from set without change_type='rename', supersedes set without change_type='supersede', a supersede with nothing to re-open, or an expires_when outside the closed grammar) the result is {"status": "error", "error": "..."} with no event written.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
diffNoThe actual change — SQL migration text, code diff, or a human-readable description of what changed. Optional but strongly recommended for non-trivial changes.
agentNoName/ID of the AI agent making the change (e.g. 'claude-code', 'cursor', 'copilot', 'human').
projectNoRepository or project name. Useful when one DB tracks multiple projects.
reasoningNoWhy the change was made. Include the user's original request, the problem being solved, or any context that won't be obvious from the diff alone. Good example: 'User asked to add 2FA — needs phone number to send SMS verification codes.' Avoid generic placeholders like 'user request' or 'done' — these are flagged by the quality validator and returned in `warnings`.
constraintNoOptional: the testable principle behind the decision, kept queryable (e.g. 'card data in our own DB = PCI scope').
git_commitNoThe git commit hash this change will land in. Can be backfilled later via `selvedge backfill-commit` or the post-commit hook.
session_idNoThe agent session or conversation ID, if available.
stale_whenNoOptional: what would invalidate this decision (e.g. 'payment provider changed'). stale_decisions matches it against later events and flags 'review suggested' — surfacing only.
supersedesNoId of the prior event this change overrides; only valid with change_type='supersede'. Empty auto-links the entity's most recent removal event (remove/delete/index_remove/revert/reject) — so after a standalone rejection it re-opens the rejection. Append-only — the old verdict is never edited, just derived as superseded.
change_typeYesWhat kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate, revert (tried and rolled back), reject (considered and decided against, without writing the change), supersede (re-open a reverted decision). Invalid values are rejected — pick the closest match.
entity_pathYesDot/slash-notation path to the entity. Required and non-empty. Examples: 'users.email' (DB column), 'users' (DB table), 'src/auth.py::login' (function in file), 'src/auth.py' (file), 'api/v1/users' (API route), 'deps/stripe' (dependency), 'env/STRIPE_SECRET_KEY' (env variable).
entity_typeNoCategory of entity. One of: column, table, file, function, class, endpoint, dependency, env_var, index, schema, config, other. Unknown values are coerced to 'other'.other
rename_fromNoThe entity's previous path, when this change is a rename. Set it together with change_type='rename' and put the NEW path in entity_path. Selvedge records the dual-event rename pattern: a 'rename' event on the old path and a 'create' event on the new path whose metadata.renamed_from points back to the old one, so blame/diff/prior_attempts on the new path still see the history. Leave empty for any non-rename change.
changeset_idNoOptional grouping ID for related changes that belong to the same feature or task. Use a short slug like 'add-stripe-billing'. All events sharing a changeset_id can be queried together via the `changeset` tool.
expires_whenNoOptional machine-checkable expiry condition for this decision. Closed grammar, validated at write time: 'library:NAME>=VERSION' (revisit when the named dependency reaches a version, e.g. 'library:django>=5.0'), 'entity:PATH:changes' (revisit when that entity next changes, e.g. 'entity:users.email:changes'), 'date:ISO' (revisit on a date, e.g. 'date:2027-01-01'), or 'manual:LABEL' (opaque label for human review; never auto-fires). `stale_decisions` evaluates these from local state — no network, no LLM — and flags 'expired' with the pattern that fired. Values outside the grammar are rejected.
revisit_afterNoOptional revisit date for an architectural decision (table, schema, dependency, config). An ISO date OR a relative offset from this event's timestamp (e.g. '90d', '6mo'). `stale_decisions` surfaces it once it passes, if the entity is still in active use. Leave empty otherwise.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYes
errorYes
statusYes
warningsYes
timestampYes
supersedesYes

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed3 schema fields changed
    • changedInput schema / properties / change_type / description
      Previous value: -"What kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate, revert (tried and rolled back), supersede (re-open a reverted decision). Invalid values are rejected — pick the closest match."New value: +"What kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate, revert (tried and rolled back), reject (considered and decided against, without writing the change), supersede (re-open a reverted decision). Invalid values are rejected — pick the closest match."
    • addedInput schema / properties / expires_when
      Added value: +{
      +  "default": "",
      +  "description": "Optional machine-checkable expiry condition for this decision. Closed grammar, validated at write time: 'library:NAME>=VERSION' (revisit when the named dependency reaches a version, e.g. 'library:django>=5.0'), 'entity:PATH:changes' (revisit when that entity next changes, e.g. 'entity:users.email:changes'), 'date:ISO' (revisit on a date, e.g. 'date:2027-01-01'), or 'manual:LABEL' (opaque label for human review; never auto-fires). `stale_decisions` evaluates these from local state — no network, no LLM — and flags 'expired' with the pattern that fired. Values outside the grammar are rejected.",
      +  "title": "Expires When",
      +  "type": "string"
      +}
    • changedInput schema / properties / supersedes / description
      Previous value: -"Id of the prior event this change overrides; only valid with change_type='supersede'. Empty auto-links the entity's most recent remove/delete. Append-only — the old verdict is never edited, just derived as superseded."New value: +"Id of the prior event this change overrides; only valid with change_type='supersede'. Empty auto-links the entity's most recent removal event (remove/delete/index_remove/revert/reject) — so after a standalone rejection it re-opens the rejection. Append-only — the old verdict is never edited, just derived as superseded."
  2. Changed6 schema fields changedv0.3.10
    • changedInput schema / properties / change_type / description
      Previous value: -"What kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate. Invalid values are rejected — pick the closest match."New value: +"What kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate, revert (tried and rolled back), supersede (re-open a reverted decision). Invalid values are rejected — pick the closest match."
    • addedInput schema / properties / constraint
      Added value: +{
      +  "default": "",
      +  "description": "Optional: the testable principle behind the decision, kept queryable (e.g. 'card data in our own DB = PCI scope').",
      +  "title": "Constraint",
      +  "type": "string"
      +}
    • addedInput schema / properties / stale_when
      Added value: +{
      +  "default": "",
      +  "description": "Optional: what would invalidate this decision (e.g. 'payment provider changed'). stale_decisions matches it against later events and flags 'review suggested' — surfacing only.",
      +  "title": "Stale When",
      +  "type": "string"
      +}
    • addedInput schema / properties / supersedes
      Added value: +{
      +  "default": "",
      +  "description": "Id of the prior event this change overrides; only valid with change_type='supersede'. Empty auto-links the entity's most recent remove/delete. Append-only — the old verdict is never edited, just derived as superseded.",
      +  "title": "Supersedes",
      +  "type": "string"
      +}
    • addedOutput schema / properties / supersedes
      Added value: +{
      +  "title": "Supersedes",
      +  "type": "string"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "id",
      -  "timestamp",
      -  "status",
      -  "error",
      -  "warnings"
      -]New value: +[
      +  "id",
      +  "timestamp",
      +  "status",
      +  "error",
      +  "warnings",
      +  "supersedes"
      +]
  3. Changed2 schema fields changedv0.3.8
    • addedInput schema / properties / rename_from
      Added value: +{
      +  "default": "",
      +  "description": "The entity's previous path, when this change is a rename. Set it together with change_type='rename' and put the NEW path in entity_path. Selvedge records the dual-event rename pattern: a 'rename' event on the old path and a 'create' event on the new path whose metadata.renamed_from points back to the old one, so blame/diff/prior_attempts on the new path still see the history. Leave empty for any non-rename change.",
      +  "title": "Rename From",
      +  "type": "string"
      +}
    • addedInput schema / properties / revisit_after
      Added value: +{
      +  "default": "",
      +  "description": "Optional revisit date for an architectural decision (table, schema, dependency, config). An ISO date OR a relative offset from this event's timestamp (e.g. '90d', '6mo'). `stale_decisions` surfaces it once it passes, if the entity is still in active use. Leave empty otherwise.",
      +  "title": "Revisit After",
      +  "type": "string"
      +}
  4. Changed11 schema fields changedv0.3.2
    • addedInput schema / properties / agent / description
      Added value: +"Name/ID of the AI agent making the change (e.g. 'claude-code', 'cursor', 'copilot', 'human')."
    • addedInput schema / properties / change_type / description
      Added value: +"What kind of change. One of: add, remove, modify, rename, retype, create, delete, index_add, index_remove, migrate. Invalid values are rejected — pick the closest match."
    • addedInput schema / properties / changeset_id / description
      Added value: +"Optional grouping ID for related changes that belong to the same feature or task. Use a short slug like 'add-stripe-billing'. All events sharing a changeset_id can be queried together via the `changeset` tool."
    • addedInput schema / properties / diff / description
      Added value: +"The actual change — SQL migration text, code diff, or a human-readable description of what changed. Optional but strongly recommended for non-trivial changes."
    • addedInput schema / properties / entity_path / description
      Added value: +"Dot/slash-notation path to the entity. Required and non-empty. Examples: 'users.email' (DB column), 'users' (DB table), 'src/auth.py::login' (function in file), 'src/auth.py' (file), 'api/v1/users' (API route), 'deps/stripe' (dependency), 'env/STRIPE_SECRET_KEY' (env variable)."
    • addedInput schema / properties / entity_type / description
      Added value: +"Category of entity. One of: column, table, file, function, class, endpoint, dependency, env_var, index, schema, config, other. Unknown values are coerced to 'other'."
    • addedInput schema / properties / git_commit / description
      Added value: +"The git commit hash this change will land in. Can be backfilled later via `selvedge backfill-commit` or the post-commit hook."
    • addedInput schema / properties / project / description
      Added value: +"Repository or project name. Useful when one DB tracks multiple projects."
    • addedInput schema / properties / reasoning / description
      Added value: +"Why the change was made. Include the user's original request, the problem being solved, or any context that won't be obvious from the diff alone. Good example: 'User asked to add 2FA — needs phone number to send SMS verification codes.' Avoid generic placeholders like 'user request' or 'done' — these are flagged by the quality validator and returned in `warnings`."
    • addedInput schema / properties / session_id / description
      Added value: +"The agent session or conversation ID, if available."
    • changedOutput schema / (root)
      Previous value: -nullNew value: +{
      +  "properties": {
      +    "error": {
      +      "title": "Error",
      +      "type": "string"
      +    },
      +    "id": {
      +      "title": "Id",
      +      "type": "string"
      +    },
      +    "status": {
      +      "title": "Status",
      +      "type": "string"
      +    },
      +    "timestamp": {
      +      "title": "Timestamp",
      +      "type": "string"
      +    },
      +    "warnings": {
      +      "items": {
      +        "type": "string"
      +      },
      +      "title": "Warnings",
      +      "type": "array"
      +    }
      +  },
      +  "required": [
      +    "id",
      +    "timestamp",
      +    "status",
      +    "error",
      +    "warnings"
      +  ],
      +  "title": "LogChangeResult",
      +  "type": "object"
      +}
  5. First observedv0.3.1

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations carry near-zero information (all false except openWorldHint), so the description carries the full burden. It comprehensively discloses: the warnings array on quality-validator failure, the exact error shape on validation failure, the dual-event rename behavior, supersede auto-linking, and append-only semantics. No contradiction with annotations (readOnlyHint=false correctly implies a write).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Long, but every section earns its place given the complexity — headers ('Renames:', 'Rejections:', 'Superseding a reverted decision:') with code examples make it scannable. Slightly verbose in repeating rename semantics already in the schema's rename_from field, but organized enough that the density is justified.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Comprehensive for a 16-parameter write tool with 5 complex change_type workflows. The description covers all change types, the validation grammar, failure/error shapes, examples for each major flow, and the output schema exists. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, giving a baseline of 3, but the description adds genuine orchestration semantics beyond the schema: rename's dual-event pattern (rename on old path + create on new path with metadata.renamed_from), the reject naming requirement ('name what was rejected AND what was chosen instead'), and that empty supersedes auto-links the most recent removal event. This is behavioral glue the schemas don't spell out.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource — 'Record a change to a codebase entity' — and immediately distinguishes itself: call it after a meaningful change, while siblings diff/blame/history/prior_attempts are read surfaces. An agent can clearly separate it from the sibling tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit when-to-use for each change_type: 'Call this immediately after making any meaningful change,' with dedicated workflows for rename, reject, revert, and supersede. Names why reject is preferable to re-deriving dead ends ('the next agent's prior_attempts query finds it as a high-confidence row') and why supersede beats editing history. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.