by sinabs
A Spring Boot agent harness built on AgentScope Java 2.0 and JDK 25: multi-turn chat over HTTP, a fresh agent per request, shared session history.
# Add to your Claude Code skills
git clone https://github.com/sinabs/spring-agent-scopeGuides for using ai agents skills like spring-agent-scope.
See how spring-agent-scope compares with popular alternatives.
spring-agent-scope is an open-source ai agents skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by sinabs. A Spring Boot agent harness built on AgentScope Java 2.0 and JDK 25: multi-turn chat over HTTP, a fresh agent per request, shared session history. It has 51 GitHub stars.
spring-agent-scope's catalog security scan is still queued. You can run an instant dependency and prompt-injection check now with the "Scan for vulnerabilities" button above.
Clone the repository with "git clone https://github.com/sinabs/spring-agent-scope" and add it to your Claude Code skills directory (see the Installation section above).
spring-agent-scope is primarily written in Java. It is open-source under sinabs on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other AI Agents skills you can browse and compare side by side. Open the AI Agents category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh spring-agent-scope against similar tools.
No comments yet. Be the first to share your thoughts!
Available after the catalog security scan passes.
⚠️ Third-Party Software Notice
This skill is third-party open-source software developed and hosted independently on GitHub. SkillsLLM is an informational directory and does not control or maintain the underlying repository.
Any security checks, ratings, or warnings displayed by SkillsLLM are automated and limited in scope. They do not constitute a security certification or guarantee that the software is safe, error-free, or free from malicious code, vulnerabilities, compromised dependencies, or prompt-injection risks.
Review the source code, permissions, dependencies, and configuration before installing or running any third-party skill. Use is at your own risk. To the maximum extent permitted by applicable law, SkillsLLM is not liable for losses arising from third-party software.
The deep catalog scan for this skill is still queued. Run an instant dependency check now instead.
Yoke · AgentHarness: a monolithic agent service built on Spring Boot and AgentScope Java (V2).
A Spring-based agent harness.
A yoke is the wooden frame that makes oxen and horses pull in the direction you want. Yoke does the same for models: it is the runtime layer wrapped around a model so it can do real work reliably. It turns AgentScope's agent capabilities into a ready-to-use HTTP service, so you can focus on your business logic instead of glue code:
POST /api/chat, plus POST /api/chat/stream that streams the reasoning process over SSE using the AG-UI protocol.get_weather tool queries current weather through the open-source Open-Meteo API (no key needed; its free tier is for non-commercial use only).local / test / prod profiles. In production, a missing API key fails the startup instead of shipping with an empty key.message returns 400.Requires JDK 25.
sdk env # switch to JDK 25 (if you use sdkman)
export ARK_API_KEY=... # the local profile talks to Volcengine Ark (OpenAI-compatible)
./gradlew bootRun # the local profile is active by default
curl -X POST localhost:8080/api/chat \
-H 'Content-Type: application/json' \
-d '{"message": "Hello"}'
# {"sessionId":"…","reply":"…"}
Send the returned sessionId with later requests to continue the same conversation; conversations with different sessionIds are isolated from each other. A sessionId you send yourself must follow the same rule as threadId below (letters, digits and _.-, 1 to 64 characters, not all dots), otherwise the request returns 400; a blank one is treated as missing. Run the tests with ./gradlew build.
POST /api/chat/stream speaks the AG-UI protocol: the request body is a standard RunAgentInput, and the response is a text/event-stream of AG-UI events pushed while the agent is reasoning. Use curl -N to turn off buffering:
curl -N -X POST localhost:8080/api/chat/stream \
-H 'Content-Type: application/json' \
-d '{"threadId": "demo", "runId": "run-1", "messages": [{"id": "msg-1", "role": "user", "content": "What is the weather in Beijing?"}]}'
Request rules (any violation returns 400 before the stream starts):
Content-Type must be application/json, otherwise 415. This also stops a cross-site page from starting a run with a text/plain request that skips the CORS preflightthreadId is the session ID and is required: letters, digits and _.-, 1 to 64 characters, not all dots. It shares the same history as sessionId of POST /api/chatrunId is required and must not be blank (RunAgentInput requires it)messages must not be empty, every message needs an id and a role, and the last one must be a user message with non-blank plain text (multimodal content blocks are not supported)user message. Earlier messages, tools, context, state, forwardedProps and resume are ignored, so a client cannot add tools to the agent or rewrite the history. Only the text of that message is kept: its id, toolCalls and toolCallId never reach the history, and the message ID is generated on the server. The adapter is also configured with ToolMergeMode.AGENT_ONLY, so only server-registered tools are ever usedIllustrative output (values are made up, field names come from real runs against a stub model, long fields are elided). Only data: lines are sent; the event type is the type field in the JSON, there is no SSE event: name:
data:{"type":"RUN_STARTED","threadId":"demo","runId":"…","input":{…}}
data:{"type":"TOOL_CALL_START","threadId":"demo","runId":"…","toolCallId":"call_1","toolCallName":"get_weather"}
data:{"type":"TOOL_CALL_ARGS","threadId":"demo","runId":"…","toolCallId":"call_1","delta":"{\"city\":\"Beijing\"}"}
data:{"type":"TOOL_CALL_END","threadId":"demo","runId":"…","toolCallId":"call_1"}
data:{"type":"TOOL_CALL_RESULT","threadId":"demo","runId":"…","toolCallId":"call_1","content":"\"Sunny, 25 C\"","role":"tool","messageId":"…"}
data:{"type":"TEXT_MESSAGE_START","threadId":"demo","runId":"…","messageId":"…","role":"assistant"}
data:{"type":"TEXT_MESSAGE_CONTENT","threadId":"demo","runId":"…","messageId":"…","delta":"It is sunny, 25 C."}
data:{"type":"TEXT_MESSAGE_END","threadId":"demo","runId":"…","messageId":"…"}
data:{"type":"CUSTOM","threadId":"demo","runId":"…","name":"token_usage","value":{"delta":{…},"cumulative":{"inputTokens":83,"outputTokens":27,…},"modelCall":{…}}}
data:{"type":"RUN_FINISHED","threadId":"demo","runId":"…"}
Event type |
Meaning |
|---|---|
RUN_STARTED |
Always the first event, carries threadId and runId |
TEXT_MESSAGE_START / TEXT_MESSAGE_CONTENT / TEXT_MESSAGE_END |
Generated text; concatenate the delta of the CONTENT events in order to get the reply |
TOOL_CALL_START / TOOL_CALL_ARGS / TOOL_CALL_END |
A tool the model decided to call; TOOL_CALL_ARGS carries fragments of the arguments JSON and only appears when the model streams the arguments in pieces |
TOOL_CALL_RESULT |
The result of a tool, matched to the call by toolCallId |
CUSTOM with name = token_usage |
Token usage after each model call, with delta and cumulative |
CUSTOM with name = hint |
A hint the framework adds during reasoning, value has source and text |
RUN_FINISHED |
The normal end |
RUN_ERROR |
Reasoning failed; the HTTP status is still 200 and the stream ends normally instead of dropping the connection. The message is the raw exception message and is not redacted, so do not show it to end users directly |
Every stream starts with RUN_STARTED, even when the agent cannot be created, and ends with either RUN_FINISHED or RUN_ERROR, never both. When the agent fails while running, the AG-UI adapter maps the exception to code (TIMEOUT_ERROR, INTERRUPTED_ERROR, INVALID_INPUT_ERROR or INTERNAL_ERROR); failures outside the adapter, such as the agent not being created, are always INTERNAL_ERROR. There is no done event and the final text is not repeated: the client concatenates TEXT_MESSAGE_CONTENT. The temporary agent is closed when the stream completes, fails or the client disconnects. The adapter's runTimeout setting is not read by AgentScope 2.0.4, so it does not limit the run time.
Breaking change The earlier private events (
textDelta,toolCall,toolResult,hint,done,error) and the{"sessionId", "message"}request body of the streaming endpoint are gone. The streaming endpoint now only acceptsContent-Type: application/json; requests without aContent-Type, or withtext/plainorapplication/*+json, get 415.POST /api/chatnow validatessessionIdwith the same rule asthreadId(non-blank values likeuser:42used to be accepted and now return 400); a blanksessionIdstill starts a new session.
Note The default in-memory state store only suits a single process, and history is lost when the service restarts. For multi-instance deployments, switch to a Redis or JDBC implementation. Concurrent requests for the same session are not serialized, so avoid sending them at the same time.
Configuration is split by profile. The local profile is active when none is specified; switch with the SPRING_PROFILES_ACTIVE=prod environment variable or the --spring.profiles.active=prod command-line argument.
application.yml: Shared by all profiles: model, agent, virtual threads, log formatapplication-local.yml: Local development: Volcengine Ark base-url and model name live in the file, the API key comes from ARK_API_KEY (may be empty), com.yoke logs at debugapplication-test.yml: Automated tests: a fake API key, the model is never really calledapplication-prod.yml: Production: the API key must come from the environment, startup fails without it; graceful shutdownThe model is created by the official AgentScope starter from agentscope.*, and the agent is built per request by a shared builder (the official singleton agent is turned off):
| Property | Description |
|---|---|
agentscope.openai.api-key |
Model API key; local reads ARK_API_KEY, prod reads OPENAI_API_KEY |
agentscope.openai.model-name |
Model name |
agentscope.openai.base-url |
Set this to use another OpenAI-compatible service (DashScop |