01 Background
1.1 Starting Point
1.1.1 Business Thinking
Note: the author is from the overseas logistics business-finance team, so the payment process is a core capability for us.A recent requirements planning discussion triggered deep reflection on our existing product. Our product colleague raised several key questions about the “Payment Approval Agent” we had previously launched, pointing directly at the limitations of the current system: First, does the Agent possess real “memory” and “wisdom”? There is a large amount of repetitive rejection reasons in the current approval scenarios. For example, after rejecting payment A because the supplier has unresolved negative bills, when payment A+ is submitted a few days later, can the Agent remember the previous rejection reason and automatically check whether that issue has been resolved? Furthermore, can the Agent analyze approval trajectories, discover efficient patterns like user A’s “one-shot clear feedback”, and recommend such best practices to users like user B who need multiple rounds to get an approval through, thereby improving overall efficiency? Second, can interactions be more personalized? Can the Agent automatically memorize a person’s operating habits and, during conversations, provide customized tips based on what the user usually cares about? Third, can operations staff achieve self-service rapid customization? We hope that in the future, with the participation of non-technical operations colleagues, anyone can quickly customize their own dedicated Agent. For example, in the “billed cost tracing analysis” scenario, helping business personnel understand “why this fee is calculated this way”. Ideally, it should have the capabilities of rapid configuration, rapid testing, and rapid go-live. Fourth, how do we guarantee finance-grade operational safety? Financial safety is the top priority — any write operation requires rigorous approval and confirmation. The current practice of relying on Prompt hints to make the model perform secondary confirmation cannot achieve 100% interception. We need a more precise mechanism that, within self-planning and self-executing workflows, strictly controls which write operations must go through user confirmation. Finally, is precise canary release and experimentation supported? The system needs to support canary releases targeting specific user groups, and even A/B tests, to validate the effectiveness of different strategies. These questions made us suddenly realize: what we had built before was just a simple Chatbox, not a truly intelligent Agent. This insight drove the team into deep reflection. Over the following month, we studied cases from other teams, held multiple rounds of analysis and discussion, and finally distilled the following technical thinking directions.
1.1.2 Technical Thinking
1.1.2.1 Thoughts
Let’s first ask: what does your high-code actually look like? We observed several Agent applications (Java) built on frameworks such as Google ADK and AgentScope. They exhibited a strikingly similar structure:- Hand-written Agent classes: each application has 10+ or even dozens of hand-written Agents; each Agent corresponds to a Java class with hardcoded prompts, specified models, and registered tools in the constructor.
- Self-built infrastructure: session persistence, context compression, SSE protocol adaptation, HITL framework — every team builds its own.
- Changes require releases: changing a prompt, adding a SKILL, adding an MCP tool all require code changes, compilation, and deployment — a simple prompt tuning becomes a full development cycle.
1.1.2.2 Typical Bad Smells in Agent Engineering
In the actual implementation of Agent systems, due to the lack of unified engineering standards and platform support, the codebase is often filled with large amounts of duplicated, rigid, and hard-to-maintain implementation patterns. The following summarizes seven typical “bad smells” that not only increase development costs but also introduce severe stability and security risks. Bad smell 1: boilerplate proliferation — “cloning” instead of “creating” Phenomenon: each Agent is implemented as an independent Java class, and every class contains an identical double-checked locking (DCL) singleton pattern,@PostConstruct initialization logic, and hardcoded dependency injection.
private static volatile instance appears 30+ times, and the getInstance() logic is copied 20+ times. Adding a new Agent essentially becomes “copy & paste → modify class name/Prompt/tool list → fix @DependsOn”. This “clone-style development” results in extremely high code redundancy, and any underlying framework upgrade requires modifying all Agent classes, causing maintenance costs to grow linearly.
Bad smell 2: core configuration hardcoded — Prompt iteration blocked
Phenomenon: the three core elements of an Agent — Prompt (instructions), model parameters (Temperature, etc.), and API Key — are all hardcoded in the code as Java constants or string literals.
- Development bottleneck: prompt tuning must go through the full “code change → Code Review → compile → deploy” cycle, measured in weeks, while business prompt iteration needs are measured in days.
- Security risk: sensitive information such as API Keys is directly exposed in the code repository.
- Lack of flexibility: Temperature parameters for different Agents are scattered everywhere, impossible to centrally control or dynamically adjust.
- Operations difficulty: if an MCP service fails and needs to be temporarily taken offline, or a new tool needs canary release, code must be modified and a new version released — runtime hot-swapping is impossible.
- High coupling: hundreds of scattered capability bindings make it hard to build a global view of dependencies, hindering reuse and composition of Agent capabilities.
- High-risk configuration: hardcoded environment addresses (e.g.,
pre-) that are not cleaned up during release will cause production to call pre-release services, resulting in P1-level incidents. - Poor performance: frequent short connections in the ReAct loop cause extra 100-500ms latency, and there is no circuit breaking — under high concurrency it can easily drag down the MCP Server.
- Missing audit trail: it is impossible to trace which user performed a sensitive operation (e.g., creating rules, approvals) through the Agent.
- Permission out of control: lack of user-level data isolation and permission control violates the zero-trust security principle. In finance and other sensitive scenarios, this is an unacceptable red line.
- Unreliable: the LLM may not correctly parse the stop signal, possibly causing infinite loops or mis-execution.
- Resource waste: blocking waits severely consume server thread resources.
- Fragmented experience: confirmation interactions are inconsistent across tools, making frontend adaptation difficult.
- Incomplete coverage: external MCP tools cannot embed local HITL logic, leaving high-risk operations without necessary human oversight.
- Difficult rollback: Prompt, model parameters, and tool sets are often changed together. When hallucinations occur or effectiveness drops, there is no way to roll back to “last week’s stable state” with one click, because related configurations are scattered across different Commits or records — the recovery process is like “archaeology”.
- No trace: there is no audit chain of “who changed what when”, nor can A/B tests or canary releases be performed, making optimization effects unquantifiable and risks uncontrollable.
1.1.3 Conclusion Summary
Based on the above business and technical thinking, we conclude:- Long-term memory needs to be structured, persisted, and customizable.
- Agent self-evolution needs to be defined at the business dimension, and to achieve an automatic closed loop requires specific and flexible high customization.
- To truly enable operations staff to customize Agents at any time requires zero-code, configuration-based access: adding new Agents/SKILLs/MCP requires no code — only page configuration for instant go-live, lowering the usage barrier.
- HITL capability, to achieve engineering-grade precise matching, requires framework-level transformation — building engineering-grade HITL.
- Canary strategy customization is naturally also engineering-grade development.
- For example, business customization of long-term memory — what platforms usually provide is a RAG mode, which is hard to make precisely controllable.
- Operations usability is even harder — after all, Agent configuration platforms are full of technical details, and precise binding with business platforms requires significant technical transformation.
- Engineering-grade HITL customization cannot possibly rely on technology platforms either.
1.2 Summary of Platform Building Key Points
1. A fully configuration-driven Agent execution engine- Zero-code, configuration-based access: adding new Agents/SKILLs/MCP requires no code — only page configuration for instant go-live, lowering the usage barrier.
- Lightweight isolated Runtime: adopts a “build on use” mechanism, dynamically constructing lightweight Agent instance shells at runtime. This mechanism reuses the underlying Skill, SKILL, and MCP instances, ensuring ① efficient resource sharing and ② excellent performance, while achieving complete isolation of the Agent session execution environment, guaranteeing stability and security.
- Universal Skill marketplace extension capability: can quickly support other Skill marketplaces
- Production environment: integrates with the Aone Skill marketplace, meeting high-availability, high-standard production-grade needs.
- Development and testing: builds OSS Skills, providing a flexible debugging and validation environment to accelerate iteration cycles.
- Inner loop: persist and structure conversation and user operation trajectories, automatically performing preference and case consolidation on a schedule.
- Supports configurable enabling and customization of collection strategies
- Feature: self-closing-loop
- Outer loop: user feedback closed-loop optimization, automatically collecting metrics such as session ratings, tool call success rate, and task completion rate, combined with human-annotated data to drive Prompt and Skill strategy iteration, making the Agent increasingly accurate.
- Not an automatic closed loop; human intervention required
- A/B experiments and canary releases: support multi-version Agent parallel execution and traffic splitting, validating improvement effects with real business data, ensuring each upgrade brings quantifiable experience improvement.
- Not an automatic closed loop; human intervention required
- Business semantic native mapping: Agent configuration items directly correspond to business-finance domain business objects (e.g., invoice types, settlement entities, expense categories) rather than abstract technical parameters; business personnel can precisely define Agent behavior boundaries without understanding the underlying model structure.
- Integrated admin console governance: Agent creation, publishing, permission assignment, and version management are all integrated into the business-finance admin backend, seamlessly connecting with existing organizational structures, role systems, and approval flows, ensuring Agent governance aligns with enterprise management systems.
- Zero-code page customization: supports freely arranging conversation interfaces, form fields, result display cards, and operation buttons through a visual editor; different business lines can independently craft dedicated interaction experiences, quickly responding to personalized needs without frontend development.
02 Technology Selection
In the selection of the Agent runtime framework, we systematically evaluated the three mainstream technology systems — LangChain, Google ADK, and AgentScope. Finally choosing AgentScope as the core foundation was a comprehensive decision based on three dimensions: enterprise-grade production requirements, Alibaba internal ecosystem fit, and the specificity of business-finance needs.2.1 Core Capability Comparison of the Three Frameworks
2.2 Final Choice
03 AgentScope’s Three-Layer Architecture Capability Analysis
I believe AgentScope has three layers of capability: the model layer, the ReAct reasoning loop layer, and the external abstraction layer:3.1 Model Layer
As the bottom-most layer of the architecture, its core is interacting with the LLM, centered on the following 5 points: 1. Unified abstraction and protocol decoupling- Rule: all model implementations must inherit
ChatModelBase, using the Formatter mechanism to convert platform-agnostic Msg into vendor-specific request/response formats. - Source analysis:
OpenAIChatModelinternally holdsFormatter<OpenAIMessage, OpenAIResponse, OpenAIRequest>; adding a new model only requires implementing the corresponding Formatter without modifying core invocation logic, naturally supporting OpenAI-compatible protocols and various domestic models.
- Rule: generation parameters are standardized via
GenerateOptions, supporting “runtime parameters > build-time default parameters” priority merging, and allowing vendor-specific extension fields to be passed through. - Source analysis:
GenerateOptions.mergeOptions(primary, fallback)implements configuration merging; the three extension Maps of additionalHeaders/BodyParams/QueryParams ensure the framework does not lag behind API evolution.
- Rule: governance capabilities such as timeout, retry, and circuit breaking sink to the model layer, automatically injected as part of the data flow rather than as external wrappers.
- Source analysis:
ModelUtils.applyTimeoutAndRetry()directly injects.timeout()and.retryWhen(Retry.backoff(...))on the Flux chain, automatically effective based on ExecutionConfig, with zero intrusion to business code.
- Rule: the entire model invocation chain is fully based on Project Reactor; streaming/non-streaming return Flux from the same interface, supporting backpressure and non-blocking I/O.
- Source analysis:
doStream0()dynamically switches between SSE streaming responses andFlux.defer().subscribeOn(boundedElastic())synchronous calls based on the stream parameter; governance operators are seamlessly embedded in the stream, and upstream perceives a continuous data flow.
- Rule: Trace instrumentation, Prompt caching, and tool invocation enhancements are automatically completed at the model layer; business code needs no manual handling.
- Source analysis:
ChatModelBase.stream()automatically wraps calls viaTracerRegistry.get().callModel(); when cacheControl=true,OpenAIBaseFormatter.applyCacheControl()automatically adds cache markers; toolChoice and parallelToolCalls parameters directly control tool behavior.
3.2 ReAct Reasoning Loop — from “Q&A” to “Multi-Step Autonomous Decision-Making”
- Standardized three-phase loop: strictly follows the “Thought → Action → Observation” state machine, with the model autonomously deciding termination or forced exit upon reaching
maxIterations, avoiding infinite recursion. - Dynamic context injection: before each reasoning round, available tool Schemas and historical trajectories are automatically formatted and injected into the Prompt, ensuring model decisions are based on the latest information and reducing hallucinated calls.
- Streaming intermediate state exposure: returns
Flux<AgentResponse>, pushing thinking, tool calls, execution results, and other events in real time, supporting frontend word-by-word display of the reasoning process, breaking the black-box experience. - Tool execution safety isolation: tool calls are executed independently, with parameter validation and return-value filtering; exceptions are fed back to the model as Observations for self-correction, preventing crashes or data leaks.
- Hard constraints on resource boundaries: through
maxIterations,maxTokensPerStep,toolTimeout, and other configurations, over-limit requests are automatically intercepted in the Flux chain, keeping production environment SLAs controllable.
3.3 External Abstraction Layer
3.3.1 Core Classes
3.3.2 Tool Shortcut Mechanism
04 The Three-Layer Architecture of the Reuse Layer
Beyond considering platform building points, before practice we also considered building the reuse layer — this is to achieve higher-level out-of-the-box usability after integrating with the Alibaba ecosystem! We also considered that in the future, other businesses wanting to reference our Agent implementation could more conveniently perform rapid customization!4.1 Tool Classes and Integration with Alibaba Ecosystem Platforms
AgentSkill + SkillBox interface definitions and does not provide any external sources. The reuse layer completes the ecosystem of enterprise-grade tools and derived classes:
4.2 Agent and Related Ecosystem Registration, Discovery, and Invocation
4.3 Link Orchestration and Management | AG-UI Protocol Link Orchestration
AguiMvcController handles RunAgentInput / emits AguiMessage event streams); internally using ReActAgent as the core orchestration engine driving the LLM reasoning-tool invocation loop, carrying request-level context (user identity, RAG query) through the AguiRuntimeContextBuilder SPI and RuntimeContext, and managing session-level state persistence (Memory / Toolkit / AgentMeta) through the Session SPI and AguiSessionManager.
4.4 Full-Link Observability
05 Practice Process Based on Development Key Points
The previous sections sorted out the core architecture and capability boundaries of AgentScope and the reuse layer. Based on this, we walk through the practice process around the five major building points one by one — what problems we encountered, how we decomposed them, and how we designed and implemented the solutions.5.1 Key Point One: DB-Driven Agent Execution Engine
5.1.1 Focused Goals
As the saying goes, “function serves value”. Before diving deep into technical implementation, let us first paint a picture of the future working scene of the logistics cost business-finance team. Imagine that on our platform, there are hundreds of frontline operations staff. Every day they shuttle between the five core links of billing, settlement, fund flow, invoice processing, and financial accounting. The current reality is: despite a huge system, a large amount of high-value energy is consumed in inefficient “manual verification” and “repetitive moving” — eyes switching wearily between screens, data mechanically copied between spreadsheets. They long for liberation and urgently need a tireless, precise, and efficient digital assistant. Therefore, our goal is not merely to deliver a few fixed automation tools, but to build a thriving Agent usage ecosystem. In this ecosystem, those who best understand business pain points are no longer the remote developers, but the frontline colleagues. We will give them the ability to manually configure Agents, allowing them to define and train dedicated Agents with their own hands, like assembling building blocks, based on current business fluctuations and personalized needs. Whether handling settlement peaks during big sales or processing complex abnormal bills, they can configure on demand, publish instantly, and iterate quickly. This is not only an efficiency revolution, but also a role reshaping: every operations staff member evolves from a tedious operator to a designer of intelligent processes, truly achieving “everyone is a developer, intelligence everywhere”.5.1.2 DB Design
5.1.3 Overall Flow Core Design (viewed together with the reuse layer)
5.1.3.1 Core Link One: Startup Registration — from DB to In-Memory Snapshot
The reuse layer has a large amount of registration capabilities; the core of the DB-driven design naturally needs to connect DB data to registration points — this is a typical design of the socket pattern! When is the right time to connect? Answer: application startup and updates of core SQL attributes both need to trigger overall Agent configuration updates through the registry! Starting with application startup: we uniformly start from a super base class, scan the DB, and complete the connection — that isAgentFactoryRegistry!
Besides connecting, another core goal of AgentFactoryRegistry is to build a runtime snapshot, which plays a key role in subsequent chat conversations!
AgentFactoryRegistry (implementing SmartInitializingSingleton) performs three-phase startup after all Spring Beans are initialized:
- Build static index: collect all
BaseFinanceAgentFactorysubclass Beans (static Agents defined in code), and put them intostaticIndexbyagentCode. - Read DB and split: read all enabled configurations from
ac_agent_config, and take the intersection with the static set:- Static Agents (with corresponding Factory classes) → call
factory.refreshFromDb() - Dynamic Agents (in DB but no Factory class in code) → call
dynamicRegistry.register(agentCode)to automatically createDynamicAgentEntry, and finally also call refreshFromDb
- Static Agents (with corresponding Factory classes) → call
- Failure aggregation: after all Agents finish, the failure list is thrown once for fail-fast.
AgentFactoryRegistry:
AgentFactoryRegistry needs to collect all static BaseFinanceAgentFactory instances and obtain DB configuration data. If we used @PostConstruct or InitializingBean.afterPropertiesSet() here, there would be timing issues.
This can be understood through the following diagram:
refreshFromDb() operation. The core action of refreshFromDb() is to build AgentConfigSnapshot — an immutable volatile snapshot object aggregating the Prompt, model parameters, Toolkit (including MCP Client), and SkillBox (including Skill content). Subsequent createAgent() calls only need one volatile read to obtain a cross-field consistent view, with no DB calls.
- Register to
AguiAgentRegistry:sparkRegistry.registerFactory(agentCode, entry::createAgent), implementing PROTOTYPE semantics via method reference (new instance per request); subsequently the framework’sDefaultAgentResolverlooks up by agentCode. - Register to
ResourceRegistry: making the Agent appear in the DevTool topology diagram. - Register to the A2A gateway: exposed as an A2A protocol endpoint via
DynamicA2ARegistrationAdvice.
5.1.3.2 Core Link Two: all main tables and association properties are in the management state, and admins persist through the page
In the Management State, all Agent-related configurations are operated through frontend pages and persisted to the relational database. The Runtime only acts as a read-only consumer, loading the corresponding configuration snapshot or draft based on the publication status, ensuring decoupling between configuration changes and online execution. The configuration system is divided into the main configuration domain (independent entities) and the binding relationship domain (associated entities), with the specific mapping as follows:- Operation: users modify Prompt, model parameters, or binding relationships on the page.
- Persistence: all changes are directly updated to the main table
ac_agent_configand its associated binding tables. - Status mark:
ac_agent_config.statusstays at1(Enabled/Draft). - Impact scope: only affects development/testing environments or debug sessions explicitly reading drafts; does not affect official online traffic.
- Trigger: the user clicks the “Publish” button.
- Snapshot generation: the system serializes the complete configuration of the current main table and all binding tables into JSON and writes it to
ac_agent_publish_record. This record is immutable, used for auditing and rollback. - Version promotion:
ac_agent_config.current_publish_versionis incremented.ac_agent_config.statusis updated to2(Published).
- Impact scope: official online traffic immediately switches to the newly published snapshot version.
- DRAFT Variant: mainly used for DevTool debugging or canary testing scenarios, directly reading the latest draft data in
ac_agent_config. - PUBLISHED Variant: the standard mode for production environments, loading the corresponding immutable snapshot from
ac_agent_publish_recordbased oncurrent_publish_version.
- Safety isolation: intermediate states during configuration modification (such as unfinished Prompt edits) do not pollute online services.
- Instant rollback: if anomalies occur after publishing, pointing
current_publish_versionto the previous version number or republishing an old snapshot achieves second-level rollback. - Audit trail:
ac_agent_publish_recordretains the complete scene of every release; combined withac_agent_config_audit_log, full-link configuration change tracing can be achieved.
5.1.3.3 Core Link Three: runtime state — full-link SPI customization of the reuse layer
Chapter 4 earlier defined the runtime link of the reuse layer — this is to keep all subsequent Agent operations links consistent! AndAguiMvcController is precisely what governs the overall runtime entry of the reuse layer!
UnifiedAguiRestController is the core controller we defined to handle chat requests; it directly delegates requests to the reuse layer framework’s AguiMvcController, fully reusing runtime capabilities such as AG-UI protocol SSE streaming, ReAct loop, and Tool invocation.
What if the reuse layer’s capabilities cannot cover customization scenarios? Customization of the reuse layer is achieved through framework-predefined SPI interfaces:
5.1.4 Runtime Deep Dive
The previous section said the runtime link fully reuses the reuse layer full link, but it also indicated that every checkpoint of the link is full of SPIs. This section analyzes this link and explains how we achieved the design goals: Goal: lightweight isolated Runtime: adopting the “build on use” mechanism, dynamically constructing lightweight Agent instance shells at runtime. It must both reuse the underlying Skill, SKILL, and MCP instances (the snapshot mentioned earlier), and ensure: ① efficient resource sharing ② excellent performance while achieving complete isolation of the Agent session execution environment, guaranteeing stability and security. Some link concept details are in Chapter 4’s operations link layer with detailed content; the focus here is analyzing how to perform runtime design combined with actual scenarios. Look again at the core classes and key SPIs of the reuse layer runtime:- Strategy pattern:
AguiRuntimeContextBuilder,AguiSessionManager,AgentTool, andHookare all replaceable strategies. - Factory pattern:
AguiAgentRegistrystoresSupplier<Agent>factories, creating a new instance per request. - Adapter pattern:
AguiAgentAdapterconverts ReActAgent’s internal events to AG-UI standard events. - Observer pattern:
Hookimplements tool invocation interception via event listening.
5.1.4.1 FinanceAguiRuntimeContextBuilder (SPI1)
FinanceAguiRuntimeContextBuilder is the first SPI we customized for the runtime link:RuntimeContext to the business layer for customization via the AguiRuntimeContextBuilder SPI.
This project implemented FinanceAguiRuntimeContextBuilder by inheriting AguiRuntimeContextBuilder, assembling the context data of each AG-UI request into the framework’s RuntimeContext, which contains 4 fields:
ThreadLocal, residual data after a thread returns to the pool may pollute subsequent sessions. RuntimeContext is bound by the framework to the AgentBase instance (per-agent-instance), created with the request and destroyed with it — naturally isolated, with no risk of cross-session data leakage.
Reason two: full-link reachability. RuntimeContext spans the complete lifecycle of an Agent from creation to execution; multiple downstream link nodes can consume it directly via agent.getRuntimeContext() without extra parameter passing:
RuntimeContext over ThreadLocal or method parameter passing.
5.1.4.2 FinanceAguiSessionManager (SPI2)
Just by its name, you might think it is only a session manager, but that’s not entirely true — let’s see its interface:InMemoryAguiSessionManager (ConcurrentHashMap cache, data lost on process restart) and SessionAwareAguiSessionManager (goes through the framework Session SPI, but fully serializes/deserializes memory every time). Neither can meet production-grade requirements.
When we designed the getOrCreateAgent method of AguiSessionManager in the reuse layer, it was both for implementing the “build on use” mechanism and, of course, for being able to use a caching mechanism.
Clearly, the reuse layer supports defining a real-time Agent creation entry point on the operations link — the framework hands the creation right of Agents to the business layer through the Supplier<Agent> parameter, and our AgentConfigSnapshot was actually designed for the caching mechanism as well.
As mentioned earlier, the snapshot builds everything related to the Agent: MCP, Skills, SkillBox, modelParams, Toolkit, and so on. So we can quickly create a lightweight agent and achieve the following:
- Efficient resource sharing: the snapshot is a volatile reference; all requests share the same configuration snapshot, and creating an Agent only performs one volatile read — zero DB/network overhead.
- Excellent performance:
createAgent()triggers no remote calls at all; it just assembles prompt + model + tools from the snapshot, completing in milliseconds. - Complete isolation, guaranteeing stability and security: each request gets an independent ReActAgent instance (PROTOTYPE scope), with per-request memory and RuntimeContext not interfering with each other.
AguiRequestProcessor.
2) The reuse layer AguiRequestProcessor: the control hub for the first two major stages of the operations link
If broken down finely, the operations link of the reuse layer has more than 20 nodes, but roughly classified it can be divided into 4 stages:
- Request preprocessing — building request context, initializing various core components.
- Agent creation and session/memory loading — instantiating the Agent and restoring historical state.
- ReAct loop mechanism — LLM reasoning → tool invocation → observing results → reasoning again.
- Session persistence and wrap-up — saving state, triggering asynchronous tasks.
AguiRequestProcessor is the core control class for everything except the ReAct loop mechanism stage. Judging from the framework code, it holds three key components and one core method — let’s take a look:
AguiRequestProcessor: it is not responsible for specific Agent creation, state loading, or context building, but for orchestrating the invocation order of these SPIs. Each SPI plays its own role:
AgentResolver holds the absolute authority over obtaining Agent instances; spark supports customization, and here we use the reuse layer’s DefaultAgentResolver by default, without any modification.
The design intent of DefaultAgentResolver is to resolve one core contradiction: AguiRequestProcessor only cares about “give me an Agent”, but the way of obtaining Agents can be completely different under different deployment modes.
DefaultAgentResolver does two things:
- Caches the relationship between threadId and agentId.
- Calls the sessionManager.getOrCreateAgent method.
a. And passes the authority of
obtaining the Agent from AguiAgentRegistryto getOrCreateAgent.
AguiRequestProcessor manages Agents through DefaultAgentResolver, and DefaultAgentResolver in turn delegates the lifecycle to sessionManager. sessionManager is essentially customizable via SPI, so we decided to customize a sessionManager to truly manage FinanceAgent’s Agent instances, history, and memory.
Let’s see how our FinanceAguiSessionManager design achieves the goals of lightweight and isolation:
The assembly process of the getOrCreateAgent method is divided into three stages, and the entire process relies only on one volatile read:
- Lightweightly assemble the Agent through agentSnapshot.
- Load historical session + long-term memory.
- Detect whether an asynchronous compression task needs to be submitted.
BaseFinanceAgentFactory builds the snapshot, so we’d better quickly locate the corresponding BaseFinanceAgentFactory. Therefore, during project initialization, we register BaseFinanceAgentFactory’s snapshot-based Agent creation method createAgent into sparkRegistry:
createAgent() is divided into three stages, relying only on the AgentConfigSnapshot obtained through one volatile read throughout, triggering no DB or remote calls at all:
Stage one: Prompt enhancement
createAgent() rather than refreshFromDb(), because they depend on request-level userId and ragQuery (from RuntimeContext), not configuration-level static data.
Stage two: ReActAgent.Builder assembly
toolkit and skillBox are both objects already built inside the snapshot. This is the core value of the snapshot: pushing all time-consuming MCP client connection establishment, Tool registration, and Skill loading forward to the refreshFromDb() stage; createAgent() only does reference passing, truly making Agent assembly for each request a pure in-memory operation.
Stage three: parameter override cascade
The six parameters temperature, topP, maxTokens, maxIters, enablePlan, and modelName all follow the same priority chain:
AgentConfigSnapshot is a final class; all List fields are wrapped with Collections.unmodifiableList(), exposing only getters externally, with no setters. BaseFinanceAgentFactory holds a volatile AgentConfigSnapshot configSnapshot field:
- Write: only replaces the entire reference in
refreshFromDb()(this.configSnapshot = newSnapshot); no partial modification exists. - Read: at the beginning of
createAgent(), one volatile read stores into local variablesnap, and this local reference is used for the entire subsequent process.
createAgent() call, prompt, toolkit, and skillBox definitely come from the same version; no intermediate state exists where the prompt is the new version while the toolkit is still the old version.
4) The overall design of FinanceAguiSessionManager
Beyond the lightweight-level creation design of createAgent(), the customization of saveAgent, removeSession, and hasMemory also contributes significantly to the overall lightness of financeAgent.
hasMemory()— double-check existence probe: first queriesac_agent_session, thenac_agent_block.maxSeq, determining “whether there is history” without fully loading conversation history. This is the key hook by which the reuse layer decides whether to go throughextractLatestUserMessage.saveAgent()— incremental persistence: skips full writes ofmemory_messagesthrough JdbcSession blacklist, only incrementally appending new blocks (seq > dbMaxSeq), reducing write amplification from O(N) to O(Δ).removeSession()— cascading cleanup: completes batch deletion ofac_agent_session+ac_agent_blockwithin a single lock acquisition, and synchronously cleans up thetailSnapshotcache.
createAgent → hasMemory → onEnter → saveAgent → removeSession.
These four methods together constitute the lightweight runtime of the finance agent: createAgent solves lightweight creation (< 1ms, zero DB), hasMemory solves lightweight probing (index hit), saveAgent solves lightweight persistence (incremental writes), removeSession solves lightweight cleanup (batch deletion).
5.1.4.3 Engineering-Grade Human in the Loop (SPI3)
HITL itself exists in two forms: one at the model level, and the other at the engineering level. Their differences:- How to confirm that the user is performing a confirmation action?
- How to confirm that interception operations are needed before/after the user uses a tool? a. Once an interception point is discovered, how to stop all Agent behavior! Wait for the confirmation result before continuing the previous link;
- The message format passed by users can be recognized — just agree on the HITL confirmation format.
- PostReaningEvent is before tool invocation and has engineering-level stopAgent.
- PostActingEvent is after tool invocation and has engineering-level stopAgent.
5.1.4.4 ContextInjectingMcpTool (SPI4)
AgentTool is AgentScope’s tool-level SPI extension point, which can define encapsulated tool invocation logic:ContextInjectingMcpTool implements this interface, wrapping the framework’s native McpTool in decorator pattern, solving one core problem: the MCP Server’s authentication identity cannot be welded at build time; it must be dynamically bound to the current request user at every invocation.
1) Problem background
The identity of AgentScope’s native McpTool is already determined at the build time of AoneMcpClientBuilder.buildSync() (DB ac_mcp_server.user_id); all users share the same identity when calling the MCP Server. This is unacceptable in financial scenarios — when different users call the same MCP tool (such as querying approval forms), the MCP Server must see the caller’s own identity to perform permission verification and data isolation.
2) Design: identity injection at invocation time
ContextInjectingMcpTool does not change the tool’s external contract (getName / getDescription / getParameters all pass through to the delegate); it only completes identity injection at the callAsync boundary:
Mono / Flux), and ThreadLocal is lost during cross-thread scheduling.
3) McpCallIdentity: the invocation-level identity carrier
ContextInjectingMcpTool → McpTransportContext) need no modification as fields are added or removed; only the two ends — the assembly end (RuntimeContext reading) and the consumption end (authentication header generation) — need to change.
5.1.5 Summary of Seven Design Philosophies
- Convention over configuration.
- DB and application layer isomorphism (eliminating the conversion layer).
- Draft/publish dual-track (modifications don’t affect production).
- Full coverage of optimistic locking (preventing concurrency conflicts).
- Component marketplace + binding separation (reuse + independent evolution).
- Asynchronous task decoupling (not blocking the main flow).
- Incremental persistence (reducing write pressure).
5.2 Key Point Two: Low-Cost Compatibility with Various Ecosystem Platforms
5.2.1 SkillSourceLoader
The current system has established theSkillSourceLoader SPI abstraction layer, encapsulating Skill source differences in the interface implementation layer; the upper-layer SkillRegistry + BaseFinanceAgentFactory are completely unaware of underlying source details. This means the cost of connecting to a new ecosystem platform is compressed to the level of “implementing one @Component class”.
(skillSource, skillName, skillVersion) — source is a first-class citizen, naturally supporting coexistence of multiple sources.
5.2.1.1 AoneSkillSourceLoader
- Strong constraint layer: every row of
ac_agent_skillmust point to anac_skillrow withenabled=1, otherwise fail-fast at startup (IllegalStateException), eliminating the hidden risk of “configured but cannot be loaded”. - Weak failure layer: when the Aone service is temporarily unavailable, the Agent can still start (skipping that Skill), with background scheduled retry for recovery.
- Version management:
ac_skill.skill_versionsupports semantic versioning; upgrading a Skill only requires adding one row in DB + updating theac_agent_skillbinding.
5.2.1.2 OssSkillSourceLoader
- Zero-approval iteration: developer modifies SKILL.md → packages zip → uploads to OSS → adds one row
ac_skill(source=OSS)in DB → refreshes Agent; the entire process requires no Aone release approval. - Rapid version switching: the same Skill can maintain multiple versions on OSS; developers switch in seconds by modifying
ac_agent_skill.skill_version. - Local debugging friendly: the local cache directory of OSS Skills is isolated from AONE, not interfering with each other.
5.2.1.3 Cost Analysis of Extending New Ecosystem Platforms
Steps to connect a new Skill marketplace:SkillRegistry— automatically discovers new Loaders, no modification needed.SkillKey— already contains the source dimension, naturally compatible.BaseFinanceAgentFactory.resolveSkills()— only cares aboutSkillKey, not source implementation.ac_agent_skill/ac_skilltable structures — theskill_sourcefield is already an open string.- Frontend management pages — just add the new source value to the dropdown.
5.2.1.4 Standardized Skill-Driven Flow
5.2.1.5 How to Monitor the Complete Driven Lifecycle of Agent-Bound Skills
1) Skill lifecycle panorama- Production environment:
AgentSkillTaskScheduler(SchedulerX2), recommended cron0/20 * * * * ?(one round every 20 seconds). - Development environment:
AgentSkillTaskLocalScheduler(Spring@Scheduled),fixedDelay=20s, needs manual enabling.
5.2.2 MCP
McpClientFactory.build() does only one thing — mapping DB rows field by field to the reuse layer framework’s AoneMcpClientBuilder:
AoneMcpType enum value, there are zero lines of code changes on this side — parseEnum() is generic reflection, automatically recognizing new enums.
5.2.3 Two Compatibility Strategies: Skill vs MCP
5.3 Key Point Three: Overall Agent Effectiveness Evolution
5.3.1 Thoughts
2026 is regarded as the first year of the AI explosion, and Hermes’s phenomenal rise made “evolution” a buzzword in the industry. Although various technical articles overwhelmingly analyze various “closed-loop evolution” strategies, few in the industry seem able to clearly define: what exactly is true “evolution”? What is its core measurement metric? We need to return to the essence and clarify a key proposition: how to distinguish “change” from “evolution”? Taking Hermes’s Skill creation strategy as an example, strictly speaking, this is more of a random “change”. Due to the lack of an effective value verification mechanism, it is hard to judge whether a newly generated Skill truly improves system capability — change without positive feedback can only be called perturbation, not evolution. I browsed some of Hermes’s source code and checked related materials, reaching a conclusion:5.3.2 How to Define Evolution
In building autonomous Agents and their underlying Skills, we often fall into a misconception: believing there exists a perfect, ultimate model state. However, the core driving force of Agent and Skill evolution is not abstract self-improvement, but effectiveness feedback based on specific scenarios, especially quantifiable effectiveness comparisons. One core cognition must be made clear: “absolute evolution” is a pseudo-proposition; only “relative evolution” is a real and executable proposition in engineering practice. 1) Why is “absolute evolution” a pseudo-proposition? In open-domain natural language processing or complex task planning, there is no unique “standard answer”. The same user intent may have multiple valid execution paths; the same piece of code may have multiple equivalent implementations. Therefore, attempting to define a universally applicable “perfect Agent” is not only theoretically infeasible but also meaningless in engineering. True evolution occurs in comparative advantage within finite boundaries. We need to map the infinite real world onto a finite evaluation set (Benchmark). Only by building high-quality test cases and introducing fine-grained scoring mechanisms such as 3-point, 6-point, or 9-point scales can we transform vague “good and bad” into trackable, optimizable “high and low”. The logical chain of this relative evolution is as follows:- Baseline establishment: establish a performance baseline on the current version’s evaluation set.
- Differential comparison: score differences on specific metrics between the new version and the old version, or between different strategies.
- Iteration orientation: locate weaknesses based on score gaps, driving the next round of parameter adjustment or logic optimization.
5.3.3 How to Develop the Capability of an Evolution Closed Loop?
Human closed loop vs automatic closed loop 1) Human closed loop: the minimum viable evolution system Before discussing automation, we must first admit one thing: the human closed loop is not a primitive form of the automatic closed loop — it is the prototype validation of the automatic closed loop. The flow of the human closed loop is intuitive:- Use a four-dimensional rubric (rather than open-ended scoring), with clear 1-5 point standards for each dimension, reducing scoring randomness.
- Average multiple evaluations (at least 3).
- Regularly calibrate with human annotations, ensuring the Pearson correlation coefficient between automatic and human scores > 0.7.
- The performance dimension fully uses automated metrics, not going through LLM-as-judge, avoiding unnecessary scoring noise.
- Update the evaluation dataset regularly (replace at least 20% monthly).
- Keep a “hidden” evaluation set, never used for optimization, only for validation.
- Monitor online metrics and evaluation metrics simultaneously; investigate immediately when the two diverge.
- Incremental evaluation first: only evaluate affected Skills, no full regression.
- Tiered evaluation sets: core set (must run every time) + extended set (low-frequency runs).
- Use small models for LLM-as-judge, escalating to large models only when uncertain.
- Collect human-annotated “overall satisfaction” scores, perform regression analysis with the four-dimensional weighted score, and reverse-engineer the true weights.
- Maintain differentiated weights by Skill type (e.g., raise the performance weight of alert-type Skills to 0.30).
- Review weight configuration quarterly, ensuring alignment with user perception.
5.4 Key Point Four: Context Compression Strategy and Long-Term Memory Strategy
5.4.1 Purpose
The purpose in one sentence:- Context compression: prevents the LLM context window of a single request from growing infinitely with conversation turns. Core idea: old conversations → LLM summarization → replace original blocks, keeping the most recent N blocks fully uncompressed.
- Long-term memory (User-level): manages “remembering who the user is and what they prefer across sessions”.
5.4.2 Compression
seq = the timestamp(ms) of the first Msg, guaranteeing strict increment and idempotency.
When does it start? Answer: still a capability of FinanceAguiSessionManager.
INSERT IGNORE + UK (agent_code, thread_id, status) → only one PENDING per thread at the same time.
Complete design:
- Asynchronous execution: compression is not on the request path and does not affect user latency.
- CAS contention: concurrency-safe for multiple workers (
updateStatus(id, PENDING, RUNNING)returning 0 = someone else already took it). - Executed within Session lock: avoiding concurrent operations on the block table with onEnter/onExit.
- Failure retry: up to 5 times; after the limit is exceeded, keep RUNNING + last_error awaiting operations intervention.
- SUMMARY seq conflict protection: abandon when
INSERT IGNOREreturns 0, avoiding duplicate archiving. - At the next
createAgentload: a.blockMapper.selectActiveAsc()only fetches blocks witharchived=0b. Returns:[SUMMARY block] + [most recent 5 NORMAL blocks]c. The SUMMARY block’srole=SYSTEM, with text starting with[Conversation Summary], which the LLM can recognize as a historical summary
5.4.3 Long-Term Memory Strategy
Automatically extract user preferences and facts from conversations, persist them across sessions, and inject them into the system prompt at each request, making the Agent “recognize” returning users.5.5 Key Point Five: Integration with the Business-Finance Platform Admin Console, Free-Form Page Customization
5.6 Key Point Six: Full-Link Identity Marking and Establishment of Fine-Grained Permission Control Mechanisms
When designing the permission system, focus on the following aspects:- How to embed permission verification logic into the Agent invocation chain.
- How to implement data isolation based on user identity and context.
- How to support dynamic, visual, auditable permission application and authorization flows.
- How to ensure permission policy consistency between the MCP/Skill layer and underlying data services.
5.6.1 Sorted Out the Four Defense-in-Depth Aspects of the Existing Permission System
5.6.2 Full-Link Identity Unification
Current state: the identity propagation chain within the financeAgent project is largely connected — BUC SSO completes authentication at the entry layer,empId + ssoToken are bridged to AG-UI’s RuntimeContext via HTTP Header, and the MCP invocation layer achieves per-request identity isolation through McpCallIdentity + Normandy SM2 signatures (ContextInjectingMcpTool → McpClientFactory.buildIsolated()). This chain is closed within the project.
Problem: but when the invocation chain goes beyond this project’s boundaries, the identity context breaks:
- HSF invocation chain identity propagation: inject EagleEye RpcContext in the
HsfUtilgeneric invocation layer, propagatingempIdas caller context to downstream HSF services. Downstream services can then perform user-level authentication and auditing, rather than only identifying the caller application. Prioritize covering the AMDP data query chain (currently all user queries share the sameAuthParam, with unauthorized query risks). - Full coverage of MCP identity isolation: currently
shouldIsolate()only takes effect forNORMANDY_AUTH+AONE/ZETTAtypes. In the future it should extend to all MCP Server types; for MCP Servers that do not support per-request identity, push them to integrate Normandy Auth or OAuth2 token exchange mode, gradually eliminating the security surface of shared tokens. - Unified identity context (Identity Context): abstract a
CallerIdentitylayer (beyond the MCP scope of the currentMcpCallIdentity), covering all outbound invocation scenarios such as HSF / MCP / HTTP callbacks / MetaQ message producers. Ensuring that no matter which channel a request enters from and exits through, the end user identity is always traceable.
5.6.3 Canary System Construction
Current state: the core framework for canary routing is already built — theac_agent_gray_config table + GrayMatcher three-dimensional matching (percentage / whitelist / environment) + the version splitting logic of BaseFinanceAgentFactory.buildPublishedSnapshot(), supporting the complete lifecycle of canary release → validation → full rollout (promoteToStable).
Current shortcomings and evolution directions:
- Percentage routing user stickiness:
GrayMatcher.matchPercentage()currently usesThreadLocalRandomfor per-request randomization; the same user may hit canary in one request and stable version in the next. This is unfriendly to both troubleshooting and user experience. Change to deterministic bucketing viahash(workNo) % 100 < percentage, ensuring the same user is always routed to the same version within a canary cycle. - Canary observability: currently canary hit results are only reflected in version loading logic, lacking explicit instrumentation and metrics. Needed:
- Mark
X-Gray-Hit: true/falseandX-Agent-Versionin AG-UI response Headers, for frontend awareness and debugging. - Split core metrics (success rate, average latency, tool invocation failure rate) by canary/stable version dimension, connecting to Sunfire Dashboard.
- Output canary hit logs in structured form, supporting three-dimensional search by
empId+agentCode+version.
- Mark
- Multi-level canary orchestration: current canary granularity is single Agent level. Future needs to support:
- Skill-level canary: within the same Agent, some Skills use canary versions (e.g., new prompt templates) while others keep online versions.
- MCP Server-level canary: new-version MCP Servers are only open to canary users, avoiding new tool instability affecting all users.
- Combined strategies: whitelist + percentage can be stacked (first whitelist internal validation → then percentage expansion); currently the three strategies are mutually exclusive.
- Automatic canary promotion and rollback:
- Set automatic promotion conditions for canary stages: canary user count ≥ N and success rate ≥ threshold → automatically expand percentage → full rollout.
- Set automatic rollback conditions: canary version error rate suddenly increases (> X% relative to stable version) → automatically switch canary traffic back to stable version, emit Sunfire alert.
- Currently
promoteToStableis a manual operation; an automated decision layer needs to be added on this basis.
- Canary-HITL linkage: new tools/Skills in canary versions should automatically raise the HITL (Human-in-the-Loop) confirmation level — tool invocations in canary traffic go through BEFORE mode confirmation by default, while stable versions can downgrade to AFTER or no confirmation, reducing the blast radius of canary versions.
5.6.4 Other Security Infrastructure
GrayMatcher, McpClientFactory.buildIsolated(), HitlHook, UserUtils, etc.); each improvement has clear code entry points and can be implemented in phases by priority.