Skip to content

Instantly share code, notes, and snippets.

@jfarcand
Created April 17, 2026 23:11
Show Gist options
  • Select an option

  • Save jfarcand/561e1eb12e39baadf0e255d117c4514e to your computer and use it in GitHub Desktop.

Select an option

Save jfarcand/561e1eb12e39baadf0e255d117c4514e to your computer and use it in GitHub Desktop.
Atmosphere — AI Agent Foundation v0.7 (shipped + how-to-build, supersedes v0.6)

Atmosphere — AI Agent Foundation v0.7 (what shipped + how to build an app)

Supersedes v0.6. v0.6 was the final strategy + implementation plan; v0.7 is the "what landed, how you use it" pass.

Branch: feat/ai-agent-foundation — CI fully green. Target release: atmosphere-ai 4.0.39.


TL;DR

  • 8 named primitives shipped, one default implementation each, pinned by unit tests.
  • 2 proof samples drive the primitives end-to-end. Both validated live via chrome-devtools against a real LLM (Gemini 2.5 Flash via Google's OpenAI-compatibility endpoint).
  • 2 regression fixes landed during validation:
    1. Built-in runtime's OpenAiCompatibleClient now emits tool_calls on assistant messages and name on tool-role messages — additive for OpenAI, required by stricter OpenAI-compatible endpoints.
    2. coding-agent sample switched from session.stream() (which dispatches text to the LLM as a fresh user turn) to session.send() (which streams bytes to the client unchanged) — so the real file content reaches the UI instead of a hallucinated chat reply.
  • Playwright regression suite pins both fixes end-to-end.
  • Docs updated in-repo (README.md, docs/foundation-primitives.md, e2e/README.md) and on atmosphere.github.io (tutorial/09-ai-endpoint.md).

The eight primitives

# Primitive What it is Default implementation
1 AgentState Conversation history, facts, notes, rules FileSystemAgentState — OpenClaw-compatible layout under users/<userId>/agents/<agentId>/
2 AgentWorkspace Agent-as-artefact: parse a directory into an AgentDefinition OpenClawWorkspaceAdapter + AtmosphereNativeWorkspaceAdapter
3 ProtocolBridge Inbound facade for how an agent is reached InMemoryProtocolBridge — puts in-JVM dispatch on equal footing with wire bridges (MCP, A2A, AG-UI, gRPC)
4 AiGateway Outbound choke point for every LLM call Permissive default (1M/hour); CredentialResolver + GatewayTraceExporter pluggable
5 AgentIdentity Per-user permission mode, quota, audit File-backed; three PermissionMode values (accept-edits / plan / default)
6 ToolExtensibilityPoint Per-user MCP server discovery + credential resolution Reads MCP.md in the workspace, resolves creds from AgentIdentity.CredentialStore
7 Sandbox Isolated exec for untrusted code DockerSandboxProvider (preferred); in-process fallback for dev only
8 AgentResumeHandle Reattach a disconnected client to an in-flight run RunRegistry + X-Atmosphere-Run-Id header; DurableSessionInterceptor reattaches

Every primitive is runtime-agnostic: works across the seven hosted runtimes (Built-in, Spring AI, LangChain4j, ADK, Koog, Semantic Kernel, Embabel).

Proof samples

samples/spring-boot-personal-assistant@Coordinator + scheduler / research / drafter crew dispatched over InMemoryProtocolBridge. LLM-driven tool calling via three @AiTool methods; deterministic keyword-router fallback for no-key demos. Exercises primitives 1-6.

samples/spring-boot-coding-agent — clones a Git repo into a Docker sandbox, reads files, and streams the real content back to the UI. Exercises primitives 7-8.

OpenAI API compatibility

The Built-in runtime speaks OpenAI Chat Completions and works against any endpoint that exposes the OpenAI wire shape — OpenAI itself, local proxies (Embacle, Ollama), cloud providers that ship an OpenAI-compatible surface. Some endpoints are stricter than OpenAI on tool-call round trips: OpenAI treats tool_calls on assistant messages and name on tool-role messages as optional, but strict endpoints require both. OpenAiCompatibleClient now serializes both unconditionally. The JSON wire shape is pinned by ChatMessageSerializationTest so future refactors cannot silently regress either side.


Building an app with the foundation

Minimal app: a research coordinator with one crew member that returns a brief. Every snippet below uses real API on feat/ai-agent-foundation.

pom.xml — pick your runtime

<dependency>
  <groupId>org.atmosphere</groupId>
  <artifactId>atmosphere-spring-boot-starter</artifactId>
  <version>4.0.39-SNAPSHOT</version>
</dependency>
<dependency>
  <groupId>org.atmosphere</groupId>
  <artifactId>atmosphere-agent</artifactId>
  <version>4.0.39-SNAPSHOT</version>
</dependency>
<dependency>
  <groupId>org.atmosphere</groupId>
  <artifactId>atmosphere-coordinator</artifactId>
  <version>4.0.39-SNAPSHOT</version>
</dependency>
<!-- Built-in runtime has zero extra deps and talks to any OpenAI-compatible endpoint. -->

The coordinator

@Coordinator(name = "researcher",
             skillFile = "skill:researcher",
             description = "Answers questions by delegating to a research specialist.")
@Fleet({ @AgentRef(type = ResearchAgent.class) })
public class Researcher {

    // ThreadLocal bridges @Prompt's per-call fleet to the @AiTool method
    // the LLM invokes on the same thread.
    private static final ThreadLocal<AgentFleet> ACTIVE = new ThreadLocal<>();

    @Prompt
    public void onPrompt(String message, AgentFleet fleet, StreamingSession session) {
        ACTIVE.set(fleet);
        try {
            // Stream the user message through the runtime; the LLM sees the
            // @AiTool below and invokes it whenever the user wants research.
            session.stream(message);
        } finally {
            ACTIVE.remove();
        }
    }

    @AiTool(name = "research_topic",
            description = "Research a topic using the research specialist and return a short brief.")
    public String researchTopic(@Param("topic") String topic) {
        AgentResult r = ACTIVE.get()
                .agent("research-agent")
                .call("summarize_topic", Map.of("topic", topic));
        return r.textOr("(research specialist unavailable)");
    }
}

The crew member (A2A-style skill)

@Agent(name = "research-agent", description = "Research specialist.")
public class ResearchAgent {

    @AgentSkill(id = "summarize_topic", name = "Summarize Topic",
                description = "Return a short research brief on a single topic.",
                tags = {"research"})
    @AgentSkillHandler
    public void summarize(TaskContext task,
                          @AgentSkillParam(name = "topic",
                                           description = "Topic to research")
                          String topic) {
        String brief = "Brief on '" + topic + "':\n"
                     + "- Key finding A\n"
                     + "- Key finding B";
        task.addArtifact(Artifact.text(brief));
        task.complete("Summary generated");
    }
}

Skill file — src/main/resources/prompts/researcher.md

# Researcher

You help users answer research questions. You have one tool:

- `research_topic(topic)` — delegates to the research specialist.

Use the tool whenever the user is asking you to investigate or summarize a
topic. Keep your replies short and cite the brief the tool returns.

application.properties

server.port=8080
atmosphere.packages=com.example.researcher
# Any of the seven runtimes works. "built-in" has zero extra deps.
atmosphere.ai.runtime=built-in

Spring Boot entrypoint

@SpringBootApplication
public class ResearcherApplication {
    public static void main(String[] args) {
        SpringApplication.run(ResearcherApplication.class, args);
    }
}

Run it

# OpenAI
export LLM_API_KEY=sk-...

# or any strict OpenAI-compatible endpoint, e.g. Google's compat layer:
export LLM_API_KEY=AIzaSy...
export LLM_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai
export LLM_MODEL=gemini-2.5-flash

./mvnw spring-boot:run
open http://localhost:8080/atmosphere/console/

Ask: "Give me a quick brief on WebTransport adoption." The coordinator forwards the message to the LLM, which picks research_topic, which dispatches to ResearchAgent over InMemoryProtocolBridge. The specialist emits a text artifact, the LLM synthesizes a reply, and the Atmosphere AI Console streams it.

What's next (Phase 5 — release prep)

  • Cut atmosphere-ai 4.0.39 (includes the tool-round-trip fix).
  • Publish the foundation docs on the public site.
  • File follow-ups for: cross-runtime contract tests per primitive; AiGateway wire-in across all seven runtimes (currently mandatory on Built-in only); AgentResumeHandle end-to-end under network chaos.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment