18
Every action type, one at a time
Each block applies exactly one action to a clean document and prints what the planner did with it -- including the consequences it derived on its own.
examples/18_action_vocabulary.py — ran in 1.11s · exit 0
Console output
============================================================================
18 · Action vocabulary
============================================================================
--- text actions — no structural consequence ---
replace_text (clause-scoped)
applied x1 replaced 'thirty (30) days' -> 'forty-five (45) days'
replace_text (all=true, document-wide)
applied x8 replaced 'Provider shall' -> 'Provider will'
replace_text (regex=true)
applied x1 replaced '1\\.5% per month' -> '1.0% per month'
insert_text (anchor + position)
applied x1 inserted ', without setoff or deduction' after of the invoice date
delete_text
applied x1 deleted ' automatically'
delete_text (regex=true)
applied x1 deleted '\\s*\\(d\\) use the Services to build a competing product;'
rewrite_clause — number kept, body word-diffed
applied x4 rewrote clause 6.4
--- structural actions — these trigger the renumbering cascade ---
insert_clause (before_clause + title)
applied x1 inserted new clause 1.1 (Affiliate)
renumbered 5: 1.1->1.2, 1.2->1.3, 1.3->1.4, 1.4->1.5, 1.5->1.6
warning clause number 1.1 is carried by more than one paragraph; renumbering resolves it, but ch
insert_clause (into_section — appends to the end of a section)
applied x1 inserted new clause 3.5 (Currency)
delete_clause
applied x1 deleted clause 2.3
move_clause (after_clause)
applied x1 moved clause 12.1 after 4.1
renumbered 9: 12.1->4.2, 4.2->4.3, 4.3->4.4, 4.4->4.5, 12.2->12.1, 12.3->12.2 ...
reference Section 4.2 -> Section 4.3
move_clause (into_section + position='first')
applied x1 moved clause 10.3 to the top of section 10
renumbered 3: 10.3->10.1, 10.1->10.2, 10.2->10.3
reference Section 10.2 -> Section 10.3
reference SECTION 10.3 -> SECTION 10.1
move_clause (before_clause)
applied x1 moved clause 7.2 before 7.1
renumbered 2: 7.2->7.1, 7.1->7.2
reorder_clauses — only what must move, moves
applied x1 reordered section 8: 8.1 -> 8.3 -> 8.2 (1 clause(s) moved, 2 left in place)
renumbered 2: 8.3->8.2, 8.2->8.3
insert_section (after_section + title + text)
applied x2 inserted new section 13 (Insurance)
delete_section — takes its sub-clauses with it
applied x4 deleted section 5 (+3 sub-clause(s))
renumbered 30: 6->5, 6.1->5.1, 6.2->5.2, 6.3->5.3, 6.4->5.4, 7->6 ...
reference SECTION 9 -> SECTION 8
reference SECTION 10.3 -> SECTION 9.3
reference Section 8 -> Section 7
move_section (before_section)
applied x3 moved section 11 before section 10
renumbered 7: 11->10, 11.1->10.1, 11.2->10.2, 10->11, 10.1->11.1, 10.2->11.2 ...
reference Section 10 -> Section 11
reference SECTION 10.3 -> SECTION 11.3
reference Section 10.2 -> Section 11.2
--- table actions ---
insert_row
applied x1 inserted row into table 0
insert_row (at an index)
applied x1 inserted row into table 0
delete_row
applied x1 deleted row 1 of table 1
update_cell
applied x1 updated cell (1,0) of table 0
--- presentation and annotation ---
format_text (bold + color)
applied x1 formatted 'Delaware'
format_text (italic + underline + highlight)
applied x1 formatted 'AS IS'
format_clause (alignment + spacing)
applied x1 reformatted clause 6.4
format_clause (style)
applied x1 reformatted clause 1.1
comment (anchored on a clause)
applied x1 commented on clause 10.2
comment (anchored on a phrase, anywhere)
applied x1 commented on 'SOC 2 Type II'
--- targeting unnumbered content — quote it, uniquely ---
replace_text in Exhibit A (no clause number to address)
applied x1 replaced 'Annual Fees: $186,000, payable annually in advance' -> 'Annual Fees: $186,000, payable quarterly in arrears'
--- an ambiguous quote is refused, not applied to the first match ---
replace_text with a quote that appears twice
failed x0 'Order Form' appears in 13 paragraphs -- add a clause, quote more surrounding text, or set all=true. Matches: Order Form Reference: NSS-2026-00142; Provider and Customer are each a “Party” and col; 1.1 “Authorized Users” means Customer’s employe
--- derived actions a model must never emit ---
['renumber_clause', 'update_cross_reference']
letting the model renumber as well as the engine is how numbering
silently double-applies, so the planner refuses the whole batch --
before the document is opened, and regardless of strict=
validate_actions -> X: 'renumber_clause' is derived by the planner and must not be supplied
apply_actions -> RedlineError, nothing written
validate_actions -> X: 'update_cross_reference' is derived by the planner and must not be supplied
apply_actions -> RedlineError, nothing written
--- the full vocabulary, from the schema itself ---
comment required=['text'] optional=['clause', 'find']
*delete_clause required=['clause'] optional=[]
delete_row required=['table', 'row'] optional=[]
*delete_section required=['section'] optional=[]
delete_text required=['find'] optional=['clause', 'all', 'regex']
format_clause required=['clause'] optional=['alignment', 'space_after', 'space_before', 'style']
format_text required=['find'] optional=['clause', 'bold', 'italic', 'underline', 'color', 'highlight']
*insert_clause required=['text'] optional=['after_clause', 'before_clause', 'into_section', 'title']
insert_row required=['table', 'values'] optional=['row']
*insert_section required=['title'] optional=['after_section', 'text']
insert_text required=['text'] optional=['clause', 'anchor', 'position']
*move_clause required=['clause'] optional=['after_clause', 'before_clause', 'into_section', 'position']
*move_section required=['section'] optional=['after_section', 'before_section', 'position']
*reorder_clauses required=['section', 'order'] optional=[]
replace_text required=['find', 'replace'] optional=['clause', 'all', 'regex']
rewrite_clause required=['clause', 'text'] optional=[]
update_cell required=['table', 'row', 'col', 'text'] optional=[]
(* = structural: triggers renumbering and cross-reference repair)
Source
"""18 · Every action type, one at a time.
Each block applies exactly one action to a clean document and prints what the
planner did with it -- including the consequences it derived on its own.
"""
from _shared import banner, fresh, section
from docx_redline import RedlineError, apply_actions, validate_actions
from docx_redline.planning.actions import ACTION_SCHEMA, DERIVED_ACTIONS, STRUCTURAL_ACTIONS
banner("18 · Action vocabulary")
def one(title, item, show_consequences=False):
rl = fresh()
report = apply_actions(rl, [dict(item, id=item.get("id", "AI-001"))])
res = report.results[0]
print(f"\n {title}")
print(f" {res.status:<8} x{res.edits} {res.detail}")
if show_consequences:
if report.renumbered:
moves = ", ".join(f"{r['from']}->{r['to']}" for r in report.renumbered[:6])
print(
f" renumbered {len(report.renumbered)}: {moves}"
f"{' ...' if len(report.renumbered) > 6 else ''}"
)
for ref in report.references[:3]:
print(f" reference {ref['context']}")
for warn in report.warnings:
print(f" warning {warn[:88]}")
return report
section("text actions — no structural consequence")
one(
"replace_text (clause-scoped)",
{
"type": "replace_text",
"clause": "3.2",
"find": "thirty (30) days",
"replace": "forty-five (45) days",
},
)
one(
"replace_text (all=true, document-wide)",
{"type": "replace_text", "find": "Provider shall", "replace": "Provider will", "all": True},
)
one(
"replace_text (regex=true)",
{
"type": "replace_text",
"clause": "3.3",
"find": r"1\.5% per month",
"replace": "1.0% per month",
"regex": True,
},
)
one(
"insert_text (anchor + position)",
{
"type": "insert_text",
"clause": "3.2",
"anchor": "of the invoice date",
"position": "after",
"text": ", without setoff or deduction",
},
)
one("delete_text", {"type": "delete_text", "clause": "4.2", "find": " automatically"})
one(
"delete_text (regex=true)",
{
"type": "delete_text",
"clause": "2.2",
"regex": True,
"find": r"\s*\(d\) use the Services to build a competing product;",
},
)
one(
"rewrite_clause — number kept, body word-diffed",
{
"type": "rewrite_clause",
"clause": "6.4",
"text": "Breach Notification. Provider will notify Customer without undue delay, "
"and in no event later than forty-eight (48) hours, after becoming aware of "
"a confirmed security breach affecting Customer Data.",
},
)
section("structural actions — these trigger the renumbering cascade")
one(
"insert_clause (before_clause + title)",
{
"type": "insert_clause",
"before_clause": "1.1",
"title": "Affiliate",
"text": "“Affiliate” means any entity under common control with a Party.",
},
True,
)
one(
"insert_clause (into_section — appends to the end of a section)",
{
"type": "insert_clause",
"into_section": "3",
"title": "Currency",
"text": "All amounts are payable in U.S. dollars.",
},
True,
)
one("delete_clause", {"type": "delete_clause", "clause": "2.3"}, True)
one(
"move_clause (after_clause)",
{"type": "move_clause", "clause": "12.1", "after_clause": "4.1"},
True,
)
one(
"move_clause (into_section + position='first')",
{"type": "move_clause", "clause": "10.3", "into_section": "10", "position": "first"},
True,
)
one(
"move_clause (before_clause)",
{"type": "move_clause", "clause": "7.2", "before_clause": "7.1"},
True,
)
one(
"reorder_clauses — only what must move, moves",
{"type": "reorder_clauses", "section": "8", "order": ["8.1", "8.3", "8.2"]},
True,
)
one(
"insert_section (after_section + title + text)",
{
"type": "insert_section",
"after_section": "12",
"title": "Insurance",
"text": "Provider will maintain cyber-liability insurance of $5,000,000.",
},
True,
)
one(
"delete_section — takes its sub-clauses with it",
{"type": "delete_section", "section": "5"},
True,
)
one(
"move_section (before_section)",
{"type": "move_section", "section": "11", "before_section": "10"},
True,
)
section("table actions")
one("insert_row", {"type": "insert_row", "table": 0, "values": ["Countersigned by", "Date"]})
one(
"insert_row (at an index)",
{"type": "insert_row", "table": 0, "row": 0, "values": ["Party", "Detail"]},
)
one("delete_row", {"type": "delete_row", "table": 1, "row": 1})
one(
"update_cell",
{"type": "update_cell", "table": 0, "row": 1, "col": 0, "text": "Name (please print)"},
)
section("presentation and annotation")
one(
"format_text (bold + color)",
{"type": "format_text", "clause": "12.1", "find": "Delaware", "bold": True, "color": "C00000"},
)
one(
"format_text (italic + underline + highlight)",
{
"type": "format_text",
"clause": "9.3",
"find": "AS IS",
"italic": True,
"underline": True,
"highlight": "YELLOW",
},
)
one(
"format_clause (alignment + spacing)",
{
"type": "format_clause",
"clause": "6.4",
"alignment": "JUSTIFY",
"space_before": 12,
"space_after": 18,
},
)
one("format_clause (style)", {"type": "format_clause", "clause": "1.1", "style": "Heading 4"})
one(
"comment (anchored on a clause)",
{"type": "comment", "clause": "10.2", "text": "Confirm the cap with finance."},
)
one(
"comment (anchored on a phrase, anywhere)",
{"type": "comment", "find": "SOC 2 Type II", "text": "Is ISO 27001 also in scope?"},
)
section("targeting unnumbered content — quote it, uniquely")
one(
"replace_text in Exhibit A (no clause number to address)",
{
"type": "replace_text",
"find": "Annual Fees: $186,000, payable annually in advance",
"replace": "Annual Fees: $186,000, payable quarterly in arrears",
},
)
section("an ambiguous quote is refused, not applied to the first match")
one(
"replace_text with a quote that appears twice",
{"type": "replace_text", "find": "Order Form", "replace": "Ordering Document"},
)
section("derived actions a model must never emit")
print(" ", sorted(DERIVED_ACTIONS))
print(" letting the model renumber as well as the engine is how numbering")
print(" silently double-applies, so the planner refuses the whole batch --")
print(" before the document is opened, and regardless of strict=")
for kind in sorted(DERIVED_ACTIONS):
item = [{"id": "X", "type": kind, "clause": "3.1"}]
print(f" validate_actions -> {validate_actions(item)[0]}")
try:
apply_actions(fresh(), item, strict=False)
except RedlineError as exc:
print(f" apply_actions -> {type(exc).__name__}, nothing written")
section("the full vocabulary, from the schema itself")
for kind, (required, optional) in sorted(ACTION_SCHEMA.items()):
mark = "*" if kind in STRUCTURAL_ACTIONS else " "
print(f" {mark}{kind:<18} required={list(required)} optional={list(optional)}")
print("\n (* = structural: triggers renumbering and cross-reference repair)")
What it wrote
This example prints its result rather than saving a document — read the
console output beside it.