Tests / Test failed: 2, passed: 849
- ToolCallDecision captures thinking blocks and pre-tool text output
- AnthropicToolCallingClient supports optional extended thinking (budget_tokens + beta header)
- PlannedStep carries rationale and thinking from each LLM decision
- WorldEvent replaces scene_summary with rationale/thinking/page fields (backward-compatible)
- AI planner system prompt instructs reflection before each tool call
- _history_summary() emits compact {page, rationale, action, success} dicts
- Cloud DB migration 0011 adds nullable rationale/thinking columns to planner_decision_log
- OpenAI client extracts reasoning_content into thinking field
5.4 KiB
5.4 KiB
1. ToolCallDecision — capture thinking and text output
- 1.1 Add
thinking: str | None = Noneandtext_output: str | None = Nonefields toToolCallDecisioninruntime/tool_calling_client.py - 1.2 Update
_decision_from_anthropic_response()to iterate all content blocks: collect firstthinking-type block text intothinking, concatenate alltext-type blocks intotext_output, continue totool_useas before - 1.3 Update
_decision_from_openai_response()to extractreasoning_contentfrom the first choice's message intothinkingwhen present - 1.4 Add unit tests: Anthropic response with thinking block →
decision.thinkingpopulated; Anthropic response with text block →decision.text_outputpopulated; response with onlytool_use→ bothNone; OpenAI response withreasoning_content→decision.thinkingpopulated
2. PlannerConfig — extended thinking configuration
- 2.1 Add
thinking_budget_tokens: int | None = NonetoPlannerConfiginruntime/planner_config.py - 2.2 Add
AI_PLANNER_THINKING_BUDGET_TOKENSenv var constant and_parse_thinking_budget()helper; wire intoload_config() - 2.3 Update
AnthropicToolCallingClient._create_message(): whenthinking_budget_tokensis set, addthinking: {type: "enabled", budget_tokens: N}to kwargs, addinterleaved-thinking-2025-05-14beta header viabetasparameter, and enforcemax_tokens >= thinking_budget_tokens + 1 - 2.4 Add unit tests:
load_config()with env var set →thinking_budget_tokensparsed;_create_message()with budget set → kwargs include thinking param and beta header; without budget → no thinking param in kwargs
3. PlannedStep — carry rationale and thinking
- 3.1 Add
rationale: str | None = Noneandthinking: str | None = Noneto thePlannedStepdataclass inruntime/planner.py - 3.2 Update
AIPlanner.plan()inruntime/ai_planner.py: populatePlannedStep.rationale = decision.text_outputandPlannedStep.thinking = decision.thinking
4. Planner prompts — reflection instruction
- 4.1 Update
PLANNER_SYSTEM_PROMPTinruntime/planner_prompts.pyto instruct the AI to output a short text block (1–2 sentences) before each tool call: first assessing whether the previous action achieved its intended effect (or noting "first step" if no history), then stating the intent of the current action - 4.2 Verify via prompt review that the instruction is consistent with
tool_choice: {type: "any"}(text blocks are already allowed before tool_use blocks — no API change needed)
5. History format — compact rationale-based representation
- 5.1 Update
_history_summary()inruntime/ai_planner.pyto return compact dicts{page, rationale, action, success}instead of[event.to_dict() for event in world.history] - 5.2 Add unit test:
_history_summary()with a populatedWorldStatereturns list of compact dicts without scene element data
6. WorldEvent — rationale, thinking, page fields
- 6.1 Update
WorldEventinworld/models.py: makescene_summaryoptional (SemanticScene | Scene | None = None), addrationale: str | None = None,thinking: str | None = None,page: str | None = None - 6.2 Update
WorldEvent.to_dict()to includerationale,thinking,pagefields; keepscene_summaryserialisation for backward compatibility - 6.3 Update
WorldModel._append_history()inworld/model.pyto passrationale=step.rationale,thinking=step.thinking,page=world_state.current_pagewhen constructingWorldEvent;scene_summarybecomes optional (passNoneor existing value as appropriate) - 6.4 Add unit tests:
WorldEventwith rationale/thinking/page →to_dict()includes those fields;WorldEventwith all-None optional fields → readable without error;_append_history()picks upcurrent_pagefromWorldStateat time of call
7. Cloud DB — planner_decision_log extension
- 7.1 Add
thinkingandrationalenullable TEXT columns to theplanner_decision_logtable inpackages/cloud-platform/cloud/db_models.py - 7.2 Create a new Alembic migration (e.g.
0010_planner_decision_log_reflection) adding the two nullable columns; verify it applies cleanly on both SQLite and PostgreSQL migration paths - 7.3 Add
thinking: str | None = Noneandrationale: str | None = NonetoPlannerDecisionRecordinpackages/cloud-platform/cloud/internal_api/models.py - 7.4 Update
record_planner_decision()inpackages/cloud-platform/cloud/internal_api/api.pyto writethinkingandrationalefrom the incomingPlannerDecisionRecordto the new columns - 7.5 Add unit tests: decision record with rationale/thinking → both stored; decision record with nulls → stored without error; existing rows queried → both columns read as NULL without error
8. End-to-end verification
- 8.1 Run full non-integration test suite (
uv run --all-packages pytest -m "not integration") and confirm no regressions - 8.2 Run
ruff checkandruff format --checkon all modified files - 8.3 Run
python -m compileallon modified packages - 8.4 Run
openspec validate --strict --change "planner-reflection-history"and confirm all artifacts pass validation - 8.5 Manual smoke test (optional, requires Host Agent + Appium + iPhone): verify that a live task run produces non-null
rationaleinWorldEventhistory and that the history prompt passed to the LLM is in compact format