PayPal · Sr. Content Designer · 2026
Content as code
I wrote the schema before anyone asked for one. Then the engine that resolves against it, the MCP server that hands it to agents, and the registry that renders it in a native iOS build.
Fintech · AI and agentsI built a content governance stack that runs end to end
Five months of work, from a locked schema to content rendering in a native app. Scroll to follow the path.
Check your routing and account numbers and try again. They’re on the bottom of a check or in your banking app.
Try againGovernance and agent-readiness turned out to be the same list
Governance needs these fields. So does an agent.
I spent weeks making two lists. One was what content needs to be governed: who owns it, whether it’s locked, what legal basis it sits on, where it is in its lifecycle, which locales it covers. The other was what an agent needs in order to use a string safely.
They were the same list. Governance isn’t overhead in the agent era, it’s the thing that makes agents usable at all. Everything below is me proving that in something you can run.
Here, drive it
This is the content, stored as data. Edit a field and watch the component on the right. Then try to change the button.
Rendered component
Round up every purchase
We add the spare change to your savings goal. Turn it off any time in settings.
Start rounding upnot shippable in this state
Governance log
Token loaded and validated against schema v1
Left panel is the content, stored as data. Right panel is a component built from it. Edit a field and the component changes. Type a word off my own reject list and it gets blocked before it renders.
Then try to change the button.
A locked field with no way forward doesn't stop drift, it hides it
Locked string. Edit rejected. The person who needed different copy now has a ticket, a Slack thread, and a workaround.
Locked string. Edit rejected, and a variant is minted at v2 with the field open. The v1 record is untouched. Nothing was overwritten.
A locked string is a compliance record. Somebody in legal signed off on those exact words, and if they change quietly, the signoff is now attached to a sentence nobody approved. Every content tool I’ve used handles this the same way: it makes the field read-only and walks off. Which is correct, and useless, because the person who wanted to change it still has a real reason and now has no legitimate path.
So they find an illegitimate one. They ship a second string somewhere else. They put the new wording in a different component. They ask an engineer to hardcode it “just for this screen.” The lock didn’t prevent drift. It relocated it somewhere nobody is auditing.
The fix is boring and it comes straight from version control: you never edit a locked record, you mint a new variant from it. v1 stays exactly as approved, v2 exists with the field open, and the relationship between them is in the data. Compliance can see what changed and why there are now two. The person who needed different copy gets it in about a second, inside the system, where it can be reviewed.
That’s a content-design decision wearing engineering clothes. Deciding that a string is a record with a history rather than a value you overwrite is the same instinct as keeping a decision log instead of a style guide. I just wrote it down in something that runs.
I wasn't going to ask engineering to fund a hunch
Ungrounded output drops from roughly a third of responses to under one percent when generation is grounded.
Vectara HHEM
How often legal models hallucinate on queries without grounding.
Stanford RegLab
Constraint adherence, once outputs are structured rather than free text.
Structured outputs research
Public research, not my numbers. It runs through the pitch, the schema, and the audit rules, because a platform ask needs a reason that survives someone else repeating it in a room I’m not in.
A domain model you can't run is just an opinion
I’m not an engineer and that isn’t the point. The hardest part of content as code is the domain model, and the domain model is content-design work. Building the engine, the validator, the bridge, and the native render was how I made the model impossible to wave away in a review.
None of it has shipped to customers. It was scoped as exploration, built to retire the technical risk on a platform bet, and it did that. The work now is adoption: aligning teams, and building the GitHub library and the MCP connector schemas that make it easy for someone else to say yes.
Four layers, so a sentence inherits its rules instead of being told them
The model isn’t a diagram anywhere. It’s in the record.
Primitives are the smallest decisions, the ones that hold across every surface: plain tone, second person, an eighth-grade reading level. Patterns are shapes we’ve agreed on, like leading with the value before the action. Types are what a component will accept. Instances are the actual words.
Write a headline at the instance layer and it has already inherited the tone, the shape, and the shape’s constraints. Nobody has to remember the style guide, because the style guide is upstream of the field.
An agent that doesn't sound like you needs a list of things it may not say
A seamless way to unlock your savings potential.
- seamlessuntestable filler, say what it does
- unlockempty verb, name the action
Round up every purchase and we'll move the spare change into savings.
When an agent doesn’t sound like you, the answer isn’t a better eval or a Likert scale. It’s a list of things it may not say. Take choices away and what comes out starts sounding like the team instead of generic AI cruft.
Evals catch what falls through. They don’t define the standard. The reject list runs before render, which is why the demo above refuses to publish a card with any of these in it.
Project details
- Company
- PayPal
- Role
- Sr. Content Designer, Consumer Content Design
- Scope
- Settings, Profile, Activity, Subscriptions and Open Banking, Notifications
- Timeline
- 2026, ongoing
- Tools and methods
- TypeScriptJSON SchemaMCPContract testsSwiftContent modelingDesign systemsClaude Code