Skip to main content
Glama

hold_add

Destructive

Insert a gap in the voiceover after a specified word, cue an asset's picture and audio in that gap with margins, and refuse if it cannot fit.

Instructions

Splice a hold into clip_id after gap_word_index: a real gap opens in the VO (vo_extend's own mechanism, reused) and a picture cue pins asset's own in-point, snapped to whole words with margin and refused, never clamped, when it cannot fit.

Addressed by (clip_id, gap_word_index), unique — a second hold_add at the same address is refused. gap_word_index/cue_word_index/ word_index_first+word_index_last each also accept a phrase alternative: gap_phrase binds its last word (the gap opens right after it), cue_phrase binds its first, and asset_phrase resolves against asset's own transcript and binds its first and last words to word_index_first/word_index_last together.

Everything else is resolved live: elapsed (how long the VO plays between the cue and the gap), src_start (deterministically — phrase_start - elapsed - head_margin), and hold_length (the phrase's own span plus both margins). Refused, with the measured numbers, when there is no room or the asset runs out.

Mix-only fields (head_margin/tail_margin/under/fade_in/ fade_out) are re-settable on an already-spliced hold by calling again with the same address and no change to word_index_first/ word_index_last — those two are one-way once spliced (hold_rm then hold_add again, or proofcut undo, are the only ways to resize one).

plan=True resolves and reports without writing anything.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathNoThe project directory to act on. Omit it — the usual case — when this server is bound to a project (started as `proofcut -C DIR mcp`, or inside a project; `ping` says which): it then resolves to that one bound project, a relative path resolves against it, and a path outside it is refused by name. Unbound, `path` is the whole address and omitting it refuses rather than guessing.
planNoResolve the whole call and report what it would do, writing nothing. Prefer it over doing the thing and undoing it.
afterNoA forward cursor over the matches of `gap_phrase`/`cue_phrase`/`asset_phrase`: any match at or before this word index is skipped. -1, the default, means from the start.
assetNoThe film clip whose own audio plays in the gap, and whose picture the cue pins.
underNoHow far below the VO the held audio sits, in LU. Re-settable.
clip_idYesThe VO track the gap opens in.
fade_inNoSeconds of fade as the held audio comes in. Re-settable.
fade_outNoSeconds of fade as it goes out. Re-settable.
cue_phraseNoAddress the cue by wording; it binds its **first** word.
gap_phraseNoAddress the gap by wording; it binds its **last** word, since the gap opens right after it.
occurrenceNoDisambiguate `gap_phrase`/`cue_phrase`/`asset_phrase` by count, **1-based**. Unset, an ambiguous phrase is refused rather than guessed at.
head_marginNoSeconds kept before the line, so it does not start on the word. Re-settable on an already-spliced hold.
tail_marginNoSeconds kept after the line. Re-settable.
asset_phraseNoThe line to play, resolved against `asset`'s **own** transcript, binding its first and last words together. One phrase is the source of truth for both ends; hand-typed indices drift the moment a transcript changes under them.
cue_word_indexNoThe word the picture cue for `asset` is placed on.
gap_word_indexNoThe word the gap opens right after. With `clip_id` it is the hold's address, and a second `hold_add` at the same address is refused.
word_index_lastNoLast word of that line. With `word_index_first` it is one-way once spliced: resizing means `hold_rm` then `hold_add`, or `undo`.
word_index_firstNoFirst word of the line to play, in **`asset`'s own** transcript — not the VO's.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed67 schema fields changedv0.25.0
    • addedInput schema / properties / after / description
      Added value: +"A forward cursor over the matches of `gap_phrase`/`cue_phrase`/`asset_phrase`: any match at or before this word index is skipped. -1, the default, means from the start."
    • removedInput schema / properties / after / title
      Removed value: -"After"
    • removedInput schema / properties / asset / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / asset / description
      Added value: +"The film clip whose own audio plays in the gap, and whose picture the cue pins."
    • removedInput schema / properties / asset / title
      Removed value: -"Asset"
    • addedInput schema / properties / asset / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedInput schema / properties / asset_phrase / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / asset_phrase / description
      Added value: +"The line to play, resolved against `asset`'s **own** transcript, binding its first and last words together. One phrase is the source of truth for both ends; hand-typed indices drift the moment a transcript changes under them."
    • removedInput schema / properties / asset_phrase / title
      Removed value: -"Asset Phrase"
    • addedInput schema / properties / asset_phrase / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • addedInput schema / properties / clip_id / description
      Added value: +"The VO track the gap opens in."
    • removedInput schema / properties / clip_id / title
      Removed value: -"Clip Id"
    • removedInput schema / properties / cue_phrase / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / cue_phrase / description
      Added value: +"Address the cue by wording; it binds its **first** word."
    • removedInput schema / properties / cue_phrase / title
      Removed value: -"Cue Phrase"
    • addedInput schema / properties / cue_phrase / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedInput schema / properties / cue_word_index / anyOf
      Removed value: -[
      -  {
      -    "type": "integer"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / cue_word_index / description
      Added value: +"The word the picture cue for `asset` is placed on."
    • removedInput schema / properties / cue_word_index / title
      Removed value: -"Cue Word Index"
    • addedInput schema / properties / cue_word_index / type
      Added value: +[
      +  "integer",
      +  "null"
      +]
    • removedInput schema / properties / fade_in / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / fade_in / description
      Added value: +"Seconds of fade as the held audio comes in. Re-settable."
    • removedInput schema / properties / fade_in / title
      Removed value: -"Fade In"
    • addedInput schema / properties / fade_in / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedInput schema / properties / fade_out / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / fade_out / description
      Added value: +"Seconds of fade as it goes out. Re-settable."
    • removedInput schema / properties / fade_out / title
      Removed value: -"Fade Out"
    • addedInput schema / properties / fade_out / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedInput schema / properties / gap_phrase / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / gap_phrase / description
      Added value: +"Address the gap by wording; it binds its **last** word, since the gap opens right after it."
    • removedInput schema / properties / gap_phrase / title
      Removed value: -"Gap Phrase"
    • addedInput schema / properties / gap_phrase / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedInput schema / properties / gap_word_index / anyOf
      Removed value: -[
      -  {
      -    "type": "integer"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / gap_word_index / description
      Added value: +"The word the gap opens right after. With `clip_id` it is the hold's address, and a second `hold_add` at the same address is refused."
    • removedInput schema / properties / gap_word_index / title
      Removed value: -"Gap Word Index"
    • addedInput schema / properties / gap_word_index / type
      Added value: +[
      +  "integer",
      +  "null"
      +]
    • removedInput schema / properties / head_margin / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / head_margin / description
      Added value: +"Seconds kept before the line, so it does not start on the word. Re-settable on an already-spliced hold."
    • removedInput schema / properties / head_margin / title
      Removed value: -"Head Margin"
    • addedInput schema / properties / head_margin / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedInput schema / properties / occurrence / anyOf
      Removed value: -[
      -  {
      -    "type": "integer"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / occurrence / description
      Added value: +"Disambiguate `gap_phrase`/`cue_phrase`/`asset_phrase` by count, **1-based**. Unset, an ambiguous phrase is refused rather than guessed at."
    • removedInput schema / properties / occurrence / title
      Removed value: -"Occurrence"
    • addedInput schema / properties / occurrence / type
      Added value: +[
      +  "integer",
      +  "null"
      +]
    • removedInput schema / properties / path / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / path / description
      Added value: +"The project directory to act on. Omit it — the usual case — when this server is bound to a project (started as `proofcut -C DIR mcp`, or inside a project; `ping` says which): it then resolves to that one bound project, a relative path resolves against it, and a path outside it is refused by name. Unbound, `path` is the whole address and omitting it refuses rather than guessing."
    • removedInput schema / properties / path / title
      Removed value: -"Path"
    • addedInput schema / properties / path / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • addedInput schema / properties / plan / description
      Added value: +"Resolve the whole call and report what it would do, writing nothing. Prefer it over doing the thing and undoing it."
    • removedInput schema / properties / plan / title
      Removed value: -"Plan"
    • removedInput schema / properties / tail_margin / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / tail_margin / description
      Added value: +"Seconds kept after the line. Re-settable."
    • removedInput schema / properties / tail_margin / title
      Removed value: -"Tail Margin"
    • addedInput schema / properties / tail_margin / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedInput schema / properties / under / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / under / description
      Added value: +"How far below the VO the held audio sits, in LU. Re-settable."
    • removedInput schema / properties / under / title
      Removed value: -"Under"
    • addedInput schema / properties / under / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedInput schema / properties / word_index_first / anyOf
      Removed value: -[
      -  {
      -    "type": "integer"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / word_index_first / description
      Added value: +"First word of the line to play, in **`asset`'s own** transcript — not the VO's."
    • removedInput schema / properties / word_index_first / title
      Removed value: -"Word Index First"
    • addedInput schema / properties / word_index_first / type
      Added value: +[
      +  "integer",
      +  "null"
      +]
    • removedInput schema / properties / word_index_last / anyOf
      Removed value: -[
      -  {
      -    "type": "integer"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedInput schema / properties / word_index_last / description
      Added value: +"Last word of that line. With `word_index_first` it is one-way once spliced: resizing means `hold_rm` then `hold_add`, or `undo`."
    • removedInput schema / properties / word_index_last / title
      Removed value: -"Word Index Last"
    • addedInput schema / properties / word_index_last / type
      Added value: +[
      +  "integer",
      +  "null"
      +]
    • removedInput schema / title
      Removed value: -"hold_addArguments"
  2. First observedv0.24.0

TDQS

A4.8/5.0
Behavior5/5

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

Beyond the destructiveHint annotation, it discloses exact behavioral edge cases: refusal rather than clamping when there is no room, deterministic live resolution of elapsed/src_start/hold_length, one-way word indices once spliced, and plan=True writing nothing. This is substantial context beyond the structured hints.

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?

The first sentence is an excellent front-loaded summary and the dense paragraphs earn their place for an 18-parameter tool. It is longer than ideal, with some information already present in the schema, but still well organized.

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?

Given the tool's complexity, a rich output schema, and per-parameter schema descriptions, the description covers mutation constraints, refusal modes, live computation, re-set semantics, and plan mode. An agent has enough to call it correctly without missing core behavior.

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?

With 100% schema coverage the baseline is 3, but the description adds cross-parameter meaning: (clip_id, gap_word_index) is the unique address, phrase alternatives bind first/last words, and asset_phrase ties word_index_first and word_index_last together. It stops short of describing every optional combination, so not a 5.

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?

Opens with a specific operation: 'Splice a hold into `clip_id` after `gap_word_index`' and details the two effects (opening a real gap in the VO and pinning a picture cue). The address uniqueness and refusal behavior further distinguish it from siblings like hold_rm/hold_ls.

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?

Explicitly says when a second hold_add is refused, when re-calling is allowed (re-settable mix-only fields), and states that resizing requires hold_rm + hold_add or proofcut undo. plan=True also offers a no-write alternative, so an agent knows to preview before mutating.

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