AI marketing agents need a claims file with a read date
Your marketing agent will send a sentence with a price in it, and someone above you will ask how anyone knows the price was right
Your marketing agent will send a sentence with a price in it, and someone above you will ask how anyone knows the price was right. That question arrives with every AI marketing agent that drafts at volume, and a copy review cannot answer it. The paragraph reads well. A seat limit or a certification sits inside a clean sentence, and the number behind it went stale two months ago.
Kortix is the open-source AI Operating System. The part of it that answers this question is small: a claims file in the project repo, and a change request that shows the claim beside its source and the date a person read it.
A clean sentence with a stale number
An editor checks what is on the page. Register, clarity, one word for one thing, a phrase the brand kit bans. A number needs a different check, because a number is a quotation from a page another team owns: a pricing table, a limit in the docs, a compliance report in a shared drive.
Say Acme published a line in June: the Growth plan has a monthly price and includes a fixed number of seats. By September the plan cost more and carried fewer seats. The sentence did not change and stayed on every landing page it had been copied to, because nothing about it looked wrong. The person who wrote it had moved to another company, and the pricing page it came from had been rewritten by someone in sales.
Three editors can read that line in a review and all three approve it, each one assuming somebody else checked the price last. A number has an owner only when one is named. The usual place a team names one is a spreadsheet nobody opens.
An agent makes the problem wider. Reuse is what it is for, so the same claim lands on page after page before anyone notices the price moved.
One line per claim, with the date it was read
A claims file is a Markdown file in the repo. Each line carries four things: the claim as it goes out, the URL it came from, the date someone read that URL, and the event that would make the line wrong. For Acme the last part is a pricing page edit. For a product with a quota it is a release that changes the limit.
The agent reads that file every session, in the same way it reads the voice file. The page we publish for marketing teams puts the mechanism in two sentences: "Your voice, your claims and your banned words live in the repo as a skill file every session reads. Marketing work lands as a draft in a change request, so the review is a diff" (marketing on Kortix).
The rule that makes it hold is short. A claim comes from a line in that file. When no line covers the claim, the agent leaves it out and asks, and that instruction lives in a skill the team can read and change.
Why claims split from voice
Voice changes a few times a year. A register does not move monthly, and when it moves, three people argue about it in a meeting first. Claims change whenever a price page, a quota or a report moves, which in a busy quarter is weekly.
Held in one file, the two rhythms collide. A price update arrives as a diff in the paragraph that defines the tone, so the reviewer reads a style document to check a number. The approval gets muddled as well: the head of marketing signs off on voice once a year, while the person who can confirm a price sits in finance and has never opened the repo.
Who may send, and what waits for a person
A record of what was said covers half the question. The other half is who may send without a person in the room, and most teams leave that half unwritten until a draft reaches an inbox.
Approval gates you set. Off until you set them. In kortix.yaml, policy.default_mode takes allow_all or risk and defaults to allow_all (manifest reference). Under risk, a read runs and a write or a destructive call waits for a person. A held call waits for approvals.ttl, which defaults to 24h and expires after that. At the level of one call, a connector rule is always_run, require_approval or block, shown in the product as Allow, Ask, Block.
Scope is a separate line in the same file. In the starter manifest the agents: map holds governance only: connectors, secrets, project permissions, skills, the workspace boundary, the enabled flag, and the path to the agent's markdown. Connectors, secrets and project permissions are deny-by-default, so a grant the block leaves out resolves to none (the starter kortix.yaml).
kortix_version: 2
default_agent: marketing-editor
connectors:
- slug: gmail
name: Gmail
provider: composio
app: gmail
agents:
marketing-editor:
file: agents/marketing-editor.md
connectors: [gmail] # the send, nothing else
secrets: none
skills: [brand-voice, claims-file]
kortix_permissions: [project.file.read] # no project.gitops.merge
policy:
default_mode: risk # a write waits for a person
approvals:
ttl: 4h # the default is 24h
policies:
- match: gmail.send_email
action: require_approval
The agent in that block reaches Gmail, reads project files, and holds no secret of its own. Its grant omits project.gitops.merge, so nothing it does lands on main without a person. default_mode: risk is what turns a send into a held call. The rule on gmail.send_email keeps that send held even if someone later loosens the default. Connector credentials are brokered server-side and never enter the machine, so the agent reaches the mailbox through an account the connector holds.
The change request is the record
When the agent drafts, the work goes onto a branch and opens a change request. The diff shows the claim line, the URL under it and the date the URL was read. If the price moved, the claims file and the copy change in the same diff and the reviewer sees both.
Approving a change request merges it, and a merged change request is final. git log claims.md then answers when a line changed, and the change request behind that commit holds the diff a person read before the merge. Edit the sentence a month later and the work arrives as a new change request with its own review, which puts the old line and the new one in front of whoever signs it off next.
Session work reaches main through a change request. Merge is default-deny for agents. An admin can grant project.gitops.merge. Without that grant, a person approves the merge on the Review page.
A hosted marketing tool keeps the current state of an account. You can see what the agent may do this afternoon. What it cannot show you is the claim beside the page it came from, at the version that shipped. A claims file and a change request do, and Kortix is open source. Read it, fork it, audit it.
What stays a person's job
Taste is not in the file. An agent can match the register and still produce a page with nothing worth reading in it. Somebody has to decide what the company is willing to argue in public.
Strategy is a call the file cannot make. Which launch, which market, which claim leads the page. The claims file limits what a sentence may assert, and choosing the argument stays with the team.
The line nobody verified is the one to watch. Every claims file has a line someone added from memory, and the date next to it is the day it was typed. That line needs a person to open the source and read it, which is the reason the file has a reviewer at all.
The HR agent post shows the same pattern applied to a policy library, and the Slack install post covers the grants behind a channel. If failure modes are the part you are mapping, reliable agent workflows is the longer read.
More from the blog
An AI agent for HR has to read the policy library your team wrote
HR agents in the big suites answer from their own product's data, while an open-source agent reads your policy library and drafts the reply.
An open-source AI Operating System should keep a sales agent's queue small
Every demo of an AI sales agent sells you the reply. It lands in seconds, in a voice close to your team's, and the only decision left is whether to press send