Most tooling we build is wrong on the day we ship it. Not broken, just wrong in the small ways that only show up once you’re using it for real work. The friction is small enough to absorb, every single time. The tool stops getting better, and work routes around it.

We establish a habit that is to treat the tool as fixed and the work as flexible, when usually the more effective move is the other way around. If the same five-second hesitation shows up twice in a session, the tool is the thing that needs the edit, not your patience.

The interesting version is what happens when the tool is an AI agent and its “spec” is its instruction file that itself can read.

The claim

AI agents and skills should treat self-reflection and gated self-modification as a generic step in the workflow. Every invocation should be able to ask, “what in my operating model didn’t fit this run, and what would I change?”

I’m going to walk through one area with my Second Brain where I lived this end-to-end, then explain why I think the shape generalizes, what I’d want it to look like in agents I haven’t built yet, and where it falls over.

A daily journal agent that rewrote its own instruction

I continue to iterate on my Second Brain structure, and a while back I shipped a new format for intra-day context capture in my daily journals. The idea was that instead of waiting until the end of the day to write up what happened, I would capture decisions, blockers, and corrections as they happened, timestamped, into a section of my daily note. The first iteration was a topic-grouped format.

With the standard set, my existing Daily Journal agent would just read the new template and use it. Within the space of a morning, the frictions appeared. And they weren’t necessarily bugs, they were just things I hadn’t considered the AI would do.

  • A correction would come in and the agent didn’t know whether to overwrite the original entry or append a new one with an explicit “correction” tag.
  • A decision touched two projects and the format didn’t help AI decide where to put it.
  • A status change at early in the morning and another in the afternoon were both the same project, but felt structurally different and the agent couldn’t work out what to do with that.
  • Half the entries had a precise timestamp, half were fuzzy “this morning” records.

By the end of the day, I was shipping the next iteration of this process. The bit that matters for this post is who proposed the update. It wasn’t me, it was purely the agent operating under that first iteration, asked one question at the end of the day: what in your current operating spec didn’t fit today’s work?

The agent had been the one hitting the friction. It had the friction log in session memory. It was in the best possible position to draft the update. I ratified, tightened a couple of clauses, and the agent committed an update to its own instruction.

The loop closed within the day, and it closed because the agent could read its instruction, propose changes against it, and have a human review it.

What this would look like elsewhere

Treat what follows as the shape I hold. But the shape is general enough, and one that I’ve been applying across all my agents and their skills for some time.

Imagine a code review agent that lists categories it will and won’t comment on, severity thresholds, and a built-in style for suggested fixes. A reasonable implementation, probably copied from a template.

You run it on a real PR. Three things happen that the agent didn’t decide.

  1. The author already addressed the agent’s “Must Fix” findings in the PR description, but the instruction didn’t tell the agent to read the description before commenting, so it re-raises them anyway.
  2. A finding sits awkwardly between “nit” and “suggestion.” The instruction doesn’t explain this and the agent raises a suggestion, which inflates the severity of the review comments.
  3. The agent suggests a refactor in a file that’s flagged elsewhere in the repo as frozen. The spec doesn’t know about frozen-file conventions.

In the standard model, you might notice these, fix the comments inline, and the agent stays unchanged. Tomorrow the same agent makes the same three mistakes on a different PR. The cost of changing the instruction was minutes. The cost of absorbing the friction is paid every PR for as long as the agent goes unchanged.

In the self-reflection model, the agent’s last step on this run should be to ask itself the reflection question. And the expectation is that it produces a structured proposal: read the PR description before commenting and skip findings already acknowledged. Add a “nit” severity that defaults to suppressed but is surfaceable on request. Read the repo’s frozen-files convention if one exists. Three proposed diffs against its own instructions, presented as suggestions, gated on your approval. You glance, you accept two, you reject one, you commit. The next run is meaningfully different.

Why this isn’t just dogfooding

Dogfooding gets you most of the way there. Use your own tool, hit its friction, fix it. That’s been good advice for as long as anyone has built tools. What changes with agents is the asymmetry between noticing friction and proposing the change.

In an agent loop, noticing is still the human’s job (or the agent’s, if the friction is internal), but proposing can be delegated. The agent already speaks its own language, and it has the session context. The cost of producing a high-quality edit collapses to simply asking the reflection question. That asymmetry is what turns the loop from a habit you have to maintain into a default that runs every time.

However, agents proposing edits to their own contract is the right shape only if humans ratify. Silent self-modification is how you end up with an agent that has drifted away from anything you’d have approved and you can’t tell where it went wrong. Version them like code, commit each iteration with a message pointing at the friction it answered, and you have both the leverage and the auditability.

Putting agentic self-reflection into practice

Four things, in roughly the order I’m adding them.

  1. Make self-reflection a generic step. Every agent invocation ends with the same prompt to itself: what in your operating spec didn’t fit this run? Empty answers are fine and common. The point is that the step is always there, so you never have to decide to ask.
  2. Capture friction as structured artefacts. A friction note should at minimum identify the section it touches, the specific case that didn’t fit, and an example of what would have fit. This is what makes proposals tractable.
  3. Let the agent propose; do not let it auto-apply. A proposed diff, an inline suggestion, a structured callout in its instruction. Whatever your tooling supports. The point is that the proposal is cheap and the application is gated. The human ratifies, edits, or rejects, and only then does the agent change its behavior.
  4. Version your agents like code. Discrete commits, messages pointing at the friction. This gives you the ability to revert an iteration that over-fitted without losing the original friction signal, and it gives future agent invocations the traceable why behind the current shape of its instruction. That “why” is the most valuable context the next iteration can have.

When this doesn’t apply

Two cases come to mind where the loop doesn’t close cleanly, and one where it shouldn’t.

The first is when the agent isn’t yours to evolve. If you’re operating under a contract someone else owns, you can capture friction but you can’t ratify. The mitigation is the same as it’s always been: upstream the friction (a GitHub issue, a PR, a conversation), or wrap the foreign contract in a thin layer that is yours to evolve and apply the loop there. The wrapper gets the iteration. The wrapped thing doesn’t.

The second is when the friction is incidental rather than structural. Not every awkward run is evidence of a problem. Sometimes the run was awkward because the work was awkward. The rule of thumb I use: if the same friction has surfaced multiple times, it’s structural and the instruction needs an edit. Just once, well finish the work and move on.

The case where it shouldn’t apply is high-stakes agents operating on shared contracts. An agent shared across a team, or one whose instruction is itself a policy other systems depend on, needs a slower loop with explicit review and change-control. The asymmetry I’ve been celebrating (cheap proposal, gated apply) is right for early iterations and small-team agents. It is not a substitute for a large scale change process when the blast radius is wider.

Summary

The thing I keep coming back to is that AI agents and skills are just a markdown file. It’s a file that the agent reads, that you can edit, and that lives in a git repo. That’s a profoundly low-ceremony substrate for self-modification, and it’s the reason the loop can close so quickly.

We’ve often treat agents as configuration. But they’re closer to source code, and they deserve the same iteration discipline source code gets, including the version history that lets you ask later: what did we learn that made us write it this way?

If the instruction hasn’t changed in a month and you’ve been using the agent every day, either it is unusually good or, much more likely, the friction is being absorbed silently. The bet I’d take is the second one, almost every time.


Discover more from James Croft

Subscribe to get the latest posts sent to your email.

James Croft

Author James Croft

James is a senior software engineer at Microsoft with over 10 years experience designing and building large-scale, distributed, cloud-native systems. He's deeply experienced in C#, Python, and TypeScript, with specialization in AI agent architectures, retrieval-augmented generation (RAG), and production-grade Azure systems.

More posts by James Croft

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.