Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default async function Page({
</div>
<div className="flex flex-col lg:flex-row lg:space-x-8">
<div className="lg:w-3/4">
<article className="prose lg:prose-xl max-w-none">
<article className="prose prose-invert lg:prose-xl max-w-none">
<Post />
</article>
</div>
Expand Down
39 changes: 34 additions & 5 deletions src/app/blog/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,42 @@
import React from "react";
import { Background } from "@/components/background";
import { HomeFooter, HomeHeader, geistMono, geistSans } from "@/components/home/chrome";

interface BlogLayoutProps {
children: React.ReactNode;
}

/* The blog shares the homepage's look: its header and footer, Geist, and its dark palette.
The theme variables are set to the homepage's colors so the shared components follow along. */
const HOME_PALETTE = {
"--background": "0 0% 3.9%",
"--foreground": "0 0% 98%",
"--card": "0 0% 7.8%",
"--card-foreground": "0 0% 98%",
"--popover": "0 0% 7.8%",
"--popover-foreground": "0 0% 98%",
"--primary": "255.1 91.7% 76.3%",
"--primary-foreground": "0 0% 3.9%",
"--secondary": "0 0% 11%",
"--secondary-foreground": "0 0% 98%",
"--muted": "0 0% 11%",
"--muted-foreground": "0 0% 56.1%",
"--accent": "0 0% 11%",
"--accent-foreground": "0 0% 98%",
"--border": "0 0% 14.9%",
"--input": "0 0% 14.9%",
"--ring": "255.1 91.7% 76.3%",
} as React.CSSProperties;

export default function BlogLayout({ children }: BlogLayoutProps) {
return (
<>
<Background />
<div
style={HOME_PALETTE}
className={`${geistSans.variable} ${geistMono.variable} dark relative isolate min-h-screen overflow-hidden bg-[#0a0a0a] font-geist text-white antialiased [color-scheme:dark]`}
>
<div aria-hidden className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[620px]">
<div className="absolute left-1/2 top-0 h-[480px] w-[1000px] -translate-x-1/2 rounded-full bg-[radial-gradient(closest-side,rgba(139,92,246,0.18),rgba(139,92,246,0.04)_55%,transparent)]" />
</div>
<HomeHeader />
<div className="min-h-screen">
<div className="max-w-7xl mx-auto flex flex-col md:flex-row min-h-[calc(100vh-256px)]">

Expand All @@ -17,6 +45,7 @@ export default function BlogLayout({ children }: BlogLayoutProps) {
</div>
</div>
</div>
</>
<HomeFooter />
</div>
);
}
}
9 changes: 7 additions & 2 deletions src/app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Background } from "@/components/background";
import Link from "next/link";
import React from "react";
import externalPostsData from "@/data/external-blog-posts.yaml";
Expand All @@ -14,6 +13,13 @@ function shortDate(date: string) {
}

const posts = [
{
slug: 'kagent-v1-0-release',
publishDate: '2026-11-09',
title: 'kagent 1.0 Is Here: Open Source Agents on Agent Substrate',
description: 'kagent 1.0 is out, and every agent now runs on Agent Substrate: sandboxed by default, suspended when idle, and resumed on the next message. Why Pods don\'t fit agents, what Substrate and kagent each do, and how to get started.',
authorId: 'sebastianmaniak',
},
{
slug: 'agent-sandboxes-four-axes',
publishDate: '2026-10-27',
Expand Down Expand Up @@ -183,7 +189,6 @@ type ExternalYamlPost = { title: string; description: string; publishDate: strin
export default async function BlogPage() {
return (
<>
<Background />
<div className="min-h-screen py-8">
<div className="max-w-3xl mx-auto px-4">
<h1 className="text-4xl font-bold text-center mb-16 text-foreground">Blog</h1>
Expand Down
131 changes: 131 additions & 0 deletions src/blogContent/kagent-v1-0-release.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
export const metadata = {
title: "kagent 1.0 Is Here: Open Source Agents on Agent Substrate",
publishDate: "2026-11-09T10:00:00Z",
description:
"kagent 1.0 is out, and every agent now runs on Agent Substrate: sandboxed by default, suspended when idle, and resumed on the next message. Why Pods don't fit agents, what Substrate and kagent each do, and how to get started.",
author: "Sebastian Maniak",
authorIds: ["sebastianmaniak"],
};

# kagent 1.0 Is Here: Open Source Agents on Agent Substrate

Back in August, Eitan wrote [The Future of kagent](/blog/the-future-of-kagent). The short version: the team was going to rip out kagent's Deployment-based runtime and rebuild it on [Agent Substrate](https://github.com/agent-substrate/substrate). He boiled the reasoning down to three things we'd learned about agents:

1. They can be dangerous, so they need a sandbox.
2. They do better work when they have their own filesystem.
3. They spend most of their time idle, so keeping a Pod running for each one is a waste.

That future is here. **kagent 1.0 is out**, and every agent now runs on Agent Substrate. Agent Substrate hit 1.0 too, and both are open source CNCF projects. Let's walk through why we made the switch, what each project does, and how to get it running.

## Agents changed under us

When kagent started, most agents were "platform" agents built with frameworks like CrewAI, LangChain or ADK and wired up to a fixed set of tools. We ran them as Kubernetes Deployments, the same way you'd run a microservice, and for that kind of agent it worked fine.

That's not what most people are building now. Two patterns keep showing up:

- **The agent harness.** A general-purpose loop around a model that gets a goal, a workspace, and some skills, tools and CLIs, then works out the steps on its own. These agents run code the model wrote, keep a workspace around between turns, and spend a lot of time waiting on a model, a tool, or a human.
- **The tool sandbox.** Plenty of agent work doesn't need a long-running home at all. It needs somewhere safe to run a tool call (run this script, check this code, transform these files) and hand back the result.

Agent Substrate was built for both.

## Why a Pod per agent doesn't work

A Pod comes with assumptions that are great for microservices. It runs code you wrote and reviewed, its replicas are stateless and interchangeable, it stays busy, and every replica shares one ServiceAccount. Agents break every one of those.

**The model decides what runs.** Ask an agent to "fix this doc" and there's a good chance it writes a `sed` command or a quick Python script that nobody reviewed, steered by whatever was in its context: an issue, a README, a tool response. Each session needs a real sandbox with egress controls and safe credential handling.

**State piles up.** An agent clones a repo, installs packages and edits files. Its environment *is* the work, and a restart, eviction or node drain throws it away.

**Agents mostly wait.** And while they wait, their Pods hold CPU and memory. Tool sandboxes have the opposite problem: a Pod or Job per tool call can spend longer getting through the API server and scheduler than actually running.

![A grid of squares, each standing for 50 sessions in their own Pods. Only a handful are working; the rest are waiting for their next message, yet each waiting Pod still holds memory, schedulable capacity on a node, and an IP address](/images/blog/kagent-v1-0-release/pods-idle-grid.png)

**Sessions act for people.** An agent session works on behalf of a specific user, and one shared ServiceAccount can't tell sessions apart for policy or audit. Once you start moving sessions between Pods to fix the idle problem, an identity tied to the Pod can't follow them anyway.

Lin dug into this question in [Is a Pod the Right Deployment Unit for an AI Agent?](/blog/is-pod-right-deployment-unit-ai-agent) if you want the longer version.

## How Agent Substrate fixes it

Agent Substrate is a layer on top of Kubernetes built for these workloads. It keeps the **worker** (a warm Pod in a WorkerPool that Kubernetes runs and scales) separate from the **actors** that run on it.

When an actor goes idle, Substrate snapshots its workspace (and optionally its whole memory) and gives the worker back. When a request comes in for a paused actor, Substrate's router finds a free worker and restores the actor into it. If no worker is free yet, the router holds the request for a moment instead of failing it. None of this goes through the Kubernetes API server, which is why it's cheap enough to do on every turn or every tool call.

![The suspend and resume cycle: an Actor on Worker A suspends to an actor snapshot holding its memory and files, freeing the slot; when a request arrives, the atenet-router routes it and the Actor resumes on Worker B with the same state](/images/blog/kagent-v1-0-release/suspend-resume.png)

The other half is security. Every actor runs in its own gVisor or microVM sandbox. Substrate doesn't run plain containers at all, because every actor is treated as untrusted, even when it shares a worker with other actors.

![Two Worker Pods: a gVisor worker hosting two Actors, each in its own sandbox with a user-space kernel, and a Cloud Hypervisor microVM worker hosting an Actor with a guest kernel of its own. Every Actor is treated as untrusted, and each activation starts from its own snapshot](/images/blog/kagent-v1-0-release/actor-sandboxes.png)

Each actor also gets its own SPIFFE-based identity that follows it from worker to worker, instead of belonging to the Pod. Incoming requests hit an ingress router that authenticates the caller and checks policy before the actor even wakes up. Outgoing traffic goes through the egress gateway, and the sandbox itself holds no credentials. Both gateways run on [agentgateway](https://agentgateway.dev), which understands LLM, MCP and A2A traffic, so a policy can say which models, MCP tools or other agents an actor is allowed to call.

My favorite part is how secrets work. The agent only ever sees a placeholder. When it calls an API, the egress gateway checks the request against that actor's policy and swaps in the real key or token on the way out. Anything the policy doesn't allow is denied.

![Actor -1 sends a request carrying a placeholder Authorization header to agentgateway, atenet-egress's data plane, which checks the actor's EgressPolicy, fetches the real token from a credential provider using the actor's JWT, and swaps it in before the request reaches source control. A prompt-injected agent has nothing to leak, and the token never ends up in an actor snapshot](/images/blog/kagent-v1-0-release/egress-credentials.png)

So a prompt-injected agent has nothing to steal, and the token never ends up in a snapshot either.

Kubernetes still does what it's good at: running and scaling the worker Pods. Everything you know about operating a cluster still applies. Substrate just handles the part that changes too fast for the Kubernetes control plane to keep up with.

## What kagent adds on top

Substrate is deliberately low level. Its own README calls it "a low-opinion runtime, not an agent SDK". It's very good at actors, workers, snapshots and egress policy. What it doesn't know is what an actor is *for*. It has no idea what a user, a conversation or an agent is. It can't tell an agent waiting on its user from one halfway through a tool call, and it doesn't know who's allowed to talk to which agent.

That's kagent's job. The simplest way I can put it: **kagent decides what an agent does, and Agent Substrate decides where it runs.**

![The stack: kagent as the agent platform on top (Sessions kept in kagent's database, credential injection on egress, Agent plus Harness compiled into an ActorTemplate, suspending Actors while agents wait, and an A2A gateway that decides who may talk to which conversation), Agent Substrate in the middle (Actors, Workers, snapshots, sandboxes, identity, egress), and Kubernetes at the bottom (nodes, Worker Pods, networking, storage, autoscaling)](/images/blog/kagent-v1-0-release/kagent-substrate-layers.png)

Here's what you get in 1.0:

- **Bring your own harness.** Run kagent's own agent runtime, Claude, Codex, or any container that speaks kagent's A2A contract, all with the same lifecycle, access controls and sandboxing.
- **Declarative agents.** Define an agent from a model (OpenAI, Anthropic, Gemini, Vertex AI, Azure, Bedrock, Ollama and more), a prompt, MCP tools, skills and plugins, and reuse that definition across agents and harnesses.
- **Safe rollouts.** Change a prompt or a model and new conversations pick it up, while the ones already in flight stay on the version they started with.
- **Auto-suspend.** Every conversation suspends when its turn ends and resumes on whichever worker is free when the next message arrives.
- **Human-in-the-loop.** An agent can stop and ask a person for approval, suspend right there, and pick up the same operation when they answer.
- **History, checkpoints and forks.** You can read a conversation's history without waking its sandbox, checkpoint a conversation and fork a new one from it, and idle conversations clean themselves up after seven days by default.
- **Users, ownership and sharing.** People sign in through your OIDC provider, every conversation belongs to whoever started it, and they can share it with others.
- **No credentials in the agent.** Model API keys and MCP credentials get injected at egress and rotate without restarting anything.
- **Sandboxes on demand.** People and agents can grab a short-lived sandbox to run a script or process some files. kagent can also act as the self-hosted sandbox for Claude Managed Agents (experimental for now).
- **Use it from anywhere.** Agents are reachable over A2A and MCP, and kagent has a UI with a view into Substrate, a CLI, and OpenTelemetry traces, metrics and token usage.

Most of that maps straight back to the assumptions Pods broke. Auto-suspend and human-in-the-loop mean an agent that mostly waits stops costing you a Pod. Sandboxes and gateway-held credentials let you run code nobody reviewed. Checkpoints and forks treat the agent's state as the work it is. Ownership plus a per-actor identity means policy and audit know which user an agent is acting for.

### The new API in one paragraph

Under the hood, 1.0 splits an agent in two. An **AgentTemplate** is what the agent does: model, prompt, tools, skills. A **Harness** is how it's allowed to run: which engine, which worker pool, where snapshots go. An **Agent** pairs one of each, and every chat with it becomes a **Session** running on its own actor. It's one more resource to learn, but the split pays off quickly: you can run the same behavior on a bigger pool or a different engine without copying the prompt and tool list around.

## See it in action

Here's the kagent UI on a real cluster running Agent Substrate. A new conversation starts an actor, and once the turn is done, the Substrate view shows that session (and every other one) suspended and holding no compute until the next message comes in. Keep an eye on **Actors running: 0/20**.

![The kagent UI on a cluster running Agent Substrate: starting a conversation with an agent, then switching to the Substrate view, where every session is suspended and 0 of 20 actors are running](/images/blog/kagent-v1-0-release/kagent-ui-substrate.gif)

If you'd rather click around yourself without installing anything, I built an interactive version of the UI on the [kagent.dev](https://kagent.dev) homepage. Hit **Take the tour** for the guided version. This is the one that sums up Substrate for me: an SRE agent works out why checkouts are failing, then stops to ask before running `kubectl rollout undo`. While it waits for that approval, it's suspended and using no compute at all.

![An SRE incident commander agent in the interactive kagent UI demo: it queries Loki logs through the Grafana MCP server, reports 1,240 failed checkout attempts, and asks for approval before running kubectl rollout undo. A status pill reads gVisor, kagent-high, suspended, and a divider reads idle, actor suspended to snapshot, worker freed](/images/blog/kagent-v1-0-release/kagent-ui-chat.png)

## Getting started

You need a Kubernetes cluster (1.37 or later, since Substrate uses a couple of certificate APIs that went GA there), `helm`, and a model provider key. OpenAI is the default. Install the CLI and run `kagent install`, which sets up Agent Substrate, PostgreSQL and kagent in one go:

```bash
curl -sL https://raw.githubusercontent.com/kagent-dev/kagent/refs/heads/main/scripts/get-kagent | bash -s -- --version v1.0.0
export OPENAI_API_KEY=<your key>
kagent install
```

No cluster handy? `kind create cluster --image kindest/node:v1.37.0` works fine for kicking the tires.

From there, [Your first agent](https://kagent.dev/docs/kagent/latest/get-started/your-first-agent/) walks you through defining an agent and talking to it. The [installation guide](https://kagent.dev/docs/kagent/latest/setup/installation/) covers installing with the Helm charts directly if you're doing GitOps. To size things before you go further, the [Agent Substrate calculator](/substrate-calculator) estimates wake-up times and the CPU, memory and storage your agents need, based on the performance and scale testing done for 1.0.

**On 0.x today?** 1.0 is a new API on a new runtime, so plan on a fresh install. The [Upgrade from 0.x guide](https://kagent.dev/docs/kagent/latest/operations/upgrade-from-0x/) maps each old resource onto its new equivalent.

## Get involved

kagent and Agent Substrate are both open source CNCF projects, and both are moving fast. If you're running agents on Kubernetes, or trying to, give 1.0 a spin and tell us where it breaks, what your agents need that it doesn't do yet, and what you'd want from the layer above Substrate.

- **kagent:** [GitHub](https://github.com/kagent-dev/kagent), [kagent.dev](https://kagent.dev), the [Discord](https://discord.gg/Fu3k65f2k3), and the [community meeting](https://kagent.dev/community) every Tuesday.
- **Agent Substrate:** [GitHub](https://github.com/agent-substrate/substrate) and [ate.dev](https://ate.dev), with Helm charts in [kagent-dev/substrate](https://github.com/kagent-dev/substrate).

Want to go deeper? Check out our earlier posts on [kagent and Agent Substrate sandboxes](/blog/kagent-agent-substrate-sandboxes) and [comparing agent sandboxes](/blog/agent-sandboxes-four-axes).

Eitan promised agents that wake up fast, cost nothing when idle, and are sandboxed by default. They're here. Go build something.
5 changes: 1 addition & 4 deletions src/components/mdx/code-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import React, { useState } from 'react';
import { Highlight, themes } from 'prism-react-renderer';
import { Check, Copy } from 'lucide-react';
import { useTheme } from 'next-themes';

interface CodeBlockProps {
children: string;
Expand All @@ -11,8 +10,6 @@ interface CodeBlockProps {
}

export function CodeBlock({ children, language = 'typescript', className }: CodeBlockProps) {
const { resolvedTheme } = useTheme();

const [copied, setCopied] = useState(false);

const highlightedLines = new Set<number>();
Expand Down Expand Up @@ -63,7 +60,7 @@ export function CodeBlock({ children, language = 'typescript', className }: Code
</button>
</div>
<Highlight
theme={resolvedTheme === 'dark' ? themes.jettwaveDark : themes.jettwaveLight}
theme={themes.jettwaveDark}
code={code}
language={language}
>
Expand Down
4 changes: 3 additions & 1 deletion src/components/site-chrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { usePathname } from "next/navigation";
/* The homepage and the pages that share its look bring their own nav and footer; every other page uses the shared ones. */
const OWN_CHROME = new Set(["/", "/substrate-calculator"]);

const ownsChrome = (path: string) => OWN_CHROME.has(path) || path === "/blog" || path.startsWith("/blog/");

export default function SiteChrome({ children }: { children: React.ReactNode }) {
return OWN_CHROME.has(usePathname()) ? null : <>{children}</>;
return ownsChrome(usePathname()) ? null : <>{children}</>;
}
Loading