AI agents run most of the engineering work on Matador, a live options-trading platform. A mistake here ripples outward in milliseconds: a bad fill, then a red candle you could see from orbit, then real dollars gone before the stack trace finishes printing. One bad line, tsunami.
Every day, without me:
- they groom the backlog and start the work that is ready;
- they read production logs and surface anything new;
- they repair the market-data feed before I know it broke;
- they patch security advisories the morning they land.
They have never touched the six files that could cost me money. That sentence is the whole article: getting agents to do work is easy, getting them to never destroy anything is the job. Below are the rules and the templates.
Value first. Paste this into your coding agent now. You will have a plan for your repo by the time you finish reading:
Read this article: https://norik.io/posts/ai-agents-trading-platform Then read my repo and give me a plan to automate a TPM for it. Be specific to what you find here: 1. What my north star doc should say, and where it lives. 2. Which labels my issue tracker needs, what each one means, and which issues are mislabelled now. 3. Which files belong on the protected list - the ones where a bad change costs money or data - and why each one. 4. What "verified" means in this repo, as the exact commands (typecheck, lint, build, tests). 5. Which of my recurring production errors belong in a known-error catalog, and what each one is. 6. What should run daily, weekly and monthly. Then write the skill file and the memory index, filled in for this project. Change no code. Open no pull requests. Show me the plan first, and ask me questions wherever you need a decision from me rather than guessing.
The AI Product Manager Is a File in the Repo
Your product manager should be a file, not a chat window. Mine is markdown in the repo, versioned beside the code it manages and reviewable in a pull request. Its modes: daily, groom, prioritize, critical-path, dispatch, deps, security.
Rule one: three sources of truth, and they never overlap.
- North star doc — direction, in stages with exit criteria. The agent proposes edits. I ratify them.
- Issues and milestones — the only place status lives. Milestones are the north-star stages.
- Plan docs and changelogs — reasoning and history. Never status.
That third line kills a class of drift. A roadmap file that says “in progress” is a lie within a week. An issue that says it is a fact you can query.
Rule two: every run starts from the same read-only snapshot. Issues, PRs, branches, commits, the dependency audit. Same shape every morning, so no two runs disagree about what is true.
Rule three: read code from origin/main, never the working tree. My checkout is usually twenty commits behind and shared with other sessions. A citation from a stale tree is a hallucination with a line number on it — the most convincing kind.
Copy it. Every project-specific fact sits in one table at the top; nothing below it changes between repos.
--- name: project-manager description: > Technical program manager for this repo. Owns the north star and keeps every open issue, plan doc and maintenance chore in order behind it. Use for: what to work on next, the backlog, priorities, grooming, the critical path, milestones, planning, starting work on an issue, outdated packages, security, or project status. argument-hint: "[daily|groom|prioritize|dispatch]" --- # Project Manager ## Project facts Fill these in. Nothing else below is project-specific. north_star: docs/NORTH_STAR.md approver: <name> protected_files: <path>, <path>, ... verify_with: <typecheck> <lint> <build> <test> worktree_root: .claude/worktrees/ agent_cap: 2 ## Sources of truth - **North star doc** - where the project is going, in stages with exit criteria. You propose edits; the approver ratifies. - **Issues + milestones** - the ONLY place that carries status. Milestones are the stages. Label, comment, set milestones, and close *with evidence*. - **Plan docs, changelogs** - reasoning and history, never status. ## Modes daily (default) - groom - prioritize critical-path - dispatch [#n] - deps - security Every mode starts from one read-only snapshot: issues, PRs, branches, commits, the dependency audit. So every run reasons from the same evidence. Read code state from origin/main in its own worktree, never the working tree - a citation from a stale checkout is a hallucination with a line number on it. ## Grooming - Every open issue is either actionable or a decision. Actionable means a What, a `## Do`, and enough file references to start in ten minutes. - Close by evidence, never by vibe. Cite the merged commit as the closing comment. - Never close a `decision`. Make deciding cheap instead: options, a recommendation, what it blocks, what happens by default. - Do not churn. Relabel when a label is wrong, not when it is merely imperfect. - Dead citations are bugs: a reference to code that moved sends the next reader on a detour. ## Prioritizing Hard gates first - safety, live security exposure. Then north-star contribution, blast radius and evidence of pain, discounted by effort. Output a ranked Next 10, one line of "why here" each. Stability beats precision: move an item only when a FACT changed - a decision landed, an incident happened, a prerequisite shipped - and name the fact beside the move. ## Dispatch Eligible = P0/P1 and none of: - no `## Do` section - names a protected file - asks for a plan or a design - open blockers in its `Blocked by:` line - already referenced by an open PR or branch - labelled decision, blocked, or in-progress Print two lists - "launch now" and "not dispatched, and why" - and put the second in the report verbatim. Never widen eligibility by judgment: if a rule is wrong, change the rule, in a PR, where someone can review it. ## Guardrails - read before acting These exist because each one has already cost real work or real money. - Protected files are absolute. You never edit them, no agent you launch edits them, and an issue naming one goes to *Needs you* for a per-file OK. Write this rule in three places: the agent-instructions file, this skill, and the dispatch script. An instruction in one place is a suggestion. - You open PRs; the approver merges. Never merge, never push to the default branch, never delete a branch - report merged ones as a list. - Never touch the shared checkout. No checkout, switch, stash or reset: other sessions are using it. Work happens in a fresh worktree. - No production writes. You read the repo and the issue tracker. - Surgical edits only: labels, milestones, `Blocked by:`, a `## Do` section, checklist ticks, comments. Never rewrite anyone's prose. - Read-only when unsure. If the snapshot fails or the tracking issue looks hand-edited in a way you do not understand, report and stop. - First-run gate. Creating milestones, relabelling dozens of issues and launching agents are a lot of outward-facing change: present the plan and get a yes. Security patches are the one exception - the patch is the point. ## Report *Needs you* first - decisions, approvals, agents that returned without a PR. Then the critical path, what you did, maintenance, the watch list. Keep it short; the tracking issue is the long form. End every run with one status line, always the same shape, so a month of them reads as a trend: PM <date>: <open> open - P0 <n> - P1 <n> decisions <n> - audit C<n>/H<n> agents <running>/<cap> - needs you: <n>
GitHub Issues as the Only Source of Status
If one place holds status, it has to be machine-readable. GitHub drops this repo’s issue types silently, so labels carry every classification — and the set stays small enough that each one has a job.
P0 Capital safety or live security exposure
- before anything else
P1 Reliability, or a blocker on the path to
the next stage
decision Not work - a call a human makes
backlog An umbrella checklist, not an item
blocked A "Blocked by:" line names an open
issue
in-progress An agent was dispatched; a comment
names the branchDeclare dependencies, do not describe them. Somebody writes “after we land the transaction work” in a body; the groom turns it into a Blocked by: line. Now the critical path is the longest chain of those edges, and its head — the one item unblocked right now — is what to work on today. No meeting required.
Close by evidence, never by vibe. An issue closes when the agent can cite the commit that delivered it, posted as the closing comment. Never “this looks done.”
Never let the agent close a decision. Its job there is to make deciding cheap: the options, a recommendation, what it blocks, and what happens by default if nobody decides.
Dispatch Rules and Protected Files
A planner that only plans is a diary. A script computes eligibility and prints two lists: launch now, and not dispatched, and why. The second goes into the report verbatim, so every exclusion is visible.
An item is eligible only if it is P0 or P1 and none of these holds:
- it is a decision, an umbrella, blocked, or already in flight;
- it has no “Do” section — the agent would be guessing at scope, and writing that section is the TPM’s own work, so it dispatches tomorrow instead;
- it asks for a plan, because a plan is a conversation and not a pull request;
- a branch or open PR already references it — two agents on one issue is how you get competing branches nobody can merge.
Eligibility is never widened by judgment. If a rule is wrong, change the rule — in a pull request, where someone reviews it. An agent that can reason past its own eligibility check does not have one.
Six files are permanently off-limits: the coordinator that turns a signal into an intent, the worker that runs it, the client that routes live versus paper, live execution, paper execution, pricing. Change one badly and you get a fill.
The rule is written three times: in the agent-instructions file every coding agent reads on entry, in the TPM’s guardrails, and in the dispatch script as a mechanical exclusion that routes any issue naming one of those files to Needs you.
Three places, because an instruction in one place is a suggestion. The prose explains the boundary; the script makes crossing it impossible by accident at three in the morning. A seventh file joined later by incident — the set grows, it never shrinks quietly.
Production Error Triage Through an MCP
Production errors cost you twice: when they happen, and every morning somebody re-derives what they mean. Delete the second cost.
A daily sweep reads production logs through the BetterStack MCP. One column does the work: the store derives a pattern per line with dynamic parts stripped, so grouping by it collapses tens of thousands of near-identical lines into a handful of buckets. The agent reads fifteen buckets, biggest first, and labels each:
- Benign noise — count it, move on.
- Known and actionable — is it worse than baseline?
- NEW — not in the catalog. Top of the report, with an example.
- SPIKE — a known-benign pattern at abnormal volume. This is the one that catches real incidents.
A nominal day is one line. A daily report nobody can skim is a daily report nobody reads.
TRIAGE <date>: <total> patterns | <new> new | <spikes> spiking | <actionable> actionable
The sweep observes and proposes. It never acts. No pull requests, no pushes, no config changes. A recurring new pattern becomes a proposed catalog row, and it asks first. If the MCP is not attached it falls back to runtime logs and says so, instead of quietly reporting a narrower window as the whole history.
The catalog is the asset. Without it, triage re-derives the same conclusions every morning and gets them wrong about one morning in five. The row that saved me the most time describes an error-level alert that looks exactly like production — until you check the stack and find a path under a user’s home directory and a fixture account number. A local test run, shipping into the production log source, firing a real alert. Hand an agent that error with no catalog and it investigates for an hour, then writes a convincing postmortem of something that never happened.
Self-Healing Infrastructure: Four Steps to Recovery
An alert that means “a human must fix this” does not scale. Build the ladder, and the alert changes meaning.
What happened. The shared market-data socket went quiet ten minutes after the open. The price path noticed, forced a reconnect, and trading recovered. Quote capture never did: seven hours of staleness alarms while the day’s bars became a hole in the store.
Why. A forced reconnect builds a new feed object. The capture service had attached its listener once, guarded by a boolean nobody reset, so every tick re-added subscriptions to the new feed while the listener stayed bound to the dead one. Bars were flowing. Nobody was listening.
The fix is a ladder:
- Rebind on replacement — a changed feed reference heals within one tick.
- Re-arm subscriptions every tick, in paced groups — not because subscriptions vanish often, but because they vanish partly. Sent as one burst, a whole watchlist only partly lands, and an unpaced retry loses the same chunk every time.
- Repair backfills driven by staleness — pulled through the one-shot bulk path, which attaches its own listener to the current feed and works even while the stream is broken.
- Bounded auto-reconnect — triggered when two consecutive repairs of the lead symbol come back empty, floored by a cooldown. The feed is shared with live trading, so recovery must never become a reconnect storm.
The payoff is the alert. It used to mean a human needs to reconnect this. Now it says recovery is running — and a repeated alert means the ladder itself is failing, a rarer and more interesting page. The catalog row uses the same words, so the agent at 6am and the engineer at midnight reach the same conclusion.
The same shape turns up everywhere. The database pool’s error handler used to kill the process; now it logs and lets the pool re-establish. At the trading layer, plain-English rules watch open positions and act before a bad trade gets worse: trailing stops that tighten as price moves, daily loss limits, caps on time in a trade that is not working. Every adjustment is recorded with its reason. New rules start on paper.
Self-Upgrading Software: Daily, Weekly and Monthly Maintenance
Maintenance runs on a ledger, not on the weekday. A missed Monday still owes its weekly.
- Daily — the dependency audit. A critical or high advisory with a non-major fix becomes a pull request today, without asking: typechecked, linted, built, tested. A vulnerability is not a decision; the patch is the point. Never fix one by jumping a major version.
- Weekly — the bump batch, a groom, a re-prioritization, and a report of stale branches: a list, never a deletion.
- Monthly — a fact-check pass over the two or three specs least recently touched. Scheduled maintenance for the documentation itself, and the most underrated line in the cadence.
New capability earns its way out of the shadows. Pre-trade advice ran as a dry run first: thousands of signals observed, every piece of advice it would have given recorded, nothing acted on. It graduates only after being right in the dark long enough to argue about. The exit logic shipped the same way.
The ceiling, everywhere: pull requests are opened, never merged. Autonomy ends where something becomes irreversible.
Stability beats precision. A priority order that reshuffles every morning teaches everyone to ignore it, so an item moves only when a fact changed — and the report names the fact next to the move. When the snapshot fails, or the tracking issue looks hand-edited in a way the agent does not understand, the run reports and stops.
The Documentation Layers, and What Each One Owns
One folder of equally-weighted markdown is where drift starts. Nothing in a file tells you whether it is a promise, a record or a leftover, so an agent reads a two-year-old plan as fact and builds on it.
Every document declares, in its first lines, what genre it is, how much authority it carries, and when it was last true.
What each layer owns
- North star — direction only, in stages with exit criteria.
- ARCHITECTURE.md — the map: layering, data flow, where business logic is allowed to live. First thing anyone reads.
- Spec of record, one per subsystem, named after it — what is true right now.
- pipeline/ — the plan of record: phases, promotion gates, decisions reserved for a human.
- research/ — investigations that open by stating they changed nothing and naming the window they looked at. Explicitly not commitments.
- guide/ — user-facing docs, read at build time and rendered inside the product.
- design/ — mockups and prototypes.
- archive/ — plans whose feature shipped, moved rather than deleted, each stamped with the date and branch that delivered it.
- changelogs/ and postmortems/ — what shipped and what broke, with timelines.
- Audits — the date is in the filename, so staleness is visible without opening it.
A spec of record, in its first four lines
# Watchbird - Pre-Trade Signal Intelligence (Spec of Record) **Status:** Phase 0-0.5 (shadow) merged; Phase 6 enforcement landed 2026-08-21 for ACCOUNT-RISK rules only. Market-context rules remain SHADOW ONLY - they cannot change, delay, or drop a trade. **Roadmap:** pipeline/WATCHBIRD_PLAN.md - phases, promotion gates, infrastructure. **Siblings:** WATCHDOG_FREEFORM_RULES.md (the machinery Phase 5 clones), WATCHDOG_EXTENDING.md (shared invariants). ## 1. What this is The watchdog watches POSITIONS after entry. The watchbird looks at an incoming ENTRY signal before an order exists, and answers: what is the market context this trade is stepping into, and what would a careful trader do about it?
Four lines do the work. The title declares the genre, so nobody mistakes it for a plan. Status says which part enforces and which only advises, with the date — then states the negative outright: the shadow rules cannot change, delay, or drop a trade. That sentence stops a reader granting the system more authority than it has. Roadmap points at where it is going. Siblings point at the machinery it borrows and the invariants it shares.
An agent landing there knows what is true, what is planned, what it must not assume, and where to look next — before reading a line of code. A document that only described the feature leaves all four to inference, and inference arrives with full confidence.
Two later headings are worth stealing. Honesty guards — why a reading is null instead of wrong points the anti-hallucination rule at the product: when a metric cannot be computed, the system returns nothing rather than a plausible number. Extending — the seams, cheapest first tells the next person where to cut, in order. That is the difference between a spec that documents a system and one that hands it over.
When the shape is a diagram
Its sibling spec looks nothing like it, and that is the point: shape follows the question. Watchbird’s hard part is vocabulary — what each metric means and when a reading can be trusted. The watchdog’s is sequence, so its spec is one picture.
positionMonitorService tick - every 10s
|
v
mark fetched for an open position
|
v
peakTracker.observe() folds the mark into the
| high-water mark, seeded
| from the DB so a mid-day
| deploy cannot reset a peak
v
watchdogEngine.evaluatePosition(snapshot)
|
v
freeformEvaluator - THE DETERMINISTIC GATE
pure, runs every tick
scope match? all conditions hold?
|
no --+--> nothing happens
|
yes -+--> verdict
|
decision: decision:
"deterministic" "llm"
| |
| v
| per-(rule, position) cooldown
| |
| v
| the model, given: the trader's own
| sentence, the approved examples,
| every metric reading for this tick,
| and four verbs only -
| flatten | tighten | alert | hold
| 8s timeout, then no action
| |
| hold / error / timeout -> nothing
| |
| escalation guard: may de-escalate,
| never exceed the verb the trader
| authorised
| |
+--------------+
|
v
engine.dispatch()
- close position / update stop
- append-only ledger row
- in-app alertRead as documentation, it gives the order of operations. Read as a specification of authority, it says the model is never the first thing to run. A deterministic gate evaluates every position every tick, in pure code. The model is consulted only for rules that asked for judgment — then handed four verbs, a cooldown, an eight-second timeout that fails to no action, and an escalation guard that lets it soften but never exceed the verb the trader authorised. Every edge is a constraint somebody chose. A paragraph loses all of them.
Keeping each layer true
A plan whose feature ships moves to the archive that week. The monthly pass fact-checks the two or three specs least recently touched. Layering helps only while the layer a document sits in is still true.
How Documentation Stops AI Agents Hallucinating
An agent’s failure mode is confident wrongness. Give it a plausible field name and it writes a query around it, gets an empty result, and explains the empty result to you. Give it a tool name that no longer resolves and it invents an authentication step to explain the failure. None of it looks like a mistake while it happens. It looks like work.
Write documents that correct the reader
Not documents that describe the system. The logs runbook:
- lists the fields you can actually query — then says, in its own voice, that three fields it used to list are not in the catalog and must not be extracted;
- warns that the file and line attached to every log line point at the logging wrapper, not the real call site;
- records that the MCP’s tool names changed, lists every old one, and states plainly that each resolves to nothing;
- dates its verifications, so a reader knows how old each claim is.
That is a different genre: a list of the specific wrong turns a competent reader will take, written at the fork. Dead citations are bugs for the same reason — an issue pointing at a line where the behaviour no longer lives sends the next reader on a detour.
Memory: the corrections too small for a doc
One file per correction, behind an index that is the first thing any agent reads. Mine is scar tissue. Here are the first few, project specifics stripped:
# Memory
One file per memory, named <type>_<slug>.md:
feedback - how to work in this repo
project - ongoing work and constraints
reference - external facts worth caching
This index is the first thing an agent reads.
One line each: what it is, and the hook.
- [Never push to the default branch](feedback_no_push.md)
- ask first, every time
- [Keep the long-lived branches](feedback_keep_branches.md)
- exclude them from any cleanup
- [No branch switch mid-pipeline](feedback_no_switch.md)
- it kills the running batch
- [Everything is insert-only](feedback_immutable.md)
- never UPDATE; newest row is the current value
- [<file> is protected too](feedback_protected_extra.md)
- ask before editing, though it is not on the
explicit do-not-modify list
- [Build hits the live database](project_build_prod.md)
- a local build's first tick runs against prod;
make new queries safe on first run
...Each file holds one fact, why it exists and how to apply it. The index loads before the agent does anything, so a rule is in context when it matters, not in a document nobody opened. Three entries, three jobs:
- Never push to main without permission — stops an irreversible action. One line, written the day it was needed, that removes a whole class of mistake from every session after it.
- Everything is immutable — stops plausible, wrong code. Nothing in the schema prevents an agent writing a perfectly reasonable UPDATE, and it would pass review. It would just quietly overwrite the history the rest of the system reads back.
- The pricing helper is protected pipeline — amends the rules themselves. That file was never on the original protected list, and the one-line memory that added it is why nothing autonomous has touched it since.
One stops an action. One stops code from being written. One grows the guardrails. A correction made in conversation is gone by the next session; written to a file, it is the first thing the next session knows. The model forgets. The record does not.
The same rule, pointed at users
The product ships its documentation under the same rule: what happens between an alert firing and an order existing, stated plainly, with the cost of each behaviour named instead of its implementation. The page gets rewritten whenever it drifts back into the codebase’s voice — internal flag names in a user guide mean the doc stopped being written for the reader.
Why this is the part that compounds
The compounding asset is the written record. The agents and the prompts get rewritten every few months. Every sweep adds a catalog row. Every incident adds a correction. Every groom turns prose into a queryable field. Tomorrow’s agent starts from a better description of reality than today’s did, because of what today’s agent wrote down. That is the real self-upgrading part. The dependency pull requests are housekeeping.
What These Agents Never Do
The limits, stated plainly:
- The sweep needs my laptop. A cloud routine exists and is disabled: a cloud checkout inherits none of what reading production logs requires, so it could only report “no log source reachable”. The reason is written next to the switch.
- No SQL against production. No production state changes.
- Decisions are mine. Merges are mine.
- The first run of anything outward-facing waits for a yes — security patches being the one exception, because the patch is the point.
This spends my judgment on the decisions, the merges and the six files, by refusing to spend it on grooming, triage, dependency patches, or remembering that a particular error at six in the morning is a laptop pretending to be production.
The leash is what makes the autonomy usable.