feat: 算法组自迭代闭环 - 代码项目 + 记忆积累
创建三个算法代码项目(algo-base 子目录): - antishake/: EIS 防抖,目标 <3px jitter / <16ms 延迟 - positioning/: BLE/UWB 定位,目标 <30cm 精度 / ≥10Hz - navigation/: GPS/IMU 导航,目标 <2° 姿态 / ≥50Hz projects.yaml 新增三项配置,路径显式指向各子目录 _enqueue_algo_cycle() 重写: - project 从 "algo-base"(不存在)改为实际项目名 - type 从 "tech_support" 改为 "code_improve" - context 明确六步闭环:读记忆→看代码→改代码→rk3566验证→写记忆→定方向 AlgoResearcherAgent system prompt 重写: - 从纯调研模式改为代码迭代模式 - 每轮必须产出可运行的代码改进 - 三个子类 system prompt 更新:聚焦代码文件路径和目标指标 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+12
-8
@@ -31,10 +31,6 @@ projects:
|
||||
logodetect-x86:
|
||||
mode: auto
|
||||
description: Logo 检测(x86 开发版)
|
||||
os-base:
|
||||
path: /data/company/os-base
|
||||
mode: auto
|
||||
description: 基础技术组:RTP/网络/内核调度调研与优化(虚拟项目,任务由调度器入队)
|
||||
rtp-research:
|
||||
path: /data/company/os-base/rtp-research
|
||||
mode: auto
|
||||
@@ -47,10 +43,18 @@ projects:
|
||||
path: /data/company/os-base/kernel-analysis
|
||||
mode: auto
|
||||
description: Linux 内核调度分析,RK3588 多核 NPU 推理优化
|
||||
claude-wx:
|
||||
path: /data/company/claude-wx
|
||||
mode: auto
|
||||
description: Claude Code 飞书监控控制系统,Hook 审批+双向控制,自主优化中
|
||||
ui-tars:
|
||||
mode: auto
|
||||
description: UI 自动化测试框架
|
||||
antishake:
|
||||
path: /data/company/algo-base/antishake
|
||||
mode: auto
|
||||
description: EIS 电子防抖算法,目标<3px抖动、<16ms延迟,rk3566验证
|
||||
positioning:
|
||||
path: /data/company/algo-base/positioning
|
||||
mode: auto
|
||||
description: BLE/UWB 短距定位融合,目标<30cm精度、≥10Hz刷新,rk3566验证
|
||||
navigation:
|
||||
path: /data/company/algo-base/navigation
|
||||
mode: auto
|
||||
description: GPS/IMU 融合导航,目标<2°姿态精度、≥50Hz更新,rk3566验证
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from nmfs_agents.agents.algo_researcher import AlgoResearcherAgent
|
||||
from nmfs_agents.core.queue import Task
|
||||
|
||||
ANTISHAKE_SYSTEM = """专项领域:EIS 电子防抖(项目:antishake)
|
||||
|
||||
代码文件:src/eis.py(核心),tests/test_eis.py(验证)
|
||||
|
||||
迭代重点(按优先级):
|
||||
1. 陀螺仪数据融合:低通滤波 + 卡尔曼平滑,消除高频抖动
|
||||
2. 运动估计:光流(Lucas-Kanade)或特征匹配(ORB),计算帧间位移
|
||||
3. 图像变换:warpAffine/warpPerspective 补偿位移,边缘裁剪最小化
|
||||
|
||||
rk3566 目标指标:
|
||||
- 防抖后帧间 jitter < 3px(720p)
|
||||
- 处理延迟 < 16ms(60fps 实时)
|
||||
|
||||
记忆键:
|
||||
- [记忆] __hardware__: algo.antishake.jitter_px=N, algo.antishake.latency_ms=N, algo.antishake.fps=N
|
||||
"""
|
||||
|
||||
|
||||
class AlgoAntishakeAgent(AlgoResearcherAgent):
|
||||
"""算法专项:防抖算法(EIS/OIS/光流稳像)"""
|
||||
|
||||
def _build_context(self, task: Task) -> str:
|
||||
return super()._build_context(task) + "\n" + ANTISHAKE_SYSTEM
|
||||
@@ -0,0 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from nmfs_agents.agents.algo_researcher import AlgoResearcherAgent
|
||||
from nmfs_agents.core.queue import Task
|
||||
|
||||
NAV_SYSTEM = """专项领域:GPS/IMU 融合导航(项目:navigation)
|
||||
|
||||
代码文件:src/navigation.py(核心),tests/test_nav.py(验证)
|
||||
|
||||
迭代重点(按优先级):
|
||||
1. Mahony 互补滤波:Kp/Ki 参数调优,四元数姿态解算
|
||||
2. EKF 松耦合:状态=[位置/速度/姿态],融合 GPS 位置和 IMU 加速度
|
||||
3. 漂移抑制:零速检测(ZUPT)、磁力计辅助航向修正
|
||||
|
||||
rk3566 目标指标:
|
||||
- 姿态精度 < 2°(静止,10s 积分)
|
||||
- 位置更新频率 ≥ 50Hz(纯 IMU)
|
||||
- GPS 丢失漂移 < 1m/min
|
||||
|
||||
记忆键:
|
||||
- [记忆] __hardware__: algo.nav.attitude_deg=N, algo.nav.hz=N, algo.nav.drift_m_per_min=N
|
||||
"""
|
||||
|
||||
|
||||
class AlgoNavAgent(AlgoResearcherAgent):
|
||||
"""算法专项:导航与姿态估算(GPS/IMU 融合,Kalman 滤波)"""
|
||||
|
||||
def _build_context(self, task: Task) -> str:
|
||||
return super()._build_context(task) + "\n" + NAV_SYSTEM
|
||||
@@ -0,0 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from nmfs_agents.agents.algo_researcher import AlgoResearcherAgent
|
||||
from nmfs_agents.core.queue import Task
|
||||
|
||||
POSITION_SYSTEM = """专项领域:BLE/UWB 短距定位(项目:positioning)
|
||||
|
||||
代码文件:src/positioning.py(核心),tests/test_positioning.py(验证)
|
||||
|
||||
迭代重点(按优先级):
|
||||
1. RSSI 路径损耗模型:拟合 FSPL 参数,修正环境遮挡影响
|
||||
2. 卡尔曼滤波:状态量=[x,y,vx,vy],观测量=多锚点 RSSI 估距
|
||||
3. 多点融合:三边测量最小二乘,异常值 RANSAC 剔除
|
||||
|
||||
rk3566 目标指标:
|
||||
- 定位精度 < 30cm(静止目标,3 锚点)
|
||||
- 刷新率 ≥ 10Hz
|
||||
|
||||
记忆键:
|
||||
- [记忆] __hardware__: algo.position.accuracy_cm=N, algo.position.hz=N, algo.position.anchors=N
|
||||
"""
|
||||
|
||||
|
||||
class AlgoPositionAgent(AlgoResearcherAgent):
|
||||
"""算法专项:短距定位与测距算法(UWB/BLE/ToF)"""
|
||||
|
||||
def _build_context(self, task: Task) -> str:
|
||||
return super()._build_context(task) + "\n" + POSITION_SYSTEM
|
||||
@@ -11,25 +11,30 @@ from nmfs_agents.tools.project_memory import ProjectMemory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ALGO_RESEARCHER_SYSTEM = """你是基础技术研发组的算法研究员,专注嵌入式平台算法研究与落地。
|
||||
ALGO_RESEARCHER_SYSTEM = """你是基础技术研发组的算法工程师,负责在 rk3566 上实现并持续迭代算法代码。
|
||||
|
||||
研究方向:
|
||||
- 防抖算法:陀螺仪数据融合、EIS(电子防抖)、OIS 协同
|
||||
- 短距定位:UWB/BLE RSSI 测距、ToF 测距、多点定位融合
|
||||
- GPS/惯导:IMU(加速度计+陀螺仪)融合、Kalman 滤波、姿态解算
|
||||
## 工作模式
|
||||
不是单纯研究——每轮任务必须产出可运行的代码改进:
|
||||
1. **读记忆**:从 ProjectMemory __hardware__ 获取上轮实测数据和改进建议
|
||||
2. **看代码**:读取项目 src/ 下现有实现,理解当前版本的瓶颈
|
||||
3. **改代码**:基于上轮结论,实施针对性改进(不要推倒重来)
|
||||
4. **测验证**:在 rk3566 运行 tests/,取得实测数字
|
||||
5. **写记忆**:[记忆] 标签更新 benchmark 数据
|
||||
6. **定方向**:[后续] 标签指明下轮改进方向
|
||||
|
||||
验证设备:**仅使用 rk3566**(OrangePi,低功耗算力代表)
|
||||
- 不使用 rk3588(高性能平台,非算法组职责范围)
|
||||
- TinyML 算法须在 ESP32/Nordic 上验证(资源极度受限)
|
||||
- rk3566 RKNN Lite 推理:`rknn-lite`(NPU 1 TOPS,INT8)
|
||||
## 验证设备:**仅使用 rk3566**(OrangePi,低功耗算力代表)
|
||||
- SSH: orangepi@192.168.123.183,workspace=/home/orangepi/Desktop
|
||||
- NPU: RKNN Lite,~1 TOPS,INT8,优先轻量化模型
|
||||
- 不使用 rk3588(高性能平台,非算法组职责)
|
||||
|
||||
硬件约束意识:
|
||||
- 始终读取 __hardware__ facts 确认目标平台算力/内存
|
||||
- rk3566 NPU 算力约为 rk3588 的 1/3,须优先轻量化模型
|
||||
## 算法实现约束
|
||||
- 语言:Python 3.10,可用 numpy/scipy/opencv-python
|
||||
- 实时性:尽量用 numpy 向量化,避免 Python 循环
|
||||
- 测试:每个函数必须有对应 tests/test_*.py 用例
|
||||
|
||||
输出格式:
|
||||
- [记忆] __hardware__: algo.antishake.fps=N, algo.gps_ins.latency_ms=N
|
||||
- [后续] <下一轮迭代聚焦点,如:基于新精度数据验证 EKF 收敛速度>
|
||||
## 输出格式(必须包含)
|
||||
- [记忆] __hardware__: algo.{domain}.{metric}=值
|
||||
- [后续] <下一轮具体改进目标,如:将卡尔曼 Q 矩阵改为自适应调整>
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from apscheduler.schedulers.blocking import BlockingScheduler
|
||||
|
||||
from nmfs_agents.config import load_config
|
||||
from nmfs_agents.core.executor import Executor
|
||||
from nmfs_agents.core.queue import TaskQueue
|
||||
from nmfs_agents.core.scanner import Scanner
|
||||
from nmfs_agents.core.watchdog import Watchdog
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _scan_and_enqueue() -> None:
|
||||
cfg = load_config()
|
||||
q = TaskQueue()
|
||||
scanner = Scanner(cfg)
|
||||
results = scanner.scan_all()
|
||||
for r in results:
|
||||
from nmfs_agents.core.queue import Task
|
||||
tid = q.enqueue(Task(
|
||||
project=r.project, type=r.type, title=r.title,
|
||||
context=r.context, priority=r.priority,
|
||||
mode=r.mode, agent_role=r.agent_role,
|
||||
initiator=r.initiator, discussion=r.discussion,
|
||||
))
|
||||
if tid > 0:
|
||||
logger.info("新任务入队 [%s] %s (priority=%d)", r.project, r.title, r.priority)
|
||||
|
||||
|
||||
def _get_queue() -> "TaskQueue":
|
||||
"""可被测试 monkeypatch 替换的工厂函数。"""
|
||||
return TaskQueue()
|
||||
|
||||
|
||||
def _enqueue_planner_task() -> None:
|
||||
"""将 planner 分析任务入队(同标题去重,不重复触发)。"""
|
||||
from nmfs_agents.core.queue import Task
|
||||
q = _get_queue()
|
||||
tid = q.enqueue(Task(
|
||||
project="research",
|
||||
type="planner",
|
||||
title="PlannerAgent 周期差距分析",
|
||||
context="由 Scheduler 定时触发,分析平台目标与项目现状差距",
|
||||
priority=5,
|
||||
mode="report",
|
||||
agent_role="planner",
|
||||
initiator="scheduler",
|
||||
discussion="6h 定时 planner 分析",
|
||||
))
|
||||
if tid > 0:
|
||||
logger.info("PlannerAgent 任务入队 #%d", tid)
|
||||
else:
|
||||
logger.debug("PlannerAgent 任务已在队列中,跳过")
|
||||
|
||||
|
||||
def _enqueue_research_cycle() -> None:
|
||||
"""将三专项调研轮次入队(同标题去重)。"""
|
||||
from nmfs_agents.core.queue import Task
|
||||
q = _get_queue()
|
||||
topics = [
|
||||
("rtp-researcher", "rtp-research", "RTP 周期调研:协议最新进展与延迟优化",
|
||||
"调研最新 RTP/RTCP 优化技术,重点关注低延迟媒体传输、RK3588 MPP 编码路径优化。"
|
||||
"用 [调研报告] 标签写入 __research__.rtp.* facts,用 [后续] 提出下阶段调研方向。"),
|
||||
("net-researcher", "net-research", "网络栈周期调研:QoS 与拥塞控制最新进展",
|
||||
"调研内核网络栈调优方案(TC/qdisc/eBPF),RK3588 stmmac 驱动优化。"
|
||||
"用 [调研报告] 标签写入 __research__.net.* facts,用 [后续] 提出下阶段方向。"),
|
||||
("kernel-analyzer", "kernel-analysis","内核调度周期分析:NPU/CPU 协同与功耗",
|
||||
"分析 RK3588 内核调度(CPUFreq/NPU 资源争用),给出调优参数建议。"
|
||||
"用 [调研报告] 标签写入 __research__.kernel.* facts,用 [后续] 提出下阶段方向。"),
|
||||
]
|
||||
for role, proj, title, ctx in topics:
|
||||
tid = q.enqueue(Task(
|
||||
project=proj, type="tech_support", title=title,
|
||||
context=ctx, priority=3, mode="auto",
|
||||
agent_role=role, initiator="scheduler",
|
||||
))
|
||||
if tid > 0:
|
||||
logger.info("调研任务入队 #%d [%s] %s", tid, role, title)
|
||||
|
||||
|
||||
def _enqueue_algo_cycle() -> None:
|
||||
"""将算法组代码迭代任务入队(入 base_opt 队列)。
|
||||
|
||||
每轮为每个算法项目生成一个闭环迭代任务:
|
||||
读取上轮记忆 → 改进代码 → rk3566验证 → 写回记忆 → 提出下轮方向
|
||||
"""
|
||||
from pathlib import Path
|
||||
from nmfs_agents.core.queue import Task
|
||||
q = TaskQueue(db_path=Path("data/base_opt_tasks.db"))
|
||||
algo_tasks = [
|
||||
(
|
||||
"algo-antishake", "antishake",
|
||||
"EIS 防抖算法迭代",
|
||||
"【代码迭代任务】\n"
|
||||
"1. 读取 ProjectMemory __hardware__.algo.antishake.* 了解上轮实测数据\n"
|
||||
"2. 查看 src/eis.py 现有实现(如不存在则新建骨架)\n"
|
||||
"3. 改进核心算法(陀螺仪融合/光流估计/帧间对齐)\n"
|
||||
"4. 在 rk3566(orangepi@192.168.123.183)运行 tests/ 验证效果\n"
|
||||
"5. 用 [记忆] 记录实测 fps/jitter_px 数据\n"
|
||||
"6. 用 [后续] 提出下一轮聚焦方向\n"
|
||||
"验证优先级:精度(jitter_px) > 延迟(ms) > FPS",
|
||||
),
|
||||
(
|
||||
"algo-position", "positioning",
|
||||
"BLE/UWB 定位算法迭代",
|
||||
"【代码迭代任务】\n"
|
||||
"1. 读取 ProjectMemory __hardware__.algo.position.* 了解上轮实测数据\n"
|
||||
"2. 查看 src/positioning.py 现有实现(如不存在则新建骨架)\n"
|
||||
"3. 改进核心算法(RSSI 路径损耗模型/卡尔曼滤波/多点融合)\n"
|
||||
"4. 在 rk3566(orangepi@192.168.123.183)运行 tests/ 验证效果\n"
|
||||
"5. 用 [记忆] 记录实测 accuracy_cm/hz 数据\n"
|
||||
"6. 用 [后续] 提出下一轮聚焦方向\n"
|
||||
"验证优先级:精度(cm) > 刷新率(Hz)",
|
||||
),
|
||||
(
|
||||
"algo-nav", "navigation",
|
||||
"GPS/IMU 导航算法迭代",
|
||||
"【代码迭代任务】\n"
|
||||
"1. 读取 ProjectMemory __hardware__.algo.nav.* 了解上轮实测数据\n"
|
||||
"2. 查看 src/navigation.py 现有实现(如不存在则新建骨架)\n"
|
||||
"3. 改进核心算法(Mahony 互补滤波/EKF 融合/漂移抑制)\n"
|
||||
"4. 在 rk3566(orangepi@192.168.123.183)运行 tests/ 验证效果\n"
|
||||
"5. 用 [记忆] 记录实测 attitude_hz/drift_m_per_min 数据\n"
|
||||
"6. 用 [后续] 提出下一轮聚焦方向\n"
|
||||
"验证优先级:姿态精度(°) > 更新频率(Hz) > 漂移(m/min)",
|
||||
),
|
||||
]
|
||||
for role, project, title, ctx in algo_tasks:
|
||||
tid = q.enqueue(Task(
|
||||
project=project, type="code_improve", title=title,
|
||||
context=ctx, priority=3, mode="auto",
|
||||
agent_role=role, initiator="scheduler",
|
||||
))
|
||||
if tid > 0:
|
||||
logger.info("算法迭代任务入队 #%d [%s] %s", tid, role, title)
|
||||
|
||||
|
||||
def _enqueue_market_intel_task() -> None:
|
||||
"""将 Market-PM 深度情报收集任务入队(每日一次,同标题去重)。"""
|
||||
from nmfs_agents.core.queue import Task
|
||||
context = (
|
||||
"每日市场情报全量更新,覆盖三大目标领域(运动、适老、AI安全)。\n"
|
||||
"每个领域请完成:①竞品全景(≥5个,含定价/功能/优劣势)"
|
||||
"②近期技术趋势(含具体数据)③用户痛点 ④差异化机会。\n"
|
||||
"输出 [市场情报] 标签更新 __market__.{goal}.summary/competitors/trends/opportunities/updated_at。"
|
||||
)
|
||||
q = _get_queue()
|
||||
tid = q.enqueue(Task(
|
||||
project="research",
|
||||
type="market_intel",
|
||||
title="Market-PM 每日情报收集",
|
||||
context=context,
|
||||
priority=4,
|
||||
mode="auto",
|
||||
agent_role="market-pm",
|
||||
initiator="scheduler",
|
||||
))
|
||||
if tid > 0:
|
||||
logger.info("Market-PM 情报任务入队 #%d", tid)
|
||||
else:
|
||||
logger.debug("Market-PM 情报任务已在队列中,跳过")
|
||||
|
||||
|
||||
def _collect_env() -> None:
|
||||
"""后台静默收集环境信息,失败不影响主流程。"""
|
||||
try:
|
||||
from nmfs_agents.config import load_config
|
||||
from nmfs_agents.tools.env_collector import run_collection
|
||||
from nmfs_agents.tools.project_memory import ProjectMemory
|
||||
cfg = load_config()
|
||||
run_collection(cfg, ProjectMemory())
|
||||
logger.info("EnvCollector 完成收集")
|
||||
except Exception:
|
||||
logger.exception("EnvCollector 收集失败(静默跳过)")
|
||||
|
||||
|
||||
def _run_pending() -> None:
|
||||
cfg = load_config()
|
||||
executor = Executor(cfg)
|
||||
count = asyncio.run(executor.run_all_pending())
|
||||
logger.info("本轮执行任务数: %d", count)
|
||||
if count == 0:
|
||||
# 队列空时触发 planner 补充需求
|
||||
_enqueue_planner_task()
|
||||
logger.info("队列为空,已触发 PlannerAgent 差距分析")
|
||||
|
||||
|
||||
def _run_pending_queue(queue_name: str) -> None:
|
||||
"""执行指定队列的待处理任务。"""
|
||||
from pathlib import Path
|
||||
from nmfs_agents.core.queue import TaskQueue
|
||||
cfg = load_config()
|
||||
q_cfg = cfg.queues.get(queue_name)
|
||||
if not q_cfg:
|
||||
logger.warning("队列配置不存在: %s", queue_name)
|
||||
return
|
||||
|
||||
db_path = Path(q_cfg.db)
|
||||
queue = TaskQueue(db_path=db_path)
|
||||
executor = Executor(cfg, queue=queue)
|
||||
role_filter = q_cfg.roles if q_cfg.roles else None
|
||||
count = asyncio.run(executor.run_all_pending(role_filter=role_filter))
|
||||
logger.info("[%s] 本轮执行任务数: %d", queue_name, count)
|
||||
|
||||
# project_delivery 队列空时触发 planner
|
||||
if count == 0 and queue_name == "project_delivery":
|
||||
_enqueue_planner_task()
|
||||
logger.info("project_delivery 队列为空,已触发 PlannerAgent")
|
||||
|
||||
|
||||
def start() -> None:
|
||||
cfg = load_config()
|
||||
interval = cfg.scheduler.interval_hours
|
||||
|
||||
# 启动恢复:将上次运行遗留的 running 任务重置为 failed,避免阻塞 semaphore
|
||||
q = TaskQueue()
|
||||
recovered = q.reset_stale_running(timeout_minutes=0)
|
||||
if recovered:
|
||||
logger.warning("启动恢复:%d 个遗留 running 任务已重置为 failed", recovered)
|
||||
|
||||
# 启动 Watchdog(daemon 线程,60s 一次超时检测 + 优先级老化)
|
||||
watchdog = Watchdog(q)
|
||||
watchdog.start()
|
||||
|
||||
scheduler = BlockingScheduler()
|
||||
scheduler.add_job(_scan_and_enqueue, "interval", hours=interval, id="scan")
|
||||
scheduler.add_job(
|
||||
_enqueue_planner_task, "interval", hours=6, id="planner",
|
||||
next_run_time=datetime.now() + timedelta(minutes=10),
|
||||
)
|
||||
# 三队列独立调度,首次执行错开启动时间
|
||||
scheduler.add_job(
|
||||
lambda: _run_pending_queue("project_delivery"),
|
||||
"interval", hours=interval, id="run_project",
|
||||
next_run_time=datetime.now() + timedelta(minutes=5),
|
||||
)
|
||||
scheduler.add_job(
|
||||
lambda: _run_pending_queue("base_opt"),
|
||||
"interval", hours=2, id="run_base", # 2h(原4h),算法组更频繁执行
|
||||
next_run_time=datetime.now() + timedelta(minutes=20),
|
||||
)
|
||||
scheduler.add_job(
|
||||
lambda: _run_pending_queue("insight"),
|
||||
"interval", hours=2, id="run_insight", # 2h(原6h),洞察组更频繁执行
|
||||
next_run_time=datetime.now() + timedelta(minutes=30),
|
||||
)
|
||||
# 三专项调研轮次(每8小时入一轮)
|
||||
scheduler.add_job(
|
||||
_enqueue_research_cycle, "interval", hours=8, id="research_cycle",
|
||||
next_run_time=datetime.now() + timedelta(minutes=2),
|
||||
)
|
||||
# 算法组迭代轮次(每8小时一轮)
|
||||
scheduler.add_job(
|
||||
_enqueue_algo_cycle, "interval", hours=8, id="algo_cycle",
|
||||
next_run_time=datetime.now() + timedelta(minutes=25),
|
||||
)
|
||||
scheduler.add_job(
|
||||
_enqueue_market_intel_task, "interval", hours=24, id="market_intel",
|
||||
next_run_time=datetime.now() + timedelta(hours=1),
|
||||
)
|
||||
scheduler.add_job(
|
||||
_collect_env, "interval", hours=24, id="env_collect",
|
||||
next_run_time=datetime.now() + timedelta(minutes=30),
|
||||
)
|
||||
logger.info("Scheduler 已启动,扫描间隔: %d 小时", interval)
|
||||
_scan_and_enqueue() # 启动时立即扫描一次
|
||||
# 启动时后台触发一次环境收集(daemon 线程,不阻塞调度器)
|
||||
threading.Thread(target=_collect_env, daemon=True, name="env-collector-init").start()
|
||||
try:
|
||||
scheduler.start()
|
||||
finally:
|
||||
watchdog.stop()
|
||||
Reference in New Issue
Block a user