A LangGraph-based workflow that turns rough work notes into a polished, manager-friendly daily report with evidence-aware Git enrichment and explicit human approval.
The agent structures your input, pulls in today's git commits as context, selects (or lets you choose) a report template, drafts and refines the content from a manager's perspective, then pauses for your review. You can accept, edit, or request revisions (up to 3 rounds) before the report is saved and exported as a markdown file. Interrupted runs remain recoverable from their LangGraph checkpoints.
Built as a portfolio project demonstrating: LangGraph state machine design, Human-in-the-Loop interrupt/resume with SQLite persistence, multi-node Pydantic structured outputs, and Streamlit UI integration.
User input: "今天修了个 bug,跑了个数据,开了两个会"
→ extract: { tasks: ["修复登录bug"], outputs: ["数据报告"], blockers: [] }
→ enrich: git log pulled: "fix(auth): resolve token expiry edge case"
→ template: 技术型
→ draft: structured first draft
→ polish: "完成登录模块 Token 过期边界问题修复..."
[HITL pause — user reviews in Streamlit]
User: "突出这是登录稳定性修复,不要添加未提供的数据"
→ revise → polish (round 2)
User: approve
→ saved to history.db
→ exported to exports/daily_report_2026-05-06_<report-id>.md
- Python 3.10+
- An LLM API key — pick one backend:
- SiliconFlow (domestic, free credits, recommended for China) — https://cloud.siliconflow.cn/
- Official OpenAI or another OpenAI-compatible provider
- Anthropic (direct API key or corporate proxy)
git clone https://github.com/xyma2003/workdiary-agent.git
cd workdiary-agent# Option A: venv
python3 -m venv .venv
source .venv/bin/activate # macOS/Linux
# .venv\Scripts\activate # Windows
# Option B: conda
conda create -n workdiary python=3.10
conda activate workdiarypip install -r requirements.txtThe app supports SiliconFlow, official OpenAI, other OpenAI-compatible providers, and Anthropic. Provider-specific keys are intentionally separated so an official OpenAI key is never sent to a third-party endpoint by default.
Option A — SiliconFlow / OpenAI-compatible (default, recommended for domestic use):
# .env
LLM_PROVIDER=siliconflow
SILICONFLOW_API_KEY=sk-...
SILICONFLOW_MODEL=Qwen/Qwen3-32B
WORKDIARY_TIMEZONE=Asia/Shanghai
# Optional for deployments or when launching outside the repository directory
# WORKDIARY_DATA_DIR=/absolute/path/to/workdiary-dataGet a SiliconFlow key at https://cloud.siliconflow.cn/. See .env.example for all provider configurations.
Option B — Official OpenAI:
LLM_PROVIDER=openai
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-miniOption C — Direct Anthropic API key:
# .env
LLM_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-api03-...Option D — Corporate/internal proxy:
# .env
LLM_PROVIDER=anthropic
ANTHROPIC_BASE_URL=https://your-proxy-base-url
ANTHROPIC_AUTH_TOKEN=your-auth-token
# Optional: extra headers required by your proxy, newline-separated "Key: Value" pairs
ANTHROPIC_CUSTOM_HEADERS=X-Custom-Header: valueThe app auto-loads
.envviapython-dotenv. Existing deployment or shell variables take precedence over the local file.
streamlit run app.pyOpen http://localhost:8501 in your browser.
- Enter a rough description of your day (口语化输入, any style) and optionally choose a template instead of automatic routing
- Optionally paste a git repo path and author/email to pull in only your exact-identity commits from the report date in the configured timezone
- Optionally paste raw data/metrics for the agent to extract and include
- Click 生成日报 — the agent runs through all nodes and pauses for your review
- Read the draft and its deterministic fact warnings, edit inline if needed, then 接受 or 修改(number warnings require acknowledgement; potential credentials must be removed; final save always requires approval)
- The final report is saved to history and exported as a markdown file in
exports/ - Past reports appear in the 历史记录 sidebar tab, with content search, template/date filters, pagination, editing, and confirmed deletion
- If generation is interrupted or the browser closes during review, reopen it from 恢复未完成日报
Deleting a saved report removes its history row first and attempts to clean up its
managed Markdown export. If cleanup fails, the page shows a partial-success
warning and a 待清理导出 → 重试清理 action. Pending tasks are stored in
history.db, so they remain available after a refresh or restart, even when no
reports remain. Missing files count as already cleaned up. Files outside the export
directory, paths containing symlinks, and exports still used by another report are
preserved with an explicit notice.
- Work notes, pasted metrics, and selected Git commit subjects are sent to the configured LLM provider. Common credential formats are redacted before model calls, but this is best-effort; do not submit data your provider is not allowed to process. The original input remains stored locally in history after approval.
- Git enrichment skips commit collection when it cannot resolve an explicit author/email or the repository's local
user.email/user.name; it never attributes every contributor's commits to you. - User text and repository-derived text are delimited as untrusted source data in prompts. This reduces prompt-injection risk but is not a complete security boundary.
- Repository paths are read from the machine running Streamlit. Keep the app local or add deployment-level access controls before exposing it to other users.
User input
│
▼
extract ── structured extraction (tasks / outputs / blockers / progress)
│
▼
enrich ── git log (today's commits) + LLM extraction of data metrics
│
▼
route_template ── TemplateRouterAgent subgraph (技术型 / 业务型 / 混合型)
│
▼
draft ── template-specific first draft
│
▼
polish ── rewrite from manager's perspective, emphasise outcomes
│
▼
review ── interrupt() → Streamlit HITL pause
│
┌─┴──────────────┐
approve revise (up to 3×)
│ │
save revise_node → polish (loop)
│
exports/ + history.db
Key design decisions:
| Decision | Rationale |
|---|---|
| "Manager's perspective" as a separate polish node | Decouples tone/framing from content generation; polish can be reused across templates |
| TemplateRouterAgent as a subgraph | Keeps classification state isolated from the main graph; accuracy still requires evaluation |
| interrupt() inside review node | Gives fine-grained control (pause mid-node with context payload); more flexible than compile-level interrupt_before |
| Two SQLite files | graph_state.db is owned exclusively by LangGraph's SqliteSaver; mixing app data into it breaks serialisation |
| Revision limit (3×) | Applies all three revisions, then disables further model revisions while preserving manual edit and explicit approval |
| Checkpoint recovery | Incomplete graph threads are discovered directly from the LangGraph checkpointer; retry and review use the same persisted state |
| Checkpoint lifecycle | Completed UI runs remove their graph checkpoints after the approved report is persisted; unfinished runs remain recoverable |
| Stable data directory | WORKDIARY_DATA_DIR can place both databases and exports on a durable volume independent of the launch directory |
| Pre-save fact warnings | Checks compare the report and model-derived metric summary against original input/Git/data, require acknowledgement, and block likely credentials |
| Synchronized CRUD | Saved-report edits update SQLite and the Markdown export together; deletion is confirmed and restricted to managed export paths |
workdiary-agent/
├── app.py # Streamlit UI — generation page + history page
├── pyproject.toml # Package metadata, dependencies, pytest markers
├── .github/workflows/ # Offline test CI
├── evals/ # Labeled AI-quality evaluation cases
├── requirements.txt
├── workdiary_agent/
│ ├── graph.py # StateGraph assembly, conditional edges, checkpointer init
│ ├── state.py # AgentState TypedDict + StructuredInfo Pydantic model
│ ├── recovery.py # Discover incomplete checkpoint threads
│ ├── time_utils.py # Shared work-day timezone calculation
│ ├── paths.py # Stable runtime database/export paths
│ ├── redaction.py # Best-effort secret redaction before LLM calls
│ ├── quality.py # Deterministic pre-save factual-risk checks
│ ├── report_service.py # Coordinated history update/delete + export sync
│ ├── utils.py # make_llm() factory + validate_repo_path()
│ ├── nodes/
│ │ ├── extract.py # Structured extraction via with_structured_output
│ │ ├── enrich.py # Git log reader + LLM metric extraction
│ │ ├── route_template.py # Calls TemplateRouterAgent subgraph
│ │ ├── draft.py # Template-specific first draft (3 templates)
│ │ ├── polish.py # Manager-perspective rewrite (accepts revision feedback)
│ │ ├── review.py # HITL interrupt node
│ │ ├── revise.py # Increments revision_count
│ │ └── save.py # Persist to history.db + export markdown
│ ├── router/
│ │ └── agent.py # TemplateRouterAgent subgraph (analyse → decide)
│ └── storage/
│ ├── sqlite.py # history.db read/write API
│ └── export.py # Markdown file export to exports/
├── scripts/ # Manual integration test scripts
│ ├── test_hitl_cycle.py
│ ├── test_skeleton.py
│ └── evaluate_router.py # Live routing accuracy/latency benchmark
├── tests/ # pytest unit tests (5 phases)
│ ├── test_graph_skeleton.py
│ ├── test_phase02_llm_nodes.py
│ ├── test_phase03_enrichment.py
│ ├── test_phase04_hitl.py
│ └── test_phase05_storage.py
└── exports/ # Auto-created; exported markdown reports
# Offline unit/integration tests (no API key or model cost)
python -m pytest tests/ -m "not integration" -v
# Live provider checks (requires a configured .env and incurs model calls)
python -m pytest tests/ -m integration -v
# Labeled router benchmark; the router makes two model calls per case
python scripts/evaluate_router.py --limit 3
python scripts/evaluate_router.py --output eval-results/router.jsonThe routing dataset contains 30 balanced cases across the three templates. Do not claim an accuracy number until the benchmark has been run against the specific provider/model being discussed and the boundary labels have been reviewed.
| Component | Library | Version |
|---|---|---|
| Agent orchestration | LangGraph | 1.1.9 |
| LLM (default) | SiliconFlow / OpenAI-compatible via langchain-openai | — |
| LLM (alt) | Claude via langchain-anthropic | 1.4.1 |
| Structured outputs | Pydantic | 2.x |
| HITL persistence | SQLite (langgraph-checkpoint-sqlite) | 3.0.3 |
| UI | Streamlit | 1.x |
| Git context | GitPython | 3.1.47 |
Built a LangGraph agent that converts unstructured daily work notes into polished manager-facing reports, featuring a Human-in-the-Loop review loop with full state persistence across Streamlit reruns, a two-step template classification subgraph, and git commit enrichment. (LangGraph · Claude API · Pydantic · Streamlit · SQLite)