AgentSpaces for Spring AI applications. Spring AI gives an application a
portable, well-engineered way to call models; AgentSpaces gives a fleet of
processes a shared, leased, signed, replicated tuple space. This project plugs
the fleet into Spring AI's own extension points (tools, advisors, chat memory,
ChatModel, EmbeddingModel, observations, and MCP), so an application stays
ordinary Spring AI code while its agents coordinate across machines.
It needs no Embabel. The Embabel extension (embabel-agentspaces in the core
repository) integrates at the level of a planned agent and its actions; this
project integrates at the level of a model call and its tools. An application
can use either or both.
Requirements: Java 21, Spring Boot 4.1, Spring AI 2.0, and the AgentSpaces
starter (agentspaces-spring-boot-starter). The design and its rationale are in
docs/design.md.
Add the library beside the starter and a Spring AI model starter:
<dependency>
<groupId>ai.badmonkey.agentspaces</groupId>
<artifactId>agentspaces-springai</artifactId>
<version>0.2.0</version>
</dependency>A worker is a @SpaceAgent bean with an injected ChatClient.Builder and a
@SpaceTake method. The space distributes the work, the model does it, and the
take lease renews itself on every model round-trip:
@SpaceAgent(description = "Summarizes a topic into a short briefing")
public class Summarizer {
private final ChatClient chat;
Summarizer(ChatClient.Builder builder) { this.chat = builder.build(); }
@SpaceTake(space = "work", lease = "2m")
public Summary summarize(Brief brief) {
return new Summary(brief.topic(), chat.prompt().user("Summarize: " + brief.topic()).call().content());
}
}An orchestrator attaches the fleet's tools to its ChatClient, and its model
calls agents on other peers from its own tool loop:
@Bean
Orchestrator orchestrator(ChatClient.Builder builder, FleetTools fleet) {
return new Orchestrator(builder.defaultToolCallbacks(fleet.toolCallbacks()).build());
}Example 15 runs exactly this across three Spring Boot applications.
| Feature | Beans | What it gives an application |
|---|---|---|
| F1. Fleet tools | FleetTools |
The fleet's AgentCards as Spring AI ToolCallbacks. Each request sees the live tool set; a FleetToolsChangedEvent announces changes. |
| F2. Discovery and data tools | FleetDiscoveryTools |
find_fleet_agents, list_fleet_assets, and fetch_fleet_data, each enabled separately. |
| F3. Crash-safe LLM workers | TakeLeaseAdvisor, SpaceChatMemoryRepository, FleetChatMemoryAdvisor |
A take lease that renews on every model round-trip, and chat memory in a replicated space, so a task that reappears after a crash resumes with its conversation. |
| F4. Model access as a fleet service | FleetChatModel, ModelServer |
A ChatModel whose provider is the fleet: crash tolerance, routing by model or by price, media over the block exchange, streaming with cancellation and restart policies. |
| F5. Real embeddings | SpringAiEmbedder |
Semantic discovery ranks with the application's EmbeddingModel, and the embedder's identity is advertised so peers query only peers that rank alike. |
| F6. Fleet-wide usage | FleetUsageObservationHandler, FleetUsage, UsagePanel |
Token usage from Spring AI's gen_ai observations, as fleet-wide push-sum totals, attributed usage entries, and a console panel. |
| F7. MCP export | FleetMcpToolSync |
The fleet agents a deployment names, published through Spring AI's MCP server and kept in step with list_changed. |
| F8. Patterns | VectorStoreAssetProvider (served with vector-store.enabled=true); examples/springai-patterns |
A VectorStore as a fleet data asset; model judges voting with @Ballot; models priced by tokens under AUCTION; an extraction ensemble. |
Each feature has its own auto-configuration and its own enabled property;
F1 and the take-lease advisor are on by default, and every feature that widens
what a model can reach or sends prompts across the fleet is off by default.
A fleet tool writes its input as a task, and in a tuple space the task goes to
whichever agent takes its type. A tool's card names one agent that does, and
the tool's description says so; it does not pin the work to that agent. To keep
an agent's work private, give it a request type of its own, or restrict who may
take from the space with admission: authorizer and the SPACE_TAKE grant.
Spring AI's MCP server auto-configuration publishes every ToolCallbackProvider
bean in the context to outside MCP clients. FleetTools and
FleetDiscoveryTools are therefore plain beans that hand out their providers
through toolCallbacks(), which the application attaches to a ChatClient
explicitly. The fleet crosses into MCP only through F7, which exposes nothing
until agentspaces.springai.mcp.include-agents names the agents it may.
Every decision a deployment might want to change is an interface with one default bean. Declaring a bean of the interface replaces the default.
| Interface | Default | Replace it to |
|---|---|---|
FleetToolFilter |
include and exclude lists and require-attested |
apply a custom trust rule to which cards become tools |
ToolNamingStrategy |
fleet_ prefix, sanitized, truncated, deduplicated |
follow a tool naming convention |
ConversationIdResolver |
memory.conversation-id: take, agent, or explicit |
key conversations by tenant, user, or session |
ModelCatalog |
the names in model-server.models, served by the provider ChatModel |
serve models from a registry |
ModelRequestRouter |
by model name, or by price under AUCTION | route by tenant, region, or load |
StreamChunkingPolicy |
100 ms or 32 deltas per chunk | trade latency against entry count |
StreamRestartPolicy |
fail or restart |
recover a stream whose server died in a custom way |
UsageSink |
fleet totals, usage entries, console panel | export usage to a billing or FinOps system |
Spring AI's own interfaces are the rest of the API: the components are a
ToolCallbackProvider (through toolCallbacks()), a ChatMemoryRepository, a
ChatModel, a CallAdvisor and StreamAdvisor, and an ObservationHandler.
All properties sit under agentspaces.springai. The spaces that F2, F3, F4,
F6, and F8 use must be declared under agentspaces.groups[].spaces; a missing one
fails at startup with the YAML that adds it.
agentspaces:
groups:
- name: research-fleet
founding: research-fleet-v1
spaces:
- { name: work }
- { name: conversations } # F3 memory
- { name: model-requests } # F4 model service
springai:
tools:
prefix: fleet_
exclude-agents: [internal-auditor]
memory:
enabled: true
conversation-id: take
model-client:
enabled: true # this node calls models through the fleet
model-server:
enabled: false # true on the peers that hold provider keys
models: [gpt-4.1-mini]
embedder:
type: spring-ai
model: text-embedding-3-small
usage:
enabled: true
mcp:
enabled: false
include-agents: []| Property | Default | Meaning |
|---|---|---|
group |
first group | The group this project works in |
tools.enabled |
true |
Register FleetTools |
tools.prefix, tools.include-agents, tools.exclude-agents, tools.require-attested |
fleet_, all, none, false |
Tool naming and filtering |
tools.timeout, tools.on-timeout |
30s, result |
How long a tool call waits, and whether a timeout returns an explanation or throws |
tools.refresh-interval, tools.cache-ttl |
5s, 1s |
Change-event cadence and snapshot caching |
discovery-tools.agents, .assets, .data |
false |
Enable each discovery tool |
discovery-tools.data-space, .timeout, .max-result-chars |
data, 10s, 20000 |
Data space, query timeout, result cap |
take-lease.enabled, take-lease.stream-renew-interval |
true, 30s |
The lease advisor |
memory.enabled, .space, .ttl, .conversation-id, .max-messages |
false, conversations, 24h, take, 20 |
Chat memory in the space |
model-client.enabled, .space, .model, .timeout, .stream.on-restart |
false, model-requests, server default, 120s, fail |
The calling side of F4 |
model-server.enabled, .space, .models, .routing, .prices.<model> |
false, model-requests, none, model, 1.0 |
The serving side of F4 |
model-server.lease, .chunk-interval, .chunk-max-deltas, .chunk-lease, .response-lease, .concurrency, .model-beans.<model> |
2m, 100ms, 32, 2m, 10m, 4, none |
Server tuning; model-beans maps a model to a ChatModel bean when several providers are present |
embedder.type, .model, .require-match, .cache-size |
hashing, class name, true, 4096 |
F5 |
usage.enabled, .metrics-space, .entry-lease, .publish-interval |
false, none, 24h, 30s |
F6 |
mcp.enabled, .include-agents, .discovery-tools |
false, none, false |
F7 |
vector-store.enabled, .space, .asset, .description, .freshness |
false, data, knowledge-base, a generic description, 5m |
F8: serve the application's VectorStore bean as a fleet data asset |
- Prompts cross the fleet under F3 and F4. Give the conversation and model
spaces their own admission (
allowlist,credential, orauthorizer) and a group content key. - Only trusted servers answer.
FleetChatModelaccepts a response or stream chunk only from an issuer the authorizer permitsMODEL_SERVEfor the model:agentspaces.security.grants.model-serveunder the membership profiles, theaspace:model-serve[:<model>]scope under the OIDC profiles. - Tool descriptions are model input. Filter which cards become tools, and
consider
tools.require-attested=truewith subordinate agent keys. - Credentials stay on server peers. Under F4, provider keys live only on the
ModelServerpeers. - MCP clients are outside the fleet. F7 exposes nothing until named.
The project consumes the published AgentSpaces libraries by Maven coordinates.
Once the core artifacts at agentspaces.version are on Maven Central:
mvn clean verify
Until then, install the core into your local repository first:
git clone https://github.com/badmonkeyai/agentspaces.git
mvn -f agentspaces/pom.xml install -DskipTests
mvn clean verify
The example applications (examples/) build only with -Pexamples; CI runs
mvn verify -Pexamples, so they stay green with the library.
mvn install -Pcentral-publish -Dgpg.skip=true builds exactly what Maven
Central receives: the library's jar, sources jar, and javadoc jar, plus the
parent pom. See CONTRIBUTING.md.
Tests run on scripted ChatModel and EmbeddingModel beans, so no test calls a
live provider. Unit tests cover each component; ApplicationContextRunner tests
cover every auto-configuration condition and override; and flow tests run real
fleets over TCP: fleet tools, discovery, a worker killed mid-loop that resumes on
another peer with its conversation, the model service with crashes, streaming,
cancellation, price routing, and trust, usage across peers, and MCP export to a
real MCP client.
| Module | What it is |
|---|---|
core |
The library and its auto-configurations (artifact agentspaces-springai) |
examples/springai-fleet |
Example 15: a Spring AI orchestrator on the fleet |
examples/springai-patterns |
Judges, token-priced auctions, extraction ensembles |
Contributions are welcome under the Developer Certificate of Origin; see CONTRIBUTING.md and the Code of Conduct. Report vulnerabilities privately, as SECURITY.md describes.
Copyright 2026 Bad Monkey, Inc.
AgentSpaces Spring AI is an open source project from Bad Monkey, Inc, licensed
under the Apache License 2.0; see LICENSE.
For questions, contact oss@badmonkey.ai