Files
konstruct/.planning/phases/10-agent-capabilities/10-02-PLAN.md
Adolfo Delorenzo eae4b0324d
Some checks failed
CI / Backend Tests (push) Has been cancelled
CI / Portal E2E (push) Has been cancelled
docs(10): create phase plan
2026-03-25 23:33:27 -06:00

15 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
phase plan type wave depends_on files_modified autonomous requirements user_setup must_haves
10-agent-capabilities 02 execute 1
packages/shared/shared/api/calendar_auth.py
packages/orchestrator/orchestrator/tools/builtins/calendar_lookup.py
packages/orchestrator/orchestrator/tools/registry.py
tests/unit/test_calendar_lookup.py
tests/unit/test_calendar_auth.py
true
CAP-05
CAP-06
service why env_vars dashboard_config
google-cloud Google Calendar OAuth for per-tenant calendar access
name source
GOOGLE_CLIENT_ID Google Cloud Console -> APIs & Services -> Credentials -> OAuth 2.0 Client ID (Web application)
name source
GOOGLE_CLIENT_SECRET Google Cloud Console -> APIs & Services -> Credentials -> OAuth 2.0 Client ID secret
task location
Create OAuth 2.0 Client ID (Web application type) Google Cloud Console -> APIs & Services -> Credentials
task location
Add authorized redirect URI: {PORTAL_URL}/api/portal/calendar/callback Google Cloud Console -> Credentials -> OAuth client -> Authorized redirect URIs
task location
Enable Google Calendar API Google Cloud Console -> APIs & Services -> Library -> Google Calendar API
truths artifacts key_links
Tenant admin can initiate Google Calendar OAuth from the portal and authorize calendar access
Calendar OAuth callback exchanges code for tokens and stores them encrypted per tenant
Calendar tool reads per-tenant OAuth tokens from channel_connections and calls Google Calendar API
Calendar tool supports list events, check availability, and create event actions
Token auto-refresh works — expired access tokens are refreshed via stored refresh_token and written back to DB
Tool results are formatted as natural language (no raw JSON)
path provides exports
packages/shared/shared/api/calendar_auth.py Google Calendar OAuth install + callback endpoints
calendar_auth_router
path provides exports
packages/orchestrator/orchestrator/tools/builtins/calendar_lookup.py Per-tenant OAuth calendar tool with list/create/check_availability
calendar_lookup
path provides
tests/unit/test_calendar_lookup.py Unit tests for calendar tool with mocked Google API
from to via pattern
packages/shared/shared/api/calendar_auth.py channel_connections table Upsert ChannelConnection(channel_type='google_calendar') with encrypted token google_calendar.*encrypt
from to via pattern
packages/orchestrator/orchestrator/tools/builtins/calendar_lookup.py channel_connections table Load encrypted token, decrypt, build Credentials, call Google API Credentials.*refresh_token
Build Google Calendar OAuth per-tenant integration and replace the service-account stub with full CRUD calendar tool.

Purpose: Enables CAP-05 (calendar availability checking + event creation) by replacing the service account stub in calendar_lookup.py with per-tenant OAuth token lookup. Also addresses CAP-06 (natural language tool results) by ensuring calendar and all tool outputs are formatted as readable text.

Output: Google Calendar OAuth install/callback endpoints, fully functional calendar_lookup tool with list/create/check_availability actions, encrypted per-tenant token storage, token auto-refresh with write-back.

<execution_context> @/home/adelorenzo/.claude/get-shit-done/workflows/execute-plan.md @/home/adelorenzo/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/10-agent-capabilities/10-CONTEXT.md @.planning/phases/10-agent-capabilities/10-RESEARCH.md

From packages/shared/shared/api/channels.py:

channels_router = APIRouter(prefix="/api/portal/channels", tags=["channels"])

def _generate_oauth_state(tenant_id: uuid.UUID) -> str:
    """HMAC-SHA256 signed state with embedded tenant_id + nonce."""
    ...

def _verify_oauth_state(state: str) -> uuid.UUID:
    """Verify HMAC signature, return tenant_id. Raises HTTPException on failure."""
    ...

From packages/shared/shared/crypto.py:

class KeyEncryptionService:
    def encrypt(self, plaintext: str) -> str: ...
    def decrypt(self, ciphertext: str) -> str: ...

From packages/shared/shared/models/tenant.py:

class ChannelConnection(Base):
    __tablename__ = "channel_connections"
    id: Mapped[uuid.UUID]
    tenant_id: Mapped[uuid.UUID]
    channel_type: Mapped[ChannelTypeEnum]  # TEXT + CHECK in DB
    workspace_id: Mapped[str]
    config: Mapped[dict]  # JSON — stores encrypted token
    created_at: Mapped[datetime]

From packages/shared/shared/config.py (after Plan 01):

class Settings(BaseSettings):
    google_client_id: str = ""
    google_client_secret: str = ""
Task 1: Google Calendar OAuth endpoints and calendar tool replacement packages/shared/shared/api/calendar_auth.py, packages/orchestrator/orchestrator/tools/builtins/calendar_lookup.py, tests/unit/test_calendar_lookup.py, tests/unit/test_calendar_auth.py - OAuth install endpoint returns redirect URL with HMAC-signed state containing tenant_id - OAuth callback verifies HMAC state, exchanges code for tokens, encrypts and stores in channel_connections as google_calendar type - OAuth callback redirects to portal settings page with connected=true param - calendar_lookup(date, action="list", tenant_id=...) loads encrypted token from DB, decrypts, calls Google Calendar API, returns formatted event list - calendar_lookup(date, action="create", event_summary=..., event_start=..., event_end=..., tenant_id=...) creates a Google Calendar event and returns confirmation - calendar_lookup(date, action="check_availability", tenant_id=...) returns free/busy summary - calendar_lookup returns informative message when no Google Calendar is connected for tenant - Token refresh: if access_token expired, google-auth auto-refreshes, updated token written back to DB - All results are natural language strings, not raw JSON 1. **Calendar OAuth router** (`packages/shared/shared/api/calendar_auth.py`): - calendar_auth_router = APIRouter(prefix="/api/portal/calendar", tags=["calendar"]) - Import and reuse _generate_oauth_state / _verify_oauth_state from channels.py (or extract to shared utility if private) - If they are private (_prefix), create equivalent functions in this module using the same HMAC pattern - GET /install?tenant_id={id}: - Guard with require_tenant_admin - Generate HMAC-signed state with tenant_id - Build Google OAuth URL: https://accounts.google.com/o/oauth2/v2/auth with: - client_id from settings - redirect_uri = settings.portal_url + "/api/portal/calendar/callback" - scope = "https://www.googleapis.com/auth/calendar" (full read+write per locked decision) - state = hmac_state - access_type = "offline" (to get refresh_token) - prompt = "consent" (force consent to always get refresh_token) - Return {"url": oauth_url}
   - GET /callback?code={code}&state={state}:
     - NO auth guard (external redirect from Google — no session cookie)
     - Verify HMAC state to recover tenant_id
     - Exchange code for tokens using google_auth_oauthlib or httpx POST to https://oauth2.googleapis.com/token
     - Encrypt token JSON with KeyEncryptionService (Fernet)
     - Upsert ChannelConnection(tenant_id=tenant_id, channel_type="google_calendar", workspace_id=str(tenant_id), config={"token": encrypted_token})
     - Redirect to portal /settings?calendar=connected

   - GET /{tenant_id}/status:
     - Guard with require_tenant_member
     - Check if ChannelConnection with channel_type='google_calendar' exists for tenant
     - Return {"connected": true/false}

2. **Replace calendar_lookup.py** entirely:
   - Remove all service account code
   - New signature: async def calendar_lookup(date: str, action: str = "list", event_summary: str | None = None, event_start: str | None = None, event_end: str | None = None, calendar_id: str = "primary", tenant_id: str | None = None, **kwargs) -> str
   - If no tenant_id: return "Calendar not available: missing tenant context."
   - Load ChannelConnection(channel_type='google_calendar', tenant_id=tenant_uuid) from DB
   - If not found: return "Google Calendar is not connected for this tenant. Ask an admin to connect it in Settings."
   - Decrypt token JSON, build google.oauth2.credentials.Credentials
   - Build Calendar service: build("calendar", "v3", credentials=creds, cache_discovery=False)
   - Run API call in thread executor (same pattern as original — avoid blocking event loop)
   - action="list": list events for date, format as "Calendar events for {date}:\n- {time}: {summary}\n..."
   - action="check_availability": list events, format as "Busy slots on {date}:\n..." or "No events — the entire day is free."
   - action="create": insert event with summary, start, end, return "Event created: {summary} from {start} to {end}"
   - After API call: check if credentials.token changed (refresh occurred) — if so, encrypt and UPDATE channel_connections.config with new token
   - All errors return human-readable messages, never raw exceptions

3. **Update tool registry** if needed — ensure calendar_lookup parameters schema includes action, event_summary, event_start, event_end fields so LLM knows about CRUD capabilities. Check packages/orchestrator/orchestrator/tools/registry.py for the calendar_lookup entry and update its parameters JSON schema.

4. **Tests** (write BEFORE implementation):
   - test_calendar_lookup.py: mock Google Calendar API (googleapiclient.discovery.build), mock DB session to return encrypted token, test list/create/check_availability actions, test "not connected" path, test token refresh write-back
   - test_calendar_auth.py: mock httpx for token exchange, test HMAC state generation/verification, test callback stores encrypted token
cd /home/adelorenzo/repos/konstruct && python -m pytest tests/unit/test_calendar_lookup.py tests/unit/test_calendar_auth.py -x -q Google Calendar OAuth install/callback endpoints work. Calendar tool loads per-tenant tokens, supports list/create/check_availability, formats results as natural language. Token refresh writes back to DB. Service account stub completely removed. All tests pass. Task 2: Mount new API routers on gateway and update tool response formatting packages/gateway/gateway/main.py, packages/orchestrator/orchestrator/tools/registry.py, packages/orchestrator/orchestrator/agents/prompt.py 1. **Mount routers on gateway** (`packages/gateway/gateway/main.py`): - Import kb_router from shared.api.kb and include it on the FastAPI app (same pattern as channels_router, billing_router, etc.) - Import calendar_auth_router from shared.api.calendar_auth and include it on the app - Verify both are accessible via curl or import
2. **Update tool registry** (`packages/orchestrator/orchestrator/tools/registry.py`):
   - Update calendar_lookup tool definition's parameters schema to include:
     - action: enum ["list", "check_availability", "create"] (required)
     - event_summary: string (optional, for create)
     - event_start: string (optional, ISO 8601 with timezone, for create)
     - event_end: string (optional, ISO 8601 with timezone, for create)
     - date: string (required, YYYY-MM-DD format)
   - Update description to mention CRUD capabilities: "Look up, check availability, or create calendar events"

3. **Tool result formatting check** (CAP-06):
   - Review agent runner prompt — the LLM already receives tool results as 'tool' role messages and formulates a response. Verify the system prompt does NOT contain instructions to dump raw JSON.
   - If the system prompt builder (`packages/orchestrator/orchestrator/agents/prompt.py` or similar) has tool-related instructions, ensure it says: "When using tool results, incorporate the information naturally into your response. Never show raw data or JSON to the user."
   - If no such instruction exists, add it as a tool usage instruction appended to the system prompt when tools are assigned.

4. **Verify CAP-04 (HTTP request tool)**: Confirm http_request.py needs no changes — it already works. Just verify it's in the tool registry and functions correctly.

5. **Verify CAP-07 (audit logging)**: Confirm executor.py already calls audit_logger.log_tool_call() on every invocation (it does — verified in code review). No changes needed.
cd /home/adelorenzo/repos/konstruct && python -c "from shared.api.kb import kb_router; from shared.api.calendar_auth import calendar_auth_router; print('Routers import OK')" && python -c "from orchestrator.tools.registry import TOOL_REGISTRY; print(f'Registry has {len(TOOL_REGISTRY)} tools')" KB and Calendar Auth routers mounted on gateway. Calendar tool registry updated with CRUD parameters. System prompt includes tool result formatting instruction. CAP-04 (HTTP) confirmed working. CAP-07 (audit) confirmed working. All routers importable. - Calendar OAuth endpoints accessible: GET /api/portal/calendar/install, GET /api/portal/calendar/callback - KB API endpoints accessible: POST/GET/DELETE /api/portal/kb/{tenant_id}/documents - Calendar tool supports list, create, check_availability actions - All unit tests pass: `pytest tests/unit/test_calendar_lookup.py tests/unit/test_calendar_auth.py -x -q` - Tool registry has updated calendar_lookup schema with CRUD params

<success_criteria>

  • Google Calendar OAuth flow: install -> Google consent -> callback -> encrypted token stored in channel_connections
  • Calendar tool reads per-tenant tokens and calls Google Calendar API for list, create, and availability check
  • Token auto-refresh works with write-back to DB
  • Natural language formatting on all tool results (no raw JSON)
  • All new routers mounted on gateway
  • CAP-04 and CAP-07 confirmed already working
  • All unit tests pass </success_criteria>
After completion, create `.planning/phases/10-agent-capabilities/10-02-SUMMARY.md`