SmartGateSmartGate

RAG vs Agentic AI: One Names a Pattern, the Other a System

RAG and agentic AI are not two generations of one idea; they are categories of different kinds. RAG names a retrieval pattern — an index, a query, and the passages a prompt is built from — and it describes a component. Agentic AI names a way of running a model: a loop that plans, calls tools and acts until a stop condition is met, and it describes a system. Neither category contains the other.

Short answer: RAG and agentic AI are not two generations of one idea; they are categories of different kinds. RAG names a retrieval pattern — an index, a query, and the passages a prompt is built from — and it describes a component. Agentic AI names a way of running a model: a loop that plans, calls tools and acts until a stop condition is met, and it describes a system. Neither category contains the other. What separates them in practice is side effects: a retriever reads, an agent acts, and acting is what drags identity, authority and metering into the stack.

Key takeaways

  • They are different parts of speech. "RAG" is used as a component noun (the index, the stack, the evaluation); "agentic AI" is used as a system noun (the runtime, the product, the risk review).
  • The overlap is one case, not the definition. An agent that retrieves is agentic RAG. An agent that never reads an index is still agentic, and a single-pass pipeline over a vector store is still RAG.
  • Side effects draw the line. Reading an index can be retried freely; sending a message or changing a record cannot. Everything expensive about the agentic half follows from that.
  • Identity arrives with action. The moment a system does something on a user's behalf it needs a subject, a capability and an expiry, which no retrieval pipeline ever needs.
  • Progress is measured with different instruments. Recall and groundedness for the retrieval half; task completion, steps and cost per task for the acting half.
  • Do this next: classify one system you run by the three questions below — who writes the query, does the run have side effects, what stops it. The label that comes out is the category, and the words you use in the next design review should match it.

Why the professional vocabulary insists on two terms

The two phrases arrived from different literatures and they are different parts of speech. Retrieval-augmented generation is a paper title — the original RAG paper — and it names a pattern: fetch passages that look relevant to a question, put them in the prompt, then generate. Agentic AI names a way of running a model: the model picks the next action, calls a tool, observes what came back and continues until a stop condition is met, which is the shape ReAct described (reasoning and acting). One phrase describes a component; the other describes a system that may or may not contain it.

That is not pedantry, because the words are used to ask different questions. "The RAG pipeline", "the RAG index" and "RAG evaluation" all treat the phrase as a component with inputs and outputs, and every question about it is a question about data: what is indexed, how it is chunked, how recall is measured, how fresh the corpus is. "Agentic AI" is used as the name of a class of product, and the questions are about control: what it is allowed to do, who approves a run, what a task costs and what stops it. A purchase conversation about a vector database and a conversation about an autonomous workflow only look like the same conversation at the level of the slide that draws both as boxes.

The learning material reflects the split. Courses and certification tracks in this field are usually named after one half or the other rather than both, and job descriptions inherit the same vocabulary: a retrieval role is a data-and-evaluation job, while an agentic line on a platform team is a design, permissioning and incident-response job. The practical consequence is cheap to avoid: a team that uses the two words for one thing will spend a meeting disagreeing about scope instead of architecture.

There is a second, less obvious reason the boundary holds, and it is visible in code. The function below is the account-creation path of a product whose agents act for people. It writes the user and then tries to send a welcome email inside a block that swallows the failure, because an unreachable mail provider must never fail a sign-up:

# auth.ts — source lines 21–29 (createUser)
async createUser({ user }) {
      try {
        if (user?.email) {
          await sendWelcomeEmail({ to: user.email, firstName: user.name });
        }
      } catch (e) {
        console.error("[email] welcome email failed", e);
      }
    }

No retrieval pipeline contains this function. A retriever reads an index on behalf of whoever asked and stores nothing; an acting system needs a subject with an identity, a contact address and a place in a permission model, and that machinery is a large part of what the second term is naming.

Where the boundary sits: agentic AI is a runtime, not a technique

The two categories overlap without either containing the other. Written as a set relationship, the arguments about terminology mostly dissolve.

  • A non-agentic RAG system decides everything about retrieval before the model runs: one index, one query, the top-k passages, one generation. It is a function — same question, same evidence, no side effects.
  • A non-retrieval agent acts without any index at all: a coding agent with a shell, a browser driver filling a form, an operations agent calling an API. "Agentic" describes its control loop, not where its evidence came from.
  • The overlap — agentic RAG — is an agent for which retrieval is one available tool. That case has its own pages in this cluster: how the retrieval loop is built works through the mechanism, and the pipeline-versus-loop comparison decides it per workload.

Three questions classify any system you are looking at, and all three are properties of the runtime rather than of the model:

  1. Who writes the query? If the application writes it, you have a pipeline; if the model writes it after reading a result, the control loop is the architecture.
  2. Does the run have side effects? Reading an index changes nothing anywhere. Writing a record, sending a message or moving money makes the system an actor with obligations.
  3. What stops it? A pipeline ends when it has generated. A loop ends because a step ceiling, a budget or a judge says so, and that decision has to be implemented somewhere.

The runtime answers those questions on somebody's behalf, so identity is where the boundary becomes concrete. The sign-in callback below is a small, load-bearing example: when the provider is Google it reads the verification flag from the profile and refuses the sign-in when the mail address is unverified.

# auth.ts — source lines 32–38 (signIn)
async signIn({ account, profile }) {
      if (account?.provider === "google") {
        const emailVerified = (profile as any)?.email_verified;
        if (emailVerified === false) return false;
      }
      return true;
    }

A retriever never needs to know who is asking — at most it filters on a tenant field that something else set — while a runtime cannot proceed without an authenticated subject, because the actions it takes are attributable to one. That is the engineering reason "agentic" is a product-category word and "RAG" is a component word, and it is also why the two words come with different review processes attached.

The professional split in practice: who owns which half

Follow the money and the two categories separate into two budgets. The retrieval half is spent on data: corpus curation, chunking, embedding, index refresh cadence, and the labelled question sets that make relevance measurable. The agentic half is spent on control: the tool surface, the permission model, approvals, metering, and the runbooks for what to do when an unattended loop spends a week of budget in an hour. Those are different skills, different on-call rotations and different failure stories, which is why treating the two words as synonyms misplans the work.

The retrieval half is mostly a read path, and read paths are cheap to reason about. The snippet below reads one index key from a key-value store and returns nothing when the key is empty:

# lib/forgenova-kv-index.server.ts — source lines 11–14 (getKVIndex)
async function getKVIndex(indexKey: string): Promise<PseoKVIndex | null> {
  if (!indexKey) return null;
  return kvGet<PseoKVIndex>(indexKey);
}

Three properties of that function are the whole character of the retrieval half. It is a single read with no write, so nothing it does needs a transaction, an audit row or a compensating action. Its cost is fixed and knowable before the request arrives, which is what makes caching easy and latency predictable. And its failure mode is an absence rather than an error, so the caller has to decide what an empty index means — a decision the read path cannot make for you.

None of those properties survive the move to an action. An action can be applied halfway, it costs whatever the provider charges at the moment it runs, and it fails in ways that need compensation rather than a retry. That asymmetry is the boundary in one sentence: one half of the stack is engineering over data, and the other is engineering over consequences. the index design choices are the first half's design surface; the second half's starts with who is allowed to act.

What an agentic loop changes about the read path

A pipeline knows every key it needs before it reads any of them, so it can fetch them together. The batched variant below does exactly that: it resolves each key concurrently and reassembles the results into a single object keyed by the same identifiers it was given.

# lib/forgenova-kv-index.server.ts — source lines 45–52 (getKVIndexes)
async function getKVIndexes(
  indexKeys: string[],
): Promise<Record<string, PseoKVIndex | null>> {
  const entries = await Promise.all(
    indexKeys.map(async (key) => [key, await getKVIndex(key)] as const),
  );
  return Object.fromEntries(entries);
}

That shape is available only to a system whose next step is known in advance. An agentic loop cannot use it, because the second key depends on what the first read returned — the reads are sequential by construction, and the concurrency that makes a pipeline cheap per request cannot be scheduled. The consequence is visible in the numbers a hosting bill is made of: a pipeline's reads per request is a constant you can size capacity from, while a loop's is a distribution with a tail.

Two habits follow. Prefetch only what the loop has already decided it needs, since speculative prefetching in a loop spends tokens on evidence the loop may discard a hop later. And keep the read count in the per-call record, because the difference between a two-read question and a twelve-read question is invisible in the answer and obvious in the invoice. metering what an action costs is the longer version of that accounting, and per-call token accounting is where the compression side of it lives.

Capability: the agentic half's work with no retrieval equivalent

Nothing in a retrieval pipeline needs a secret to be created. An index read is authorised by the same credentials as any other read, and a tenant filter is a value in a query. An acting system needs something different: a capability that proves the right to do the thing, in a form nobody can guess. The smallest useful version of that idea is a random value of a fixed length:

# lib/invite.ts — source lines 11–13 (generateToken)
function generateToken(): string {
  return randomBytes(32).toString("hex");
}

A 32-byte random value rendered as hex is worth reading closely. It is not derived from a user identifier or a timestamp, so it cannot be computed by anyone who knows the account — it can only be verified by looking it up on the server that issued it. That lookup is also what makes revocation possible: a value that is a pure function of something stable can never be withdrawn, while a stored random value can be deleted in one write.

There is no retrieval analogue for this function, and that absence is a good test. If the honest answer to "what does this do to the index" is nothing, you are reading the agentic half of a product, and the code around it will be about issuing, scoping or withdrawing authority rather than about ranking. Scoped credentials for agents is the version of that argument written for tool traffic between a host and a server.

Authority that expires: role, scope and single use

The invitation record below is the second half of the capability story. Creating an invitation looks up the team, mints a token, stamps an expiry computed from a configured number of hours, and stores the role the invitation grants:

# lib/invite.ts — source lines 15–45 (createInvite)
async function createInvite(
  teamId: string,
  role: "MEMBER" | "ADMIN" = "MEMBER",
): Promise<InviteResult> {
  const team = await prisma.team.findUnique({
    where: { id: teamId },
    select: { id: true, name: true },
  });

  if (!team) {
    return { ok: false, error: "Team not found" };
  }

  const token = generateToken();
  const expiresAt = new Date(Date.now() + INVITE_EXPIRY_HOURS * 60 * 60 * 1000);

  try {
    await prisma.teamInvite.create({
      data: {
        teamId: team.id,
        token,
        role,
        expiresAt,
      },
    });

    return { ok: true, token, teamName: team.name, expiresAt };
  } catch (e) {
    return { ok: false, error: "Failed to create invite" };
  }
}

Read the stored fields as the shape of a grant: a subject (the team), a capability (the role), a lifetime (the expiry) and a state (used or not). An agent's tool permission has the same four fields, which is why the two are usually implemented by the same table in the same product. The other thing to notice is the failure style: an unknown team returns a structured failure rather than throwing, because the caller is a user interface that has to render a message. Inside an unattended agent run the same choice has to be made deliberately — a tool that refuses politely is a step the loop can recover from, while an exception ends the run.

Scoping is also where the two categories most often get confused in review. A retrieval system's "permission" is a filter on what it can read, and getting it wrong leaks documents. An acting system's permission is a grant of what it may change, and getting it wrong changes them. The second class of error is the one that builds a review process around the word "agentic".

At-most-once actions, and why progress is measured differently

The accepting half of the invitation flow is where the two categories' metrics diverge most sharply. It checks the token, refuses a used or expired one, refuses a duplicate membership, and then creates the membership and marks the invitation used in a single transaction:

# lib/invite.ts — source lines 47–103 (validateAndAcceptInvite)
async function validateAndAcceptInvite(
  token: string,
  userId: string,
): Promise<InviteResult & { teamId?: string }> {
  const invite = await prisma.teamInvite.findUnique({
    where: { token },
    include: { team: { select: { id: true, name: true } } },
  });

  if (!invite) {
    return { ok: false, error: "Invalid or expired invitation link." };
  }

  if (invite.usedAt) {
    return { ok: false, error: "This invitation has already been used." };
  }

  if (invite.expiresAt < new Date()) {
    return { ok: false, error: "This invitation has expired." };
  }

  // Check if user is already a member
  const existingMember = await prisma.teamMember.findUnique({
    where: { teamId_userId: { teamId: invite.teamId, userId } },
  });

  if (existingMember) {
    return { ok: false, error: "You are already a member of this team." };
  }

  // Create membership and mark invite as used in a transaction
  try {
    await prisma.$transaction([
      prisma.teamMember.create({
        data: {
          teamId: invite.teamId,
          userId,
          role: invite.role,
        },
      }),
      prisma.teamInvite.update({
        where: { id: invite.id },
        data: { usedAt: new Date() },
      }),
    ]);

    return {
      ok: true,
      token,
      teamName: invite.team.name,
      expiresAt: invite.expiresAt,
      teamId: invite.teamId,
    };
  } catch (e) {
    return { ok: false, error: "Failed to accept invitation." };
  }
}

That transaction is at-most-once semantics, and it exists because the action cannot be undone: a membership granted twice is a support ticket, and an invitation accepted twice after a retry is a duplicate record nobody can explain three months later. A retrieval pipeline has no equivalent requirement, because reading the same passage twice costs a little money and changes nothing. When you are asked whether a system is "agentic", the presence of exactly-once mechanics is often a faster tell than the presence of a planning loop.

The metric split follows the same fault line. Progress on the retrieval side is a measurement of evidence: recall at k on a labelled question set, whether the passage a human would have cited was retrieved, whether the answer survives when the passage is removed. Progress on the acting side is a measurement of outcomes under constraint: tasks completed, steps per task, cost per completed task, and the share of runs that needed a human to unblock them. A stack that reports one number for both halves hides the trade the reader actually has to make, and it is why fitting evidence into context and per-task completion are two separate dashboards in every system mature enough to have both.

The meeting point: agentic search next to a fixed corpus

The cleanest place to watch the categories meet is a tool that retrieves from the open web, because it is a retrieval step in the pipeline sense and a tool in the agentic sense at the same time. The window below shows a search implementation choosing its backend and falling through when the preferred one returns nothing, recording which engine actually answered:

# backend/smartgate/modules/search/algorithm.py — source lines 119–167 (Search.search)
    async def search(self):
        import time

        self.start_time = time.time()
        backend = self.settings.resolved_backend()
        self.effective_backend = backend
        try:
            if backend == "firecrawl":
                rows = await self._search_firecrawl()
                self.result_container.extend("firecrawl", rows or [])
            elif backend == "searxng":
                rows: list[dict] = []
                try:
                    rows = await self._search_searxng()
                except Exception as e:
                    logger.warning("SearXNG failed: %s", e)
                    if self.settings.searxng_fallback:
                        self.result_container.add_unresponsive_engine(
                            "searxng", str(e)
                        )
                    else:
                        raise
                if rows:
                    self.result_container.extend("searxng", rows)
                    self.effective_backend = "searxng"
                elif self.settings.searxng_fallback:
                    logger.info(
                        "SearXNG returned no rows; falling back to DuckDuckGo Lite"
                    )
                    try:
                        fb_rows = await self._search_duckduckgo_lite()
                        self.result_container.extend("duckduckgo", fb_rows or [])
                        self.effective_backend = "duckduckgo"
                    except Exception as fb_e:
                        logger.warning("DuckDuckGo Lite fallback failed: %s", fb_e)
                        self.result_container.add_unresponsive_engine(
                            "duckduckgo", str(fb_e)
                        )
                else:
                    logger.warning(
                        "SearXNG returned no rows (searxng_fallback=false)"
                    )
            else:
                rows = await self._search_duckduckgo_lite()
                self.result_container.extend("duckduckgo", rows or [])
        except Exception as e:
            logger.warning("Search backend '%s' failed: %s", backend, e)
            self.result_container.add_unresponsive_engine(backend, str(e))
        return self.result_container

That fallback chain is a runtime decision. A pipeline does not choose its index at request time, and it does not need to record which engine answered, because the index is a deployment input fixed in configuration and the same query returns the same top-k until somebody reindexes. A search tool keeps no such promise: the ranking belongs to a search engine you do not operate, the corpus changes between two identical calls, and freshness is a property of the moment rather than of your data pipeline. The line between the two retrieval paths — which corpus, whose ranking, what a call costs and when each is the right one — is the other half of this comparison, and the stages both paths share are drawn in the retrieval stages underneath and the box-by-box diagram.

Which category owns that call is a genuinely useful question with a two-part answer. The read is retrieval, so it earns the retrieval half's evaluation habits — relevance, freshness, redundancy across the results. The call is an action from the loop's point of view, so it earns the acting half's metering, rate limits and retry policy. Systems that get this wrong pay twice: they measure the search results like a fixed index and are surprised by the variance, then they meter the call like a pure function and are surprised by the tail.

The first of those two surprises has a page of its own: when the ranking you are metering belongs to a search engine you do not operate, the failure modes are different ones — agentic search vs RAG works through what changes and what it costs to find out.

How SmartGate compares

The distinction in this article is not about picking a winner between two words; it is about knowing which half of a stack you are looking at, because that decides what to measure.

What it decides What that means in practice
A retrieval-only stack What evidence an answer is conditioned on Data engineering, deterministic per query, no side effects to compensate for
An agent framework (LangGraph and similar) The route through a declared graph Control flow you write and operate; identity, limits and metering are still yours to build
A hand-rolled loop behind a proxy The next action the model chooses Whatever the proxy does not count surfaces as a budget overrun instead of an error
SmartGate The tool surface both halves call, with per-key limits, a spend guard and one audit row per call Retrieval and action share one endpoint, so the read/act split is visible in one place instead of two dashboards

The row that decides most of these comparisons is the last column, because a limit that lives somewhere other than the call path is a limit that disagrees with the meter. The gateway's free tier covers 2,000,000 tokens a month and all seven tools; Pro starts at 18 dollars a month and Teams at 55 dollars a month, which is the same shape of number as the retrieval half's index bill and the acting half's call bill. the gateway in the call path is where those numbers are enforced, and the tools a host declares is the surface an agent is granted from.

What to do with the distinction

  1. Label the system before you argue about it. Run the three classification questions — who writes the query, does the run have side effects, what stops it — and write the answer down. Half of the confusion in a design review is one person talking about the index and another about the actions.
  2. Give the two halves separate metrics. Recall and groundedness for the reads; completion, steps and cost per task for the actions. A blended number cannot tell you which half regressed.
  3. Move the identity work to the agentic inventory. Accounts, capabilities, grants and expiries are not retrieval work, and they are the parts a "we already have RAG" plan forgets to fund.
  4. Audit the reads for the properties that make them cheap. Fixed cost, no side effects, cacheable, retryable. If a read has stopped having those properties, it has quietly become an action.
  5. Write the stop condition down before the first unattended run. A ceiling on steps, a spend guard that refuses rather than warns, and a record per call. All three are configuration, and none can be added after an incident without explaining the incident.

Start on the free tier to see both halves on one endpoint — 2,000,000 tokens a month and all seven tools — with start free; the plan limits are on the pricing page, the tool parameters are in the product docs, and contract traffic starts at the contact form.

Frequently Asked Questions

Limitations and what this does not do

  • The classification is about vocabulary, not performance. Nothing here says which category answers your questions better. The comparison of the two retrieval paths on latency, cost and answer shape is a separate page in this cluster, and it is decided per workload.
  • The quoted code is a product's plumbing, used as evidence about work. The slices show the action path of a live system — accounts, identity, capabilities, expiring grants, at-most-once actions — and the read path it sits next to. They are not a reference implementation of either category, and the page makes no claim that this is how any other product splits the work.
  • One slice is quoted through a window. The search class at the end of this page is 232 lines; the fragment shown is the backend selection and fallback chain, which is the part that demonstrates a runtime decision. The remaining methods are HTTP plumbing with the same shape.
  • Metrics are named, not benchmarked. Recall at k and cost per completed task are the right instruments for their halves; the thresholds that count as good are properties of your corpus and your traffic, and this page does not invent them.
  • Identity is treated as a boundary marker, not as a security design. The pages on credentials and on tool permissions in this cluster cover authorisation properly; here it appears only as the thing that makes the agentic half a different kind of engineering.

Sources

Method note

The code in this article is not transcribed. Each block was cut directly out of the slice body returned by the SmartGate slice API and re-asserted byte-for-byte as a substring of that body before publication; the first line inside every fence records the file and the exact source lines. Symbols were pinned with whole-name containment (rule A level 2) and confirmed by the service's slot-proof endpoint — every planned section matched, with no abstentions. The slices were chosen as the concrete form of the argument rather than as examples of the topic: the point of this page is that the retrieval half and the acting half of a product contain different kinds of code, and the quoted functions are what each half actually looks like — a read path that returns nothing on a miss, a batched prefetch, and the account, identity, capability, grant and at-most-once machinery that only the acting half needs. The search class at the end is quoted through one window, as the limitations section says.

Slice provenance

# SERP keyword Symbol File Source lines How it was pinned sha256(12)
1 ibm rag and agentic ai professional certificate createUser auth.ts 21–29 rule A L2 → slot-proof 257c8414e23a
2 rag vs agentic ai signIn auth.ts 32–38 rule A L2 → slot-proof c2838589b4e0
3 ibm rag and agentic ai professional certificate coursera getKVIndex lib/forgenova-kv-index.server.ts 11–14 rule A L2 → slot-proof 2df2fc7b55c4
4 agentic ai rag getKVIndexes lib/forgenova-kv-index.server.ts 45–52 rule A L2 → slot-proof eb6fd369164a
5 rag and agentic ai generateToken lib/invite.ts 11–13 rule A L2 → slot-proof e17b0cc50bf2
6 agentic rag createInvite lib/invite.ts 15–45 rule A L2 → slot-proof 70061bc8e160
7 progress agentic rag validateAndAcceptInvite lib/invite.ts 47–103 rule A L2 → slot-proof da5838d9b470
8 agentic search vs rag Search backend/smartgate/modules/search/algorithm.py 119–167 rule A L2 → slot-proof 5d88cd6aed7c

Every fenced block above was cut from the slice body and re-asserted against it byte-for-byte before publication. 8 of 8 sections pinned, 0 abstentions — every planned section matched a unique symbol in the codebase, so no section on this page is written without a quoted implementation. The code is evidence about the shape of each category's work, not a reference implementation of either.