Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions api/ops/orchestrator/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from api.ops.llm.types import LlmUsage
from api.ops.queries import OpsQueries
from api.ops.review.rules import review_result
from api.ops.store.artifacts import save_artifact_with_failure_event
from api.ops.store.runs import OpsRunStore, append_event
from api.ops.tracing import trace_span, traceable, update_current_span_metadata

Expand Down Expand Up @@ -358,6 +359,19 @@ def run_deep(
"verdict": final_verdict,
},
)
save_artifact_with_failure_event(
run_id,
"deep.final_answer",
{
"answer": answer,
"agent": agent_name,
"issue_number": analyst_result.get("issue_number"),
"verdict": final_verdict,
"intent": intent or "issue_contribution",
"route": "deep",
},
store=store,
)
store.append_event(run_id, "orchestrator", "run.end", node_id="deep.end")
# B5: run 级 metrics_json 汇总
metrics_json = _build_metrics_json(
Expand Down
13 changes: 13 additions & 0 deletions api/ops/react_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from api.ops.queries import OpsQueries
from api.ops.react_tools import _build_v0_registry, _truncate_summary
from api.ops.review.rules import review_result
from api.ops.store.artifacts import save_artifact_with_failure_event
from api.ops.store.runs import OpsRunStore, append_event
from api.ops.tracing import trace_span, traceable, update_current_span_metadata

Expand Down Expand Up @@ -315,6 +316,18 @@ def run_react_fallback(
status=final_verdict,
final_answer={"answer": answer, "verdict": final_verdict},
)
save_artifact_with_failure_event(
run_id,
"react.final_answer",
{
"answer": answer,
"verdict": final_verdict,
"intent": "fallback",
"route": "react",
"steps_taken": step,
},
store=store,
)
store.append_event(run_id, "orchestrator", "run.end", node_id="react.end")

# Run metrics
Expand Down
70 changes: 70 additions & 0 deletions api/ops/store/artifacts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Ops Chat run artifacts 仓储层(P1-1)。"""

from __future__ import annotations

from typing import Any


class ArtifactStoreError(RuntimeError):
"""Artifact 写入失败时的自说明异常。"""


def save_artifact(
run_id: str,
kind: str,
payload: dict[str, Any],
store: Any | None = None,
max_retries: int = 3,
) -> dict[str, Any]:
"""幂等写入 ops_run_artifacts;按 (run_id, kind) 去重。

- 未提供 store 时,使用全局 supabase_client() 构造 OpsRunStore。
- 重试 max_retries + 1 次后仍失败则抛出 ArtifactStoreError。
"""
from api.ops.store.runs import OpsRunStore
from api.rag_env import supabase_client

target = store if store is not None else OpsRunStore(supabase_client())
normalized = dict(payload)
last_exc: Exception | None = None

for _attempt in range(max_retries + 1):
try:
return target.save_artifact(run_id, kind, normalized)
except Exception as exc:
last_exc = exc

raise ArtifactStoreError(
f"Failed to save artifact run_id={run_id} kind={kind} "
f"after {max_retries + 1} attempts: {last_exc}"
)


def save_artifact_with_failure_event(
run_id: str,
kind: str,
payload: dict[str, Any],
store: Any | None = None,
max_retries: int = 3,
) -> dict[str, Any] | None:
"""保存 artifact;写入失败时记录 `artifact.write_failed` 事件并吞掉异常。

返回值:成功返回写入行;失败返回 None。
"""
from api.ops.events_schema import SCHEMA_VERSION
from api.ops.store.runs import append_event

try:
return save_artifact(run_id, kind, payload, store=store, max_retries=max_retries)
except ArtifactStoreError as exc:
append_event(
run_id,
"artifact.write_failed",
{
"kind": kind,
"error": str(exc),
"schema_version": SCHEMA_VERSION,
},
store=store,
)
return None
32 changes: 32 additions & 0 deletions api/ops/store/runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,38 @@ def _once() -> dict[str, Any]:

return supabase_execute_with_retry(_once)

def save_artifact(
self, run_id: str, kind: str, payload: dict[str, Any]
) -> dict[str, Any]:
"""幂等写入 ops_run_artifacts;由 (run_id, kind) 唯一去重。"""
row = {"run_id": run_id, "kind": kind, "payload": payload}

def _once() -> dict[str, Any]:
res = (
self.client.table("ops_run_artifacts")
.upsert(row, on_conflict="run_id,kind")
.execute()
)
data = res.data if isinstance(res.data, list) else []
if data and isinstance(data[0], dict):
return data[0]
raise RuntimeError("ops_run_artifacts upsert did not return row")

return supabase_execute_with_retry(_once)

def list_artifacts(self, run_id: str) -> list[dict[str, Any]]:
def _once() -> list[dict[str, Any]]:
res = (
self.client.table("ops_run_artifacts")
.select("*")
.eq("run_id", run_id)
.order("created_at", desc=True)
.execute()
)
return res.data if isinstance(res.data, list) else []

return supabase_execute_with_retry(_once)


def append_event(
run_id: str,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# 30 执行编码帽 · Invoke 快照

| 字段 | 内容 |
| --- | --- |
| **timestamp** | 20260709_1622 |
| **hat** | 30-execute-code |
| **task_slug** | ops-chat-session-sink-p0-p1 |
| **subtask** | P1-1 Artifacts → Supabase |
| **git_branch** | task/ops-chat-session-sink-p0-p1 |
| **worktree_root** | ai-ink-brain-api-python/ |
| **verify_command** | `pytest tests/ops tests/ops_desk -m "not intent_eval and not intent_benchmark" -q && ruff check api/ops` |
| **task_path** | `docs/harness/tasks/active/task_ops_chat_session_sink_p0_p1_v1.md` |
| **audit_review_path** | `ai-ink-brain-api-python/docs/harness/reviews/task_ops_chat_session_sink_p0_p1_v1_audit_R2_20260708.md` |
| **plan_path** | `docs/harness/guides/PLAN_ops_chat_session_sink_p0_p1_v1_zh.md` |

## 用户消息快照

```text
你正在扮演工作区 Harness「30-execute-code · 执行编码帽」,严格遵循 docs/harness/prompts/30-execute-code.md。

**输入(已替换占位符)**
- 主 task 路径(相对 Projects/):`docs/harness/tasks/active/task_ops_chat_session_sink_p0_p1_v1.md`
- 逻辑子仓(相对 Projects/):`ai-ink-brain-api-python`
- Worktree 研发目录(所有 git/pytest/ruff 默认 cwd):`ai-ink-brain-api-python`
- 当前分支:`task/ops-chat-session-sink-p0-p1`(已基于 main fast-forward)
- 合并前须跑通的验证命令:
```bash
pytest tests/ops tests/ops_desk -m "not intent_eval and not intent_benchmark" -q && ruff check api/ops
```
- 关联任务审核书面结论路径:`ai-ink-brain-api-python/docs/harness/reviews/task_ops_chat_session_sink_p0_p1_v1_audit_R2_20260708.md`
- 关联 PLAN / 总规:`docs/harness/guides/PLAN_ops_chat_session_sink_p0_p1_v1_zh.md`

**本棒目标:P1-1 Artifacts → Supabase**

P1-1 具体要求(来自 PLAN §2、§3.1 D6、task §失败路径、§实现备忘):
- 新建 Supabase 表 `ops_run_artifacts`:至少含 `run_id` (uuid, FK ops_runs)、`kind` (text)、`payload` (jsonb)、`created_at`;与现有 `ops_run_events` 风格保持一致。
- 新增 `supabase_sql/` migration 文件(可回滚),按本仓 migration 命名与编号惯例落盘。
- 新增 `api/ops/store/artifacts.py`:实现 `save_artifact(run_id, kind, payload)`,幂等写入 `ops_run_artifacts`;失败时抛出自说明异常。
- 在 `api/ops/orchestrator/core.py` 的 deep 路径与 `api/ops/react_loop.py` 的 ReAct 路径中,于关键结果产出处调用 `save_artifact`,将 deep/ReAct 结果写入 DB artifact(kind 建议如 `deep.final_answer`、`react.final_answer`,或按 task 失败路径约定)。
- 与 P0-2 events 集成:artifact 写失败时,须记录 `ops_run_events` 事件 `kind=artifact.write_failed`,并仍返回答案(见 task §失败路径)。

**范围限制**
- 只做 P1-1;不改 P1-2 checkpoint、P1-3 clarify、P1-4 LLM router
- 不改 `harness_runtime` 生产图
- 不改 Agently lab
- 不改前端代码

**test_strategy: required**
- 先写/调整可失败的自动化测试,再改实现
- 新增 `tests/ops/test_artifacts.py` 覆盖:
- `save_artifact` 成功写入并可按 run_id 读取
- schema 字段(run_id / kind / payload / created_at)存在
- 写失败时生成 `artifact.write_failed` event(可 mock Supabase 失败)
- deep / ReAct 路径在运行时调用 save_artifact(可 mock 或集成)
- 最终验证命令必须绿

**失败路径硬性检查**
- task §失败路径已列 `Supabase artifact 写失败`:行为 = 记录 event `artifact.write_failed` + 仍返回答案;可观测 = `ops_run_events` 行 `kind=artifact.write_failed`;可重试 = 幂等重试 N 次后仍失败则记录;验证命令 = `pytest tests/ops/test_artifacts.py -k write_failed`

**你必须完成**
0. **Invoke 快照(开帽起点)**:将本用户消息全文落盘到 `ai-ink-brain-api-python/docs/harness/invokes/by-task/ops-chat-session-sink-p0-p1/invoke_YYYYMMDD_HHMM_30_ops_chat_session_sink_p0_p1_P1-1.md`(含元数据表 + 快照 fenced code)。同一会话内追问 **不** 再新增快照文件。
0b. **人工闸**:扫描 task / 关联 reviews 的 human_gate。若任一对本帽(30)为 pending → 仅输出须人改的 gate_id 与路径,拒开工;禁止代填 approved。
1. 通读 task 全文:头部 gates_before_code、audit_profile、orchestration、chain_prompt、test_strategy / test_strategy_note、failure_paths、验收标准、必读列表、非范围。
2. 阅读 PLAN §2、§3.1 与关联 SNAPSHOT/gap matrix。
3. 先读现有代码:`api/ops/store/runs.py`、`api/ops/events_schema.py`、`api/ops/orchestrator/core.py`、`api/ops/react_loop.py`、`api/ops/review/rules.py`。
4. 先写失败可复现的测试(`tests/ops/test_artifacts.py`),再实现 migration、`api/ops/store/artifacts.py` 与写入点。
5. 在 deep/ReAct 路径中合适位置调用 `save_artifact`,并保证失败时记录 `artifact.write_failed` event。
6. 执行验证命令,保留可核对输出要点;修复直至通过。
7. 按 40-self-check.md 将结论与命令摘要回填至 task 正文「### 自检结论(执行者)」小节(在 P1-1 区域新增;不要覆盖 P0 已有结论)。
8. 对话回复:生成可以完整复制的 Prompt,用于直接交给下一棒 40 自检执行。
9. **自动 commit**:在输出下一棒 Prompt 且本轮代码/测试/task 自检回填已落盘后,按 HANDOFF_AUTO_COMMIT.md 在 ai-ink-brain-api-python/ commit(仅本轮路径;禁止 git add -A;对话报 short-hash)。
10. **禁止**自行 push;由 Lead 合并。

**输出要求**
- 若拒开工:仅 Markdown 阻塞清单
- 若执行:diff 摘要、验证命令输出、commit short-hash、下一棒 40 Prompt

**Judgment(本帽 · 对话末尾必填)**:experience_capture / gate/risk / hat_self
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 40 执行者自检帽 · Invoke 快照

| 字段 | 内容 |
| --- | --- |
| **timestamp** | 20260709_1622 |
| **hat** | 40-self-check |
| **task_slug** | ops-chat-session-sink-p0-p1 |
| **subtask** | P1-1 Artifacts → Supabase |
| **git_branch** | task/ops-chat-session-sink-p0-p1 |
| **worktree_root** | ai-ink-brain-api-python/ |
| **verify_command** | `pytest tests/ops tests/ops_desk -m "not intent_eval and not intent_benchmark" -q && ruff check api/ops` |
| **task_path** | `docs/harness/tasks/active/task_ops_chat_session_sink_p0_p1_v1.md` |
| **30_invoke_path** | `ai-ink-brain-api-python/docs/harness/invokes/by-task/ops-chat-session-sink-p0-p1/invoke_20260709_1622_30_ops_chat_session_sink_p0_p1_P1-1.md` |
| **30_commit** | 待 30 自动 commit 后回填 |

## 用户消息快照(30 → 40 交接 Prompt)

```text
你正在扮演工作区 Harness「40-self-check · 执行者自检帽」,严格遵循 docs/harness/prompts/40-self-check.md。

**输入(已替换占位符)**
- 主 task 路径(相对 Projects/):`docs/harness/tasks/active/task_ops_chat_session_sink_p0_p1_v1.md`
- 逻辑子仓(相对 Projects/):`ai-ink-brain-api-python`
- Worktree 研发目录(所有 git/pytest/ruff 默认 cwd):`ai-ink-brain-api-python`
- 本棒校验范围:**P1-1 Artifacts → Supabase**
- 30 执行者已声明的验证命令:
```bash
pytest tests/ops tests/ops_desk -m "not intent_eval and not intent_benchmark" -q && ruff check api/ops
```
- 30 invoke 快照:`ai-ink-brain-api-python/docs/harness/invokes/by-task/ops-chat-session-sink-p0-p1/invoke_20260709_1622_30_ops_chat_session_sink_p0_p1_P1-1.md`

**你必须完成**
1. 独立阅读 30 invoke 快照与 task 正文「### 自检结论(执行者)· P1-1」小节。
2. 独立阅读本轮 P1-1 改动代码:
- `supabase/sql/ops_desk_p1_artifacts.sql`
- `supabase/sql/ops_desk_p1_artifacts_rollback.sql`
- `api/ops/store/artifacts.py`
- `api/ops/store/runs.py`
- `api/ops/orchestrator/core.py`
- `api/ops/react_loop.py`
- `tests/ops/test_artifacts.py`
3. 在 `ai-ink-brain-api-python/` 内完整执行 30 声明的验证命令;保留命令输出要点。
4. 执行 `git diff origin/main...HEAD --stat`(在 ai-ink-brain-api-python 内)核对全量变更路径,确认未扩 scope 到 P1-2/3/4、Session 生产图、Agently lab、前端。
5. 按 40-self-check.md 将复核结论回填至 task 正文「### 自检结论(40 复核)· P1-1」小节(不要覆盖 P0 或 30 已有结论)。
6. 与 30 结论逐项差异核对;若不一致,列出差异项并判断阻塞性。
7. 生成可以完整复制的 Prompt,用于直接交给下一棒 50 独立复检执行。
8. 在输出下一棒 Prompt 且 task 40 自检回填已落盘后,按 HANDOFF_AUTO_COMMIT.md 在 ai-ink-brain-api-python/ commit(仅本轮路径;禁止 git add -A;对话报 short-hash)。
9. 禁止自行 push;由 Lead 合并。

**输出要求**
- 复核方法、命令输出、与 30 结论差异核对表、验收项复核表、阻塞项清单、合并建议、下一棒 50 Prompt。
- Judgment(本帽 · 对话末尾必填):experience_capture / gate/risk / hat_self
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# 50 独立复检 + 全局验收帽 · Invoke 快照

| 字段 | 内容 |
| --- | --- |
| **timestamp** | 20260709_1650 |
| **hat** | 50-reinspect |
| **task_slug** | ops-chat-session-sink-p0-p1 |
| **subtask** | P1-1 Artifacts → Supabase |
| **git_branch** | task/ops-chat-session-sink-p0-p1 |
| **worktree_root** | ai-ink-brain-api-python/ |
| **verify_command** | `pytest tests/ops tests/ops_desk -m "not intent_eval and not intent_benchmark" -q && ruff check api/ops` |
| **task_path** | `docs/harness/tasks/active/task_ops_chat_session_sink_p0_p1_v1.md` |
| **30_invoke_path** | `ai-ink-brain-api-python/docs/harness/invokes/by-task/ops-chat-session-sink-p0-p1/invoke_20260709_1622_30_ops_chat_session_sink_p0_p1_P1-1.md` |
| **30_commit** | `api-python@13eb0524` |
| **40_commit** | `Projects@59e4aae` · `api-python@197ce5b7` |
| **audit_review_path** | `ai-ink-brain-api-python/docs/harness/reviews/task_ops_chat_session_sink_p0_p1_v1_audit_R2_20260708.md` |

## 用户消息快照(40 → 50 交接 Prompt)

```text
你正在扮演工作区 Harness「50-reinspect · 独立复检 + 全局验收帽」,严格遵循 docs/harness/prompts/50-reinspect.md。

**输入(已替换占位符)**
- 主 task 路径(相对 Projects/):`docs/harness/tasks/active/task_ops_chat_session_sink_p0_p1_v1.md`
- 逻辑子仓(相对 Projects/):`ai-ink-brain-api-python`
- Worktree 研发目录(所有 git/pytest/ruff 默认 cwd):`ai-ink-brain-api-python`
- 本棒校验范围:**P1-1 Artifacts → Supabase**
- 30 执行者已声明的验证命令:
```bash
pytest tests/ops tests/ops_desk -m "not intent_eval and not intent_benchmark" -q && ruff check api/ops
```
- 30 invoke 快照:`ai-ink-brain-api-python/docs/harness/invokes/by-task/ops-chat-session-sink-p0-p1/invoke_20260709_1622_30_ops_chat_session_sink_p0_p1_P1-1.md`
- 30 commit:`api-python@13eb0524`
- 40 commit:`Projects@59e4aae` · `api-python@197ce5b7`

**你必须完成**
1. 独立阅读 task 正文「### 自检结论(执行者)· P1-1」与「### 自检结论(40 复核)· P1-1」小节。
2. 独立阅读本轮 P1-1 改动代码:
- `supabase/sql/ops_desk_p1_artifacts.sql`
- `supabase/sql/ops_desk_p1_artifacts_rollback.sql`
- `api/ops/store/artifacts.py`
- `api/ops/store/runs.py`
- `api/ops/orchestrator/core.py`
- `api/ops/react_loop.py`
- `tests/ops/test_artifacts.py`
3. 在 `ai-ink-brain-api-python/` 内完整执行 30 声明的验证命令;保留命令输出要点。
4. 额外执行 task §失败路径所列的 `pytest tests/ops/test_artifacts.py -k write_failed -q`。
5. 执行 `git diff origin/main...HEAD --stat`(在 ai-ink-brain-api-python 内)核对全量变更路径,确认未扩 scope 到 P1-2/3/4、Session 生产图、Agently lab、前端。
6. 与 30 commit `api-python@13eb0524`、40 commit `Projects@59e4aae` / `api-python@197ce5b7`、R2 任务审核书面结论逐条核对。
7. 将 50 复检书面结论落盘至 `ai-ink-brain-api-python/docs/harness/reviews/task_ops_chat_session_sink_p0_p1_v1_audit_R1_20260709_50_P1-1.md`。
8. 按 50-reinspect.md 将全局验收结论回填至 task 正文「### 自检结论(50 复检)· P1-1」小节(不要覆盖 P0 或 30/40 已有结论)。
9. 输出:复核方法、命令输出、与 30/40 结论差异核对表、验收项复核表、阻塞项清单、合并建议、Judgment。
10. 在输出结论且 task 50 复检回填已落盘后,按 HANDOFF_AUTO_COMMIT.md 在 `ai-ink-brain-api-python/` 与 `Projects/` 分仓 commit(仅本轮路径;禁止 git add -A;对话报 short-hash)。
11. 禁止自行 push;由 Lead 合并。

**输出要求**
- 复核方法、命令输出、与 30/40 结论差异核对表、验收项复核表、阻塞项清单、合并建议。
- Judgment(本帽 · 对话末尾必填):experience_capture / gate/risk / hat_self
```
Loading
Loading