Phase 26: Task Code Reorg & HTTP-Backed Agent Worker#57
Merged
SimplicityGuy merged 74 commits intoMay 13, 2026
Conversation
Pulls the Phase 26 planning files (PLAN, CONTEXT, RESEARCH, PATTERNS, VALIDATION, DISCUSSION-LOG) plus updated STATE/ROADMAP/REQUIREMENTS from main into this worktree so plan execution has the canonical references.
Wave 0 foundation (Task 1 of Plan 26-01, D-11 + D-31 + D-33): - Add tenacity>=8.5.0 to [project].dependencies (runtime retry decorator for PhazeAgentClient HTTP methods in Plan 02). - Add respx>=0.21.1 to [dependency-groups].dev (httpx mock library for PhazeAgentClient contract tests in Plan 02). - Add [[tool.mypy.overrides]] blocks for phaze.services.agent_client and phaze.services.agent_task_router that opt them into strict checking (disallow_untyped_defs, check_untyped_defs, warn_return_any, strict_equality) despite the global services/ exclude. New code in these two modules will be fully type-checked. - Regenerate uv.lock with the new constraints. The global mypy exclude = "^(tests/|prototype/|services/)" is unchanged so the rest of services/ continues to iterate without strict gates.
Wave 0 foundation (Task 2 of Plan 26-01):
- Add `Role` StrEnum with values "control" and "agent".
- Split the legacy `Settings` class into:
* `BaseSettings(PydanticBaseSettings)` β shared fields (database_url,
redis_url, debug, scan_path, models_path, output_path, worker_*,
audfprint_url, panako_url, discogsography_url, api_host/port,
agent_token_prefix, agent_file_chunk_max).
* `ControlSettings(BaseSettings)` β application-server role: Discogs
concurrency + LLM proposal generation fields.
* `AgentSettings(BaseSettings)` β file-server role: `agent_api_url`,
`agent_token: SecretStr`, `scan_roots: list[str]`. A
`model_validator(mode="after")` raises ValueError at construction
time if any is missing/empty so the agent worker fails fast rather
than silently producing 401s or path-traversal rejections at
runtime (D-14 + threat model T-26-01-T2 mitigation).
- Add `@lru_cache(maxsize=1) def get_settings() -> BaseSettings`: reads
`PHAZE_ROLE` env (default "control") and dispatches to the right
subclass.
- Replace `settings = Settings()` with `settings: ControlSettings = ...`
module-level singleton (annotated as ControlSettings so existing
call sites that read `settings.llm_*` / `settings.discogs_match_concurrency`
still type-check). Module-level singleton stays Control-typed; the
agent worker (Plan 10) calls `get_settings()` / `AgentSettings()`
directly and stashes the instance at ctx["agent_settings"].
- Add `Settings = ControlSettings` back-compat alias for test files
that still import the legacy class name.
Deviations from the literal plan text (Rule 2 β missing critical
functionality):
- pydantic-settings v2 does NOT natively comma-split `PHAZE_AGENT_SCAN_ROOTS
=/a,/b` into `["/a", "/b"]`. It expects a JSON-encoded list and raises
JSONDecodeError on bare comma-strings. Fix: annotate the field as
`Annotated[list[str], NoDecode]` so pydantic-settings skips the JSON
decode step, then add a `@field_validator("scan_roots", mode="before")`
classmethod `_split_scan_roots` that comma-splits string input while
passing native list input through unchanged.
- pydantic-settings reads env vars by *field name* (case-insensitive)
absent an `env_prefix`. The documented env-var names `PHAZE_AGENT_*`
required explicit `validation_alias=AliasChoices(...)` per field so
both the documented env-var form and direct kwargs work.
Module-level `settings = get_settings()` keeps every existing
`from phaze.config import settings` call site (37+ across src/ and
tests/) working unchanged. Tests pass: 593 routers+services, 102
models+config-worker+phase01-gaps. Full repo mypy clean.
Wave 0 foundation (Task 3 of Plan 26-01):
- ProposalStatus gains EXECUTED + FAILED. These are the terminal targets
of the PATCH /api/internal/agent/proposals/{id}/state router (Plan 08).
State machine: APPROVED -> EXECUTED or APPROVED -> FAILED. Re-PATCHing
the same terminal value is a 200 idempotent no-op; any other transition
returns 409.
- FileState gains MOVED + UNCHANGED. MOVED pairs with ProposalStatus.
EXECUTED (file successfully copy-verified-deleted at new path);
UNCHANGED pairs with ProposalStatus.FAILED (file stays at original
path). The pre-existing FileState.EXECUTED + FAILED values are
retained for Phase 25-era execution-log emit paths; Phase 28's batch
execution will adopt MOVED/UNCHANGED when wiring the PATCH endpoint
into the live dispatch loop.
No alembic migration needed:
- Proposal.status uses String(20): "executed" (8) and "failed" (6) fit.
- FileRecord.state uses String(30): "moved" (5) and "unchanged" (9) fit.
StrEnum values store as plain strings β adding values widens the
accepted value set without altering DDL. Existing 83 model tests pass.
Captures the outcome of Plan 26-01 (tenacity+respx deps, settings split into BaseSettings + ControlSettings + AgentSettings with fail-fast scan_roots validator, enum extensions ProposalStatus. EXECUTED|FAILED + FileState.MOVED|UNCHANGED) and advances STATE.md: - Current Position now points to Phase 26 / Plan 01 complete, ready for Plan 02 (PhazeAgentClient + AgentApiError). - Resume file updated to 26-02-PLAN.md. - Four [Phase 26-01]: decisions appended to Accumulated Context documenting the pydantic-settings v2 env-var quirks discovered during execution (no native comma-split for list[str]; no automatic PHAZE_AGENT_* prefix mapping) and the back-compat alias choices. - Progress recalculated to 14/26 plans (54%). The SUMMARY.md records all four deviations (two Rule-2 fixes for pydantic-settings quirks, two Rule-1 fixes for type-system and test-import regressions) with their exact rationale.
- 9 respx-mocked async tests covering D-09..D-13 invariants - Asserts 4xx never retried (call_count == 1) for 401/403/404/422 - Asserts 5xx retries 3 times (call_count == 3) on persistent 500 - Asserts 5xx-then-200 recovery (call_count == 2) - Asserts ConnectError retries 3 times (call_count == 3) - Asserts Authorization: Bearer <token> header injected - whoami parses AgentIdentity model from response JSON Tests fail at import (ModuleNotFoundError) until Task 2 ships phaze.services.agent_client and Plan 03 ships agent_analysis + agent_identity schemas. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RED phase for Task 1 of Plan 26-03. Tests target modules that don't exist yet: - tests/test_schemas/test_agent_identity.py (AgentIdentity β D-15) - tests/test_schemas/test_agent_analysis.py (AnalysisWritePayload/Response β D-26) - tests/test_schemas/test_agent_tracklists.py (TracklistCreatePayload + nested β D-27, T-26-07-DoS) - tests/test_schemas/test_agent_proposals.py (ProposalStatePatch/Response β D-28) Covers: - extra='forbid' on request bodies and nested items - Literal value validation against ProposalStatus/FileState enum string values - _require_path_when_moved conditional validator - max_length=2000 cap on tracks (T-26-07-DoS) - Optional-everything semantics on AnalysisWritePayload - bpm >= 0, danceability/energy in [0, 1] bounds
β¦-26, D-27, D-28) GREEN for Task 1 of Plan 26-03. Implements the wire-level Pydantic models consumed by Wave 2 routers (Plans 05-08) and the PhazeAgentClient (Plan 02): - agent_identity.py: AgentIdentity (response-only, loose schema for /whoami) - agent_analysis.py: AnalysisWritePayload + AnalysisWriteResponse (PUT /analysis, all fields optional for partial-PUT, extra='forbid' + ge/le bounds) - agent_tracklists.py: TracklistTrackPayload + TracklistCreatePayload (POST /tracklists, request_id idempotency, tracks min=1/max=2000 per T-26-07-DoS) + TracklistCreateResponse - agent_proposals.py: ProposalStatePatch with _require_path_when_moved model_validator (current_path mandatory when file_state='moved') + ProposalStateResponse. Literal values mirror ProposalStatus.EXECUTED/FAILED + FileState.MOVED/UNCHANGED enum string values from Plan 26-01. Every request/nested schema sets ConfigDict(extra='forbid') per Phase 25 D-16 (unknown body keys -> 422). Response schemas stay loose for forward-compat. All 34 unit tests pass; mypy strict + ruff clean.
RED for Task 2 of Plan 26-03. Tests the 6 models that will land in
src/phaze/schemas/agent_tasks.py: ProcessFilePayload, ExtractMetadataPayload,
FingerprintFilePayload, ScanLiveSetPayload, ExecuteApprovedBatchPayload, and
the nested ExecuteBatchProposalItem.
Covers D-22..D-24 invariants:
- D-22: only ProcessFilePayload carries models_path
- D-23: ExecuteApprovedBatchPayload is fully self-contained
(no DB read-back mid-job; B2 Option A)
- D-24: NO payload has a current_path field (agents work off original_path)
- min/max_length=500 on proposals list (DoS hardening)
- JSON round-trip equality preserved
- extra='forbid' on all 6 classes
β¦4-class error hierarchy (GREEN) Implements D-09..D-13 + D-31/32/33: - PhazeAgentClient mirrors DiscogsographyClient pattern (services/discogs_matcher.py). - Constructor injects bearer token as default Authorization header on the underlying httpx.AsyncClient -- token is NEVER stored as an instance attribute (T-26-02-I mitigation, D-13). - _request funnel applies tenacity AsyncRetrying with stop_after_attempt(3), wait_exponential_jitter(initial=0.5, max=4.0), retry_if_exception(_should_retry). - _should_retry returns True for ConnectError, ReadTimeout, WriteTimeout, and 5xx HTTPStatusError. NEVER returns True for 4xx (D-11, D-32). - 4-class exception hierarchy (D-12): AgentApiError base + AgentApiAuthError (401/403, no retry) + AgentApiClientError (other 4xx, no retry) + AgentApiServerError (5xx + persistent network, after retry exhaustion). - DEBUG on success, WARNING on failure; log format strings exclude the token (T-26-02-I, D-13). - 10 endpoint methods (D-10): whoami, upsert_files, put_metadata, put_fingerprint, put_analysis, create_tracklist, post_execution_log, patch_execution_log, patch_proposal_state, heartbeat. - PUT/PATCH methods use exclude_unset=True per Phase 25 CR-01 partial-update semantics. POST + heartbeat use full bodies. - _client kwarg supports respx test injection (leading underscore = private). - Plan 03 schema imports gated behind TYPE_CHECKING + lazy method-body imports so the module loads independent of merge order; type: ignore[import-not-found] markers self-delete via warn_unused_ignores once Plan 03 lands. Verified: - uv run ruff check src/phaze/services/agent_client.py exits 0 - uv run mypy src/phaze/services/agent_client.py exits 0 (strict per pyproject.toml [[tool.mypy.overrides]] for the agent_client module) - Contract tests fail at import for Plan 03 schemas (agent_analysis, agent_identity, agent_proposals, agent_tracklists) -- GREEN gate satisfied at Wave 2 merge per plan body acceptance criteria. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦-24)
GREEN for Task 2 of Plan 26-03. Ships the typed payload models used by every
file-bound SAQ task in Plan 11. Task bodies will call <Payload>.model_validate
(ctx kwargs) at entry, applying the same extra='forbid' strictness as HTTP
request bodies (D-16).
Payloads:
- ProcessFilePayload (essentia analysis; only payload with models_path)
- ExtractMetadataPayload (mutagen tag extraction)
- FingerprintFilePayload (audfprint + panako submission)
- ScanLiveSetPayload (live-set fingerprint resolution)
- ExecuteApprovedBatchPayload (per-agent execution sub-batch β B2 Option A)
- ExecuteBatchProposalItem (nested: per-proposal copy+verify+delete details)
Invariants enforced:
- D-22: only ProcessFilePayload carries models_path
- D-23: ExecuteApprovedBatchPayload is fully self-contained (proposals carry
original_path + proposed_path + optional sha256_hash so the agent
never reads DB state mid-job)
- D-24: NO payload carries current_path anywhere
- Field(min_length=1, max_length=500) caps per-job batch size
- All 6 classes set ConfigDict(extra='forbid')
`from __future__ import annotations` omitted because Pydantic resolves the
uuid.UUID field annotation at runtime; deferred annotations would trigger
ruff TC003 on the runtime-needed uuid import (mirrors agent_metadata.py pattern).
22 unit tests pass; mypy strict + ruff clean.
- 26-02-SUMMARY.md captures contract-test invariants, retry policy values, mypy-strict-override confirmation (RESEARCH A8 / A12), and the 3 auto-fix deviations (PLC0415 noqa, import-not-found ignore, uuid TC003 hoist). - STATE.md advances Current Plan to 02 (complete), records the 3 Plan-26-02 decisions, and bumps progress to 15/26 plans (58%). - ROADMAP.md updated for Phase 26 plan-progress row (2/13 plans complete). - REQUIREMENTS.md marks TASK-02 + TASK-03 complete with traceability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦ + 5 SAQ schemas Plan 26-03 ships 5 new Pydantic schema modules (4 HTTP-boundary + 1 SAQ-payload bundle) with 56 unit tests, all green. mypy strict + ruff clean across all modules. Pre-commit hooks pass on all files. 582 non-integration tests pass overall. Two auto-fix deviations folded into GREEN commits (both Rule 1 β ruff lint fixes to the plan's verbatim source): removed unused `# noqa: TC003` in agent_identity.py; dropped `from __future__ import annotations` in agent_tasks.py to avoid TC003 firing on runtime-needed `import uuid`. Closes: Plan 26-03 of Phase 26 (TASK-02, TASK-03, DIST-03 β partial; full completion deferred to phase-end verifier).
Plan 03 has merged, so the parallelization-debt `# type: ignore[import-not-found]`
comments on phase 26 schema imports are now unused. `warn_unused_ignores` flags
them as errors and blocks every commit on this branch. Removing per the original
comment intent ("removed once Plan 03 merges").
Rule 3 (blocking issue) deviation -- minimal, surgical cleanup required to
proceed with Plan 26-04.
- 4 integration tests covering per-agent queue isolation, lazy cache identity, close() drain, and enqueue_for_file delegation - Uses real Redis via PHAZE_REDIS_URL env (default redis://localhost:6379/0) - Marked @pytest.mark.integration per D-30; skips cleanly when Redis is down - Module import currently fails (ModuleNotFoundError) -- RED confirmed
- 8 contract tests covering happy path, replay idempotence, partial-PUT field-level LWW (CR-01 invariant), empty-body no-op, first-PUT-with- empty-body row creation, 422 on extra fields (AUTH-01 spoof block), 401 missing auth, 403 unknown token. - Mirrors test_agent_metadata.py smoke-app pattern with _seed_file FK helper (AnalysisResult.file_id FKs files.id). - Asserts mood/style dict -> summary string conversion at storage boundary (per D-26 storage discretion area). - Collection fails with ModuleNotFoundError until Task 2 ships the router module (RED state confirmed). Deviation (Rule 3 -- auto-fix blocking issue): removed four `# type: ignore[import-not-found]` tripwires in services/agent_client.py. These were intentional self-deleting markers placed by Plan 03's author; now that Plan 03 has merged the schema modules they reference, mypy with `warn_unused_ignores=true` correctly errors on the now-resolvable imports. Removing them unblocks the pre-commit mypy gate for this and all subsequent commits in Phase 26 Wave 3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦e (GREEN) Controller-side service that enqueues file-bound SAQ jobs onto the right per-agent queue. Replaces the inline Queue.from_url + try/finally pattern at agent_files.py:99-117 with a reusable service whose Queue instances are cached per-agent (one Redis connection pool per agent, reused across enqueues). API: - AgentTaskRouter(redis_url) -- stores URL + initializes empty cache - _queue_for(agent_id) -- lazy Queue.from_url with name=phaze-agent-<id> (D-18) - enqueue_for_agent(*, agent_id, task_name, payload) -- enqueue via model_dump - enqueue_for_file(*, file_record, task_name, payload) -- delegate via FileRecord.agent_id - close() -- disconnect every cached Queue and clear cache (idempotent) Decisions: D-19 (class shape), D-20 (lifespan-wired in Plan 12), D-21 (replaces inline enqueue in agent_files.py in Plan 12), D-30 (real-Redis integration tests), D-33 (mypy strict opt-in). All 4 integration tests pass against a real Redis instance.
β¦s in agent_client.py Plan 26-02's PhazeAgentClient annotated 4 Plan 03 schema imports with '# type: ignore[import-not-found]' parallelization debt, with a comment explicitly declaring them a self-deleting tripwire that would fire once Plan 03 schemas landed. Plan 03 merged via 6ae8a49 (5 schema modules including agent_analysis, agent_identity, agent_proposals, agent_tracklists). The four 'type: ignore' comments are now unused -- mypy's 'warn_unused_ignores' flag turns this into a hard failure (Rule 3 blocker preventing Plan 08 commits). Removed all four ignore directives. Mypy now exits clean. No runtime change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦nt (Rule 3) Plan 26-02 (PhazeAgentClient) added `# type: ignore[import-not-found]` to five schema imports as self-deleting tripwires, intended to fire unused-ignore errors once Plan 26-03 (schemas) merged so the cleanup would be obvious. Plan 26-03 merged at 6ae8a49 but the cleanup did not happen, so every subsequent commit fails the local mypy pre-commit hook with 4 `Unused "type: ignore" comment [unused-ignore]` errors -- blocking Wave 3 plans 26-04..26-08. Removing the ignores (and their stale explanatory comments) restores mypy green and unblocks Plan 26-05 (this plan). Rule 3 deviation: scope-adjacent to Plan 26-05's `files_modified` but required to commit at all (pre-commit mypy is mandatory). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦s/{id}/state (RED)
11 contract tests for the joint Proposal+FileRecord state-transition endpoint
(D-28). Covers:
- APPROVED -> EXECUTED joint update (proposal_state + file_state=moved + current_path)
- APPROVED -> FAILED joint update (proposal_state + file_state=unchanged)
- Same-state PATCH idempotent no-op (EXECUTED -> EXECUTED returns 200)
- Illegal transitions (EXECUTED -> FAILED, PENDING -> EXECUTED -> 409)
- 404 on unknown proposal_id
- 422 on extra field (extra='forbid' from ProposalStatePatch)
- 422 on moved-without-current_path (_require_path_when_moved validator)
- 401 missing bearer, 403 unknown token
- Cross-agent 403 (W1 / T-26-08-S2): agent B cannot mutate agent A's proposal
Uses smoke-app pattern (mirrors test_agent_execution.py) so tests don't depend
on Plan 12 wiring the router into main.py. Confirms RED: ImportError because
phaze.routers.agent_proposals does not exist yet (implemented in Task 2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 4 contract tests: happy path (200 + AgentIdentity body), missing header -> 401 (with WWW-Authenticate: Bearer per RFC 6750), unknown token -> 403, revoked-mid-session -> 403 (AUTH-04). - Uses Phase 25 per-router smoke-app pattern (mirrors tests/test_routers/test_agent_metadata.py:30-38) so the suite is independent of Plan 12's main.py wiring. - Re-uses session + seed_test_agent fixtures from tests/conftest.py. Currently RED: import of phaze.routers.agent_identity fails because the router module has not been created yet (implemented in Task 2 of this plan). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The four ``# type: ignore[import-not-found]`` comments in agent_client.py were placed by Plan 26-02 as a deliberate self-deleting tripwire (see the inline comment) -- once Plan 26-03 lands the schema modules, mypy's ``warn_unused_ignores`` flags the comments as unused and fails the local hook. Plan 26-03 merged into the phase branch in Wave 2; removing the ignores unblocks Wave 3 plans (including this one, 26-07) from running their pre-commit suite. Rule 3 scope-blocker fix: pre-existing mypy failure in a file not authored by Plan 26-07. Surgical removal of dead comments only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 7 integration tests covering happy path, idempotent replay, multi-version promotion via new request_id, extra-field 422, too-many-tracks 422 (T-26-07-DoS), 401, 403 - Smoke-app fixture pattern + real-Redis fixture with scan_iter cleanup - Tests fail RED until router lands (ImportError: phaze.routers.agent_tracklists) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦EEN) Mirror of agent_metadata.py for AnalysisResult. Key behaviors: - pg_insert + on_conflict_do_update on AnalysisResult.file_id (UQ from models/analysis.py:18) for idempotent upsert. - body.model_dump(exclude_unset=True) so only fields the caller set land in the UPDATE SET clause (Phase 25 CR-01 field-level LWW invariant). - Empty body -> on_conflict_do_nothing branch (avoids Postgres "empty SET" syntax error; new rows still get an INSERT with all NULL fields). - payload["id"] = uuid.uuid4() stamped explicitly because AnalysisResult.id has a Python-only default that pg_insert bypasses. - agent_id sourced from auth dep, never from body (AUTH-01). - Depends(get_authenticated_agent) -> 401/403 surface honored. Storage representation: - mood/style: dict[str, float] -> top-3 "k=v,k=v" summary string via _summarize_dict_to_string (bounded 50 chars to fit existing String(50) columns; deterministic alphabetical tiebreak on equal scores). - Overflow funnel (Rule 1 + Rule 3 -- plan-discretion area): wire-format fields without a dedicated column (`danceability`, `energy`) are merged into the existing `features` JSONB column rather than dropped, preserving D-26's wire contract without an Alembic migration. Plan task originally asserted `row.danceability == 0.8` directly; the model has no such column, so RED-tests were updated to assert against `row.features["danceability"]` to match the storage funnel. Tests: 8/8 contract tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦ (GREEN)
Joint Proposal + FileRecord state-transition endpoint per D-28. Mirrors the
agent_execution.py PATCH handler (Phase 25) for structural symmetry:
- Table-driven transitions via _PROPOSAL_TRANSITIONS (not monotonic ladder --
D-28 has only one allowed from-state, APPROVED -> {EXECUTED, FAILED})
- Idempotent same-state PATCH: returns 200 echoing current row state with NO
DB writes (canonical SAQ retry case for terminal states)
- Joint Proposal + FileRecord update in ONE session.commit() (Pitfall 6)
- 409 with detail 'illegal transition {cur} -> {new}' for any other transition
- 404 when proposal_id not found
- W1 / T-26-08-S2: cross-tenant guard via FileRecord.agent_id check; returns
403 BEFORE state-machine logic so a leaked proposal_id cannot be timing-probed
via 409 vs 403
- Auth gated by Depends(get_authenticated_agent) -- 401/403 inherited from
HTTPBearer + agent_auth.get_authenticated_agent
Also fixes a Rule 1 bug in test_agent_proposals.py: AsyncSession.expire_all()
is a sync method (not async); the original 'await session.expire_all()' raised
TypeError. Stripped 'await' from all three callsites.
All 11 contract tests pass:
- test_executed_joint_update
- test_failed_joint_update
- test_same_state_idempotent_no_op
- test_illegal_transition_409
- test_pending_to_executed_409
- test_proposal_not_found_404
- test_proposal_extra_field_422
- test_moved_without_current_path_422
- test_proposal_cross_agent_403 (W1)
- test_proposal_missing_auth_returns_401
- test_proposal_unknown_token_returns_403
Router NOT wired into main.py here -- that lands in Plan 26-12.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six test invocations exercising every documented edge case of the helper that converts dict[str, float] -> "k=v,k=v,k=v" summary string for mood/style storage in AnalysisResult's String(50) columns: - Empty dict -> "" - Single-key dict -> one entry, no comma - 3-key dict -> top-3 sorted by score descending - 10-key dict -> top-3 only (truncation contract) - Identical scores -> alphabetical tiebreak (determinism invariant pinned to the `(-score, key)` two-key sort) - Long keys -> hard 50-char cap fires The alphabetical-tiebreak case prevents regression to the older `reverse=True` single-key sort that would tiebreak by insertion order. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 26-04-SUMMARY.md recording the lazy-cache implementation choice (plain dict over functools.cache / LRU), real-Redis integration test fixture pattern, and the 1 deviation (Rule 3 unblocker for stale type:ignore tripwires in agent_client.py) - STATE.md advances Current Plan to 04 (complete); 4/26 plans of v4.0 done -> 19/26 plans overall (73%) - ROADMAP.md flips 26-03 + 26-04 checkboxes (26-03 SUMMARY already on disk from prior merge); progress 4/13 plans in phase 26
β¦ idempotency (GREEN)
POST /api/internal/agent/tracklists -- idempotent atomic create of
Tracklist + new TracklistVersion + N TracklistTrack rows in a single
transaction. Three-path Stripe-style idempotency keyed on body.request_id:
1. Fast path: tracklist_resp:{request_id} cached JSON -> return without DB work
2. Concurrent-writer path: SET NX lost -> poll resp_key 10x50ms -> 409 on timeout
3. Owner path: SET NX won -> UPSERT Tracklist + version_number+1 + INSERT tracks
+ UPDATE latest_version_id pointer + commit + cache response
Key choices documented in 26-07-SUMMARY (to follow):
- No payload-hash check on cached replays (T-26-07-T accept, single-operator trust)
- max_length=2000 on tracks lives in schemas/agent_tracklists.py (Plan 26-03)
- Redis client pulled via `request.app.state.redis` (Plan 26-12 wires the lifespan)
Implements: D-27, TASK-03
Resolves RED: tests/test_routers/test_agent_tracklists.py (7 integration tests GREEN)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦FastAPI lifespan - Add include_router calls for agent_identity, agent_analysis, agent_tracklists, agent_proposals (Phase 26 D-15, D-26, D-27, D-28). All 10 /api/internal/agent/* endpoints are now reachable from the production create_app() factory. - Wire AgentTaskRouter at app.state.task_router (D-20); used by agent_files.py's auto-enqueue path in Task 2. - Wire shared async Redis client at app.state.redis with decode_responses=True for the tracklists idempotency cache (D-27); agent_tracklists.py reads it via request.app.state.redis. - Lifespan shutdown closes both new resources before the existing default queue (reverse construction order). Verifies: 10 unique agent route paths enumerated from create_app(); all 31 Phase 25 router tests still green; mypy + ruff clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Launches a clean Python subprocess to import phaze.tasks.agent_worker. - Asserts phaze.database, phaze.tasks.session, sqlalchemy.ext.asyncio are absent from sys.modules. - Runs on every CI build (no marks, no skips) β Phase 26 structural invariant. - Currently RED: ModuleNotFoundError until Task 2 creates agent_worker.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦ role (D-01..D-04, D-16) Companion to phaze.tasks.controller (Plan 09). Boots the file-server role under PHAZE_ROLE=agent. Six-step startup: 1. Models check (essentia .pb files). 2. Construct PhazeAgentClient(base_url, token, timeout=30.0). 3. /whoami probe with bounded exponential backoff (1sβ32s, β€63s total). 4. Queue-name mismatch guard β token-derived agent_id MUST match operator-supplied PHAZE_AGENT_QUEUE env (anti-misconfig probe). 5. FingerprintOrchestrator(AudfprintAdapter + PanakoAdapter) stashed at ctx["fingerprint_orchestrator"] (B1 β Plan 11 readers). 6. CPU-bound essentia process pool. functions list contains exactly the 5 file-bound tasks: process_file, extract_file_metadata, fingerprint_file, scan_live_set, execute_approved_batch. Module-level Queue.from_url(redis_url, name=PHAZE_AGENT_QUEUE) β env-driven at import time per RESEARCH Pitfall 7. Module exits non-zero if PHAZE_AGENT_QUEUE is unset. D-13 invariant: bearer never logged. Banner emits a 12-char preview (`phaze_agent_...`) under a non-secret format key (`auth_id_prefix=`). The variable is named `token_preview` for grep-ability of the D-13 intent across the codebase. D-25 import-boundary test (tests/test_task_split.py from Task 1) now passes β no phaze.database / phaze.tasks.session / sqlalchemy.ext.asyncio in sys.modules after `import phaze.tasks.agent_worker`. Structural invariant of Phase 26 is enforced. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦ent_files.upsert_files
Phase 26 D-20 / D-21 -- the auto-enqueue path in agent_files.upsert_files no
longer constructs a SAQ Queue per request. Instead it reads the lifespan-wired
AgentTaskRouter at request.app.state.task_router and calls
enqueue_for_agent(agent_id=..., task_name="extract_file_metadata", payload=...).
- Handler signature gains `request: Request` (positional, before `body`).
- `from saq import Queue` import removed; `from fastapi import Request` and
`from phaze.schemas.agent_tasks import ExtractMetadataPayload` added.
- UPSERT RETURNING extended with `FileRecord.original_path` so the payload
can be built without a re-query.
- The handler no longer owns the Queue lifecycle -- close() runs once in the
FastAPI lifespan shutdown via app.state.task_router.close().
Test fixture (tests/test_routers/test_agent_files.py) migrates from
`patch("phaze.routers.agent_files.Queue")` to installing an AsyncMock at
`app.state.task_router` on the smoke-app. Assertions now inspect
`mock_router.enqueue_for_agent.await_args_list` and verify the
ExtractMetadataPayload's typed fields (file_id, original_path, file_type,
agent_id). All 9 existing tests continue to pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Asserts the startup banner contains module name, role=agent, agent_id=<value>, and the 12-char token preview `phaze_agent_...`. - Asserts the full bearer-token secret bytes never appear in the log output (D-13 invariant). - Heavy constructors (PhazeAgentClient, process pool, fingerprint adapters/orchestrator, models check) are monkeypatched so the test runs in-memory with no Postgres/Redis/.pb files required. - Mirrors tests/test_tasks/test_controller_startup_banner.py (Plan 09) with agent-specific assertions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦P + REQUIREMENTS Plan 26-12 wired the four Phase 26 agent routers (whoami, analysis, tracklists, proposals) into create_app(), installed AgentTaskRouter + async Redis client at app.state.task_router / app.state.redis in the FastAPI lifespan, and refactored agent_files.upsert_files off the inline Queue.from_url pattern. Marks DIST-03 requirement complete. - SUMMARY: 26-12-SUMMARY.md (created) - STATE: current_plan advanced 11 -> 12; progress 92% (24/26); metric recorded - ROADMAP: phase 26 progress 11/13 -> in progress (Plan 13 outstanding) - REQUIREMENTS: DIST-03 marked complete Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add 26-10-SUMMARY.md documenting agent_worker.py + import-boundary test (D-25) + banner test (D-13/OPS-01). - Advance STATE.md plan counter (10 of 13 done; in-progress). - Mark TASK-01, DIST-03 complete in REQUIREMENTS.md (OPS-01 already marked). - Update ROADMAP.md plan progress (11/13 summaries). - Record three key decisions: import-boundary structural invariant, format-key rename to avoid secret-detector false-positive, and /whoami retry budget + queue-name mismatch guard semantics. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
β¦or (Wave 5) # Conflicts: # .planning/ROADMAP.md # .planning/STATE.md
β¦e to controller.settings
Closes Phase 26 D-04 + D-06 + D-08:
- DELETED src/phaze/tasks/worker.py (115 lines) -- replaced by phaze.tasks.controller
(fileless, control role) and phaze.tasks.agent_worker (file-bound, agent role).
- DELETED src/phaze/tasks/session.py (5 lines) -- the legacy v1.0 session-helper stub;
both new SAQ settings modules build their own session pool in their startup hooks.
- Updated docker-compose.yml worker service:
command: uv run saq phaze.tasks.controller.settings (was: phaze.tasks.worker.settings)
environment: + PHAZE_ROLE=control
depends_on: dropped audfprint + panako (controller is fileless per D-04)
- Updated test references that imported from phaze.tasks.worker / phaze.tasks.session:
- tests/test_tasks/test_worker.py DELETED (covered by test_controller_startup_banner.py)
- tests/test_tasks/test_session.py DELETED (covered by test_task_split.py D-25 invariant)
- tests/test_tasks/test_pool.py stripped worker.startup/shutdown tests (3); kept
pool-helper tests (2) which still exercise
phaze.tasks.pool (used by agent_worker).
- tests/test_tasks/test_proposal.py retargeted test_worker_* -> test_controller_*
- tests/test_tasks/test_tracklist.py retargeted test_worker_* -> test_controller_*
- tests/test_phase04_gaps.py models-dir checks retargeted to agent_worker
(the new owner per D-04); docker-compose-command
assertion now expects controller.settings.
- Logged D-3 in deferred-items.md: agent_task_router + agent_tracklists tests need a live
Redis; pre-existing flakiness independent of Plan 26-13.
Acceptance: `uv run pytest tests/test_task_split.py tests/test_tasks/ tests/test_phase04_gaps.py
-x --no-cov` => 62 passed; `uv run mypy src/` clean; `uv run ruff check .` clean;
`docker compose config -q` exits 0.
β¦roller Per Phase 26 D-02 / D-33, replaces every forward-looking reference to `phaze.tasks.lux_worker` (the original roadmap name that leaked the application-server hostname "lux") with `phaze.tasks.controller`. The new name pairs cleanly with `PHAZE_ROLE=control` and reads naturally without betraying physical topology. Sweep targets defined by D-33 (5 files): - .planning/PROJECT.md β milestone v4.0 task-code-reorg bullet (1 hit) - .planning/ROADMAP.md β Phase 26 plan-13 line + Phase 29 success-criterion #1 (2 hits) - .planning/REQUIREMENTS.md β clean (already used `controller` / `agent_worker`) - .planning/STATE.md β clean (no historical lux_worker references) - .planning/phases/25-internal-agent-http-api-bearer-auth/25-CONTEXT.md β clean Historical records preserved (intentionally untouched): - .planning/phases/26-β¦/26-13-PLAN.md - .planning/phases/26-β¦/26-10-SUMMARY.md - .planning/phases/26-β¦/26-CONTEXT.md - .planning/phases/26-β¦/26-DISCUSSION-LOG.md These document what was planned and decided at the time and are part of the audit trail; per Plan 26-13 Task 2 explicit guidance, historical SUMMARY / CONTEXT / DISCUSSION-LOG files are not rewritten. Verification: $ grep -rn 'lux_worker' .planning/ROADMAP.md .planning/REQUIREMENTS.md \ .planning/STATE.md .planning/PROJECT.md \ .planning/phases/25-internal-agent-http-api-bearer-auth/25-CONTEXT.md # β 0 matches
β¦ROADMAP Plan 26-13 final metadata commit: - Adds .planning/phases/26-β¦/26-13-SUMMARY.md (closing housekeeping plan summary; deletions + docker-compose + doc sweep details; deviations + threat-flag scan) - STATE.md advanced to Plan 13/13 (100% v4.0-phase-26 progress); decision recorded - ROADMAP.md updated by roadmap.update-plan-progress: Phase 26 marked complete (13/13 plans), aligned with the SUMMARY artefacts on disk Phase 26 (Task Code Reorg & HTTP-Backed Agent Worker) is ready for verifier sweep + merge. The next milestone-v4.0 phase (Phase 27: Watcher Service & User-Initiated Scan) can plan against a clean controller/agent_worker split.
β¦es, lux_workerβcontroller doc sweep (Wave 6)
β¦rage gaps Verification: All 5 success criteria PASS via goal-backward analysis (task split, role-driven startup, HTTP-only file-bound tasks, per-agent queue isolation, self-contained payloads). 26-VERIFICATION.md records evidence. Nyquist audit: NEEDS GAPS FILLED β 26-NYQUIST.md identifies: - GAP-1 (critical): tests/test_config_role_split.py missing β AgentSettings validators + get_settings() PHAZE_ROLE dispatch untested - GAP-2 (high): tests/test_services/test_agent_client_endpoints.py missing β 5 client methods (create_tracklist, patch_proposal_state, execution_log POST+PATCH, heartbeat) have no respx tests - GAP-3 (medium): bearer-token-never-logged invariant has no caplog assertion in PhazeAgentClient WARNING path Recommendation: address via /gsd:validate-phase before shipping. Pre-existing infrastructure issues (D-1 DB fixture flakiness, D-2 enum-type race, D-3 Redis daemon requirement) are logged in deferred-items.md and out of scope.
Cover 7 behaviors of get_settings() dispatch and AgentSettings fail-fast validators: role dispatch to AgentSettings/ControlSettings, missing PHAZE_AGENT_API_URL/TOKEN/SCAN_ROOTS each raise, and comma-split produces correct list. No DB or Redis required. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Five respx happy-path tests covering Phase-26-new methods: create_tracklist, patch_proposal_state, post_execution_log, patch_execution_log, and heartbeat. Verifies URL construction, payload serialization (including exclude_unset=True on PATCH methods), and response model type for each endpoint. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds one caplog test to test_agent_client.py that mocks a 500 response, captures WARNING-level logs from PhazeAgentClient._request(), and asserts the token string does not appear β enforcing the D-13 invariant on the HTTP-client warning path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
β¦AP-4) Adds one test where original_path="/etc/shadow" (outside scan_root) while proposed_path is inside the scan root. Verifies that _resolve_and_check_containment rejects the operation (error_count==1) and leaves the proposed destination uncreated β confirming the guard covers both paths, not just proposed_path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
β¦tup (GAP-5) Adds a second test to test_agent_startup_banner.py where PHAZE_AGENT_QUEUE is set to 'phaze-agent-wrong-id' while whoami returns agent_id='correct-id'. Asserts startup() raises RuntimeError matching 'queue/token mismatch', exercising the Pitfall 1 anti-misconfiguration guard at agent_worker.py:133-142. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Audit trail appended to 26-VALIDATION.md documenting the 5 gap closures (GAP-1 critical through GAP-5 low) added in commits ac6e3b0..9179907. 15 new test assertions across 2 new files (test_config_role_split.py, test_agent_client_endpoints.py) + 3 existing-file edits. Phase 26 is now nyquist_compliant. All DIST-03, TASK-01/02/03, OPS-01 requirements have automated verification.
Phase 26 introduced 11 integration tests (test_agent_task_router.py, test_agent_tracklists.py) that require live Redis at PHAZE_REDIS_URL. SAQ Queue.from_url is not compatible with fakeredis at saq>=0.26.3, so these tests have no fakeredis fallback and were failing in CI. Adds redis:7-alpine service container and PHAZE_REDIS_URL env, mirroring the existing postgres service pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Reportβ Patch coverage is π’ Thoughts on this report? Let us know! |
Codecov flagged 68 lines missing in the Phase 26 patch. Adds 19 targeted tests across 8 files; per-file patch coverage rises sharply: - agent_worker.py: 68.29% β 97.56% (whoami retry exhaustion, role mismatch, shutdown cleanup, module-import RuntimeError on missing PHAZE_AGENT_QUEUE) - agent_client.py: 86.41% β 97.53% (upsert_files / put_metadata / put_fingerprint happy paths) - execution.py: 89.74% β 100% (4 best-effort log/PATCH failure paths) - controller.py: 78.12% β 100% (shutdown disposes engine + closes client) - agent_tracklists.py: 88.24% β 100% (409 concurrent-writer poll exhaustion) - functions.py: 88.46% β 100% (malformed mood/style prediction skips) - agent_files.py: 78.57% β 94.12% (non-music skip + enqueue failure swallow) Two log-content assertions replaced with mock-call assertions because caplog record propagation is fragile when other tests in the suite reconfigure root logger handlers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SimplicityGuy
added a commit
that referenced
this pull request
May 13, 2026
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 26: Task Code Reorg & HTTP-Backed Agent Worker
Goal: SAQ task code is cleanly split between the application server (fileless
phaze.tasks.controller) and agents (file-boundphaze.tasks.agent_worker), with role-driven startup and per-agent queues so the same Docker image runs both roles correctly. Three new internal-agent endpoints close the contract gap from Phase 25 so the full file-bound task surface can run on agents.Status: Verified β (5/5 truths, 5/5 requirements, Nyquist 5/5 gaps resolved)
This phase converts the SAQ worker into a role-driven dual-mode service.
PHAZE_ROLE=controlboots a fileless application-server worker (generate_proposals,match_tracklist_to_discogs,search_tracklist,scrape_and_store_tracklist,refresh_tracklistscron) with Postgres access;PHAZE_ROLE=agentboots an agent worker (process_file,extract_file_metadata,fingerprint_file,scan_live_set,execute_approved_batch) with an HTTP client, no Postgres driver, and a per-agentphaze-agent-<agent_id>SAQ queue. Four new internal-agent endpoints (GET /whoami,PUT /analysis/{file_id},POST /tracklists,PATCH /proposals/{id}/state) close the contract gap from Phase 25 so file-bound task bodies execute exclusively against the HTTP boundary with self-contained payloads. A subprocess import-boundary test enforces thatphaze.databaseandsqlalchemy.ext.asyncionever enter the agent import graph.Changes
Plan 01: Settings split + deps + enum extensions (Wave 1)
Adds
tenacityruntime dep +respxdev dep + mypy strict overrides; full rewrite ofphaze.configintroducingRole/BaseSettings/ControlSettings/AgentSettings/get_settings()with fail-fast validation. ExtendsProposalStatus(EXECUTED,FAILED) andFileState(MOVED,UNCHANGED).Key files:
pyproject.toml,src/phaze/config.py,src/phaze/models/proposal.py,src/phaze/models/file.pyPlan 02: PhazeAgentClient + error hierarchy + retries (Wave 2)
Single
httpx.AsyncClientwrapper with 10 endpoint methods, a 4-class exception hierarchy (AgentApiError/AgentApiAuthError/AgentApiClientError/AgentApiServerError), tenacityAsyncRetryingretry funnel (4xx never retried; 5xx +ConnectError/Timeoutretried up to 3 attempts), and respx contract tests. Bearer token lives only inside the client's headers, never as an instance attribute.Key files:
src/phaze/services/agent_client.py,tests/test_services/test_agent_client.pyPlan 03: Agent schemas package (Wave 2)
Five new Pydantic modules (
agent_identity,agent_analysis,agent_tracklists,agent_proposals,agent_tasks) covering router request/response models and five SAQ-job payload models (extra="forbid", nocurrent_pathin any payload β enforced by test).Key files:
src/phaze/schemas/agent_*.py,tests/test_schemas/test_agent_*.pyPlan 04: AgentTaskRouter (Wave 3)
Per-agent SAQ
Queuecache with lazy_queue_for(agent_id)lookup; two enqueue surfaces (byagent_id, byFileRecord); idempotentclose()lifecycle.Key files:
src/phaze/services/agent_task_router.py,tests/test_services/test_agent_task_router.pyPlan 05: GET /api/internal/agent/whoami (Wave 3)
Returns
AgentIdentity{agent_id, name, scan_roots, created_at}for the authenticated agent. Introduces the per-router smoke-app test pattern reused by Plans 06/07/08.Key files:
src/phaze/routers/agent_identity.py,tests/test_routers/test_agent_identity.pyPlan 06: PUT /api/internal/agent/analysis/{file_id} (Wave 3)
Idempotent upsert via
pg_insert(...).on_conflict_do_update; deterministic_summarize_dict_to_stringfor top-3 mood/style compaction; overflow funnel toAnalysisResult.featuresJSONB for wire fields without dedicated columns.Key files:
src/phaze/routers/agent_analysis.py,tests/test_routers/test_agent_analysis.pyPlan 07: POST /api/internal/agent/tracklists (Wave 3)
Stripe-style request-id idempotency via Redis
SET NX EX; three-path handling (fast-path cache / concurrent-writer poll β 409 / owner DB path); atomic multi-row write ofTracklist+TracklistVersion+ NTracklistTrackrows in a single commit.Key files:
src/phaze/routers/agent_tracklists.py,tests/test_routers/test_agent_tracklists.pyPlan 08: PATCH /api/internal/agent/proposals/{id}/state (Wave 3)
Table-driven state-machine transitions; cross-tenant guard returns 403 BEFORE state evaluation (prevents 409-vs-403 timing side-channel); joint
Proposal+FileRecordmutation in a singleawait session.commit(); same-state PATCH is a no-op with zero DB writes.Key files:
src/phaze/routers/agent_proposals.py,tests/test_routers/test_agent_proposals.pyPlan 09: phaze.tasks.controller SAQ settings (Wave 4)
Fileless settings module: 4 tasks +
refresh_tracklistscron; module-levelQueue("controller"); startup banner logsrole=control queue=controller(OPS-01 evidence); stashesctx["queue"]for proposal/execution rate-limit readers.Key files:
src/phaze/tasks/controller.py,tests/test_tasks/test_controller_settings.pyPlan 10: phaze.tasks.agent_worker SAQ settings (Wave 5)
File-bound settings module: bounded exponential
/whoamistartup probe (1sβ32s, β€63s budget); queue-name mismatch guard (token-derivedagent_idvsPHAZE_AGENT_QUEUE); subprocess import-boundary test enforcesphaze.database/sqlalchemy.ext.asyncioabsence from the agent import graph (D-25).Key files:
src/phaze/tasks/agent_worker.py,tests/test_task_split.py,tests/test_tasks/test_agent_startup_banner.pyPlan 11: Rewrite 5 file-bound task bodies over HTTP (Wave 4)
process_file,extract_file_metadata,fingerprint_file,scan_live_set,execute_approved_batchnow consume self-contained payloads viaPayload.model_validate(kwargs)and call out viactx["api_client"]. ExtractsExecutionStatusinto a new DB-freephaze.enumspackage so agent schemas load without SQLAlchemy.Key files:
src/phaze/tasks/functions.py,src/phaze/tasks/metadata_extraction.py,src/phaze/services/fingerprint.py,src/phaze/services/scan.py,src/phaze/services/execution.py,src/phaze/enums/Plan 12: FastAPI lifespan wiring (Wave 5)
Includes 4 new agent routers in
create_app(); lifespan constructsapp.state.task_router = AgentTaskRouter(redis_url=...)andapp.state.redis(decode_responses=True) and closes both on shutdown. Refactorsagent_files.upsert_filesto useapp.state.task_router.enqueue_for_agent(no more inlineQueueconstruction).Key files:
src/phaze/main.py,src/phaze/routers/agent_files.pyPlan 13: Closing housekeeping β delete legacy modules (Wave 6)
Deletes
src/phaze/tasks/worker.py(115 LOC legacy combined settings) andsrc/phaze/tasks/session.py(5 LOC deprecated stub). Rewiresdocker-compose.ymlworker service touv run saq phaze.tasks.controller.settings+PHAZE_ROLE=control; dropsaudfprint/panakofrom controllerdepends_on. Doc sweep replaceslux_workerhostname leak withcontrolleracrossPROJECT.md+ROADMAP.md(D-33).Key files:
docker-compose.yml,PROJECT.md,ROADMAP.md, deletions ofsrc/phaze/tasks/worker.py+src/phaze/tasks/session.pyRequirements Addressed
phaze-agent-<agent_id>; enqueuer routes viaFileRecord.agent_idasync_sessionPHAZE_ROLE={control,agent}selects SAQ settings module and startup resourcesVerification
tests/test_task_split.py): 1 passed β confirmsphaze.database/sqlalchemy.ext.asyncioabsent from agent import graphoriginal_pathescape inexecute_approved_batch, queue/token mismatch raises at agent startup)TODO/FIXME/PLACEHOLDER; noasync_sessionreachable from agent code pathstest_agent_task_router.py,test_agent_tracklists.py) skip β pre-existing infra gap (D-3), planned for CI Redis sidecar per D-30Key Decisions
PHAZE_ROLE(D-14):get_settings()returnsControlSettings()orAgentSettings()at construction time; back-compat aliasSettings = ControlSettingspreserved so existingfrom phaze.config import Settingscall sites keep type-checking until migratedhttpx.AsyncClient.headersAsyncRetryingasync-iterator (not@retrydecorator): cleaner integration for post-loop 4xx/5xx status-code mappingdict[str, Queue]cache, notfunctools.cacheor LRU: LRU eviction without.disconnect()would leak Redis connections; bounded growth not needed at v4.0's 1β5 agent scaleSET NX EXwith 1h TTL; concurrent-writer poll bounded at 10Γ50ms then 409featuresJSONB instead of being dropped β preserves D-26 wire contract without an Alembic migrationphaze.enumspackage:ExecutionStatusextracted DB-free so agent schemas load withoutsqlalchemy/phaze.databaseβ the D-03 import boundary holds for the agent worker/whoamistartup probe (1sβ32s, β€63s wall-clock):RuntimeErroron exhaustion; queue-name mismatch guard catchesPHAZE_AGENT_QUEUEvs token-derivedagent_idmisconfigπ€ Generated with Claude Code