---
phase: 02-action-registry
plan: 03
type: execute
wave: 2
depends_on: ["02-02"]
files_modified:
  - src/ztlctl/controllers/create.py
  - src/ztlctl/controllers/update.py
  - src/ztlctl/controllers/query.py
  - src/ztlctl/controllers/session.py
  - src/ztlctl/controllers/ingest.py
  - src/ztlctl/controllers/workflow.py
  - src/ztlctl/controllers/init_ctrl.py
  - src/ztlctl/controllers/__init__.py
  - tests/controllers/test_create.py
  - tests/controllers/test_query.py
  - tests/controllers/test_session.py
autonomous: true
requirements:
  - ACTN-02

must_haves:
  truths:
    - "CreateController wraps all 4 CreateService public methods (create_note, create_reference, create_task, create_batch)"
    - "QueryController wraps all 10 QueryService public methods"
    - "SessionController wraps all 9 SessionService public methods"
    - "UpdateController wraps update, archive, supersede"
    - "IngestController wraps list_providers, ingest_text, ingest_file, ingest_url"
    - "WorkflowController and InitController exist for custom_presentation actions"
  artifacts:
    - path: "src/ztlctl/controllers/create.py"
      provides: "CreateController wrapping CreateService"
      contains: "class CreateController"
    - path: "src/ztlctl/controllers/query.py"
      provides: "QueryController wrapping QueryService + ContextAssembler"
      contains: "class QueryController"
    - path: "src/ztlctl/controllers/session.py"
      provides: "SessionController wrapping SessionService"
      contains: "class SessionController"
    - path: "src/ztlctl/controllers/init_ctrl.py"
      provides: "InitController wrapping InitService"
      contains: "class InitController"
  key_links:
    - from: "src/ztlctl/controllers/create.py"
      to: "src/ztlctl/services/create.py"
      via: "lazy local import"
      pattern: "from ztlctl\\.services\\.create import CreateService"
    - from: "src/ztlctl/controllers/query.py"
      to: "src/ztlctl/services/query.py"
      via: "lazy local import"
      pattern: "from ztlctl\\.services\\.query import QueryService"
    - from: "src/ztlctl/controllers/session.py"
      to: "src/ztlctl/services/session.py"
      via: "lazy local import"
      pattern: "from ztlctl\\.services\\.session import SessionService"
---

<objective>
Create the remaining 7 controllers: write-heavy services (Create, Update, Ingest), complex services (Query, Session), and custom_presentation services (Workflow, Init). This completes the full controller layer.

Purpose: After this plan, every service has a corresponding controller. The controller layer is the single interface for the registry layer (Plan 04).

Output: 7 controller files + integration tests for Create, Query, and Session controllers.
</objective>

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

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-action-registry/02-CONTEXT.md
@.planning/phases/02-action-registry/02-RESEARCH.md
@.planning/phases/02-action-registry/02-02-SUMMARY.md

<interfaces>
<!-- Service public method signatures to wrap -->

CreateService:
  create_note(title, *, subtype=None, tags=None, topic=None, body=None, key_points=None, links=None, aliases=None) -> ServiceResult
  create_reference(title, *, subtype=None, source_url=None, tags=None, topic=None, body=None, key_points=None, links=None, aliases=None) -> ServiceResult
  create_task(title, *, tags=None, topic=None, body=None, links=None, aliases=None) -> ServiceResult
  create_batch(items) -> ServiceResult

UpdateService:
  update(content_id, *, changes: dict[str, Any]) -> ServiceResult
  archive(content_id) -> ServiceResult
  supersede(old_id, new_id) -> ServiceResult

QueryService:
  count_items(*, include_archived=False) -> ServiceResult
  search(query, *, content_type=None, status=None, tags=None, space=None, limit=20, after=None, before=None, sort_by="relevance") -> ServiceResult
  get(content_id) -> ServiceResult
  list_items(*, content_type=None, status=None, tags=None, space=None, limit=20, offset=0, sort_by="created", order="desc", include_archived=False) -> ServiceResult
  work_queue(*, space=None) -> ServiceResult
  list_tags(*, prefix=None, limit=100) -> ServiceResult
  decision_support(content_id, *, include_context=True) -> ServiceResult
  topic_packet(topic, *, token_budget=4000) -> ServiceResult
  draft_from_topic(topic, *, content_type="note", subtype=None) -> ServiceResult
  vault_review(*, top=10, stale_days=7) -> ServiceResult

SessionService:
  start(topic) -> ServiceResult
  close(*, summary=None) -> ServiceResult
  reopen(session_id) -> ServiceResult
  status() -> ServiceResult
  log_entry(message, *, entry_type="observation", tags=None) -> ServiceResult
  cost(*, report=None) -> ServiceResult
  context(*, token_budget=4000, include_session=True) -> ServiceResult
  brief() -> ServiceResult
  extract_decision(session_id, *, title=None) -> ServiceResult

IngestService:
  list_providers() -> ServiceResult
  ingest_text(text, *, title=None, tags=None, topic=None, provider=None) -> ServiceResult
  ingest_file(file_path, *, title=None, tags=None, topic=None, provider=None) -> ServiceResult
  ingest_url(url, *, title=None, tags=None, topic=None, provider=None) -> ServiceResult

WorkflowService (static methods — custom_presentation):
  init_workflow(vault_root, choices) -> ServiceResult
  update_workflow(vault_root, ...) -> ServiceResult
  export_assets(vault_root, *, client="both") -> ServiceResult

InitService (static methods — custom_presentation):
  init_vault(vault_root, ...) -> ServiceResult
  regenerate_self(vault) -> ServiceResult
  check_staleness(vault) -> ServiceResult

From src/ztlctl/controllers/base.py (created in Plan 02):
```python
class BaseController:
    def __init__(self, vault: Vault) -> None:
        self._vault = vault
    def _dispatch_event(self, hook_name, payload, warnings, *, session_id=None) -> int | None: ...
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create write-heavy and complex controllers (Create, Update, Query, Session, Ingest)</name>
  <files>
    src/ztlctl/controllers/create.py,
    src/ztlctl/controllers/update.py,
    src/ztlctl/controllers/query.py,
    src/ztlctl/controllers/session.py,
    src/ztlctl/controllers/ingest.py
  </files>
  <read_first>
    src/ztlctl/services/create.py,
    src/ztlctl/services/update.py,
    src/ztlctl/services/query.py,
    src/ztlctl/services/session.py,
    src/ztlctl/services/ingest.py,
    src/ztlctl/controllers/base.py,
    src/ztlctl/controllers/check.py
  </read_first>
  <action>
Create 5 controllers following the exact same pattern established in Plan 02 (thin wrappers with lazy local imports).

**CreateController** (`src/ztlctl/controllers/create.py`):
- `create_note(self, title: str, *, subtype: str | None = None, tags: list[str] | None = None, topic: str | None = None, body: str | None = None, key_points: list[str] | None = None, links: dict[str, list[str]] | None = None, aliases: list[str] | None = None) -> ServiceResult`
- `create_reference(self, title: str, *, subtype: str | None = None, source_url: str | None = None, tags: list[str] | None = None, topic: str | None = None, body: str | None = None, key_points: list[str] | None = None, links: dict[str, list[str]] | None = None, aliases: list[str] | None = None) -> ServiceResult`
- `create_task(self, title: str, *, tags: list[str] | None = None, topic: str | None = None, body: str | None = None, links: dict[str, list[str]] | None = None, aliases: list[str] | None = None) -> ServiceResult`
- `create_batch(self, items: list[dict[str, Any]]) -> ServiceResult`

Each method: `from ztlctl.services.create import CreateService` then `return CreateService(self._vault).method(...)`.

**UpdateController** (`src/ztlctl/controllers/update.py`):
- `update(self, content_id: str, *, changes: dict[str, Any]) -> ServiceResult`
- `archive(self, content_id: str) -> ServiceResult`
- `supersede(self, old_id: str, new_id: str) -> ServiceResult`

**QueryController** (`src/ztlctl/controllers/query.py`):
Read `src/ztlctl/services/query.py` carefully — this has 10 public methods with many kwargs. Copy exact signatures:
- `count_items`, `search`, `get`, `list_items`, `work_queue`, `list_tags`, `decision_support`, `topic_packet`, `draft_from_topic`, `vault_review`

Note: `topic_packet` and `draft_from_topic` also use `ContextAssembler` in the existing service — the controller wraps the service, so the service handles ContextAssembler internally. Just delegate.

**SessionController** (`src/ztlctl/controllers/session.py`):
9 methods: `start`, `close`, `reopen`, `status`, `log_entry`, `cost`, `context`, `brief`, `extract_decision`

**IngestController** (`src/ztlctl/controllers/ingest.py`):
4 methods: `list_providers`, `ingest_text`, `ingest_file`, `ingest_url`

For `ingest_file`, note the parameter is `file_path: str | Path` — check the actual service signature.

All controllers: `from __future__ import annotations`, `TYPE_CHECKING` guard for imports, lazy service imports inside each method body.
  </action>
  <verify>
    <automated>cd /Users/shparki/Documents/Workspace/thatdev/ztlctl && uv run mypy src/ztlctl/controllers/create.py src/ztlctl/controllers/update.py src/ztlctl/controllers/query.py src/ztlctl/controllers/session.py src/ztlctl/controllers/ingest.py && uv run ruff check src/ztlctl/controllers/</automated>
  </verify>
  <acceptance_criteria>
    - src/ztlctl/controllers/create.py contains "class CreateController(BaseController):"
    - src/ztlctl/controllers/create.py contains "def create_note(" and "def create_reference(" and "def create_task(" and "def create_batch("
    - src/ztlctl/controllers/create.py contains "from ztlctl.services.create import CreateService"
    - src/ztlctl/controllers/update.py contains "class UpdateController(BaseController):"
    - src/ztlctl/controllers/update.py contains "def update(" and "def archive(" and "def supersede("
    - src/ztlctl/controllers/query.py contains "class QueryController(BaseController):"
    - src/ztlctl/controllers/query.py contains "def search(" and "def get(" and "def list_items(" and "def work_queue(" and "def topic_packet(" and "def vault_review("
    - src/ztlctl/controllers/session.py contains "class SessionController(BaseController):"
    - src/ztlctl/controllers/session.py contains "def start(" and "def close(" and "def status(" and "def log_entry(" and "def extract_decision("
    - src/ztlctl/controllers/ingest.py contains "class IngestController(BaseController):"
    - No module-level service imports in any controller file (all lazy inside methods)
    - `uv run mypy` on all 5 files exits 0
  </acceptance_criteria>
  <done>5 controllers (Create, Update, Query, Session, Ingest) exist with correct method signatures matching their respective services. All use lazy local imports. mypy and ruff clean.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Create custom_presentation controllers (Workflow, Init) and integration tests</name>
  <files>
    src/ztlctl/controllers/workflow.py,
    src/ztlctl/controllers/init_ctrl.py,
    src/ztlctl/controllers/__init__.py,
    tests/controllers/test_create.py,
    tests/controllers/test_query.py,
    tests/controllers/test_session.py
  </files>
  <read_first>
    src/ztlctl/services/workflow.py,
    src/ztlctl/services/init.py,
    src/ztlctl/controllers/create.py,
    src/ztlctl/controllers/query.py,
    src/ztlctl/controllers/session.py,
    tests/services/test_create.py,
    tests/conftest.py
  </read_first>
  <behavior>
    - CreateController(vault).create_note("Test") returns a ServiceResult with ok=True
    - QueryController(vault).search("test") returns a ServiceResult
    - QueryController(vault).list_items() returns a ServiceResult
    - SessionController(vault).start("test topic") returns a ServiceResult with ok=True
    - WorkflowController exists with init_workflow, update_workflow, export_assets methods
    - InitController exists with init_vault, regenerate_self, check_staleness methods
  </behavior>
  <action>
**WorkflowController** (`src/ztlctl/controllers/workflow.py`):
WorkflowService uses static methods that take `vault_root: Path` instead of a Vault instance. The controller should still extend BaseController for consistency but the methods will need `vault_root` as an explicit parameter:
- `init_workflow(self, vault_root: Path, choices: Any) -> ServiceResult`
- `update_workflow(self, vault_root: Path, ...) -> ServiceResult`
- `export_assets(self, vault_root: Path, *, client: str = "both") -> ServiceResult`

Read `src/ztlctl/services/workflow.py` for exact signatures of `WorkflowService.init_workflow()`, `update_workflow()`, `export_assets()`. These are `@staticmethod` — the controller calls `WorkflowService.method(vault_root, ...)` directly.

**InitController** (`src/ztlctl/controllers/init_ctrl.py` — named `init_ctrl.py` to avoid shadowing `__init__.py`):
InitService also has static methods. Read `src/ztlctl/services/init.py` for exact signatures:
- `init_vault(self, vault_root: Path, ...) -> ServiceResult` — wraps `InitService.init_vault(vault_root, ...)`
- `regenerate_self(self) -> ServiceResult` — wraps `InitService.regenerate_self(self._vault)`
- `check_staleness(self) -> ServiceResult` — wraps `InitService.check_staleness(self._vault)`

**Update `src/ztlctl/controllers/__init__.py`** to re-export ALL 13 controller classes:
```python
from ztlctl.controllers.base import BaseController
from ztlctl.controllers.check import CheckController
from ztlctl.controllers.create import CreateController
from ztlctl.controllers.export import ExportController
from ztlctl.controllers.graph import GraphController
from ztlctl.controllers.ingest import IngestController
from ztlctl.controllers.init_ctrl import InitController
from ztlctl.controllers.query import QueryController
from ztlctl.controllers.reweave import ReweaveController
from ztlctl.controllers.session import SessionController
from ztlctl.controllers.update import UpdateController
from ztlctl.controllers.upgrade import UpgradeController
from ztlctl.controllers.vector import VectorController
from ztlctl.controllers.workflow import WorkflowController

__all__ = [
    "BaseController", "CheckController", "CreateController", "ExportController",
    "GraphController", "IngestController", "InitController", "QueryController",
    "ReweaveController", "SessionController", "UpdateController", "UpgradeController",
    "VectorController", "WorkflowController",
]
```

**Integration tests** — look at how existing service tests create vault fixtures (check `tests/conftest.py` and `tests/services/test_create.py` for the pattern). Create tests that:

`tests/controllers/test_create.py`:
- Test CreateController(vault).create_note("Test Title") returns ServiceResult with ok=True and data containing "id"
- Test CreateController(vault).create_task("Test Task") returns ServiceResult with ok=True

`tests/controllers/test_query.py`:
- Test QueryController(vault).list_items() returns ServiceResult with ok=True
- Test QueryController(vault).count_items() returns ServiceResult with ok=True
- Test QueryController(vault).search("test") returns ServiceResult

`tests/controllers/test_session.py`:
- Test SessionController(vault).start("test topic") returns ServiceResult with ok=True
- Test SessionController(vault).status() returns ServiceResult
  </action>
  <verify>
    <automated>cd /Users/shparki/Documents/Workspace/thatdev/ztlctl && uv run pytest tests/controllers/ -x -q</automated>
  </verify>
  <acceptance_criteria>
    - src/ztlctl/controllers/workflow.py contains "class WorkflowController(BaseController):"
    - src/ztlctl/controllers/init_ctrl.py contains "class InitController(BaseController):"
    - src/ztlctl/controllers/__init__.py contains all 14 names: BaseController, CheckController, CreateController, ExportController, GraphController, IngestController, InitController, QueryController, ReweaveController, SessionController, UpdateController, UpgradeController, VectorController, WorkflowController
    - tests/controllers/test_create.py contains "class TestCreateController"
    - tests/controllers/test_create.py contains "test_create_note"
    - tests/controllers/test_query.py contains "class TestQueryController"
    - tests/controllers/test_session.py contains "class TestSessionController"
    - `uv run pytest tests/controllers/ -x -q` exits 0
    - `uv run mypy src/ztlctl/controllers/` exits 0
  </acceptance_criteria>
  <done>All 13 controllers exist (7 new in this plan + 6 from Plan 02). controllers/__init__.py re-exports all 14 classes (including BaseController). Integration tests prove Create, Query, and Session controllers produce correct ServiceResult outputs. Full controller test suite passes.</done>
</task>

</tasks>

<verification>
- `uv run pytest tests/controllers/ -x -q` — all controller tests green
- `uv run mypy src/ztlctl/controllers/` — strict type checking passes
- `uv run ruff check src/ztlctl/controllers/` — no lint errors
- `uv run pytest` — full suite still green (no regressions)
</verification>

<success_criteria>
- 7 new controllers (Create, Update, Query, Session, Ingest, Workflow, Init) wrapping their services
- All 13 controllers re-exported from controllers/__init__.py
- Integration tests for Create, Query, Session controllers
- All controllers use lazy local imports, mypy strict clean, ruff clean
</success_criteria>

<output>
After completion, create `.planning/phases/02-action-registry/02-03-SUMMARY.md`
</output>
