diff --git a/.github/workflows/docker-build-push-beta.yml b/.github/workflows/docker-build-push-beta.yml
index 76cbb61ae..0946bbf25 100644
--- a/.github/workflows/docker-build-push-beta.yml
+++ b/.github/workflows/docker-build-push-beta.yml
@@ -150,6 +150,46 @@ jobs:
- name: Push web image (arm64) to DockerHub
run: docker push nexent/nexent-web:beta-arm64
+ build-and-push-terminal-amd64:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Set up Docker Buildx
+ run: |
+ if ! docker buildx inspect nexent_builder > /dev/null 2>&1; then
+ docker buildx create --name nexent_builder --use
+ else
+ docker buildx use nexent_builder
+ fi
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build terminal image (amd64) and load locally
+ run: |
+ docker buildx build --platform linux/amd64 -t nexent/nexent-ubuntu-terminal:beta-amd64 --load -f make/terminal/Dockerfile .
+ - name: Login to DockerHub
+ run: echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u nexent --password-stdin
+ - name: Push terminal image (amd64) to DockerHub
+ run: docker push nexent/nexent-ubuntu-terminal:beta-amd64
+
+ build-and-push-terminal-arm64:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Set up Docker Buildx
+ run: |
+ if ! docker buildx inspect nexent_builder > /dev/null 2>&1; then
+ docker buildx create --name nexent_builder --use
+ else
+ docker buildx use nexent_builder
+ fi
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build terminal image (arm64) and load locally
+ run: |
+ docker buildx build --platform linux/arm64 -t nexent/nexent-ubuntu-terminal:beta-arm64 --load -f make/terminal/Dockerfile .
+ - name: Login to DockerHub
+ run: echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u nexent --password-stdin
+ - name: Push terminal image (arm64) to DockerHub
+ run: docker push nexent/nexent-ubuntu-terminal:beta-arm64
+
manifest-push-main:
runs-on: ubuntu-latest
needs:
@@ -193,4 +233,19 @@ jobs:
docker manifest create nexent/nexent-web:beta \
nexent/nexent-web:beta-amd64 \
nexent/nexent-web:beta-arm64
- docker manifest push nexent/nexent-web:beta
\ No newline at end of file
+ docker manifest push nexent/nexent-web:beta
+
+ manifest-push-terminal:
+ runs-on: ubuntu-latest
+ needs:
+ - build-and-push-terminal-amd64
+ - build-and-push-terminal-arm64
+ steps:
+ - name: Login to DockerHub
+ run: echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u nexent --password-stdin
+ - name: Create and push manifest for terminal (DockerHub)
+ run: |
+ docker manifest create nexent/nexent-ubuntu-terminal:beta \
+ nexent/nexent-ubuntu-terminal:beta-amd64 \
+ nexent/nexent-ubuntu-terminal:beta-arm64
+ docker manifest push nexent/nexent-ubuntu-terminal:beta
\ No newline at end of file
diff --git a/.github/workflows/docker-build-push-mainland.yml b/.github/workflows/docker-build-push-mainland.yml
index 84be4f13b..1628b0bf7 100644
--- a/.github/workflows/docker-build-push-mainland.yml
+++ b/.github/workflows/docker-build-push-mainland.yml
@@ -147,6 +147,46 @@ jobs:
- name: Push web image (arm64) to Tencent Cloud
run: docker push ccr.ccs.tencentyun.com/nexent-hub/nexent-web:arm64
+ build-and-push-terminal-amd64:
+ runs-on: ${{ fromJson(inputs.runner_label_json) }}
+ steps:
+ - name: Set up Docker Buildx
+ run: |
+ if ! docker buildx inspect nexent_builder > /dev/null 2>&1; then
+ docker buildx create --name nexent_builder --use
+ else
+ docker buildx use nexent_builder
+ fi
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build terminal image (amd64) and load locally
+ run: |
+ docker buildx build --platform linux/amd64 --load -t ccr.ccs.tencentyun.com/nexent-hub/nexent-ubuntu-terminal:amd64 -f make/terminal/Dockerfile .
+ - name: Login to Tencent Cloud
+ run: echo ${{ secrets.TCR_PASSWORD }} | docker login ccr.ccs.tencentyun.com --username=${{ secrets.TCR_USERNAME }} --password-stdin
+ - name: Push terminal image (amd64) to Tencent Cloud
+ run: docker push ccr.ccs.tencentyun.com/nexent-hub/nexent-ubuntu-terminal:amd64
+
+ build-and-push-terminal-arm64:
+ runs-on: ${{ fromJson(inputs.runner_label_json) }}
+ steps:
+ - name: Set up Docker Buildx
+ run: |
+ if ! docker buildx inspect nexent_builder > /dev/null 2>&1; then
+ docker buildx create --name nexent_builder --use
+ else
+ docker buildx use nexent_builder
+ fi
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build terminal image (arm64) and load locally
+ run: |
+ docker buildx build --platform linux/arm64 --load -t ccr.ccs.tencentyun.com/nexent-hub/nexent-ubuntu-terminal:arm64 -f make/terminal/Dockerfile .
+ - name: Login to Tencent Cloud
+ run: echo ${{ secrets.TCR_PASSWORD }} | docker login ccr.ccs.tencentyun.com --username=${{ secrets.TCR_USERNAME }} --password-stdin
+ - name: Push terminal image (arm64) to Tencent Cloud
+ run: docker push ccr.ccs.tencentyun.com/nexent-hub/nexent-ubuntu-terminal:arm64
+
manifest-push-main:
runs-on: ubuntu-latest
needs:
@@ -190,4 +230,19 @@ jobs:
docker manifest create ccr.ccs.tencentyun.com/nexent-hub/nexent-web:latest \
ccr.ccs.tencentyun.com/nexent-hub/nexent-web:amd64 \
ccr.ccs.tencentyun.com/nexent-hub/nexent-web:arm64
- docker manifest push ccr.ccs.tencentyun.com/nexent-hub/nexent-web:latest
\ No newline at end of file
+ docker manifest push ccr.ccs.tencentyun.com/nexent-hub/nexent-web:latest
+
+ manifest-push-terminal:
+ runs-on: ubuntu-latest
+ needs:
+ - build-and-push-terminal-amd64
+ - build-and-push-terminal-arm64
+ steps:
+ - name: Login to Tencent Cloud
+ run: echo ${{ secrets.TCR_PASSWORD }} | docker login ccr.ccs.tencentyun.com --username=${{ secrets.TCR_USERNAME }} --password-stdin
+ - name: Create and push manifest for terminal (Tencent Cloud)
+ run: |
+ docker manifest create ccr.ccs.tencentyun.com/nexent-hub/nexent-ubuntu-terminal:latest \
+ ccr.ccs.tencentyun.com/nexent-hub/nexent-ubuntu-terminal:amd64 \
+ ccr.ccs.tencentyun.com/nexent-hub/nexent-ubuntu-terminal:arm64
+ docker manifest push ccr.ccs.tencentyun.com/nexent-hub/nexent-ubuntu-terminal:latest
\ No newline at end of file
diff --git a/.github/workflows/docker-build-push-overseas.yml b/.github/workflows/docker-build-push-overseas.yml
index e1d6a0977..e648aad92 100644
--- a/.github/workflows/docker-build-push-overseas.yml
+++ b/.github/workflows/docker-build-push-overseas.yml
@@ -147,6 +147,46 @@ jobs:
- name: Push web image (arm64) to DockerHub
run: docker push nexent/nexent-web:arm64
+ build-and-push-terminal-amd64:
+ runs-on: ${{ fromJson(inputs.runner_label_json) }}
+ steps:
+ - name: Set up Docker Buildx
+ run: |
+ if ! docker buildx inspect nexent_builder > /dev/null 2>&1; then
+ docker buildx create --name nexent_builder --use
+ else
+ docker buildx use nexent_builder
+ fi
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build terminal image (amd64) and load locally
+ run: |
+ docker buildx build --platform linux/amd64 -t nexent/nexent-ubuntu-terminal:amd64 --load -f make/terminal/Dockerfile .
+ - name: Login to DockerHub
+ run: echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u nexent --password-stdin
+ - name: Push terminal image (amd64) to DockerHub
+ run: docker push nexent/nexent-ubuntu-terminal:amd64
+
+ build-and-push-terminal-arm64:
+ runs-on: ${{ fromJson(inputs.runner_label_json) }}
+ steps:
+ - name: Set up Docker Buildx
+ run: |
+ if ! docker buildx inspect nexent_builder > /dev/null 2>&1; then
+ docker buildx create --name nexent_builder --use
+ else
+ docker buildx use nexent_builder
+ fi
+ - name: Checkout code
+ uses: actions/checkout@v4
+ - name: Build terminal image (arm64) and load locally
+ run: |
+ docker buildx build --platform linux/arm64 -t nexent/nexent-ubuntu-terminal:arm64 --load -f make/terminal/Dockerfile .
+ - name: Login to DockerHub
+ run: echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u nexent --password-stdin
+ - name: Push terminal image (arm64) to DockerHub
+ run: docker push nexent/nexent-ubuntu-terminal:arm64
+
manifest-push-main:
runs-on: ubuntu-latest
needs:
@@ -190,4 +230,19 @@ jobs:
docker manifest create nexent/nexent-web:latest \
nexent/nexent-web:amd64 \
nexent/nexent-web:arm64
- docker manifest push nexent/nexent-web:latest
\ No newline at end of file
+ docker manifest push nexent/nexent-web:latest
+
+ manifest-push-terminal:
+ runs-on: ubuntu-latest
+ needs:
+ - build-and-push-terminal-amd64
+ - build-and-push-terminal-arm64
+ steps:
+ - name: Login to DockerHub
+ run: echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u nexent --password-stdin
+ - name: Create and push manifest for terminal (DockerHub)
+ run: |
+ docker manifest create nexent/nexent-ubuntu-terminal:latest \
+ nexent/nexent-ubuntu-terminal:amd64 \
+ nexent/nexent-ubuntu-terminal:arm64
+ docker manifest push nexent/nexent-ubuntu-terminal:latest
\ No newline at end of file
diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
index ed36999fe..82f4ddd24 100644
--- a/.github/workflows/docker-build.yml
+++ b/.github/workflows/docker-build.yml
@@ -41,4 +41,13 @@ jobs:
uses: actions/checkout@v4
- name: Build web frontend image
- run: docker build --build-arg MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple --progress=plain -t nexent/nexent-web -f make/web/Dockerfile .
\ No newline at end of file
+ run: docker build --build-arg MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple --progress=plain -t nexent/nexent-web -f make/web/Dockerfile .
+
+ build-terminal:
+ runs-on: ${{ fromJson(inputs.runner_label_json) }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Build terminal image
+ run: docker build --progress=plain -t nexent/nexent-ubuntu-terminal -f make/terminal/Dockerfile .
\ No newline at end of file
diff --git a/MAINTENANCE.md b/MAINTENANCE.md
index 868c3459a..74609e1a1 100644
--- a/MAINTENANCE.md
+++ b/MAINTENANCE.md
@@ -23,6 +23,13 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
+
+ # 流式响应支持
+ proxy_buffering off;
+ proxy_cache off;
+ proxy_set_header Connection '';
+ proxy_http_version 1.1;
+ chunked_transfer_encoding off;
}
}
@@ -37,6 +44,13 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
+
+ # 流式响应支持
+ proxy_buffering off;
+ proxy_cache off;
+ proxy_set_header Connection '';
+ proxy_http_version 1.1;
+ chunked_transfer_encoding off;
}
}
```
diff --git a/backend/agents/create_agent_info.py b/backend/agents/create_agent_info.py
index 94a6235fd..1045338eb 100644
--- a/backend/agents/create_agent_info.py
+++ b/backend/agents/create_agent_info.py
@@ -16,6 +16,7 @@
from smolagents.utils import BASE_BUILTIN_MODULES
from services.memory_config_service import build_memory_context
from jinja2 import Template, StrictUndefined
+from datetime import datetime
from nexent.memory.memory_service import search_memory_in_levels
@@ -128,7 +129,8 @@ async def create_agent_config(agent_id, tenant_id, user_id, language: str = 'zh'
"APP_NAME": app_name,
"APP_DESCRIPTION": app_description,
"memory_list": memory_list,
- "knowledge_base_summary": knowledge_base_summary
+ "knowledge_base_summary": knowledge_base_summary,
+ "time" : datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
else:
system_prompt = agent_info.get("prompt", "")
diff --git a/backend/apps/agent_app.py b/backend/apps/agent_app.py
index 77a09bb30..8159ccd1a 100644
--- a/backend/apps/agent_app.py
+++ b/backend/apps/agent_app.py
@@ -2,22 +2,13 @@
from typing import Optional
from fastapi import HTTPException, APIRouter, Header, Request, Body
-from fastapi.responses import StreamingResponse, JSONResponse
-from nexent.core.agents.run_agent import agent_run
-
-from database.agent_db import delete_related_agent
-from utils.auth_utils import get_current_user_info, get_current_user_id
-from agents.create_agent_info import create_agent_run_info
+from fastapi.responses import JSONResponse
from consts.model import AgentRequest, AgentInfoRequest, AgentIDRequest, ConversationResponse, AgentImportRequest
from services.agent_service import get_agent_info_impl, \
get_creating_sub_agent_info_impl, update_agent_info_impl, delete_agent_impl, export_agent_impl, import_agent_impl, \
- list_all_agent_info_impl, insert_related_agent_impl
-from services.conversation_management_service import save_conversation_user, save_conversation_assistant
-from services.memory_config_service import build_memory_context
-from utils.config_utils import config_manager
-from utils.thread_utils import submit
-from agents.agent_run_manager import agent_run_manager
-from agents.preprocess_manager import preprocess_manager
+ list_all_agent_info_impl, insert_related_agent_impl, run_agent_stream, stop_agent_tasks
+from database.agent_db import delete_related_agent
+from utils.auth_utils import get_current_user_info, get_current_user_id
router = APIRouter(prefix="/agent")
@@ -30,43 +21,10 @@ async def agent_run_api(agent_request: AgentRequest, http_request: Request, auth
"""
Agent execution API endpoint
"""
- user_id, tenant_id, language = get_current_user_info(authorization, http_request)
- memory_context = build_memory_context(user_id, tenant_id, agent_request.agent_id)
-
- agent_run_info = await create_agent_run_info(agent_id=agent_request.agent_id,
- minio_files=agent_request.minio_files,
- query=agent_request.query,
- history=agent_request.history,
- authorization=authorization,
- language=language)
-
- agent_run_manager.register_agent_run(agent_request.conversation_id, agent_run_info)
- # Save user message only if not in debug mode
- if not agent_request.is_debug:
- submit(save_conversation_user, agent_request, authorization)
-
- async def generate():
- messages = []
- try:
- async for chunk in agent_run(agent_run_info, memory_context):
- messages.append(chunk)
- yield f"data: {chunk}\n\n"
- except Exception as e:
- raise HTTPException(status_code=500, detail=f"Agent run error: {str(e)}")
- finally:
- # Save assistant message only if not in debug mode
- if not agent_request.is_debug:
- submit(save_conversation_assistant, agent_request, messages, authorization)
- # Unregister agent run instance for both debug and non-debug modes
- agent_run_manager.unregister_agent_run(agent_request.conversation_id)
-
- return StreamingResponse(
- generate(),
- media_type="text/event-stream",
- headers={
- "Cache-Control": "no-cache",
- "Connection": "keep-alive"
- }
+ return await run_agent_stream(
+ agent_request=agent_request,
+ http_request=http_request,
+ authorization=authorization
)
@@ -75,21 +33,8 @@ async def agent_stop_api(conversation_id: int):
"""
stop agent run and preprocess tasks for specified conversation_id
"""
- # Stop agent run
- agent_stopped = agent_run_manager.stop_agent_run(conversation_id)
-
- # Stop preprocess tasks
- preprocess_stopped = preprocess_manager.stop_preprocess_tasks(conversation_id)
-
- if agent_stopped or preprocess_stopped:
- message_parts = []
- if agent_stopped:
- message_parts.append("agent run")
- if preprocess_stopped:
- message_parts.append("preprocess tasks")
-
- message = f"successfully stopped {' and '.join(message_parts)} for conversation_id {conversation_id}"
- return {"status": "success", "message": message}
+ if stop_agent_tasks(conversation_id).get("status") == "success":
+ return {"status": "success", "message": "agent run and preprocess tasks stopped successfully"}
else:
raise HTTPException(status_code=404, detail=f"no running agent or preprocess tasks found for conversation_id {conversation_id}")
diff --git a/backend/apps/conversation_management_app.py b/backend/apps/conversation_management_app.py
index c1816d539..2a1ca6181 100644
--- a/backend/apps/conversation_management_app.py
+++ b/backend/apps/conversation_management_app.py
@@ -2,8 +2,6 @@
from typing import Dict, Any, Optional
from fastapi import HTTPException, APIRouter, Header, Request
-from fastapi.encoders import jsonable_encoder
-from pydantic import BaseModel
from consts.model import ConversationResponse, ConversationRequest, RenameRequest, GenerateTitleRequest, OpinionRequest, MessageIdRequest
from services.conversation_management_service import (
diff --git a/backend/apps/mock_user_management_app.py b/backend/apps/mock_user_management_app.py
index 2c337d303..26a6f58f9 100644
--- a/backend/apps/mock_user_management_app.py
+++ b/backend/apps/mock_user_management_app.py
@@ -19,8 +19,8 @@
MOCK_SESSION = {
"access_token": "mock_access_token",
"refresh_token": "mock_refresh_token",
- "expires_at": int((datetime.now() + timedelta(hours=1)).timestamp()),
- "expires_in_seconds": 3600
+ "expires_at": int((datetime.now() + timedelta(days=3650)).timestamp()),
+ "expires_in_seconds": 315360000
}
@@ -74,7 +74,7 @@ async def signin(request: UserSignInRequest):
# Return mock success response
return ServiceResponse(
code=STATUS_CODES["SUCCESS"],
- message="Login successful, session validity is 3600 seconds",
+ message="Login successful, session validity is 10 years",
data={
"user": {
"id": MOCK_USER["id"],
@@ -98,17 +98,19 @@ async def refresh_token(request: Request):
"""
logger.info("Mock refresh token request")
- # Return mock success response with new tokens
- new_expires_at = int((datetime.now() + timedelta(hours=1)).timestamp())
+ # In speed/mock mode, extend for a very long time (10 years)
+ new_expires_at = int((datetime.now() + timedelta(days=3650)).timestamp())
return ServiceResponse(
code=STATUS_CODES["SUCCESS"],
message="Token refreshed successfully",
data={
- "access_token": f"mock_access_token_{new_expires_at}",
- "refresh_token": f"mock_refresh_token_{new_expires_at}",
- "expires_at": new_expires_at,
- "expires_in_seconds": 3600
+ "session": {
+ "access_token": f"mock_access_token_{new_expires_at}",
+ "refresh_token": f"mock_refresh_token_{new_expires_at}",
+ "expires_at": new_expires_at,
+ "expires_in_seconds": 315360000
+ }
}
)
diff --git a/backend/apps/model_managment_app.py b/backend/apps/model_managment_app.py
index cf639ed13..32e6455f0 100644
--- a/backend/apps/model_managment_app.py
+++ b/backend/apps/model_managment_app.py
@@ -329,57 +329,6 @@ async def check_model_healthcheck(
return await check_model_connectivity(display_name, authorization)
-
-@router.post("/update_connect_status", response_model=ModelResponse)
-async def update_model_connect_status(
- model_name: str = Body(..., embed=True),
- connect_status: str = Body(..., embed=True),
- authorization: Optional[str] = Header(None)
-):
- """
- Update model connection status
-
- Args:
- model_name: Model name, including repository info, e.g. openai/gpt-3.5-turbo
- connect_status: New connection status
- authorization: Authorization header
- """
- try:
- user_id, tenant_id = get_current_user_id(authorization)
- # Split model_name
- repo, name = split_repo_name(model_name)
- # Ensure repo is empty string instead of null
- repo = repo if repo else ""
-
- # Query model information
- model = get_model_by_name(name, repo)
- if not model:
- return ModelResponse(
- code=404,
- message=f"Model not found: {model_name}",
- data={"connect_status": ""}
- )
-
- # Update connection status
- update_data = {"connect_status": connect_status}
- update_model_record(model["model_id"], update_data, user_id)
-
- return ModelResponse(
- code=200,
- message=f"Successfully updated connection status for model {model_name}",
- data={
- "model_name": model_name,
- "connect_status": connect_status
- }
- )
- except Exception as e:
- return ModelResponse(
- code=500,
- message=f"Failed to update model connection status: {str(e)}",
- data={"connect_status": ModelConnectStatusEnum.NOT_DETECTED.value}
- )
-
-
@router.post("/verify_config", response_model=ModelResponse)
async def verify_model_config(request: ModelRequest):
"""
diff --git a/backend/consts/model.py b/backend/consts/model.py
index 3725688c0..ecc929ba9 100644
--- a/backend/consts/model.py
+++ b/backend/consts/model.py
@@ -8,10 +8,10 @@
class ModelConnectStatusEnum(Enum):
"""Enum class for model connection status"""
- NOT_DETECTED = "未检测"
- DETECTING = "检测中"
- AVAILABLE = "可用"
- UNAVAILABLE = "不可用"
+ NOT_DETECTED = "not_detected"
+ DETECTING = "detecting"
+ AVAILABLE = "available"
+ UNAVAILABLE = "unavailable"
@classmethod
def get_default(cls) -> str:
@@ -317,6 +317,7 @@ class MessageIdRequest(BaseModel):
class ExportAndImportAgentInfo(BaseModel):
agent_id: int
name: str
+ display_name: Optional[str] = None
description: str
business_description: str
model_name: str
@@ -329,9 +330,19 @@ class ExportAndImportAgentInfo(BaseModel):
tools: List[ToolConfig]
managed_agents: List[int]
+ class Config:
+ arbitrary_types_allowed = True
+
+
+class MCPInfo(BaseModel):
+ mcp_server_name: str
+ mcp_url: str
+
+
class ExportAndImportDataFormat(BaseModel):
agent_id: int
agent_info: Dict[str, ExportAndImportAgentInfo]
+ mcp_info: List[MCPInfo]
class AgentImportRequest(BaseModel):
diff --git a/backend/data_process_service.py b/backend/data_process_service.py
index cd44ad398..49cd06deb 100644
--- a/backend/data_process_service.py
+++ b/backend/data_process_service.py
@@ -776,9 +776,6 @@ async def lifespan(app: FastAPI):
# Startup
logger.info("Starting data processing service...")
- # Services should already be started by main()
- logger.info("Data processing service started successfully")
-
yield
# Shutdown
@@ -827,7 +824,7 @@ def main():
# Create and start FastAPI app
app = create_app()
- logger.debug(f"🌐 Starting API server on {args.api_host}:{args.api_port}")
+ logger.info(f"🌐 Starting API server on {args.api_host}:{args.api_port}")
uvicorn.run(
app,
host=args.api_host,
diff --git a/backend/database/agent_db.py b/backend/database/agent_db.py
index 26d055e73..95ee400e1 100644
--- a/backend/database/agent_db.py
+++ b/backend/database/agent_db.py
@@ -25,6 +25,20 @@ def search_agent_info_by_agent_id(agent_id: int, tenant_id: str):
return agent_dict
+def search_agent_id_by_agent_name(agent_name: str, tenant_id: str):
+ """
+ Search agent id by agent name
+ """
+ with get_db_session() as session:
+ agent = session.query(AgentInfo).filter(
+ AgentInfo.name == agent_name,
+ AgentInfo.tenant_id == tenant_id,
+ AgentInfo.delete_flag != 'Y').first()
+ if not agent:
+ raise ValueError("agent not found")
+ return agent.agent_id
+
+
def search_blank_sub_agent_by_main_agent_id(tenant_id: str):
"""
Search blank sub agent by main agent id
diff --git a/backend/database/remote_mcp_db.py b/backend/database/remote_mcp_db.py
index 4deeac471..cc5834e81 100644
--- a/backend/database/remote_mcp_db.py
+++ b/backend/database/remote_mcp_db.py
@@ -96,4 +96,45 @@ def get_mcp_records_by_tenant(tenant_id: str) -> List[Dict[str, Any]]:
).order_by(McpRecord.create_time.desc()).all()
- return [as_dict(record) for record in mcp_records]
\ No newline at end of file
+ return [as_dict(record) for record in mcp_records]
+
+def get_mcp_server_by_name_and_tenant(mcp_name: str, tenant_id: str) -> str:
+ """
+ Get MCP server address by name and tenant ID
+
+ :param mcp_name: MCP name
+ :param tenant_id: Tenant ID
+ :return: MCP server address, empty string if not found
+ """
+ with get_db_session() as session:
+ try:
+ mcp_record = session.query(McpRecord).filter(
+ McpRecord.mcp_name == mcp_name,
+ McpRecord.tenant_id == tenant_id,
+ McpRecord.delete_flag != 'Y'
+ ).first()
+
+ return mcp_record.mcp_server if mcp_record else ""
+ except SQLAlchemyError:
+ return ""
+
+
+def check_mcp_name_exists(mcp_name: str, tenant_id: str) -> bool:
+ """
+ Check if MCP name already exists for a tenant
+
+ :param mcp_name: MCP name
+ :param tenant_id: Tenant ID
+ :return: True if name exists, False otherwise
+ """
+ with get_db_session() as session:
+ try:
+ mcp_record = session.query(McpRecord).filter(
+ McpRecord.mcp_name == mcp_name,
+ McpRecord.tenant_id == tenant_id,
+ McpRecord.delete_flag != 'Y'
+ ).first()
+
+ return mcp_record is not None
+ except SQLAlchemyError:
+ return False
\ No newline at end of file
diff --git a/backend/prompts/managed_system_prompt_template.yaml b/backend/prompts/managed_system_prompt_template.yaml
index 318232d6f..63d689474 100644
--- a/backend/prompts/managed_system_prompt_template.yaml
+++ b/backend/prompts/managed_system_prompt_template.yaml
@@ -1,6 +1,6 @@
system_prompt: |-
### 基本信息 ###
- 你是{{APP_NAME}},{{APP_DESCRIPTION}}
+ 你是{{APP_NAME}},{{APP_DESCRIPTION}},现在是{{time|default('当前时间')}}
{%- if memory_list and memory_list|length > 0 %}
### 上下文记忆 ###
diff --git a/backend/prompts/managed_system_prompt_template_en.yaml b/backend/prompts/managed_system_prompt_template_en.yaml
index 9f0ad131e..3aa2685ee 100644
--- a/backend/prompts/managed_system_prompt_template_en.yaml
+++ b/backend/prompts/managed_system_prompt_template_en.yaml
@@ -1,6 +1,6 @@
system_prompt: |-
### Basic Information ###
- You are {{APP_NAME}}, {{APP_DESCRIPTION}}
+ You are {{APP_NAME}}, {{APP_DESCRIPTION}}, it is {{time|default('current time')}} now
{%- if memory_list and memory_list|length > 0 %}
### Contextual Memory ###
diff --git a/backend/prompts/manager_system_prompt_template.yaml b/backend/prompts/manager_system_prompt_template.yaml
index 1e0593720..9ec5541b1 100644
--- a/backend/prompts/manager_system_prompt_template.yaml
+++ b/backend/prompts/manager_system_prompt_template.yaml
@@ -1,6 +1,6 @@
system_prompt: |-
### 基本信息 ###
- 你是{{APP_NAME}},{{APP_DESCRIPTION}}
+ 你是{{APP_NAME}},{{APP_DESCRIPTION}}, 现在是{{time|default('当前时间')}}
{%- if memory_list and memory_list|length > 0 %}
### 上下文记忆 ###
diff --git a/backend/prompts/manager_system_prompt_template_en.yaml b/backend/prompts/manager_system_prompt_template_en.yaml
index f72dc04f7..3fccb899c 100644
--- a/backend/prompts/manager_system_prompt_template_en.yaml
+++ b/backend/prompts/manager_system_prompt_template_en.yaml
@@ -1,6 +1,6 @@
system_prompt: |-
### Basic Information ###
- You are {{APP_NAME}}, {{APP_DESCRIPTION}}
+ You are {{APP_NAME}}, {{APP_DESCRIPTION}}, it is {{time|default('current time')}} now
{%- if memory_list and memory_list|length > 0 %}
### Contextual Memory ###
diff --git a/backend/services/agent_service.py b/backend/services/agent_service.py
index 0e60f72da..5021cdf0a 100644
--- a/backend/services/agent_service.py
+++ b/backend/services/agent_service.py
@@ -3,19 +3,30 @@
import logging
from collections import deque
-from fastapi import Header
-from fastapi.responses import JSONResponse
+from fastapi import Header, Request, HTTPException
+from fastapi.responses import JSONResponse, StreamingResponse
+from consts.model import AgentRequest
from agents.create_agent_info import create_tool_config_list
-from consts.model import AgentInfoRequest, ExportAndImportAgentInfo, ExportAndImportDataFormat, ToolInstanceInfoRequest
+from consts.model import AgentInfoRequest, ExportAndImportAgentInfo, ExportAndImportDataFormat, ToolInstanceInfoRequest, MCPInfo
from database.agent_db import create_agent, query_all_enabled_tool_instances, \
search_blank_sub_agent_by_main_agent_id, \
search_tools_for_sub_agent, search_agent_info_by_agent_id, update_agent, delete_agent_by_id, query_all_tools, \
create_or_update_tool_by_tool_info, check_tool_is_available, query_all_agent_info_by_tenant_id, \
- query_sub_agents_id_list, insert_related_agent, delete_all_related_agent
+ query_sub_agents_id_list, insert_related_agent, delete_all_related_agent, search_agent_id_by_agent_name
+from database.remote_mcp_db import get_mcp_server_by_name_and_tenant, check_mcp_name_exists
+from services.remote_mcp_service import add_remote_mcp_server_list
+from services.tool_configuration_service import update_tool_list
+from services.conversation_management_service import save_conversation_user, save_conversation_assistant
-from utils.auth_utils import get_current_user_id
+from utils.auth_utils import get_current_user_info
from utils.memory_utils import build_memory_config
+from utils.thread_utils import submit
from nexent.memory.memory_service import clear_memory
+from nexent.core.agents.run_agent import agent_run
+from services.memory_config_service import build_memory_context
+from agents.create_agent_info import create_agent_run_info
+from agents.agent_run_manager import agent_run_manager
+from agents.preprocess_manager import preprocess_manager
logger = logging.getLogger("agent_service")
@@ -66,7 +77,7 @@ def get_agent_info_impl(agent_id: int, tenant_id: str):
def get_creating_sub_agent_info_impl(authorization: str = Header(None)):
- user_id, tenant_id = get_current_user_id(authorization)
+ user_id, tenant_id, _ = get_current_user_info(authorization)
try:
sub_agent_id = get_creating_sub_agent_id_service(tenant_id, user_id)
@@ -97,7 +108,7 @@ def get_creating_sub_agent_info_impl(authorization: str = Header(None)):
"sub_agent_id_list": query_sub_agents_id_list(main_agent_id=sub_agent_id, tenant_id=tenant_id)}
def update_agent_info_impl(request: AgentInfoRequest, authorization: str = Header(None)):
- user_id, tenant_id = get_current_user_id(authorization)
+ user_id, tenant_id, _ = get_current_user_info(authorization)
try:
update_agent(request.agent_id, request, tenant_id, user_id)
@@ -106,7 +117,7 @@ def update_agent_info_impl(request: AgentInfoRequest, authorization: str = Heade
raise ValueError(f"Failed to update agent info: {str(e)}")
async def delete_agent_impl(agent_id: int, authorization: str = Header(None)):
- user_id, tenant_id = get_current_user_id(authorization)
+ user_id, tenant_id, _ = get_current_user_info(authorization)
try:
delete_agent_by_id(agent_id, tenant_id, user_id)
@@ -180,12 +191,14 @@ async def export_agent_impl(agent_id: int, authorization: str = Header(None)) ->
This function recursively finds all managed sub-agents and exports the detailed configuration of each agent (including tools, prompts, etc.) as a dictionary, and finally returns it as a formatted JSON string for frontend download and backup.
"""
- user_id, tenant_id = get_current_user_id(authorization)
+ user_id, tenant_id, _ = get_current_user_info(authorization)
export_agent_dict = {}
search_list = deque([agent_id])
agent_id_set = set()
+ mcp_info_set = set()
+
while len(search_list):
left_ele = search_list.popleft()
if left_ele in agent_id_set:
@@ -193,10 +206,23 @@ async def export_agent_impl(agent_id: int, authorization: str = Header(None)) ->
agent_id_set.add(left_ele)
agent_info = await export_agent_by_agent_id(agent_id=left_ele, tenant_id=tenant_id, user_id=user_id)
+
+ # collect mcp name
+ for tool in agent_info.tools:
+ if tool.source == "mcp" and tool.usage:
+ mcp_info_set.add(tool.usage)
+
search_list.extend(agent_info.managed_agents)
export_agent_dict[str(agent_info.agent_id)] = agent_info
- export_data = ExportAndImportDataFormat(agent_id=agent_id, agent_info=export_agent_dict)
+ # convert mcp info to MCPInfo list
+ mcp_info_list = []
+ for mcp_server_name in mcp_info_set:
+ # get mcp url by mcp_server_name and tenant_id
+ mcp_url = get_mcp_server_by_name_and_tenant(mcp_server_name, tenant_id)
+ mcp_info_list.append(MCPInfo(mcp_server_name=mcp_server_name, mcp_url=mcp_url))
+
+ export_data = ExportAndImportDataFormat(agent_id=agent_id, agent_info=export_agent_dict, mcp_info=mcp_info_list)
return export_data.model_dump()
async def export_agent_by_agent_id(agent_id: int, tenant_id: str, user_id: str)->ExportAndImportAgentInfo:
@@ -214,6 +240,7 @@ async def export_agent_by_agent_id(agent_id: int, tenant_id: str, user_id: str)-
agent_info = ExportAndImportAgentInfo(agent_id=agent_id,
name=agent_info["name"],
+ display_name=agent_info["display_name"],
description=agent_info["description"],
business_description=agent_info["business_description"],
model_name=agent_info["model_name"],
@@ -232,9 +259,49 @@ async def import_agent_impl(agent_info: ExportAndImportDataFormat, authorization
"""
Import agent using DFS
"""
- user_id, tenant_id = get_current_user_id(authorization)
+ user_id, tenant_id, _ = get_current_user_info(authorization)
agent_id = agent_info.agent_id
+ # First, add MCP servers if any
+ if agent_info.mcp_info:
+ for mcp_info in agent_info.mcp_info:
+ if mcp_info.mcp_server_name and mcp_info.mcp_url:
+ try:
+ # Check if MCP name already exists
+ if check_mcp_name_exists(mcp_name=mcp_info.mcp_server_name, tenant_id=tenant_id):
+ # Get existing MCP server info to compare URLs
+ existing_mcp = get_mcp_server_by_name_and_tenant(mcp_name=mcp_info.mcp_server_name, tenant_id=tenant_id)
+ if existing_mcp and existing_mcp == mcp_info.mcp_url:
+ # Same name and URL, skip
+ logger.info(f"MCP server {mcp_info.mcp_server_name} with same URL already exists, skipping")
+ continue
+ else:
+ # Same name but different URL, add import prefix
+ import_mcp_name = f"import_{mcp_info.mcp_server_name}"
+ logger.info(f"MCP server {mcp_info.mcp_server_name} exists with different URL, using name: {import_mcp_name}")
+ mcp_server_name = import_mcp_name
+ else:
+ # Name doesn't exist, use original name
+ mcp_server_name = mcp_info.mcp_server_name
+
+ result = await add_remote_mcp_server_list(
+ tenant_id=tenant_id,
+ user_id=user_id,
+ remote_mcp_server=mcp_info.mcp_url,
+ remote_mcp_server_name=mcp_server_name
+ )
+ # Check if the result is a JSONResponse with error status
+ if hasattr(result, 'status_code') and result.status_code != 200:
+ raise Exception(f"Failed to add MCP server {mcp_server_name}: {result.body.decode() if hasattr(result, 'body') else 'Unknown error'}")
+ except Exception as e:
+ raise Exception(f"Failed to add MCP server {mcp_info.mcp_server_name}: {str(e)}")
+
+ # Then, update tool list to include new MCP tools
+ try:
+ await update_tool_list(tenant_id=tenant_id, user_id=user_id)
+ except Exception as e:
+ raise Exception(f"Failed to update tool list: {str(e)}")
+
agent_stack = deque([agent_id])
agent_id_set = set()
mapping_agent_id = {}
@@ -298,6 +365,7 @@ async def import_agent_by_agent_id(import_agent_info: ExportAndImportAgentInfo,
raise ValueError(f"Invalid agent name: {import_agent_info.name}. agent name must be a valid python variable name.")
# create a new agent
new_agent = create_agent(agent_info={"name": import_agent_info.name,
+ "display_name": import_agent_info.display_name,
"description": import_agent_info.description,
"business_description": import_agent_info.business_description,
"model_name": import_agent_info.model_name,
@@ -399,4 +467,121 @@ def insert_related_agent_impl(parent_agent_id, child_agent_id, tenant_id):
return JSONResponse(
status_code=400,
content={"message":"Failed to insert relation", "status": "error"}
- )
\ No newline at end of file
+ )
+
+
+# Helper function for run_agent_stream, used to prepare context for an agent run
+async def prepare_agent_run(agent_request: AgentRequest, http_request: Request, authorization: str):
+ """
+ Prepare for an agent run by creating context and run info, and registering the run.
+ """
+ user_id, tenant_id, language = get_current_user_info(authorization, http_request)
+
+ memory_context = build_memory_context(user_id, tenant_id, agent_request.agent_id)
+ agent_run_info = await create_agent_run_info(agent_id=agent_request.agent_id,
+ minio_files=agent_request.minio_files,
+ query=agent_request.query,
+ history=agent_request.history,
+ authorization=authorization,
+ language=language)
+ agent_run_manager.register_agent_run(agent_request.conversation_id, agent_run_info)
+ return agent_run_info, memory_context
+
+
+# Helper function for run_agent_stream, used to save messages for either user or assistant
+def save_messages(agent_request, target:str, messages=None, authorization=None):
+ if target == "user":
+ if messages is not None:
+ raise ValueError("Messages should be None when saving for user.")
+ submit(save_conversation_user, agent_request, authorization)
+ elif target == "assistant":
+ if messages is None:
+ raise ValueError("Messages cannot be None when saving for assistant.")
+ submit(save_conversation_assistant, agent_request, messages, authorization)
+
+
+# Helper function for run_agent_stream, used to generate stream response
+async def generate_stream(agent_run_info, memory_context, agent_request: AgentRequest, authorization: str):
+ messages = []
+ try:
+ async for chunk in agent_run(agent_run_info, memory_context):
+ messages.append(chunk)
+ yield f"data: {chunk}\n\n"
+ except Exception as e:
+ logger.error(f"Agent run error: {str(e)}")
+ raise HTTPException(status_code=500, detail=f"Agent run error: {str(e)}")
+ finally:
+ # Save assistant message only if not in debug mode
+ if not agent_request.is_debug:
+ save_messages(agent_request, target="assistant", messages=messages, authorization=authorization)
+ # Unregister agent run instance for both debug and non-debug modes
+ agent_run_manager.unregister_agent_run(agent_request.conversation_id)
+
+
+async def run_agent_stream(agent_request: AgentRequest, http_request: Request, authorization: str):
+ """
+ Start an agent run and stream responses, using explicit user/tenant context.
+ Mirrors the logic of agent_app.agent_run_api but reusable by services.
+ """
+ agent_run_info, memory_context = await prepare_agent_run(
+ agent_request=agent_request,
+ http_request=http_request,
+ authorization=authorization
+ )
+
+ # Save user message only if not in debug mode
+ if not agent_request.is_debug:
+ save_messages(
+ agent_request,
+ target="user",
+ authorization=authorization
+ )
+
+ return StreamingResponse(
+ generate_stream(agent_run_info, memory_context, agent_request, authorization),
+ media_type="text/event-stream",
+ headers={
+ "Cache-Control": "no-cache",
+ "Connection": "keep-alive"
+ }
+ )
+
+
+def stop_agent_tasks(conversation_id: int):
+ """
+ Stop agent run and preprocess tasks for the specified conversation_id.
+ Matches the behavior of agent_app.agent_stop_api.
+ """
+ # Stop agent run
+ agent_stopped = agent_run_manager.stop_agent_run(conversation_id)
+
+ # Stop preprocess tasks
+ preprocess_stopped = preprocess_manager.stop_preprocess_tasks(conversation_id)
+
+ if agent_stopped or preprocess_stopped:
+ message_parts = []
+ if agent_stopped:
+ message_parts.append("agent run")
+ if preprocess_stopped:
+ message_parts.append("preprocess tasks")
+
+ message = f"successfully stopped {' and '.join(message_parts)} for conversation_id {conversation_id}"
+ logging.info(message)
+ return {"status": "success", "message": message}
+ else:
+ message = f"no running agent or preprocess tasks found for conversation_id {conversation_id}"
+ logging.error(message)
+ return {"status": "error", "message": message}
+
+
+def get_agent_id_by_name(agent_name: str, tenant_id: str) -> int:
+ """
+ Resolve unique agent id by its unique name under the same tenant.
+ """
+ if not agent_name:
+ raise HTTPException(status_code=400, detail="agent_name required")
+ try:
+ return search_agent_id_by_agent_name(agent_name, tenant_id)
+ except Exception as _:
+ logger.error(f"Failed to find agent id with '{agent_name}' in tenant {tenant_id}")
+ raise HTTPException(status_code=404, detail="agent not found")
diff --git a/backend/services/remote_mcp_service.py b/backend/services/remote_mcp_service.py
index ef603957d..0d02d17bc 100644
--- a/backend/services/remote_mcp_service.py
+++ b/backend/services/remote_mcp_service.py
@@ -1,7 +1,7 @@
import logging
from fastapi.responses import JSONResponse
-from database.remote_mcp_db import create_mcp_record, delete_mcp_record_by_name_and_url, get_mcp_records_by_tenant
+from database.remote_mcp_db import create_mcp_record, delete_mcp_record_by_name_and_url, get_mcp_records_by_tenant, check_mcp_name_exists
from fastmcp import Client
logger = logging.getLogger("remote_mcp_service")
@@ -34,6 +34,15 @@ async def add_remote_mcp_server_list(tenant_id: str,
remote_mcp_server: str,
remote_mcp_server_name: str):
+ # check if MCP name already exists
+ if check_mcp_name_exists(mcp_name=remote_mcp_server_name, tenant_id=tenant_id):
+ logger.error(
+ f"MCP name already exists, tenant_id: {tenant_id}, remote_mcp_server_name: {remote_mcp_server_name}")
+ return JSONResponse(
+ status_code=400,
+ content={"message": f"MCP server name '{remote_mcp_server_name}' already exists", "status": "error"}
+ )
+
# check if the address is available
response = await mcp_server_health(remote_mcp_server=remote_mcp_server)
if response.status_code != 200:
diff --git a/backend/utils/auth_utils.py b/backend/utils/auth_utils.py
index 29722c4d4..ccb71e8db 100644
--- a/backend/utils/auth_utils.py
+++ b/backend/utils/auth_utils.py
@@ -35,6 +35,10 @@ def get_jwt_expiry_seconds(token: str) -> int:
int: 令牌的有效期(秒),如果解析失败则返回默认值3600
"""
try:
+ # Speed mode: treat sessions as never expiring
+ if IS_SPEED_MODE:
+ # 10 years in seconds
+ return 10 * 365 * 24 * 60 * 60
# 确保token是纯JWT,去除可能的Bearer前缀
jwt_token = token.replace("Bearer ", "") if token.startswith("Bearer ") else token
@@ -68,6 +72,10 @@ def calculate_expires_at(token: Optional[str] = None) -> int:
Returns:
int: 过期时间的时间戳
"""
+ # Speed mode: far future expiration
+ if IS_SPEED_MODE:
+ return int((datetime.now() + timedelta(days=3650)).timestamp())
+
expiry_seconds = get_jwt_expiry_seconds(token) if token else 3600
return int((datetime.now() + timedelta(seconds=expiry_seconds)).timestamp())
diff --git a/backend/utils/logging_utils.py b/backend/utils/logging_utils.py
index 22fd60012..209679419 100644
--- a/backend/utils/logging_utils.py
+++ b/backend/utils/logging_utils.py
@@ -28,10 +28,6 @@ def configure_logging(level=logging.INFO):
root_logger.addHandler(handler)
root_logger.setLevel(level)
- # --- Silence overly verbose third-party libraries ----------------------
- for name in ("mem0", "mem0.memory", "mem0.memory.main"):
- logging.getLogger(name).setLevel(logging.WARNING)
-
def configure_elasticsearch_logging():
"""Configure logging for Elasticsearch client to reduce verbosity"""
diff --git a/doc/docs/assets/architecture_en.png b/doc/docs/assets/architecture_en.png
new file mode 100644
index 000000000..eda19615e
Binary files /dev/null and b/doc/docs/assets/architecture_en.png differ
diff --git a/doc/docs/assets/architecture_zh.png b/doc/docs/assets/architecture_zh.png
new file mode 100644
index 000000000..61e5c875f
Binary files /dev/null and b/doc/docs/assets/architecture_zh.png differ
diff --git a/doc/docs/en/deployment/docker-build.md b/doc/docs/en/deployment/docker-build.md
index ea2f838e0..06b6c0bfd 100644
--- a/doc/docs/en/deployment/docker-build.md
+++ b/doc/docs/en/deployment/docker-build.md
@@ -19,6 +19,10 @@ docker buildx build --progress=plain --platform linux/amd64,linux/arm64 -t ccr.c
# 📚 build documentation for multiple architectures
docker buildx build --progress=plain --platform linux/amd64,linux/arm64 -t nexent/nexent-docs -f make/docs/Dockerfile . --push
docker buildx build --progress=plain --platform linux/amd64,linux/arm64 -t ccr.ccs.tencentyun.com/nexent-hub/nexent-docs -f make/docs/Dockerfile . --push
+
+# 💻 build Ubuntu Terminal for multiple architectures
+docker buildx build --progress=plain --platform linux/amd64,linux/arm64 -t nexent/nexent-terminal -f make/terminal/Dockerfile . --push
+docker buildx build --progress=plain --platform linux/amd64,linux/arm64 -t ccr.ccs.tencentyun.com/nexent-hub/nexent-terminal -f make/terminal/Dockerfile . --push
```
### 💻 Local Development Build
@@ -35,6 +39,9 @@ docker build --progress=plain -t nexent/nexent-web -f make/web/Dockerfile .
# 📚 Build documentation image (current architecture only)
docker build --progress=plain -t nexent/nexent-docs -f make/docs/Dockerfile .
+
+# 💻 Build OpenSSH Server image (current architecture only)
+docker build --progress=plain -t nexent/nexent-ubuntu-terminal -f make/terminal/Dockerfile .
```
### 🧹 Clean up Docker resources
@@ -66,6 +73,23 @@ docker builder prune -f && docker system prune -f
- Built from `make/docs/Dockerfile`
- Provides project documentation and API reference
+#### OpenSSH Server Image (nexent/nexent-ubuntu-terminal)
+- Ubuntu 24.04-based SSH server container
+- Built from `make/terminal/Dockerfile`
+- Pre-installed with Conda, Python, Git and other development tools
+- Supports SSH key authentication with username `linuxserver.io`
+- Provides complete development environment
+
+##### Pre-installed Tools and Features
+- **Python Environment**: Python 3 + pip + virtualenv
+- **Conda Management**: Miniconda3 environment management
+- **Development Tools**: Git, Vim, Nano, Curl, Wget
+- **Build Tools**: build-essential, Make
+- **SSH Service**: Port 2222, root login and password authentication disabled
+- **User Permissions**: `linuxserver.io` user has sudo privileges (no password required)
+- **Timezone Setting**: Asia/Shanghai
+- **Security Configuration**: SSH key authentication, 60-minute session timeout
+
### 🏷️ Tagging Strategy
Each image is pushed to two repositories:
@@ -77,6 +101,7 @@ All images include:
- `nexent/nexent-data-process` - Data processing service
- `nexent/nexent-web` - Next.js frontend application
- `nexent/nexent-docs` - Vitepress documentation site
+- `nexent/nexent-ubuntu-terminal` - OpenSSH development server container
## 📚 Documentation Image Standalone Deployment
diff --git a/doc/docs/en/getting-started/installation.md b/doc/docs/en/getting-started/installation.md
index bd6fcffc8..a26c8f2ba 100644
--- a/doc/docs/en/getting-started/installation.md
+++ b/doc/docs/en/getting-started/installation.md
@@ -107,7 +107,7 @@ The deployment includes the following components:
| MinIO API | 9000 | 9010 | Object storage API |
| MinIO Console | 9001 | 9011 | Storage management UI |
| Redis | 6379 | 6379 | Cache service |
-| SSH Server | 2222 | 2222 | Terminal tool access |
+| SSH Server | 22 | 2222 | Terminal tool access |
For complete port mapping details, see our [Dev Container Guide](../deployment/devcontainer.md#port-mapping).
diff --git a/doc/docs/en/getting-started/software-architecture.md b/doc/docs/en/getting-started/software-architecture.md
index 2a1429307..701d89319 100644
--- a/doc/docs/en/getting-started/software-architecture.md
+++ b/doc/docs/en/getting-started/software-architecture.md
@@ -1,7 +1,166 @@
# Software Architecture
-We will update the comprehensive software architecture diagram and documentation soon.
+Nexent adopts a modern distributed microservices architecture designed to provide high-performance, scalable AI agent platform. The entire system is based on containerized deployment, supporting cloud-native and enterprise-grade application scenarios.
+
+
+
+## 🏗️ Overall Architecture Design
+
+Nexent's software architecture follows layered design principles, structured into the following core layers from top to bottom:
+
+### 🌐 Frontend Layer
+- **Technology Stack**: Next.js + React + TypeScript
+- **Functions**: User interface, agent interaction, multimodal input processing
+- **Features**: Responsive design, real-time communication, internationalization support
+
+### 🔌 API Gateway Layer
+- **Core Service**: FastAPI high-performance web framework
+- **Responsibilities**: Request routing, authentication, API version management, load balancing
+- **Ports**: 5010 (main service), 5012 (data processing service)
+
+### 🧠 Business Logic Layer
+- **Agent Management**: Agent generation, execution, monitoring
+- **Conversation Management**: Multi-turn dialogue, context maintenance, history tracking
+- **Knowledge Base Management**: Document processing, vectorization, retrieval
+- **Model Management**: Multi-model support, health checks, load balancing
+
+### 📊 Data Layer
+Distributed data storage architecture with multiple specialized databases:
+
+#### 🗄️ Structured Data Storage
+- **PostgreSQL**: Primary database storing user information, agent configurations, conversation records
+- **Port**: 5434
+- **Features**: ACID transactions, relational data integrity
+
+#### 🔍 Search Engine
+- **Elasticsearch**: Vector database and full-text search engine
+- **Port**: 9210
+- **Functions**: Vector similarity search, hybrid search, large-scale optimization
+
+#### 💾 Cache Layer
+- **Redis**: High-performance in-memory database
+- **Port**: 6379
+- **Usage**: Session caching, temporary data, distributed locks
+
+#### 📁 Object Storage
+- **MinIO**: Distributed object storage service
+- **Port**: 9010
+- **Functions**: File storage, multimedia resource management, large file processing
+
+## 🔧 Core Service Architecture
+
+### 🤖 Agent Services
+```
+Agent framework based on SmolAgents, providing:
+├── Agent generation and configuration
+├── Tool calling and integration
+├── Reasoning and decision execution
+└── Lifecycle management
+```
+
+### 📈 Data Processing Services
+```
+Distributed data processing architecture:
+├── Real-time document processing (20+ format support)
+├── Batch data processing pipelines
+├── OCR and table structure extraction
+└── Vectorization and index construction
+```
+
+### 🌐 MCP Ecosystem
+```
+Model Context Protocol tool integration:
+├── Standardized tool interfaces
+├── Plugin architecture
+├── Third-party service integration
+└── Custom tool development
+```
+
+## 🚀 Distributed Architecture Features
+
+### ⚡ Asynchronous Processing Architecture
+- **Foundation Framework**: High-performance async processing based on asyncio
+- **Concurrency Control**: Thread-safe concurrent processing mechanisms
+- **Task Queue**: Celery + Ray distributed task execution
+- **Stream Processing**: Real-time data and response streaming
+
+### 🔄 Microservices Design
+```
+Service decomposition strategy:
+├── nexent (main service) - Agent core logic
+├── nexent-data-process (data processing) - Document processing pipeline
+├── nexent-mcp-service (MCP service) - Tool protocol service
+└── Optional services (SSH, monitoring, etc.)
+```
+
+### 🌍 Containerized Deployment
+```
+Docker Compose service orchestration:
+├── Application service containerization
+├── Database service isolation
+├── Network layer security configuration
+└── Volume mounting for data persistence
+```
+
+## 🔐 Security and Scalability
+
+### 🛡️ Security Architecture
+- **Authentication**: Multi-tenant support, user permission management
+- **Data Security**: End-to-end encryption, secure transmission protocols
+- **Network Security**: Inter-service secure communication, firewall configuration
+
+### 📈 Scalability Design
+- **Horizontal Scaling**: Independent microservice scaling, load balancing
+- **Vertical Scaling**: Resource pool management, intelligent scheduling
+- **Storage Scaling**: Distributed storage, data sharding
+
+### 🔧 Modular Architecture
+- **Loose Coupling Design**: Low inter-service dependencies, standardized interfaces
+- **Plugin Architecture**: Hot-swappable tools and models
+- **Configuration Management**: Environment isolation, dynamic configuration updates
+
+## 🔄 Data Flow Architecture
+
+### 📥 User Request Flow
+```
+User Input → Frontend Validation → API Gateway → Route Distribution → Business Service → Data Access → Database
+```
+
+### 🤖 Agent Execution Flow
+```
+User Message → Agent Creation → Tool Calling → Model Inference → Streaming Response → Result Storage
+```
+
+### 📚 Knowledge Base Processing Flow
+```
+File Upload → Temporary Storage → Data Processing → Vectorization → Knowledge Base Storage → Index Update
+```
+
+### ⚡ Real-time Processing Flow
+```
+Real-time Input → Instant Processing → Agent Response → Streaming Output
+```
+
+## 🎯 Architecture Advantages
+
+### 🏢 Enterprise-grade Features
+- **High Availability**: Multi-layer redundancy, failover capabilities
+- **High Performance**: Asynchronous processing, intelligent caching
+- **High Concurrency**: Distributed architecture, load balancing
+- **Monitoring Friendly**: Comprehensive logging and status monitoring
+
+### 🔧 Developer Friendly
+- **Modular Development**: Clear hierarchical structure
+- **Standardized Interfaces**: Unified API design
+- **Flexible Configuration**: Environment adaptation, feature toggles
+- **Easy Testing**: Unit testing and integration testing support
+
+### 🌱 Ecosystem Compatibility
+- **MCP Standard**: Compliant with Model Context Protocol
+- **Open Source Ecosystem**: Integration with rich open source tools
+- **Cloud Native**: Support for Kubernetes and Docker deployment
+- **Multi-model Support**: Compatible with mainstream AI model providers
---
-*This documentation is under active development. We will update it with comprehensive architecture details soon.*
\ No newline at end of file
+This architectural design ensures that Nexent can provide a stable, scalable AI agent service platform while maintaining high performance. Whether for individual users or enterprise-level deployments, it delivers excellent user experience and technical assurance.
\ No newline at end of file
diff --git a/doc/docs/en/user-guide/assets/memory/add-mem.png b/doc/docs/en/user-guide/assets/memory/add-mem.png
new file mode 100644
index 000000000..314baa403
Binary files /dev/null and b/doc/docs/en/user-guide/assets/memory/add-mem.png differ
diff --git a/doc/docs/en/user-guide/assets/memory/delete-mem.png b/doc/docs/en/user-guide/assets/memory/delete-mem.png
new file mode 100644
index 000000000..7d5c960e6
Binary files /dev/null and b/doc/docs/en/user-guide/assets/memory/delete-mem.png differ
diff --git a/doc/docs/en/user-guide/assets/memory/mem-config.png b/doc/docs/en/user-guide/assets/memory/mem-config.png
new file mode 100644
index 000000000..113ed4d83
Binary files /dev/null and b/doc/docs/en/user-guide/assets/memory/mem-config.png differ
diff --git a/doc/docs/en/user-guide/memory.md b/doc/docs/en/user-guide/memory.md
new file mode 100644
index 000000000..5d0ea53b7
--- /dev/null
+++ b/doc/docs/en/user-guide/memory.md
@@ -0,0 +1,85 @@
+# 🧠Nexent Intelligent Memory System Technical Specification
+
+## 1. System Architecture Overview
+
+The Nexent Intelligent Memory System is built on an advanced memory storage architecture that provides intelligent agents with persistent context-aware capabilities. Through a multi-layered memory management mechanism, the system achieves cross-conversation knowledge accumulation and retrieval, significantly enhancing the coherence and personalization of human-machine interactions.
+
+### Core Technical Features
+- **Layered Memory Architecture**: Four-level memory storage system built on the mem0 framework
+- **Adaptive Memory Management**: Supports both automated and manual memory operation modes
+- **Cross-Session Persistence**: Ensures continuity of knowledge and context across multiple conversations
+- **Fine-Grained Permission Control**: Provides flexible memory sharing strategy configuration
+
+---
+
+## 2. Configuration and Initialization
+
+### 2.1 System Activation
+1. Access the memory management interface: Click the **Memory Management Icon** in the upper right corner of the conversation interface
+2. Enter the **System Configuration** module for initialization settings
+
+### 2.2 Core Configuration Parameters
+
+| Configuration Item | Options | Default Value | Description |
+|-------------------|---------|---------------|-------------|
+| Memory Service Status | Enable/Disable | Enable | Controls the operational status of the entire memory system |
+| Agent Memory Sharing Strategy | Always Share/Ask Me Each Time/Prohibit Sharing | Always Share | Defines whether user authorization consent is required for memory sharing between agents |
+
+
+
+
+
+---
+
+## 3. Layered Memory Architecture
+
+Nexent adopts a four-layer memory storage architecture based on **mem0**, achieving precise memory classification and retrieval through different scopes and lifecycle management:
+
+### 3.1 Architecture Layer Details
+
+| Memory Level | Scope | Storage Content | Lifecycle | Configuration Role | Typical Applications |
+|--------------|-------|-----------------|-----------|-------------------|---------------------|
+| **Tenant Level Memory** | Organization-wide | Enterprise-level standard operating procedures, compliance policies, organizational structure, factual information | Long-term storage | Tenant Administrator | Enterprise knowledge management, standardized process execution, compliance checking |
+| **Agent Level Memory** | Specific Agent | Professional domain knowledge, skill templates, historical conversation summaries, learning accumulation | Consistent with agent lifecycle | Tenant Administrator | Professional skill accumulation, domain knowledge sedimentation, experiential learning |
+| **User Level Memory** | Specific User Account | Personal preference settings, usage habits, common instruction templates, personal information | Long-term storage | All Users | Personalized services, user experience optimization, preference management |
+| **User-Agent Level Memory** | Specific Agent under Specific User Account | Collaboration history, personalized factual information, specific task context, relationship models | Consistent with agent lifecycle | All Users | Deep collaboration scenarios, personalized tuning, task continuity maintenance |
+
+### 3.2 Memory Priority and Retrieval Strategy
+
+Memory retrieval follows the following priority order (from high to low):
+1. **Tenant Level** → Basic facts
+2. **User-Agent Level** → Most specific context information
+3. **User Level** → Personal preferences and habits
+4. **Agent Level** → Professional knowledge and skills
+
+---
+
+## 4. Operation Modes and Functional Interfaces
+
+### 4.1 Automated Memory Management
+- **Intelligent Extraction**: Automatically identifies key factual information in conversations and generates memory entries
+- **Automatic Context Embedding**: Agents automatically retrieve the most relevant memory entries and implicitly embed them in conversation context
+- **Incremental Updates**: Supports progressive updates, supplementation, and automatic cleanup of memory content
+
+### 4.2 Manual Memory Operations
+
+#### Adding Memory
+- Click the green plus button, input text, then click the checkmark to add a memory entry (maximum 500 characters)
+
+
+
+
+
+#### Deleting Memory
+- Click the red cross button, then click confirm in the popup confirmation dialog to delete all memory entries under a specific Agent group
+- Click the red eraser button to delete a specific memory entry
+
+