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
1 change: 1 addition & 0 deletions .markdownlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ MD033:
- List
- PackageInstallation
- Panel
- PhotoGrid
- PipelineChoiceTabs
- Schema
- span
Expand Down
3 changes: 3 additions & 0 deletions dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ basicsaga
bbox
BCPROCKS
Bedok
Benjie
bfnrt
birthdates
blazor
Expand Down Expand Up @@ -95,6 +96,7 @@ GHSA
Giroux
grafbase
GraphiQL
graphqlconf
graphqlconfig
graphqlrc
graphqls
Expand Down Expand Up @@ -224,6 +226,7 @@ roundtrips
rovo
rovodev
RRGGBB
Ruckstuhl
runbooks
sandboxed
Satisfiability
Expand Down
112 changes: 112 additions & 0 deletions website-next/content/blog/2026-06-24-graphqlconf-2026.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
date: "2026-06-24"
title: "Agents, Federation, and a Community"
description: "Notes from GraphQLConf 2026 and Working Group Day at Meta: AI agents meeting GraphQL schemas, the Composite Schema federation standard, and a community building what comes next."
tags:
[
"graphql",
"graphqlconf",
"community",
"ai",
"agents",
"federation",
"fusion",
]
author: Salome Ruckstuhl
authorUrl: https://github.com/sal-ome
authorImageUrl: https://avatars.githubusercontent.com/u/67280421?v=4
featuredImage: "header.jpg"
---

Every year, GraphQLConf brings the community together and reminds us how much can change in a year. This time we made the trip to the Bay Area for GraphQLConf 2026, stayed for the Working Group Day at Meta, and came home with a lot of notes, a lot of ideas, and a camera roll that was mostly redwoods.

A few themes kept coming up in the talks, hallway conversations, and working sessions. AI agents are becoming a real part of the GraphQL world, both as consumers of APIs and as tools that help us build them. Federation now has a shared, vendor-neutral standard. And underneath both of those things, the community is putting more structure in place for what comes next.

Here are the parts that stood out to us.

# When agents meet your schema

Pascal opened one of our favorite threads of the conference with a simple question: what if an agent could pick up any GraphQL API and use it reliably, without a human wiring everything together first?

GraphQL is, on paper, an ideal foundation for agents. The schema describes exactly what exists, the introspection gives an agent a way to discover capabilities, queries let it retrieve information, and mutations let it take action. Yet in the real world, schemas are large, and contexts are tiny.

Semantic introspection is a way to work around this problem. Instead of handing an agent an entire schema and hoping it figures things out, semantic introspection gives it a more reliable way to explore a graph, understand the meaning behind types and fields, and pull in only the context it needs.

The goal is simple: any agent should be able to work with any GraphQL endpoint without a pile of custom glue code. If you want the deeper version, we wrote about it earlier this year in [Semantic Introspection](./2026-04-22-semantic-introspection.md).

# Closing the loop for coding agents

Where Pascal looked at agents using your API, Michael looked at agents changing it. Coding agents now implement features, refactor systems, and ship changes at a pace that would have sounded made up twelve months ago. But an agent is only as good as its feedback loop.

If you point an agent at your product API and ask it to add a review system, it might produce something that looks reasonable in isolation. And without knowing what clients actually use, it might happily reshape your types, move fields, and break consumers you had forgotten were out there.

GraphQL has a quiet advantage here: every client operation declares exactly which fields and types it needs. That gives you field-level usage data.

Give that information to a coding agent, and it is no longer guessing. It can understand what is actually used, make safer schema changes, and avoid breaking existing consumers.

That combination of GraphQL's visibility and capable coding agents creates a feedback loop we did not really have before. It means less time reviewing AI-generated code that (just) almost works, and more time working with changes that are much closer to correct the first time.

You can try out our prototyping skills with:

```bash
dnx skillz chillicream/agent-skills
```

# The state of GraphQL federation

The community has spent a lot of time standardizing how distributed GraphQL systems should work, with GraphQL acting as the gateway. The result is the Composite Schema specification, now being developed under the GraphQL Foundation.

The important detail is where the spec stands today: it is still Stage 0, Preliminary. In other words, it is an active proposal, not a finished standard yet, and parts of it can still change before it reaches Draft.

Even so, this is already a big step. Instead of every vendor shipping its own version of federation, Composite Schema gives the ecosystem a shared, vendor-neutral direction for composition, validation, and distributed execution.

Fusion is our implementation of that direction, and it is already compatible with the Composite Schema spec as it evolves. If you want to try it today, start with the [Fusion getting started guide](../docs/fusion/getting-started.md).

# A community shaping what comes next

GraphQL has always been a community-driven project, and the closing keynote was a good reminder of how much of the ecosystem exists because people kept showing up and doing the work.

The GraphQL GAP proposal is part of that next phase. It is meant to create new ways for people across the community to collaborate and contribute. Together with updates from the Working Groups, it made one thing very clear: GraphQL's next chapter will not be written by one company or one team.

# Behind the scenes: Working Group Day at Meta

One of our highlights was the community day after the conference. This year, we spent a full day together on Meta's campus, split across different Working Group tracks.

A big part of the day focused on semantics, taking semantic introspection and turning it into concrete next steps. Salome spent the day working on the community side: shaping a new Community Hub to give the ecosystem a clearer home, and helping improve the Ambassador program to recognize and support the people bringing GraphQL into their companies, communities, and local meetups.

The best part of the day was the side discussions. A casual conversation turned into Jordan shipping a new [Relay.js feature](https://github.com/facebook/relay/pull/5295), and Benjie could move several Golden Path initiatives forward, because so many maintainers were in one room. Community Day also turned out to be one of the most productive GraphQL Working Group sessions we've had. It's amazing what happens when you lock the TSC in the same room: suddenly discussions start, opinions align, and quorum is reached!

# When we were not talking GraphQL

Outside the conference schedule, we managed to see a bit of the Bay Area too. We hiked through Big Basin Redwoods State Park, saw the Mother of the Forest, and met a few banana slugs moving through the forest at their own pace.

We also drove across the Golden Gate Bridge, spent some time wandering through malls, and drank enough coffee to keep the whole trip moving. More than anything, it was a reminder that one of the best parts of any conference is getting to experience a new place with good people.

# Wrap up

What stayed with us after GraphQLConf was not one single talk or announcement, but the feeling that GraphQL is entering a new phase.

Agents are starting to change how APIs are used and built. Federation now has a shared, vendor-neutral standard. And just as importantly, the community is putting real structure behind the work through Working Groups, GAP, the Community Hub, and the Ambassador program all pointing in the same direction.

That is what made the week feel so energizing. GraphQL does not feel like something being handed down by one company or one team. It feels like something a lot of people are building together.

If you want to follow along, watch for the GraphQLConf recordings, read our deep dive on [Semantic Introspection](./2026-04-22-semantic-introspection.md) and if you want to try GraphQL Federation yourself, give [Fusion](../docs/fusion/getting-started.md) a go!

And if you want to talk GraphQL with people who think about it a little too much, our [Slack](https://slack.chillicream.com) is always open.

See you at the next one.

# A few snapshots from the trip

<PhotoGrid
images={[
{ src: "/images/blog/2026-06-24-graphqlconf-2026/sf1.jpg", alt: "Photo from the GraphQLConf 2026 trip to the Bay Area" },
{ src: "/images/blog/2026-06-24-graphqlconf-2026/sf2.jpg", alt: "Photo from the GraphQLConf 2026 trip to the Bay Area" },
{ src: "/images/blog/2026-06-24-graphqlconf-2026/sf3.jpg", alt: "Photo from the GraphQLConf 2026 trip to the Bay Area" },
{ src: "/images/blog/2026-06-24-graphqlconf-2026/sf4.jpg", alt: "Photo from the GraphQLConf 2026 trip to the Bay Area" },
{ src: "/images/blog/2026-06-24-graphqlconf-2026/sf5.jpg", alt: "Photo from the GraphQLConf 2026 trip to the Bay Area" },
{ src: "/images/blog/2026-06-24-graphqlconf-2026/sf6.jpg", alt: "Photo from the GraphQLConf 2026 trip to the Bay Area" },
{ src: "/images/blog/2026-06-24-graphqlconf-2026/sf7.jpg", alt: "Photo from the GraphQLConf 2026 trip to the Bay Area" },
{ src: "/images/blog/2026-06-24-graphqlconf-2026/sf8.jpg", alt: "Photo from the GraphQLConf 2026 trip to the Bay Area" },
]}
/>
2 changes: 2 additions & 0 deletions website-next/mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from "@/src/components/ExampleTabs";
import { MochaTopologyVisualization } from "@/src/components/MochaTopologyVisualization";
import { PackageInstallation } from "@/src/components/PackageInstallation";
import { PhotoGrid } from "@/src/components/PhotoGrid";
import { YouTubeVideo } from "@/src/components/YouTubeVideo";
import { detectAdmonition } from "@/src/helpers/detectAdmonition";
import {
Expand Down Expand Up @@ -83,6 +84,7 @@ const components: MDXComponents = {
Tab,
YouTubeVideo,
MochaTopologyVisualization,
PhotoGrid,

// TODO: Replace these with proper components
ApiChoiceTabs,
Expand Down
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.
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.
62 changes: 62 additions & 0 deletions website-next/src/components/PhotoGrid.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Meta, StoryObj } from "@storybook/nextjs-vite";
import { PhotoGrid } from "./PhotoGrid";

// Deterministic inline placeholders: no network (so the screenshot is stable in
// visual tests) and no text (so there is no cross-environment font rendering).
// The grid forces a uniform 2:3 cell, so the source dimensions only affect how
// each placeholder is cropped.
const swatch = (hue: number, width = 400, height = 600) =>
"data:image/svg+xml," +
encodeURIComponent(
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">` +
`<rect width="100%" height="100%" fill="hsl(${hue},55%,45%)"/>` +
`<circle cx="${width * 0.5}" cy="${height * 0.38}" r="${Math.min(width, height) * 0.28}" fill="hsl(${hue},65%,72%)"/>` +
`</svg>`,
);

const photo = (index: number, width?: number, height?: number) => ({
src: swatch(index * 45, width, height),
alt: `Trip photo ${index + 1}`,
});

const SAMPLE = Array.from({ length: 8 }, (_, i) => photo(i));

const meta = {
title: "Components/PhotoGrid",
component: PhotoGrid,
parameters: { layout: "padded" },
decorators: [
(Story) => (
<div style={{ width: "min(100%, 1100px)" }}>
<Story />
</div>
),
],
} satisfies Meta<typeof PhotoGrid>;

export default meta;
type Story = StoryObj<typeof meta>;

// Click any thumbnail to open the popout (Esc / arrow keys / buttons navigate).
export const Default: Story = {
args: { images: SAMPLE },
};

export const ThreePhotos: Story = {
args: { images: SAMPLE.slice(0, 3) },
};

// Sources with different aspect ratios (landscape, portrait, square) still line
// up on a uniform 2:3 grid because every cell uses object-cover.
export const MixedAspectRatios: Story = {
args: {
images: [
photo(0, 900, 450),
photo(1, 400, 720),
photo(2, 500, 500),
photo(3, 960, 420),
photo(4, 380, 660),
photo(5, 480, 480),
],
},
};
41 changes: 41 additions & 0 deletions website-next/src/components/PhotoGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Picture } from "@/src/design-system/Picture";
import { PhotoLightbox } from "@/src/components/PhotoLightbox";

interface PhotoGridImage {
readonly src: string;
readonly alt: string;
}

interface PhotoGridProps {
readonly images: readonly PhotoGridImage[];
}

export function PhotoGrid({ images }: PhotoGridProps) {
if (images.length === 0) {
return null;
}

return (
<PhotoLightbox
images={images}
className="my-8 grid list-none grid-cols-2 gap-3 p-0 sm:grid-cols-3 lg:grid-cols-4"
>
{images.map((image, index) => (
<li key={`${image.src}-${index}`} className="m-0 p-0">
<a
href={image.src}
data-photo-index={index}
className="group block cursor-zoom-in overflow-hidden rounded-lg"
>
<Picture
src={image.src}
alt={image.alt}
sizes="(max-width: 639px) 50vw, (max-width: 1023px) 33vw, 256px"
className="aspect-[2/3] w-full object-cover transition-transform duration-300 group-hover:scale-105"
/>
</a>
</li>
))}
</PhotoLightbox>
);
}
75 changes: 75 additions & 0 deletions website-next/src/components/PhotoLightbox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { Meta, StoryObj } from "@storybook/nextjs-vite";
import { Picture } from "@/src/design-system/Picture";
import { PhotoLightbox } from "./PhotoLightbox";

// Deterministic inline placeholders: no network (so the screenshot is stable in
// visual tests) and no text (so there is no cross-environment font rendering).
const swatch = (hue: number, width = 400, height = 600) =>
"data:image/svg+xml," +
encodeURIComponent(
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">` +
`<rect width="100%" height="100%" fill="hsl(${hue},55%,45%)"/>` +
`<circle cx="${width * 0.5}" cy="${height * 0.38}" r="${Math.min(width, height) * 0.28}" fill="hsl(${hue},65%,72%)"/>` +
`</svg>`,
);

const SAMPLE = Array.from({ length: 6 }, (_, i) => ({
src: swatch(i * 60),
alt: `Trip photo ${i + 1}`,
}));

const GRID_CLASS =
"grid list-none grid-cols-2 gap-3 p-0 sm:grid-cols-3 lg:grid-cols-4";

// PhotoLightbox wraps arbitrary thumbnail markup; the only contract is that each
// clickable thumbnail is an `<a data-photo-index={i}>`. The popout image comes
// from the `images` prop, matched to the clicked thumbnail by that index.
const thumbnails = (images: readonly { src: string; alt: string }[]) =>
images.map((image, index) => (
<li key={`${image.alt}-${index}`} className="m-0 p-0">
<a
href={image.src}
data-photo-index={index}
className="group block cursor-zoom-in overflow-hidden rounded-lg"
>
<Picture
src={image.src}
alt={image.alt}
className="aspect-[2/3] w-full object-cover transition-transform duration-300 group-hover:scale-105"
/>
</a>
</li>
));

const meta = {
title: "Components/PhotoLightbox",
component: PhotoLightbox,
parameters: { layout: "padded" },
decorators: [
(Story) => (
<div style={{ width: "min(100%, 1100px)" }}>
<Story />
</div>
),
],
} satisfies Meta<typeof PhotoLightbox>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
images: SAMPLE,
className: GRID_CLASS,
children: thumbnails(SAMPLE),
},
};

// A single photo hides the previous/next controls and the counter.
export const SinglePhoto: Story = {
args: {
images: SAMPLE.slice(0, 1),
className: GRID_CLASS,
children: thumbnails(SAMPLE.slice(0, 1)),
},
};
Loading
Loading