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.
- 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:
- Built-in runtime's
OpenAiCompatibleClientnow emitstool_callson assistant messages andnameon tool-role messages — additive for OpenAI, required by stricter OpenAI-compatible endpoints. coding-agentsample switched fromsession.stream()(which dispatches text to the LLM as a fresh user turn) tosession.send()(which streams bytes to the client unchanged) — so the real file content reaches the UI instead of a hallucinated chat reply.
- Built-in runtime's
- Playwright regression suite pins both fixes end-to-end.
- Docs updated in-repo (
README.md,docs/foundation-primitives.md,e2e/README.md) and onatmosphere.github.io(tutorial/09-ai-endpoint.md).
| # | 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).
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.
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.
Minimal app: a research coordinator with one crew member that returns a brief. Every snippet below uses real API on feat/ai-agent-foundation.
<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. -->@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)");
}
}@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");
}
}# 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.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
@SpringBootApplication
public class ResearcherApplication {
public static void main(String[] args) {
SpringApplication.run(ResearcherApplication.class, args);
}
}# 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.
- 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;
AiGatewaywire-in across all seven runtimes (currently mandatory on Built-in only);AgentResumeHandleend-to-end under network chaos.