Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2024-2026 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.agentscope.spring.boot;

import io.agentscope.core.ReActAgent;
import java.util.function.Consumer;

/**
* Customizer for {@link ReActAgent.Builder}.
*
* <p>Example usage:
*
* <pre>{@code
* @Bean
* public AgentBuilderCustomizer agentBuilderCustomizer() {
* return builder -> builder.middleware(new MyMiddleware());
* }
* }</pre>
*
* @see AgentscopeAutoConfiguration#agentscopeReActAgent
*/
@FunctionalInterface
public interface AgentBuilderCustomizer extends Consumer<ReActAgent.Builder> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Info] This mirrors the Boot idiom (WebServerFactoryCustomizer) except that it both extends Consumer<ReActAgent.Builder> and declares customize. The accept default keeps the two in sync today, but it makes the interface assignable to Consumer, so a plain Consumer<ReActAgent.Builder> lambda can no longer be distinguished from a customizer and the ordering contract silently applies to both. Consider dropping extends Consumer and keeping customize only — cheaper to do now than after 2.0.4 ships.

/**
* Customize the {@link ReActAgent.Builder}.
*
* @param builder the builder to customize
*/
void customize(ReActAgent.Builder builder);

/**
* Accept and invoke the given builder.
*
* @param builder the builder to customize
*/
@Override
default void accept(ReActAgent.Builder builder) {
this.customize(builder);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@
import io.agentscope.core.ReActAgent;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.memory.Memory;
import io.agentscope.core.middleware.MiddlewareBase;
import io.agentscope.core.model.Model;
import io.agentscope.core.permission.PermissionContextState;
import io.agentscope.core.tool.Toolkit;
import io.agentscope.spring.boot.properties.AgentProperties;
import io.agentscope.spring.boot.properties.AgentscopeProperties;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
Expand All @@ -31,6 +37,8 @@
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

/**
* Spring Boot auto-configuration that exposes default Memory, Toolkit and ReActAgent beans for
Expand All @@ -51,12 +59,35 @@
* sys-prompt: "You are a helpful AI assistant."
* max-iters: 10
* }</pre>
*
* <p>In addition to the core beans, this configuration provides the following
* conveniences when {@code agentscope.agent.enabled=true}:
*
* <ul>
* <li><b>Middleware auto-assembly</b> — every {@link MiddlewareBase} bean
* is auto-injected into the agent builder via an
* {@link AgentBuilderCustomizer}, ordered by
* {@link org.springframework.core.annotation.Order @Order}. Opt out with
* {@code agentscope.agent.auto-assemble-middleware=false}.</li>
* <li><b>PermissionContextState auto-injection</b> — if exactly one
* {@link PermissionContextState} bean exists in the context, it is
* auto-applied to the agent builder. No-op when absent; a warning is
* logged when ambiguous (two or more beans).</li>
* </ul>
*
* <p>Both conveniences are implemented as {@link AgentBuilderCustomizer} beans ordered with
* {@link Ordered#HIGHEST_PRECEDENCE}, so a user-defined {@code AgentBuilderCustomizer}
* <b>without an explicit {@code @Order}</b> (and therefore defaulting to
* {@link Ordered#LOWEST_PRECEDENCE}) runs afterwards and can override them. A user customizer
* that sets its own small {@code @Order} can run earlier instead.
*/
@AutoConfiguration
@EnableConfigurationProperties(AgentscopeProperties.class)
@ConditionalOnClass(ReActAgent.class)
public class AgentscopeAutoConfiguration {

private static final Logger logger = LoggerFactory.getLogger(AgentscopeAutoConfiguration.class);

/**
* Default Memory implementation backed by InMemoryMemory.
*
Expand Down Expand Up @@ -105,14 +136,109 @@ public Toolkit agentscopeToolkit() {
@ConditionalOnBean(Model.class)
@ConditionalOnProperty(prefix = "agentscope.agent", name = "enabled", havingValue = "true")
public ReActAgent agentscopeReActAgent(
Model model, Memory memory, Toolkit toolkit, AgentscopeProperties properties) {
Model model,
Memory memory,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[INFO] The injected Memory memory parameter is still never used: the new builder chain (lines 145-151) sets name/sysPrompt/model/toolkit/maxIters but no memory, and ReActAgent.Builder exposes no memory(...) method, so neither the auto-configuration nor an AgentBuilderCustomizer can apply the agentscopeMemory() bean. This also contradicts the method javadoc (line 128: "wires together the configured Model, Memory and Toolkit beans") and the memory-bean javadoc (lines 96-99) that recommends obtaining it via method injection. Action: either drop the parameter (and the now-unconsumed Memory bean contract) or document explicitly where application code is expected to supply memory (e.g. via AgentState). Pre-existing, but this PR rewrote this method body and added the customizer SPI on top of it.

Toolkit toolkit,
AgentscopeProperties properties,
ObjectProvider<AgentBuilderCustomizer> customizers) {
AgentProperties config = properties.getAgent();
return ReActAgent.builder()
.name(config.getName())
.sysPrompt(config.getSysPrompt())
.model(model)
.toolkit(toolkit)
.maxIters(config.getMaxIters())
.build();
ReActAgent.Builder builder =
ReActAgent.builder()
.name(config.getName())
.sysPrompt(config.getSysPrompt())
.model(model)
.toolkit(toolkit)
.maxIters(config.getMaxIters());
customizers.orderedStream().forEach(c -> c.customize(builder));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this customizer is ordered at HIGHEST_PRECEDENCE + 10, auto-assembled middlewares are always appended before any middleware added by a user-defined AgentBuilderCustomizer, which changes the effective hook order for apps that previously controlled ordering by their own builder.middleware(...) call order. Middleware ordering is behaviour-significant (short-circuit / tracing / permission hooks), so please state in the class javadoc and the docs snippet that auto-assembled middlewares run first and that @Order on the middleware bean is the knob to control relative order.

return builder.build();
}

// ------------------------------------------------------------------
// Middleware auto-assembly
// ------------------------------------------------------------------

/**
* Auto-injects all {@link MiddlewareBase} beans into the agent builder,
* ordered by {@link org.springframework.core.annotation.Order @Order}.
*
* <p>Ordered at {@link Ordered#HIGHEST_PRECEDENCE}{@code + 10} — before the permission and
* hook customizers, and before any user-defined {@link AgentBuilderCustomizer}.
*
* <p><b>Sharing contract:</b> a {@code MiddlewareBase} bean is a singleton, and every agent
* built from this auto-configuration receives the same instance. Middleware must therefore be
* stateless / thread-safe — keep per-request state in {@code RuntimeContext}, never in
* instance fields.
*
* <p>Disable with {@code agentscope.agent.auto-assemble-middleware=false} when middleware is
* wired manually, to avoid attaching the same middleware twice. To replace just the assembly
* logic, shadow the {@code middlewareAutoCustomizer} bean by name.
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
@ConditionalOnProperty(prefix = "agentscope.agent", name = "enabled", havingValue = "true")
@ConditionalOnProperty(
prefix = "agentscope.agent",
name = "auto-assemble-middleware",
havingValue = "true",
matchIfMissing = true)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Warning] auto-assemble-middleware / auto-assemble-hooks are read only through @ConditionalOnProperty, so they are invisible to configuration binding: AgentProperties has no matching fields and the starter has no additional-spring-configuration-metadata.json. Users get no IDE completion and no validation of typos (auto-assemble-middlewarea=false silently keeps the default), which is a bad failure mode for an opt-out switch intended as an escape hatch.

Please add boolean autoAssembleMiddleware = true / autoAssembleHooks to AgentProperties (or metadata entries) so the two new keys are part of the documented property surface, and reference them from the docs/v2/*/middleware.md sections added here.

@ConditionalOnMissingBean(name = "middlewareAutoCustomizer")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[INFO] All three built-in customizers back off only by exact bean name (middlewareAutoCustomizer here, permissionContextAutoCustomizer at line 220, hookAutoCustomizer in HookAutoConfiguration.java:70), yet only the middleware case is documented (javadoc at line 174, and no mention in docs/v2/{en,zh}/.../middleware.md). Users therefore cannot discover how to replace the permission or hook assembly, and an unrelated bean that happens to carry one of these names silently disables the built-in customizer. Action: document the three shadowing bean names in the reference docs (or switch the conditions to type-based @ConditionalOnMissingBean where a single seam type is intended).

public AgentBuilderCustomizer middlewareAutoCustomizer(
ObjectProvider<MiddlewareBase> middlewares) {
return builder -> {
List<MiddlewareBase> beans = middlewares.orderedStream().toList();
if (!beans.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug(
"Auto-assembled {} MiddlewareBase bean class(es): {}",
beans.size(),
beans.stream().map(b -> b.getClass().getSimpleName()).toList());
}
beans.forEach(builder::middleware);
}
};
}

// ------------------------------------------------------------------
// PermissionContextState auto-injection
// ------------------------------------------------------------------

/**
* If exactly one {@link PermissionContextState} bean exists in the context,
* auto-applies it to the agent builder. No-op when no
* {@code PermissionContextState} bean is present.
*
* <p>When more than one {@code PermissionContextState} bean is present the context is
* ambiguous, so nothing is injected and a warning is logged — the permission engine decides
* allow/approve/deny, so silently picking one would be a security-relevant choice.
*
* <p>Ordered at {@link Ordered#HIGHEST_PRECEDENCE}{@code + 20} — after middleware assembly
* but still before the hook customizer and any user-defined {@link AgentBuilderCustomizer}.
*
* <p>Backs off when a bean named {@code permissionContextAutoCustomizer} already exists
* ({@code @ConditionalOnMissingBean(name = ...)}), so supplying one disables or replaces this
* auto-injection.
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
@ConditionalOnProperty(prefix = "agentscope.agent", name = "enabled", havingValue = "true")
@ConditionalOnMissingBean(name = "permissionContextAutoCustomizer")
public AgentBuilderCustomizer permissionContextAutoCustomizer(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PermissionContextState is an immutable per-session/per-user rule set (allow/deny/ask rules + working directories), but this injects a single bean instance into the singleton ReActAgent, so every session in the context shares the same permission rules. That is a multi-tenant isolation risk for deployments that publish one shared agent. Suggest either requiring an explicit opt-in (agentscope.agent.auto-assemble-permission-context, default false) or documenting that a PermissionContextState bean must be treated as a global default only — unlike the middleware/hook assembly, there is currently no way to switch this one off.

ObjectProvider<PermissionContextState> permissionContext) {
return builder -> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Info] ObjectProvider<T>.getIfUnique() expresses exactly the "exactly one bean, otherwise do nothing" rule here and avoids materializing the whole stream on every agent build; the size() > 1 warn branch then becomes getIfUnique() == null plus an optional stream().count() for the log message.

Also note the ordering guarantee is subtler than the javadoc suggests: orderedStream() puts un-@Ordered user customizers at LOWEST_PRECEDENCE, so a user customizer that does implement Ordered/@Order(HIGHEST_PRECEDENCE) runs before these built-ins and its permissionContext(...) will be overwritten. Worth one sentence in the class javadoc.

List<PermissionContextState> contexts = permissionContext.orderedStream().toList();
if (contexts.size() == 1) {
builder.permissionContext(contexts.get(0));
} else if (contexts.size() > 1) {
logger.warn(
"Found {} PermissionContextState beans; refusing to auto-inject an"
+ " ambiguous permission context. Declare exactly one bean, or"
+ " apply it via a user-defined AgentBuilderCustomizer.",
contexts.size());
} else {
logger.debug(
"No PermissionContextState bean present;"
+ " skipping permission context auto-injection");
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2024-2026 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.agentscope.spring.boot;

import io.agentscope.core.hook.Hook;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

/**
* Auto-configuration for {@link Hook} auto-assembly.
*
* <p>Isolated from {@link AgentscopeAutoConfiguration} because {@link Hook} and
* {@link io.agentscope.core.hook.HookEvent} are
* {@link Deprecated @Deprecated} for removal since 2.0.0. This class will be
* removed together with the Hook API.
*
* <p><b>Opt-in:</b> hook auto-attach is disabled by default because {@link Hook} is deprecated
* for removal. Enable it with {@code agentscope.agent.auto-assemble-hooks=true}; when enabled,
* every {@link Hook} bean is attached to the agent builder, so applications that also attach
* hooks manually must remove the manual attachment to avoid registering the same hook twice.
*
* <p>The injected hooks are ordered by
* {@link org.springframework.core.annotation.Order @Order}. The customizer
* itself is ordered at {@link Ordered#HIGHEST_PRECEDENCE}{@code + 30} so that it
* runs after the built-in middleware and permission customizers but still before
* any user-defined {@link AgentBuilderCustomizer}.
*/
@AutoConfiguration
@ConditionalOnClass(Hook.class)
@ConditionalOnProperty(prefix = "agentscope.agent", name = "enabled", havingValue = "true")
@SuppressWarnings("deprecation")
public class HookAutoConfiguration {

private static final Logger logger = LoggerFactory.getLogger(HookAutoConfiguration.class);

/**
* Auto-injects all {@link Hook} beans into the agent builder, ordered by

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Info] Isolating the deprecated Hook plumbing in its own auto-configuration is the right call and the javadoc explaining it is appreciated. Two smaller points: this file ships without a license header check concern only in that it is the first auto-configuration in the starter activated purely by AutoConfiguration.imports, so @ConditionalOnClass(Hook.class) plus @ConditionalOnProperty(...) is what keeps it off for users without hooks — worth stating that in the PR description as a behaviour change ("existing Hook beans are now auto-attached when agentscope.agent.enabled=true"), since previously nothing was attached implicitly. If a release-notes/CHANGELOG entry is expected for starter behaviour changes, this and the @ToolBean registration both need one.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed review — every finding is valid. Here is how each was addressed in
the updated commit dcd2ab72.

[Critical] ToolBean.java:51 — @ToolBean is not a stereotype

Confirmed. getBeansWithAnnotation only sees beans that already exist, so a bare @ToolBean
class never becomes one — the documented usage (and the test, which passed only because of the
extra @Bean method) was wrong.

Rather than paper over it by adding @Component, I removed the whole tool auto-registration
path
(@ToolBean + ToolAutoRegistrationBeanPostProcessor), its tests, and the tool docs.

Rationale: auto tool scan is already tracked by #821. #1192 attempted it and was closed;
its description shows the blocker is that Spring AOP proxies (CGLIB/JDK) drop
@Tool/@ToolParam annotations, so the fix requires targetClass-aware scanning in core
Toolkit/ToolMethodInvoker — not something a starter-level PR can do correctly. #626
(custom annotation on an @Tool method prevents registration) points at the same
annotation-handling area. Deferring to #821 keeps this PR free of that dependency.

This also removes the [Warning] against ToolAutoRegistrationBeanPostProcessor.java:56 — the
bootstrap-order hazard and the missing de-dup no longer exist.

[Critical] AgentscopeAutoConfiguration.java:196 — getIfAvailable() throws on multiple beans

Fixed. Now uses getIfUnique(), with a debug log when it resolves to nothing. Added
shouldNotFailWhenMultiplePermissionContextStateBeans, which registers two
PermissionContextState beans and asserts the context starts and the mode stays at the
default (no auto-injection, no NoUniqueBeanDefinitionException).

[Warning] AgentscopeAutoConfiguration.java:178 — no @Order on auto customizers

Fixed. middlewareAutoCustomizer, permissionContextAutoCustomizer, and hookAutoCustomizer
are all annotated @Order(Ordered.HIGHEST_PRECEDENCE) and documented as such, so user-defined
AgentBuilderCustomizer beans always run afterwards and win.

Added two precedence tests:

  • userCustomizerShouldOverrideAutoInjectedPermissionContext — auto injects ACCEPT_EDITS,
    a user customizer overwrites with BYPASS, final mode is BYPASS.
  • userCustomizerShouldRunAfterAutoMiddlewareCustomizer — asserts the user middleware appears
    after the auto-injected one in agent.getMiddlewares().

[Warning] AgentBuilderCustomizer.java:41 — SPI shape differs from OpenAIChatModelBuilderCustomizer

Fixed. The interface now declares void customize(ReActAgent.Builder) and a
default void accept(...) that delegates to it, exactly matching the model customizer family,
and agentscopeReActAgent invokes customize(builder). The javadoc no longer claims a
"mirror" that was not there — it is now the same shape.

[Info] HookAutoConfiguration.java:45 — implicit hook attach is a behaviour change

Agreed and now called out explicitly in the class javadoc and in the PR description: previously
nothing was attached implicitly; with agentscope.agent.enabled=true every Hook bean is
auto-attached. The javadoc also notes that applications attaching hooks manually should drop
the manual attachment to avoid double registration. Flagged for release notes.

Validation

Check Result
mvn spotless:apply Pass
mvn test -pl .../agentscope-spring-boot-starter Pass — 12 run, 0 failures, 0 errors
Public API change Additive only; no customizer beans → behaviour unchanged

* {@link org.springframework.core.annotation.Order @Order}.
*
* <p>Disabled by default; enable with {@code agentscope.agent.auto-assemble-hooks=true}.
*
* <p>Backs off when a bean named {@code hookAutoCustomizer} already exists
* ({@code @ConditionalOnMissingBean(name = ...)}), so supplying one disables or replaces this
* auto-attach.
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 30)
@ConditionalOnProperty(
prefix = "agentscope.agent",
name = "auto-assemble-hooks",
havingValue = "true")
@ConditionalOnMissingBean(name = "hookAutoCustomizer")
public AgentBuilderCustomizer hookAutoCustomizer(ObjectProvider<Hook> hooks) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agentscope.agent.enabled must be set explicitly for this class to apply (havingValue="true" without matchIfMissing), while AgentProperties.enabled defaults to true. The mismatch is pre-existing for the agent bean, but the new auto-assemble-hooks/auto-assemble-middleware flags inherit the same trap: a user who sets only auto-assemble-hooks=false and never sets enabled gets the default behaviour silently. Consider matchIfMissing = true on the enabled condition (to match the field default) or aligning the docs on "explicitly required".

return builder -> {
List<Hook> beans = hooks.orderedStream().toList();
if (!beans.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug(
"Auto-assembled {} Hook bean class(es): {}",
beans.size(),
beans.stream().map(b -> b.getClass().getSimpleName()).toList());
}
beans.forEach(builder::hook);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ public class AgentProperties {
*/
private int maxIters = 10;

/**
* Whether every {@code MiddlewareBase} bean is auto-injected into the agent builder.
*
* <p>Default {@code true}. Auto-assembly does not de-duplicate: a middleware an application
* already attaches itself via {@code builder.middleware(...)} would be registered twice. Set
* this to {@code false} to opt out and wire middleware yourself.
*/
private boolean autoAssembleMiddleware = true;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the hand-written metadata entry is gone, this field javadoc is now the only source of the hint a user sees when the default bites them. Two facts are worth having in that one paragraph, because they are what a surprised applicant asks: that auto-assembly does not de-duplicate, so middleware already attached by the application or by another customizer is registered a second time; and that the opt-out is this single flag rather than removing the beans. Same note applies to autoAssembleHooks below.


/**
* Whether every {@code Hook} bean is auto-attached to the agent builder.
*
* <p>Default {@code false}. {@code Hook} is deprecated for removal, so auto-attach is opt-in;
* when enabled it does not de-duplicate against hooks the application attaches itself. Set
* this to {@code true} to opt in.
*/
private boolean autoAssembleHooks = false;

public boolean isEnabled() {
return enabled;
}
Expand Down Expand Up @@ -82,4 +100,20 @@ public int getMaxIters() {
public void setMaxIters(int maxIters) {
this.maxIters = maxIters;
}

public boolean isAutoAssembleMiddleware() {
return autoAssembleMiddleware;
}

public void setAutoAssembleMiddleware(boolean autoAssembleMiddleware) {
this.autoAssembleMiddleware = autoAssembleMiddleware;
}

public boolean isAutoAssembleHooks() {
return autoAssembleHooks;
}

public void setAutoAssembleHooks(boolean autoAssembleHooks) {
this.autoAssembleHooks = autoAssembleHooks;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.agentscope.spring.boot.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;

/**
* Root configuration properties for AgentScope Spring Boot starter.
Expand All @@ -26,13 +27,18 @@
* <li>{@link AgentProperties} under {@code agentscope.agent}</li>
* <li>{@link ModelProperties} under {@code agentscope.model}</li>
* </ul>
*
* <p>{@link NestedConfigurationProperty} makes the configuration metadata processor expand
* these nested groups into {@code spring-configuration-metadata.json}. Measured on this starter,
* omitting the annotation leaves the generated {@code properties} array empty, so it is required
* here for {@code agentscope.agent.*} / {@code agentscope.model.*} entries to appear.
*/
@ConfigurationProperties(prefix = "agentscope")
public class AgentscopeProperties {

private final AgentProperties agent = new AgentProperties();
@NestedConfigurationProperty private final AgentProperties agent = new AgentProperties();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This head deletes META-INF/additional-spring-configuration-metadata.json (it existed at the previously reviewed deddb436, absent here) and now relies entirely on the annotation processor expanding these nested groups because of @NestedConfigurationProperty. That is the cleaner shape, but it makes IDE hints for agentscope.agent.auto-assemble-middleware / auto-assemble-hooks — including their defaultValue and the "set false to avoid attaching the same middleware twice" guidance — an unverified side effect of the build: no check in validate, Check Module Sync or the test suite asserts on the generated spring-configuration-metadata.json. Please attach the generated fragment for those two keys in the PR description, or keep the two entries in the additional file as a belt-and-braces fallback. Also soften the new javadoc claim ("required ... without it the metadata contains no agentscope.agent.* entries"): Boot expands nested types from the same compilation unit without the annotation, so if that was the observed behaviour it is worth a sentence about how it was measured.


private final ModelProperties model = new ModelProperties();
@NestedConfigurationProperty private final ModelProperties model = new ModelProperties();

public AgentProperties getAgent() {
return agent;
Expand Down
Loading
Loading