experienceos 0.7.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. experienceos/__init__.py +36 -0
  2. experienceos/__main__.py +6 -0
  3. experienceos/ai/__init__.py +52 -0
  4. experienceos/ai/demo.py +60 -0
  5. experienceos/ai/enrich.py +137 -0
  6. experienceos/ai/eval_cases.jsonl +9 -0
  7. experienceos/ai/eval_manifest.json +23 -0
  8. experienceos/ai/evaluation.py +389 -0
  9. experienceos/ai/extraction.py +284 -0
  10. experienceos/ai/factory.py +18 -0
  11. experienceos/ai/interview.py +78 -0
  12. experienceos/ai/mock.py +34 -0
  13. experienceos/ai/prompts.py +126 -0
  14. experienceos/ai/provider.py +264 -0
  15. experienceos/ai/reporting.py +92 -0
  16. experienceos/ai/responses.py +175 -0
  17. experienceos/ai/schemas.py +36 -0
  18. experienceos/ai/tools.py +153 -0
  19. experienceos/ai/transport.py +367 -0
  20. experienceos/ai/workflow.py +305 -0
  21. experienceos/api/__init__.py +5 -0
  22. experienceos/api/app.py +191 -0
  23. experienceos/cli/__init__.py +5 -0
  24. experienceos/cli/app.py +1536 -0
  25. experienceos/cli/render.py +124 -0
  26. experienceos/config.py +132 -0
  27. experienceos/connectors/__init__.py +49 -0
  28. experienceos/connectors/base.py +124 -0
  29. experienceos/connectors/github.py +390 -0
  30. experienceos/connectors/gitrepo.py +250 -0
  31. experienceos/connectors/languages.py +98 -0
  32. experienceos/connectors/projectfiles.py +171 -0
  33. experienceos/connectors/registry.py +54 -0
  34. experienceos/connectors/resume/__init__.py +18 -0
  35. experienceos/connectors/resume/extractor.py +189 -0
  36. experienceos/connectors/resume/parser.py +384 -0
  37. experienceos/core/__init__.py +54 -0
  38. experienceos/core/draft.py +67 -0
  39. experienceos/core/errors.py +74 -0
  40. experienceos/core/fsutil.py +43 -0
  41. experienceos/core/guardrails.py +105 -0
  42. experienceos/core/models.py +269 -0
  43. experienceos/core/ulid.py +50 -0
  44. experienceos/exporters/__init__.py +29 -0
  45. experienceos/exporters/base.py +46 -0
  46. experienceos/exporters/html.py +119 -0
  47. experienceos/exporters/json_resume.py +168 -0
  48. experienceos/exporters/markdown.py +118 -0
  49. experienceos/exporters/registry.py +47 -0
  50. experienceos/exporters/templates/profile.html.tpl +46 -0
  51. experienceos/exporters/templates/profile.md.tpl +10 -0
  52. experienceos/exporters/templates/timeline.md.tpl +9 -0
  53. experienceos/plugins.py +93 -0
  54. experienceos/presentation.py +86 -0
  55. experienceos/py.typed +0 -0
  56. experienceos/services/__init__.py +9 -0
  57. experienceos/services/experiences.py +153 -0
  58. experienceos/services/homeops.py +151 -0
  59. experienceos/services/ingest.py +69 -0
  60. experienceos/services/verify.py +207 -0
  61. experienceos/stats.py +119 -0
  62. experienceos/storage/__init__.py +6 -0
  63. experienceos/storage/fts.py +200 -0
  64. experienceos/storage/locking.py +85 -0
  65. experienceos/storage/migrations.py +83 -0
  66. experienceos/storage/query.py +144 -0
  67. experienceos/storage/store.py +251 -0
  68. experienceos/web/__init__.py +1 -0
  69. experienceos/web/demo.py +138 -0
  70. experienceos/web/server.py +341 -0
  71. experienceos/web/static/app.js +251 -0
  72. experienceos/web/static/index.html +73 -0
  73. experienceos/web/static/style.css +243 -0
  74. experienceos-0.7.2.dist-info/METADATA +334 -0
  75. experienceos-0.7.2.dist-info/RECORD +78 -0
  76. experienceos-0.7.2.dist-info/WHEEL +4 -0
  77. experienceos-0.7.2.dist-info/entry_points.txt +14 -0
  78. experienceos-0.7.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,36 @@
1
+ """ExperienceOS - an open-source personal experience operating system.
2
+
3
+ > Never forget what you have built.
4
+
5
+ ExperienceOS helps developers record, organize, understand and preserve
6
+ every project and creative experience they have taken part in. It turns
7
+ fragmented traces (code, repositories, GitHub activity, resumes, and
8
+ conversations) into structured, evidence-backed Experience Assets.
9
+ """
10
+
11
+ from experienceos.core.models import (
12
+ SCHEMA_VERSION,
13
+ Evidence,
14
+ EvidenceKind,
15
+ Experience,
16
+ ExperienceType,
17
+ Period,
18
+ Source,
19
+ SourceOrigin,
20
+ Status,
21
+ )
22
+
23
+ __version__ = "0.7.2"
24
+
25
+ __all__ = [
26
+ "SCHEMA_VERSION",
27
+ "Evidence",
28
+ "EvidenceKind",
29
+ "Experience",
30
+ "ExperienceType",
31
+ "Period",
32
+ "Source",
33
+ "SourceOrigin",
34
+ "Status",
35
+ "__version__",
36
+ ]
@@ -0,0 +1,6 @@
1
+ """Allow ``python -m experienceos``."""
2
+
3
+ from experienceos.cli.app import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,52 @@
1
+ """AI layer: providers, tools, structured outputs, and durable workflows.
2
+
3
+ M0 shipped the contracts; #010 wired OpenAI-compatible endpoints; the
4
+ checkpointed evidence-brief workflow, its read-only tool registry, the
5
+ evaluation harness, and the provider factory make the layer a testable
6
+ system — "AI propose, human decide" throughout.
7
+ """
8
+
9
+ from experienceos.ai.factory import create_provider
10
+ from experienceos.ai.mock import MockProvider
11
+ from experienceos.ai.prompts import (
12
+ ALL_PROMPTS,
13
+ EXTRACTION_PROMPT_V1,
14
+ INTAKE_INTERVIEW_PROMPT_V1,
15
+ render_prompt,
16
+ )
17
+ from experienceos.ai.provider import (
18
+ LLMProvider,
19
+ Message,
20
+ ModelResponse,
21
+ OpenAICompatibleProvider,
22
+ RecordedProvider,
23
+ ToolCall,
24
+ complete_structured,
25
+ )
26
+ from experienceos.ai.responses import OpenAIResponsesProvider
27
+ from experienceos.ai.schemas import BriefCitation, EvidenceBrief, ProviderHealth
28
+ from experienceos.ai.tools import ExperienceToolRegistry
29
+ from experienceos.ai.workflow import EvidenceBriefWorkflow, WorkflowCheckpointStore
30
+
31
+ __all__ = [
32
+ "ALL_PROMPTS",
33
+ "EXTRACTION_PROMPT_V1",
34
+ "INTAKE_INTERVIEW_PROMPT_V1",
35
+ "BriefCitation",
36
+ "EvidenceBrief",
37
+ "EvidenceBriefWorkflow",
38
+ "ExperienceToolRegistry",
39
+ "LLMProvider",
40
+ "Message",
41
+ "MockProvider",
42
+ "ModelResponse",
43
+ "OpenAICompatibleProvider",
44
+ "OpenAIResponsesProvider",
45
+ "ProviderHealth",
46
+ "RecordedProvider",
47
+ "ToolCall",
48
+ "WorkflowCheckpointStore",
49
+ "complete_structured",
50
+ "create_provider",
51
+ "render_prompt",
52
+ ]
@@ -0,0 +1,60 @@
1
+ # ruff: noqa: RUF001
2
+ """Recorded model turns for the offline demo.
3
+
4
+ The model responses are deterministic, but all three tool calls, argument
5
+ validation, store reads, checkpoints, grounding checks, and structured-output
6
+ validation run through the same production path as a live model.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from experienceos.ai.provider import ModelResponse, RecordedProvider, ToolCall
12
+ from experienceos.ai.schemas import BriefCitation, EvidenceBrief
13
+ from experienceos.core.errors import WorkflowError
14
+ from experienceos.storage import ExperienceStore
15
+
16
+
17
+ def build_recorded_demo_provider(store: ExperienceStore) -> RecordedProvider:
18
+ experiences = store.list_all()
19
+ if not experiences:
20
+ raise WorkflowError("离线演示至少需要一条经历,请先新增或导入经历")
21
+ experience = experiences[0]
22
+ locations = [evidence.location for evidence in experience.evidence]
23
+ gap = (
24
+ []
25
+ if locations
26
+ else [f"{experience.title} 尚未关联证据,请补充仓库、提交、PR 或文档。"]
27
+ )
28
+ output = EvidenceBrief(
29
+ answer=f"本地经历库中最近录入的项目是“{experience.title}”。",
30
+ highlights=[
31
+ experience.description or f"已记录项目“{experience.title}”。",
32
+ *experience.result[:2],
33
+ ],
34
+ citations=[
35
+ BriefCitation(
36
+ experience_id=experience.id,
37
+ claim=f"本地经历库包含项目“{experience.title}”。",
38
+ evidence_locations=locations,
39
+ )
40
+ ],
41
+ evidence_gaps=gap,
42
+ next_actions=["核对引用记录,并为尚无证据支撑的成果补充材料。"],
43
+ )
44
+ return RecordedProvider(
45
+ responses=[
46
+ ModelResponse(
47
+ tool_calls=(
48
+ ToolCall("demo_search", "search_experiences", {"query": "", "limit": 5}),
49
+ ToolCall(
50
+ "demo_get",
51
+ "get_experience",
52
+ {"id_or_prefix": experience.id},
53
+ ),
54
+ ToolCall("demo_stats", "get_evidence_stats", {}),
55
+ )
56
+ ),
57
+ ModelResponse(content=output.model_dump_json()),
58
+ ],
59
+ name="recorded-demo",
60
+ )
@@ -0,0 +1,137 @@
1
+ """Enrich pipeline (#012): AI proposals for one existing record.
2
+
3
+ The model returns a proposal list; this module is the *server-side
4
+ gatekeeper*: proposals targeting anything outside
5
+ {contribution, challenge, solution, result, technology} are dropped
6
+ outright — title, period, evidence and factual numbers can never be
7
+ touched through enrich, no matter what the model says. Accepted
8
+ proposals mutate the record in memory; the CLI's confirmation loop and
9
+ ``ExperienceStore.save`` (which bumps ``updated_at``) do the rest.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import re
16
+ from dataclasses import dataclass
17
+ from typing import Any
18
+
19
+ from experienceos.ai.prompts import ENRICH_PROMPT_V1
20
+ from experienceos.ai.provider import Message
21
+ from experienceos.core.models import Experience
22
+
23
+ ALLOWED_FIELDS = ("contribution", "challenge", "solution", "result", "technology")
24
+ _FENCE_RE = re.compile(r"```(?:json)?\s*(.*?)\s*```", re.DOTALL)
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class EnrichProposal:
29
+ """One in-scope, normalized proposal ready for confirmation."""
30
+
31
+ field: str
32
+ current: str
33
+ suggested_items: tuple[str, ...]
34
+ reason: str
35
+
36
+ @property
37
+ def suggested_display(self) -> str:
38
+ return " | ".join(self.suggested_items)
39
+
40
+
41
+ def build_enrich_messages(experience: Experience) -> list[Message]:
42
+ """System enrich prompt + the record JSON as material."""
43
+ material = json.dumps(experience.to_dict(), ensure_ascii=False, indent=2)
44
+ return [
45
+ Message(role="system", content=ENRICH_PROMPT_V1),
46
+ Message(
47
+ role="user",
48
+ content=f"Record:\n\n{material}\n\nOutput ONLY the JSON array.",
49
+ ),
50
+ ]
51
+
52
+
53
+ def parse_proposals(raw: str) -> list[dict[str, Any]]:
54
+ """Parse the model reply into a list of proposal dicts."""
55
+ text = raw.strip()
56
+ fence = _FENCE_RE.search(text)
57
+ if fence:
58
+ text = fence.group(1)
59
+ else:
60
+ start, end = text.find("["), text.rfind("]")
61
+ if start != -1 and end > start:
62
+ text = text[start : end + 1]
63
+ try:
64
+ data = json.loads(text)
65
+ except json.JSONDecodeError as exc:
66
+ raise ValueError(f"enrich output is not valid JSON: {exc}") from exc
67
+ if isinstance(data, dict):
68
+ if "proposals" not in data:
69
+ raise ValueError("enrich output is not a JSON array")
70
+ data = data["proposals"]
71
+ if not isinstance(data, list):
72
+ raise ValueError("enrich output is not a JSON array")
73
+ return [item for item in data if isinstance(item, dict)]
74
+
75
+
76
+ def normalize_proposals(
77
+ data: list[dict[str, Any]],
78
+ ) -> tuple[list[EnrichProposal], list[str]]:
79
+ """Split raw proposals into (in-scope, rejected descriptions).
80
+
81
+ A proposal is rejected when its field is outside the whitelist or
82
+ its payload is malformed — the record itself is the source of truth,
83
+ so anything questionable is dropped, never guessed.
84
+ """
85
+ accepted: list[EnrichProposal] = []
86
+ rejected: list[str] = []
87
+ for item in data:
88
+ field = item.get("field")
89
+ if field not in ALLOWED_FIELDS:
90
+ rejected.append(
91
+ f"out-of-scope field {field!r} (allowed: {', '.join(ALLOWED_FIELDS)})"
92
+ )
93
+ continue
94
+ suggested = item.get("suggested")
95
+ if isinstance(suggested, str):
96
+ suggested_items = (suggested.strip(),) if suggested.strip() else ()
97
+ elif isinstance(suggested, list):
98
+ suggested_items = tuple(
99
+ name.strip() for name in suggested if isinstance(name, str) and name.strip()
100
+ )
101
+ else:
102
+ suggested_items = ()
103
+ if not suggested_items:
104
+ rejected.append("malformed proposal (suggested missing or empty)")
105
+ continue
106
+ current = item.get("current")
107
+ has_current = isinstance(current, str) and bool(current.strip())
108
+ if field != "technology" and not has_current:
109
+ # STAR rewrites replace an existing item, so the model must
110
+ # name it; technology replaces the whole list instead
111
+ rejected.append("malformed proposal (current missing)")
112
+ continue
113
+ accepted.append(
114
+ EnrichProposal(
115
+ field=field,
116
+ current=current.strip() if has_current else "",
117
+ suggested_items=suggested_items,
118
+ reason=str(item.get("reason", "")).strip(),
119
+ )
120
+ )
121
+ return accepted, rejected
122
+
123
+
124
+ def apply_proposal(experience: Experience, proposal: EnrichProposal) -> None:
125
+ """Mutate *experience* in place; pydantic validates the assignment."""
126
+ if proposal.field == "technology":
127
+ experience.technology = list(proposal.suggested_items)
128
+ return
129
+ items = list(getattr(experience, proposal.field))
130
+ for index, item in enumerate(items):
131
+ if item.casefold() == proposal.current.casefold():
132
+ items[index] = proposal.suggested_items[0]
133
+ break
134
+ else:
135
+ # the model paraphrased the current item; append instead of guessing
136
+ items.append(proposal.suggested_items[0])
137
+ setattr(experience, proposal.field, items)
@@ -0,0 +1,9 @@
1
+ {"id": "search_with_evidence", "question": "找出检索项目,概括技术亮点和可验证结果。", "experiences": [{"id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAV", "title": "Campus Search Engine", "type": "course_project", "period": {"start": "2024-01", "end": "2024-06"}, "description": "Built a Chinese document search engine with BM25 ranking.", "technology": ["Python", "BM25"], "result": ["Top-10 hit rate reached 92% on a 200-query set."], "evidence": [{"kind": "repo", "location": "github.com/example/campus-search"}]}], "expected_tool_sequence": ["search_experiences", "get_experience", "get_evidence_stats"], "expected_terms": ["BM25", "92%"], "label_source": "ai-assisted-synthetic", "expected_behavior_notes": "Search, load the full record, then inspect evidence coverage before citing the stored metric.", "recorded_turns": [{"tool_calls": [{"id": "s1", "name": "search_experiences", "arguments": {"query": "search", "limit": 5}}, {"id": "g1", "name": "get_experience", "arguments": {"id_or_prefix": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAV"}}, {"id": "e1", "name": "get_evidence_stats", "arguments": {}}]}, {"content": {"answer": "Campus Search Engine is the matching retrieval project.", "highlights": ["It uses Python and BM25.", "Top-10 hit rate reached 92%."], "citations": [{"experience_id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAV", "claim": "The archive records BM25 and a 92% hit rate.", "evidence_locations": ["github.com/example/campus-search"]}], "evidence_gaps": [], "next_actions": ["Keep the evaluation report with the repository."]}}]}
2
+ {"id": "missing_evidence", "question": "检查发布自动化的量化结果是否有证据,并给出补证建议。", "experiences": [{"id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAW", "title": "Release Automation", "type": "work", "period": {"start": "2025-01", "end": "2025-08"}, "description": "Automated the release pipeline.", "technology": ["Python", "GitHub Actions"], "result": ["Reduced release time by 40%."], "evidence": []}], "expected_tool_sequence": ["search_experiences", "get_experience", "get_evidence_stats"], "expected_terms": ["40%", "evidence"], "label_source": "ai-assisted-synthetic", "expected_behavior_notes": "Report the stored number while explicitly identifying the missing supporting artifact.", "recorded_turns": [{"tool_calls": [{"id": "s2", "name": "search_experiences", "arguments": {"query": "release", "limit": 5}}, {"id": "g2", "name": "get_experience", "arguments": {"id_or_prefix": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAW"}}, {"id": "e2", "name": "get_evidence_stats", "arguments": {}}]}, {"content": {"answer": "Release Automation records a 40% reduction, but it has no linked evidence.", "highlights": ["Python and GitHub Actions automate the workflow."], "citations": [{"experience_id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAW", "claim": "The stored result says release time fell by 40%.", "evidence_locations": []}], "evidence_gaps": ["The 40% result has no linked evidence."], "next_actions": ["Attach a before-and-after timing report."]}}]}
3
+ {"id": "similar_record_disambiguation", "question": "只总结面向用户的 Release Dashboard,不要混入发布流水线项目。", "experiences": [{"id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAX", "title": "Release Dashboard", "type": "personal", "period": {"start": "2025-03", "end": "2025-05"}, "description": "Built a dashboard for release status.", "technology": ["TypeScript"], "evidence": [{"kind": "repo", "location": "github.com/example/release-dashboard"}]}, {"id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAY", "title": "Release Pipeline", "type": "work", "period": {"start": "2025-02", "end": "2025-06"}, "description": "Automated package publishing.", "technology": ["Python"], "evidence": [{"kind": "pull_request", "location": "github.com/example/platform/pull/42"}]}], "expected_tool_sequence": ["search_experiences", "get_experience"], "expected_terms": ["Dashboard", "TypeScript"], "label_source": "ai-assisted-synthetic", "expected_behavior_notes": "Search returns similar records; the full-record read and citation must select only the dashboard.", "recorded_turns": [{"tool_calls": [{"id": "s3", "name": "search_experiences", "arguments": {"query": "Release", "limit": 5}}, {"id": "g3", "name": "get_experience", "arguments": {"id_or_prefix": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAX"}}]}, {"content": {"answer": "Release Dashboard is the user-facing project.", "highlights": ["It is implemented in TypeScript."], "citations": [{"experience_id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAX", "claim": "The dashboard tracks release status.", "evidence_locations": ["github.com/example/release-dashboard"]}], "evidence_gaps": [], "next_actions": ["Add a screenshot or usability note."]}}]}
4
+ {"id": "direct_record_lookup", "question": "总结 exp_01ARZ3NDEKTSV4RRFFQ69G5FAZ,并保持引用可追溯。", "experiences": [{"id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAZ", "title": "CLI Importer", "type": "personal", "period": {"start": "2024-07", "end": "2024-08"}, "description": "Imported resume entries into structured records.", "technology": ["Python", "Typer"], "evidence": [{"kind": "file", "location": "demo/importer.cast"}]}], "expected_tool_sequence": ["get_experience"], "expected_terms": ["CLI Importer", "Typer"], "label_source": "ai-assisted-synthetic", "expected_behavior_notes": "A full exact ID makes search unnecessary; read the record and cite its real demo.", "recorded_turns": [{"tool_calls": [{"id": "g4", "name": "get_experience", "arguments": {"id_or_prefix": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAZ"}}]}, {"content": {"answer": "CLI Importer converts resume entries into structured records.", "highlights": ["It uses Python and Typer."], "citations": [{"experience_id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FAZ", "claim": "The CLI imports resume entries.", "evidence_locations": ["demo/importer.cast"]}], "evidence_gaps": [], "next_actions": ["Keep the demo recording current."]}}]}
5
+ {"id": "reject_invented_location", "question": "总结证据,但不要虚构地址。", "experiences": [{"id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FB0", "title": "Evidence Guard", "type": "personal", "period": {"start": "2025-04", "end": "2025-04"}, "description": "Validated grounded citations.", "evidence": [{"kind": "repo", "location": "github.com/example/evidence-guard"}]}], "expected_tool_sequence": ["get_experience"], "expected_terms": [], "expected_status": "paused", "expected_error_contains": "unknown evidence locations", "label_source": "ai-assisted-synthetic", "expected_behavior_notes": "The workflow must pause when final structured output invents an evidence location.", "recorded_turns": [{"tool_calls": [{"id": "g5", "name": "get_experience", "arguments": {"id_or_prefix": "exp_01ARZ3NDEKTSV4RRFFQ69G5FB0"}}]}, {"content": {"answer": "Evidence Guard validates citations.", "highlights": [], "citations": [{"experience_id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FB0", "claim": "The project validates citations.", "evidence_locations": ["github.com/example/invented"]}], "evidence_gaps": [], "next_actions": []}}]}
6
+ {"id": "invalid_tool_arguments", "question": "搜索记录并限制返回零条。", "experiences": [], "expected_tool_sequence": ["search_experiences"], "expected_terms": [], "expected_status": "paused", "expected_error_contains": "invalid arguments", "label_source": "ai-assisted-synthetic", "expected_behavior_notes": "Strict tool schemas must reject an out-of-range limit and leave a resumable checkpoint.", "recorded_turns": [{"tool_calls": [{"id": "s6", "name": "search_experiences", "arguments": {"query": "anything", "limit": 0}}]}]}
7
+ {"id": "provider_failure_resume", "question": "总结可恢复项目,并在临时网络错误后继续。", "experiences": [{"id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FB1", "title": "Recoverable Workflow", "type": "work", "period": {"start": "2025-05", "end": "2025-07"}, "description": "Checkpointed long-running work.", "technology": ["Python"], "evidence": [{"kind": "repo", "location": "github.com/example/recoverable"}]}], "expected_tool_sequence": ["get_experience"], "expected_terms": ["Recoverable Workflow", "checkpoint"], "resume_after_error": true, "label_source": "ai-assisted-synthetic", "expected_behavior_notes": "A provider error after tool execution must resume without executing the completed tool twice.", "recorded_turns": [{"tool_calls": [{"id": "g7", "name": "get_experience", "arguments": {"id_or_prefix": "exp_01ARZ3NDEKTSV4RRFFQ69G5FB1"}}]}, {"error": "simulated network failure"}, {"content": {"answer": "Recoverable Workflow checkpoints long-running work.", "highlights": ["The implementation uses Python."], "citations": [{"experience_id": "exp_01ARZ3NDEKTSV4RRFFQ69G5FB1", "claim": "The workflow persists checkpoints.", "evidence_locations": ["github.com/example/recoverable"]}], "evidence_gaps": [], "next_actions": ["Exercise recovery in CI."]}}]}
8
+ {"id": "empty_archive", "question": "档案为空时明确说明证据缺口。", "experiences": [], "expected_tool_sequence": ["search_experiences", "get_evidence_stats"], "expected_terms": ["empty", "evidence"], "label_source": "ai-assisted-synthetic", "expected_behavior_notes": "The agent should inspect the empty archive and finish without fabricating a citation.", "recorded_turns": [{"tool_calls": [{"id": "s8", "name": "search_experiences", "arguments": {"query": "", "limit": 5}}, {"id": "e8", "name": "get_evidence_stats", "arguments": {}}]}, {"content": {"answer": "The experience archive is empty, so no evidence-backed brief can be produced.", "highlights": [], "citations": [], "evidence_gaps": ["No experience records or evidence are available."], "next_actions": ["Record and verify one experience first."]}}]}
9
+ {"id": "invalid_structured_output", "question": "返回严格结构化摘要。", "experiences": [], "expected_tool_sequence": [], "expected_terms": [], "expected_status": "paused", "expected_error_contains": "schema validation", "label_source": "ai-assisted-synthetic", "expected_behavior_notes": "Local Pydantic validation must reject a response missing required structured fields; the workflow gives the model exactly one schema repair round, and a second invalid answer still pauses the run.", "recorded_turns": [{"content": {"answer": "Incomplete output", "highlights": [], "citations": [], "evidence_gaps": []}}, {"content": {"answer": "Still incomplete", "citations": [], "evidence_gaps": []}}]}
@@ -0,0 +1,23 @@
1
+ {
2
+ "manifest_version": 1,
3
+ "dataset": "experience_brief.jsonl",
4
+ "dataset_sha256": "18433be486d4d2b3e374dfe6f27372cb10a934209f45252b9656eb50526b1c29",
5
+ "case_count": 9,
6
+ "created_on": "2026-08-28",
7
+ "data_origin": "ai-assisted-synthetic",
8
+ "label_review": "not-independently-human-reviewed",
9
+ "contains_real_user_data": false,
10
+ "contains_personal_data": false,
11
+ "uses_external_source_records": false,
12
+ "evidence_locations": "placeholder-only",
13
+ "intended_use": [
14
+ "deterministic workflow regression",
15
+ "live-model smoke evaluation"
16
+ ],
17
+ "not_valid_for": [
18
+ "model accuracy claims",
19
+ "representative user-task claims",
20
+ "production performance claims"
21
+ ],
22
+ "license": "MIT repository test fixture"
23
+ }