Commit Graph

15 Commits

Author SHA1 Message Date
sinohqb
bf1ec16ef6 perf(eval): case/rule/campaign 并发执行
- EvalEngine case 循环改 asyncio.gather + semaphore(默认 3 并发)
- 规则评估并行(默认 5 并发),LLM 评分耗时从串行求和降为最慢一条
- _resolve_model 改 async + 双重检查锁,保护共享模型缓存
- _case_errors / case_outcomes 并发写入加状态锁
- Campaign occurrence 派生并行(默认 2 并发),claim 拒绝时提前收敛
- 新增配置:max_concurrent_cases=3 / max_concurrent_rules=5 / max_concurrent_runs=2

SQLite StaticPool 单连接下 DB 写入仍天然串行,并行收益集中在
channel I/O 与 LLM 调用的等待重叠。
2026-08-24 23:18:11 +08:00
sinohqb
7eae6de52d refactor(evaluation/storage): 结算统一与 repository 拆分(Phase 2 + 3)
合并两个不可分割的深化:

Phase 2 — 智能作业结算统一(ADR-0012)
- intelligence_jobs.execute(job_kind, campaign_id, ...) 作为结算的
  唯一实现:建行 → 认领 → 校验 → generating → 落账,一处编排、
  一处截断(500 字符)。两个 executor 退化为 ensure_queued /
  validate / work_fn 三个小 adapter。
- analysis.validate_analysis_request() 共享校验入口(活动终态 →
  模型),路由捕获映射 400、executor 捕获落 failed 行,与
  validate_comparison_request 先例同构。
- campaign_runner._auto_start_analysis 的跳过守卫收敛至
  auto_intelligence_eligible 单一判断点。
- comparison.py 删除零调用的 build_comparison_payload;
  load_comparison_view 投影归位至 campaign_read_model。
- 新增 characterization 测试(认领竞争、重复触发、截断、恢复上限)。

Phase 3 — storage/repository.py 拆分
- AsyncJobRepository 及两个子类迁至
  storage/async_job_repository.py(Phase 2 的 intelligence_jobs
  与 comparison 必须 import 自该路径,故与 Phase 2 同 commit)。
- ExplorationSession / ExplorationMessage 迁至
  storage/exploration_repository.py;repository.py 由 1180 行降至
  约 814 行,grep 确认无残留符号。
- exploration 子模块与路由 import 全部更新;测试 import 跟随。

刻意不做:CAS 共享原语、app.py 五 registry 关停顺序归一
(ADR-0006 精神,等真实需求出现再议)。
2026-08-24 05:50:27 +08:00
sinohqb
1782b245bf refactor(architecture): deepen campaign runtime modules 2026-08-11 13:18:48 +08:00
sinohqb
864ae2b7fe fix(review): address release correctness findings 2026-08-09 03:20:40 +08:00
sinohqb
62485684ca fix(architecture): enforce lifecycle consistency 2026-08-07 10:59:27 +08:00
sinohqb
c896ab3f71 refactor(architecture): deepen evaluation lifecycle and read model 2026-08-07 03:11:37 +08:00
sinohqb
160332665e refactor(exploration): absorb settlement.py into ExplorationSessionRepository
Some checks failed
CI / test (push) Failing after 12s
将 settlement.py 的 settle_campaign_sessions 函数吸收为
ExplorationSessionRepository.expire_running_sessions 方法。删除浅模块
settlement.py(30 行,接口宽如实现),会话生命周期操作集中在 repository。

- 新增 ExplorationSessionRepository.expire_running_sessions(campaign_id)
- 更新 campaigns.py 和 campaign_runner.py 两个调用点
- 删除 backend/agenteval/exploration/settlement.py
- 所有测试通过,行为不变
2026-08-04 13:28:23 +08:00
sinohqb
38849d46f1 refactor(repository): narrow atomic updates for patrol/cancel/scheduler writes
全行 update 逼着调用方承诺「持有最新完整快照」,巡检水位、活动取消、
调度循环三方并发写入会互相覆盖(后写者抹掉先写者的 summary/status)。
CampaignRepository 新增三个窄口径原子操作:touch_patrol_watermark、
mark_cancelled、save_scheduler_state,各自只写自己的列;三处调用点迁移。
2026-08-04 03:39:42 +08:00
sinohqb
62f7031b5b feat(exploration): expire dangling sessions on campaign finalize 2026-08-04 01:23:37 +08:00
sinohqb
5d04455664 feat(campaigns): auto-trigger analysis and include it in markdown export
The scheduler loop enqueues the analysis task when a realtime campaign
completes; accelerated or cancelled campaigns and a missing analysis
model skip silently. The campaign markdown export appends the analysis
appendix (overall, problems, narratives, suggestions) when a completed
analysis exists.
2026-08-03 02:25:07 +08:00
sinohqb
aa40c8e0d8 refactor(campaign): type Campaign.summary as CampaignSummary VO
Some checks failed
CI / test (push) Failing after 12s
Campaign.summary was a bare Optional[dict] while RunSummary is a typed VO —
scheduler state (spawned_indices/errors) flowed untyped through
set_/get_summary. Introduce CampaignSummary + SchedulerState (extra=allow,
validate_assignment), mirroring RunSummary; campaign_runner reads/writes the
VO. Also converge the ~10 repeated JSON column get/set pairs onto
_json_dumps/_json_loads helpers, unifying ensure_ascii=False and fixing the
set_modalities ensure_ascii=True trap.
2026-07-31 15:09:10 +08:00
sinohqb
0cca4963d1 refactor(tasks): unify run/campaign task registries into TaskRegistry
Both the single-run path and the campaign scheduler drove long-lived
asyncio tasks through their own duplicated _tasks/_cancel_events dicts and
shutdown loops. Collapse them into one deep TaskRegistry module,
instantiated as run_registry and campaign_registry. launch() creates the
cancel event before the task (so a cancel during startup is never lost),
wires done-callback cleanup, and is idempotent per id; this makes runs.py's
hard-cancel fallback provably dead, so it is removed. App shutdown now
gracefully stops in-flight runs too, not just campaigns.
2026-07-31 03:39:03 +08:00
sinohqb
e815298ce5 refactor(campaign): move tick decisions into the pure scheduler seam
Extend the pure scheduler with elapsed_seconds (clock injected), decide_tick
(offset + due + lifecycle action) and resolve_finalize (cancel-race guard),
so the durable loop stops hand-coding elapsed/finished/status checks and only
does I/O. Deletes the runner's private _elapsed_seconds and converges
current_window_offset onto the one pure elapsed computation. The clock-skew
tolerance and cancel-race guard are now unit-testable at the seam.
2026-07-31 02:20:14 +08:00
sinohqb
8910fd17e0 feat(campaigns): durable scheduler loop with restart recovery and cancel
Add a thin async loop (run_campaign_loop) that ticks on real wall-clock time,
maps elapsed×time_scale to a window offset via the pure decide_schedule, spawns
due child Runs, and marks the campaign COMPLETED at window end. All authority
lives in the DB (started_at, spawned_indices, status), so the app lifespan can
resume every RUNNING campaign on startup without double-spawning and stop all
loops gracefully on shutdown. A failing plan entry is skipped and recorded
rather than wedging the campaign.

Creating a campaign now starts its loop; POST /api/campaigns/{id}/cancel stops
further spawning (completed child Runs are kept); GET /api/campaigns/{id}
reports live progress (window offset, spawned/completed Run counts).
2026-07-30 13:33:10 +08:00
sinohqb
c6b102a9b5 feat(campaigns): add scheduling decision and child-Run spawning
Add the pure scheduling seam (campaign_scheduler.decide_schedule) that, given a
static plan and window-clock offset, decides which plan entries are due and
whether the window ended — mirroring judgement.combine_case_outcome, with
time_scale confined to the clock mapping so it never touches judgement/report.

The campaign_runner shell maps injected elapsed time to a window offset, spawns
due child Runs through the existing EvalEngine.run(existing_run=...) path with
campaign_id + RunTrigger.CAMPAIGN, and persists spawned-entry indices per entry
for idempotent, restart-recoverable progress. No auto loop yet (ticket 03).
2026-07-30 12:06:31 +08:00