Effortless Dart Coding with dart_extensions_pro


Introduction
Introducing dart_extensions_pro a Dart package that offers a collection of handy extensions and helper functions designed to enhance the development process. By simplifying common tasks and providing streamlined solutions, it allows developers to write code more efficiently and focus on building features rather than repetitive tasks. Ideal for improving productivity, this package is a valuable tool for both novice and experienced programmers.

Key Features
📊 Comparison: Simplify comparison operations with intuitive extension methods.

📅** Date Handling:** Effortlessly manage date and time with a variety of helpful functions.

✍️ String Utilities: Enhance string manipulation with powerful utility functions.

📋 List Enhancements: Improve list handling with convenient extensions for common operations.

🧭 Navigation: Streamline navigation tasks with specialized navigation functions.

👆 Tap Gestures: Easily handle tap gestures to improve user interaction.

🔁 Iterable Enhancements: Optimize iterable processing with enhanced methods.

🎨 Color Conversion: Simplify color manipulations and conversions with dedicated functions.

🔢 Number Utilities: Access a range of number-related utilities for calculations and formatting.


🛠️ Utility Functions: Utilize various handy utility functions to simplify your coding experience.

Installation
Add dependency to your pubspec.yaml file & run Pub get

dependencies:
  dart_extensions_pro: ^0.0.1

And import package into your class file

import 'package:dart_extensions_pro/dart_extensions_pro.dart';
**Analytics**

Visit EXTENSIONS.md for a complete list of all the available extensions.

Extensions:                    271
Helper Classes:                7
Helper Functions & Getters:    21
Typedefs:                      7
Mixins:                        2

Here’s a quick preview of dart_extensions_pro,
String extension

'hello'.iscapitalize(); // Capitalizes first letter // Hello
'Copy this text'.copyTo(); // Copies string to clipboard
'test@example.com'.isValidEmail(); // Checks if valid email // true
'flutter'.reverse(); // Reverses string // rettulf
'madam'.isPalindrome(); // Checks for palindrome // true
'flutter example'.toCamelCase(); // Converts to camel case // FlutterExample
'{"name": "Flutter"}'.decodeJson(); // Parses JSON string to map // {name: Flutter}

Comparison extension


5.gt(3);  // true, checks if 5 is greater than 3
3.lt(5);  // true, checks if 3 is less than 5
5.eq(5);  // true, checks if 5 is equal to 5
3.lte(3); // true, checks if 3 is less than or equal to 3
5.gte(3); // true, checks if 5 is greater than or equal to 3
5.ne(3);  // true, checks if 5 is not equal to 3

Date extension

DateTime.now().isSameDate(DateTime(2023, 9, 14));  // true, checks if today matches the provided date
DateTime.now().isToday();  // true, checks if today is today
DateTime.now().isTomorrow();  // true, checks if today is tomorrow (unlikely)
DateTime.now().wasYesterday();  // true, checks if today is yesterday (false)
DateTime.now().addDays(5);  // adds 5 days to the current date
DateTime.now().addMonths(3);  // adds 3 months to the current date
DateTime.now().addYears(2);  // adds 2 years to the current date
DateTime.now().subtractDays(7);  // subtracts 7 days from the current date
DateTime.now().subtractMonths(1);  // subtracts 1 month from the current date
DateTime.now().subtractYears(1);  // subtracts 1 year from the current date

List extension

final list = [1, 2, 3] << 4;  // [1, 2, 3, 4], appends 4 to the list using the `<<` operator
list.replaceFirstWhere(10, (item) => item == 2);  // true, replaces the first occurrence of 2 with 10
list.replaceLastWhere(20, (item) => item > 1);  // true, replaces the last item greater than 1 with 20

Navigation extension

context.to(MyPage());  // Navigates to `MyPage` using `to()`
context.toNamed('/home');  // Navigates to the named route '/home' using `toNamed()`
context.back();  // Pops the current route using `back()`
context.backUntil((route) => route.isFirst);  // Pops routes until the first one using `backUntil()`
context.toWithReplace(AnotherPage());  // Replaces current route with `AnotherPage` using `toWithReplace()`
context.replaceWithNamed('/dashboard');  // Replaces the current route with named route '/dashboard' using `replaceWithNamed()`
context.toAndRemoveAll(HomePage(), (route) => false);  // Navigates to `HomePage` and removes all previous routes using `toAndRemoveAll()`
context.toNamedAndRemoveAll('/login', (route) => false);  // Navigates to named route '/login' and removes all previous routes using `toNamedAndRemoveAll()`

Gesture extension

widget.onInkTap(() => 'Tapped!'.logMsg());  // Adds an ink splash effect with `onInkTap()`
widget.onTap(() => 'Tapped!'.logMsg());  // Adds a basic tap gesture with `onTap()`
widget.onDoubleTap(() => 'Double Tapped!'.logMsg());  // Adds a double-tap gesture with `onDoubleTap()`
widget.onTapCancel(() => 'Tap Cancelled!'.logMsg());  // Adds a tap cancel gesture with `onTapCancel()`
widget.onLongPress(() => 'Long Pressed!'.logMsg());  // Adds a long press gesture with `onLongPress()`
widget.onTapDown((details) => 'Tap Down!'.logMsg());  // Adds a tap down gesture with `onTapDown()`
widget.onScale(
  onScaleStart: (details) => 'Scale Started!'.logMsg(),
  onScaleUpdate: (details) => 'Scaling!'.logMsg(),
  onScaleEnd: (details) => 'Scale Ended!'.logMsg(),
);  // Adds a scale gesture with `onScale()`

Iterable extension

iterable.lastElementIndex;  // Returns the index of the last element or -1 if empty.
iterable.hasSingleElement;  // Checks if the iterable has exactly one element.
iterable.addAllMatchingTo(targetList, (e) => e.isEven);  // Adds elements matching the predicate to the target list.
iterable.whereFilter((e) => e.isEven);  // Filters elements matching the predicate.
iterable.whereFilterIndexed((index, e) => index % 2 == 0);  // Filters elements with their index.
iterable.mapTransform((e) => e.toString());  // Transforms each element and maps to a new iterable.
iterable.skipElements(2);  // Skips the first 2 elements.
iterable.takeLastElements(2);  // Takes the last 2 elements.
iterable.skipWhileElements((e) => e < 5);  // Skips elements while the predicate is true.
iterable.skipLastElements(2);  // Skips the last 2 elements.

Color conversion

String.toColor();  // Converts a hex color string to a Color object, assuming full opacity.
HexColor.getColorFromHex(hexColor);  // Converts a hex color string to an integer color value, adding alpha if missing.
HexColor(hexColor);  // Creates a HexColor instance from a hex color string.

Number conversion


num.negative;  // Converts positive numbers to their negative counterparts.
num.isBetween(value1, value2, {inclusive = false});  // Checks if [this] is between [value1] and [value2], inclusive if [inclusive] is true.
num.roundToDecimals(decimalPlaces);  // Rounds the number to [decimalPlaces] decimal places.
double.asRadians;  // Converts degrees to radians.
double.asDegrees;  // Converts radians to degrees.
T.maxim(upperBound, {exclusive = false});  // Limits the value to [upperBound], exclusive if [exclusive] is true.
T.minm(lowerBound, {exclusive = false});  // Ensures the value is not less than [lowerBound], exclusive if [exclusive] is true.
T.clampAtMin(lowerBound);  // Ensures the value is not below [lowerBound].
T.clampAtMax(upperBound);  // Ensures the value does not exceed [upperBound].
num.orZero;  // Returns this value or 0 if null.
num.orOne;  // Returns this value or 1 if null.
num.or(value);  // Returns this value or [value] if null.

Utility conversion

double.isWhole;  // Checks if the value is a whole number.
double.roundToPrecision(nthPosition);  // Rounds the value to [precision] decimal places.
bool.isCloseTo(other, {precision = 1.0e-8});  // Checks if the value is close to [other] within [precision].
double.randomDouble({max});  // Generates a random double between 0.0 (inclusive) and 1.0 (exclusive).
int Duration.inYears;  // Returns the number of whole years spanned by this [Duration].
bool Duration.isInYears;  // Returns `true` if the [Duration] is equal to or longer than one year.
int Duration.absoluteSeconds;  // Returns the number of seconds remaining after accounting for whole minutes.
void Map<K, V>.operator <<(MapEntry<K, V> entry);  // Inserts a [MapEntry] into the map using the `<<` operator.
String Map<K, V>.toJson();  // Converts the map into a JSON string.

For more information, check out the below link

dart_extensions_pro | Flutter package

A Dart package that provides handy extensions and helper functions, designed to simplify and speed up development, making coding more efficient and streamlined.

favicon
pub.dev


Thanks for reading! If this post was helpful, feel free to share it and follow for more Flutter tips and tutorials. Keep coding and stay awesome!

Spring AI Explained — ChatClient, RAG, Advisors, and Every Core Component

Most Spring AI tutorials jump straight to code. You copy the dependency, paste the config, call ChatClient, and something works. But when you need to actually build something — a chatbot that remembers conversations, an API that answers questions from your own documents — you hit a wall. Because you don’t know what’s actually doing what. Friend’s Link

What Spring AI actually is — in one sentence

Spring AI is an abstraction layer that lets you wire LLMs into your Spring Boot app without hardcoding any particular AI provider.

That last part matters. OpenAI, Google Gemini, Anthropic Claude, and Ollama are running locally on your machine — Spring AI talks to all of them through the same API. Swap providers without touching your business logic. That’s the entire value proposition, and everything else is built on top of it.

Spring AI Components

ChatClient — the front door
ChatClient is the component you’ll interact with the most. It’s the fluent API that sits at the top of the stack and handles the actual request-response cycle with the LLM.

Think of it like a RestTemplate or a WebClient— but instead of calling a REST endpoint, you’re sending a prompt and getting a response back. It handles all the low-level connection details, request formatting, and response parsing so you don’t have to.

What makes ChatClient genuinely well-designed is its fluent builder style. You don’t configure it once globally and hope for the best. Each call is composable — you can set the system prompt, attach advisors, pass user input, and control the output format all in one readable chain.

It also separates two things that often get conflated: the default configuration you set at startup (your system prompt, default advisors, model parameters) and the per-request configuration you apply at call time. That separation matters in production, where different endpoints need different behaviours from the same underlying client.

PromptTemplate — how you talk to the LLM properly

A raw string shoved into an LLM is not a prompt. A prompt is a structured piece of text with placeholders, context, and instructions — and this PromptTemplate is how Spring AI handles that.

The idea is simple: you define a template with variables, and at runtime, you fill those variables in. Instead of building prompt strings with Java string concatenation — which gets messy fast — you define the shape of the prompt separately from the data that goes into it.

This matters for three reasons. First, it keeps prompts readable and maintainable. Second, it separates the “what to ask” from the “what data to inject” which is the same separation concerns you apply everywhere else in your codebase. Third, it makes prompt versioning possible. When your prompt needs tweaking, you’re editing a template, not hunting through business logic.

PromptTemplate also gives you a proper Prompt object that carries both the human message and the system message. That distinction — system prompt (the instructions) vs user prompt (the question) — is one of the most important things to understand when working with LLMs, and Spring AI models it explicitly.

EmbeddingModel — the piece that makes search smart

An EmbeddingModel takes text and converts it into a vector — a list of floating point numbers that represents the meaning of that text in multi-dimensional space.

That sounds abstract. Here’s the concrete thing to grasp: two pieces of text that mean similar things will produce vectors that are close to each other mathematically. “What’s your return policy?” and “How do I get a refund?” are different strings, but their vectors will be very close — because semantically, they’re the same question.

This is what makes semantic search possible. Traditional search matches keywords. Embedding-based search matches meaning. A user asking “how do I cancel my order” will find a document titled “Order cancellation policy” even if the words don’t overlap, because the meanings are geometrically close in vector space.

In Spring AI, EmbeddingModel is the interface that abstracts over whatever embedding service you’re using — OpenAI’s text-embedding-ada-002, Gemini’s embedding API, or a local model via Ollama. The abstraction is consistent regardless of provider, which means your RAG pipeline doesn’t break if you switch models.

VectorStore — where embeddings live

VectorStore is the database for embeddings. You put vectors in, and you query them by similarity — “give me the top 5 stored vectors that are closest to this query vector.”

It’s worth understanding that this is a fundamentally different kind of database from what you’re used to. You don’t query it with SQL. You don’t look things up by ID. You ask: which stored content is most semantically similar to this input? And it returns the matches ranked by similarity score.

Spring AI’s VectorStore interface abstracts over the actual storage engine underneath. In development, you might use SimpleVectorStore an in-memory implementation. In production, you’d swap to Pinecone, Weaviate, pgvector on top of Postgres, or Elasticsearch. The interface stays identical. Your code doesn’t change.

The VectorStore is also responsible for handling the metadata that travels alongside each vector — the document title, page number, source URL, whatever you stored at ingestion time. When it returns matching chunks, that metadata comes with it, so your prompt builder knows where the information came from.

Advisors — the middleware nobody talks about enough

This is the component most tutorials skip, and it’s arguably the most powerful part of the whole framework.

An Advisor in Spring AI is a piece of middleware that wraps around every ChatClient request. Before the request goes to the LLM, advisors can intercept it and modify it — add context, inject memory, apply safety rules, log the conversation, filter the input. After the response comes back, they can post-process it too.

The important thing to understand is that advisors form a chain. Each one wraps the next, like servlet filters in a web application. You configure which advisors run in which order, and each one has a defined responsibility.

QuestionAnswerAdvisor is the one you’ll use for RAG. Before your question reaches the LLM, this advisor takes that question, queries VectorStore for the most relevant chunks, and injects them into the prompt automatically. From ChatClient’s perspective, you just asked a question. Internally, your question has been enriched with your own data before the LLM ever sees it.

MessageChatMemoryAdvisor is what makes conversations persistent. Without it, every call to ChatClient starts fresh — no memory of what was said before. With it, previous turns from ChatMemory are injected into each new request so the LLM has context.

You can write your own advisors too. Any cross-cutting concern that applies to every LLM call — rate limiting, PII detection, response caching, A/B testing between prompts — belongs in an advisor, not in your business logic.

ChatMemory — giving the LLM a memory

LLMs are stateless. Every API call is completely independent. Ask an LLM “what’s the capital of France,” then ask “what did I just ask you,” and it has no idea — because, from its perspective, that second request is the first thing you’ve ever said.

ChatMemory is how Spring AI solves this. It’s a storage abstraction for conversation history. After each exchange, the message — both the user’s question and the LLM’s response — gets saved. On the next request, that history gets loaded and injected into the prompt so the LLM has context.

InMemoryChatMemory is the default — history lives in your application’s heap and disappears on restart. That’s fine for development and short stateless sessions. For production chatbots that need to remember users across sessions, you’d implement a persistent ChatMemory backed by Redis or a database.

There’s a real constraint here worth knowing upfront: every message you inject into the conversation history costs tokens. LLMs have a context window limit — usually somewhere between 8K and 128K tokens, depending on the model. If a conversation goes on long enough, the accumulated history will either exceed the limit and fail, or you’ll need to implement a summarisation strategy to compress older messages.

This is not a Spring AI problem — it’s a fundamental LLM constraint. But ChatMemory is where you manage it.

RAG Flow

How RAG brings it all together
RAG — Retrieval-Augmented Generation — is the pattern that makes Spring AI genuinely production-useful. The diagram above shows both phases. Here’s the thinking behind it.

The core problem: your LLM knows nothing about your company. It doesn’t know your product documentation, your internal policies, your customer data. Fine-tuning a model on your data is expensive, slow, and goes stale every time the data changes.

RAG is the pragmatic answer. Instead of teaching the model your data, you just hand it the relevant pages at the moment it needs them. Like giving a contractor a specific clause from the contract rather than asking them to memorise the whole thing.

The ingestion phase runs once, or whenever your data changes. Your documents are loaded, split into manageable chunks, embedded into vectors, and stored in a VectorStore. This is how your data gets indexed for semantic retrieval.

The query phase runs on every request. The user’s question is embedded into a vector. That vector is used to query the VectorStore for the closest matching chunks. Those chunks — plus the original question — get injected into the prompt. The LLM reads them as context and answers based on what it finds there.

The LLM never “learned” your data. It reads it fresh on each request, like an open-book exam. That framing matters because it sets the right expectations: if the relevant information isn’t in the retrieved chunks, the model will still try to answer — and that’s when hallucinations happen. RAG reduces hallucinations by providing grounding. It doesn’t eliminate them.

The part that controls retrieval quality isn’t the LLM and isn’t the vector database — it’s the chunking strategy. How you split your documents determines what gets retrieved. A chunk that’s too large buries the relevant detail in noise. A chunk too small loses the surrounding context that makes it meaningful. Getting chunking right is usually where the real tuning work happens.

The one-line mental model for each component

ChatClient — you talk to the LLM through this. PromptTemplate — You structure what you say. EmbeddingModel — converts meaning into math. VectorStore — stores and searches that math. Advisors — middleware that enriches every request automatically. ChatMemory — gives the conversation a past. Together, they’re the full stack for building LLM features that actually behave like software — predictable, configurable, and debuggable.

Building a UI for a Team Task Manager (Laravel + Tailwind)

“I’ve been working on a project, a simple team task management system built with Laravel. After finishing the backend features like teams, invitations, and authentication, I finally shifted my focus to something I was avoiding for quite a while now: the UI. I also added a name for this project instead of just calling it a team task manager. The website name is Worklio.

Here’s the home page of the website:
Welcome page

I’m keeping the UI as simple as possible because my main focus is learning the backend side. I also use ChatGPT to help generate some of the frontend code, and the results have been surprisingly good. Right now, I just need to break parts of the UI into reusable components to make the codebase easier to read and maintain, while also centralizing repeated logic and code.

Here’s the rest of the pages:

Team Page

Team page

Show Single Team Page with Projects

Project Page

What’s next

I currently have both frontend and backend for teams, invitations, projects, and now I’ll be working on the task part.

Note: I’m using ChatGPT as part of my learning process—to sanity-check ideas, understand Laravel patterns, and think through tradeoffs. I still write the code myself and use the project to test whether I actually understand what I’m building.

Hermes Agent Remembers You

For the past two years, the AI industry has obsessed over model intelligence.
Bigger context windows.
Smarter benchmarks.
More parameters.
Faster inference.
But most AI assistants still suffer from the same fatal flaw:
They forget everything.
Every session starts from zero.
Every workflow requires re-explaining context.
Every “AI agent” often behaves like a temporary script wearing a chatbot costume.
Then Hermes Agent arrived.
Built by Nous Research, Hermes Agent is not trying to be another copilot or another flashy autonomous demo. It is attempting something much more ambitious:
An AI system that evolves through use.
And that changes the conversation entirely.
What Is Hermes Agent?
Hermes Agent is an open-source autonomous AI agent framework designed around one central idea:
Persistence.
Not just persistent memory.
Persistent skills.
Persistent workflows.
Persistent identity.
Unlike traditional chat-based assistants, Hermes runs as a long-lived system that can continuously operate across platforms, tools, terminals, APIs, and messaging apps.
The official tagline says it best:
“The agent that grows with you.”
That sounds like marketing copy at first.
Until you understand how Hermes actually works.
The Core Breakthrough: AI That Learns Operationally
Most AI systems today are stateless.
Even when they simulate memory, the “memory” is usually just:
conversation history,
vector retrieval,
or manually injected context.
Hermes goes further.
After solving tasks, Hermes creates reusable “skills” from successful execution traces. Those skills become searchable operational knowledge the agent can reuse later.
This is the real innovation.
Hermes does not merely answer.
It accumulates experience.
That distinction matters more than most people realize.
Why Hermes Agent Feels Different
The easiest way to understand Hermes is this:
Chatbots respond.
Copilots assist.
Hermes persists.
That persistence creates entirely new behavior patterns.
A normal AI assistant:
solves a task,
forgets it,
and starts over next time.
Hermes:
solves a task,
stores successful workflows,
refines them,
and reuses them later.
Over time, your agent slowly becomes specialized around:
your workflows,
your preferences,
your infrastructure,
and your recurring problems.
That is much closer to hiring a junior operator than opening a chatbot.
The Three-File Architecture That Makes Hermes Unique
One of the most fascinating design decisions inside Hermes is its identity system.
According to community documentation and framework breakdowns, Hermes organizes persistent behavior into three evolving files:
SOUL.md → personality, principles, behavioral constants
MEMORY.md → accumulated factual knowledge
USER.md → evolving understanding of the user
This is incredibly important conceptually.
Most AI systems merge everything into one giant context blob.
Hermes separates:
identity,
memory,
and user modeling.
That separation mirrors how humans actually operate.
You are not the same as your memories.
And your memories are not the same as your understanding of another person.
Hermes encodes that distinction directly into the architecture.
That is not just clever engineering.
It is a glimpse into where agent design is heading.
Hermes vs Traditional Agent Frameworks
The current AI agent ecosystem is crowded:
LangChain
AutoGen
OpenClaw
CrewAI
OpenAI Agents SDK
countless orchestration layers
Most frameworks optimize for:
tool calling,
chaining,
orchestration,
or multi-agent coordination.
Hermes optimizes for continuity.
That is a fundamentally different design philosophy.
Framework Type Main Focus
LangChain Orchestration
AutoGen Multi-agent collaboration
OpenAI Agents API-level workflows
OpenClaw Autonomous execution
Hermes Agent Persistent self-improving operation
Hermes is less interested in “agent demos.”
It is trying to become infrastructure.
The Most Underrated Feature: Multi-Platform Presence
Hermes can operate across:
Telegram,
Discord,
Slack,
WhatsApp,
Signal,
email,
terminal interfaces,
IDE integrations,
and more.
At first glance, this sounds like a convenience feature.
It is not.
This transforms Hermes from a tool into an ambient computing layer.
Imagine:
asking your agent something from Telegram,
continuing the task in VS Code,
receiving summaries through Slack,
and letting background automations continue overnight.
The agent persists independently from the interface.
That architecture feels much closer to operating systems than applications.
Local-First AI Finally Becomes Real
One reason Hermes exploded in popularity is because it aligns perfectly with a growing movement in AI:
AI sovereignty.
Developers increasingly want:
local models,
self-hosted infrastructure,
private memory,
ownership of workflows,
and freedom from API lock-in.
Hermes supports multiple providers and local inference backends, including OpenAI-compatible APIs, Hugging Face integrations, Anthropic, Google, OpenRouter, and local stacks like LM Studio.
It can run:
on a laptop,
on a cheap VPS,
or on GPU infrastructure.
That flexibility matters.
For years, powerful AI systems required centralized cloud dependency.
Hermes suggests another future:
personal AI infrastructure.
The Real Shift: From Prompt Engineering to Agent Evolution
Prompt engineering dominated the first wave of generative AI.
But Hermes points toward something bigger:
Experience engineering.
The value is no longer just crafting prompts.
The value becomes:
shaping long-term agent behavior,
building reusable operational knowledge,
and evolving persistent systems over time.
This is a massive conceptual shift.
Instead of:
“How do I prompt the model?”
The question becomes:
“How do I train my operational agent ecosystem through use?”
That is a much more interesting future.
The Biggest Weaknesses of Hermes Agent
Hermes is exciting.
But it is not magic.
There are still major limitations.

  1. Complexity
    Hermes is not beginner-friendly.
    Running persistent self-hosted agents requires:
    infrastructure knowledge,
    API management,
    model selection,
    memory management,
    and operational discipline.
    This is still very much a builder’s tool.
  2. Long-Running Drift
    Persistent agents introduce a new category of problems:
    memory pollution,
    behavioral drift,
    recursive errors,
    and degraded context quality over time.
    An agent that remembers incorrectly can become dangerous faster than one that forgets.
  3. Autonomous Reliability Is Still Hard
    Even advanced agents still struggle with:
    long task chains,
    edge cases,
    hallucinated tool use,
    and execution reliability.
    Hermes improves the structure around the model.
    It does not magically solve reasoning limitations.
    Why Developers Are Paying Attention
    Hermes Agent grew extraordinarily fast because it landed at the exact right moment.
    The industry is moving from:
    isolated prompts
    toward:
    persistent autonomous systems.
    From:
    AI chat
    toward:
    AI operations.
    From:
    asking questions
    toward:
    delegating workflows.
    Hermes is one of the clearest early examples of what that transition looks like in practice.
    My Take: Hermes Agent Is More Important Than Most People Realize
    The biggest idea behind Hermes is not tool use.
    It is not automation.
    It is not memory.
    The biggest idea is this:
    AI systems are starting to accumulate operational experience.
    That changes everything.
    Because once agents can:
    remember,
    refine,
    specialize,
    and evolve through execution,
    they stop behaving like software in the traditional sense.
    They begin behaving more like digital coworkers.
    We are still early.
    The systems are imperfect.
    The reliability problems are real.
    But Hermes Agent feels like one of the first open-source projects pointing clearly toward the next era of AI:
    Not isolated intelligence.
    Persistent intelligence.

Introducing Agent Note: saving the why behind AI-assisted code in Git

Hi, I’m wasabeef.

I have been using coding agents such as Claude Code, Codex CLI, Cursor, and Gemini CLI regularly in daily development.

They no longer feel like experiments. They can already produce reviewable Pull Requests. But while reviewing AI-assisted changes, I kept running into the same problem.

A diff tells you what changed. It does not tell you why it changed.

That is already a problem with human-written commits when the commit message is weak. With AI-assisted commits, the missing context is even larger: the prompt, the response, the discussion that led to the implementation, the agent that touched each file, and the reason a particular path was chosen.

That is why I built Agent Note.

Agent Note — AI conversations saved to Git

This article focuses less on the exact usage and more on why this kind of record is needed, and how Agent Note keeps that context in Git.

What is missing in AI-era code review

AI coding agents have become common in everyday development.

They write code quickly. They add tests. They update documentation. They can even open Pull Requests.

But review exposes a different problem.

The final diff does not show the background of the implementation.

  • What request started the change?
  • What assumptions did the AI make?
  • Did the direction change halfway through?
  • Is this a generated bundle, or source code someone intentionally edited?
  • Which commits were mostly AI-assisted, and which were mostly human follow-up?

In human-to-human development, commit messages, Pull Request descriptions, and review comments have carried that context.

In AI-assisted development, prompts and responses also belong in the review context. Without them, reviewers lose the trail before review even starts.

Until now, the conversation with the AI often stayed inside the agent UI or a local transcript. Once the session ended, the team usually received only the commit and the Pull Request.

The reason behind the change disappears.

AI review tools need context too

I also use AI review tools such as Copilot, CodeRabbit, Devin, and Greptile.

Their main inputs are usually the diff and the repository code.

That means AI can review AI-written code without seeing the prompt or intent that produced it.

When that happens, the review tends to stay near the surface of the diff.

To judge whether an implementation matches the intended change, a reviewer needs more than the final code. The reviewer needs to know what the author asked for, what the agent understood, and which parts of the repository were supposed to change.

Agent Note keeps that context in the Pull Request in a form AI review tools can read.

It renders a human-readable summary in the Pull Request body, and also embeds an agentnote-reviewer-context hidden comment. It is invisible in the rendered PR body, but AI review tools that read the raw Pull Request description can use it to understand changed areas, review focus, and author intent.

The reviewer gets more than the diff.

Today

git diff
Pull Request description

Prompt?       missing
Response?     missing
Why this way? reviewers have to infer it

With Agent Note

git diff
Pull Request description
refs/notes/agentnote
Dashboard

Prompt / Response / Context / AI Ratio stay connected to the commit

What gets recorded

Agent Note saves the AI conversation and changed files for each commit.

Think of it as git log with the AI conversation behind the change attached to it.

It records four kinds of information.

Data What it helps you see
Prompt / Response What was requested and how the AI answered
Files Which files the agent touched
AI Ratio A practical estimate of how much of the commit involved AI
Context Extra context when the prompt alone is too short

For example, a prompt like yes, implement it does not carry enough meaning when it appears alone in a Pull Request.

Agent Note does not try to inflate that prompt. Instead, when the surrounding commit evidence helps, it can attach a short Context note.

Context shown in the Agent Note Dashboard

The point is not to say “this code is correct because AI wrote it” or “this code is risky because AI wrote it.”

The point is to give reviewers better evidence.

How it works

Agent Note is not a hosted service.

It adds a thin recording layer next to the normal Git workflow.

You prompt your coding agent
        │
        ▼
Agent hooks save the conversation and session info
        │
        ▼
The agent edits files
        │
        ▼
Hooks or local transcripts record changed files
        │
        ▼
You run `git commit`
        │
        ▼
A Git hook links the session to the commit
        │
        ▼
Agent Note writes a Git note for that commit
        │
        ▼
Agent Note's pre-push hook shares `refs/notes/agentnote`

Temporary session data lives under .git/agentnote/.

The permanent record lives in refs/notes/agentnote.

Agent Note does not modify the commit diff. It adds only a short session trailer to the commit message and stores the detailed record in Git notes. When you need the AI context behind a commit, you read the Git note.

Why Git notes

The design constraint I cared about most was avoiding unnecessary workflow changes.

I did not want to replace git commit, and I did not want the core record to depend on a hosted service.

The context behind AI-assisted code should be a team asset, just like the commit itself. Keeping that context in Git felt natural.

Git notes let Agent Note attach structured data to a commit without changing the regular commit history.

That balance felt right.

  • Use normal git log and Pull Requests most of the time
  • Read Agent Note data only when you need the deeper context
  • Share it with the team through refs/notes/agentnote
  • Avoid requiring a hosted service

The design keeps AI development context close to Git instead of sending it somewhere else.

What Agent Note does not do

Agent Note is not a tool for proving that AI-written code is correct.

AI Ratio is not an automatic judgment of responsibility or quality. It is a practical signal for understanding how much AI involvement a commit appears to have.

Agent Note also does not claim perfect line-to-prompt attribution today. agent-note why is a shortcut from a line, to the blamed commit, to the prompts, responses, and context attached to that commit.

The goal is not to replace review. The goal is to keep the context reviewers need from disappearing.

How it fits with Spec-Driven Development

Spec-Driven Development makes the intent explicit before implementation.

That works well with AI coding agents. If the input is vague, the agent may still produce code quickly, but reviewers later have to guess why the implementation took that shape.

A spec alone does not preserve the implementation conversation. It does not show how the agent interpreted the task, what changed during the session, or which prompts ended up in each commit.

If the spec is the intent before implementation, Agent Note is the execution record after implementation.

Together, they let reviewers compare the implementation against the spec, and also inspect the AI conversation that produced the commit.

How it relates to Entire

Agent Note is not the only project working on this problem.

Entire also connects the context behind AI-assisted code changes to Git. Entire records prompts, transcripts, tool calls, changed files, and other session data as Checkpoints linked to commits. It is a broader system for agent development history, including rewind, resume, search, and a web UI.

Agent Note is intentionally narrower.

It focuses on commits and Pull Request review. The persistent record lives in Git notes under refs/notes/agentnote, and the main surfaces are the PR Report, Dashboard, hidden reviewer context for AI review tools, and agent-note why.

I do not see this as a matter of which approach is correct. The scope is different.

If you want full session Checkpoints, rewind, resume, and repository-wide search, a system like Entire makes sense. If you mainly want lightweight commit-level review context in Pull Requests, Agent Note is designed for that narrower workflow.

PR Report and Dashboard

In Pull Requests, Agent Note renders a human-readable summary.

## 🧑💬🤖 Agent Note

**Total AI Ratio:** ██████░░ 73%
**Model:** `claude-sonnet-4-20250514`

| Commit | AI Ratio | Prompts | Files |
|---|---|---|---|
| ce941f7 feat: add auth | ████░ 73% | 2 | auth.ts, token.ts |

Open Dashboard ↗

The PR Report is the entry point for review.

The Dashboard is for deeper reading.

In the Dashboard, you can inspect Prompt / Response, changed files, AI Ratio, and diffs by PR and by commit.

Agent Note Dashboard preview

The report answers “what should I look at first?” The Dashboard answers “what happened in this commit?”

The idea behind agent-note why

Agent Note also includes agent-note why.

It starts from a target line, uses git blame to find the commit, then reads the Agent Note attached to that commit.

npx agent-note why README.md:111

It does not claim exact line-to-prompt attribution yet.

But even without a new schema, connecting an individual line to the commit conversation is useful. It shortens the path from “why is this line here?” to “what did we ask the agent to do in that commit?”

Eventually, I want to get closer to line-level explanations. The MVP is intentionally smaller: connect existing Git blame data with existing Git note data and make the available context easy to reach.

Different agents expose different context

Agent Note supports multiple coding agents, but each agent exposes a different level of detail.

That is because every agent exposes hooks and transcripts differently.

Claude Code provides the richest signal today. Codex CLI, Cursor, and Gemini CLI are also supported, but Agent Note records only the prompt, response, changed files, and AI Ratio evidence that each agent can expose reliably.

I also do not want to overstate the evidence.

If Agent Note cannot know something reliably, it does not pretend to know it. AI Ratio is an estimate, not proof.

The latest support matrix is available in Agent Support.

Things to keep in mind

Agent Note records conversations with AI for the team.

That record should be handled carefully.

  • Do not put secrets in prompts or responses
  • When Git notes are pushed, the team can read the saved conversation
  • AI Ratio is an estimate, not an automatic judgment of quality or responsibility
  • Different agents expose different levels of detail
  • Gemini CLI support is still Preview

Agent Note is closer to review context than to an audit verdict.

Closing

The more we use AI coding agents, the less a diff alone is enough for code review.

Human commits have commit messages and Pull Request discussions. AI-assisted commits should also preserve prompts, responses, context, and AI Ratio.

Agent Note is an open source, Git-native way to do that.

  • GitHub: https://github.com/wasabeef/AgentNote
  • Documentation: https://wasabeef.github.io/AgentNote/
  • npm: https://www.npmjs.com/package/agent-note

If you want AI-assisted code to remain understandable after the session is over, please give Agent Note a try.