Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1482536
Introduce agentscope harness module
chickenlj Apr 21, 2026
0f1d23d
Merge branch 'main' into harness
chickenlj Apr 21, 2026
0e1c809
docs(harness): Harness documentations (#1286)
fang-tech Apr 26, 2026
be9feea
remove final qualifier, make stream() method in AgentBase extensible.
chickenlj Apr 27, 2026
1f3004a
update harness implementation
chickenlj Apr 27, 2026
338ddda
add runtime context support to ReActAgent
chickenlj Apr 27, 2026
cab799b
add harness sandbox example
chickenlj Apr 27, 2026
f1d2ff9
code format
chickenlj Apr 27, 2026
4d795e3
delete .agentscope
chickenlj Apr 27, 2026
2d7af6a
Merge branch 'harness' of https://github.com/agentscope-ai/agentscope…
chickenlj Apr 27, 2026
1b2b497
add docs & code naming refactor
chickenlj Apr 28, 2026
3ee52ee
update docs
chickenlj Apr 28, 2026
1c82d7d
skip license check
chickenlj Apr 28, 2026
060238c
update harness implementation
chickenlj May 6, 2026
9f2be71
RuntimeContext refactor
chickenlj May 6, 2026
b582c06
WorkspaceSession check
chickenlj May 6, 2026
9c0e197
refactor harness examples module structure and using interactive chat…
fang-tech May 6, 2026
9906a06
example: Add python docker image for python env sandbox example
fang-tech May 6, 2026
136ccd5
Refactor system message handling in PreCallEvent and related hooks
chickenlj May 7, 2026
9f5f09d
Add ModelRegistry documentation and usage examples
chickenlj May 7, 2026
13cfe42
delete examples
chickenlj May 7, 2026
6f7c69f
Merge branch 'harness' of https://github.com/agentscope-ai/agentscope…
chickenlj May 7, 2026
216a407
code format
chickenlj May 7, 2026
7876400
update harness example
chickenlj May 7, 2026
d508ae1
update harness example
chickenlj May 7, 2026
e58fa55
code format
chickenlj May 7, 2026
f871bd8
Merge branch 'main' into harness
chickenlj May 7, 2026
a8c9561
feat: enhance SandboxDistributedOptions and related tests for improve…
chickenlj May 7, 2026
cafb9d7
code format
chickenlj May 7, 2026
8cdd055
fix javadoc
chickenlj May 7, 2026
7822f3a
remove roadmap.md
fang-tech May 8, 2026
668a59a
add network option and additional run args option for docker sandbox
fang-tech May 8, 2026
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ logs/
**/boba-tea-shop/supervisor-agent/**/static/
**/boba-tea-shop/**/node_modules/
**/boba-tea-shop/**/dist/

##agentscope
.agentscope/

## harness db
**/*.db
5 changes: 5 additions & 0 deletions .licenserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ header:
limitations under the License.

paths-ignore:
# Config / example files without license headers
- 'agentscope-examples/harness-example/.env.example'
- 'agentscope-examples/harness-example/src/main/resources/agentscope.json.example'
- 'agentscope-examples/harness-example/src/main/resources/log4j2.xml'
- 'agentscope-harness/src/main/resources/agentscope.json.example'
- '**/*.versionsBackup'
- '**/.idea/'
- '**/*.iml'
Expand Down
185 changes: 160 additions & 25 deletions agentscope-core/src/main/java/io/agentscope/core/ReActAgent.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
*/
package io.agentscope.core;

import com.fasterxml.jackson.databind.JsonNode;
import io.agentscope.core.agent.Event;
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.agent.StreamOptions;
import io.agentscope.core.agent.StructuredOutputCapableAgent;
import io.agentscope.core.agent.accumulator.ReasoningContext;
import io.agentscope.core.hook.ActingChunkEvent;
Expand Down Expand Up @@ -151,6 +155,17 @@ public class ReActAgent extends StructuredOutputCapableAgent {
private final PlanNotebook planNotebook;
private final ToolExecutionContext toolExecutionContext;
private final StatePersistence statePersistence;
private RuntimeContext pendingRuntimeContext;

/**
* Per-call system message, propagated across PreCallEvent → PreReasoningEvent /
* PreSummaryEvent. It is safe to use an {@link java.util.concurrent.atomic.AtomicReference}
* here because {@code AgentBase.acquireExecution()} guarantees that only one {@code call()}
* runs concurrently per agent instance, so this reference is effectively owned by a single
* logical execution at any time.
*/
private final java.util.concurrent.atomic.AtomicReference<Msg> currentSystemMsg =
new java.util.concurrent.atomic.AtomicReference<>();

// ==================== Constructor ====================

Expand Down Expand Up @@ -178,6 +193,91 @@ private ReActAgent(Builder builder, Toolkit agentToolkit) {
: StatePersistence.all();
}

// ==================== RuntimeContext ====================

@Override
protected void beforeAgentExecution(List<Msg> msgs) {
RuntimeContext ctx = this.pendingRuntimeContext;
this.pendingRuntimeContext = null;
if (ctx == null) {
ctx = RuntimeContext.empty();
}
bindRuntimeContextToHooks(ctx);
// Reset per-call system message; will be initialised by consumeSystemMsgAfterPreCall
currentSystemMsg.set(null);
}

@Override
protected Msg seedSystemMsg() {
if (sysPrompt != null && !sysPrompt.trim().isEmpty()) {
return Msg.builder()
.name("system")
.role(MsgRole.SYSTEM)
.content(TextBlock.builder().text(sysPrompt).build())
.build();
}
return null;
}

@Override
protected void consumeSystemMsgAfterPreCall(Msg systemMsg) {
currentSystemMsg.set(systemMsg);
}

@Override
protected void afterAgentExecution() {
unbindRuntimeContextFromHooks();
}

private ToolExecutionContext buildMergedToolContext() {
RuntimeContext run = getRuntimeContext();
if (run == null) {
return toolExecutionContext != null
? toolExecutionContext
: ToolExecutionContext.empty();
}
return ToolExecutionContext.merge(run.asToolExecutionContext(), toolExecutionContext);
}

/**
* Calls the agent with a per-call {@link RuntimeContext} (metadata for hooks and tools, not
* persisted).
*/
public Mono<Msg> call(List<Msg> msgs, RuntimeContext context) {
this.pendingRuntimeContext = context;
return call(msgs);
}

public Mono<Msg> call(List<Msg> msgs, Class<?> structuredOutputClass, RuntimeContext context) {
this.pendingRuntimeContext = context;
return call(msgs, structuredOutputClass);
}

public Mono<Msg> call(List<Msg> msgs, JsonNode outputSchema, RuntimeContext context) {
this.pendingRuntimeContext = context;
return call(msgs, outputSchema);
}

public Flux<Event> stream(List<Msg> msgs, StreamOptions options, RuntimeContext context) {
this.pendingRuntimeContext = context;
return stream(msgs, options);
}

public Flux<Event> stream(
List<Msg> msgs,
StreamOptions options,
Class<?> structuredModel,
RuntimeContext context) {
this.pendingRuntimeContext = context;
return stream(msgs, options, structuredModel);
}

public Flux<Event> stream(
List<Msg> msgs, StreamOptions options, JsonNode schema, RuntimeContext context) {
this.pendingRuntimeContext = context;
return stream(msgs, options, schema);
}

// ==================== New StateModule API ====================

/**
Expand Down Expand Up @@ -466,17 +566,17 @@ private Mono<Msg> reasoning(int iter, boolean ignoreMaxIters) {
ReasoningContext context = new ReasoningContext(getName());

return checkInterruptedAsync()
.then(notifyPreReasoningEvent(prepareMessages()))
.then(notifyPreReasoningEvent(memory.getMessages()))
.flatMapMany(
event -> {
GenerateOptions options =
event.getEffectiveGenerateOptions() != null
? event.getEffectiveGenerateOptions()
: buildGenerateOptions();
return model.stream(
event.getInputMessages(),
toolkit.getToolSchemas(),
options)
List<Msg> modelInput =
prependSystemMsg(
event.getInputMessages(), event.getSystemMessage());
return model.stream(modelInput, toolkit.getToolSchemas(), options)
.concatMap(chunk -> checkInterruptedAsync().thenReturn(chunk));
})
.doOnNext(
Expand Down Expand Up @@ -665,7 +765,7 @@ private Msg buildSuspendedMsg(List<Map.Entry<ToolUseBlock, ToolResultBlock>> pen
*/
private Mono<List<Map.Entry<ToolUseBlock, ToolResultBlock>>> executeToolCalls(
List<ToolUseBlock> toolCalls) {
return toolkit.callTools(toolCalls, toolExecutionConfig, this, toolExecutionContext)
return toolkit.callTools(toolCalls, toolExecutionConfig, this, buildMergedToolContext())
.map(
results ->
IntStream.range(0, toolCalls.size())
Expand Down Expand Up @@ -734,7 +834,10 @@ protected Mono<Msg> summarizing() {
return notifyPreSummaryHook(messageList, generateOptions)
.flatMap(
preSummaryEvent -> {
List<Msg> effectiveMessages = preSummaryEvent.getInputMessages();
List<Msg> effectiveMessages =
prependSystemMsg(
preSummaryEvent.getInputMessages(),
preSummaryEvent.getSystemMessage());
GenerateOptions effectiveOptions =
preSummaryEvent.getEffectiveGenerateOptions();

Expand Down Expand Up @@ -775,7 +878,7 @@ private Mono<Msg> streamAndAccumulateSummary(
}

private List<Msg> prepareSummaryMessages() {
List<Msg> messageList = prepareMessages();
List<Msg> messageList = new ArrayList<>(memory.getMessages());
messageList.add(
Msg.builder()
.name("user")
Expand Down Expand Up @@ -816,20 +919,21 @@ private Mono<Msg> handleSummaryError(Throwable error) {
// ==================== Helper Methods ====================

/**
* Prepare messages for model input.
* Prepends the system message to {@code msgs} if non-null.
*
* <p>Called immediately before each {@code model.stream()} invocation to build the final
* LLM input without contaminating the in-memory message list.
*/
private List<Msg> prepareMessages() {
List<Msg> messages = new ArrayList<>();
if (sysPrompt != null && !sysPrompt.trim().isEmpty()) {
messages.add(
Msg.builder()
.name("system")
.role(MsgRole.SYSTEM)
.content(TextBlock.builder().text(sysPrompt).build())
.build());
private static List<Msg> prependSystemMsg(List<Msg> msgs, Msg systemMsg) {
if (systemMsg == null) {
return msgs != null ? msgs : List.of();
}
messages.addAll(memory.getMessages());
return messages;
List<Msg> result = new ArrayList<>();
result.add(systemMsg);
if (msgs != null) {
result.addAll(msgs);
}
return result;
}

/**
Expand Down Expand Up @@ -911,7 +1015,9 @@ private <T extends HookEvent> Mono<T> notifyHooks(T event) {
}

private Mono<PreReasoningEvent> notifyPreReasoningEvent(List<Msg> msgs) {
return notifyHooks(new PreReasoningEvent(this, model.getModelName(), null, msgs));
PreReasoningEvent event = new PreReasoningEvent(this, model.getModelName(), null, msgs);
event.setSystemMessage(currentSystemMsg.get());
return notifyHooks(event);
}

private Mono<PostReasoningEvent> notifyPostReasoning(Msg msg) {
Expand Down Expand Up @@ -981,9 +1087,11 @@ private Mono<Void> notifyReasoningChunk(Msg chunkMsg, ReasoningContext context)

private Mono<PreSummaryEvent> notifyPreSummaryHook(
List<Msg> msgs, GenerateOptions generateOptions) {
return notifyHooks(
PreSummaryEvent event =
new PreSummaryEvent(
this, model.getModelName(), generateOptions, msgs, maxIters, maxIters));
this, model.getModelName(), generateOptions, msgs, maxIters, maxIters);
event.setSystemMessage(currentSystemMsg.get());
return notifyHooks(event);
}

private Mono<PostSummaryEvent> notifyPostSummaryHook(Msg msg, GenerateOptions generateOptions) {
Expand Down Expand Up @@ -1219,6 +1327,7 @@ public Builder maxIters(int maxIters) {
* @param hook The hook to add, must not be null
* @return This builder instance for method chaining
* @see Hook
* @see Hook#tools()
*/
public Builder hook(Hook hook) {
this.hooks.add(hook);
Expand All @@ -1234,6 +1343,7 @@ public Builder hook(Hook hook) {
* @param hooks The list of hooks to add, must not be null
* @return This builder instance for method chaining
* @see Hook
* @see Hook#tools()
*/
public Builder hooks(List<Hook> hooks) {
this.hooks.addAll(hooks);
Expand Down Expand Up @@ -1374,7 +1484,8 @@ public Builder planNotebook(PlanNotebook planNotebook) {
* <p>The skill box is used to manage the skills for this agent. It will be used to register the skills to the toolkit.
* <ul>
* <li>Skill loader tools will be automatically registered to the toolkit</li>
* <li>A skill hook will be added to inject skill prompts and manage skill activation</li>
* <li>A skill hook will be added to inject skill prompts on {@link io.agentscope.core.hook.PreCallEvent}
* and manage skill activation</li>
* </ul>
* @param skillBox The skill box to use for this agent
* @return This builder instance for method chaining
Expand Down Expand Up @@ -1562,6 +1673,8 @@ public ReActAgent build() {
// Deep copy toolkit to avoid state interference between agents
Toolkit agentToolkit = this.toolkit.copy();

registerToolsFromHooks(agentToolkit);

if (enableMetaTool) {
agentToolkit.registerMetaTool();
}
Expand Down Expand Up @@ -1594,6 +1707,26 @@ public ReActAgent build() {
return new ReActAgent(this, agentToolkit);
}

/**
* Registers tool objects declared by hooks ({@link Hook#tools()}) on the agent toolkit.
*
* <p>Runs after {@link Toolkit#copy()} so hook-supplied tools are scoped to this agent
* instance without modifying the builder's original toolkit.
*/
private void registerToolsFromHooks(Toolkit agentToolkit) {
for (Hook hook : hooks) {
List<Object> toolObjects = hook.tools();
if (toolObjects == null || toolObjects.isEmpty()) {
continue;
}
for (Object toolObject : toolObjects) {
if (toolObject != null) {
agentToolkit.registerTool(toolObject);
}
}
}
}

/**
* Configures long-term memory based on the selected mode.
*
Expand Down Expand Up @@ -1748,7 +1881,9 @@ public <T extends HookEvent> Mono<T> onEvent(T event) {
* <p>This method automatically:
* <ul>
* <li>Registers skill load tool to the toolkit
* <li>Adds the skill hook to inject skill prompts and manage skill activation
* <li>Adds the skill hook to inject skill prompts on {@link io.agentscope.core.hook.PreCallEvent}
* (priority {@link io.agentscope.core.skill.SkillHook#SKILL_HOOK_PRIORITY}) and manage skill
* activation
* <li>Uploads skill files to the upload directory if auto upload is enabled
* </ul>
*/
Expand Down
Loading
Loading