Lynxe Framework

repository·main·Indexed 22 days ago

https://github.com/spring-ai-alibaba/lynxe

A Java-based multi-agent collaboration framework (formerly JManus) built on Spring Boot for high-determinism exploratory tasks and automated workflows. It supports integration via HTTP or as a library, includes a Vue 3 and TypeScript-based web interface (Lynxe UI), and provides deployment options via Docker, executable JARs, or source builds.

Tokens
18.1K
Snippets
46
Records
69
Agent score
76%

What's inside Lynxe

  1. What is Lynxe?

    main

    Lynxe (formerly JManus) is a pure Java implementation of the Manus multi-agent system. It is designed for exploratory tasks that require high determinism, such as extracting data from massive datasets into databases or analyzing logs to generate alerts.

    Key features include:

    • Pure Java Multi-Agent Collaboration: Provides full HTTP service capabilities, making it easy for Java developers to integrate into existing projects.
    • Func-Agent Mode: Offers precise control over execution details, providing high determinism for complex, repetitive workflows.
    • MCP Integration: Native support for the Model Context Protocol (MCP), enabling seamless integration with external services and tools.
  2. Understand the purpose of toolcallId in DynamicAgent

    main

    In Lynxe, toolcallId is a unique identifier used to manage the lifecycle and traceability of tool executions. It serves three primary purposes:

    1. Establishing Parent-Child Relationships: When a tool call triggers a sub-plan, the toolcallId links the tool execution to the resulting sub-plan.
    2. Execution Traceability: It allows the system to track the complete execution chain (e.g., which specific tool call triggered which sub-plan).
    3. Database Association: It acts as a foreign key in the database to link ActToolInfoEntity and PlanExecutionRecord.
  3. Decide where to store data (Placement Matrix)

    main

    When designing your application, use this matrix to decide whether to use a Pinia store, component state, or a composable:

    Data kindPreferReason
    Shared across routes/componentsPinia storeSingle source of truth, devtools, easy to inject
    Server-backed (list/detail)Pinia store + API layerCache, loading/error state, sync with backend
    UI-only (modals, tabs, collapse)Pinia store or component stateStore if multiple components need it; else ref in component
    Derived onlyComputed in store or composableNo duplication; recompute from source of truth
    Form draft / transientComponent or composableDon’t put in global store until “saved” or “applied”
    True singleton (e.g. current user)One store, one “current” refAvoid two places holding “current"
  4. Understand the Lynxe UI project structure

    main

    The project follows a standard Vue 3 structure:

    • src/components/: Reusable components (e.g., editor/ contains the Monaco Editor component).
    • src/layout/: Layout components.
    • src/views/: Page components (e.g., conversation/ for chat, plan/ for task planning, error/ for error pages).
    • src/router/: Vue Router configuration.
    • src/base/: Base utilities including i18n/ (internationalization), http/ (HTTP client), and constants.ts.
    • src/utils/: General utility functions.
    src/
    ├── components/          # Reusable components
    │   └── editor/         # Monaco Editor component
    ├── layout/             # Layout components
    ├── views/              # Page components
    │   ├── conversation/   # Main conversation page
    │   ├── plan/          # Task planning page
    │   └── error/          # Error pages
    ├── router/            # Vue Router configuration
    ├── base/              # Base utilities
    │   ├── i18n/         # Internationalization
    │   ├── http/         # HTTP client
    │   └── constants.ts  # Constants
    └── utils/            # Utility functions
  5. Understand toolcallId storage for single tool execution

    main

    When a single tool is executed, the toolcallId is stored in the act_tool_info database table in two distinct stages to track the lifecycle of the tool call.

    Stage 1: Initial Storage (Before Execution)

    Timing: After the LLM returns a tool call, but before the tool is actually executed. Process:

    1. DynamicAgent.think() creates an ActToolParam containing the toolcallId.
    2. planExecutionRecorder.recordThinkingAndAction() is called.
    3. The ActToolParam is converted to an ActToolInfoEntity and saved to the act_tool_info table. State: At this point, the record exists in the database, but the result field is null.

    Stage 2: Result Update (After Execution)

    Timing: After the tool execution is complete and the result is available. Process:

    1. DynamicAgent.processSingleTool() executes the tool and sets the result in the ActToolParam.
    2. recordActionResult() is called.
    3. The system uses the toolCallId to find the existing ActToolInfoEntity in the act_tool_info table.
    4. The result field of the existing entity is updated with the tool's output, and the record is saved.

    Database Details:

    • Table: act_tool_info
    • Unique Key: tool_call_id (used for lookups during the update stage)
    // Stage 1: Initial storage in DynamicAgent.think()
    ActToolParam actToolInfo = new ActToolParam(toolCall.name(), toolCall.arguments(), toolcallId);
    
    // Stage 2: Updating result in NewRepoPlanExecutionRecorder.recordActionResult()
    Optional<ActToolInfoEntity> existingEntityOpt = actToolInfoRepository
        .findByToolCallId(actToolParam.getToolCallId());
    
    if (existingEntityOpt.isPresent()) {
        ActToolInfoEntity existingEntity = existingEntityOpt.get();
        existingEntity.setResult(actToolParam.getResult());
        actToolInfoRepository.save(existingEntity);
    }
  6. Choose a Pinia store layout strategy

    main

    Depending on the scale of your application, choose one of two folder structures for your stores/ directory:

    Best for simplicity and smaller to medium-sized apps. Each domain gets its own file in the root of the stores folder.

    stores/
      namespace.ts
      task.ts
      planTemplateConfig.ts
      messageDialog.ts
      planExecution.ts
      ...

    Option B: Grouped by feature

    Best for large-scale applications where you want clear feature-based boundaries.

    stores/
      workspace/
        namespace.ts
      plan/
        template.ts
        execution.ts
      chat/
        messageDialog.ts
        memory.ts
      ...
  7. Design pattern for Pinia stores in Lynxe

    main

    When building the frontend for Lynxe, follow a strict separation between backend-mirrored data and UI-only state.

    Design Rule:

    • Backend-mirror stores: Each store should hold data that is either received from or sent to the backend (e.g., lists of namespaces, conversation history, configuration settings). These stores should primarily contain Ref objects that mirror API responses.
    • UI-only state: State that only affects the visual presentation (e.g., isCollapsed, activeTab, sidebarVisible) should live in the individual components or a dedicated ui / memoryPanel store.

    This separation ensures that the core data logic remains clean and decoupled from the user interface behavior.

  8. Understand toolcallId storage for SubplanTool execution

    main

    For sub-plans (plans that have a parentPlanId), the toolcallId is used to establish a relationship between the tool call and the resulting sub-plan execution.

    Timing: At the start of a sub-plan execution, before any agent begins working. Process:

    1. SubplanToolWrapper extracts the toolcallId from the ToolContext.
    2. The toolcallId is passed to PlanningCoordinator.executeByPlan() and set in the ExecutionContext.
    3. AbstractPlanExecutor.initializePlanExecution() triggers recordPlanExecutionStart().
    4. NewRepoPlanExecutionRecorder.createPlanRelationship() sets the toolCallId on a PlanExecutionRecordEntity.
    5. The record is saved to the database.

    Database Details:

    • Table: plan_execution_record
    • Field: tool_call_id (This field is only populated for sub-plans).

    Relationship: The toolCallId in PlanExecutionRecordEntity references the toolCallId in ActToolInfoEntity, creating a link between the tool call and the sub-plan it triggered.

    // SubplanToolWrapper extracts and passes the ID
    String toolCallId = extractToolCallIdFromContext(toolContext);
    planningCoordinator.executeByPlan(plan, rootPlanId, currentPlanId, newPlanId, 
        toolCallId, RequestSource.HTTP_REQUEST, null, planDepth, null);
    
    // NewRepoPlanExecutionRecorder establishes the link
    if (toolcallId != null && !toolcallId.trim().isEmpty()) {
        planRecord.setToolCallId(toolcallId);
    }
    planExecutionRecordRepository.save(planRecord);
  9. Important considerations for toolcallId

    main

    When working with toolcallId in Lynxe, keep the following rules in mind:

    • Naming Convention: Note the difference in casing. In ToolContext, use lowercase toolcallId, but in Java code/fields, use camelCase toolCallId.
    • Uniqueness: Every parallel tool execution must have its own unique toolcallId to ensure sub-plans are linked correctly.
    • Lifecycle:
      • For single tools, the ActToolInfoEntity is created before execution and updated after execution.
      • For sub-plans, the PlanExecutionRecordEntity.toolCallId is set exactly once at the start.
    • Inheritance: Sub-plans inherit the toolcallId from the parent tool call to maintain the execution chain.
    • Concurrency: All storage operations are wrapped in @Transactional to ensure data consistency.
  10. Track asynchronous task progress

    main

    To monitor the progress of an asynchronous task, poll the /api/executor/details/{planId} endpoint.

    • Polling Frequency: The default frontend polling interval is 1 second.
    • Data Structure: The endpoint returns a complete execution tree structure, which includes all sub-plans generated during the process.
  11. Install and run Lynxe UI in development mode

    main

    To set up the Lynxe UI (a modern Web management interface for Spring AI Alibaba Lynxe) for local development, ensure you have Node.js (>= 16) and pnpm installed. Follow these steps:

    1. Clone the repository (if not already done).
    2. Navigate to the ui-vue3 directory.
    3. Install dependencies using pnpm.
    4. Start the development server.
    # Clone the repository
    git clone https://github.com/spring-ai-alibaba/spring-ai-alibaba.git
    
    # Enter the UI directory
    cd ui-vue3
    
    # Install dependencies
    pnpm install
    
    # Start the development server
    pnpm run dev
  12. Build and preview Lynxe UI for production

    main

    To prepare the Lynxe UI for production deployment, use the build scripts provided by pnpm.

    Build for production

    Generates optimized static assets.

    pnpm run build

    Preview production build

    Starts a local server to preview the production build locally.

    pnpm run preview
    pnpm run build
    pnpm run preview