10 KiB
Calendar Timezone Unification Implementation Plan
For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Eliminate calendar time mismatches by enforcing one end-to-end timezone policy across App input, Agent runtime context, tool execution, and UTC database storage.
Architecture: Keep database schema unchanged (start_at/end_at TIMESTAMPTZ + timezone) and enforce strict runtime normalization. Device timezone is injected from RunAgentInput.forwardedProps, resolved into a single effective_timezone, then written explicitly into tool arguments and persisted as event timezone while timestamps are stored in UTC. Calendar read responses include deterministic event-timezone-rendered values so frontend rendering is stable and no implicit toLocal() conversion remains.
Tech Stack: FastAPI, Pydantic v2, AgentScope runtime/tooling, Flutter (Dart), PostgreSQL TIMESTAMPTZ, pytest, Flutter test.
Chunk 1: Protocol and Backend Runtime Normalization
Task 1: Freeze protocol and timezone precedence contract
Files:
-
Modify:
docs/protocols/agent/run-agent-input.md -
Create:
docs/protocols/calendar/timezone-policy.md -
Step 1: Write protocol delta checklist in docs first
Document the exact policy:
-
event_timezone > device_timezone > profile.timezone > UTC -
event_timezonemust be present in final tool call -
start_at/end_atmust be timezone-aware -
DB stores UTC timestamps and IANA timezone string
-
Step 2: Update RunAgentInput protocol with forwardedProps contract
Add canonical payload example:
{
"forwardedProps": {
"client_time": {
"device_timezone": "America/Los_Angeles",
"client_now_iso": "2026-03-16T09:12:33-07:00",
"client_epoch_ms": 1773658353000
}
}
}
- Step 3: Add calendar timezone policy protocol doc
Include:
-
accepted datetime formats
-
explicit error codes
-
write/read response semantics
-
DST handling rule
-
Step 4: Verify docs consistency
Run: cd backend && uv run python -m pytest tests/unit/core/agentscope/test_system_prompt.py -q
Expected: PASS (no protocol-breaking prompt assumptions)
Task 2: Parse forwarded device time and compute effective timezone
Files:
-
Modify:
backend/src/core/agentscope/schemas/agui_input.py -
Modify:
backend/src/core/agentscope/runtime/runner.py -
Modify:
backend/src/core/agentscope/prompts/system_prompt.py -
Test:
backend/tests/unit/core/agentscope/test_system_prompt.py -
Step 1: Write failing tests for effective timezone resolution
Add tests covering:
-
forwarded
device_timezonepresent -> selected -
missing forwarded timezone -> fallback profile timezone
-
invalid forwarded timezone -> fallback profile timezone
-
Step 2: Run tests to confirm RED
Run: cd backend && uv run pytest tests/unit/core/agentscope/test_system_prompt.py -k timezone -v
Expected: FAIL on new assertions
- Step 3: Implement minimal runtime context extraction
Implement a typed helper in runner path to read:
run_input.forwarded_props.client_time.device_timezoneclient_now_isoclient_epoch_ms
Compute effective_timezone using fixed precedence and pass it into build_system_prompt(...).
- Step 4: Inject effective_timezone into ENV section
Update build_system_prompt env payload to include:
timezone_profiletimezone_devicetimezone_effective
Update guidance sentence to resolve ambiguous time with timezone_effective.
- Step 5: Run tests to confirm GREEN
Run: cd backend && uv run pytest tests/unit/core/agentscope/test_system_prompt.py -v
Expected: PASS
Task 3: Remove timezone ambiguity and hidden fallbacks from calendar write
Files:
-
Modify:
backend/src/core/agentscope/tools/utils/calendar_domain.py -
Modify:
backend/src/core/agentscope/tools/custom/calendar.py -
Modify:
backend/src/v1/schedule_items/schemas.py -
Modify:
backend/src/v1/schedule_items/service.py -
Test:
backend/tests/unit/core/agentscope/test_calendar_tools.py -
Test:
backend/tests/unit/v1/schedule_items/test_schemas.py -
Test:
backend/tests/unit/v1/schedule_items/test_service.py -
Step 1: Write failing tests for forbidden naive datetime and required timezone
Add tests for:
-
naive
start_atrejected -
missing
event_timezonerejected in tool path -
parse failure does not fallback to
now + 1h -
Step 2: Run tests to confirm RED
Run: cd backend && uv run pytest tests/unit/core/agentscope/test_calendar_tools.py tests/unit/v1/schedule_items/test_schemas.py -v
Expected: FAIL on new constraints
- Step 3: Implement strict parsing and normalization
Implementation requirements:
-
parse_iso_datetimerejects naive input -
remove default
Asia/Shanghaiin tool -
remove fallback auto-generated start time
-
validate IANA timezone and normalize
start_at/end_atto UTC before persistence -
Step 4: Enforce service-level invariants
Service invariant set:
-
timezone non-empty and valid IANA
-
end_at is None or end_at >= start_at -
Step 5: Run backend tests
Run: cd backend && uv run pytest tests/unit/core/agentscope/test_calendar_tools.py tests/unit/v1/schedule_items/test_schemas.py tests/unit/v1/schedule_items/test_service.py tests/integration/test_schedule_items_routes.py -v
Expected: PASS
Task 4: Keep DB schema, add non-breaking constraint migration only
Files:
-
Create:
backend/alembic/versions/20260316_000x_schedule_items_time_constraints.py -
Test:
backend/tests/integration/test_schedule_items_routes.py -
Step 1: Write migration test expectation first
Add/extend integration assertion for invalid end_at < start_at returning 422.
- Step 2: Run integration test to confirm RED
Run: cd backend && uv run pytest tests/integration/test_schedule_items_routes.py -k end_at -v
Expected: FAIL
- Step 3: Implement migration with CHECK only (no new columns)
Migration includes:
-
CHECK (end_at IS NULL OR end_at >= start_at) -
Step 4: Run migration + integration test
Run: cd backend && uv run alembic upgrade head && uv run pytest tests/integration/test_schedule_items_routes.py -v
Expected: PASS
Chunk 2: Frontend Deterministic Display and Agent Input Wiring
Task 5: Wire device timezone into RunAgentInput forwardedProps
Files:
-
Modify:
apps/lib/features/chat/data/services/ag_ui_service.dart -
Modify:
apps/lib/features/chat/data/models/ag_ui_event.dart(only if serialization helper is needed) -
Test:
apps/test/features/chat/ag_ui_event_test.dart -
Step 1: Write failing test for forwarded client_time payload
Assert outgoing run request contains:
-
forwardedProps.client_time.device_timezone -
client_now_iso -
client_epoch_ms -
Step 2: Run test to confirm RED
Run: cd apps && flutter test test/features/chat/ag_ui_event_test.dart
Expected: FAIL
- Step 3: Implement payload injection in one place
Add a single helper to build client time context and attach it to run input requests.
- Step 4: Run test to confirm GREEN
Run: cd apps && flutter test test/features/chat/ag_ui_event_test.dart
Expected: PASS
Task 6: Remove implicit local-time rendering and render by event timezone
Files:
-
Modify:
apps/lib/features/calendar/data/models/schedule_item_model.dart -
Modify:
apps/lib/features/calendar/ui/screens/calendar_event_detail_screen.dart -
Modify:
apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart -
Modify:
apps/lib/features/calendar/ui/screens/calendar_month_screen.dart -
Modify:
apps/lib/features/messages/ui/widgets/calendar_message_card.dart -
Modify:
apps/lib/features/calendar/ui/widgets/create_event_sheet.dart -
Test:
apps/test/features/calendar/ui/calendar_time_utils_test.dart -
Test:
apps/test/features/calendar/ui/create_event_sheet_time_align_test.dart -
Step 1: Write failing tests for timezone-specific rendering
Cover cases:
-
same UTC event shows different local clock time under different
event.timezone -
list/day/week/month are consistent for one event
-
create sheet sends explicit timezone in payload
-
Step 2: Run tests to confirm RED
Run: cd apps && flutter test test/features/calendar/ui/calendar_time_utils_test.dart test/features/calendar/ui/create_event_sheet_time_align_test.dart
Expected: FAIL
- Step 3: Implement deterministic time conversion utility
Implement one utility used by all calendar UI surfaces:
- input: UTC datetime + IANA timezone
- output: event-local datetime
Replace direct .toLocal() usage in calendar model/view with this utility.
- Step 4: Enforce explicit timezone on create/update payload
Create/update must always include timezone field from selected event timezone.
- Step 5: Run Flutter tests
Run: cd apps && flutter test test/features/calendar/ui/calendar_time_utils_test.dart test/features/calendar/ui/create_event_sheet_time_align_test.dart
Expected: PASS
Task 7: End-to-end verification matrix and release checklist
Files:
-
Modify:
docs/plans/timezone-e2e-checklist.md -
Test:
backend/tests/integration/test_schedule_items_routes.py -
Test:
apps/test/features/calendar/ui/create_event_sheet_time_align_test.dart -
Step 1: Add reproducible matrix
Matrix axes:
-
device timezone:
America/Los_Angeles,Asia/Shanghai -
profile timezone:
Asia/Shanghai,Europe/Paris -
explicit event timezone:
Asia/Tokyo -
Step 2: Run backend + frontend verification commands
Run:
cd backend && uv run pytest tests/unit/core/agentscope/test_system_prompt.py tests/unit/core/agentscope/test_calendar_tools.py tests/integration/test_schedule_items_routes.py -vcd apps && flutter test test/features/chat/ag_ui_event_test.dart test/features/calendar/ui/calendar_time_utils_test.dart test/features/calendar/ui/create_event_sheet_time_align_test.dart
Expected: all PASS
- Step 3: Manual scenario check
Manual script:
- device timezone set to Los Angeles
- profile timezone set to Shanghai
- ask agent create "明天上午9点开会"
- verify assistant text, tool card, DB UTC value, and calendar detail all align to chosen event timezone semantics
- Step 4: Capture release notes
Record:
- removed hidden timezone defaults
- deterministic precedence
- no schema expansion
Plan complete and saved to docs/superpowers/plans/2026-03-16-calendar-timezone-unification.md. Ready to execute?