---
phase: 10-agent-capabilities
plan: "02"
subsystem: agent-capabilities
tags: [calendar, oauth, google, tools, cap-05, cap-06]
dependency_graph:
  requires: [10-01]
  provides: [CAP-05, CAP-06]
  affects: [orchestrator, gateway, shared-api]
tech_stack:
  added: [google-auth, google-api-python-client]
  patterns: [per-tenant-oauth, token-refresh-writeback, natural-language-tool-results]
key_files:
  created:
    - packages/shared/shared/api/calendar_auth.py
    - tests/unit/test_calendar_auth.py
    - tests/unit/test_calendar_lookup.py
    - migrations/versions/013_google_calendar_channel.py
  modified:
    - packages/orchestrator/orchestrator/tools/builtins/calendar_lookup.py
    - packages/orchestrator/orchestrator/tools/registry.py
    - packages/orchestrator/orchestrator/agents/builder.py
    - packages/shared/shared/api/__init__.py
    - packages/gateway/gateway/main.py
decisions:
  - "calendar_lookup receives _session param for test injection — production obtains session from async_session_factory"
  - "Token write-back is non-fatal: refresh failure logged but API result still returned"
  - "requires_confirmation=False for calendar CRUD — user intent (asking agent to book) is the confirmation"
  - "build() imported at module level for patchability in tests (try/except ImportError handles missing dep)"
  - "Tool result formatting instruction added to build_system_prompt when agent has tool_assignments (CAP-06)"
metrics:
  duration: ~10m
  completed: "2026-03-26"
  tasks: 2
  files: 9
---

# Phase 10 Plan 02: Google Calendar OAuth and Calendar Tool CRUD Summary

Per-tenant Google Calendar OAuth install/callback with encrypted token storage, full CRUD calendar tool replacing the service account stub, and natural language tool result formatting (CAP-05, CAP-06).

## Tasks Completed

### Task 1: Google Calendar OAuth endpoints and calendar tool replacement (TDD)

**Files created/modified:**
- `packages/shared/shared/api/calendar_auth.py` — OAuth install/callback/status endpoints
- `packages/orchestrator/orchestrator/tools/builtins/calendar_lookup.py` — Per-tenant OAuth calendar tool
- `migrations/versions/013_google_calendar_channel.py` — Add google_calendar to CHECK constraint
- `tests/unit/test_calendar_auth.py` — 6 tests for OAuth endpoints
- `tests/unit/test_calendar_lookup.py` — 10 tests for calendar tool

**Commit:** `08572fc`

What was built:
- `calendar_auth_router` at `/api/portal/calendar` with 3 endpoints:
  - `GET /install?tenant_id=` — generates HMAC-signed state, returns Google OAuth URL with offline/consent
  - `GET /callback?code=&state=` — verifies HMAC state, exchanges code for tokens, upserts ChannelConnection
  - `GET /{tenant_id}/status` — returns `{"connected": bool}`
- `calendar_lookup.py` fully replaced — no more `GOOGLE_SERVICE_ACCOUNT_KEY` dependency:
  - `action="list"` — fetches events for date, formats as `- HH:MM: Event title`
  - `action="check_availability"` — lists busy slots or "entire day is free"
  - `action="create"` — creates event with summary/start/end, returns confirmation
  - Token auto-refresh: google-auth refreshes expired access tokens, updated token written back to DB
  - Returns informative messages for missing tenant_id, no connection, and errors

### Task 2: Mount new API routers and update tool schema + prompt builder

**Files modified:**
- `packages/shared/shared/api/__init__.py` — export `kb_router` and `calendar_auth_router`
- `packages/gateway/gateway/main.py` — mount kb_router and calendar_auth_router
- `packages/orchestrator/orchestrator/tools/registry.py` — updated calendar_lookup schema with CRUD params
- `packages/orchestrator/orchestrator/agents/builder.py` — add tool result formatting instruction (CAP-06)

**Commit:** `a64634f`

What was done:
- KB and Calendar Auth routers mounted on gateway under Phase 10 section
- calendar_lookup schema updated: `action` (enum), `event_summary`, `event_start`, `event_end` added
- `required` updated to `["date", "action"]`
- `build_system_prompt()` now appends "Never show raw data or JSON to user" when agent has tool_assignments
- Confirmed CAP-04 (http_request): in registry, works, no changes needed
- Confirmed CAP-07 (audit logging): executor.py calls `audit_logger.log_tool_call()` on every tool invocation

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 2 - Missing functionality] Module-level imports for patchability**
- **Found during:** Task 1 TDD GREEN phase
- **Issue:** `KeyEncryptionService` and `googleapiclient.build` imported lazily (inside function), making them unpatchable in tests with standard `patch()` calls
- **Fix:** Added module-level imports with try/except ImportError guard for the google library optional dep; `settings` and `KeyEncryptionService` imported at module level
- **Files modified:** `packages/orchestrator/orchestrator/tools/builtins/calendar_lookup.py`
- **Commit:** `08572fc`

**2. [Rule 1 - Bug] Test patched non-existent module attribute**
- **Found during:** Task 1 TDD GREEN phase
- **Issue:** Tests patched `get_async_session` and `KeyEncryptionService` before those names existed at module level; tests also needed `settings` patched to bypass `platform_encryption_key` check
- **Fix:** Updated tests to pass `_session` directly (no need to patch `get_async_session`), extracted `_make_mock_settings()` helper, added `patch(_PATCH_SETTINGS)` to all action tests
- **Files modified:** `tests/unit/test_calendar_lookup.py`
- **Commit:** `08572fc`

**3. [Already done] google_client_id/secret in Settings and GOOGLE_CALENDAR in ChannelTypeEnum**
- These were already committed in plan 10-01 — no action needed for this plan

## Requirements Satisfied

- **CAP-05:** Calendar availability checking and event creation — per-tenant OAuth, list/check_availability/create actions
- **CAP-06:** Natural language tool results — formatting instruction added to system prompt; calendar_lookup returns human-readable strings, not raw JSON

## Self-Check: PASSED

All files verified:
- FOUND: packages/shared/shared/api/calendar_auth.py
- FOUND: packages/orchestrator/orchestrator/tools/builtins/calendar_lookup.py
- FOUND: migrations/versions/013_google_calendar_channel.py
- FOUND: tests/unit/test_calendar_auth.py
- FOUND: tests/unit/test_calendar_lookup.py
- FOUND: commit 08572fc (Task 1)
- FOUND: commit a64634f (Task 2)
