---
name: varick-engagement-letter
description: "Produce a Varick Agents (SolidCart, Inc.) client engagement letter as an editable .docx for Google Docs plus a matching PDF, from a JSON spec. Use whenever the user asks for an engagement letter, a scope letter, a statement of work, an audit or diagnostic scope document, a Phase 1 letter, a countersignature document, or wants to update or reissue one — including phrasings like 'engagement letter for Ivanti', 'scope letter for the Merristone diagnostic', 'draft the SOW for Phase 1', 'add a deliverables section', 'change the fee to $175k', 'make the timeline 8 weeks', or 'turn this proposal into a letter they can sign'. Trigger it even when the user doesn't say 'Varick' or 'brand', since every engagement letter the company issues uses this exact layout. Handles the wordmark masthead, numbered sections, engagement-at-a-glance and commercials label tables, hypothesis and scope tables, the week-by-week gantt, deliverables with impact bands, prerequisites, and the two-party signature block with page numbers throughout. Do NOT use for invoices (varick-invoice), multi-page navy-cover reports and leave-behinds (varick-report), one-pagers or pre-call briefs (varick-agents-brand), or standalone charts (varick-charts)."
---

# Varick Agents engagement letters

Every Varick engagement letter is the same document with different content in
it. The visual system is not in the JSON — it is in `scripts/build_letter.py`
and `assets/letter-base.docx`: the wordmark lockup masthead on every page,
Helvetica Neue at one weight, navy `#002870`, hairline rules, the three-part
footer with page numbers. The spec supplies content as an ordered list of
blocks and each block has exactly one renderer, so a letter cannot drift
off-brand by being written differently. Fill the spec; don't rebuild the
layout.

**The deliverable is a Google Doc.** Not a PDF. Build the `.docx`, upload it to
Drive with conversion on, and hand over the Doc link. A PDF is a proofing
artifact only — render one, look at it, throw it away.

## Workflow

1. Collect the content. Ask only for what's missing — the client and
   addressees, the objective, the fee and payment split, the duration, the
   deliverables, and who signs.

2. Write the spec. Start from `assets/letter.example.json`, which is the
   Ivanti net-working-capital letter in full and covers every block type.

3. Build and proof it:

```bash
python scripts/build_letter.py letter.json \
  -o "Varick Agents — Engagement Letter — Ivanti.docx" --check
```

`--check` renders a throwaway PDF, prints the page count, and rasters every
page to `_check/`. **Look at every page.** Then check five things: the masthead
and its rule sit at the top of *each* page, every page's first line clears that
rule by about 0.3in, the footer reads `Page X of Y` with the right total, no
subhead is stranded from the table it introduces, and the signature block is
whole rather than split.

4. Build the delivery payload and upload it:

```bash
python scripts/build_letter.py letter.json -o out.docx --gdoc
```

`--gdoc` writes a font-stripped `.docx` plus `out.docx.b64`, about 33 KB. Upload
that with the Google Drive `create_file` tool — `contentMimeType`
`application/vnd.openxmlformats-officedocument.wordprocessingml.document`, and
leave conversion enabled — and Drive converts it to a Google Doc on the way in.
Give the person the Doc link.

Stripping the fonts is safe: Google Docs ignores embedded fonts. It also keeps
the payload small enough to pass through a tool call, which the full-fat
`.docx` at ~175 KB is not. Build the full version only when someone
specifically wants a file to send to a recipient who may not have Helvetica
Neue installed.

## The spec

Front matter, then blocks:

```json
{
  "kicker": "Engagement Letter",
  "title": "Net Working Capital and Cash Forecasting: Operational Audit",
  "from": "SolidCart, Inc (d/b/a Varick Agents) · San Francisco, CA · varickagents.com",
  "meta": [
    ["Date", "July 17, 2026"],
    ["To", "Ivanti, Inc. — Attn: Kevin Speed, Peter De Bock"],
    ["cc", "Andrew Alea, Vice President, Clearlake Capital Group"]
  ],
  "blocks": [
    { "type": "para", "text": "Dear Kevin and Peter," },
    { "type": "section", "title": "Engagement at a Glance" },
    { "type": "deflist", "rows": [["Fee", "$150,000 fixed, inclusive of expenses."]] }
  ]
}
```

**Required:** `title`, `blocks`. Everything else has a default — `kicker`
defaults to nothing, `footer_left` to `[CONFIDENTIAL]`, `footer_center` to
`Varick Agents`. `from` renders as the first row of the meta block, above
`Date`; don't put it in `meta` yourself.

**Spacing is fixed too.** Every table, band, gantt and figure sits on the same
`SP_BEFORE` / `SP_AFTER` pair so the rhythm down the page is even, and the top
margin is set so each page's first line clears the masthead rule by the same
amount page one does. Don't tune spacing per block.

**The type scale is fixed:** section headings 14pt, body text 10.5pt, and
every table cell 10.5pt to match the body. Subheads 12pt, the title 17pt navy,
meta labels and footer 9pt and smaller. Don't set sizes per block — if a
letter runs long, cut copy.

**Emphasis:** wrap a phrase in `**double asterisks**` in any prose string and
it renders navy. That is the only inline emphasis there is, and it is what
carries `**Milestones:**` and `**Impact:**`. There is no bold and no italic.

## Blocks

| Block | What it's for |
|---|---|
| `section` | Numbered heading with a hairline under it. Numbers itself in order — pass `"number"` to override, or `"number": null` for an unnumbered heading |
| `subhead` | Navy label inside a section: `Commercials`, `Team`, `Midpoint (week 3)` |
| `para` | Running prose |
| `bullets` | `items` as round navy bullets, a real list that stays a list in Google Docs |
| `deflist` | Label/value rows in a grey grid with a pale label column — Engagement at a Glance, Commercials. Labels are sentence case, not small caps |
| `table` | `head` plus `rows`, navy header row, grey grid on every cell. A cell that is a **list** renders as bullets; `{"items": [...], "note": "..."}` adds a grey qualifier under them. `"emphasize_first": true` sets the first column in ink as a claim — that's the hypothesis table |
| `band` | Tinted callout with a navy left bar. The `Impact:` lines |
| `gantt` | The week-by-week plan: a rule between each row, never a column rule. See below |
| `figure` | A PNG or JPEG at the content width, with `caption`. Charts come from `varick-charts` |
| `signature` | Two parties side by side, each field on its own hairline |
| `note` | 7.5pt grey qualifier, for "illustrative only" lines |
| `rule` | A hairline divider |
| `pagebreak` | Forces a new page |
| `spacer` | Vertical space, in dxa |

Section order that works, and the one the Ivanti letter uses: salutation and
framing paragraph → Engagement at a Glance → hypotheses → Scope of Work →
Audit Plan and Timeline → Deliverables → Commercials, Team and Prerequisites
→ Optional Phase 2 → Terms and Next Steps → signature.

### The gantt

Native table markup, not an image, so it stays editable in Google Docs.
Columns are weeks; `spans` are `[first, last, kind]` with `kind` either
`solid` (committed) or `light` (indicative). Overlapping and adjacent spans
merge themselves.

```json
{ "type": "gantt",
  "cols": ["W1", "W2", "W3", "W4", "W5", "W6"],
  "rows": [
    { "label": "Kickoff, data handoff, scheduling", "spans": [[1, 1, "solid"], [2, 2, "light"]] },
    { "label": "Synthesis, ROI model, roadmap, readout", "spans": [[5, 5, "light"], [6, 6, "solid"]] }
  ],
  "milestones": { "3": "Midpoint briefing", "6": "Final read" } }
```

## What not to change

The renderers are the design decision; the spec is the content. Resist these
in particular:

- **Don't replace the wordmark with text.** The masthead is
  `word/media/image1.png` inside `assets/letter-base.docx`, lifted from
  `varick-invoice`, scaled to the 6.7in content width. That asset is a
  *lockup*: wordmark plus a hairline spanning the full canvas, so the rule
  **is** the masthead divider. Never add a second rule under it, and never
  set the wordmark as type.
- **Don't add a tagline under the wordmark.** It was removed deliberately.
- **Don't switch fonts, and don't reach for bold.** Helvetica Neue only.
  Every run is written with `<w:b w:val="0"/>` and the bold faces are not in
  the package, so hierarchy comes from navy, small caps, and size — see the
  note in `references/layout.md` before you go looking for a Medium face.
- **Don't add colour.** Navy `#002870`, one mid-navy `#3e6fa8` for indicative
  gantt bars, `#f3f6fb` for band fills, `#fafafa` for the glance table's label
  column, `#d9d9d9` for every rule — 0.5pt on table strokes, 0.75pt on the
  footer divider. No second accent, no gradients, no rounded corners.
- **Don't put a rule under a section heading.** The source letter doesn't have
  one; the heading's size does the work.
- **Don't re-cut the wordmark, and don't render the SVG as-is.**
  `assets/varick-wordmark.svg` is the source of truth, but its single `<path>`
  is `fill="none" stroke="#022B72"`, so rendering the file directly gives
  *hollow, outlined* letterforms. `make_base.py` fills the glyph contours and
  drops the path's first subpath, which isn't a glyph at all but the lockup
  rule. The masthead divider is drawn as a paragraph border instead, at 0.5pt
  across the full 6.7in column — that's what keeps it a hairline and lets the
  wordmark sit at its proper 1.72in.
- **Don't touch the footer.** `[CONFIDENTIAL]` · `Varick Agents` ·
  `Page X of Y`, all three parts grey, on every page. The page number is a live Word field, not
  typed text — never replace it with a literal number.
- **Don't hand-edit the `.docx`.** Change the spec and rebuild.

## Watch for

- **Don't split a long table into two blocks with a "(continued)" header.**
  One `table` block, however long — it flows across the page break as a single
  table and the navy header row repeats itself. Table rows are deliberately
  splittable: a row that can't split forces the whole table onto the next page,
  which strands the subhead above it and leaves half a page empty. Pass
  `"keep_together": true` on a table only when it's short and must not break.

- **Stranded headings.** `section` and `subhead` already carry `keepNext`, so
  a heading follows its content to the next page. If a page still ends
  awkwardly, cut copy rather than inserting a `pagebreak` — the letter should
  flow.
- **`Page X of Y` shows the wrong total** in a reader that hasn't
  recalculated fields. The PDF is always right; in Google Docs the field
  updates on open.

## Taking design changes back out of Google Docs

When a letter has been edited in Google Docs and those edits are meant to
become the house style rather than a one-off, don't eyeball the document.
Export it and read the numbers:

```bash
# Google Drive: download_file_content, exportMimeType
#   application/vnd.openxmlformats-officedocument.wordprocessingml.document
unzip -oq exported.docx -d hers/
```

Then compare `hers/word/document.xml` against a fresh build of the same spec:
tally the distinct `w:sz` values on runs, the `tcBorders` weights and colours,
and the `w:spacing` triples, and diff those tallies rather than the XML. Google
rewrites the file on export — it drops zero-valued attributes, turns
`lineRule="exact"` into `auto`, merges adjacent runs, and renders `w:caps`
text as literal uppercase — so a raw diff is almost all noise. Tallies make the
real changes obvious.

## Editing a letter that already exists

Reconstruct the spec and rebuild rather than patching the `.docx` — that
keeps one source of truth. Recover the content of an issued letter with
`pandoc -t markdown old.docx`, or `pdftotext -layout old.pdf -` if all you
have is the PDF.

## Layout reference

Read `references/layout.md` before changing the renderers or the base
package. It documents the page setup, the palette and type scale, the block
internals, how the base `.docx` is regenerated from `varick-invoice`, and the
two bugs that are easy to reintroduce.

## Dependencies

`python3` with `lxml` and `Pillow`, plus `zip`/`unzip`. LibreOffice
(`soffice`), `pdfinfo` and `pdftoppm` for rendering and checking. No
`pip install` needed.
